```
├── .gitignore
├── .vscode/
├── settings.json
├── LICENSE (omitted)
├── README.md (300 tokens)
├── docx/
├── .vitepress/
├── config.mts (900 tokens)
├── about/
├── author/
├── index.md (100 tokens)
├── privacyPolicy/
├── index.md (300 tokens)
├── qqgroup.md
├── userAgreement/
├── index.md (500 tokens)
├── build_docx.sh
├── celebration/
├── helloLTY-1.md (100 tokens)
├── helloLTY-2.md (100 tokens)
├── index.md
├── develop/
├── index.md (500 tokens)
├── lyric_search/
├── index.md (1100 tokens)
├── help/
├── index.md (600 tokens)
├── issue.md (200 tokens)
├── list/
├── addSongsByPlaylist/
├── image-1.png
├── image.png
├── index.md
├── addSongsForPlaylist/
├── addSongToPlaylist.png
├── image-1.png
├── image-2.png
├── image.png
├── index.md (100 tokens)
├── pointToSongItemMenu.png
├── selectPlaylist.png
├── bindLyric/
├── image-1.png
├── image-2.png
├── image-3.png
├── image-4.png
├── image-5.png
├── image-6.png
├── image-7.png
├── image.png
├── index.md (100 tokens)
├── getMediaInfo/
├── index.md
├── lyricFormat/
├── index.md (100 tokens)
├── overlayLyric/
├── index.md
├── scanLocalSong/
├── image-1.png
├── image-2.png
├── image-3.png
├── image-4.png
├── image-5.png
├── image-6.png
├── image-7.png
├── image-8.png
├── image-9.png
├── image.png
├── index.md (300 tokens)
├── selectBranch.md (300 tokens)
├── songSheet/
├── index.md (100 tokens)
├── supportFormat.md (100 tokens)
├── platform/
├── car/
├── image-1.png
├── image-2.png
├── image-3.png
├── image-4.png
├── image.png
├── index.md (100 tokens)
├── tv/
├── image-1.png
├── image-2.png
├── image-3.png
├── image-4.png
├── image.png
├── index.md (100 tokens)
├── plugins/
├── bilibili/
├── image-1.png
├── image-2.png
├── image.png
├── index.md (100 tokens)
├── leadUpVideos/
├── index.md
├── leadVideos/
├── index.md
├── search/
├── image-3.png
├── image-4.png
├── index.md
├── star/
├── image-1.png
├── image-2.png
├── image.png
├── index.md (100 tokens)
├── image-1.png
├── image-2.png
├── image.png
├── index.md
├── share/
├── image-1.png
├── image-2.png
├── image-3.png
├── image.png
├── index.md (200 tokens)
├── webdav/
├── add/
├── image-1.png
├── image-2.png
├── image-3.png
├── image.png
├── index.md (100 tokens)
├── index.md (100 tokens)
├── question.md
├── user/
├── reciveEmailCode/
├── index.md
├── xxcodeSign/
├── image-1.png
├── image-2.png
├── image-3.png
├── image-4.png
├── image-5.png
├── image.png
├── index.md (100 tokens)
├── images/
├── coolight_s.jpg
├── logo-full.png
├── logo-tran.png
├── musicxx-glass-blue.png
├── musicxx-glass-dark.png
├── musicxx-glass-light.png
├── qqChannle.jpg
├── qqGroup.jpg
├── index.md (300 tokens)
├── info/
├── index.md (300 tokens)
├── musicPlusPlus/
├── index.md
├── package-lock.json (11.4k tokens)
├── package.json
├── download/
├── images/
├── MimicryMusic-full.png
├── MimicryMusic-s.png
├── android.png
├── apple.png
├── gravatar.jpg
├── linux.png
├── logo-tran.png
├── musicxx-glass-blue.png
├── musicxx-glass-dark.png
├── musicxx-glass-light.png
├── publicbeian.png
├── windows.png
├── index.html (3.2k tokens)
├── run.js (2.1k tokens)
├── style.css
├── oauth/
├── baidupan/
├── login_success/
├── images/
├── MimicryMusic-s.png
├── publicbeian.png
├── index.html (1000 tokens)
├── run.js (300 tokens)
├── res/
├── image/
├── image-2.png
├── image-3.png
├── image-4.png
├── image-5.png
├── image.png
├── run.sh
├── server-cxx/
├── .clang-format (1300 tokens)
├── .gitattributes (500 tokens)
├── .gitignore (1300 tokens)
├── CMakeLists.txt (100 tokens)
├── CMakeSettings.json (400 tokens)
├── LICENSE.txt (200 tokens)
├── README.md (500 tokens)
├── recoding_utf8.py (200 tokens)
├── resources/
├── html/
├── default/
├── 200.html (100 tokens)
├── 403.html (100 tokens)
├── 404.html (100 tokens)
├── 405.html (100 tokens)
├── EmailCode.html (900 tokens)
├── src/
├── CMakeLists.txt (100 tokens)
├── myMusic/
├── CMakeLists.txt (200 tokens)
├── main.cpp (100 tokens)
├── mymServiceWorkBase/
├── MyMServiceWorkBase.cpp (200 tokens)
├── MyMServiceWorkBase.h (1400 tokens)
├── MyMServiceWorkDBBase.cpp
├── MyMServiceWorkDBBase.h (100 tokens)
├── test/
├── test.cpp
├── test.h
├── vlog.md (800 tokens)
├── myServer/
├── CMakeLists.txt (400 tokens)
├── Cookie.cpp (700 tokens)
├── Cookie.h (400 tokens)
├── GlobalUtil.cpp (3k tokens)
├── GlobalUtil.h (1100 tokens)
├── MyBaseEntity.h (500 tokens)
├── MyDllHelper.cpp (500 tokens)
├── MyDllHelper.h (100 tokens)
├── MyFactory.cpp (200 tokens)
├── MyFactory.h (200 tokens)
├── MyHtml.cpp (300 tokens)
├── MyHtml.h (300 tokens)
├── MyHttpFile.cpp (1800 tokens)
├── MyHttpFile.h (400 tokens)
├── MyHttpMethod.cpp (500 tokens)
├── MyHttpMethod.h (400 tokens)
├── MyHttpServer.cpp (2.1k tokens)
├── MyHttpServer.h (700 tokens)
├── MyHttpState.h (400 tokens)
├── MyHttpTask.cpp (3.9k tokens)
├── MyHttpTask.h (1400 tokens)
├── MyLimit.h (1600 tokens)
├── MyLoadState.cpp (200 tokens)
├── MyLoadState.h (300 tokens)
├── MyLog.cpp (500 tokens)
├── MyLog.h (600 tokens)
├── MyPromise.h (300 tokens)
├── MyProxy.cpp (400 tokens)
├── MyProxy.h (100 tokens)
├── MyRouter.cpp (600 tokens)
├── MyRouter.h (1600 tokens)
├── MySqlBase.h (1100 tokens)
├── MySqlTask.h (2000 tokens)
├── SqlString.h (1000 tokens)
├── TgNullable.h (800 tokens)
├── vlog.md (900 tokens)
├── myTest/
├── CMakeLists.txt (100 tokens)
├── main.cpp
```
## /.gitignore
```gitignore path="/.gitignore"
docx/node_modules/
docx/.vitepress/cache/
docx/.vitepress/dist/
docx/build
build/
```
## /.vscode/settings.json
```json path="/.vscode/settings.json"
{
"editor.formatOnSave": true,
"liveServer.settings.port": 5501,
"liveServer.settings.proxy": {
"enable": false,
"baseUri": "/api",
"proxyUri": "https://download.music.mimicry.cool/api/"
}
}
```
## /README.md
# Musicxx 拟声
* [官网](https://blog.mimicry.cool/)
* [下载官网](https://download.music.mimicry.cool/)
* [GitHub](https://github.com/coolight7/MimicryMusic)
## 简介
* 新拟物风格的 音视频播放器
* 后端:
* c++,基于搜狗开源的 [workflow](https://github.com/sogou/workflow) 开发
* workflow 的任务流设计能把回调也变得简单易懂,c++中不可多得的异步框架,跨平台;支持 http、rpc、mysql 等网络开发和计算任务调度,你值得拥有!
* 客户端:
* flutter
* 支持[安卓]、[iPhone]、[windows]、[mac]、[linux];允许多端同时登录账号,并将自动同步 歌单、歌词、webdav连接 等
* 支持播放 bili 歌曲、本地歌曲、音乐文件链接、webdav、阿里云盘、百度云盘、115云盘,拥有内置音乐云盘
* 支持播放绝大多数音视频格式;支持视频MV、Anime4K 实时画质提升、逐帧播放
* 支持`歌词弹幕、状态栏歌词、桌面歌词`,高容错的歌词解析能力,支持歌词制作、显示翻译歌词等
* 新拟物风格主题,并支持 跟随系统、跟随时间 切换;支持自定义背景图、天气背景、动态渐变色
* 丰富的自定义功能:真/伪随机播放、自定义App启动位置、启动时自动播放、方形封面、网格布局
* `共享与控制`:可在多个设备之间共享本地、云盘歌曲,并远程控制
* `同步歌单音源`:自动拉取绑定的本地歌曲/webdav/云盘音源,更新添加到歌单中
* `智能识别`:自动从bili标题中识别歌曲标题、歌手,并支持歌手、专辑分类时自动关联简写名、繁简体、错拼名等
## 预览图
| - | - | - | - | - |
| -------------------------------- | ---------------------------------- | ---------------------------------- | ---------------------------------- | ---------------------------------- |
|  |  |  |  |  |
## 支持平台:
- android (安卓4.1及以上,arm64-v8a/armeabi-v7a/x86_64) 手机、车机、电视、平板
- iPhone(ios15以以上,arm64)
- windows (Win10及以上,x86_64)
- macos(Macos11及以上,arm64/x86_64)
- linux(x86_64)
## /docx/.vitepress/config.mts
```mts path="/docx/.vitepress/config.mts"
import { defineConfig } from 'vitepress'
// https://vitepress.dev/reference/site-config
export default defineConfig({
lang: 'zh-Hans',
title: "拟声",
head: [
["meta", { name: "keywords", content: "拟声,拟声app,音乐,歌词弹幕,状态栏歌词,桌面歌词,悬浮歌词,共享与控制,下载,新拟物,音乐云盘,阿里云盘,百度云盘,webdav,coolight,musicxx,mimicrymusic,mymusic,download,music,lyric,mediaplayer,app,android,ios,windows,macos,linux" }]
],
description: "拟物风音视频播放器",
appearance: "dark",
lastUpdated: true,
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: '主页', link: '/' },
{ text: '下载', link: 'https://download.music.mimicry.cool/' },
{ text: '了解拟声', link: '/info/' },
{ text: '使用帮助', link: '/help/' },
{ text: '关于作者', link: '/about/author/' },
],
sidebar: [
{
text: '目录',
items: [
{ text: '下载', link: 'https://download.music.mimicry.cool/' },
{ text: '了解拟声', link: '/info/' },
{ text: '使用帮助', link: '/help/' },
{ text: '拟声++', link: '/musicPlusPlus/' },
{ text: '开发记录', link: '/develop/' },
]
}
],
footer: {
message: `
<a target="_blank" href="https://beian.miit.gov.cn/">粤ICP备2021172577号</a>
<a style="display: flex;flex-direction: row;justify-content: center;" target="_blank" href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=44011302003939">
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAFOklEQVR42k3UaUzTZwDH8UfmNqPG6Oamc04lM/OYZs4DxQw85vBGBQ/KiiAqiop4C4yhAtPMA8VbZ2SbJo7JVFSYKCAooCCoUCkF5JCWo7S0lZa2XH7X8QJ48fvn/yRPPs/veZ7kEYCgydSR9iazwGQS5haFAIMA1eCmsjt+d0+HPfvnSEh+Q37sFigaCs3CarUK9HrBW4MtjR0BRMenpckgWsx60WbUCZqxJX1Wya2fYmM2rrBc9p7AAY/pRPov4MrPblz0l7bK48Ju0Pxy0TuzQbTrVaLNUG2LqgvUmpVC11QhWtsRYOmVcmI10z6dyikfBzI2T6La50u028dTluhP3J87mf6RI0lHPADjh8amamEyxAuLKb4b2FwtDNYGAdqh1uch6a5TlhAdIsEYOhpZz14UOI6g2HUsz+160BrjQuzdaJZPc+ed4vhtKOtltqYIuNcFWhsVopXyMW/ubGx2GLKY1RI/2qLGUWA/hEL7wbwYN4TiuaN5MWgA6X37QKoPm3dGMnvkHOofh9Roi4+Nkidv6gLRvBBYzk05fXQ3Qhzg2jEJhtXDePVeH+TfDkc+ZBD5fQfwatpIsoUdGu8xpNwPQ4itHDu4F2tJ+AR56sUu0CB/MLT94fLHJ4+FIXruJWH/DKpchlEsPkA+fBBFTvYUzbC3/X/GCyEoch6BIncvokcgoSHBoD+Y+q5Fa98J5t/YE2i56siVC5EIEcbdCx6Ywx3IE++TLRnLq7NOKNOWURk1l/xPetOwfTJ5j7Z3NDxzIgJkK9GW3Y7oBEvvHzqvPjeZzBuhiF7RbPX1pSVjDvcXjyTL+QsKN06kePd0SgOmUnTYBarWErzOByGCyUw5hOnWTFTpx2M6weK/AqpLD07jbU4gQ53P0fP9AB4sGk19uBOF88by7P8te09CNvMrVIEOyNY70eeDDQya8CuNiiAqzzhScXNDaVfDON+m50HTIWE+O3453bFyouhP+9e9KVr7DZluIyncPZXa8Bm0jx/IQzEAITbhHxYFme7IDjmhTPav7ARVifvjy2Jm05a7DlnyIcTnUSwSjsT360fG4N5kjfqYXFtKFo4h13k8/nYOiP4h5CQG0VoQiPL2chryDp7pBoY/eR09i6rYVVDgR+TxCx23LXr4IcQaXPu54jNsKX2FF0JssCWIHSHhUOJFdYIXyr8XoMkOzugE67JCWxWXPSiKdkd5yonGZwHI04I4vVfKluDD3L3iR16cG1LvIFa6BrJzTzgNj3yp+92J0t9+QHlzIfrifdpO0GrM9qxOduP11UUUnphL4a7RWDM8oNQHFItpy1yG4eY8eOkJ+gCQSSiPmGib68Kb227U50hp1iU4d4KAaDW9lKqfrjbUpK2gIMIJedR36PLWo3kgpSZ+GXUPJBjy1vFW5k/5BRdKzi5AnROA6lGA1qi+Puddc1K358tcKd5WPJn15taGWvUTKerMNdQkrkSTtgpNphfq9DXoc9ZQn+JBxR9zqbknQfdqG7UZfry+JtE0VMTMMVu6gc2GVGGqOHLfUrULnWI3VYneqO54ok7xRHlrKeo0b9T/ulF+/nsqbcdSk+RJja25Nm8jRtUOjOqT1026biDGXNHamNa7XhFxSpUsMdUlLaExeyW6VBsWvxCtbVx3Y35HY91TT+oeulKf40tD+f43+roroRbjPTuL6Wo3sPGJaLNkiNeZUUJ2atvAnMgfvR7vc75UFuueVXppWYEyzl1VmyBVlV+TZtUmb0nWFJw/WnwzfN7L69vtNPVJwmpOFhbjpQ7rPyXTHALIQZVcAAAAAElFTkSuQmCC"
style="width:20px;height:20px;margin-right:10px;">
<span >粤公网安备 44011302003939号</span>
</a>
`,
copyright: `版权所有 © 2022-${new Date().getFullYear()} <a href="https://github.com/coolight7">『coolight • 郑泳坤』</a> `
},
socialLinks: [
{ icon: 'github', link: 'https://github.com/coolight7/musicxx' }
],
notFound: {
title: "页面未找到",
quote: "这可能是因为文章迁移,您可以通过本页面左上角的搜索框查找相关内容。",
linkLabel: "回到主页",
linkText: "回到主页",
},
search: {
provider: 'local',
options: {
translations: {
button: {
buttonText: '搜索文档',
buttonAriaLabel: '搜索文档'
},
modal: {
resetButtonTitle: '清除查询条件',
backButtonTitle: '取消',
noResultsText: "无搜索结果",
footer: {
selectText: '选择',
navigateText: '切换',
closeText: '关闭',
},
}
}
}
},
outlineTitle: "本页",
lastUpdatedText: "最后更新",
docFooter: {
prev: '上一页',
next: '下一页'
},
outline: {
label: '本页导航'
},
langMenuLabel: '多语言',
returnToTopLabel: '回到顶部',
sidebarMenuLabel: '菜单',
darkModeSwitchLabel: '主题',
lightModeSwitchTitle: '切换到浅色模式',
darkModeSwitchTitle: '切换到深色模式',
}
})
```
## /docx/about/author/index.md
# 关于作者
<img src="/images/coolight_s.jpg" width="300" height="300">
- coolight • 郑泳坤
## 联系作者
- 邮箱:2465045051@qq.com
- Bilibili: [流明风P](https://space.bilibili.com/93784977)
- Github:[coolight7](https://github.com/coolight7)
- 博客:[猫薄荷](https://blog.coolight.cool/)
## /docx/about/privacyPolicy/index.md
# 拟声(musicxx)隐私政策
欢迎使用由 `coolight/boolxx`(以下简称 “我们”)开发的 `拟声/musicxx`(以下简称 “本软件”)音乐软件!我们深知隐私对您的重要性,因此制定了本隐私政策,向您说明我们在您使用软件过程中如何收集、使用、存储、共享和保护您的个人信息。请您仔细阅读本隐私政策,在使用我们的软件前确保您已充分理解并同意本政策的内容。如果您不同意本隐私政策的任何条款,您可以选择不使用我们的软件。
## 一、收集的个人信息
- 设备信息:我们会收集您使用软件时的设备型号、操作系统版本、设备标识符(如 IMEI、MAC 地址等)、屏幕分辨率等信息,以帮助我们优化软件性能,适配不同设备,确保软件在您的设备上稳定运行。
- 账号信息:若您注册账号使用软件,我们会收集您的注册邮箱、手机号码、设置的用户名和密码等信息,用于账号的创建、登录验证以及账号安全保护。
- 使用数据:我们会记录您在软件中的操作行为,如播放历史、收藏的歌曲、创建的歌单、搜索记录等,以此分析您的音乐偏好,为您提供更符合您口味的音乐推荐和个性化服务。
- 音视频文件信息:由于软件支持播放本地或网络上的音视频文件,我们会获取文件的名称、格式、时长、创建时间等基本信息,方便您在软件中管理和播放本地媒体文件。
## 二、信息的使用
- 提供服务:使用收集到的信息为您提供软件的各项功能,包括音乐播放、搜索、下载、创建歌单等。
- 改善服务:分析收集的数据,了解用户使用习惯和需求,优化软件功能和界面设计,提升用户体验。
- 公开共享:通过共享算法,歌单、歌词等数据可能会被本软件公开共享给其他用户,方便用户间互相共享数据。
- 安全保障:利用收集的信息进行账号安全管理,防范和处理安全风险,如识别异常登录行为,防止账号被盗用。
## 三、信息的存储
- 我们会将收集到的个人信息存储在安全的服务器上,存储期限将根据法律法规的要求以及实现本隐私政策所述目的所需的期限确定。超出存储期限后,我们将对信息进行删除或匿名化处理。
- 在数据存储过程中,我们采取加密、访问控制等安全措施,防止信息的泄露、丢失、篡改和未经授权的访问。
## 四、信息的共享与披露
- 共享原则:我们不会将您的个人信息出售给第三方,仅在以下情况下与第三方共享您的个人信息:
- 获得您的明确同意。
- 为实现软件功能或提供服务,需要与第三方合作伙伴(如云服务提供商、数据分析服务商等)共享,且这些第三方需遵守严格的保密义务和数据安全措施。
- 根据法律法规的要求或司法机关、行政机关的要求,需要向相关机构披露。
- 披露情况:在涉及合并、收购、破产清算等交易时,我们可能会将您的个人信息作为交易的一部分进行披露,但我们会要求新的所有者继续遵守本隐私政策。
## 五、用户权利
- 访问权:您有权访问我们收集的关于您的个人信息,了解信息的使用情况。
- 更正权:如果您发现我们收集的关于您的个人信息存在错误或不准确,您有权要求我们进行更正。
- 删除权:在符合法律法规规定的情况下,您有权要求我们删除您的个人信息。
- 撤回同意权:您可以随时撤回对我们收集和使用您个人信息的同意,但撤回同意可能会影响软件部分功能的正常使用。
## 六、隐私政策的变更
- 我们会根据法律法规的变化、业务发展和技术进步等情况,适时对本隐私政策进行更新。更新后的隐私政策将在软件官网或软件内显著位置公布,若您在政策更新后继续使用软件,即视为您已同意接受更新后的隐私政策约束。
## 七、联系我们
- 如果您对本隐私政策有任何疑问、意见或建议,或者您需要行使您的用户权利,请[点击这里联系我们](/about/author/)
- 我们将在收到您的反馈后,尽快与您沟通并解决问题。
## /docx/about/qqgroup.md
## 拟声内测交流Q群
- QQ群号:`364471801`
<img src="/images/qqGroup.jpg" width="300" height="300">
## 拟声QQ频道
<img src="/images/qqChannle.jpg" width="300" height="300">
## /docx/about/userAgreement/index.md
# 拟声(musicxx)用户协议
- 欢迎您使用由 `coolight/boolxx`(以下简称 “我们”)开发的 `拟声/musicxx`(以下简称 “本软件”)音视频软件!在使用本软件之前,请您仔细阅读并充分理解本用户协议(以下简称 “本协议”)的全部内容。您在注册账号、软件内点击 “同意”、“下一步” 或实际使用本软件,即视为您已阅读、理解并同意接受本协议的约束。如果您不同意本协议的任何条款,请不要使用本软件。
## 一、软件概述
- `拟声(musicxx)`是一款功能丰富的音视频软件,支持 Windows、Android、macOS、Linux、iOS 等多平台使用,为用户提供便捷的音乐播放体验。
- 本软件竭力希望打造一款方便用户跨平台同步和使用的音视频软件,并创新性地开发了 `歌词弹幕`、`共享与控制`、`音源转换`、`优化的索引栏`、`独特的UI` 等,如果您有一些建议或发现了该软件的 Bug,欢迎[联系我们](/about/author/)!
## 二、权限获取说明
- 网络权限:本软件需要访问网络,以实现音乐搜索、在线播放、歌曲下载、歌词获取、软件更新等功能。
- 本地音视频文件访问权限:为方便您播放本地存储的音乐和视频文件,软件需要获取访问您本地音视频文件的权限。
- 悬浮窗权限:为了向您提供悬浮歌词(包括歌词弹幕、桌面歌词、状态栏歌词)功能,软件需要获取悬浮窗权限,以便在其他应用界面之上显示歌词。
- 自启动权限:在车机、电视等设备上,为实现开机自启动功能,方便您快速使用软件,软件需要获取自启动权限。
- 通知权限:为了在系统通知栏显示音乐通知,如播放状态、歌曲切换等信息,软件需要获取通知权限。
- 屏幕常亮权限:当您在播放页面时,为避免屏幕自动熄灭影响您的使用体验,软件需要获取屏幕常亮权限。
- 震动权限:在移动端设备上,为了在部分操作时给予您震动反馈,如切换歌曲、暂停播放等,软件需要获取震动权限。
## 三、软件使用规则
- 合法使用:您应遵守所有适用的法律法规,不得利用本软件进行任何违法违规活动。
- 禁止恶意行为:不得对软件进行反向工程、反编译或以其他方式试图获取软件的源代码;不得对软件进行修改、篡改,以破坏软件的正常功能或影响其他用户的使用;不得利用软件漏洞进行恶意攻击、破坏或窃取数据。
- 内容使用:通过本软件获取的音乐、歌词等内容,仅用于个人欣赏和使用,未经版权所有者书面许可,不得进行复制、传播、修改、用于商业用途等任何侵犯版权的行为。
- 插件使用:插件是一个开放的平台,插件功能和开发独立于本软件之外,您在安装、使用插件前应当自行仔细甄别即将使用的插件的合法性、安全性等,您在使用插件时的违法违规行为、受到的损失和数据丢失等,我们不承担责任。
- 歌词共享和自动匹配:本软件会在不同用户间互相共享歌词,共同营造一个良好的共享环境。默认情况下,您创建的歌词会公开共享,其他用户也可以搜索查看或使用。播放歌曲时,本软件会自动读取歌曲的内嵌歌词和外置歌词,并自动上传到本软件的云服务端存储,本软件将用于自动匹配歌词和允许其他用户搜索、查看、使用该歌词。因此,如果您使用本软件创建歌词、播放歌曲时,视为同意上传、允许本软件的云服务端存储和使用、允许其他用户的查看和使用。
## 四、隐私保护
- 我们尊重并保护您的隐私,仅会按照本软件的[隐私政策](/about/privacyPolicy/)收集、使用和披露您的个人信息。您可以在软件官网或软件内查看详细的隐私政策。
我们会采取合理的安全措施保护您的个人信息,但由于网络环境的复杂性,无法保证信息的绝对安全。
## 五、信用评估
- 本软件包含信用评估功能,信用会关联用户登录的账号、绑定的第三方账号、设备和网络环境。
- 信用用于评定是否允许使用软件功能、参与活动等,请保持良好信用,本软件会降低信用不良的用户的使用体验,甚至拒绝服务,以此节省出更多资源为信用良好的用户提供服务。
## 六、账号封禁与注销
- 本软件的云服务端会存储您的账号数据。若您存在违法违规行为,我们有权封禁您的账号、删除您的数据!请合法合规地使用本软件。
- 注意,账号封禁/注销后,我们开发的其他软件也将无法登录(`拟声`、`流明AI`)!
- 如果您希望注销账号,可通过以下流程申请注销:
- 使用即将注销的本软件账号绑定的邮箱向[我们](/about/author/)发送邮件,明确说明希望注销本软件账号,如:
```txt
希望注销拟声/流明账号(UID: xxx; 邮箱: xxx)
```
- 请注意:
- 注销账号操作无法回退和恢复!
- 注销账号不会解除其绑定的邮箱、QQ等,因此不能用被注销的账号绑定的邮箱、QQ重新注册或绑定本软件账号。
- 我们会删除已注销的账号的部分数据,但仍会保留部分数据,以便记录操作、违法违规审查等。
## 七、责任限制
- 我们尽力确保软件的正常运行和功能的实现,但对于因软件故障、网络问题、第三方原因等导致的软件无法使用、数据丢失、播放异常等情况,我们不承担责任。
- 对于您因使用本软件而遭受的任何直接、间接、偶然、特殊或惩罚性的损失,包括但不限于商业利润损失、数据丢失、业务中断等,我们不承担责任。
## 八、协议变更
- 我们有权根据法律法规的变化、软件功能的调整等情况,对本协议进行变更。
- 变更后的协议将在软件官网或软件内公布,您继续使用本软件即视为同意变更后的协议。
## 九、争议解决
- 如您与我们就本协议或软件使用发生争议,双方应首先友好协商解决;协商不成的,任何一方均有权向有管辖权的人民法院提起诉讼。
## 十、其他条款
- 本协议构成您与我们之间关于软件使用的完整协议,取代之前所有关于本软件的口头或书面协议。
- 本协议的无效、部分无效或不可执行,不影响其他条款的效力和可执行性。
## /docx/build_docx.sh
```sh path="/docx/build_docx.sh"
npm run docs:build
rm -rf build/
mkdir build
mv .vitepress/dist/* build/
mkdir build/images/
cp -r images/ build/
```
## /docx/celebration/helloLTY-1.md
## 洛天依生日庆典『十二 • 壹』
- 2024.07.12~2024.07.31(已结束)
- [洛天依生日主题页](https://www.bilibili.com/blackboard/era/Lou2024.html)
- 参与活动可获得拟声插件『欢迎光临』
### 活动要求
- 拟声账号信用评估在`良好`或以上的用户可参与。
- 完成[活动内容](#活动内容)即可领取奖励。
- **自助诚信操作**,您在完成活动后需要自行返回拟声App内领取奖励。
### 活动内容
1. 为`洛天依`发布的一个[B站视频](https://space.bilibili.com/36081646)送出**点赞并投币**
2. 为`洛天依12周年`的一个[B站相关作品](https://www.bilibili.com/v/topic/detail/?topic_id=1190319&topic_name=%E6%B4%9B%E5%A4%A9%E4%BE%9D%E5%8D%81%E4%BA%8C%E5%91%A8%E5%B9%B4)送出**点赞并投币**
### 领取奖励
- 请回到`拟声`内的活动页面领取。
## /docx/celebration/helloLTY-2.md
## 洛天依生日庆典『十三 • 壹』
- 2025.07.12~2025.07.31
- [洛天依生日主题页](https://www.bilibili.com/blackboard/era/Producer2025.html)
- 参与活动可获得拟声插件『滚动弹幕』
### 活动要求
- 拟声账号信用评估在`良好`或以上的用户可参与。
- 完成[活动内容](#活动内容)即可领取奖励。
- **自助诚信操作**,完成活动后需要自行返回拟声App内领取奖励。
### 活动内容
1. 为`洛天依`的[B站视频](https://space.bilibili.com/36081646)**点赞并投币**
2. 为`洛天依13周年`的一个[B站相关作品](https://www.bilibili.com/v/topic/detail?topic_id=1308528&topic_name=%E9%97%AA%E8%80%80%E7%9A%84Producer)**点赞并投币**
### 领取奖励
- 请回到`拟声`内的活动页面领取。
## /docx/celebration/index.md
# 活动
- 拟声会不定时发布活动,敬请期待!
## /docx/develop/index.md
# 开发记录
- 本页主要记录了一些开发时的技术选择、问题探讨和解决,让其他开发者少走弯路少踩坑,一起进步~
## API接口
- [拟声云歌词匹配](./lyric_search/)
## 拟声的开发框架
- **后端**:`c++/go`;绝大部分是c++,实际上用c++开发并不舒服,仅仅是作者出于学习目的,一开始喜欢写c++才基于搜狗的`workflow`搓了一些库并拿它开发了后续的拟声服务端
- **客户端**:`flutter`;用flutter的目的就是跨平台,说实话开发体验还不错,dart和c系语言非常像,写起来很舒服。部分核心功能采用 c++ 实现,跨平台省心省事,整体性能和效果够不错
- **前端**;拟声最初是用vue前端开发的,后来由于前端在手机上表现不佳,于是着力开发了客户端
## 播放组件的选择
- 拟声目前主要使用的是`media_kit`,在此之前我们尝试过`just_audio`、`audioplayer`、`assets_audio_player`,他们体积很小,但都不尽人意,支持的格式并不丰富,而后刚好看到`media_kit`发布,试了下感觉着实不错,尽管它也有不少小问题,但基于`libmpv`和`ffmpeg`的播放组件能支持的格式和功能相当离谱
## mediaxx
- 之前用了 ffmpeg_kit 实现读取音频信息、内嵌封面、歌词等,25年初它已经宣布不再更新了,另外 media_kit 的 libmpv 也依赖了 ffmpeg,相当于app里打包了两份 ffmpeg 的动态库,我们也尝试过合并 so 库使用,但发现他们的日志会互相冲突
- 25年底,我们开发的 流明 也很依赖 ffmpeg_kit 和 libmpv,由于编解码都需要,此时程序包体积很大,下定决心舍弃 ffmpeg_kit,开发了 `mediaxx`,仍是引用 ffmpeg 的能力,实现读取音频信息、封面、频谱分析,以及图片颜色分析。经过大量的摸索,mediaxx 依赖的 ffmpeg 实现与 libmpv 共用,也可以将 mediaxx、ffmpeg、libmpv 全部编译到一起,合并成一个动态库,并优化编译配置、添加了符号表限制导出符号,让链接器更好删除未使用的代码段,大幅缩减了安装包体积。
- 流明从 140MB 缩减到 50 MB,拟声win端从 60+MB 缩减到 40 MB,而且是引入的 libmpv、ffmpeg 添加了更多的 格式支持、硬件加速 的情况下大幅缩减体积,如果砍到跟之前一样的解码支持,应该还是再少 10 几mb
- https://github.com/coolight7/mediaxx
## LRC歌词的读取和解码
- 因为LRC非标准格式很多,大部分库支持不完全,于是我们开发了尽力提高兼容性的 LRC编解码器,详见[lyric_xx](https://github.com/coolight7/lyric_xx)。
## 共享与控制的实现
- 本质是局域网内客户端之间的点对点通信,不少游戏也有类似的功能。
- 拟声并不是基于常见的`DLNA`或`Airplay`实现,而是用基础通信协议实现了该功能。其实只是因为作者一开始不知道有这些通用协议,没办法就自己实现了私有协议呜呜T_T。
- 基于UDP的广播和组播实现客户端之间的互相发现,远程控制时建立TCP连接传输对端状态信息,并使用HTTP请求传输音视频文件和部分控制命令。有关UDP的细节可看这篇[博客](https://blog.coolight.cool/udp-%e7%bb%84%e6%92%ad%e5%92%8c%e5%b9%bf%e6%92%ad/)。
- 当然后面也会支持`DLNA`和`Airplay`, 而重点发展会放在拟声自己的私有协议,因为私有协议从头到尾自己设计实现,能做的优化和功能更多.
- 为什么同时使用了UDP的广播和组播呢?理论上使用组播是相当符合我们的需求的,一开始也是基于组播实现的,但我们发现如果网关是手机或电脑开的热点时,常常收不到组播消息,于是又尝试用广播实现,实测下来广播要好一些。因此你需要多测试一下不同网络环境。
## win端的中文字体
- flutter在win端默认的中文字体超级丑,甚至应该说笔画有些都不对。可以指定为系统字体,比如`微软雅黑`、`黑体`,但我用下来还是感觉不行,于是用了鸿蒙的免费字体,确实不错。
## 安卓系统级悬浮窗
- 可以使用插件`flutter_overlay_window`创建系统级悬浮窗,在其他app上也能显示透明或不透明的内容。
## win多窗口
- 尽管flutter官方还未支持桌面端多窗口,但有一个曲线救国的办法,启动多进程,每个进程就是一个窗口,然后用socket通信即可,实现简单不少,当然如此一来运行时占用肯定要高一些的,但架不住它简单省事呀。
- 那么如何启动多进程呢?
- 一个是开发时就是多个项目,到时候自然按多个程序启动即可;但重用代码会麻烦一些,可能需要独立一些库让他们能够共享使用。
- 另一个是同一个项目启动多次,桌面端程序不像移动端,是可以同时启动多个进程的:
```dart
final progress = await io.Process.start(
// 当前项目的可执行程序文件名称
"musicxx.exe",
// 传递main函数参数
[
"cross-1", // 标记本次启动的进程是次级窗口-1,让它运行起来后知道自己的角色
server.port.toString(), // 传递通信端口,让对方启动后按这个端口和主窗口能建立socket连接
]
);
```
- 接下来被启动的次级窗口在dart的main函数参数就能接收到刚刚传入的标记和通信端口,按照有没有这些参数就可以区分当前运行的进程的身份。
## /docx/develop/lyric_search/index.md
# 拟声云歌词
- 拟声服务器的歌词是由用户上传或制作的
- 我们已经尝试过利用 mysql 的全文索引实现匹配,尽管开启了 mysql 的中文支持,但很多时候匹配结果偏差很大:
- 一方面是用户上传时,歌词的标题可能自己取的,也可能是跟随歌曲的标题,有些音源平台,如 bili 下载的歌曲标题往往混有 【4K超清】、【HR无损】、 某句歌词 等各种冗余信息,这些词容易导致匹配错误
- 标题中混杂歌手名,和上一条问题类似,容易导致匹配到同一歌手的不同歌曲上
- 以及英文短语匹配问题,英文词句切分后匹配也容易匹配错误,比如 `I LOVE U` 在被切分为三个词后匹配,经常匹配到 `love love love` 之类的标题上
- 由于歌词量积累了几十万,全文索引占用了很多内存,多次优化后仍然无解
- 25年中时,拟声在客户端初步实现了`智能识别`功能,着重解决从混杂的标题中提取 真正的歌曲标题、移除无效词、歌手列表、关联歌手常见的别名、繁简体、错别字。刚好最近重构服务端的歌词匹配功能时,换用自己实现分词、搜索,并将智能识别迁移到了服务端上实现,简单来看流程为:
- 服务端启动时读取数据,调用智能识别提取标题、歌手列表,对标题进行中英文分词,添加分词结果和歌手列表到搜索引擎中
- 对标题、歌手、不同版本(如 DJ、remix)设置不同的字段权重,强化标题的搜索得分优先级
- 搜索请求来临时,调用智能识别对搜索词进行提取,再分词,然后传入搜索引擎进行 BM25 相关性计算得分,返回关联性高的结果
- 服务端运行期间,增删改歌词时同步调整搜索引擎内的分词记录
- 目前成效显著,且性能大幅提高,内存占用也小了很多;当然也有一些问题有待解决,如果后续歌词量大幅增加,需要考虑将索引从内存放一部分到硬盘中缓解内存压力
- 歌词匹配存在违禁词检查,如果上传的歌词包含违禁词,将不会被搜索到;但上传者可以查看,或是直接检索对应歌词的 lrcid
## Api接口
### 搜索歌词
- URL: `https://api.music.bool.run/api/search/lyric`
- 方法: `GET`
- 参数:
- `info`: 可选;搜索词、短语,可以混杂 歌曲标题、歌手,服务端会执行智能识别和分词。限制最大 1000 字节,超过部分会被截断舍弃
- `str`: 可选;和 `info` 完全一致,二选一或都不传入即可
- `title`: 可选;歌曲标题,不建议这里混杂歌手名
- `artist`: 可选;歌手列表,可以传入多个歌手,用中英文的逗号、顿号、分号等方式隔开均可
- `duration`: 可选;歌曲时长(总毫秒数),指定可能有助于搜索更准确
- 注:
- `info`、`str`、`title` 必须至少传入一个
- `info`和`str`同时指定时取`str`,舍弃`info`
- `info`和`title`同时指定是,会从 `info` 中识别 {标题}和{歌手},然后 {标题}拼接`title` 进行搜索
- 示例:
```sh
GET https://api.music.bool.run/api/search/lyric?info=%E5%B9%B2%E7%89%A9%E5%A5%B3-%E6%B4%9B%E5%A4%A9%E4%BE%9D
```
- 响应:
- `code`: 响应码,成功为 `2000`;其他见 [响应码](#响应码)
- `data`: `Array<int>` lrcid数组,一般最大20个
- `tip`: 错误提示
- 示例:
```json
{
"code": 2000,
"data": [
1230669,
1164209,
1030869,
1235425,
1206788
],
"tip": ""
}
```
### 获取歌词内容
- URL: `https://api.music.bool.run/api/lyric/get/src`
- 方法: `GET`
- 参数:
- `lrcid`: 必选;歌词ID
- `cache_time`: 可选;客户端的歌词内容毫秒缓存时间戳,如果指定,服务器会对比参数和数据库中这条歌词内容的最后更新时间,如果认为客户端缓存仍然可用,将不返回歌词内容,且响应码为`2304`,即为请客户端直接使用缓存即可
- `src_type`: 可选;默认`json`,取值以下之一:
- `json`: 返回json格式的歌词,根据源歌词类型,携带的时间戳格式支持无时间戳、逐行、逐字
- `lrc`: 返回逐行格式的歌词,如果源歌词为逐字歌词,也将转为逐行歌词返回,可能存在无时间戳的LRC歌词
- `lrc_word`: 返回逐字格式的歌词,符合增强型LRC歌词格式。如果源歌词为逐行、无时间戳的歌词,则返回对应的逐行、无时间戳的增强型LRC歌词
- 示例:
```sh
GET https://api.music.bool.run/api/lyric/get/src?lrcid=1227613&src_type=json
```
- 响应:
- `code`: 响应码,成功为 `2000`;其他见 [响应码](#响应码)
- `data`: 歌词内容,根据`src_type`指定的类型返回`json`、`lrc`、`增强型LRC`格式
- `tip`: 错误提示
- 示例:
```json
{
"code": 2000,
"data": "{\"lrc\": [{\"time\": 1.507, \"content\": \"是想你的声音啊 (DJ)\", \"timelist\": [{\"time\": 1.507, \"index\": 0}, {\"time\": 1.596, \"index\": 1}, {\"time\": 1.926, \"index\": 2}, {\"time\": 2.11, \"index\": 3}, {\"time\": 2.438, \"index\": 4}, {\"time\": 2.516, \"index\": 5}, {\"time\": 2.953, \"index\": 6}, {\"time\": 3.048, \"index\": 9}, {\"time\": 4.083, \"index\": 12}]}, {\"time\": 5.36, \"content\": \"随这种感觉走\", \"timelist\": [{\"time\": 5.36, \"index\": 0}, {\"time\": 5.435, \"index\": 1}, {\"time\": 5.779, \"index\": 2}, {\"time\": 5.888, \"index\": 3}, {\"time\": 6.198, \"index\": 4}, {\"time\": 6.464, \"index\": 5}, {\"time\": 7.276, \"index\": 6}]}, {\"time\": 180.363, \"content\": \"难道这不是你最爱的天气\", \"timelist\": [{\"time\": 180.363, \"index\": 0}, {\"time\": 180.644, \"index\": 1}, {\"time\": 181.288, \"index\": 2}, {\"time\": 182.479, \"index\": 3}, {\"time\": 182.594, \"index\": 4}, {\"time\": 183.053, \"index\": 5}, {\"time\": 183.21, \"index\": 6}, {\"time\": 183.725, \"index\": 7}, {\"time\": 183.832, \"index\": 8}, {\"time\": 184.519, \"index\": 9}, {\"time\": 185.074, \"index\": 10}, {\"time\": 187.31, \"index\": 11}]}], \"timeType\": 1}",
"tip": ""
}
```
- 其中,json格式的`data`内容格式:
- `timeType`: `int` 歌词时间戳类型,部分数据未更新可能不准确,取值:
- 0: 未知
- 1: 逐字
- 2: 逐行
- `info`: `Map<String, String>` 歌词标签信息,对应 LRC格式中开头常见的 `[offset:0]`、`[ti:是想你的声音啊]` 等标签
- `lrc`: `Array<LrcItem>`歌词内容数组,每一个为一行歌词,LrcItem 内容:
- `time`: 这一行歌词的开始时间
- `content`: 这一行的歌词内容
- `timelist`: `Array<LrcTime>`歌词的逐字时间戳列表;如果源歌词不是逐字歌词,该字段数组内容可能少于2个。每一个为一个字或词的开始时间,LrcTime 内容:
- `time`: 这个字或单词的开始时间
- `index`: 这个字或单词在 `content` 作为UTF字符串中的开始下标,此时无论中英文字符长度都认为是 1。一般最后一个的 `index` 为 `content`的长度,代表最后一个字的结束时间。
```json
{
"timeType": 1,
"info": {
"offset": 0,
"ti": "是想你的声音啊",
},
"lrc": [
{
"time": 1.507,
"content": "一行歌词内容",
"timelist": [
{"time": 1.507, "index": 0},
{"time": 1.596, "index": 1},
{"time": 1.926, "index": 2},
{"time": 2.11, "index": 6}
],
}
],
}
```
- dart/flutter 歌词内容 json/lrc 解析实现:[lyric_xx](https://github.com/coolight7/lyric_xx)
## 响应码
- `2000`: 成功
- `2304`: 成功,但服务器并不返回内容,请使用缓存
- `2777`: 成功,并附带提示
- `3777`: 跳转标记,并附带提示
- `4000`: 用户需要登录
- `4001`: 权限错误
- `4003`: 拒绝请求
- `4004`: 请求资源无法找到
- `4010`: 参数缺失
- `4011`: 参数错误,指定了参数但是不符合要求,类型错误或是超出限定的数值范围、枚举范围
- `4012`: 已被删除
- `4013`: 重复动作
- `4014`: 已被封禁
- `4015`: 违反限制
- `4016`: 访问太快
- `4017`: 访问次数太多
- `4018`: 验证码等验证方式不通过
- `4019`: 信用评估较低,不可使用
- `4020`: 不允许的操作
- `4021`: 已过期
- `4022`: 无效
- `4023`: 需要等待客户端进一步操作
- `4024`: 废弃的api,建议更新
- `4777`: 客户端错误,并附带提示
- `4900`: 失败
- `5000`: 服务器错误
- `5001`: 需要等待服务端进一步操作
- `5100`: 外部服务器错误
- `5777`: 服务端错误,并附带提示
## /docx/help/index.md
# 使用帮助
- 本页将帮助您使用『拟声』,如有需要可点击前往[了解『拟声』](/info/)
- 由于服务器迁移,使用帮助乃至整个官网都在逐步重新建设中~
## 疑问
- 如果使用帮助没有解决您的疑问,可以尝试以下方式:
- 查看[常见疑问](issue)
- 查看[已知问题](question)
- [加入群聊](/about/qqgroup)获得更多帮助
## 提交Bug和建议
- 建议在QQ频道里留言,也可以在[拟声的GitHub项目](https://github.com/coolight7/MimicryMusic/issues)中的Issue提出Bug和建议(**访问Github很可能需要挂VPN/梯子**);
- **如果在群里或联系作者可能会被覆盖看不到。**
## 开始
- [拟声的版本/分支下载选择和区别](list/selectBranch)
- [支持的音视频格式](list/supportFormat)
- [在线歌单/本地歌单](list/songSheet/);两者区别、我喜欢的音乐、互相迁移
- [扫描本地歌曲](list/scanLocalSong/);忽略扫描、修改更新本地歌曲信息、歌词
- [选择歌曲到歌单中](list/addSongsForPlaylist/)
- [在歌单内添加/导入歌曲](list/addSongsByPlaylist/)
## 歌词
- [悬浮歌词](list/overlayLyric/)(歌词弹幕、状态栏歌词、桌面歌词)
- [为歌曲绑定云歌词、调整单首歌的歌词时间](list/bindLyric/);绑定歌词、在播放页面更换歌词、调整歌词时间
- [滚动歌词样式]
- [支持的歌词类型、读取顺序、自动匹配歌词](list/lyricFormat/)
- [歌词的制作和导入]
## 主题
- [白昼/夜间主题和自动切换];支持`跟随系统`、`时间`、`屏幕亮度`自动切换主题
- [自定义背景图]
- [播放页面背景]:
- [纯色]
- [动态渐变色]
- [晶格化]
- [天气背景]
- [自定义播放页背景图]
- [夜间降低图片亮度]
## 音视频播放
- [全格式播放支持];得益于 `ffmpeg`、`libmpv`,拟声几乎支持了所有音视频格式的播放,且支持大部分硬件加速;在编译优化方面,舍弃体积优化,转而全力优化性能;合并编译 `ffmpeg`、`libmpv` 到拟声开发的音视频处理库 `mediaxx`,并通过 复用依赖库、限制导出符号 等方式让拟声在 即便添加了几乎全格式支持的情况下控制较小的体积
- [切换上一曲时重新播放]
- [真/伪随机播放模式];支持 单曲循环、顺序循环、真随机循环(每次切换歌曲时随机定位)、伪随机循环(歌曲随机排序后添加到播放列表,然后按顺序循环播放)
- [自动切换播放模式];根据行为自动切换单曲循环和原有循环模式,当两次点击或切换上一曲到相同歌曲,自动切换到单曲循环,手动点击下一曲时恢复原有播放模式
- [音频焦点/不被其他应用中断播放];设置-音视频播放
- [定时停止播放];主页-侧边栏;预设时长,支持记住最近一次自定义时长、睡眠模式、播放完整首歌再停止、停止时退出应用
- [启动时自动播放];设置-音视频播放
- [视频播放与Bili视频]
- [Anime4K实时画质提升]
- [视频逐帧播放]
- [拟声内音量调整];播放页面-左滑两次
- [播放速度调整];播放页面-左滑两次
- [音调调整];播放页面-左滑两次
- [屏幕常亮];播放页面-左滑两次
- [快进/快退、步长];播放页面-左滑两次
## 歌单
- [导入音源歌曲];支持在歌单内直接选择各种音源的歌曲添加导入
- [同步音源];歌单支持同步导入 `本地歌曲`、`WebDAV/各种云盘 文件夹`、`音乐库的文件夹/歌手/专辑/歌单` 中新增的歌曲
- [分组创建歌单];当在`本地歌曲文件夹/歌手/专辑分类`、`WebDAV`、`各种云盘`、`音乐库`多选歌曲时,支持按 文件夹 将歌曲分组,一键自动创建歌单
- [自动提示新增歌曲];浏览`本地歌曲文件夹/歌手/专辑`、`WebDAV`、`各种云盘`、`音乐库`文件列表时,会自动提示当前文件夹的新增歌曲列表
- [固定排序方式/手动排序/一次性排序];歌单内歌曲支持按 歌曲名称、歌手、专辑、年份、创建时间、修改时间、添加时间、文件大小、文件名、随机 排序,且可`固定排序方式`(每次新增歌曲时自动排序)、`手动拖动排序`、`一次性排序`(选择手动排序时,支持进行一次性排序,方便后续手动操作)
## 其他
- [过渡动画]、[粒子动画];设置-界面
- [列表网格布局];可切换歌曲、歌单列表显示为 `列表布局`、`网格布局`
- [增强索引];在拟声的大部分列表中,即使列表内容无序,也支持首字母滑动索引!
- [播放页面触摸锁定/沉浸模式];默认启用,播放页面一段时间不操作后会隐藏部分按钮,扩展显示范围
- [播放页面圆形/方形歌曲图]
- [离线启动]
- [屏幕缩放与边距调整];可使用缩放字体大小、按钮大小、列表显示数量
- [缓存和下载管理]
- [开机自启动];安卓端,打开拟声设置-系统权限-开机自启动;并在系统设置中授予拟声 `自启动`、`后台弹出界面` 等权限。
- [默认快捷键与自定义快捷键]
## 插件
- [插件的查看和安装、更新、卸载](/help/plugins/)
- 拟声的基础功能之外,许多亮眼的功能都放在了插件之中,以下是目前拟声支持的插件:
- 扩展音源支持类的插件,安装后扩展对应类型的音源接入支持:
- [Bilibili](/help/plugins/bilibili/)
- [搜索Bili歌曲](/help/plugins/bilibili/search/)
- [导入Bili收藏夹]
- [导入Bili视频合集]
- [批量导入Bili视频]
- [导入B站UP的视频]
- [阿里云盘]
- [百度云盘]
- [115云盘]
- [Subsonic音乐库];支持 Subsonic/OpenSubsonic 协议音乐库服务器,如 Navidrome
- [Jellyfin音乐库]
- [Emby音乐库]
- [音乐云盘]
- [WebDav](/help/plugins/webdav/)
- [共享与控制](/help/plugins/share/)
- [音乐动效];音频可视化、音乐响度进度条,可预览歌曲响度峰谷区段
- [音效];声场扩展、动态扩展、动态压缩、强制响度平衡(不依赖配置信息,实时计算音频响度调整)、人声消减(剥离为伴奏)、人声提升
- [自动任务];对歌单、歌曲设置自动任务可实现自动跳过片头片尾、切换播放模式、设置定时等
- [拟物时钟]
- [屏幕遮罩]
- [欢迎光临];限时活动 `2024 洛天依庆典` 奖励
- [滚动弹幕];限时活动 `2025 洛天依庆典` 奖励
## 综合指南
- [降低卡顿]
- [车机使用指南](/help/platform/car/)
- [电视使用指南](/help/platform/tv/)
- [多端同步与跨设备播放]
- [听书、看剧指南]
## /docx/help/issue.md
# 常见疑问
- 本页记录了常见的一些疑问
## 安装相关
### 安全软件报毒/木马
- 只要是在[拟声下载官网](https://download.music.mimicry.cool/)或是[拟声内测群](/about/qqgroup)里下载的就可以放心安装使用。
## App相关
### 拟声会做纯本地播放器吗
- 不会。目前也有[离线启动APP]功能,在设置-界面-离线启动。
- 拟声自打一开始设计就不是本地播放器,我们设想了很多依靠网络实现的功能,[歌词共享]、[局域网共享与控制]就是典型代表,以及各种云存储和歌单同步来实现跨设备播放。
- 另外,拟声也是作者学习和实验技术的项目,更多是学网络相关技术。
## 用户相关
### 收不到邮箱验证码
- [点击前往查看](user/reciveEmailCode/)
## 插件相关
### 在插件里找不到Bilibili
- 在应用商店下载的版本不会包含`Bilibili`插件,可在[官网](https://download.music.mimicry.cool/)下载覆盖安装。
### B站搜索、导入错误/异常/失败
- 可[绑定Bili账号](plugins/bilibili/#绑定b站账号)后尝试,如果还不行请反馈。
## 歌曲/视频相关
### 标题、歌手信息、歌词乱码
- 使用`音乐标签`等软件自行修改歌曲信息,注意编码方式使用`UTF-8`,否则容易导致乱码。
- 改完回到拟声[重新扫描](list/scanLocalSong/)(取消增量扫描)本地歌曲。
### 视频相关功能
- 目前视频相关功能并不完善,拟声计划在`1.0`之前完善大部分音频播放相关功能,然后在`1.0`之后开始着力完善视频相关功能。
## 歌单相关
### 添加到歌单失败
- **歌曲数量太多**:在线歌单限制单个歌单的歌曲数量在1w首左右,本地歌单不限制数量。但单个歌单数量太多容易导致卡顿,建议拆分为多个歌单。
## /docx/help/list/addSongsByPlaylist/image-1.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/list/addSongsByPlaylist/image-1.png
## /docx/help/list/addSongsByPlaylist/image.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/list/addSongsByPlaylist/image.png
## /docx/help/list/addSongsByPlaylist/index.md
# 在歌单内添加/导入歌曲
## 准备
- 首先您需要已经创建好一个歌单,`我喜欢的音乐`也是您的歌单。
## 操作
- 进入歌单内,点击歌曲列表的右上角的`+`:

- 会显示一些快捷操作帮助您导入歌曲,安装`插件`可增加更多音源支持:

## /docx/help/list/addSongsForPlaylist/addSongToPlaylist.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/list/addSongsForPlaylist/addSongToPlaylist.png
## /docx/help/list/addSongsForPlaylist/image-1.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/list/addSongsForPlaylist/image-1.png
## /docx/help/list/addSongsForPlaylist/image-2.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/list/addSongsForPlaylist/image-2.png
## /docx/help/list/addSongsForPlaylist/image.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/list/addSongsForPlaylist/image.png
## /docx/help/list/addSongsForPlaylist/index.md
# 选择歌曲到歌单中
- 本页将为您在歌曲列表中如何选择歌曲添加到其他歌单中
## 添加单首歌曲
- 点击列表项右边的`三个点`:

- 然后点击`添加到其他歌单`:

- 最后点击一个`歌单`即可,歌曲将会被添加到所点击的歌单中:

## 批量选择
- 在歌曲列表中点击列表右上角的`三个横线`或`按住任意列表项`:

- 选择歌曲,然后点击列表底部的按钮`更多`:

- 点击`添加到其他歌单`:

- 最后点击一个`歌单`即可,歌曲将会被添加到所点击的歌单中:

## /docx/help/list/addSongsForPlaylist/pointToSongItemMenu.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/list/addSongsForPlaylist/pointToSongItemMenu.png
## /docx/help/list/addSongsForPlaylist/selectPlaylist.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/list/addSongsForPlaylist/selectPlaylist.png
## /docx/help/list/bindLyric/image-1.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/list/bindLyric/image-1.png
## /docx/help/list/bindLyric/image-2.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/list/bindLyric/image-2.png
## /docx/help/list/bindLyric/image-3.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/list/bindLyric/image-3.png
## /docx/help/list/bindLyric/image-4.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/list/bindLyric/image-4.png
## /docx/help/list/bindLyric/image-5.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/list/bindLyric/image-5.png
## /docx/help/list/bindLyric/image-6.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/list/bindLyric/image-6.png
## /docx/help/list/bindLyric/image-7.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/list/bindLyric/image-7.png
## /docx/help/list/bindLyric/image.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/list/bindLyric/image.png
## /docx/help/list/bindLyric/index.md
# 绑定云歌词与调整时间
## 在播放页面更换歌词
- 进入播放页面

- ...
- **在播放页面更换歌词会自动同步更改歌单和播放列表内该歌曲的歌词**
## 在歌曲列表绑定歌词
- 进入歌单
- 点击歌曲项右边的`三个点`:

- 点击`绑定歌词`:

- 可以看到已选择的歌词:
- 歌词绑定状态一共有三种情况:
- 未选择歌词;播放时依次尝试读取内嵌歌词、外置歌词,都没有则尝试自动匹配歌词
- 已选择歌词;播放时优先加载选择的歌词,如果没有则按`未选择歌词`时尝试
- 禁用歌词;不加载任何歌词

- 根据搜索结果点击想要的歌词项:

- 选择后会提示已选歌词,并可以`取消已选歌词`或`调整时间`:

- 最后点击底部的`确定`即可
## 调整歌词时间
- 前面`选择歌词`时您可能已经注意到了`调整时间`功能,下面来看看具体如何调整:
- 在`选择歌词`弹窗中点击`调整实际`:

- 上方有进度条可以调整播放进度,当点击右上角的`播放/暂停`按钮时,会自动切换到当前调整歌词的歌曲。
- 点击歌词行可以跳转播放,以此感受歌词的快慢,然后调整偏移时间即可。

## /docx/help/list/getMediaInfo/index.md
# 同步源歌曲信息
- 点击歌曲右边
## /docx/help/list/lyricFormat/index.md
# 支持的歌词类型、读取顺序、自动匹配歌词
## 支持的歌词类型
- 支持LRC格式歌词,但目前仅支持逐行歌词,未来计划支持逐字歌词。
- 支持读取内嵌歌词、同名外置歌词。
- 拟声有云歌词,用于自动匹配和搜索歌词。
- 关于云歌词的来源:
- 用户创作(主页-歌词-创作歌词)
- 上传;在过去的版本支持扫描本地歌词并上传,现在已经移除该功能;取而代之的是在播放歌曲时如果能读取到内嵌歌词或外置歌词,将会自动缓存和上传到云歌词,如此便不需要重复读取,还能跨设备使用。(开关:设置-歌词-自动缓存并上传歌词)
## 播放时的歌词读取顺序
- 首先读取在拟声内为歌曲绑定的歌词,如果没有绑定或读取失败将进行下一步。
- 内嵌歌词
- 外置LRC歌词
- 自动匹配云歌词
## 自动匹配歌词
- 前面提到,用户制作、上传的云歌词可被用于自动匹配歌词,当播放歌曲时,如果没有歌词将会按照歌曲的标题、艺术家等信息尝试自动匹配歌词。(开关:设置-歌词-自动匹配歌词)
- 由于仅使用拟声云歌词匹配,准确率很大程度取决于歌词库的大小,这需要时间发展和优化。
- 另外,拟声不会打算去接入其他平台的歌词API,除非其声明公开免费~ 因此如果已经了解该信息,请勿提类似需求。
## /docx/help/list/overlayLyric/index.md
# 悬浮歌词
- 作为`拟声`的一大亮点,悬浮歌词包含了许多高自定义和创新性的功能。
- 悬浮歌词一共分三种:
- `歌词弹幕`;拟声创新功能!将歌词以弹幕形式发送,由于其移动状态,对其他软件的内容遮挡影响很小。
- `状态栏歌词`;(安卓可选系统级状态栏歌词;默认悬浮窗)在系统状态栏上显示歌词
- `桌面歌词`;悬浮滚动歌词
## 歌词弹幕
## 状态栏歌词
## 桌面歌词
## /docx/help/list/scanLocalSong/image-1.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/list/scanLocalSong/image-1.png
## /docx/help/list/scanLocalSong/image-2.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/list/scanLocalSong/image-2.png
## /docx/help/list/scanLocalSong/image-3.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/list/scanLocalSong/image-3.png
## /docx/help/list/scanLocalSong/image-4.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/list/scanLocalSong/image-4.png
## /docx/help/list/scanLocalSong/image-5.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/list/scanLocalSong/image-5.png
## /docx/help/list/scanLocalSong/image-6.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/list/scanLocalSong/image-6.png
## /docx/help/list/scanLocalSong/image-7.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/list/scanLocalSong/image-7.png
## /docx/help/list/scanLocalSong/image-8.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/list/scanLocalSong/image-8.png
## /docx/help/list/scanLocalSong/image-9.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/list/scanLocalSong/image-9.png
## /docx/help/list/scanLocalSong/image.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/list/scanLocalSong/image.png
## /docx/help/list/scanLocalSong/index.md
# 扫描本地歌曲
## 扫描
- 在本地歌曲列表点击右上角的扫描按钮:

- 可以看到如下弹窗:
- **增量扫描**:针对指定目录内新增加的歌曲,读取其歌曲信息并记录进`拟声`中,并将已被删除的歌曲从`拟声`的记录中移除,打开该功能可以大幅加快扫描速度。**但不会刷新已有歌曲的信息**,因此如果您修改了歌曲信息、封面时,需要关闭该功能后重新扫描!
- **忽略扫描的位置**:这里指定的目录或文件将被忽略扫描。
- **扫描方式**:安卓端可选`快速扫描`或`完整扫描`:
- **快速扫描**:扫描非常快!引用系统的文件记录,但可能缺失部分格式。
- **完整扫描**:能扫描到的格式更多更完整,但速度慢。由拟声自行扫描和提取歌曲文件信息。需要扫描`不常见的格式`或`视频`时请选择该方式。
- 弹窗底部可以指定扫描目录:
- 默认不指定目录时自动全局扫描,这可能需要很长的时间,因此建议指定歌曲目录位置。
- 点击`添加目录`可添加指定扫描的目录。

- 后续重新扫描时,如果不需要更改配置,可直接在本地歌曲列表`下拉`刷新扫描:

## 忽略扫描
- 如果希望忽略扫描某一歌曲,可在本地歌曲列表找到该歌曲后点击歌曲项右边的`三个点`,然后点击`忽略扫描歌曲`即可:

- 如果希望忽略扫描某一个文件夹,可切换到文件夹分类,然后点击目标文件夹右边的`三个点`,然后点击`忽略扫描`即可:

- 如果希望忽略扫描某一歌手、专辑分类,操作等同忽略扫描文件夹。
## 问题
### 点击添加目录无响应或闪退
- 若点击`添加目录`无响应或闪退,可点击列表右上角的`三个点`:

- 依次尝试`选择路径`和`手动输入路径`:

### 扫描U盘、内存卡等外部存储
- 如果默认全局扫描没有扫描外部存储的歌曲,可以添加指定U盘、内存卡上的音乐目录路径后扫描。
- 注意拔了U盘、内存卡后是播放不了的,重新插回后可能可以播放,因为系统在重新插入外部存储时其名称可能改变,导致拟声之前扫描添加的外部存储的歌曲路径失效,因此如果播放不了需要重新扫描。
### 手动输入路径
- 若无法通过系统添加路径,拟声内置的`选择路径`也没有显示U盘等外部存储时,可以先用`系统文件管理器`、`MT文件管理器`等工具复制U盘的一个音乐文件夹路径,然后回到`拟声`,[手动输入路径添加](#点击添加目录无响应或闪退)
### 扫不到文件/缺失部分格式
* 大部分时候是权限问题,您可以在`拟声设置-系统权限`中点击`文件读写`获取完整的文件访问权限,否则系统可能会限制拟声只能访问被系统认为是音视频的文件,而对于一些系统不支持的音视频(尤其是dsf等格式)拟声不能扫描到。
### 更新歌曲信息、封面
- 如果希望更改歌曲信息、封面,可使用`音乐标签`等软件修改歌曲文件的信息,然后回到`拟声`,[重新扫描](#扫描)(**注意关闭增量扫描**)
### 更新本地歌曲歌词
- `拟声`在播放本地歌曲时,如果能读取到歌曲的内置、外置歌词会自动缓存,如果您更换了歌曲的歌词,可以在`设置-歌词-清理歌词缓存`清理之前缓存的歌词,让`拟声`重新读取得到正确的歌词。或是点击歌曲`右边三个点-同步源歌曲信息`更新

## /docx/help/list/selectBranch.md
# 拟声的版本/分支下载选择和区别
## 下载官网
- [下载官网](https://download.music.mimicry.cool/)
- 您也可以在一些应用商店上下载拟声,但和官网下载到的是`有区别的`,最主要的区别在于官网版拥有B站插件,可播放、导入、搜索B站的视频和歌曲,而在应用商店上下载的拟声并没有该插件!
- [历史版本下载](https://www.123865.com/s/7dgajv-9HGVv),如果需要旧版本,可前往该页面寻找
## 安卓
- **完整版**
- 最低支持安卓7.0;功能完整,积极更新维护。
- 该版本还区分了CPU架构,您可以逐个尝试安装运行,如果无法安装或闪退再尝试其他的:
- **arm64**;绝大多数时候的选择,优先尝试它,如果无法安装或闪退再尝试其他架构
- arm32;一般用于旧手机;尽管arm64架构的新设备也能运行这个,但性能会差很多
- x86_64;一般用于模拟器和一些旧平板
- **安卓5支持版**
- 最低支持安卓5.x;为了适配安卓5.x而移除了一些功能,并替换了一些兼容性更好但性能或功能差的组件进去,因此**除非完整版无法使用,否则不建议使用该版本**。
- **安卓4支持版**
- 最低支持安卓4.x;该版本已经很久未更新,为了适配安卓4.x而移除了一些功能,并替换了一些兼容性更好但性能或功能差的组件进去,因此**除非完整版无法使用,否则不建议使用该版本**。
- **车机、电视、平板**
- 一般这些设备都是安卓系统,拟声支持自适应横竖屏,直接**下载安装安卓版拟声安装包**即可。
## win
- 支持 x86_64 Win10/Win11
- 支持悬浮歌词:`歌词弹幕`、`桌面歌词`
- 部分安全软件可能会报毒,只要确定是在**拟声官网**下载的就不用担心。
## IOS
- 支持 `arm64 IOS 13.0` 或更高
- 已提供未签名安装包下载,您需要自行搜索`Bili`、`抖音`、或询问AI如何安装。相关搜索关键词:`越狱或连接电脑安装未签名ipa安装包`
- 拟声历史版本中,0.81.7 可支持 `arm64 IOS 12.1` 或更高
## macos
- 支持 arm64 Macos 11.0 或更高
- 支持悬浮歌词:`歌词弹幕`、`桌面歌词`
- 拟声历史版本中,0.81.7 可支持 `arm64/x86_64 Macos 11.0` 或更高
## linux
- 支持 x86_64;对于Ubuntu需要 Ubuntu 20 或更高
- 支持`mpris`媒体通知和歌词
- 支持悬浮歌词:`歌词弹幕`、`桌面歌词`
- 已经打包成`AppImage`,下载后直接双击运行,或者在命令行 ./{拟声程序包名称} 即可运行:
```sh
# 添加可执行权限
chmod +x Musicxx.AppImage
# 启动拟声
./Musicxx.AppImage
```
- 拟声已根据 Ubuntu 20 系统打包了依赖库,从拟声 0.82.7 版本开始,可以直接在 Ubuntu20 系统上直接运行,不需要额外安装依赖库
- 其他系统可能需要先安装依赖包:
- `libgtk-3-0`
- `libblkid1`
- `liblzma5`
- Ubuntu 用户可以使用以下命令,其他系统可以询问AI如何安装上述依赖包:
```sh
# ubuntu 安装依赖
sudo apt install libgtk-3-0 libblkid1 liblzma5
```
## 鸿蒙
- 敬请期待
## /docx/help/list/songSheet/index.md
# 歌单
- 在拟声中,歌单分为在线歌单和本地歌单两大类。
## 在线歌单
- 在线歌单可以跨设备同步,在不同设备上登录同一个账号后,可以同步歌单。
- 您需要已连接网络,且登录账号后才能创建在线歌单。
- 在线歌单还可以配置访问权限,可以允许其他用户查看和播放。
- 单个在线歌单的歌曲数量建议保持在`1万首`歌以内,否则容易导致**保存失败**。
## 我喜欢的音乐
- 这实际上也是一个`在线歌单`,只不过它不能被删除,另外播放页面增加了收藏功能可以快捷将歌曲收藏到`我喜欢的音乐`中。
## 本地歌单
- 本地歌单仅存储在您当前设备本地,因此创建它不需要网络连接,也不需要登录账号,但无法在其他设备上同步和查看。
- 本地歌单不限制歌曲数量、歌曲类型,**可以包含本地歌曲、云盘歌曲**等任意类型。
- 插件[共享与控制](../../plugins/share/)能够将本地或在线歌单分享给其他设备播放。
## 在线/本地歌单互相迁移
- `在线歌单`和`本地歌单`可以互相迁移歌单内歌曲列表。
- 操作方法和[选择歌曲到其他歌单中](../addSongsForPlaylist/)相同
## /docx/help/list/supportFormat.md
# 支持的音视频格式
- 选择使用播放组件`libmpv`时,得益于`libmpv`/`ffmpeg`的强大,拟声支持了绝大多数的音视频格式播放,但限制了扫描的格式,如果您有其他格式需求,可提出添加;以下是目前支持的扫描格式,在电脑浏览器中,您可以按`ctrl+f`快捷查找是否存在目标格式。
- 安卓端如果选择使用播放组件`Media3`,支持的格式更多是取决于您的系统。
## 音频格式
- mp3
- flac
- wav
- m4a
- aac
- aa3
- ape
- ac3
- aiff
- aif
- aifc
- afc
- alac
- wma
- dsf
- dff
- diff
- oma
- ogg
- oga
- opus
- ra
- thd
- mka
- 3gp
- m4b
## 视频格式
- m4s
- mp4
- mkv
- wmv
- flv
- avi
- mov
- m4v
- asf
- rm
## /docx/help/platform/car/image-1.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/platform/car/image-1.png
## /docx/help/platform/car/image-2.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/platform/car/image-2.png
## /docx/help/platform/car/image-3.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/platform/car/image-3.png
## /docx/help/platform/car/image-4.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/platform/car/image-4.png
## /docx/help/platform/car/image.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/platform/car/image.png
## /docx/help/platform/car/index.md
# 车机使用指南
- 大部分车机仍然是安卓系统,因此您可以尝试下载安装[拟声的安卓版](https://download.music.mimicry.cool/),安卓端分为多种架构,可以依次尝试 `arm64`、`arm32`、`x64`,如果不能安装或是启动闪退再尝试下一个架构。
## 便捷登录
- [拟声密令登录](/help/user/xxcodeSign/)
## 拟声远程控制
- [共享与控制](/help/plugins/share/)
- 在其他设备,如手机、电脑上安装拟声,并且双方都安装插件`共享与控制`后,即可互相控制和共享歌曲。
- 默认情况下,`共享与控制`仅允许登录相同账号的设备可以互相控制,您可以用修改插件设置项的`接收跨设备控制`控制权限为`公开`, 即可让其他设备连接控制:

- 点击`设置`:

- 点击`接收跨设备控制`:

- 选择`公开`:

## /docx/help/platform/tv/image-1.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/platform/tv/image-1.png
## /docx/help/platform/tv/image-2.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/platform/tv/image-2.png
## /docx/help/platform/tv/image-3.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/platform/tv/image-3.png
## /docx/help/platform/tv/image-4.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/platform/tv/image-4.png
## /docx/help/platform/tv/image.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/platform/tv/image.png
## /docx/help/platform/tv/index.md
# 电视使用指南
- 拟声可以在安卓 电视机、机顶盒 上运行,支持自适应横屏,安装时与安卓手机相同选择`安卓安装包`即可。
- 但拟声仅适配了小部分遥控器功能,因此您需要使用有线/无线`鼠标`或安装`共享与控制`插件后用手机、电脑控制。
## 便捷登录
- [拟声密令登录](/help/user/xxcodeSign/)
## 已适配的遥控器功能

## 拟声远程控制
- [共享与控制](/help/plugins/share/)
- 在其他设备,如手机、电脑上安装拟声,并且双方都安装插件`共享与控制`后,即可互相控制和共享歌曲。
- 对于电视您需要先使用鼠标操作,安装该插件。
- 默认情况下,`共享与控制`仅允许登录相同账号的设备可以互相控制,您可以用鼠标修改插件设置项的`接收跨设备控制`控制权限为`公开`,即可让其他设备连接控制:

- 点击`设置`:

- 点击`接收跨设备控制`:

- 选择`公开`:

## /docx/help/plugins/bilibili/image-1.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/plugins/bilibili/image-1.png
## /docx/help/plugins/bilibili/image-2.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/plugins/bilibili/image-2.png
## /docx/help/plugins/bilibili/image.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/plugins/bilibili/image.png
## /docx/help/plugins/bilibili/index.md
# 插件:Bilibili
> - 注意:官网下载的版本才会拥有`Bilibili`插件,应用商店下载的版本并不包含该插件!
> - 可在[下载官网]重新下载安装,在`插件`列表中即可找到`Bilibili`插件。
## 功能
- 绑定B站账号
- [搜索B站的视频和音频歌曲](/help/plugins/bilibili/search/)
- [导入Bili收藏夹](/help/plugins/bilibili/star/)
- 导入Bili视频合集
- 导入B站UP的投稿
## 绑定B站账号
- 绑定B站账号可提高播放Bili歌曲的音质、视频的画质和访问B站的稳定性。
- 在`主页`点击`Bili设置`:

- 点击扫描绑定账号

- 然后手机打开`B站客户端`扫描绑定:

## 常见问题
### 播放失败
- 请尝试以下操作:
- 检查`播放组件`,切换使用`libmpv`(设置-音视频播放-播放组件)
- 关闭视频播放(设置-音视频播放-bili视频播放)
- 绑定 Bili 账号
- 清理该歌曲的缓存(歌曲右边三个点-清理歌曲缓存)后重新尝试播放
## /docx/help/plugins/bilibili/leadUpVideos/index.md
# 导入B站UP的投稿或合集
* 文档建设中
## /docx/help/plugins/bilibili/leadVideos/index.md
# 导入bili视频合集
* 文档建设中
## /docx/help/plugins/bilibili/search/image-3.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/plugins/bilibili/search/image-3.png
## /docx/help/plugins/bilibili/search/image-4.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/plugins/bilibili/search/image-4.png
## /docx/help/plugins/bilibili/search/index.md
# 搜索bili歌曲
- 在首页点击顶部的搜索即可搜索bili歌曲:

- 选择bili搜索类型

- 您可以输入的搜索内容:
- 关键字:比如您在b站浏览发现一首好听的歌,可以在这里输入关键字搜索
- bv号:您也可以将复制来的视频bv号直接输入搜索
- 视频链接:将视频的分享链接或浏览器中的链接复制进搜索框,拟声会自动提取对应视频并搜索。
## /docx/help/plugins/bilibili/star/image-1.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/plugins/bilibili/star/image-1.png
## /docx/help/plugins/bilibili/star/image-2.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/plugins/bilibili/star/image-2.png
## /docx/help/plugins/bilibili/star/image.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/plugins/bilibili/star/image.png
## /docx/help/plugins/bilibili/star/index.md
# 导入 Bili 收藏夹
- 在拟声可以绑定 Bili 账号,然后快速导入 Bili 收藏夹内的视频和歌曲
- 选择一个歌单(可以是 `我喜欢的音乐`、`在线歌单`、`本地歌单`),点击进入:

- 点击右上角的 `+` 号:

- 选择 `导入 Bili 收藏夹`:

- 如果没有绑定 Bili 账号,会提示绑定账号,绑定账号后重新点击 `导入 Bili 收藏夹` 即可。
## /docx/help/plugins/image-1.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/plugins/image-1.png
## /docx/help/plugins/image-2.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/plugins/image-2.png
## /docx/help/plugins/image.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/plugins/image.png
## /docx/help/plugins/index.md
# 插件
- 除去必要的基础核心功能之外,拟声把大部分扩展功能放在了`插件`里。
## 安装、更新、卸载
- 在拟声`主页`点击`插件`:

- 在插件列表中您可以了解其功能、安装、更新或移除:

- 大部分插件安装后在`主页`会有入口:

## /docx/help/plugins/share/image-1.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/plugins/share/image-1.png
## /docx/help/plugins/share/image-2.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/plugins/share/image-2.png
## /docx/help/plugins/share/image-3.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/plugins/share/image-3.png
## /docx/help/plugins/share/image.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/plugins/share/image.png
## /docx/help/plugins/share/index.md
# 共享与控制
- 拟声核心功能之一,该插件效果强大,可实现跨设备共享播放、远程控制等功能。
- 需要附近设备在同一局域网内,比如连接同一 WIFI、热点等。
- 已支持`拟声私有通信协议`和`DLNA`,其中`拟声私有通信协议`由于完全出自拟声开发,支持的功能相当全面且强大。
## 安装
- 在`拟声`的`插件管理`中安装`共享与控制`:

## 使用帮助
### 远程控制
- 首先插件页面会显示附近可用设备,以及当前设备的名称,方便其他设备连接,也可以下拉扫描附近设备。
- 默认情况下,两个设备登录相同账号,且安装插件`共享与控制`后就会显示:

- 如图中,附近有一个设备登录着相同账号,可点击`控制`连接它:
### 自动共享
- 除了主动连接进行远程控制,该插件还支持自动共享本地歌曲、缓存给其他设备播放。
- 这意味着:
- 对于`本地歌曲`,假设主要存放电脑上,而手机、电视等其他设备需要播放时,拟声会自动将电脑上的歌曲文件共享传输到当前设备上播放。
- 对于`缓存`,如果即将播放需要网络加载的歌曲,而局域网内的其他设备已经有了这个歌曲的缓存文件,拟声同样会自动共享过来播放,即可大幅节约流量并提高加载速度。如果您的网盘是按流量计费,利用这个功能可实现多台设备播放时,实际只消耗了一次歌曲文件流量。
- 另外,`以上两个功能还会互相结合`,假如家里电脑上的本地歌曲共享到了手机上播放,此时手机会将该文件认为是`缓存`,带着手机来到公司时,公司的电脑也没有该歌曲文件,但会将手机上的缓存共享过来播放。
- 效果强大且无需用户额外操作,只需要安装该插件即可。
### 分享播放
- 安装该插件后,在大多数的`歌单`、`歌曲`右边的三个点中,会出现`共享到其他设备播放`功能:

- 可选择单首歌曲、多首歌曲、整个歌单、多个歌单分享到其他设备播放。

### 相关设置
## /docx/help/plugins/webdav/add/image-1.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/plugins/webdav/add/image-1.png
## /docx/help/plugins/webdav/add/image-2.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/plugins/webdav/add/image-2.png
## /docx/help/plugins/webdav/add/image-3.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/plugins/webdav/add/image-3.png
## /docx/help/plugins/webdav/add/image.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/plugins/webdav/add/image.png
## /docx/help/plugins/webdav/add/index.md
# 添加webdav连接
- 在`拟声`中安装`webdav`插件后,在主页可以进入webdav页面

- 点击右上角的添加按钮:

- 然后填入`webdav服务`的信息,这一般由您希望连接的webdav服务商提供:
- `连接名称`:随意起名
- `服务器链接`:webdav服务的链接,不能乱填或填错
- `连接用户名`:webdav服务的账号名称
- `连接密码`:webdav服务的账号密码
- `自签名证书`:可不填,默认允许任意证书。如果您的webdav服务启用了自签名证书加密,且希望验证证书,可填入证书的一段即可,验证失败再尝试另一段。
- `访问权限`:
- `公开`:允许共享您的webdav连接给其他用户使用,这意味着您愿意公开您的webdav账号密码
- `私密`:仅您的账号可使用

- 填写完成后,可滑动到底部尝试连接:

## /docx/help/plugins/webdav/index.md
# 插件:WebDAV
- `webdav`插件用于连接 `webdav` 服务端,webdav协议一般用于提供云盘服务,也就是说,用户可以自建云盘或由云服务商提供,再利用webdav协议这一通用协议允许其他APP连接使用。
- 目前常用的应用场景:
- 云盘服务商提供webdav服务,方便其他APP连接使用云文件;如坚果云、阿里云盘,官方提供了webdav接口,在`拟声`中配置对方的连接和账号密码即可连接上,然后就可以播放云盘中的音乐文件。
- 用户转换云盘服务为webdav接口;多数云盘官方并没有提供webdav服务,因此可以使用alist等软件,将云盘服务转成webdav服务,然后同样地在`拟声`配置该webdav接口即可使用。但这需要alist等软件常驻后台,且由于一些设备的省电管理,网络服务容易断开。
- 用户自己搭建云盘;如NAS,这一般需要较高的知识和硬件成本。
- 可见,webdav用于让APP使用其他云存储上的文件。
## webdav改进
- webdav协议并不是为了音视频而设计的,而是面向所有文件,因此它没有考虑音视频的一些细节,在常见的支持webdav的APP中,其文件列表并不会显示歌曲的封面、信息等。
- 拟声的`webdav`功能做了更多优化,播放过一次后可以预览其信息和封面,另外,可以使用`同步源歌曲信息`提取收集歌曲信息。
## 使用帮助
- [添加webdav连接](/help/plugins/webdav/add/)
## /docx/help/question.md
# 已知问题
- 本页记录了当前版本的已知问题
## /docx/help/user/reciveEmailCode/index.md
# 收不到邮箱验证码
* 如果收不到邮箱验证码请尝试一下操作:
1. 检查邮箱是否输入正确
2. 查看邮箱的垃圾箱,部分邮箱可能会将验证码邮件归类到垃圾箱中
3. 如果使用的是QQ邮箱,可更换尝试网易163邮箱等其他邮箱
4. 若以上都不能解决,可加入[官方Q群](/about/qqgroup.md)获得更多帮助
## /docx/help/user/xxcodeSign/image-1.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/user/xxcodeSign/image-1.png
## /docx/help/user/xxcodeSign/image-2.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/user/xxcodeSign/image-2.png
## /docx/help/user/xxcodeSign/image-3.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/user/xxcodeSign/image-3.png
## /docx/help/user/xxcodeSign/image-4.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/user/xxcodeSign/image-4.png
## /docx/help/user/xxcodeSign/image-5.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/user/xxcodeSign/image-5.png
## /docx/help/user/xxcodeSign/image.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/help/user/xxcodeSign/image.png
## /docx/help/user/xxcodeSign/index.md
# 拟声密令登录
- `拟声密令`用于方便已登录拟声的设备在其他设备上登录
- 由于手机上登录拟声往往比较方便,其他设备如电视、车机登录账号比较麻烦,因此可以利用`拟声密令`,在手机上允许其他设备登录账号
- 登录账号需要网络连接,请确保两个设备上的网络连接正常
## 待登录端
- 打开拟声,进入`登录页面`,点击`拟声密令`,即可获取登录密令:

- 如果密令过期,点击`刷新密令`即可:

- 接下来`待登录端`无需操作,请使用`已登录的设备`进行下一步操作
## 已登录端
- 打开拟声,进入用户信息页面:

- 点击`拟声密令登录`:

- 输入待登录端的密令,点击`确定`即可:

- 然后会读取显示对方的设备信息,确认设备正确即可点击`允许登录`,待登录端将会自动登录:

## /docx/images/coolight_s.jpg
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/images/coolight_s.jpg
## /docx/images/logo-full.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/images/logo-full.png
## /docx/images/logo-tran.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/images/logo-tran.png
## /docx/images/musicxx-glass-blue.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/images/musicxx-glass-blue.png
## /docx/images/musicxx-glass-dark.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/images/musicxx-glass-dark.png
## /docx/images/musicxx-glass-light.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/images/musicxx-glass-light.png
## /docx/images/qqChannle.jpg
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/images/qqChannle.jpg
## /docx/images/qqGroup.jpg
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/docx/images/qqGroup.jpg
## /docx/index.md
---
# https://vitepress.dev/reference/default-theme-home-page
layout: home
hero:
name: "拟声"
text: "拟物风本地/云盘/NAS音视频播放器"
tagline: 安卓/IPhone/Windows/Macos/Linux、歌词弹幕、跨设备共享与控制、WebDAV/阿里云盘/百度云盘/115云盘/Subsonic/Jellyfin/Emby
actions:
- theme: brand
text: 下载
link: https://download.music.mimicry.cool/
- theme: alt
text: 使用帮助
link: /help/
- theme: alt
text: GitHub
link: https://github.com/coolight7/musicxx
image:
src: images/musicxx-glass-blue.png
alt: 拟声
features:
- title: 歌词弹幕
details: 拟声创新功能,将歌词作为弹幕飘过!降低歌词对其他App内容的遮挡。也支持桌面歌词和状态栏歌词(悬浮窗/系统级)
- title: WebDav/阿里云盘/百度云盘/115云盘/Subsonic/Jellyfin/Emby
details: 支持多种云盘、Nas 音乐库,也支持本地音视频;几乎支持全音视频格式播放
- title: 多系统支持
details: 目前已支持 安卓/IPhone/Windows/Macos/Linux
- title: 共享与控制
details: 同一局域网内,可自动将其他设备的本地歌曲和缓存共享过来播放!可以远程控制其他设备,且支持 AI/MCP 接入控制
---
<style>
:root {
--vp-home-hero-name-color: transparent;
--vp-home-hero-name-background: -webkit-linear-gradient(120deg, #66ccff 30%, #41d1ff);
--vp-home-hero-image-background-image: linear-gradient(-45deg, #e1edfa 50%, #fff9ec 50%);
--vp-home-hero-image-filter: blur(44px);
}
html.dark {
--vp-home-hero-image-background-image: linear-gradient(-45deg, #13e4ea 50%, #7d78eb 50%);
}
@media (min-width: 640px) {
:root {
--vp-home-hero-image-filter: blur(56px);
}
}
@media (min-width: 960px) {
:root {
--vp-home-hero-image-filter: blur(68px);
}
}
</style>
## /docx/info/index.md
# 了解『拟声』
- 本页将帮助您了解和使用『拟声』,如有需要可点击前往[『拟声』使用帮助](/help/)
## 『拟声』是什么
- 这是一个音视频播放器,但本地播放只是冰山一角,我们更多着力于依靠网络实现令人赞叹的功能。
- 设计了不少创新和改进点:
- **歌词弹幕**;创新体验~ 将正在播放的歌词以弹幕形式飘过,降低了歌词对屏幕内容的遮挡。另外对于弹幕的实现也做了不少优化。
- **音源转换**;拟声核心功能之一,其他不少功能依赖它实现。当本地文件缺失或需要网络加载时,会先尝试寻找其他本地文件平替,或是依赖其他插件扩展局域网共享等平替方式,实现提高速度、节省流量、跨设备播放。
- **跨设备共享与控制**;与众不同的共享和控制效果!支持`拟声自定义协议`和`DLNA`推送
- 同一局域网内,可将其他设备的本地歌曲、缓存共享过来播放
- 通过`拟声自定义协议`,可以远程控制其他设备,如播放暂停、切换歌曲、播放列表、歌词、音量、主题、后台播放和退出;像电视遥控器一样可以多台设备同时对一台设备进行控制,且控制时可实时查看对方的歌曲信息、播放进度、滚动歌词、播放列表,就像本机播放一样!
- **更高兼容性的LRC歌词编解码**;网络上流传的各种标准和非标准的LRC格式很多,拟声实现了自己的LRC编解码器`lyric_xx`,力求兼容各种格式。
- **改进Webdav和音乐云盘**;实现文件列表可预览音视频的标题、艺术家、封面等信息,而不是只能显示文件名。另外拟声拥有自建音乐云盘。
- **新拟物风格UI**;新的体验~
- **天气背景**;播放页面支持多云、雨、雪、雷雨等动态天气背景
- **优化的首字母索引**;拟声在歌曲列表的右侧支持了常见的首字母索引滑动定位,并且拟声的索引不需要列表有序,无序列表也能支持快捷索引!
- 功能丰富完善:
- 支持本地歌曲、WebDAV、阿里云盘、百度云盘、拟声自建云盘
- 支持歌词弹幕、状态栏歌词(悬浮窗/安卓系统级)、桌面歌词
- 支持绑定云歌词、读取内嵌歌词、外置歌词和自动匹配
- 支持安卓/windows/macos/linux,自适应横竖屏,可运行在手机、电脑、车机、电视等设备上
- 新拟物UI;跟随系统/时间自动切换主题;自定义背景图片;纯色/动态天气/动态渐变色播放页面
- 登录账号跨设备自动同步歌单、歌词等信息
- 更多插件支持;共享与控制、自动任务、音乐动效、拟物时钟、屏幕遮罩...
- 更多自定义选择;圆形/方形歌曲图;沉浸模式;列表网格布局;自定义启动位置、自动播放、真/假随机播放、屏幕缩放与边距调整...
- 丰富的过渡动画、Q弹的滚动效果
## 下载
- [下载官网](https://download.music.mimicry.cool/)
- 支持安卓/windows/macos/linux
## 使用帮助
- [点击前往](/help/)
## 加入群聊
- [点击前往](/about/qqgroup)
## 开发计划
- 拟声计划的功能很多,目前仍不够完善,因此可以看到拟声的版本号一直在`0.x.x`而没有步入`1.0`。
- 尽管目前已经支持了音视频的播放,但重心仍在音频播放方面;计划在音频播放方面的功能和稳定性足够完善时会步入`1.0`,而后开始重点开发视频播放相关的功能。
## 开发记录
- 如果你是开发者,作者在开发拟声时留下了一些[开发记录](/develop/)也许能帮助你。
## 联系作者
- [『coolight • 郑泳坤』](/about/author/)
## 小彩蛋
- 『拟声』包含了不少小彩蛋,希望能不时给你带来小惊喜~
## /docx/musicPlusPlus/index.md
# 拟声++、诚信支付、信用评估
- 如果觉得拟声不错的话,可以考虑支持我们,开启拟声++,获得更多功能和优化~
## /docx/package-lock.json
```json path="/docx/package-lock.json"
{
"name": "docx",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"devDependencies": {
"vitepress": "^1.1.4"
}
},
"node_modules/@algolia/autocomplete-core": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz",
"integrity": "sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==",
"dev": true,
"dependencies": {
"@algolia/autocomplete-plugin-algolia-insights": "1.9.3",
"@algolia/autocomplete-shared": "1.9.3"
}
},
"node_modules/@algolia/autocomplete-plugin-algolia-insights": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz",
"integrity": "sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==",
"dev": true,
"dependencies": {
"@algolia/autocomplete-shared": "1.9.3"
},
"peerDependencies": {
"search-insights": ">= 1 < 3"
}
},
"node_modules/@algolia/autocomplete-preset-algolia": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz",
"integrity": "sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==",
"dev": true,
"dependencies": {
"@algolia/autocomplete-shared": "1.9.3"
},
"peerDependencies": {
"@algolia/client-search": ">= 4.9.1 < 6",
"algoliasearch": ">= 4.9.1 < 6"
}
},
"node_modules/@algolia/autocomplete-shared": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz",
"integrity": "sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==",
"dev": true,
"peerDependencies": {
"@algolia/client-search": ">= 4.9.1 < 6",
"algoliasearch": ">= 4.9.1 < 6"
}
},
"node_modules/@algolia/cache-browser-local-storage": {
"version": "4.23.3",
"resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.23.3.tgz",
"integrity": "sha512-vRHXYCpPlTDE7i6UOy2xE03zHF2C8MEFjPN2v7fRbqVpcOvAUQK81x3Kc21xyb5aSIpYCjWCZbYZuz8Glyzyyg==",
"dev": true,
"dependencies": {
"@algolia/cache-common": "4.23.3"
}
},
"node_modules/@algolia/cache-common": {
"version": "4.23.3",
"resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.23.3.tgz",
"integrity": "sha512-h9XcNI6lxYStaw32pHpB1TMm0RuxphF+Ik4o7tcQiodEdpKK+wKufY6QXtba7t3k8eseirEMVB83uFFF3Nu54A==",
"dev": true
},
"node_modules/@algolia/cache-in-memory": {
"version": "4.23.3",
"resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.23.3.tgz",
"integrity": "sha512-yvpbuUXg/+0rbcagxNT7un0eo3czx2Uf0y4eiR4z4SD7SiptwYTpbuS0IHxcLHG3lq22ukx1T6Kjtk/rT+mqNg==",
"dev": true,
"dependencies": {
"@algolia/cache-common": "4.23.3"
}
},
"node_modules/@algolia/client-account": {
"version": "4.23.3",
"resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.23.3.tgz",
"integrity": "sha512-hpa6S5d7iQmretHHF40QGq6hz0anWEHGlULcTIT9tbUssWUriN9AUXIFQ8Ei4w9azD0hc1rUok9/DeQQobhQMA==",
"dev": true,
"dependencies": {
"@algolia/client-common": "4.23.3",
"@algolia/client-search": "4.23.3",
"@algolia/transporter": "4.23.3"
}
},
"node_modules/@algolia/client-analytics": {
"version": "4.23.3",
"resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.23.3.tgz",
"integrity": "sha512-LBsEARGS9cj8VkTAVEZphjxTjMVCci+zIIiRhpFun9jGDUlS1XmhCW7CTrnaWeIuCQS/2iPyRqSy1nXPjcBLRA==",
"dev": true,
"dependencies": {
"@algolia/client-common": "4.23.3",
"@algolia/client-search": "4.23.3",
"@algolia/requester-common": "4.23.3",
"@algolia/transporter": "4.23.3"
}
},
"node_modules/@algolia/client-common": {
"version": "4.23.3",
"resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.23.3.tgz",
"integrity": "sha512-l6EiPxdAlg8CYhroqS5ybfIczsGUIAC47slLPOMDeKSVXYG1n0qGiz4RjAHLw2aD0xzh2EXZ7aRguPfz7UKDKw==",
"dev": true,
"dependencies": {
"@algolia/requester-common": "4.23.3",
"@algolia/transporter": "4.23.3"
}
},
"node_modules/@algolia/client-personalization": {
"version": "4.23.3",
"resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.23.3.tgz",
"integrity": "sha512-3E3yF3Ocr1tB/xOZiuC3doHQBQ2zu2MPTYZ0d4lpfWads2WTKG7ZzmGnsHmm63RflvDeLK/UVx7j2b3QuwKQ2g==",
"dev": true,
"dependencies": {
"@algolia/client-common": "4.23.3",
"@algolia/requester-common": "4.23.3",
"@algolia/transporter": "4.23.3"
}
},
"node_modules/@algolia/client-search": {
"version": "4.23.3",
"resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.23.3.tgz",
"integrity": "sha512-P4VAKFHqU0wx9O+q29Q8YVuaowaZ5EM77rxfmGnkHUJggh28useXQdopokgwMeYw2XUht49WX5RcTQ40rZIabw==",
"dev": true,
"dependencies": {
"@algolia/client-common": "4.23.3",
"@algolia/requester-common": "4.23.3",
"@algolia/transporter": "4.23.3"
}
},
"node_modules/@algolia/logger-common": {
"version": "4.23.3",
"resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.23.3.tgz",
"integrity": "sha512-y9kBtmJwiZ9ZZ+1Ek66P0M68mHQzKRxkW5kAAXYN/rdzgDN0d2COsViEFufxJ0pb45K4FRcfC7+33YB4BLrZ+g==",
"dev": true
},
"node_modules/@algolia/logger-console": {
"version": "4.23.3",
"resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.23.3.tgz",
"integrity": "sha512-8xoiseoWDKuCVnWP8jHthgaeobDLolh00KJAdMe9XPrWPuf1by732jSpgy2BlsLTaT9m32pHI8CRfrOqQzHv3A==",
"dev": true,
"dependencies": {
"@algolia/logger-common": "4.23.3"
}
},
"node_modules/@algolia/recommend": {
"version": "4.23.3",
"resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.23.3.tgz",
"integrity": "sha512-9fK4nXZF0bFkdcLBRDexsnGzVmu4TSYZqxdpgBW2tEyfuSSY54D4qSRkLmNkrrz4YFvdh2GM1gA8vSsnZPR73w==",
"dev": true,
"dependencies": {
"@algolia/cache-browser-local-storage": "4.23.3",
"@algolia/cache-common": "4.23.3",
"@algolia/cache-in-memory": "4.23.3",
"@algolia/client-common": "4.23.3",
"@algolia/client-search": "4.23.3",
"@algolia/logger-common": "4.23.3",
"@algolia/logger-console": "4.23.3",
"@algolia/requester-browser-xhr": "4.23.3",
"@algolia/requester-common": "4.23.3",
"@algolia/requester-node-http": "4.23.3",
"@algolia/transporter": "4.23.3"
}
},
"node_modules/@algolia/requester-browser-xhr": {
"version": "4.23.3",
"resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.23.3.tgz",
"integrity": "sha512-jDWGIQ96BhXbmONAQsasIpTYWslyjkiGu0Quydjlowe+ciqySpiDUrJHERIRfELE5+wFc7hc1Q5hqjGoV7yghw==",
"dev": true,
"dependencies": {
"@algolia/requester-common": "4.23.3"
}
},
"node_modules/@algolia/requester-common": {
"version": "4.23.3",
"resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.23.3.tgz",
"integrity": "sha512-xloIdr/bedtYEGcXCiF2muajyvRhwop4cMZo+K2qzNht0CMzlRkm8YsDdj5IaBhshqfgmBb3rTg4sL4/PpvLYw==",
"dev": true
},
"node_modules/@algolia/requester-node-http": {
"version": "4.23.3",
"resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.23.3.tgz",
"integrity": "sha512-zgu++8Uj03IWDEJM3fuNl34s746JnZOWn1Uz5taV1dFyJhVM/kTNw9Ik7YJWiUNHJQXcaD8IXD1eCb0nq/aByA==",
"dev": true,
"dependencies": {
"@algolia/requester-common": "4.23.3"
}
},
"node_modules/@algolia/transporter": {
"version": "4.23.3",
"resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.23.3.tgz",
"integrity": "sha512-Wjl5gttqnf/gQKJA+dafnD0Y6Yw97yvfY8R9h0dQltX1GXTgNs1zWgvtWW0tHl1EgMdhAyw189uWiZMnL3QebQ==",
"dev": true,
"dependencies": {
"@algolia/cache-common": "4.23.3",
"@algolia/logger-common": "4.23.3",
"@algolia/requester-common": "4.23.3"
}
},
"node_modules/@babel/parser": {
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz",
"integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==",
"dev": true,
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@docsearch/css": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.0.tgz",
"integrity": "sha512-+sbxb71sWre+PwDK7X2T8+bhS6clcVMLwBPznX45Qu6opJcgRjAp7gYSDzVFp187J+feSj5dNBN1mJoi6ckkUQ==",
"dev": true
},
"node_modules/@docsearch/js": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.6.0.tgz",
"integrity": "sha512-QujhqINEElrkIfKwyyyTfbsfMAYCkylInLYMRqHy7PHc8xTBQCow73tlo/Kc7oIwBrCLf0P3YhjlOeV4v8hevQ==",
"dev": true,
"dependencies": {
"@docsearch/react": "3.6.0",
"preact": "^10.0.0"
}
},
"node_modules/@docsearch/react": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.0.tgz",
"integrity": "sha512-HUFut4ztcVNmqy9gp/wxNbC7pTOHhgVVkHVGCACTuLhUKUhKAF9KYHJtMiLUJxEqiFLQiuri1fWF8zqwM/cu1w==",
"dev": true,
"dependencies": {
"@algolia/autocomplete-core": "1.9.3",
"@algolia/autocomplete-preset-algolia": "1.9.3",
"@docsearch/css": "3.6.0",
"algoliasearch": "^4.19.1"
},
"peerDependencies": {
"@types/react": ">= 16.8.0 < 19.0.0",
"react": ">= 16.8.0 < 19.0.0",
"react-dom": ">= 16.8.0 < 19.0.0",
"search-insights": ">= 1 < 3"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
},
"search-insights": {
"optional": true
}
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
"integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
"integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
"integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
"integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
"integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
"integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
"integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
"integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
"integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
"integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
"integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
"cpu": [
"loong64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
"integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
"cpu": [
"mips64el"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
"integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
"integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
"integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
"integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
"integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
"integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
"integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
"integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
"integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
"integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
"dev": true
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.2.tgz",
"integrity": "sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.17.2.tgz",
"integrity": "sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.17.2.tgz",
"integrity": "sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.17.2.tgz",
"integrity": "sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.17.2.tgz",
"integrity": "sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.17.2.tgz",
"integrity": "sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.17.2.tgz",
"integrity": "sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.17.2.tgz",
"integrity": "sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.17.2.tgz",
"integrity": "sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.17.2.tgz",
"integrity": "sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.17.2.tgz",
"integrity": "sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==",
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.17.2.tgz",
"integrity": "sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.17.2.tgz",
"integrity": "sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.17.2.tgz",
"integrity": "sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.17.2.tgz",
"integrity": "sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz",
"integrity": "sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/@shikijs/core": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.5.2.tgz",
"integrity": "sha512-wSAOgaz48GmhILFElMCeQypSZmj6Ru6DttOOtl3KNkdJ17ApQuGNCfzpk4cClasVrnIu45++2DBwG4LNMQAfaA==",
"dev": true
},
"node_modules/@shikijs/transformers": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-1.5.2.tgz",
"integrity": "sha512-/Sh64rKOFGMQLCvtHeL1Y7EExdq8LLxcdVkvoGx2aMHsYMOn8DckYl2gYKMHRBu/YUt1C38/Amd1Jdh48tWHgw==",
"dev": true,
"dependencies": {
"shiki": "1.5.2"
}
},
"node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true
},
"node_modules/@types/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
"dev": true
},
"node_modules/@types/markdown-it": {
"version": "14.1.1",
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.1.tgz",
"integrity": "sha512-4NpsnpYl2Gt1ljyBGrKMxFYAYvpqbnnkgP/i/g+NLpjEUa3obn1XJCur9YbEXKDAkaXqsR1LbDnGEJ0MmKFxfg==",
"dev": true,
"dependencies": {
"@types/linkify-it": "^5",
"@types/mdurl": "^2"
}
},
"node_modules/@types/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
"dev": true
},
"node_modules/@types/web-bluetooth": {
"version": "0.0.20",
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz",
"integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==",
"dev": true
},
"node_modules/@vitejs/plugin-vue": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz",
"integrity": "sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==",
"dev": true,
"engines": {
"node": "^18.0.0 || >=20.0.0"
},
"peerDependencies": {
"vite": "^5.0.0",
"vue": "^3.2.25"
}
},
"node_modules/@vue/compiler-core": {
"version": "3.4.27",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.27.tgz",
"integrity": "sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==",
"dev": true,
"dependencies": {
"@babel/parser": "^7.24.4",
"@vue/shared": "3.4.27",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.0"
}
},
"node_modules/@vue/compiler-dom": {
"version": "3.4.27",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.27.tgz",
"integrity": "sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw==",
"dev": true,
"dependencies": {
"@vue/compiler-core": "3.4.27",
"@vue/shared": "3.4.27"
}
},
"node_modules/@vue/compiler-sfc": {
"version": "3.4.27",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.27.tgz",
"integrity": "sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA==",
"dev": true,
"dependencies": {
"@babel/parser": "^7.24.4",
"@vue/compiler-core": "3.4.27",
"@vue/compiler-dom": "3.4.27",
"@vue/compiler-ssr": "3.4.27",
"@vue/shared": "3.4.27",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.10",
"postcss": "^8.4.38",
"source-map-js": "^1.2.0"
}
},
"node_modules/@vue/compiler-ssr": {
"version": "3.4.27",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.27.tgz",
"integrity": "sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==",
"dev": true,
"dependencies": {
"@vue/compiler-dom": "3.4.27",
"@vue/shared": "3.4.27"
}
},
"node_modules/@vue/devtools-api": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.2.0.tgz",
"integrity": "sha512-92RsjyH9WKNFO6U/dECUMakq4dm2CeqEDJYLJ8wZ81AnCifpXE7d4jPIjK34ENsPaapA6BSfIZdH/qzLOHiepA==",
"dev": true,
"dependencies": {
"@vue/devtools-kit": "^7.2.0"
}
},
"node_modules/@vue/devtools-kit": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.2.0.tgz",
"integrity": "sha512-Kx+U0QiQg/g714euYKfnCdhTcOycSlH1oyTE57D0sAmisdsRCNLfXcnnIwcFY2jdCpuz9DNbuE0VWQuYF5zAZQ==",
"dev": true,
"dependencies": {
"@vue/devtools-shared": "^7.2.0",
"hookable": "^5.5.3",
"mitt": "^3.0.1",
"perfect-debounce": "^1.0.0",
"speakingurl": "^14.0.1"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/@vue/devtools-shared": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.2.0.tgz",
"integrity": "sha512-gVr3IjKjU7axNvclRgICgy1gq/TDnF1hhBAEox+l5mMXZiTIFVIm1zpcIPssc0HxMDgzy+lXqOVsY4DGyZ+ZeA==",
"dev": true,
"dependencies": {
"rfdc": "^1.3.1"
}
},
"node_modules/@vue/reactivity": {
"version": "3.4.27",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.27.tgz",
"integrity": "sha512-kK0g4NknW6JX2yySLpsm2jlunZJl2/RJGZ0H9ddHdfBVHcNzxmQ0sS0b09ipmBoQpY8JM2KmUw+a6sO8Zo+zIA==",
"dev": true,
"dependencies": {
"@vue/shared": "3.4.27"
}
},
"node_modules/@vue/runtime-core": {
"version": "3.4.27",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.27.tgz",
"integrity": "sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA==",
"dev": true,
"dependencies": {
"@vue/reactivity": "3.4.27",
"@vue/shared": "3.4.27"
}
},
"node_modules/@vue/runtime-dom": {
"version": "3.4.27",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.27.tgz",
"integrity": "sha512-ScOmP70/3NPM+TW9hvVAz6VWWtZJqkbdf7w6ySsws+EsqtHvkhxaWLecrTorFxsawelM5Ys9FnDEMt6BPBDS0Q==",
"dev": true,
"dependencies": {
"@vue/runtime-core": "3.4.27",
"@vue/shared": "3.4.27",
"csstype": "^3.1.3"
}
},
"node_modules/@vue/server-renderer": {
"version": "3.4.27",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.27.tgz",
"integrity": "sha512-dlAMEuvmeA3rJsOMJ2J1kXU7o7pOxgsNHVr9K8hB3ImIkSuBrIdy0vF66h8gf8Tuinf1TK3mPAz2+2sqyf3KzA==",
"dev": true,
"dependencies": {
"@vue/compiler-ssr": "3.4.27",
"@vue/shared": "3.4.27"
},
"peerDependencies": {
"vue": "3.4.27"
}
},
"node_modules/@vue/shared": {
"version": "3.4.27",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz",
"integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==",
"dev": true
},
"node_modules/@vueuse/core": {
"version": "10.9.0",
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.9.0.tgz",
"integrity": "sha512-/1vjTol8SXnx6xewDEKfS0Ra//ncg4Hb0DaZiwKf7drgfMsKFExQ+FnnENcN6efPen+1kIzhLQoGSy0eDUVOMg==",
"dev": true,
"dependencies": {
"@types/web-bluetooth": "^0.0.20",
"@vueuse/metadata": "10.9.0",
"@vueuse/shared": "10.9.0",
"vue-demi": ">=0.14.7"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/core/node_modules/vue-demi": {
"version": "0.14.7",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz",
"integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==",
"dev": true,
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/@vueuse/integrations": {
"version": "10.9.0",
"resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.9.0.tgz",
"integrity": "sha512-acK+A01AYdWSvL4BZmCoJAcyHJ6EqhmkQEXbQLwev1MY7NBnS+hcEMx/BzVoR9zKI+UqEPMD9u6PsyAuiTRT4Q==",
"dev": true,
"dependencies": {
"@vueuse/core": "10.9.0",
"@vueuse/shared": "10.9.0",
"vue-demi": ">=0.14.7"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"async-validator": "*",
"axios": "*",
"change-case": "*",
"drauu": "*",
"focus-trap": "*",
"fuse.js": "*",
"idb-keyval": "*",
"jwt-decode": "*",
"nprogress": "*",
"qrcode": "*",
"sortablejs": "*",
"universal-cookie": "*"
},
"peerDependenciesMeta": {
"async-validator": {
"optional": true
},
"axios": {
"optional": true
},
"change-case": {
"optional": true
},
"drauu": {
"optional": true
},
"focus-trap": {
"optional": true
},
"fuse.js": {
"optional": true
},
"idb-keyval": {
"optional": true
},
"jwt-decode": {
"optional": true
},
"nprogress": {
"optional": true
},
"qrcode": {
"optional": true
},
"sortablejs": {
"optional": true
},
"universal-cookie": {
"optional": true
}
}
},
"node_modules/@vueuse/integrations/node_modules/vue-demi": {
"version": "0.14.7",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz",
"integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==",
"dev": true,
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/@vueuse/metadata": {
"version": "10.9.0",
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.9.0.tgz",
"integrity": "sha512-iddNbg3yZM0X7qFY2sAotomgdHK7YJ6sKUvQqbvwnf7TmaVPxS4EJydcNsVejNdS8iWCtDk+fYXr7E32nyTnGA==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/shared": {
"version": "10.9.0",
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.9.0.tgz",
"integrity": "sha512-Uud2IWncmAfJvRaFYzv5OHDli+FbOzxiVEQdLCKQKLyhz94PIyFC3CHcH7EDMwIn8NPtD06+PNbC/PiO0LGLtw==",
"dev": true,
"dependencies": {
"vue-demi": ">=0.14.7"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/shared/node_modules/vue-demi": {
"version": "0.14.7",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz",
"integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==",
"dev": true,
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/algoliasearch": {
"version": "4.23.3",
"resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.23.3.tgz",
"integrity": "sha512-Le/3YgNvjW9zxIQMRhUHuhiUjAlKY/zsdZpfq4dlLqg6mEm0nL6yk+7f2hDOtLpxsgE4jSzDmvHL7nXdBp5feg==",
"dev": true,
"dependencies": {
"@algolia/cache-browser-local-storage": "4.23.3",
"@algolia/cache-common": "4.23.3",
"@algolia/cache-in-memory": "4.23.3",
"@algolia/client-account": "4.23.3",
"@algolia/client-analytics": "4.23.3",
"@algolia/client-common": "4.23.3",
"@algolia/client-personalization": "4.23.3",
"@algolia/client-search": "4.23.3",
"@algolia/logger-common": "4.23.3",
"@algolia/logger-console": "4.23.3",
"@algolia/recommend": "4.23.3",
"@algolia/requester-browser-xhr": "4.23.3",
"@algolia/requester-common": "4.23.3",
"@algolia/requester-node-http": "4.23.3",
"@algolia/transporter": "4.23.3"
}
},
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true,
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/esbuild": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
"dev": true,
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.20.2",
"@esbuild/android-arm": "0.20.2",
"@esbuild/android-arm64": "0.20.2",
"@esbuild/android-x64": "0.20.2",
"@esbuild/darwin-arm64": "0.20.2",
"@esbuild/darwin-x64": "0.20.2",
"@esbuild/freebsd-arm64": "0.20.2",
"@esbuild/freebsd-x64": "0.20.2",
"@esbuild/linux-arm": "0.20.2",
"@esbuild/linux-arm64": "0.20.2",
"@esbuild/linux-ia32": "0.20.2",
"@esbuild/linux-loong64": "0.20.2",
"@esbuild/linux-mips64el": "0.20.2",
"@esbuild/linux-ppc64": "0.20.2",
"@esbuild/linux-riscv64": "0.20.2",
"@esbuild/linux-s390x": "0.20.2",
"@esbuild/linux-x64": "0.20.2",
"@esbuild/netbsd-x64": "0.20.2",
"@esbuild/openbsd-x64": "0.20.2",
"@esbuild/sunos-x64": "0.20.2",
"@esbuild/win32-arm64": "0.20.2",
"@esbuild/win32-ia32": "0.20.2",
"@esbuild/win32-x64": "0.20.2"
}
},
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true
},
"node_modules/focus-trap": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.4.tgz",
"integrity": "sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==",
"dev": true,
"dependencies": {
"tabbable": "^6.2.0"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/hookable": {
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
"dev": true
},
"node_modules/magic-string": {
"version": "0.30.10",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz",
"integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==",
"dev": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
}
},
"node_modules/mark.js": {
"version": "8.11.1",
"resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz",
"integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==",
"dev": true
},
"node_modules/minisearch": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/minisearch/-/minisearch-6.3.0.tgz",
"integrity": "sha512-ihFnidEeU8iXzcVHy74dhkxh/dn8Dc08ERl0xwoMMGqp4+LvRSCgicb+zGqWthVokQKvCSxITlh3P08OzdTYCQ==",
"dev": true
},
"node_modules/mitt": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
"dev": true
},
"node_modules/nanoid": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/perfect-debounce": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
"integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
"dev": true
},
"node_modules/picocolors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
"dev": true
},
"node_modules/postcss": {
"version": "8.4.38",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
"integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.0.0",
"source-map-js": "^1.2.0"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/preact": {
"version": "10.22.0",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.22.0.tgz",
"integrity": "sha512-RRurnSjJPj4rp5K6XoP45Ui33ncb7e4H7WiOHVpjbkvqvA3U+N8Z6Qbo0AE6leGYBV66n8EhEaFixvIu3SkxFw==",
"dev": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
}
},
"node_modules/rfdc": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz",
"integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==",
"dev": true
},
"node_modules/rollup": {
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.17.2.tgz",
"integrity": "sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==",
"dev": true,
"dependencies": {
"@types/estree": "1.0.5"
},
"bin": {
"rollup": "dist/bin/rollup"
},
"engines": {
"node": ">=18.0.0",
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.17.2",
"@rollup/rollup-android-arm64": "4.17.2",
"@rollup/rollup-darwin-arm64": "4.17.2",
"@rollup/rollup-darwin-x64": "4.17.2",
"@rollup/rollup-linux-arm-gnueabihf": "4.17.2",
"@rollup/rollup-linux-arm-musleabihf": "4.17.2",
"@rollup/rollup-linux-arm64-gnu": "4.17.2",
"@rollup/rollup-linux-arm64-musl": "4.17.2",
"@rollup/rollup-linux-powerpc64le-gnu": "4.17.2",
"@rollup/rollup-linux-riscv64-gnu": "4.17.2",
"@rollup/rollup-linux-s390x-gnu": "4.17.2",
"@rollup/rollup-linux-x64-gnu": "4.17.2",
"@rollup/rollup-linux-x64-musl": "4.17.2",
"@rollup/rollup-win32-arm64-msvc": "4.17.2",
"@rollup/rollup-win32-ia32-msvc": "4.17.2",
"@rollup/rollup-win32-x64-msvc": "4.17.2",
"fsevents": "~2.3.2"
}
},
"node_modules/search-insights": {
"version": "2.13.0",
"resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.13.0.tgz",
"integrity": "sha512-Orrsjf9trHHxFRuo9/rzm0KIWmgzE8RMlZMzuhZOJ01Rnz3D0YBAe+V6473t6/H6c7irs6Lt48brULAiRWb3Vw==",
"dev": true,
"peer": true
},
"node_modules/shiki": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/shiki/-/shiki-1.5.2.tgz",
"integrity": "sha512-fpPbuSaatinmdGijE7VYUD3hxLozR3ZZ+iAx8Iy2X6REmJGyF5hQl94SgmiUNTospq346nXUVZx0035dyGvIVw==",
"dev": true,
"dependencies": {
"@shikijs/core": "1.5.2"
}
},
"node_modules/source-map-js": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/speakingurl": {
"version": "14.0.1",
"resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz",
"integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/tabbable": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
"integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==",
"dev": true
},
"node_modules/vite": {
"version": "5.2.11",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.2.11.tgz",
"integrity": "sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==",
"dev": true,
"dependencies": {
"esbuild": "^0.20.1",
"postcss": "^8.4.38",
"rollup": "^4.13.0"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^18.0.0 || >=20.0.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
},
"peerDependencies": {
"@types/node": "^18.0.0 || >=20.0.0",
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.4.0"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"less": {
"optional": true
},
"lightningcss": {
"optional": true
},
"sass": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"optional": true
}
}
},
"node_modules/vitepress": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.1.4.tgz",
"integrity": "sha512-bWIzFZXpPB6NIDBuWnS20aMADH+FcFKDfQNYFvbOWij03PR29eImTceQHIzCKordjXYBhM/TjE5VKFTUJ3EheA==",
"dev": true,
"dependencies": {
"@docsearch/css": "^3.6.0",
"@docsearch/js": "^3.6.0",
"@shikijs/core": "^1.3.0",
"@shikijs/transformers": "^1.3.0",
"@types/markdown-it": "^14.0.1",
"@vitejs/plugin-vue": "^5.0.4",
"@vue/devtools-api": "^7.0.27",
"@vueuse/core": "^10.9.0",
"@vueuse/integrations": "^10.9.0",
"focus-trap": "^7.5.4",
"mark.js": "8.11.1",
"minisearch": "^6.3.0",
"shiki": "^1.3.0",
"vite": "^5.2.10",
"vue": "^3.4.25"
},
"bin": {
"vitepress": "bin/vitepress.js"
},
"peerDependencies": {
"markdown-it-mathjax3": "^4",
"postcss": "^8"
},
"peerDependenciesMeta": {
"markdown-it-mathjax3": {
"optional": true
},
"postcss": {
"optional": true
}
}
},
"node_modules/vue": {
"version": "3.4.27",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.27.tgz",
"integrity": "sha512-8s/56uK6r01r1icG/aEOHqyMVxd1bkYcSe9j8HcKtr/xTOFWvnzIVTehNW+5Yt89f+DLBe4A569pnZLS5HzAMA==",
"dev": true,
"dependencies": {
"@vue/compiler-dom": "3.4.27",
"@vue/compiler-sfc": "3.4.27",
"@vue/runtime-dom": "3.4.27",
"@vue/server-renderer": "3.4.27",
"@vue/shared": "3.4.27"
},
"peerDependencies": {
"typescript": "*"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
}
}
}
```
## /docx/package.json
```json path="/docx/package.json"
{
"devDependencies": {
"vitepress": "^1.1.4"
},
"scripts": {
"docs:dev": "vitepress dev .",
"docs:build": "vitepress build .",
"docs:preview": "vitepress preview ."
}
}
```
## /download/images/MimicryMusic-full.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/download/images/MimicryMusic-full.png
## /download/images/MimicryMusic-s.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/download/images/MimicryMusic-s.png
## /download/images/android.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/download/images/android.png
## /download/images/apple.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/download/images/apple.png
## /download/images/gravatar.jpg
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/download/images/gravatar.jpg
## /download/images/linux.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/download/images/linux.png
## /download/images/logo-tran.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/download/images/logo-tran.png
## /download/images/musicxx-glass-blue.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/download/images/musicxx-glass-blue.png
## /download/images/musicxx-glass-dark.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/download/images/musicxx-glass-dark.png
## /download/images/musicxx-glass-light.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/download/images/musicxx-glass-light.png
## /download/images/publicbeian.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/download/images/publicbeian.png
## /download/images/windows.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/download/images/windows.png
## /download/index.html
```html path="/download/index.html"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="referrer" content="never">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="keywords"
content="拟声,拟声app,音乐,歌词弹幕,状态栏歌词,桌面歌词,悬浮歌词,共享与控制,下载,新拟物,音乐云盘,阿里云盘,百度云盘,百度网盘,115云盘,webdav,coolight,musicxx,mimicrymusic,mymusic,download,music,lyric,mediaplayer,app,android,ios,windows,macos,linux">
<link rel="icon" href="images/MimicryMusic-s.png">
<title>『拟声』下载官网</title>
<style>
body {
height: 100%;
width: 100%;
margin: 0px;
padding: 0px;
overflow-x: hidden;
background: #2f363e;
}
.mimicry_column {
display: flex;
flex-direction: column;
display: -ms-flexbox;
-ms-flex-direction: column;
}
.mimicry_row {
display: flex;
flex-direction: row;
display: -ms-flexbox;
-ms-flex-direction: row;
}
.cmusic_textCross {
font-size: large;
font-weight: normal;
color: #78778f;
}
.cmusic_textMain {
font-size: large;
font-weight: bolder;
color: #dde8fc;
}
.cmusic_textLink {
font-size: large;
font-weight: normal;
color: #66ccff;
text-decoration: none;
}
.cmusic_appTitle {
font-size: 50px;
font-weight: bolder;
color: #dde8fc;
}
.clip {
background: -webkit-linear-gradient(120deg, #bd34fe 30%, #66ccff);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
@media only screen and (min-width: 640px) {
.cmusic_textCross {
font-size: 3vmin;
}
.cmusic_textLink {
font-size: 3vmin;
}
.cmusic_textMain {
font-size: 3vmin;
}
.cmusic_appTitle {
font-size: 7vmin;
}
}
/*列表ul样式*/
.cmusic_playList_ul {
width: 100%;
height: 100%;
list-style: none;
overflow-y: auto;
/*显示纵向滚动条*/
margin: 0px;
padding: 0px;
display: flex;
display: -ms-flexbox;
flex-direction: column;
}
.cmusic_icon {
width: 50px;
height: 50px;
border-radius: 8px;
padding-left: 0px;
padding-right: 10px;
}
.musicxx_hide {
display: none;
}
.cmusic_image {
border: 0.6vw solid #3c414b;
border-radius: 2.5vw;
background-color: #2e3f48;
width: 12vw;
height: 12vw;
box-shadow: 1vw 1vw 2vw #2c2c30, -1vw -1vw 2vw #3c3c4a;
-webkit-box-shadow: 1vw 1vw 2vw #2c2c30, -1vw -1vw 2vw #3c3c4a;
}
@media only screen and (max-width: 1300px) {
.cmusic_image {
border: 8px solid #3c414b;
border-radius: 50px;
background-color: #2e3f48;
width: 200px;
height: 200px;
box-shadow: 20px 20px 30px #2c2c30, -20px -20px 30px #3c3c4a;
-webkit-box-shadow: 20px 20px 30px #2c2c30, -20px -20px 30px #3c3c4a;
}
}
/*滚动条样式*/
.cmusic_playList_ul::-webkit-scrollbar {
display: none;
}
.cmusic_playList_ul>:first-child {
margin-top: 20px;
}
.cmusic_playList_ul>:last-child {
margin-bottom: 20px;
}
.cmusic_playlist_li_notransform {
width: 90%;
border-radius: 15px;
transform: none;
margin-left: auto;
margin-right: auto;
justify-self: center;
}
.cmusic_playlist_li_notransform:hover {
transform: none;
}
.mimicry_button {
font-size: large;
color: #fff;
background-color: #66ccff;
border: none;
height: 40px;
border-radius: 10px;
padding-top: 10px;
padding-bottom: 10px;
padding-left: 30px;
padding-right: 30px;
align-items: center;
justify-content: center;
box-shadow: 10px 10px 15px #19191e, -5px -5px 10px #4b505a;
-webkit-box-shadow: 10px 10px 15px #19191e, -5px -5px 10px #4b505a;
}
.mimicry_button:hover {
cursor: pointer;
background-color: #66ccffaa;
}
.mimicry_button_normal {
color: #dde8fc;
background-color: #2a3138;
}
.mimicry_button_normal:hover {
cursor: pointer;
color: #66ccff;
background-color: #2a313877;
}
.mimicry_popup {
/* 固定定位 */
position: fixed;
/* 设置在顶层 */
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgb(0, 0, 0);
background-color: rgba(0, 0, 0, 0.4);
}
.mimicry_popup_content {
display: flex;
display: -ms-flexbox;
flex-direction: column;
background-color: #2a3138;
margin: 7%;
padding: 20px;
border-radius: 30px;
}
.pl2 {
display: block;
width: 8em;
height: 8em;
}
.pl2__rect,
.pl2__rect-g {
animation: pl1-a 1.5s cubic-bezier(0.65, 0, 0.35, 1) infinite;
}
.pl2__rect,
.pl2__rect-g {
animation-name: pl2-a;
}
.pl2__rect {
animation-name: pl2-b;
}
.pl2__rect-g .pl2__rect {
transform-origin: 20px 128px;
}
.pl2__rect-g:first-child,
.pl2__rect-g:first-child .pl2__rect {
animation-delay: -0.25s;
}
.pl2__rect-g:nth-child(2),
.pl2__rect-g:nth-child(2) .pl2__rect {
animation-delay: -0.125s;
}
.pl2__rect-g:nth-child(2) .pl2__rect {
transform-origin: 64px 128px;
}
.pl2__rect-g:nth-child(3) .pl2__rect {
transform-origin: 108px 128px;
}
@keyframes pl2-a {
from,
25%,
66.67%,
to {
transform: translateY(0);
}
50% {
animation-timing-function: cubic-bezier(0.33, 0, 0.67, 0);
transform: translateY(-80px);
}
}
@keyframes pl2-b {
from,
to {
animation-timing-function: cubic-bezier(0.33, 0, 0.67, 0);
width: 40px;
height: 24px;
transform: rotate(180deg) translateX(0);
}
33.33% {
animation-timing-function: cubic-bezier(0.33, 1, 0.67, 1);
width: 20px;
height: 64px;
transform: rotate(180deg) translateX(10px);
}
66.67% {
animation-timing-function: cubic-bezier(0.33, 1, 0.67, 1);
width: 28px;
height: 44px;
transform: rotate(180deg) translateX(6px);
}
}
</style>
</head>
<body>
<div class="musicxx_hide" id="mimicry_popup">
<div class="mimicry_popup_content">
<div class="mimicry_row" style="justify-content: space-between;align-items: center;">
<span class="cmusic_textMain">选择系统架构</span>
<button class="mimicry_button" onclick="popupClose()" style="align-self: self-end;">关闭</button>
</div>
<div style="height: 20px;"></div>
<ul id="downlist" class="cmusic_playList_ul" style="background-color: #2f363e;border-radius: 20px;">
<li class="cmusic_playlist_li_notransform"
style="display: flex;justify-content: center;align-items: center;">
<span class="cmusic_textCross">『空空如也』</span>
</li>
</ul>
</div>
</div>
<div class="mimicry_column musicxx_hide" id="adaptBody">
<span class="cmusic_textMain" id="checkEnableJS">您的浏览器已禁用JavaScript,请开启才能正常浏览当前网页</span>
<div class="mimicry_column" style="flex:1; align-items: center; padding-top: 30px;">
<!-- 图标 -->
<img class="cmusic_image" src="images/musicxx-glass-blue.png" alt="拟声" />
<div style="width: 90%; margin-top: 50px;">
<div class="mimicry_column"
style="background-color: #2a3138; border-radius: 15px; margin-top: 20px; padding: 20px; display: block;">
<span class="cmusic_appTitle clip" style="margin-bottom: 15px; display: block;">拟声</span>
<span class="cmusic_textMain"
style="font-size: 30px; font-weight: bolder; margin-bottom: 15px; display: block;">•
拟物风本地/云盘/NAS音视频播放器</span>
<a class="cmusic_textLink" style="margin-bottom: 15px;display: block;"
href="https://blog.mimicry.cool/">•『 官网与使用帮助 』</a>
<a class="cmusic_textLink" style="margin-bottom: 15px;display: block; color: rosybrown"
href="https://blog.mimicry.cool/help/list/selectBranch.html">•『 如何选择版本和分支 』</a>
<a class="cmusic_textLink" style="margin-bottom: 15px;display: block;"
href="https://www.123865.com/s/7dgajv-9HGVv">•『 历史版本下载 』</a>
<a class="cmusic_textLink" style="margin-bottom: 15px;display: block; color: rosybrown"
href="https://blog.coolight.cool/">•『 我们的其他APP 』</a>
<span class="cmusic_textCross">• 若无法安装,请更换浏览器尝试</span>
</div>
</div>
</div>
<div class="mimicry_column" style="flex:1; padding-top: 30px;">
<ul class="cmusic_playList_ul" id="list">
<!-- 列表 -->
<li class="cmusic_playlist_li_notransform"
style="display: flex;flex-direction: column; justify-content: center;align-items: center;">
<div
style="display: flex;flex-direction: column; justify-content: center;align-items: center;margin-bottom: 10px;">
<svg class="pl2" viewBox="0 0 128 128" width="128px" height="128px">
<g fill="var(--primary)">
<g class="pl2__rect-g">
<rect class="pl2__rect" rx="8" ry="8" x="0" y="128" width="40" height="24"
transform="rotate(180)"></rect>
</g>
<g class="pl2__rect-g">
<rect class="pl2__rect" rx="8" ry="8" x="44" y="128" width="40" height="24"
transform="rotate(180)"></rect>
</g>
<g class="pl2__rect-g">
<rect class="pl2__rect" rx="8" ry="8" x="88" y="128" width="40" height="24"
transform="rotate(180)"></rect>
</g>
</g>
<g fill="#66ccff" mask="url(#pl-mask)">
<g class="pl2__rect-g">
<rect class="pl2__rect" rx="8" ry="8" x="0" y="128" width="40" height="24"
transform="rotate(180)"></rect>
</g>
<g class="pl2__rect-g">
<rect class="pl2__rect" rx="8" ry="8" x="44" y="128" width="40" height="24"
transform="rotate(180)"></rect>
</g>
<g class="pl2__rect-g">
<rect class="pl2__rect" rx="8" ry="8" x="88" y="128" width="40" height="24"
transform="rotate(180)"></rect>
</g>
</g>
</svg>
</div>
<span class="cmusic_textMain">正在加载下载列表...</span>
</li>
</ul>
</div>
</div>
<div class="mimicry_column"
style="justify-content: center;align-items: center;padding-top: 50px;padding-bottom: 30px;">
<span class="cmusic_textCross" style="display: block;">© 2022-2026 By『Lumenxx/coolight • 郑泳坤』</span>
<a target="_blank" href="https://beian.miit.gov.cn/" class="cmusic_textCross"
style="display: block;">粤ICP备2021172577号</a>
<a target="_blank" href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=44011302003939"
class="cmusic_textCross" style="display: block;">
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAFOklEQVR42k3UaUzTZwDH8UfmNqPG6Oamc04lM/OYZs4DxQw85vBGBQ/KiiAqiop4C4yhAtPMA8VbZ2SbJo7JVFSYKCAooCCoUCkF5JCWo7S0lZa2XH7X8QJ48fvn/yRPPs/veZ7kEYCgydSR9iazwGQS5haFAIMA1eCmsjt+d0+HPfvnSEh+Q37sFigaCs3CarUK9HrBW4MtjR0BRMenpckgWsx60WbUCZqxJX1Wya2fYmM2rrBc9p7AAY/pRPov4MrPblz0l7bK48Ju0Pxy0TuzQbTrVaLNUG2LqgvUmpVC11QhWtsRYOmVcmI10z6dyikfBzI2T6La50u028dTluhP3J87mf6RI0lHPADjh8amamEyxAuLKb4b2FwtDNYGAdqh1uch6a5TlhAdIsEYOhpZz14UOI6g2HUsz+160BrjQuzdaJZPc+ed4vhtKOtltqYIuNcFWhsVopXyMW/ubGx2GLKY1RI/2qLGUWA/hEL7wbwYN4TiuaN5MWgA6X37QKoPm3dGMnvkHOofh9Roi4+Nkidv6gLRvBBYzk05fXQ3Qhzg2jEJhtXDePVeH+TfDkc+ZBD5fQfwatpIsoUdGu8xpNwPQ4itHDu4F2tJ+AR56sUu0CB/MLT94fLHJ4+FIXruJWH/DKpchlEsPkA+fBBFTvYUzbC3/X/GCyEoch6BIncvokcgoSHBoD+Y+q5Fa98J5t/YE2i56siVC5EIEcbdCx6Ywx3IE++TLRnLq7NOKNOWURk1l/xPetOwfTJ5j7Z3NDxzIgJkK9GW3Y7oBEvvHzqvPjeZzBuhiF7RbPX1pSVjDvcXjyTL+QsKN06kePd0SgOmUnTYBarWErzOByGCyUw5hOnWTFTpx2M6weK/AqpLD07jbU4gQ53P0fP9AB4sGk19uBOF88by7P8te09CNvMrVIEOyNY70eeDDQya8CuNiiAqzzhScXNDaVfDON+m50HTIWE+O3453bFyouhP+9e9KVr7DZluIyncPZXa8Bm0jx/IQzEAITbhHxYFme7IDjmhTPav7ARVifvjy2Jm05a7DlnyIcTnUSwSjsT360fG4N5kjfqYXFtKFo4h13k8/nYOiP4h5CQG0VoQiPL2chryDp7pBoY/eR09i6rYVVDgR+TxCx23LXr4IcQaXPu54jNsKX2FF0JssCWIHSHhUOJFdYIXyr8XoMkOzugE67JCWxWXPSiKdkd5yonGZwHI04I4vVfKluDD3L3iR16cG1LvIFa6BrJzTzgNj3yp+92J0t9+QHlzIfrifdpO0GrM9qxOduP11UUUnphL4a7RWDM8oNQHFItpy1yG4eY8eOkJ+gCQSSiPmGib68Kb227U50hp1iU4d4KAaDW9lKqfrjbUpK2gIMIJedR36PLWo3kgpSZ+GXUPJBjy1vFW5k/5BRdKzi5AnROA6lGA1qi+Puddc1K358tcKd5WPJn15taGWvUTKerMNdQkrkSTtgpNphfq9DXoc9ZQn+JBxR9zqbknQfdqG7UZfry+JtE0VMTMMVu6gc2GVGGqOHLfUrULnWI3VYneqO54ok7xRHlrKeo0b9T/ulF+/nsqbcdSk+RJja25Nm8jRtUOjOqT1026biDGXNHamNa7XhFxSpUsMdUlLaExeyW6VBsWvxCtbVx3Y35HY91TT+oeulKf40tD+f43+roroRbjPTuL6Wo3sPGJaLNkiNeZUUJ2atvAnMgfvR7vc75UFuueVXppWYEyzl1VmyBVlV+TZtUmb0nWFJw/WnwzfN7L69vtNPVJwmpOFhbjpQ7rPyXTHALIQZVcAAAAAElFTkSuQmCC"
style="width:20px;height:20px;margin-right:10px;">
<span class="cmusic_textCross">粤公网安备 44011302003939号</span>
</a>
</div>
<script>
var checkEnableJS = document.getElementById('checkEnableJS');
if (checkEnableJS) {
checkEnableJS.parentElement.removeChild(checkEnableJS);
}
</script>
<script src="run.js"></script>
</body>
</html>
```
## /download/run.js
```js path="/download/run.js"
var listId = "list";
var downlistId = "downlist";
var popupId = "mimicry_popup";
var adaptBodyId = "adaptBody";
var netRespData = [];
function loadData() {
var listWidget = document.getElementById(listId);
if (null != listWidget) {
var innerHtml = "";
for (var i = 0; i < netRespData.length; ++i) {
var item = netRespData[i];
var systemName = systemTypeToViewName(item["type"]);
var branchName = branchTypeToViewName(item["branch"]);
var depict = getDepict(item["type"], item["branch"]);
var li =
'<li class="cmusic_playlist_li_notransform" style="margin-top: 10px;margin-bottom: 10px;">'
+ ' <div class="mimicry_row" style="margin-bottom: 10px;justify-content: space-between;align-items: center;">'
+ ' <div class="mimicry_row" style="align-items: center;">'
+ ' <img class="cmusic_icon" src="images/{{item_icon}}" />'
+ ' <div class="mimicry_row" style="align-items: center;">'
+ ' <span class="cmusic_textMain">{{systemName}} {{branchName}}</span>'
+ ' </div>'
+ ' </div>'
+ ' <button class="mimicry_button" onClick="download({{index}})">下载</button>'
+ ' </div>'
+ ' <div class="mimicry_row">'
+ ' <div style="display: inline-block; white-space: nowrap; background-color: #66ccff; border-radius: 5px; padding-left: 5px; padding-right: 5px; margin-bottom:10px; align-items: center; justify-content: center;">'
+ ' <span class="cmusic_textCross" style="color: #fff;">{{item_version_str}}</span>'
+ ' </div>'
+ ' </div>'
+ ' <span class="cmusic_textCross" style="white-space: pre-line;">{{depict}}</span>'
+ '</li>';
li = li.replace("{{item_icon}}", systemTypeToIconFile(item["type"]));
li = li.replace("{{systemName}}", systemName);
li = li.replace("{{branchName}}", branchName);
li = li.replace("{{index}}", i);
li = li.replace("{{item_version_str}}", item['version_str']);
li = li.replace("{{depict}}", depict);
innerHtml += li;
}
listWidget.innerHTML = innerHtml;
return true;
}
return false;
}
function download(index) {
var item = netRespData[index];
var list = JSON.parse(item["downlist"]);
if (list.length == 0) {
window.open(item["link"], "_blank");
} else if (list.length == 1) {
window.open(list[0]["link"], "_blank");
} else {
var innerHtml = "";
for (var i = 0; i < list.length; ++i) {
var item = list[i];
var li = '<li class="cmusic_playlist_li_notransform" style="padding-top: 20px;padding-bottom: 20px;">'
+ ' <div class="mimicry_row" style="justify-content: space-between;">'
+ ' <span class="cmusic_textMain">{{item_name}}</span>'
+ ' <button '
+ ' class="{{item_class}}"'
+ ' onClick="downloadLink(\'{{item_link}}\')"'
+ ' >下载</button>'
+ ' </div>'
+ ' <span class="cmusic_textCross">{{item_depict}}</span>'
+ '</li>';
li = li.replace("{{item_name}}", item["name"]);
li = li.replace("{{item_button_class}}", item["name"]);
li = li.replace("{{item_class}}", (item["name"] == "arm64") ? "mimicry_button" : 'mimicry_button mimicry_button_normal');
li = li.replace("{{item_link}}", item["link"]);
li = li.replace("{{item_depict}}", item["depict"]);
innerHtml += li;
}
var popupWidget = document.getElementById(downlistId);
popupWidget.innerHTML = innerHtml;
popupOpen();
}
}
function downloadLink(link) {
if (link && link.length > 0) {
window.open(link, "_blank");
}
}
function popupOpen() {
var popupWidget = document.getElementById(popupId);
if (null != popupWidget) {
popupWidget.className = "mimicry_popup";
}
}
function popupClose() {
var popupWidget = document.getElementById(popupId);
if (null != popupWidget) {
popupWidget.className = "musicxx_hide";
}
}
function removeBobbom() {
return false;
}
function autoAdaptLayout() {
var bodyWidget = document.getElementById(adaptBodyId);
if (window.innerHeight > window.innerWidth) {
// 竖屏
bodyWidget.className = "mimicry_column";
} else {
// 横屏
bodyWidget.className = "mimicry_row";
}
}
window.onresize = autoAdaptLayout;
window.onload = function () {
// 自适应横竖屏
autoAdaptLayout();
// 请求下载列表
xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
var rebool = (function () {
if (xhr.status >= 200 && xhr.status < 300) {
try {
netRespData = JSON.parse(xhr.responseText)["data"]
} catch (e) {
console.log(e);
netRespData = ((new Function("return " + xhr.responseText))())["data"];
}
function getNum(item) {
var branchNum = 0;
switch (item.branch) {
case "Main": {
branchNum = 10;
} break;
case "SPA4": {
branchNum = 9;
} break;
case "SPEXAMINE": {
branchNum = 8;
} break;
case "SPEXAMINELIMIT": {
branchNum = 7;
} break;
case "SPA5": {
branchNum = 6;
} break;
}
var systemNum = 0;
switch (item.type) {
case "android": {
systemNum = 10;
} break;
case "ios": {
systemNum = 9;
} break;
case "windows": {
systemNum = 8;
} break;
case "macos": {
systemNum = 7;
} break;
case "linux": {
systemNum = 6;
} break;
}
return systemNum + branchNum * 10;
}
netRespData.sort(function (left, right) {
return (getNum(right) - getNum(left));
});
return loadData();
} else {
console.log("request faild")
}
return false;
})();
if (true === rebool) {
// 加载成功
} else {
// 加载失败
var listWidget = document.getElementById(listId);
if (null != listWidget) {
listWidget.innerHTML =
'<li class="cmusic_playlist_li_notransform" style="display: flex;justify-content: center;align-items: center;">'
+ ' <span class="cmusic_textMain" style="color: #F00">『下载列表加载失败』</span>'
+ '</li>';
}
}
}
};
var url = "/api/procedure/get/download/list?application=Musicxx";
// if (window.location.host.indexOf("127.0.0.1") >= 0) {
// // 本地测试
// url = "https://api.music.bool.run" + url;
// // url = "http://127.0.0.1:1000" + url;
// }
xhr.open("GET", url, true);
xhr.setRequestHeader("Cache-control", "no-store, max-age=0");
xhr.send(null);
};
function systemTypeToViewName(type) {
switch (type) {
case "android":
return "安卓";
case 'ios':
return "IPhone";
case "windows":
return "windows";
case "macos":
return "macos";
case "linux":
return "linux";
}
return "";
}
function systemTypeToIconFile(type) {
switch (type) {
case "android":
return "android.png";
case "ios":
return "apple.png";
case "windows":
return "windows.png";
case "macos":
return "apple.png";
case "linux":
return "linux.png";
}
return "gravatar.jpg";
}
function branchTypeToViewName(type) {
switch (type) {
case "SPA4":
return "安卓4支持版";
case "SPA5":
return "安卓5支持版";
case "Main":
return "";
}
}
function getDepict(system, branch) {
switch (system) {
case "android":
if (branch == "Main") {
return '• 系统要求:Android 7.0 或以上';
} else if (branch == "SPA5") {
return '• 系统要求:Android 5.0 或以上;\n'
+ '• 为兼容安卓5.x,舍弃了很多功能和性能,不建议使用该分支。';
} else if (branch == "SPA4") {
return '• 系统要求:Android 4.1 或以上;\n'
+ '• 为兼容安卓4.x,舍弃了很多功能和性能,且已经很久未更新,不建议使用该分支。';
}
case 'ios':
return '• 系统要求:arm64 IOS 13.0 或以上\n'
+ '• 目前仅开放 未签名安装包,需要自行搜索如何安装\n';
case "windows":
if (branch == "Main") {
return '• 系统要求:x64 Windows 10 或以上';
}
case "macos":
return '• 系统要求:arm64/x64 Macos11 或以上';
case "linux":
return '• 系统要求:x64\n'
+ '• 可能需要安装依赖包,建议查看<a class="cmusic_textLink" href="https://blog.mimicry.cool/help/list/selectBranch.html#linux">安装帮助</a>';
}
return ""
}
```
## /download/style.css
```css path="/download/style.css"
```
## /oauth/baidupan/login_success/images/MimicryMusic-s.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/oauth/baidupan/login_success/images/MimicryMusic-s.png
## /oauth/baidupan/login_success/images/publicbeian.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/oauth/baidupan/login_success/images/publicbeian.png
## /oauth/baidupan/login_success/index.html
```html path="/oauth/baidupan/login_success/index.html"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="./images/MimicryMusic-s.png">
<title>『拟声』绑定百度网盘账号</title>
</head>
<body style="display: flex;flex-direction: column;">
<div style="height: 20px;"></div>
<div id="loading" class="cmusic_column">
<div
style="display: flex;flex-direction: column; justify-content: center;align-items: center;margin-bottom: 10px;">
<svg class="pl2" viewBox="0 0 128 128" width="128px" height="128px">
<g fill="var(--primary)">
<g class="pl2__rect-g">
<rect class="pl2__rect" rx="8" ry="8" x="0" y="128" width="40" height="24"
transform="rotate(180)" />
</g>
<g class="pl2__rect-g">
<rect class="pl2__rect" rx="8" ry="8" x="44" y="128" width="40" height="24"
transform="rotate(180)" />
</g>
<g class="pl2__rect-g">
<rect class="pl2__rect" rx="8" ry="8" x="88" y="128" width="40" height="24"
transform="rotate(180)" />
</g>
</g>
<g fill="#66ccff" mask="url(#pl-mask)">
<g class="pl2__rect-g">
<rect class="pl2__rect" rx="8" ry="8" x="0" y="128" width="40" height="24"
transform="rotate(180)" />
</g>
<g class="pl2__rect-g">
<rect class="pl2__rect" rx="8" ry="8" x="44" y="128" width="40" height="24"
transform="rotate(180)" />
</g>
<g class="pl2__rect-g">
<rect class="pl2__rect" rx="8" ry="8" x="88" y="128" width="40" height="24"
transform="rotate(180)" />
</g>
</g>
</svg>
</div>
<span class="cmusic_textMain">正在绑定百度网盘账号...</span>
</div>
<div id="bind_success" style="display: none;" class="cmusic_column">
<span class="cmusic_textMain">百度网盘账号绑定成功!</span>
<span style="margin-top: 10px;" class="cmusic_textCross">请关闭当前页面,返回App</span>
</div>
<div id="bind_faild" style="display: none;" class="cmusic_column">
<span class="cmusic_textMain">百度网盘账号绑定失败!</span>
<span style="margin-top: 10px;" class="cmusic_textCross">请关闭当前页面,返回App重试</span>
</div>
<script src="./run.js"></script>
</body>
<style>
* {
border: 0;
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
height: 100%;
width: 100%;
margin: 0px;
padding: 0px;
overflow-x: hidden;
background: #2f363e;
}
.cmusic_column {
display: flex;
flex-direction: column;
align-items: center;
}
.cmusic_textCross {
font-size: large;
font-weight: normal;
color: #78778f;
}
.cmusic_textMain {
font-size: x-large;
font-weight: bolder;
color: #dde8fc;
}
.pl2 {
display: block;
width: 8em;
height: 8em;
}
.pl2__rect,
.pl2__rect-g {
animation: pl1-a 1.5s cubic-bezier(0.65, 0, 0.35, 1) infinite;
}
.pl2__rect,
.pl2__rect-g {
animation-name: pl2-a;
}
.pl2__rect {
animation-name: pl2-b;
}
.pl2__rect-g .pl2__rect {
transform-origin: 20px 128px;
}
.pl2__rect-g:first-child,
.pl2__rect-g:first-child .pl2__rect {
animation-delay: -0.25s;
}
.pl2__rect-g:nth-child(2),
.pl2__rect-g:nth-child(2) .pl2__rect {
animation-delay: -0.125s;
}
.pl2__rect-g:nth-child(2) .pl2__rect {
transform-origin: 64px 128px;
}
.pl2__rect-g:nth-child(3) .pl2__rect {
transform-origin: 108px 128px;
}
@keyframes pl2-a {
from,
25%,
66.67%,
to {
transform: translateY(0);
}
50% {
animation-timing-function: cubic-bezier(0.33, 0, 0.67, 0);
transform: translateY(-80px);
}
}
@keyframes pl2-b {
from,
to {
animation-timing-function: cubic-bezier(0.33, 0, 0.67, 0);
width: 40px;
height: 24px;
transform: rotate(180deg) translateX(0);
}
33.33% {
animation-timing-function: cubic-bezier(0.33, 1, 0.67, 1);
width: 20px;
height: 64px;
transform: rotate(180deg) translateX(10px);
}
66.67% {
animation-timing-function: cubic-bezier(0.33, 1, 0.67, 1);
width: 28px;
height: 44px;
transform: rotate(180deg) translateX(6px);
}
}
</style>
</html>
```
## /oauth/baidupan/login_success/run.js
```js path="/oauth/baidupan/login_success/run.js"
const loadingWidget = document.getElementById("loading");
const bindSuccessWidget = document.getElementById("bind_success");
const bindFaildWidget = document.getElementById("bind_faild");
function getQueryString(name) {
let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
let r = window.location.search.substr(1).match(reg);
if (r != null) {
return unescape(r[2]);
};
return null;
}
window.onload = () => {
const jwtToken = getQueryString("state"); // state
const authCode = getQueryString("code");
console.log(jwtToken + " - " + authCode);
xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
loadingWidget.style = "display:none";
if (xhr.status >= 200 && xhr.status < 300) {
try {
const code = JSON.parse(xhr.responseText)["code"]
console.log(code)
if (code == 2000) {
bindSuccessWidget.style = "";
return;
}
} catch (e) {
console.log(e)
}
}
bindFaildWidget.style = "";
}
};
xhr.open(
"POST",
"https://api.music.mimicry.cool/api/user/baiduPan/refreshAccessToken?authCode=" + authCode + "&state="+jwtToken,
true,
);
xhr.send(null);
}
```
## /res/image/image-2.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/res/image/image-2.png
## /res/image/image-3.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/res/image/image-3.png
## /res/image/image-4.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/res/image/image-4.png
## /res/image/image-5.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/res/image/image-5.png
## /res/image/image.png
Binary file available at https://raw.githubusercontent.com/coolight7/musicxx/refs/heads/main/res/image/image.png
## /run.sh
```sh path="/run.sh"
#!/bin/bash
script_dir=$(pwd)
echo $script_dir
cd $script_dir/docx/
sudo ./build_docx.sh
cd $script_dir
chmod -R 0755 ./*
```
## /server-cxx/.clang-format
```clang-format path="/server-cxx/.clang-format"
---
Language: Cpp
# BasedOnStyle: LLVM
AccessModifierOffset: -4
AlignAfterOpenBracket: BlockIndent
AlignArrayOfStructures: Left
AlignConsecutiveAssignments:
Enabled: true
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: true
PadOperators: true
AlignConsecutiveBitFields:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: true
PadOperators: true
AlignConsecutiveDeclarations:
Enabled: true
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: true
PadOperators: true
AlignConsecutiveMacros:
Enabled: true
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignConsecutiveShortCaseStatements:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCaseColons: false
AlignEscapedNewlines: Left
AlignOperands: Align
AlignTrailingComments:
Kind: Always
OverEmptyLines: 0
AllowAllArgumentsOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortEnumsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: Empty
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
AttributeMacros:
- __capability
BinPackArguments: false
BinPackParameters: false
BitFieldColonSpacing: Both
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterExternBlock: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakAfterAttributes: Never
BreakAfterJavaFieldAnnotations: false
BreakArrays: true
BreakBeforeBinaryOperators: All
BreakBeforeConceptDeclarations: Always
BreakBeforeBraces: Attach
BreakBeforeInlineASMColon: OnlyMultiline
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: AfterColon
BreakInheritanceList: AfterComma
BreakStringLiterals: false
ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
EmptyLineAfterAccessModifier: Always
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IfMacros:
- KJ_IF_MAYBE
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
SortPriority: 0
CaseSensitive: false
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
SortPriority: 0
CaseSensitive: false
- Regex: '.*'
Priority: 1
SortPriority: 0
CaseSensitive: false
IncludeIsMainRegex: '(Test)?{{contextString}}#39;
IncludeIsMainSourceRegex: ''
IndentAccessModifiers: false
IndentCaseBlocks: true
IndentCaseLabels: false
IndentExternBlock: AfterExternBlock
IndentGotoLabels: true
IndentPPDirectives: None
IndentRequiresClause: true
IndentWidth: 4
IndentWrappedFunctionNames: true
InsertBraces: false
InsertNewlineAtEOF: false
InsertTrailingCommas: None
IntegerLiteralSeparator:
Binary: 0
BinaryMinDigits: 0
Decimal: 0
DecimalMinDigits: 0
Hex: 0
HexMinDigits: 0
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
KeepEmptyLinesAtEOF: false
LambdaBodyIndentation: Signature
LineEnding: DeriveLF
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: All
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PackConstructorInitializers: Never
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakOpenParenthesis: 0
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyIndentedWhitespace: 0
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Left
PPIndentWidth: -1
QualifierAlignment: Leave
ReferenceAlignment: Pointer
ReflowComments: true
RemoveBracesLLVM: false
RemoveParentheses: Leave
RemoveSemicolon: false
RequiresClausePosition: OwnLine
RequiresExpressionIndentation: OuterScope
SeparateDefinitionBlocks: Always
ShortNamespaceLines: 1
SortIncludes: CaseSensitive
SortJavaStaticImport: Before
SortUsingDeclarations: LexicographicNumeric
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceAroundPointerQualifiers: Default
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeJsonColon: false
SpaceBeforeParens: ControlStatements
SpaceBeforeParensOptions:
AfterControlStatements: true
AfterForeachMacros: true
AfterFunctionDefinitionName: false
AfterFunctionDeclarationName: false
AfterIfMacros: true
AfterOverloadedOperator: false
AfterRequiresInClause: false
AfterRequiresInExpression: false
BeforeNonEmptyParentheses: false
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: Never
SpacesInContainerLiterals: true
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParens: Never
SpacesInParensOptions:
InCStyleCasts: false
InConditionalStatements: false
InEmptyParentheses: false
Other: false
SpacesInSquareBrackets: false
Standard: Latest
StatementAttributeLikeMacros:
- Q_EMIT
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 8
UseTab: Never
VerilogBreakBetweenInstancePorts: true
WhitespaceSensitiveMacros:
- BOOST_PP_STRINGIZE
- CF_SWIFT_NAME
- NS_SWIFT_NAME
- PP_STRINGIZE
- STRINGIZE
...
```
## /server-cxx/.gitattributes
```gitattributes path="/server-cxx/.gitattributes"
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain
```
## /server-cxx/.gitignore
```gitignore path="/server-cxx/.gitignore"
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# coolight -------
# * workflow
mylib/
resources/file/
resources/html/main/
build/
mybuild/
run/
.VSCodeCounter/
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
```
## /server-cxx/CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.6)
set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "build type")
set(CMAKE_C_COMPILER_WORKS ON)
set(CMAKE_CXX_COMPILER_WORKS ON)
PROJECT(
MimicryMusic
LANGUAGES C CXX
)
add_compile_definitions(MyHttpServer_HOME="${PROJECT_SOURCE_DIR}/")
ADD_SUBDIRECTORY("src" "bin")
message(${CMAKE_BUILD_TYPE})
## /server-cxx/CMakeSettings.json
```json path="/server-cxx/CMakeSettings.json"
{
"cmake.configureSettings": { "CMAKE_TOOLCHAIN_FILE": "D:/0Acoolight/App/vcpkg/scripts/buildsystems/vcpkg.cmake" },
"configurations": [
{
"name": "x64-Debug",
"generator": "Ninja",
"configurationType": "Debug",
"inheritEnvironments": [ "msvc_x64_x64" ],
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}",
"cmakeCommandArgs": "",
"buildCommandArgs": "",
"ctestCommandArgs": "",
"variables": [
{
"name": "CMAKE_BUILD_TYPE",
"value": "Debug",
"type": "STRING"
}
]
},
{
"name": "x64-Release",
"generator": "Ninja",
"configurationType": "Release",
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}",
"cmakeCommandArgs": "",
"buildCommandArgs": "",
"ctestCommandArgs": "",
"inheritEnvironments": [ "msvc_x64_x64" ]
},
{
"name": "WSL-GCC-Debug",
"generator": "Ninja",
"configurationType": "Debug",
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}",
"cmakeExecutable": "cmake",
"cmakeCommandArgs": "",
"buildCommandArgs": "",
"ctestCommandArgs": "",
"inheritEnvironments": [ "linux_x64" ],
"wslPath": "${defaultWSLPath}"
},
{
"name": "WSL-GCC-Release",
"generator": "Ninja",
"configurationType": "RelWithDebInfo",
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}",
"cmakeExecutable": "cmake",
"cmakeCommandArgs": "",
"buildCommandArgs": "",
"ctestCommandArgs": "",
"inheritEnvironments": [ "linux_x64" ],
"wslPath": "${defaultWSLPath}"
}
]
}
```
## /server-cxx/LICENSE.txt
MIT License
Copyright (c) [year] [fullname]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
## /server-cxx/README.md
## 项目文件编码
* UTF-8 不带BOM
* 可使用根目录下的 [recoding_utf8.py] 将非UTF-8的代码文件进行一键转换
* 由于部分注释等为中文,如果使用其他编码方式打开,可能会乱码甚至编译不通过
## 编译运行
* vcpkg安装使用:
* 安装vcpkg:
* git clone https://github.com/microsoft/vcpkg
* win端执行 .bat 文件,linux端执行 .sh 文件
* ./bootstrap-vcpkg.[sh | bat]
* 并利用vcpkg安装本项目依赖库:
* vcpkg安装依赖库命令:./vcpkg install [包名]
* openssl
* fmt
* spdlog
* nlohmann-json
* jwt-cpp
* 拉取workflow:
* 在github上下载或 git clone [workflow路径] 源码到 [本项目根目录]/mylib/[对应系统的workflow文件夹]
* win端:
* 文件夹名称:workflow_win
* 下载链接:https://github.com/sogou/workflow/blob/windows
* linux端:
* 文件夹名称:workflow_linux
* 下载链接:https://github.com/sogou/workflow/blob/master
* 编译workflow
* 由于linux端的workflow目录内存在一个叫 BUILD 的文件,因此如果使用平常的名称build则会报错,
这里我们另取名称为 mybuild。
* 注意:一般您只需要编译当前系统的workflow即可。例如您正在win上使用该项目,也只需要下载workflow_win部分
并编译即可开始使用。即使有跨平台的打算,也可以把项目复制到linux上,然后下载workflow_linux部分并编译
即可。
* 在win端直接进入workflow_linux编译一般是会报错的。在linux上进入workflow_win编译也一样。
* 对于类似在win上编译用于linux的部分的操作,如果您不清楚是否需要这么做,那就是不需要。
* win端 / linux端:
* cd { workflow_win 或 workflow_linux }
* win端找到workflow_win目录下CMakeList.txt 中
* option(WORKFLOW_BUILD_STATIC_RUNTIME "Use static runtime" ON)
* 这一句中,把最后的ON改为OFF
* cmake -B mybuild -S . -DCMAKE_TOOLCHAIN_FILE=[vcpkg根目录]/scripts/buildsystems/vcpkg.cmake
* cmake --build mybuild --config Release
* cmake --build mybuild --config Debug
* 运行
* 编译完成后,可执行文件在 [项目根路径]/build/bin/ 内
* 内容分布和src代码文件夹相同
* 由于项目的main.cpp可能存在多个,因此可执行文件可能存在多个,比如[myMusic]的可执行文件
就放在 [项目根路径]/build/bin/myMusic/myMusic[.exe]
## 问题
* workflow编译失败
* 检查vcpkg 和它的库是否安装到位
* 一般不要在win端去编译workflow_linux 或 linux端去编译workflow_win
cmake构建时的提示中应当包含:
* -- Found OpenSSL: optimized;D:/0Acoolight/App/vcpkg/installed/x64-windows/lib/libcrypto.lib;debug;D:/0Acoolight/App/vcpkg/installed/x64-windows/debug/lib/libcrypto.lib (found version "3.0.5")
* 这一句提示找到了openssl依赖
* ......
* -- Building static libraries
* 这一句提示编译时会生成静态库
* ......
* -- Building: NetSSL_OpenSSL
* 这一句提示构建生成中包含NetSSl模块
* ......
* win端项目编译失败
* 检测到“RuntimeLibrary”的不匹配项: 值“MTd_StaticDebug”不匹配值“MDd_DynamicDebug”
* win端workflow_win目录下CMakeList.txt 中
* option(WORKFLOW_BUILD_STATIC_RUNTIME "Use static runtime" ON)
* 这一句中,把最后的ON改为OFF
* mysql连接失败
* task error: [https://blog.csdn.net/s634772208/article/details/81155068]
* 可能是mysql账号验证问题,修改账号验证方式为mysql_native_password
* ALTER USER [root@localhost] IDENTIFIED WITH mysql_native_password BY '[密码]';
* FLUSH PRIVILEGES;
* 在win端,如果wsl中运行本项目,然后mysql放在win里,本项目通过localhost:3389并不能
访问到win端的mysql
## /server-cxx/recoding_utf8.py
```py path="/server-cxx/recoding_utf8.py"
# -- coding: utf-8 --
# 批量修改源码文件的编码方式为utf-8
# 注意,将此 py 放在 sln 同目录
# UTF-8-SIG 对应 vs 设置的 UTF-8(带 BOM)
import os
from chardet import detect
# 代码格式获取
def predict_encoding(file_path):
with open(file_path, 'rb') as f:
d = detect(f.read())
file_encoding = d['encoding']
return file_encoding
# 获取目录下所有源文件绝对路径
def get_filepaths(dir):
filepaths = []
for rootpath, _, files in os.walk(dir):
for file in files:
if file.endswith(".cpp") or file.endswith(".cc") or file.endswith(".h") or file.endswith(".hpp"):
filepaths.append(os.path.join(rootpath, file))
return filepaths
if __name__ == '__main__':
filepaths = get_filepaths("./")
for fn in filepaths:
with open(fn, 'rb+') as fp:
content = fp.read()
if len(content)==0:
continue
else:
codeType = detect(content)['encoding']
content = content.decode(codeType, "ignore").encode("UTF-8")
fp.seek(0)
fp.write(content)
print(fn, ":已修改为 utf8 不带 BOM 编码")
```
## /server-cxx/resources/html/default/200.html
```html path="/server-cxx/resources/html/default/200.html"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello MyHttpServer</title>
</head>
<body>
<div style="display: flex;flex-direction: column;justify-content: center;align-items: center;">
<h1>你好</h1>
<br>
<span style="font-size: larger;">(^ ω ^)</span>
<br>
<span>MimicryHttpServer</span>
</div>
</body>
</html>
```
## /server-cxx/resources/html/default/403.html
```html path="/server-cxx/resources/html/default/403.html"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>403 Forbidden</title>
</head>
<body>
<div style="display: flex;flex-direction: column;justify-content: center;align-items: center;">
<h1>403 请求被拒绝</h1>
<br>
<span style="font-size: larger;">(≖ ω ≖)</span>
<br>
<span>MimicryHttpServer</span>
</div>
</body>
</html>
```
## /server-cxx/resources/html/default/404.html
```html path="/server-cxx/resources/html/default/404.html"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>404 Not Found</title>
</head>
<body>
<div style="display: flex;flex-direction: column;justify-content: center;align-items: center;">
<h1>404 请求资源未找到</h1>
<br>
<span style="font-size: larger;">(° ω °)</span>
<br>
<span>MimicryHttpServer</span>
</div>
</body>
</html>
```
## /server-cxx/resources/html/default/405.html
```html path="/server-cxx/resources/html/default/405.html"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>405 MethodNotAllowed</title>
</head>
<body>
<div style="display: flex;flex-direction: column;justify-content: center;align-items: center;">
<h1>405 请求方法不正确</h1>
<br>
<span style="font-size: larger;">(- ω -)</span>
<br>
<span>MimicryHttpServer</span>
</div>
</body>
</html>
```
## /server-cxx/resources/html/default/EmailCode.html
```html path="/server-cxx/resources/html/default/EmailCode.html"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
width: 100%;
margin: 0px;
padding: 0px;
overflow-x: hidden;
background-color: #dde8fc;
}
.myRow {
width: 100%;
display: flex;
}
.myColumn {
width: 100%;
display: flex;
flex-direction: column;
}
.mytitle {
align-self: center;
}
.mytextMain {
color: #485862;
font-size: x-large;
font-weight: bolder;
}
.mytextCross {
color: #a3b4ce;
font-size: large;
font-weight: bold;
}
/*控制按钮*/
.cmusic_control_btn {
width: 40px;
height: 40px;
border: none;
background: transparent;
border-radius: 10px;
fill: #66ccff;
margin-right: 10px;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 3px 3px 5px rgba(0, 0, 0, 0.1),
-3px -3px 5px rgb(204, 240, 251);
-webkit-box-shadow: 3px 3px 5px rgba(0, 0, 0, 0.1),
-3px -3px 5px rgb(204, 240, 251);
transition: box-shadow .25s;
}
@media (any-hover: hover) {
.cmusic_control_btn:hover {
cursor: pointer;
border: 3px solid #dde8fc;
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2),
0px 0px 0px rgba(255, 255, 255, 0.2),
inset 18px 18px 30px rgba(0, 0, 0, 0.1),
inset -18px -18px 30px rgba(255, 255, 255, 0.2);
transition: box-shadow .25s;
}
}
.cmusic_control_btn svg {
display: inline-block;
height: 24px;
}
.cmusic_control_btn span {
color: #78778f;
font-size: large;
font-weight: bold;
margin-left: 10px;
margin-right: 10px;
}
/*选中按钮样式*/
.cmusic_control_btn_selected {
background: #66ccff;
fill: #fff;
border: 3px solid #fff;
box-shadow: 3px 3px 5px rgba(0, 0, 0, 0.1),
-3px -3px 5px rgb(204, 240, 251),
inset 2px 2px 5px #a5c2cf,
inset -1px -1px 4px #dde8fc;
-webkit-box-shadow: 3px 3px 5px rgba(0, 0, 0, 0.1),
-3px -3px 5px rgb(204, 240, 251),
inset 2px 2px 5px #a5c2cf,
inset -1px -1px 4px #dde8fc;
}
.cmusic_control_btn_selected span {
color: #fff !important;
}
.cmusic_control_btn_disabled {
border: 3px dashed #aab7c2;
}
@media (any-hover: hover) {
.cmusic_control_btn_disabled:hover {
cursor: default;
border: 3px dashed #aab7c2;
}
}
/*通用按钮样式*/
.cmusic_control_btn_noShadow {
width: 40px;
height: 40px;
border: transparent;
background: transparent;
border-radius: 10px;
margin: auto;
fill: #18a9db;
cursor: pointer;
}
.cmusic_control_btn_noShadow svg {
width: 18px;
height: 18px;
}
.myContainer {
width: 70%;
margin-top: 30px;
margin-left: auto;
margin-right: auto;
border-radius: 20px;
padding: 30px;
box-shadow: 11px 11px 22px #a3cde0, -11px -11px 22px #dcfbfd;
-webkit-box-shadow: 11px 11px 22px #a3cde0, -11px -11px 22px #dcfbfd;
}
</style>
</head>
<body class="myColumn">
<div class="myColumn myContainer">
<span class="mytextCross">您好,您的验证码为:</span>
<div class="myRow cmusic_control_btn cmusic_control_btn_selected"
style="width: auto;margin-top: 20px;margin-bottom: 20px;">
<span>777777</span>
</div>
<span class="mytextCross">如非本人操作,请忽略该邮件。</span>
</div>
</body>
</html>
```
## /server-cxx/src/CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.6)
find_package(OpenSSL REQUIRED)
if(WIN32)
find_package(workflow REQUIRED CONFIG HINTS ${CMAKE_SOURCE_DIR}/mylib/workflow_win/)
else()
find_package(workflow REQUIRED CONFIG HINTS ${CMAKE_SOURCE_DIR}/mylib/workflow_linux/)
endif()
include_directories(${OPENSSL_INCLUDE_DIR} ${WORKFLOW_INCLUDE_DIR})
include_directories(${CMAKE_SOURCE_DIR}/src)
ADD_SUBDIRECTORY(myServer)
ADD_SUBDIRECTORY(myMusic)
ADD_SUBDIRECTORY(myTest)
## /server-cxx/src/myMusic/CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.6)
aux_source_directory(. DIR_myMusic)
aux_source_directory(./mymServiceWorkBase DIR_myMusic_mymServiceWorkBase)
aux_source_directory(./test DIR_myMusic_test)
set(NAME_myMusic myMusic)
if (WIN32)
# /wd4819 禁用msvc的4819[代码文件编码和当前系统环境不兼容]警告
# 由于win中文系统默认为GB2312等非UTF-8编码,而项目整体代码都使用UTF-8,因此会有这个警告
# 设置 /utf-8 即可解决
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /utf-8 /wd4200")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8 /wd4200 /Zc:__cplusplus /std:c++20")
else ()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -fPIC -pipe -std=gnu90")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fPIC -pipe -std=c++2a -fexceptions")
endif ()
find_path(JWT_CPP_INCLUDE_DIRS "jwt-cpp/base.h")
add_executable(${NAME_myMusic}
${DIR_myMusic}
${DIR_myMusic_mymServiceWorkBase}
${DIR_myMusic_test}
)
target_include_directories(${NAME_myMusic} PRIVATE
${JWT_CPP_INCLUDE_DIRS}
)
target_link_libraries(${NAME_myMusic}
myServer
)
if (WIN32)
target_compile_definitions(
${NAME_myMusic} PRIVATE
strdup=_strdup
strcasecmp=_stricmp
strncasecmp=_strnicmp
)
endif ()
## /server-cxx/src/myMusic/main.cpp
```cpp path="/server-cxx/src/myMusic/main.cpp"
#include <iostream>
#include <string>
#include "myMusic/test/test.h"
#ifndef MyHttpServer_HOME
#define MyHttpServer_HOME "."
#endif
using std::cin;
using std::cout;
using std::endl;
using std::string;
int main(int argc, char** argv) {
// 指定配置文件路径
std::string config_path;
if (argc >= 2) {
config_path = argv[1];
}
cout << add(7, 77) << endl;
cout << "build time: " << __DATE__ << " | " << __TIME__ << endl;
int num;
cout << "<< input a number to stop" << endl << ">>>";
cin >> num;
return 0;
}
```
## /server-cxx/src/myMusic/mymServiceWorkBase/MyMServiceWorkBase.cpp
```cpp path="/server-cxx/src/myMusic/mymServiceWorkBase/MyMServiceWorkBase.cpp"
#include "myMusic/user/User.h"
#include <fstream>
#include <regex>
#include "MyMServiceWorkBase.h"
#include "myMusic/lyric/LyricDB.h"
using std::string;
using std::regex;
using std::regex_match;
bool MyMServiceWorkBase_c::assert_uid_id(MyHttpTask_c* in_http_task, int& re_uid, int& re_id)
{
UserJWT_c ujwt;
if (User_c::assert_jwt_user(in_http_task, ujwt)) {
auto&& args = in_http_task->get_req()->get_arg();
auto id_it = args.find(this->argName_id);
if (id_it == args.end()) {
in_http_task->get_resp()->out_err(mimicry::ResponeState_e::ValueHiatus);
return false;
}
if (this->isFormat_id(id_it->second) == false) {
in_http_task->get_resp()->out_err(mimicry::ResponeState_e::ValueError);
return false;
}
re_id = atoi(id_it->second.c_str());
re_uid = ujwt.aud;
return true;
}
return false;
}
```
## /server-cxx/src/myMusic/mymServiceWorkBase/MyMServiceWorkBase.h
```h path="/server-cxx/src/myMusic/mymServiceWorkBase/MyMServiceWorkBase.h"
#ifndef MYMUSIC_SRCINFOBASE_H
#define MYMUSIC_SRCINFOBASE_H
#include "myMusic/user/User.h"
#include <string>
#include <initializer_list>
#include "myServer/MyHttpServer.h"
#define _MyArgItem_d(entity, argName, isFormat, enable, necessary) \
MyArgItem_c{entity.argName, #argName, isFormat, enable, necessary}
#define _MyArgItem_notNecessary_d(entity, argName, isFormat) \
_MyArgItem_d(entity, argName, isFormat, true, false)
#define _MyArgItem_necessary_d(entity, argName, isFormat) \
_MyArgItem_d(entity, argName, isFormat, true, true)
// 请求参数验证解析
template<typename T = int>
class MyArgItem_c {
protected:
bool enable = false, //是否使用
necessary = false; //是否必要
const std::function<bool()>
fun_enable = nullptr,
fun_necessary = nullptr;
public:
T& data; //数据
const std::string& argName; //参数名称
const std::function<bool(const std::string&)> format_fun = nullptr; //格式检查函数
bool isEnable() {
if (fun_enable) {
return fun_enable();
}
else {
return enable;
}
}
bool isNecessary() {
if (fun_necessary) {
return fun_necessary();
}
else {
return necessary;
}
}
MyArgItem_c(
T& in_data,
const std::string& in_argName,
const std::function<bool(const std::string&)>& in_format_fun,
const std::function<bool()>& in_fun_able,
const std::function<bool()>& in_fun_necessary
): fun_enable(in_fun_able),
fun_necessary(in_fun_necessary),
data(in_data),
argName(in_argName),
format_fun(in_format_fun){}
MyArgItem_c(
T& in_data,
const std::string& in_argName,
const std::function<bool(const std::string&)>& in_format_fun,
bool in_able,
const std::function<bool()>& in_fun_necessary
):enable(in_able),
fun_necessary(in_fun_necessary),
data(in_data),
argName(in_argName),
format_fun(in_format_fun) {}
MyArgItem_c(
T& in_data,
const std::string& in_argName,
const std::function<bool(const std::string&)>& in_format_fun,
const std::function<bool()>& in_fun_able,
bool in_necessary
):necessary(in_necessary),
fun_enable(in_fun_able),
data(in_data),
argName(in_argName),
format_fun(in_format_fun) {}
MyArgItem_c(
T& in_data,
const std::string& in_argName,
const std::function<bool(const std::string&)>& in_format_fun = nullptr,
bool in_able = true,
bool in_necessary = false
):enable(in_able),
necessary(in_necessary),
data(in_data),
argName(in_argName),
format_fun(in_format_fun) {}
template<typename T1, typename... Args>
static bool analyse(
MyHttpTask_c* in_http_task,
std::multimap<std::string, std::string>& in_map,
MyArgItem_c<T1>&& in_item
) {
if (in_item.isEnable()) {
auto it = in_map.find(in_item.argName);
if (in_item.isNecessary() && it == in_map.end()) {
// 不存在该必要参数
#if _isDebug_d
in_http_task->get_resp()->out_err(
mimicry::ResponeState_e::ValueHiatus,
in_item.argName
);
#else
in_http_task->get_resp()->out_err(
mimicry::ResponeState_e::ValueHiatus
);
#endif
return false;
}
if (it != in_map.end()) {
// 存在参数
if (in_item.format_fun && in_item.format_fun(it->second) == false) {
// 有格式检查函数且检查失败
#if _isDebug_d
in_http_task->get_resp()->out_err(
mimicry::ResponeState_e::ValueError,
in_item.argName
);
#else
in_http_task->get_resp()->out_err(
mimicry::ResponeState_e::ValueError
);
#endif
return false;
}
else {
// TODO: 仅支持 int | string | bool
if constexpr (std::is_same_v<T1, std::string>) {
in_item.data = std::move(it->second);
} else if constexpr(std::is_same_v<T1, int>
|| std::is_same_v<T1, unsigned>
|| std::is_same_v<T1, long>
|| std::is_same_v<T1, long long>
|| std::is_same_v<T1, unsigned long>
|| std::is_same_v<T1, unsigned long long>
) {
in_item.data = atoi(it->second.c_str());
} else if constexpr (std::is_same_v<T1, bool>) {
if (it->second == "true") {
in_item.data = true;
}
else {
in_item.data = false;
}
} else {
assert(false);
}
}
}
}
return true;
}
template<typename T1, typename... Args>
static bool analyse(
MyHttpTask_c* in_http_task,
std::multimap<std::string, std::string>& in_map,
MyArgItem_c<T1>&& in_item,
MyArgItem_c<Args>&&... in_args
) {
return MyArgItem_c<>::analyse(in_http_task, in_map, std::forward<MyArgItem_c<T1>>(in_item))
&& MyArgItem_c<>::analyse(in_http_task, in_map, std::forward<MyArgItem_c<Args>>(in_args)...);
}
/* 解析请求参数
* re_jwt:将会检查http中是否携带jwt,并验证其有效性
* in_http_task:http task
* in_fun_checkJWT:
* 可选,传入nullptr则不执行
* 在检查jwt后执行用户自定义的jwt检查,返回false时为失败,终止后续解析
* in_args:希望接收的参数
*/
template<typename... Args>
static bool analyse(
UserJWT_c& re_jwt,
MyHttpTask_c* in_http_task,
const std::function<bool(UserJWT_c&)>& in_fun_checkJWT,
MyArgItem_c<Args>&&... in_args
) {
if (User_c::assert_jwt_user(in_http_task, re_jwt) == false) {
return false;
}
if (nullptr != in_fun_checkJWT && in_fun_checkJWT(re_jwt) == false) {
in_http_task->get_resp()->out_err(mimicry::ResponeState_e::Permission);
return false;
}
auto&& args = in_http_task->get_req()->get_arg();
//如果解析成功
return MyArgItem_c<>::analyse(in_http_task, args, std::forward<MyArgItem_c<Args>>(in_args)...);
}
template<typename... Args>
static bool analyse(
MyHttpTask_c* in_http_task,
MyArgItem_c<Args>&&... in_args
) {
auto&& args = in_http_task->get_req()->get_arg();
//如果解析成功
return MyArgItem_c<>::analyse(in_http_task, args, std::forward<MyArgItem_c<Args>>(in_args)...);
}
template<typename... Args>
static MyArgItem_c<Args...> builder() {
}
};
class MyMServiceWorkBase_c {
protected:
MyHttpServer_c* server;
std::string path_prefix; // "[/project_name]/api/playlist"
size_t limit_get_list_ids = 20,
limit_get_like_list = 100;
std::string argName_id;
public:
static void insert_fun(MySqlTask_c* task, MyHttpTask_c* in_http_task) {
if (task->is_success()) {
auto&& insert_id = task->get_resp()->get_last_insert_id();
in_http_task->get_resp()->out_success(&insert_id);
} else {
in_http_task->get_resp()->out_err(mimicry::ResponeState_e::Exception);
}
}
//如果song内容没有变化,则修改行数为0,因此部分情况若判断修改的行数会导致请求返回错误
static void update_fun(
MySqlTask_c* task,
MyHttpTask_c* in_http_task,
bool in_check_affected,
int err_code
) {
if (task->is_success() &&
(false == in_check_affected
|| task->get_resp()->get_affected_rows() > 0)) {
in_http_task->get_resp()->out_success();
} else {
in_http_task->get_resp()->out_err(err_code);
}
}
MyMServiceWorkBase_c(
MyHttpServer_c* in_server,
const std::string& in_path_prefix,
const std::string& in_argName_id
) :server(in_server),
path_prefix(in_path_prefix),
argName_id(in_argName_id){}
/* 验证id格式
*/
virtual bool isFormat_id(const std::string& in_id) = 0;
//断言请求中包含uid和id参数
virtual bool assert_uid_id(MyHttpTask_c* in_http_task, int& re_uid, int& re_id);
virtual ~MyMServiceWorkBase_c() {}
};
#endif // !MYMUSIC_SRCINFOBASE_H
```
## /server-cxx/src/myMusic/mymServiceWorkBase/MyMServiceWorkDBBase.cpp
```cpp path="/server-cxx/src/myMusic/mymServiceWorkBase/MyMServiceWorkDBBase.cpp"
#include "./MyMServiceWorkDBBase.h"
```
## /server-cxx/src/myMusic/mymServiceWorkBase/MyMServiceWorkDBBase.h
```h path="/server-cxx/src/myMusic/mymServiceWorkBase/MyMServiceWorkDBBase.h"
#ifndef MYMUSIC_SRCINFODBBASE_H
#define MYMUSIC_SRCINFODBBASE_H
#include <string>
#include <vector>
#include <functional>
#include "myServer/MySqlBase.h"
#include "myServer/MySqlTask.h"
class MyMServiceWorkDB_c {
protected:
MySqlBase_c* sql = nullptr;
MyMServiceWorkDB_c(const std::string& in_url)
:sql(new MySqlBase_c{ in_url }) {}
public:
MyMServiceWorkDB_c(const MyMServiceWorkDB_c&) = delete;
virtual ~MyMServiceWorkDB_c() {
delete this->sql;
this->sql = nullptr;
}
};
#endif // !MYMUSIC_SRCINFODBBASE_H
```
## /server-cxx/src/myMusic/test/test.cpp
```cpp path="/server-cxx/src/myMusic/test/test.cpp"
#include "test.h"
int add(int a, int b) {
return (a + b);
}
```
## /server-cxx/src/myMusic/test/test.h
```h path="/server-cxx/src/myMusic/test/test.h"
#pragma once
int add(int a, int b);
```
## /server-cxx/src/myMusic/vlog.md
## future
## bug
##
* 2024/1/29
* 添加百度云盘账号绑定接口
* 2024/1/28
* 添加阿里云盘的账号绑定接口
* 2023/12/28
* 增加了MediaManager接口
* 2023/8/8
* 增加插件接口支持
* 增加云盘接口支持
* 2023/6/5
* 新增webdav接口
* 调整部分限制
* 2023/4/19
* 增加对qq登录的支持
* 支持在增加经验值
* 调整默认keep-alive
* 修复json为判断是否存在,直接读取导致抛异常的问题
* 增加获取用户歌词信息接口
* 2023/4/8
* 下调用户名最小长度限制
* 修复用户名对utf-8字符的长度判断错误的问题
* 2023/3/29
* 新增发送email接口和对应限制
* 新增设置email接口
* 修改注册接口需要验证email
* 2023/3/28
* 增加go调用email接口
* 调整main中部分模块初始化方式使用share_ptr
* 修复版本值version检查的正则表达式不正确的问题
* 2023/3/24
* 对接go服务的 cos
* 修复 UserJwt 的 status 未初始化的问题
* 2023/3/18
* 增加Ip限制/请求头限制功能
* 增加html返回示例
* 2023/3/10
* 优化Mysql调用
* 2023/3/9
* Recommend:
* 更新主页提示信息
* 主页提示信息全文索引查询
* 修复部分接口查询行数为空时响应体无内容的问题
* 2023/3/8
* 新增Recommend接口
* 新增GlobalApi接口
* 优化MyArgItem_c::analyse()在debug下的错误提示
* dosql日志打印时增加所在文件和行数
* 2023/3/6
* 回退部分接口的代码
* srcInfo部分更名
* 2023/3/5
* 修复歌单收藏数量未变化的问题
* 2023/2/25
* 修复mysql触发器auto_remove_playlist中JSON_LENGTH(NEW.songs)可能返回null导致出错的问题
* 修复注册接口应当返回Json而不是字符串的问题
* 2023/2/21
* 调整歌词结构
* 2023/2/20
* 新增用户最爱歌单
* sql
* playlist增加类型标记
* 2023/2/19
* 资源管理:
* 支持检查文件是否存在
* 更换md5+sha1作为文件hash值校验
* 上传文件增加验证登录状态
* 修复sql插入user_status没有添加status的问题
* 2023/1/11
* 修复收藏功能问题
* 修复用户创建歌单后删除,sql触发器未正确删除pid的问题
* 2023/1/8
* 新增用户身份
* 优化版本发布接口和公告发布接口仅支持admin用户调用
* 2023/1/7
* 新增获取最低版本支持接口
* 优化接口获取为空时不返回错误
* 优化更新版本信息增加"必要"字段和"重要"字段
* 2023/1/6
* 新增程序管理相关接口
* 新增添加版本发布
* 新增获取最新版本信息
* 新增获取从指定版本至今的更新信息
* 新增发布公告接口
* 新增获取最新公告接口
* 2022/12/8
* 修复登录接口返回的数据中jwt_user名称未同步修改的问题
* 2022/11/16
* 调整适配linux
* 2022/11/10
* 新增Proxy代理bili链接
* 新增用户修改基本信息接口
* 2022/11/5
* 新增Srcinfo,优化代码结构
* 2022/10/30
* 新增歌词接口:
* 修改歌词基本信息
* 修改歌词内容
* 重排序创建的歌词
* 2022/10/28
* 新增歌词Lyric:
* 创建歌词
* 删除歌词
* 获取歌词基本信息
* 获取歌词内容
* 获取用户创建的歌词列表
* 点赞歌词
* 匹配用户是否点赞过歌词
* MyLog:
* 新增MAKE_MYLOG宏定义,可以快捷生成模块专属日志
* 调整UserLog_c PlaylistLog_c SearchLog_c使用MAKE_MYLOG
* 调整点赞记录功能调用的日志等级为errlog
* 调整部分功能的update操作增加判断,若更新行数量为0则返回对应场景的错误
* 修复Playlist::get_like_list() 当所有pid都不匹配时却返回错误的问题
* 修复Playlist::get_list_songs() 响应的songs字符串格式错误的问题
* 增加部分注释
* 2022/10/27
* 新增file功能:
* MyFileManager
* FileDB
* 新增通用资源文件管理 SrcManager
* 2022/10/11
* 同步MySql_c::dosql_log()和dosql_errlog()的修改,去除countHttpTask(), 简化调用
* 2022/10/9
* 使用std::format()代替sql拼接
* 2022/10/6
* 重定义用户名和密码允许的字符范围
* 新增获取用户公开信息接口
* 2022/10/4
* 同步MyServer的更新进行调整
* 2022/10/3
* 接口:
* 获取/修改歌单的song内容
* 修改歌单基本信息
* 获取用户创建/收藏的歌单pid
* 点赞
* 收藏
* 取消收藏
* 重排序创建/收藏的歌单
* 判断给定pid是否已经被用户点赞
* 自动登录
* 开启/关闭自动登录
* 搜索
* 优化json返回
* sql
* playlist.song的类型定义为json
* 修改部分触发器
* 修改playlist_create和playlist_star的定义
* 2022/9/26
* 接口:
* 新增创建/删除歌单,和获取歌单基本信息
* 注册接口增加返回uid
* 日志类文件路径由对应的功能类指定并管理
* sql:修改playlist的部分触发器定义,playlist.songs允许null
* 2022/9/21
* 接口:
* 新增获取用户信息
* 登录接口增加返回jwt
* sql:移动部分user表中的用户信息到user_data表中
* 同步sql改动,修改对应Entity定义和UserDB功能
* 2022/09/17
* sql增加触发器
* 新增接口:
* 注册
* 获取用户密码盐
* 登录
* 新增MySqlTask_c::countHttpTask()简化延长HttpTask生命的操作
* 新增MyHttpTask_c::getMyTask() 和 MySqlTask_c::getMyTask()隐藏new操作
* 新增数据库、表和字段名宏定义
* 新增数据库字段类型枚举
* 修复UserLog初始化未加锁,可能导致重复打开同一日志文件而导致崩溃的bug
* 修复短时间内处理同一时出错的bug
* 2022/09/09
* 新建文件夹
* 建立数据库表
* 新增User部分
## /server-cxx/src/myServer/CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.6)
aux_source_directory(. DIR_myServer)
set(NAME_myServer myServer)
#workflow + openssl
#find_library(LIBRT rt)
find_package(OpenSSL REQUIRED)
if(WIN32)
find_package(workflow REQUIRED CONFIG HINTS ${CMAKE_SOURCE_DIR}/mylib/workflow_win/)
if(${CMAKE_BUILD_TYPE} STREQUAL "Release"
OR ${CMAKE_BUILD_TYPE} STREQUAL "MinSizeRel")
find_library(LIBWORKFLOW_PATH workflow ${WORKFLOW_LIB_DIR}/Release)
else()
find_library(LIBWORKFLOW_PATH workflow ${WORKFLOW_LIB_DIR}/Debug)
endif()
else()
find_package(workflow REQUIRED CONFIG HINTS ${CMAKE_SOURCE_DIR}/mylib/workflow_linux/)
find_library(LIBWORKFLOW_PATH workflow ${WORKFLOW_LIB_DIR})
endif()
include_directories(${OPENSSL_INCLUDE_DIR} ${WORKFLOW_INCLUDE_DIR})
#spdlog
find_package(spdlog REQUIRED)
#nlohmann_json
find_package(nlohmann_json CONFIG REQUIRED)
if (APPLE)
set(WORKFLOW_LIB ${LIBWORKFLOW_PATH} pthread OpenSSL::SSL OpenSSL::Crypto)
elseif (WIN32)
set(WORKFLOW_LIB ${LIBWORKFLOW_PATH} ws2_32 wsock32 OpenSSL::SSL OpenSSL::Crypto)
else ()
#set(WORKFLOW_LIB workflow ${LIBRT} pthread OpenSSL::SSL OpenSSL::Crypto)
set(WORKFLOW_LIB ${LIBWORKFLOW_PATH} pthread OpenSSL::SSL OpenSSL::Crypto)
endif ()
if (WIN32)
# /wd4819 禁用msvc的4819[代码文件编码和当前系统环境不兼容]警告
# 由于win中文系统默认为GB2312等非UTF-8编码,而项目整体代码都使用UTF-8,因此会有这个警告
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /utf-8 /wd4200")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8 /wd4200 /Zc:__cplusplus /std:c++20")
else ()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -fPIC -pipe -std=gnu90")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fPIC -pipe -std=c++2a -fexceptions")
endif ()
add_library(${NAME_myServer} STATIC ${DIR_myServer})
target_link_libraries(${NAME_myServer}
${WORKFLOW_LIB}
fmt::fmt-header-only
spdlog::spdlog_header_only
nlohmann_json
)
if (WIN32)
target_compile_definitions(
${NAME_myServer} PRIVATE
strdup=_strdup
strcasecmp=_stricmp
strncasecmp=_strnicmp
)
endif ()
## /server-cxx/src/myServer/Cookie.cpp
```cpp path="/server-cxx/src/myServer/Cookie.cpp"
#include "Cookie.h"
#include <regex>
#include <stdlib.h>
using std::map;
using std::string;
void mimicry::Cookie_c::clear_toStr() {
if (this->toStr != nullptr) {
delete toStr;
toStr = nullptr;
}
}
std::map<std::string, std::string>
mimicry::Cookie_c::from_cookie(const std::string& in_cookie_str) {
std::map<std::string, std::string> arr;
std::regex reg{"(\\S+)=([^;\\s]*)"};
std::sregex_iterator pos{in_cookie_str.begin(), in_cookie_str.end(), reg},
end;
for (; pos != end; ++pos)
arr[pos->str(1)] = std::move(pos->str(2));
return arr;
}
bool mimicry::Cookie_c::set(const string& in_key, const string& in_value) {
this->key = in_key;
this->value = in_value;
this->clear_toStr();
return true;
}
const std::string& mimicry::Cookie_c::get_key() const {
return this->key;
}
const std::string& mimicry::Cookie_c::get_value() const {
return this->value;
}
mimicry::Cookie_c& mimicry::Cookie_c::set_Expires(time_t in_time) {
this->expires = in_time;
this->clear_toStr();
return *this;
}
time_t mimicry::Cookie_c::get_Expires() const {
return this->expires;
}
mimicry::Cookie_c& mimicry::Cookie_c::set_MaxAge(time_t in_age) {
this->max_age = in_age;
this->clear_toStr();
return *this;
}
time_t mimicry::Cookie_c::get_MaxAge() const {
return this->max_age;
}
mimicry::Cookie_c& mimicry::Cookie_c::set_Domain(const string& in_domain) {
this->domain = in_domain;
this->clear_toStr();
return *this;
}
const std::string& mimicry::Cookie_c::get_Domain() const {
return this->domain;
}
mimicry::Cookie_c& mimicry::Cookie_c::set_Path(const string& in_path) {
this->path = in_path;
this->clear_toStr();
return *this;
}
const string& mimicry::Cookie_c::get_Path() const {
return this->path;
}
mimicry::Cookie_c& mimicry::Cookie_c::set_Secure(bool in_bool) {
this->m_is_secure = in_bool;
this->clear_toStr();
return *this;
}
bool mimicry::Cookie_c::is_Secure() const {
return this->m_is_secure;
}
mimicry::Cookie_c& mimicry::Cookie_c::set_HttpOnly(bool in_bool) {
this->m_is_HttpOnly = in_bool;
this->clear_toStr();
return *this;
}
bool mimicry::Cookie_c::is_HttpOnly() const {
return this->m_is_HttpOnly;
}
const std::string& mimicry::Cookie_c::to_string(bool isMust) {
if (toStr == nullptr) {
toStr = new std::string{std::move(this->to_string_const())};
}
return *toStr;
}
std::string mimicry::Cookie_c::to_string_const() const {
if (toStr == nullptr) {
std::string reStr;
if (this->key.empty() == false) {
reStr.reserve(200); // 假设每个键值对共有20个字符长度
reStr += this->key;
if (this->value.empty() == false) {
reStr += '=';
reStr += this->value;
}
reStr += ';';
if (this->path.empty() == false) {
reStr += "Path=";
reStr += this->path;
reStr += ";";
}
if (this->domain.empty() == false) {
reStr += "Domain=";
reStr += this->domain;
reStr += ";";
}
reStr += "Max-Age=";
reStr += std::to_string(this->max_age);
reStr += ';';
if (this->expires > 0) {
reStr += "Expires=";
reStr += std::to_string(this->expires);
reStr += ';';
}
if (this->m_is_HttpOnly)
reStr += "HttpOnly;";
if (this->m_is_secure)
reStr += "Secure;";
}
return reStr;
} else {
return *toStr;
}
}
```
## /server-cxx/src/myServer/Cookie.h
```h path="/server-cxx/src/myServer/Cookie.h"
#ifndef MIMICRY_COOKIES_H
#define MIMICRY_COOKIES_H
#include <map>
#include <string>
#include "GlobalUtil.h"
namespace mimicry {
class Cookie_c {
protected:
std::string key, value;
bool m_is_secure = false, m_is_HttpOnly = false;
time_t max_age = -1, expires = -1;
std::string path; // 路径
std::string domain; // 域名
std::string* toStr = nullptr; // 缓存序列化的string
void clear_toStr();
public:
Cookie_c() {}
Cookie_c(const Cookie_c& in_cookie) {
if (in_cookie.toStr != nullptr) {
this->toStr = new std::string{*(in_cookie.toStr)};
}
}
static std::map<std::string, std::string>
from_cookie(const std::string& in_cookie_str);
// from_set-cookie
bool set(const std::string& in_key, const std::string& in_value);
const std::string& get_key() const;
const std::string& get_value() const;
Cookie_c& set_Expires(time_t in_time);
time_t get_Expires() const;
Cookie_c& set_MaxAge(time_t in_age);
time_t get_MaxAge() const;
Cookie_c& set_Domain(const std::string& in_Domain);
const std::string& get_Domain() const;
Cookie_c& set_Path(const std::string& in_path);
const std::string& get_Path() const;
Cookie_c& set_Secure(bool in_bool);
bool is_Secure() const;
Cookie_c& set_HttpOnly(bool in_bool);
bool is_HttpOnly() const;
const std::string& to_string(bool isMust = false);
std::string to_string_const() const;
operator std::string() {
return to_string();
}
~Cookie_c() {
clear_toStr();
}
};
}; // namespace mimicry
#endif
```
## /server-cxx/src/myServer/GlobalUtil.cpp
```cpp path="/server-cxx/src/myServer/GlobalUtil.cpp"
#include "GlobalUtil.h"
#include <algorithm>
#include <assert.h>
#include <ctype.h>
#include <fstream>
#include <regex>
#include <sstream>
#include <string_view>
#include <time.h>
// #include "unicode/ucnv.h"
// #include "unicode/utypes.h"
// #include "unicode/ucsdet.h"
#include "openssl/md5.h"
#include "openssl/sha.h"
#ifdef _WIN32
#include <WS2tcpip.h>
#include <direct.h>
#include <io.h>
#endif // _WIN32
#ifndef _WIN32
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <unistd.h>
#endif // !_WIN32
using std::fstream;
using std::regex;
using std::regex_match;
using std::string;
using std::string_view;
using std::vector;
using std::map;
vector<string> mimicry::strSplit(const string& in_str, char in_char) {
vector<std::string> re_strlist;
std::istringstream iss{in_str}; // 输入流
for (string item; getline(iss, item, in_char);) { // 以split为分隔符
re_strlist.push_back(item);
}
return re_strlist;
}
void mimicry::strEliminate(string& in_str, char in_char) {
in_str.erase(
std::remove(in_str.begin(), in_str.end(), in_char),
in_str.end()
);
}
bool mimicry::has_string(
const std::string& in_str,
const std::initializer_list<const char*>& in_strs
) {
for (auto it = in_strs.begin(), endit = in_strs.end(); it != endit; ++it) {
if (in_str.find(*it) != string::npos) {
return false;
}
}
return true;
}
bool mimicry::has_char(
const std::string& in_str,
const std::initializer_list<char>& in_chars
) {
const char *endch = in_chars.end(), *ch;
for (auto it = in_str.begin(), endit = in_str.end(); it != endit; ++it) {
for (ch = in_chars.begin(); ch != endch; ++ch) {
if (*it == *ch) {
return false;
}
}
}
return true;
}
std::string& mimicry::removeBetweenSpace(std::string& in_str) {
auto it = in_str.end();
if (in_str.size() > 0) {
do {
--it;
if (*it == ' ')
in_str.erase(it);
else
break;
} while (it != in_str.begin());
}
for (auto it = in_str.begin(); it != in_str.end() && *it == ' '; ++it) {
in_str.erase(it);
}
return in_str;
}
std::string mimicry::toHttpTimeStr(time_t in_time) {
char nowTimeStr[64] = {};
struct tm tm;
#ifdef _WIN32
localtime_s(&tm, &in_time);
#endif // _WIN32
#ifndef _WIN32
localtime_r(&in_time, &tm);
#endif // !_WIN32
strftime(nowTimeStr, sizeof(nowTimeStr), "%a, %d %b %Y %H:%M:%S GMT", &tm);
return string{nowTimeStr};
}
std::string& mimicry::escape(
std::string& in_str,
const std::initializer_list<char>& in_chars
) {
if (in_str.empty()) {
return in_str;
}
const char *endch = in_chars.end(), *ch;
for (auto it = in_str.begin(), endit = in_str.end();;) {
for (ch = in_chars.begin(); ch != endch; ++ch) {
if (*it == *ch) {
it = in_str.insert(it, '\\');
++it;
endit = in_str.end();
break;
}
}
if (it == endit || ++it == endit) {
break;
}
}
return in_str;
}
std::string mimicry::escape(
const std::string& in_str,
const std::initializer_list<char>& integers
) {
std::string str = in_str;
return mimicry::escape(str, integers);
}
std::string mimicry::getMD5(const std::string& in_str) {
return mimicry::getMD5(in_str.c_str(), in_str.size());
}
std::string mimicry::getMD5(const char* in_str, size_t len) {
char result[34] = {};
unsigned char md[MD5_DIGEST_LENGTH] = {};
MD5((const unsigned char*)in_str, len, md);
std::stringstream ss;
ss.fill('0');
for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) {
ss.width(2);
ss << std::hex << int(md[i]);
}
ss.read(result, 32);
return result;
}
std::string mimicry::getSHA1(const std::string& in_str) {
return mimicry::getSHA1(in_str.c_str(), in_str.size());
}
std::string mimicry::getSHA1(const char* in_str, size_t len) {
char result[42] = {};
unsigned char md[SHA_DIGEST_LENGTH];
SHA1((const unsigned char*)in_str, len, md);
std::stringstream ss;
ss.fill('0');
for (int i = 0; i < SHA_DIGEST_LENGTH; ++i) {
ss.width(2);
ss << std::hex << int(md[i]);
}
ss.read(result, 40);
return result;
}
std::string mimicry::getSHA512(const std::string& in_str) {
return mimicry::getSHA512(in_str.c_str(), in_str.size());
}
std::string mimicry::getSHA512(const char* in_str, size_t len) {
char result[130] = {};
unsigned char md[SHA512_DIGEST_LENGTH];
SHA512((unsigned char*)in_str, len, md);
std::stringstream ss;
ss.fill('0');
for (int i = 0; i < SHA512_DIGEST_LENGTH; ++i) {
ss.width(2);
ss << std::hex << int(md[i]);
}
ss.read(result, 128);
return result;
}
size_t mimicry::getUtf8Len(const char* in_str, size_t in_strLen) {
size_t length = 0;
for (size_t i = 0, len = 0; i < in_strLen; i += len) {
unsigned char byte = in_str[i];
if (byte >= 0xFC) // lenght 6
len = 6;
else if (byte >= 0xF8)
len = 5;
else if (byte >= 0xF0)
len = 4;
else if (byte >= 0xE0)
len = 3;
else if (byte >= 0xC0)
len = 2;
else
len = 1;
length++;
}
return length;
}
size_t mimicry::getUtf8Len(const std::string in_str) {
return mimicry::getUtf8Len(in_str.c_str(), in_str.size());
}
bool mimicry::isFormat_limitChar(
const char* in_str,
size_t in_len,
const std::initializer_list<char>& in_chars,
size_t in_minLen,
size_t in_maxLen
) {
assert(in_minLen <= in_maxLen);
size_t length = 0;
for (size_t i = 0, len = 0; i < in_len; i += len) {
unsigned char byte = in_str[i];
if (byte >= 0xFC) // lenght 6
len = 6;
else if (byte >= 0xF8)
len = 5;
else if (byte >= 0xF0)
len = 4;
else if (byte >= 0xE0)
len = 3;
else if (byte >= 0xC0)
len = 2;
else {
// 该字符为ASCii
len = 1;
// 检查该char是否在禁用列表中
for (auto it = in_chars.begin(); it != in_chars.end(); ++it) {
if (in_str[i] == *it) {
return false;
}
}
}
// 增加字符数量
length++;
// 判断是否超过
if (length > in_maxLen) {
return false;
}
}
return (length >= in_minLen);
}
bool mimicry::isFormat_limitChar(
const std::string& in_str,
const std::initializer_list<char>& in_chars,
size_t in_minLen,
size_t in_maxLen
) {
return isFormat_limitChar(
in_str.c_str(),
in_str.size(),
in_chars,
in_minLen,
in_maxLen
);
}
std::string mimicry::url_decode(const std::string& str) {
auto fun_fromHex = [](unsigned char x) -> unsigned char {
unsigned char y;
if (x >= 'A' && x <= 'Z')
y = x - 'A' + 10;
else if (x >= 'a' && x <= 'z')
y = x - 'a' + 10;
else if (x >= '0' && x <= '9')
y = x - '0';
else
y = 0;
return y;
};
string re_str{};
size_t length = str.length();
for (size_t i = 0; i < length; i++) {
if (str[i] == '+') {
re_str += ' ';
} else if (i + 2 < length && str[i] == '%') {
unsigned char high = fun_fromHex((unsigned char)str[++i]);
unsigned char low = fun_fromHex((unsigned char)str[++i]);
re_str += high * 16 + low;
} else
re_str += str[i];
}
return re_str;
}
std::string mimicry::url_encode(const std::string& str) {
auto fun_toHex = [](unsigned char x) -> unsigned char {
return x > 9 ? x + 55 : x + 48;
};
string re_str;
size_t len = str.length();
for (size_t i = 0; i < len; i++) {
if (isalnum((unsigned char)str[i]) || (str[i] == '-') || (str[i] == '_')
|| (str[i] == '.') || (str[i] == '~')) {
re_str += str[i];
} else if (str[i] == ' ') {
re_str += "+";
} else {
re_str += '%';
re_str += fun_toHex((unsigned char)str[i] >> 4);
re_str += fun_toHex((unsigned char)str[i] % 16);
}
}
return re_str;
}
bool mimicry::isFormat_int(const std::string& in_str) {
regex reg{"[+-]?\\d+"};
return regex_match(in_str, reg);
}
bool mimicry::isFormat_unsignedInt(const std::string& in_str) {
regex reg{"\\d+"};
return regex_match(in_str, reg);
}
bool mimicry::isFormat_bool(const std::string& in_str) {
return (in_str == "true" || in_str == "false");
}
bool mimicry::isFormat_MD5(const std::string& in_str) {
if (in_str.size() != MD5_DIGEST_LENGTH * 2) {
return false;
}
regex reg_passwd{"[0-9a-zA-Z]*"};
return regex_match(in_str, reg_passwd);
}
bool mimicry::isFormat_SHA1(const std::string& in_str) {
if (in_str.size() != SHA_DIGEST_LENGTH * 2) {
return false;
}
regex reg_passwd{"[0-9a-zA-Z]*"};
return regex_match(in_str, reg_passwd);
}
bool mimicry::isFormat_SHA512(const std::string& in_str) {
if (in_str.size() != SHA512_DIGEST_LENGTH * 2) {
return false;
}
regex reg_passwd{"[0-9a-zA-Z]*"};
return regex_match(in_str, reg_passwd);
}
bool mimicry::isFormat_ipv4(const std::string& in_ipv4) {
regex reg_passwd{
"(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}"
};
return regex_match(in_ipv4, reg_passwd);
}
bool mimicry::isFormat_email(const std::string& in_str) {
// 匹配任意字符开头,包含@,然后接任意字符,包含.然后接任意字符
regex reg_passwd{"^.+\\@.+\\..+{{contextString}}quot;};
return regex_match(in_str, reg_passwd);
}
unsigned int mimicry::ipv4_strToNum(const std::string& in_ipstr) {
struct in_addr addr {};
struct in6_addr ipv6_inaddr {};
if (inet_pton(AF_INET, in_ipstr.c_str(), (void*)&addr) > 0) {
// 转换成功
#ifdef _WIN32
return (unsigned int)(addr.s_addr);
#else
return addr.s_addr;
#endif // _WIN32
}
return 0;
}
bool mimicry::isExist(
const std::multimap<std::string, std::string>& in_map,
const std::initializer_list<
std::multimap<std::string, std::string>::const_iterator>& in_its
) {
auto map_endit = in_map.end();
for (auto it = in_its.begin(), endit = in_its.end(); it != endit; ++it) {
if (*it == map_endit) {
return false;
}
}
return true;
}
bool mimicry::isExist_dir(const char* in_path) {
#ifdef _WIN32
return (_access(in_path, 0) == 0);
#endif // _WIN32
#ifndef _WIN32
return (access(in_path, 0) == 0);
#endif // !_WIN32
}
bool mimicry::createDir(const std::string& in_path) {
char* view = new char[in_path.size() + 1]{};
bool rebool = true;
for (size_t i = 0, len = in_path.size(); i < len; ++i) {
view[i] = in_path[i];
if (view[i] == '\\' || view[i] == '/' || i == len - 1) {
if (mimicry::isExist_dir(view) == false) {
#ifdef _WIN32
if (_mkdir(view) != 0)
#endif // _WIN32
#ifndef _WIN32
if (mkdir(view, 0) != 0)
#endif
{
rebool = false;
break;
}
}
}
}
delete[] view;
return rebool;
}
bool mimicry::createFile(const std::string& in_path) {
if (in_path.empty()) {
return false;
}
std::ofstream file{in_path};
if (file.is_open() == false) {
auto it = in_path.end();
do {
--it;
if (*it == '\\' || *it == '/')
break;
} while (it != in_path.begin());
if (mimicry::createDir(string(in_path.begin(), it)) == false) {
return false;
}
file.close();
file.open(in_path);
}
return file.is_open();
}
/*
bool mimicry::get_str_encoding(const std::string& in_str, size_t in_use_len,
std::string& re_encoding) { in_use_len = (in_use_len <= in_str.size()) ?
in_use_len : in_str.size(); UCharsetDetector* csd; const UCharsetMatch** csm;
int32_t matchCount = 0;
UErrorCode status = U_ZERO_ERROR;
csd = ucsdet_open(&status);
bool rebool = false;
if (status == U_ZERO_ERROR) {
ucsdet_setText(csd, in_str.c_str(), in_use_len, &status);
if (status == U_ZERO_ERROR) {
csm = ucsdet_detectAll(csd, &matchCount, &status);
if (status == U_ZERO_ERROR && matchCount > 0) {
re_encoding = ucsdet_getName(csm[0], &status);
//分配了内存, 需要释放 if (status == U_ZERO_ERROR){ rebool = true;
}
}
}
}
ucsdet_close(csd);
return rebool;
}
int mimicry::to_encoding(const std::string& in_str,
size_t in_str_len,
std::string& re_str,
const std::string& from_encoding,
const std::string& to_encoding) {
UErrorCode error = U_ZERO_ERROR;
in_str_len = (in_str_len <= in_str.size()) ? in_str_len : in_str.size();
int32_t new_len = in_str_len * 2 + 1;
char* re_str_p = new char[new_len];
ucnv_convert(to_encoding.c_str(), from_encoding.c_str(), re_str_p,
new_len, in_str.c_str(), in_str_len, &error); re_str = re_str_p;
delete[]re_str_p;
return error;
}
bool mimicry::to_encoding_utf8(const std::string& in_str,
size_t in_str_len,
std::string& re_str) {
string from_encoding;
size_t test_len = in_str_len;
if (test_len > 1000)
test_len = 1000;
if (mimicry::get_str_encoding(in_str, in_str_len, from_encoding)) {
if (mimicry::to_encoding(in_str, in_str_len, re_str,
from_encoding, "UTF-8") == 0) { return true;
}
}
return false;
}
*/
std::wstring mimicry::to_wstring(const std::string str) { // string转wstring
size_t len = str.size() * 2; // 预留字节数
setlocale(LC_CTYPE, ""); // 必须调用此函数
wchar_t* p = new wchar_t[len]; // 申请一段内存存放转换后的字符串
mbstowcs(p, str.c_str(), len); // 转换
std::wstring str1(p);
delete[] p; // 释放申请的内存
return str1;
}
std::string mimicry::to_string(const std::wstring str) { // wstring转string
size_t len = str.size() * 4;
setlocale(LC_CTYPE, "");
char* p = new char[len];
wcstombs(p, str.c_str(), len);
std::string str1(p);
delete[] p;
return str1;
}
```
## /server-cxx/src/myServer/GlobalUtil.h
```h path="/server-cxx/src/myServer/GlobalUtil.h"
#ifndef MIMICRY_GLOBALUTIL_H
#define MIMICRY_GLOBALUTIL_H
/*
// __ __
// 0__0o0o0__0
// o8888888o
// 88" * "88
// (| /\ /\ |)
// 0\ _ /0
// ___/`---'\___
// .' \\| |// '.
// / \\||| : |||// \
// / _||||| -:- |||||- \
// | | \\\ - /// | |
// | \_| ''\---/'' |_/ |
// \ .-\__ '-' ___/-. /
// ___'. .' /--.--\ `. .'___
// ."" '< `.___\_<|>_/___.' >' "".
// | | : `- \`.;`\ _ /`;.`/ - ` : | |
// \ \ `_. \_ __\ /__ _/ .-` / /
// =====`-.____`.___ \_____/___.-`___.-'=====
// `=---='
//
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// 天依保佑 永无BUG
*/
#include <initializer_list>
#include <map>
#include <string>
#include <vector>
namespace mimicry {
// 将in_str按in_char分割为多部分
std::vector<std::string> strSplit(const std::string& in_str, char in_char);
// 在 in_str 中删除 in_char 字符
void strEliminate(std::string& in_str, char in_char);
// 将in_str中的in_chars字符用反斜杠\\转义
std::string& escape(std::string& in_str, const std::initializer_list<char>& in_chars);
std::string escape(const std::string& in_str, const std::initializer_list<char>& integers);
bool has_string(const std::string& in_str, const std::initializer_list<const char*>& in_strs);
bool has_char(const std::string& in_str, const std::initializer_list<char>& in_chars);
// 移除字符串两端的空格
std::string& removeBetweenSpace(std::string& in_str);
/*将时间戳转换为Http时间格式
* \param in_time 时间戳,单位:秒
*/
std::string toHttpTimeStr(time_t in_time);
std::string getMD5(const std::string& in_str);
std::string getMD5(const char* in_str, size_t len);
std::string getSHA1(const std::string& in_str);
std::string getSHA1(const char* in_str, size_t len);
std::string getSHA512(const std::string& in_str);
std::string getSHA512(const char* in_str, size_t len);
// url编解码
std::string url_decode(const std::string& str);
std::string url_encode(const std::string& str);
// 无符号值,即只包含数字
bool isFormat_int(const std::string& in_str);
bool isFormat_unsignedInt(const std::string& in_str);
bool isFormat_bool(const std::string& in_str);
bool isFormat_MD5(const std::string& in_str);
// char 类型的 SHA1 值检查: [0-9a-zA-Z]{40}
bool isFormat_SHA1(const std::string& in_str);
bool isFormat_SHA512(const std::string& in_str);
bool isFormat_ipv4(const std::string& in_ipv4);
bool isFormat_email(const std::string& in_str);
unsigned int ipv4_strToNum(const std::string& in_ipstr);
/// <summary>
/// 获取utf-8字符串的字符个数
/// </summary>
/// <param name="in_str"></param>
/// <param name="in_strLen"></param>
/// <returns></returns>
size_t getUtf8Len(const char* in_str, size_t in_strLen);
size_t getUtf8Len(const std::string in_str);
bool isFormat_limitChar(
const char* in_str,
size_t in_len,
const std::initializer_list<char>& in_chars,
size_t in_minLen = 0,
size_t in_maxLen = size_t(-1)
);
bool isFormat_limitChar(
const std::string& in_str,
const std::initializer_list<char>& in_chars,
size_t in_minLen = 0,
size_t in_maxLen = size_t(-1)
);
// bool ip_numTostr(in_ip);
// 判断 in_its 是否都 != in_map.end()
bool isExist(
const std::multimap<std::string, std::string>& in_map,
const std::initializer_list<std::multimap<std::string, std::string>::const_iterator>& in_its
);
// 是否存在目录路径
bool isExist_dir(const char* in_path);
/*创建目录路径
* 如果存在则不操作且返回true
* 如果不存在则创建,并返回是否创建成功
*/
bool createDir(const std::string& in_path);
/*创建文件
* 如果文件存在则返回true
* 如果不存在则创建,并返回是否创建成功
*/
bool createFile(const std::string& in_path);
/*判断字符串编码
* \param in_str 待判断的字符串
* \param in_use_len 取in_str的长度,长度越长耗时越长,但准确性会更高
* \param re_encoding 返回可能性最高的编码格式名称
*
* \return 是否判断成功
*/
/*
bool get_str_encoding(const std::string& in_str, size_t in_use_len,
std::string& re_encoding); int to_encoding(const std::string& in_str, size_t
in_str_len, std::string& re_str, const std::string& from_encoding, const
std::string& to_encoding); bool to_encoding_utf8(const std::string& in_str,
size_t in_str_len,
std::string& re_str);
*/
std::wstring to_wstring(const std::string str);
std::string to_string(const std::wstring str);
/// ## 运行的系统平台信息
class MyPlatform_c {
#ifdef NDEBUG
// release
#define _isDebug_d false
#define _isRelease_d true
static constexpr bool isDebug = false;
static constexpr bool isNotDebug = true;
#else
// debug
#define _isDebug_d true
#define _isRelease_d false
static constexpr bool isDebug = true;
static constexpr bool isNotDebug = false;
#endif // NDEBUG
#ifdef _WIN32
#define _isWindows_d true
static constexpr bool isWindows = true;
static constexpr bool isLinux = false;
#else
#define _isLinux_d true
static constexpr bool isWindows = false;
static constexpr bool isLinux = true;
#endif // _WIN32
};
} // namespace mimicry
#endif // !MIMICRY_GLOBALUTIL_H
```
## /server-cxx/src/myServer/MyBaseEntity.h
```h path="/server-cxx/src/myServer/MyBaseEntity.h"
#ifndef MYBASEENTITY_H
#define MYBASEENTITY_H
#include <string>
#include "MyHttpState.h"
#include "nlohmann/json.hpp"
template<typename T = int>
class MyRespBaseEntity_c {
public:
T* data;
int code;
std::string tip;
MyRespBaseEntity_c(T* in_data, int in_code, const std::string& in_tip = "")
:data(in_data), code(in_code), tip(in_tip) {}
MyRespBaseEntity_c(int in_code, const std::string& in_tip = "")
:data(nullptr), code(in_code), tip(in_tip) {}
bool isSuccess() {
return (code == mimicry::ResponeState_e::OK || code == mimicry::ResponeState_e::OKCustom);
}
template<typename T1 = int>
static MyRespBaseEntity_c<T1>* success(T1* in_data, int in_code = mimicry::ResponeState_e::OK) {
return new MyRespBaseEntity_c<T1>(in_data, in_code);
}
template<typename T1 = int>
static MyRespBaseEntity_c<T1>* error(int in_code, const std::string& in_tip) {
return new MyRespBaseEntity_c<T1>(in_code, in_tip);
}
template<typename T1 = int>
static bool fromWFTask(WFHttpTask* in_task, MyRespBaseEntity_c<T1>& reEntity) {
if (in_task->get_state() == WFT_STATE_SUCCESS) {
// task 正常
std::string reStatucode;
auto resp = in_task->get_resp();
// 请求成功
if (resp->get_status_code(reStatucode) && reStatucode.starts_with("2")) {
const void* body;
size_t len;
if (resp->get_parsed_body(&body, &len)) {
nlohmann::json j = nlohmann::json::parse(
std::string((const char*)body),
nullptr,
false);
from_json(j, reEntity);
return true;
}
}
}
return false;
}
};
template<typename T>
inline void from_json(const nlohmann::json& j, MyRespBaseEntity_c<T>& base) {
try {
// j[name] 不存在时将抛出异常
auto& reCode = j["code"];
if (reCode.is_number_integer()) {
base.code = reCode.get<int>();
}
auto& reTip = j["tip"];
if (reTip.is_string()) {
base.tip = reTip.get<std::string>();
}
auto& redata = j["data"];
if (base.data != nullptr && redata.is_null() == false) {
*base.data = redata.get<T>();
}
}
catch (...) {}
}
template<typename T>
inline void to_json(nlohmann::json& j, const MyRespBaseEntity_c<T>& base) {
if (base.data != nullptr) {
j = nlohmann::json{
{"data", *(base.data)},
{"code", base.code},
{"tip", base.tip}
};
}
else {
j = nlohmann::json{
{"code", base.code},
{"tip", base.tip}
};
}
}
#endif // !MYBASEENTITY_H
```
## /server-cxx/src/myServer/MyDllHelper.cpp
```cpp path="/server-cxx/src/myServer/MyDllHelper.cpp"
#include "MyDllHelper.h"
#include "GlobalUtil.h"
#ifdef _isWindows_d
// -- windows --
#include <wtypes.h>
class MyDllHelperWin_c : public MyDllHelper_c {
protected:
HINSTANCE handle = nullptr;
public:
MyDllHelperWin_c(HINSTANCE in_handle) {
handle = in_handle;
}
static std::shared_ptr<MyDllHelperWin_c> getDllHelper(
const std::string& in_path,
TgNullable_c<int>&& linux_mode = nullptr
) {
HINSTANCE dll = LoadLibrary(in_path.c_str());
if (nullptr != dll) {
return std::make_shared<MyDllHelperWin_c>(dll);
} else {
return nullptr;
}
}
virtual void* getFunction(const std::string& in_name) override {
return GetProcAddress(handle, in_name.c_str());
}
virtual void close() override {
if (nullptr == handle) {
return;
}
FreeLibrary(handle);
handle = nullptr;
}
~MyDllHelperWin_c() {}
};
#elif _isLinux_d
// -- linux --
#include <dlfcn.h>
class MyDllHelperLinux_c : public MyDllHelper_c {
protected:
void* handle = nullptr;
public:
MyDllHelperLinux_c(void* in_handle) {
handle = in_handle;
}
static std::shared_ptr<MyDllHelperLinux_c> getDllHelper(
const std::string& in_path,
TgNullable_c<int>&& linux_mode = nullptr
) {
void* dll = dlopen(in_path.c_str(), RTLD_LAZY);
if (nullptr != dll) {
return std::make_shared<MyDllHelperLinux_c>(dll);
} else {
return nullptr;
}
}
virtual void* getFunction(const std::string& in_name) override {
return dlsym(handle, in_name.c_str());
}
void close() override {
if (nullptr == handle) {
return;
}
dlclose(handle);
handle = nullptr;
}
~MyDllHelperLinux_c() {}
};
#endif
/// -- MyDllHelper_c --
std::shared_ptr<MyDllHelper_c> MyDllHelper_c::getDllHelper(
const std::string& in_path,
TgNullable_c<int>&& linux_mode
) {
#ifdef WIN32
// windows
return MyDllHelperWin_c::getDllHelper(
in_path,
std::forward<TgNullable_c<int>>(linux_mode)
);
#elif _isLinux_d
// linux
return MyDllHelperLinux_c::getDllHelper(
in_path,
std::forward<TgNullable_c<int>>(linux_mode)
);
#endif
}
```
## /server-cxx/src/myServer/MyDllHelper.h
```h path="/server-cxx/src/myServer/MyDllHelper.h"
#pragma once
#include "TgNullable.h"
#include <memory>
#include <string>
class MyDllHelper_c {
protected:
public:
static std::shared_ptr<MyDllHelper_c> getDllHelper(
const std::string& in_path,
TgNullable_c<int>&& linux_mode = nullptr
);
virtual void* getFunction(const std::string& in_name) = 0;
virtual void close() = 0;
virtual ~MyDllHelper_c() {}
};
```
## /server-cxx/src/myServer/MyFactory.cpp
```cpp path="/server-cxx/src/myServer/MyFactory.cpp"
#include "MyFactory.h"
#include <memory>
mimicry::MyTimer_c::MyTimer_c(const std::chrono::seconds& in_seconds, const std::function<void()>& in_fun, bool in_isLoop) {
seconds = in_seconds;
isLoop = in_isLoop;
fun = in_fun;
_createTimer();
}
/// 创建一个循环
/// * 注意
std::shared_ptr<mimicry::MyTimer_c> mimicry::MyTimer_c::cLoop(const std::chrono::seconds& in_seconds, const std::function<void()>& in_fun) {
return std::make_shared<mimicry::MyTimer_c>(in_seconds, in_fun, true);
}
/// 取消定时器的执行
void mimicry::MyTimer_c::cancle() {
isDispose = true;
}
void mimicry::MyTimer_c::_createTimer() {
timer = WFTaskFactory::create_timer_task(
seconds.count(), 0,
[this](WFTimerTask* in_timer) {
this->_onClockDo(in_timer);
});
timer->set_callback([this](WFTimerTask* in_timer) {
this->_onClockDo(in_timer);
});
timer->start();
}
// 计时位置到来时执行
void mimicry::MyTimer_c::_onClockDo(WFTimerTask* in_timer) {
if(false == isDispose) {
fun();
// 由于fun内可能会修改 [isDispose],因此这里再判断一次
if(false == isDispose && isLoop) {
_createTimer();
}
}
}
```
## /server-cxx/src/myServer/MyFactory.h
```h path="/server-cxx/src/myServer/MyFactory.h"
#ifndef MIMICRY_FACTORY_H
#define MIMICRY_FACTORY_H
#include <chrono>
#include "workflow/WFHttpServer.h"
namespace mimicry {
/// 定时器
class MyTimer_c {
protected:
/// 是否为循环计时器
bool isLoop = false;
/// 是否已经销毁
bool isDispose = false;
WFTimerTask* timer = nullptr;
std::function<void()> fun = nullptr;
std::chrono::seconds seconds = std::chrono::seconds::zero();
void _createTimer();
// 计时位置到来时执行
void _onClockDo(WFTimerTask* in_timer);
public:
MyTimer_c(const std::chrono::seconds& in_seconds, const std::function<void()>& in_fun, bool in_isLoop = false);
static std::shared_ptr<mimicry::MyTimer_c> cLoop(const std::chrono::seconds& in_seconds, const std::function<void()>& in_fun);
/// 取消定时器的执行
void cancle();
~MyTimer_c() {
cancle();
}
};
}
#endif
```
## /server-cxx/src/myServer/MyHtml.cpp
```cpp path="/server-cxx/src/myServer/MyHtml.cpp"
#include "MyHtml.h"
#include "MyHttpFile.h"
using std::map;
using std::string;
std::map<int, MyHtml_c::DefaultHtmlValue_c> MyHtml_c::defaultHtml;
void MyHtml_c::defaultHTML_set_html(int in_code, const string& in_html) {
defaultHtml[in_code] = MyHtml_c::DefaultHtmlValue_c(
MyHtml_c::DefaultHtmlValue_c::DefaultHtmlType_e::Html,
in_html
);
}
void MyHtml_c::defaultHTML_set_file(int in_code, const string& in_path) {
defaultHtml[in_code] = MyHtml_c::DefaultHtmlValue_c(
MyHtml_c::DefaultHtmlValue_c::DefaultHtmlType_e::File,
in_path
);
}
bool MyHtml_c::defaultHTML_out(MyHttpTask_c* task, int in_codeInt, const string& in_codeStr) {
auto resp = task->get_resp();
auto it = defaultHtml.find(in_codeInt);
if (it == defaultHtml.end()) {
return false;
}
resp->clear_output_body();
if (it->second.type == MyHtml_c::DefaultHtmlValue_c::DefaultHtmlType_e::Html) {
resp->append_output_body(it->second.str);
} else {
MyHttpFile_c::out_file(task, it->second.str, false, false);
}
if (in_codeStr.empty())
resp->set_status_code(std::to_string(in_codeInt));
else
resp->set_status_code(in_codeStr);
return true;
}
MyHttpProcess_t MyHtml_c::defaultHTML_fun(int in_codeInt) {
return [=](MyHttpTask_c* task) {
defaultHTML_out(task, in_codeInt);
};
}
```
## /server-cxx/src/myServer/MyHtml.h
```h path="/server-cxx/src/myServer/MyHtml.h"
#ifndef MYHTML_H
#define MYHTML_H
#include <map>
#include <string>
#include "MyHttpTask.h"
class MyHtml_c {
protected:
public:
class DefaultHtmlValue_c { // 默认html的map的value类型
public:
enum DefaultHtmlType_e {
Html,
File
};
int type = DefaultHtmlType_e::Html;
std::string str;
DefaultHtmlValue_c() {}
DefaultHtmlValue_c(int in_type, const std::string& in_str) {
type = in_type;
str = in_str;
}
};
static std::map<int, DefaultHtmlValue_c> defaultHtml; // http状态码默认html页面
/* 添加http状态码对应的默认html页面
* \param in_code http状态码
* \param in_html html内容字符串
*/
static void defaultHTML_set_html(int in_code, const std::string& in_html);
/* 添加http状态码对应的默认html页面
* \param in_code http状态码
* \param in_path html文件路径
*/
static void defaultHTML_set_file(int in_code, const std::string& in_path);
/* 将指定http状态码对应的默认html内容输出到task返回
* \param task Http请求的task
* \param in_codeInt int类型http状态码
* \param in_codeStr string类型http状态码,当指定该值时则int型不使用并使用该值
*/
static bool
defaultHTML_out(MyHttpTask_c* task, int in_codeInt, const std::string& in_codeStr = "");
static MyHttpProcess_t defaultHTML_fun(int in_codeInt);
};
#endif // !MYHTML_H
```
## /server-cxx/src/myServer/MyHttpFile.cpp
```cpp path="/server-cxx/src/myServer/MyHttpFile.cpp"
#include "MyHttpFile.h"
#include <fstream>
#include <regex>
#include <sys/stat.h>
#include <time.h>
#ifdef _WIN32
#endif // _WIN32
#ifndef _WIN32
#endif // linux
#include "workflow/WFTaskFactory.h"
#include "MyHttpFile.h"
#include "MyLog.h"
using std::ifstream;
using std::ofstream;
using std::string;
string mimicry::getFileWETag(const mimicry::myStat& in_fstat) {
return "W/\""
+ mimicry::getSHA1(std::to_string(in_fstat.st_size) + std::to_string(in_fstat.mtime))
+ '"';
}
std::string mimicry::getFileMIME(const std::string& in_filePath) {
if (in_filePath.ends_with(".html") || in_filePath.ends_with(".htm")) {
return "text/html";
} else if (in_filePath.ends_with(".css")) {
return "text/css";
} else if (in_filePath.ends_with(".js")) {
return "application/javascript";
} else {
return "";
}
}
bool MyHttpFile_c::out_file(
MyHttpTask_c* task,
const string& path,
bool enable_range,
bool enable_ETag
) {
mimicry::myStat fstat;
if (mimicry::getFileStat(path, fstat) == false)
return false;
auto resp = task->get_resp();
auto req = task->get_req();
bool doRange = false;
string range, fETag;
if (enable_ETag) { // 是否启用ETag
resp->add_header_pair("Cache-Control", "no-cache");
resp->add_header_pair("Vary", "User-Agent");
string eTag;
fETag = mimicry::getFileWETag(fstat);
if (req->find_header("If-None-Match", eTag)) { // 查找请求中是否有ETag
if (fETag == eTag) { // ETag未变化
resp->add_header_pair("Date", mimicry::toHttpTimeStr(time(NULL)).c_str());
resp->add_header_pair("ETag", fETag);
resp->add_header_pair(
"Expires",
mimicry::toHttpTimeStr(time(NULL) + size_t(60 * 60 * 24 * 365)).c_str()
);
resp->set_status_code("304");
return true;
}
}
}
if (enable_range) { // 是否使用分段
resp->add_header_pair("Accept-Ranges", "bytes");
range.reserve(32);
doRange = req->find_header("Range", range);
} else {
resp->add_header_pair("Accept-Ranges", "none");
}
ifstream ifile;
ifile.open(path, std::fstream::binary);
if (ifile.is_open()) {
ifile.seekg(0, std::ios::end);
const size_t flength = size_t(ifile.tellg()); // 计算文件大小
size_t fbegin = 0, fend = 0; // 读取请求的range需要的范围
if (doRange) { // 启用分段下载
std::smatch mlist;
doRange = regex_search(
range,
mlist,
std::regex("\\s*([\\d]*)\\s*-\\s*([\\d]*)")
); // 正则表达式匹配出请求指定的范围
if (doRange) {
auto &&lstr = mlist.str(1), rstr = mlist.str(2);
if (lstr.empty()) { // 设定左范围
fbegin = 0; // 省略左范围
if (rstr.empty()) // 设定右范围
fend = flength;
else { // - end ,返回结尾的end个字节
fbegin = flength - atoi(rstr.c_str()) + 1;
fend = flength;
}
} else {
fbegin = atoi(lstr.c_str());
if (rstr.empty()) // 设定右范围
fend = flength;
else
fend = atoi(rstr.c_str());
}
if (fbegin < 0 || fbegin > flength || fend < 0 || fend > flength || fbegin > fend) {
string re_if_range; // Range 请求范围越界
if (req->find_header(
"If-Range",
re_if_range
)) { // 若存在条件范围控制If-Range则修正
fbegin = 0;
fend = flength;
} else { // 不存在条件范围控制If-Range则返回错误
resp->set_status_code("416");
resp->add_header_pair("Content-Range", "*/" + std::to_string(flength));
return false;
}
} else {
// 文件最后修改时间
resp->add_header_pair(
"Last-Modified",
mimicry::toHttpTimeStr(fstat.mtime).c_str()
);
resp->set_status_code("206");
}
}
}
if (doRange == false) { // 当请求不指定range或正则表达式匹配失败时
fbegin = 0;
fend = flength;
}
const size_t readLen = fend - fbegin;
char* buffer = new char[readLen + 1]{};
ifile.seekg(fbegin, std::ios::beg);
ifile.read(buffer, readLen);
const size_t real_readLen = ifile.gcount();
ifile.close();
if (doRange) {
resp->add_header_pair(
"Content-Range",
"bytes " + std::to_string(fbegin) + "-" + std::to_string(fbegin + real_readLen - 1)
+ "/" + std::to_string(flength)
);
}
if (enable_ETag) {
resp->add_header_pair("ETag", fETag);
}
resp->add_header_pair("Date", mimicry::toHttpTimeStr(time(NULL)).c_str());
resp->clear_cache_header();
resp->append_output_body(buffer, real_readLen);
delete[] buffer;
// 尝试添加content-type,
// 部分文件请求需要类型才可以正常使用,比如.js需要指定类型,浏览器才会运行它
auto reMIME = mimicry::getFileMIME(path);
if (reMIME.empty() == false) {
resp->add_header_pair("Content-Type", reMIME.c_str());
}
return true;
} else
return false;
}
bool MyHttpFile_c::out_dir(
MyHttpTask_c* in_task,
const std::string& in_dir_path,
const std::string& in_defaultFileName
) {
auto req = in_task->get_req();
const string& path = req->get_path();
string router_path{req->get_router_path()};
while (false == router_path.empty() && (router_path.back() == '*' || router_path.back() == '/')
) {
router_path.pop_back();
}
if (path.size() > router_path.size()) {
auto file_path = in_dir_path + string(path.c_str() + router_path.size());
if (file_path.back() == '/' && in_defaultFileName.empty() == false) {
file_path += in_defaultFileName;
}
return MyHttpFile_c::out_file(in_task, file_path);
} else
return false;
}
bool MyHttpFile_c::upload_file(
MyHttpTask_c* in_task,
const std::string& in_path,
int max_size,
int body_type
) {
string data, type;
in_task->get_req()->find_header("Content-Type", type);
if ((body_type & mimicry::HttpBodyType_e::Form_data_int)
&& type == mimicry::HttpBodyType_e::Form_data) {
auto&& form = in_task->get_req()->get_form_data();
if (form.empty() == false) {
for (auto it = form.begin(); it != form.end(); ++it)
if (it->second.isFile) {
data = std::move(it->second.value);
}
}
}
if (data.empty() && (body_type & mimicry::HttpBodyType_e::Binary_int)
&& (type == mimicry::HttpBodyType_e::Binary)) {
data = std::move(in_task->get_req()->get_parsed_body());
}
if (data.empty() == false && (max_size < 0 || data.size() <= size_t(max_size))) {
return MyHttpFile_c::save_file(data, in_path);
}
return false;
}
bool MyHttpFile_c::save_file(const string& in_data, const std::string& in_path) {
bool rebool = false;
if (mimicry::createFile(in_path)) {
ofstream ofile{in_path, std::ios::binary};
if (ofile.is_open()) {
ofile.write(in_data.c_str(), in_data.size());
rebool = true;
}
ofile.close();
}
return rebool;
}
size_t MyHttpFile_c::read_file(
const std::string& in_path,
std::string& re_data,
size_t shift,
int read_len
) {
ifstream ifile;
ifile.open(in_path, std::fstream::binary);
if (ifile.is_open()) {
size_t flength = 0, rlen = 0, real_rlen = 0;
ifile.seekg(0, std::ios::end);
flength = size_t(ifile.tellg()) - shift; // 计算文件大小
if (read_len > 0)
rlen = read_len;
else
rlen = flength;
char* read_str = new char[rlen + 1]{};
ifile.seekg(shift, std::ios::beg);
ifile.read(read_str, rlen);
real_rlen = ifile.gcount();
ifile.close();
re_data = string(read_str, real_rlen);
delete[] read_str;
return real_rlen;
}
return 0;
}
bool mimicry::getFileStat(const std::string& in_path, mimicry::myStat& in_stat) {
struct stat buf;
if (stat(in_path.c_str(), &buf) == 0) {
in_stat.st_size = buf.st_size;
in_stat.atime = buf.st_atime;
in_stat.ctime = buf.st_ctime;
in_stat.mtime = buf.st_mtime;
return true;
} else
return false;
}
```
## /server-cxx/src/myServer/MyHttpFile.h
```h path="/server-cxx/src/myServer/MyHttpFile.h"
#ifndef MYHTTPFILE_H
#define MYHTTPFILE_H
#include <string>
#include "MyHttpTask.h"
// 文件/文件夹
class MyHttpFile_c {
protected:
public:
// 提供文件下载
static bool out_file(
MyHttpTask_c* in_task,
const std::string& in_path,
bool enable_range = true,
bool enable_ETag = true
);
// 提供文件夹内所有文件下载
static bool out_dir(
MyHttpTask_c* in_task,
const std::string& in_dir_path,
const std::string& in_defaultFileName = ""
);
/*文件上传
* 支持form-data 和 binary 类型的请求体, 默认两个都允许
* 仅保存一个文件,即包含fileName和fileType
* 如果请求体包含了多个文件,则只保存第一个符合的
*
* \param in_task
* \param in_path 文件保存路径,该路径需要包含具体文件名
* \param max_size 限制文件最大大小,默认负值不限制
* \param body_type 指定允许的body类型
*/
static bool upload_file(
MyHttpTask_c* in_task,
const std::string& in_path,
int max_size = -1,
int body_type = mimicry::HttpBodyType_e::Form_data_int | mimicry::HttpBodyType_e::Binary_int
);
// 将in_data保存的in_path指定的路径(当路径不存在时将自动创建)
static bool save_file(const std::string& in_data, const std::string& in_path);
/*读取文件
* \param in_path 文件所在路径
* \param re_data 读取的数据将保存到re_data中
* \param shift 偏移shift个字节后读取,默认0不偏移
* \param read_len 读取最大长度,默认-1读取整个文件
*
* \return 返回实际读取到的字节数
*/
static size_t read_file(
const std::string& in_path,
std::string& re_data,
size_t shift = 0,
int read_len = -1
);
};
// 相关全局函数定义
namespace mimicry {
struct myStat {
unsigned long st_size;
// 时间单位:秒
time_t mtime;
time_t atime;
time_t ctime;
};
std::string getFileWETag(const mimicry::myStat& in_fstat);
bool getFileStat(const std::string& in_path, mimicry::myStat& in_stat);
std::string getFileMIME(const std::string& filePath);
} // namespace mimicry
#endif // !MYHTTPFILE_H
```
## /server-cxx/src/myServer/MyHttpMethod.cpp
```cpp path="/server-cxx/src/myServer/MyHttpMethod.cpp"
#include "MyHttpMethod.h"
#include <algorithm>
#include <cctype>
#include <cmath>
using std::string;
bool MyHttpMethod_c::isAllow(int in_m1, int in_m2) {
return in_m1 & in_m2;
}
bool MyHttpMethod_c::isAllow(const string& in_m1, int in_m2) {
return MyHttpMethod_c::strToInt(in_m1) & in_m2;
}
bool MyHttpMethod_c::isAllow_strOne(const string& in_m1, int in_m2) {
return MyHttpMethod_c::strToInt_one(in_m1) & in_m2;
}
bool MyHttpMethod_c::isAllow(const string& in_m1, const string& in_m2) {
if (in_m1.size() >= in_m2.size())
return in_m1.find(in_m2) != string::npos;
else
return in_m2.find(in_m1) != string::npos;
}
int MyHttpMethod_c::strToInt(const string& in_str) {
string upStr{in_str};
// ת»»×ÖĸΪ´óд
std::transform(upStr.begin(), upStr.end(), upStr.begin(), ::toupper);
int reInt = 0;
for (int i = MyHttpMethod_e::HttpMethod_size; i-- > 0;) {
if (upStr.find(myHttpMethodArr[i].mStr) != string::npos) {
reInt = reInt | myHttpMethodArr[i].mInt;
}
}
return reInt;
}
int MyHttpMethod_c::strToInt_one(const string& in_str) {
string upStr{in_str};
std::transform(upStr.begin(), upStr.end(), upStr.begin(), ::toupper);
for (int i = 0; i < MyHttpMethod_e::HttpMethod_size; ++i) {
if (upStr == myHttpMethodArr[i].mStr) {
return myHttpMethodArr[i].mInt;
}
}
return 0;
}
string MyHttpMethod_c::intToStr(int in_int) {
string reStr;
for (int i = MyHttpMethod_e::HttpMethod_size; i-- > 0;) {
if (in_int & myHttpMethodArr[i].mInt) {
reStr += myHttpMethodArr[i].mStr + " ";
}
}
return reStr;
}
string MyHttpMethod_c::intToStr_one(int in_int) {
for (int i = 0; i < MyHttpMethod_e::HttpMethod_size; ++i) {
if (in_int & myHttpMethodArr[i].mInt) {
return myHttpMethodArr[i].mStr;
}
}
return "";
}
int MyHttpMethod_c::toIndex_one(int in_method) {
int reNum = int(log2(in_method));
if (reNum >= 0 && reNum < MyHttpMethod_e::HttpMethod_size)
return reNum;
else
return -1;
}
int MyHttpMethod_c::toIndex_one(const string& in_method) {
string upStr{in_method};
std::transform(upStr.begin(), upStr.end(), upStr.begin(), ::toupper);
for (int i = 0; i < MyHttpMethod_e::HttpMethod_size; ++i) {
if (upStr == myHttpMethodArr[i].mStr) {
return i;
}
}
return -1;
}
```
## /server-cxx/src/myServer/MyHttpMethod.h
```h path="/server-cxx/src/myServer/MyHttpMethod.h"
#ifndef HTTPMETHOD_H
#define HTTPMETHOD_H
#include <string>
// http请求类型枚举
class MyHttpMethod_e {
public:
static constexpr int HttpMethod_size = 8, // http请求类型的数量
Get = 1, Post = 2, Put = 4, Delete = 8, Head = 16, Options = 32, Trace = 64, Connect = 128,
Min = MyHttpMethod_e::Get, Max = MyHttpMethod_e::Connect;
};
class MyHttpMethodArrItem_s {
public:
int mNum; // 在数组中的下标
int mInt; // method的枚举数值
std::string mStr; // 类型字符串名称
MyHttpMethodArrItem_s(int in_mNum, int in_mInt, const std::string& in_mStr) {
mNum = in_mNum;
mInt = in_mInt;
mStr = in_mStr;
}
};
// HTTP请求类型数组,便于循环判断
static const MyHttpMethodArrItem_s myHttpMethodArr[MyHttpMethod_e::HttpMethod_size]
= {MyHttpMethodArrItem_s(0, MyHttpMethod_e::Get, "GET"),
MyHttpMethodArrItem_s(1, MyHttpMethod_e::Post, "POST"),
MyHttpMethodArrItem_s(2, MyHttpMethod_e::Put, "PUT"),
MyHttpMethodArrItem_s(3, MyHttpMethod_e::Delete, "DELETE"),
MyHttpMethodArrItem_s(4, MyHttpMethod_e::Head, "HEAD"),
MyHttpMethodArrItem_s(5, MyHttpMethod_e::Options, "OPTIONS"),
MyHttpMethodArrItem_s(6, MyHttpMethod_e::Trace, "TRACE"),
MyHttpMethodArrItem_s(7, MyHttpMethod_e::Connect, "CONNECT")};
// HTTP请求类型的相关操作方法
class MyHttpMethod_c {
public:
// 判断in_m1与in_m2包含的请求类型是否有交集
static bool isAllow(int in_m1, int in_m2);
static bool isAllow(const std::string& in_m1, int in_m2);
static bool isAllow_strOne(const std::string& in_m1, int in_m2);
static bool isAllow(const std::string& in_m1, const std::string& in_m2);
// 将包含请求类型的in_str转为int值表示
static int strToInt(const std::string& in_str);
/*将包含请求类型的in_str转为int值表示
* 但要求in_str只包含了一个请求类型
* 否则只返回按HttpMethod_e枚举顺序第一个找到的类型对应的int值
*/
static int strToInt_one(const std::string& in_str);
// 将使用int值表示的请求类型转为string表示
static std::string intToStr(int in_int);
/*将使用int值表示的请求类型转为string表示
* 但要求in_int只表示一个请求类型
* 否则只返回按HttpMethod_e枚举顺序第一个找到的类型对应的string
*/
static std::string intToStr_one(int in_int);
static int toIndex_one(int in_method);
static int toIndex_one(const std::string& in_method);
};
#endif
```
## /server-cxx/src/myServer/MyHttpState.h
```h path="/server-cxx/src/myServer/MyHttpState.h"
#ifndef MYHTTPSTATE_H
#define MYHTTPSTATE_H
class MyHttpState_e {
public:
static constexpr int Continue = 100, // 继续
OK = 200, // 成功
Created = 201, // 已创建。成功请求并创建了新的资源
PartialContent = 206, // 部分内容。请求分块时需要使用
MovedPermanently = 301, // 永久移动
Found = 302, // 临时移动
BadRequest = 400, // 请求格式错误
Unauthorized = 401, // 需要用户身份认证
Forbidden = 403, // 拒绝请求
NotFound = 404, // 未找到
MethodNotAllowed = 405, // 方法不允许
PreconditionFailed = 412, // 客户端希望的条件不满足
InternalServerError = 500, // 服务器内部错误
BadGateway = 502, // 网关或代理服务器未及时从远端服务器获取请求
ServiceUnavailable = 503; // 服务不可访问
};
namespace mimicry {
class ResponeState_e {
public:
static constexpr int Undefined = -1,
Continue = 1000, // 继续
ContinueCustom = 1777, // 继续,并附带自定义提示
OK = 2000, // 成功
OKCustom = 2777, // 成功,并附带自定义提示
JumpCustom = 3777, // 跳转标记,并附带自定义提示
LoginStateError = 4000, // 用户需要登录
Permission = 4001, // 权限错误
Forbidden = 4003, // 拒绝请求
NotFound = 4004, // 请求资源无法找到
ValueHiatus = 4010, // 参数缺失
ValueError = 4011, // 参数错误
DeletedError = 4012, // 已被删除
RepearError = 4013, // 重复动作
LockedError = 4014, // 已被封禁
LimitError = 4015, // 违反限制
Toofast = 4016, // 访问太快
TooMany = 4017, // 访问次数太多
VerifyError = 4018, // 验证码等验证方式不通过
InsufficientCredit = 4019, // 信用评估较低,不可使用
FaildCustom = 4777, // 客户端错误,并附带自定义提示
FaildTag = 4900, // 失败
Exception = 5000, // 服务器错误
ExceptionCustom = 5777; // 服务端错误,并附带自定义提示
static int faildTag(int num) {
return (FaildTag + num);
}
};
}; // namespace mimicry
#endif // !MYHTTPSTATE_H
```
## /server-cxx/src/myServer/MyProxy.h
```h path="/server-cxx/src/myServer/MyProxy.h"
#include <string>
#include "MyHttpTask.h"
#include "MyLog.h"
class MyProxy_c {
public:
MyProxy_c() {}
static WFHttpTask* http(
MyHttpTask_c* in_req_task,
const std::string& in_toPath,
bool enable_autoInfo,
const http_callback_t& req_call,
const http_callback_t& resp_call
);
};
```
## /server-cxx/src/myTest/CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.6)
aux_source_directory(. DIR_myTest)
set(NAME_myTest myTest)
if (WIN32)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /utf-8 /wd4200")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8 /wd4200 /Zc:__cplusplus /std:c++20")
else ()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -fPIC -pipe -std=gnu90")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fPIC -pipe -std=c++2a -fexceptions")
endif ()
add_executable(${NAME_myTest} ${DIR_myTest})
target_link_libraries(${NAME_myTest} myServer)
if (WIN32)
target_compile_definitions(
${NAME_myTest} PRIVATE
strdup=_strdup
strcasecmp=_stricmp
strncasecmp=_strnicmp
)
endif ()
The content has been capped at 50000 tokens. The user could consider applying other filters to refine the result. The better and more specific the context, the better the LLM can follow instructions. If the context seems verbose, the user can refine the filter using uithub. Thank you for using https://uithub.com - Perfect LLM context for any GitHub repo.