Compare commits

...

8 Commits

182 changed files with 2451 additions and 1992 deletions

View File

@@ -10,8 +10,6 @@ Ceru Music 是基于 Electron 和 Vue 开发的跨平台桌面音乐播放器工
![image-20251003173654569](assets/image-20251003173654569.png)
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=timeshiftsauce/CeruMusic&type=Date)](https://www.star-history.com/#timeshiftsauce/CeruMusic&Date)
@@ -215,11 +213,9 @@ CeruMuisc/
├── tsconfig.web.json
└── yarn.lock
```
</details>
## 主要功能
- 提供插件加载与管理功能,支持通过合规插件获取公开音乐信息

View File

@@ -8,8 +8,15 @@ export default defineConfig({
head: [
['link', { rel: 'icon', href: '/logo.svg' }],
['meta', { name: 'author', href: '时迁酱无聊的霜霜star' }],
['meta', { name: 'keywords', content: 'Ceru Music,音乐播放器,音乐播放器工具,音乐播放器软件,音乐播放器下载,音乐播放器下载地址,澜音播放器,免费的音乐播放器,cerumusic,时迁酱,周晨鹭,无聊的霜霜,star,洛雪音乐,洛雪'}],
['meta', { name: 'baidu-site-verification', content: 'codeva-ocKFImCsOO' }],
[
'meta',
{
name: 'keywords',
content:
'Ceru Music,音乐播放器,音乐播放器工具,音乐播放器软件,音乐播放器下载,音乐播放器下载地址,澜音播放器,免费的音乐播放器,cerumusic,时迁酱,周晨鹭,无聊的霜霜,star,洛雪音乐,洛雪'
}
],
['meta', { name: 'baidu-site-verification', content: 'codeva-ocKFImCsOO' }]
],
description:
'Ceru Music 是基于 Electron 和 Vue 开发的跨平台桌面音乐播放器工具,一个跨平台的音乐播放器应用,支持基于合规插件获取公开音乐信息与播放功能。',

View File

@@ -2,15 +2,17 @@
## 鸣谢
| 昵称 | 赞助金额 |
| :------------------------: | :------: |
| **群友**:可惜情绪落泪零碎 | 6.66 |
| **群友**:🍀 | 5 |
| **群友**:涟漪 | 50 |
| **作者朋友** | 188 |
| **群友**:我叫阿狸 | 3 |
| RiseSun | 9.9 |
| **b站小友**:光牙阿普斯木兰 | 5 |
| 青禾 | 8.8 |
| 昵称 | 赞助金额 |
| :-------------------------: | :------: |
| **群友**:可惜情绪落泪零碎 | 6.66 |
| **群友**:🍀 | 5 |
| **群友**:涟漪 | 50 |
| **作者朋友** | 188 |
| **群友**:我叫阿狸 | 3 |
| RiseSun | 9.9 |
| **b站小友**:光牙阿普斯木兰 | 5 |
| 青禾 | 8.8 |
| li peng | 200 |
| **群友**XIZ | 3 |
据不完全统计 如有疏漏可联系sqj@shiqianjiang.cn
据不完全统计 如有疏漏可联系sqj@shiqianjiang.cn

View File

@@ -2,29 +2,55 @@
## 日志
- ###### 2025-10-3 (v1.3.12)
- ###### 2025-10-7 (v1.4.0)
1. 优化搜索联想功能
支持
- 歌单
- 专辑
- 歌手
- 单曲名
2. 设置功能
- 性能优化设置
- 歌词弹簧跳动 开关
- 背景布朗运动 开关
- 音频可视化 开关
- 网络负载优化设置
- 存储设置 -> 缓存可以设置是否开启
优化网络差情况歌曲加载卡顿
3. 新增播放列表 **tag** 动画
4. **debug**
- 修复 2 条 接口失效无法获取搜索联想建议
- SMTC: 如果歌曲是播放状态时 切换到其他页面导致组件注销后 歌曲确实在播放是正常的可是切换回来时 能暂停 但是图标马上变为播放图标 后续无法播放的问题
- 去除播放歌曲多余提醒
- ###### 2025-10-6 (v1.3.13)
1. 添加搜索联想功能
2. debug: 某云歌单导入 限制1000问题
- ###### 2025-10-3 (v1.3.12)
1. 支持暗黑主题
2. 调整插件页面ui
- ###### 2025-9-29 (v1.3.11)
1. 新增插件在线导入
- ###### 2025-9-28 (v1.3.10)
1. 优化播放列表
2. 单击播放
3. 右键菜单
4. 调整播放进度调粗细
- ###### 2025-09-27 (v1.3.9)
1. debug:flac格式使用ffmpeg
1. debug:flac格式使用ffmpeg
2. 修复高音质下载失效
- ###### 2025-9-26 (v1.3.8)
1. 写入歌曲tag信息
2. 歌曲下载 选择音质
3. 歌单 头部自动压缩

View File

@@ -1,6 +1,6 @@
{
"name": "ceru-music",
"version": "1.3.13",
"version": "1.4.3",
"description": "一款简洁优雅的音乐播放器",
"main": "./out/main/index.js",
"author": "sqj,wldss,star",
@@ -47,7 +47,7 @@
"@pixi/filter-bulge-pinch": "^5.1.1",
"@pixi/filter-color-matrix": "^7.4.3",
"@pixi/sprite": "^7.4.3",
"@types/fluent-ffmpeg": "^2.1.27",
"@types/howler": "^2.2.12",
"@types/needle": "^3.3.0",
"NeteaseCloudMusicApi": "^4.27.0",
"animate.css": "^4.1.1",
@@ -57,8 +57,7 @@
"dompurify": "^3.2.6",
"electron-log": "^5.4.3",
"electron-updater": "^6.3.9",
"ffmpeg-static": "^5.2.0",
"fluent-ffmpeg": "^2.1.3",
"howler": "^2.2.4",
"hpagent": "^1.2.0",
"iconv-lite": "^0.7.0",
"jss": "^10.10.0",
@@ -69,7 +68,7 @@
"mitt": "^3.0.1",
"needle": "^3.3.1",
"node-fetch": "2",
"node-id3": "^0.2.9",
"node-taglib-sharp": "^6.0.1",
"pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^4.5.0",
"tdesign-icons-vue-next": "^0.4.1",

View File

@@ -0,0 +1,92 @@
export const QUALITY_ORDER = [
'master',
'atmos_plus',
'atmos',
'hires',
'flac24bit',
'flac',
'320k',
'192k',
'128k'
] as const
export type KnownQuality = (typeof QUALITY_ORDER)[number]
export type QualityInput = KnownQuality | string | { type: string; size?: string }
const DISPLAY_NAME_MAP: Record<string, string> = {
'128k': '标准',
'192k': '高品',
'320k': '超高',
flac: '无损',
flac24bit: '超高解析',
hires: '高清臻音',
atmos: '全景环绕',
atmos_plus: '全景增强',
master: '超清母带'
}
/**
* 统一获取音质中文显示名称
*/
export function getQualityDisplayName(quality: QualityInput | null | undefined): string {
if (!quality) return ''
const type = typeof quality === 'object' ? (quality as any).type : quality
return DISPLAY_NAME_MAP[type] || String(type || '')
}
/**
* 比较两个音质优先级(返回负数表示 a 优于 b
*/
export function compareQuality(aType: string, bType: string): number {
const ia = QUALITY_ORDER.indexOf(aType as KnownQuality)
const ib = QUALITY_ORDER.indexOf(bType as KnownQuality)
const va = ia === -1 ? QUALITY_ORDER.length : ia
const vb = ib === -1 ? QUALITY_ORDER.length : ib
return va - vb
}
/**
* 规范化 types兼容 string 与 {type,size}
*/
export function normalizeTypes(
types: Array<string | { type: string; size?: string }> | null | undefined
): string[] {
if (!types || !Array.isArray(types)) return []
return types
.map((t) => (typeof t === 'object' ? (t as any).type : t))
.filter((t): t is string => Boolean(t))
}
/**
* 获取数组中最高音质类型
*/
export function getHighestQualityType(
types: Array<string | { type: string; size?: string }> | null | undefined
): string | null {
const arr = normalizeTypes(types)
if (!arr.length) return null
return arr.sort(compareQuality)[0]
}
/**
* 构建并按优先级排序的 [{type, size}] 列表
* 支持传入:
* - 数组:[{type,size}]
* - _types 映射:{ [type]: { size } }
*/
export function buildQualityFormats(
input:
| Array<{ type: string; size?: string }>
| Record<string, { size?: string }>
| null
| undefined
): Array<{ type: string; size?: string }> {
if (!input) return []
let list: Array<{ type: string; size?: string }>
if (Array.isArray(input)) {
list = input.map((i) => ({ type: i.type, size: i.size }))
} else {
list = Object.keys(input).map((k) => ({ type: k, size: input[k]?.size }))
}
return list.sort((a, b) => compareQuality(a.type, b.type))
}

150
src/main/events/index.ts Normal file
View File

@@ -0,0 +1,150 @@
import InitPluginService from './plugins'
import '../services/musicSdk/index'
import aiEvents from '../events/ai'
import { app, powerSaveBlocker, Menu } from 'electron'
import path from 'node:path'
import { type BrowserWindow, Tray, ipcMain } from 'electron'
export default function InitEventServices(mainWindow: BrowserWindow) {
InitPluginService()
aiEvents(mainWindow)
basisEvent(mainWindow)
}
function basisEvent(mainWindow: BrowserWindow) {
let psbId: number | null = null
let tray: Tray | null = null
let isQuitting = false
function createTray(): void {
// 创建系统托盘
const trayIconPath = path.join(__dirname, '../../resources/logo.png')
tray = new Tray(trayIconPath)
// 创建托盘菜单
const contextMenu = Menu.buildFromTemplate([
{
label: '显示窗口',
click: () => {
if (mainWindow) {
mainWindow.show()
mainWindow.focus()
}
}
},
{
label: '播放/暂停',
click: () => {
// 这里可以添加播放控制逻辑
console.log('music-control')
mainWindow?.webContents.send('music-control')
}
},
{ type: 'separator' },
{
label: '退出',
click: () => {
isQuitting = true
app.quit()
}
}
])
tray.setContextMenu(contextMenu)
tray.setToolTip('Ceru Music')
// 单击托盘图标显示窗口
tray.on('click', () => {
if (mainWindow) {
if (mainWindow.isVisible()) {
mainWindow.hide()
} else {
mainWindow.show()
mainWindow.focus()
}
}
})
}
createTray()
// 应用退出前的清理
app.on('before-quit', () => {
isQuitting = true
})
// 窗口控制 IPC 处理
ipcMain.on('window-minimize', () => {
mainWindow.minimize()
})
ipcMain.on('window-maximize', () => {
if (mainWindow.isMaximized()) {
mainWindow.unmaximize()
} else {
mainWindow.maximize()
}
})
ipcMain.on('window-close', () => {
mainWindow.close()
})
// Mini 模式 IPC 处理 - 最小化到系统托盘
ipcMain.on('window-mini-mode', (_, isMini) => {
if (mainWindow) {
if (isMini) {
// 进入 Mini 模式:隐藏窗口到系统托盘
mainWindow.hide()
// 显示托盘通知(可选)
if (tray) {
tray.displayBalloon({
title: '澜音 Music',
content: '已最小化到系统托盘啦,点击托盘图标可重新打开~'
})
}
} else {
// 退出 Mini 模式:显示窗口
mainWindow.show()
mainWindow.focus()
}
}
})
// 全屏模式 IPC 处理
ipcMain.on('window-toggle-fullscreen', () => {
const isFullScreen = mainWindow.isFullScreen()
mainWindow.setFullScreen(!isFullScreen)
})
// 阻止窗口关闭,改为隐藏到系统托盘
mainWindow.on('close', (event) => {
if (!isQuitting) {
event.preventDefault()
mainWindow?.hide()
// 显示托盘通知
if (tray) {
tray.displayBalloon({
title: 'Ceru Music',
content: '已最小化到系统托盘啦,点击托盘图标可重新打开~'
})
}
}
})
// 阻止系统息屏 IPC开启/关闭)
ipcMain.handle('power-save-blocker:start', () => {
if (psbId == null) {
psbId = powerSaveBlocker.start('prevent-display-sleep')
}
return psbId
})
ipcMain.handle('power-save-blocker:stop', () => {
if (psbId != null && powerSaveBlocker.isStarted(psbId)) {
powerSaveBlocker.stop(psbId)
}
psbId = null
return true
})
// 获取应用版本号
ipcMain.handle('get-app-version', () => {
return app.getVersion()
})
}

View File

@@ -0,0 +1,82 @@
import { ipcMain } from 'electron'
import pluginService from '../services/plugin'
function PluginEvent() {
ipcMain.handle('service-plugin-selectAndAddPlugin', async (_, type): Promise<any> => {
try {
return await pluginService.selectAndAddPlugin(type)
} catch (error: any) {
console.error('Error selecting and adding plugin:', error)
return { error: error.message }
}
})
ipcMain.handle('service-plugin-downloadAndAddPlugin', async (_, url, type): Promise<any> => {
try {
return await pluginService.downloadAndAddPlugin(url, type)
} catch (error: any) {
console.error('Error downloading and adding plugin:', error)
return { error: error.message }
}
})
ipcMain.handle('service-plugin-addPlugin', async (_, pluginCode, pluginName): Promise<any> => {
try {
return await pluginService.addPlugin(pluginCode, pluginName)
} catch (error: any) {
console.error('Error adding plugin:', error)
return { error: error.message }
}
})
ipcMain.handle('service-plugin-getPluginById', async (_, id): Promise<any> => {
try {
return pluginService.getPluginById(id)
} catch (error: any) {
console.error('Error getting plugin by id:', error)
return { error: error.message }
}
})
ipcMain.handle('service-plugin-loadAllPlugins', async (): Promise<any> => {
try {
// 使用新的 getPluginsList 方法,但保持 API 兼容性
return await pluginService.getPluginsList()
} catch (error: any) {
console.error('Error loading all plugins:', error)
return { error: error.message }
}
})
ipcMain.handle('service-plugin-getPluginLog', async (_, pluginId): Promise<any> => {
try {
return await pluginService.getPluginLog(pluginId)
} catch (error: any) {
console.error('Error getting plugin log:', error)
return { error: error.message }
}
})
ipcMain.handle('service-plugin-uninstallPlugin', async (_, pluginId): Promise<any> => {
try {
return await pluginService.uninstallPlugin(pluginId)
} catch (error: any) {
console.error('Error uninstalling plugin:', error)
return { error: error.message }
}
})
}
export default function InitPluginService() {
setTimeout(async () => {
// 初始化插件系统
try {
await pluginService.initializePlugins()
PluginEvent()
console.log('插件系统初始化完成')
} catch (error) {
console.error('插件系统初始化失败:', error)
}
}, 1000)
}

View File

@@ -1,12 +1,16 @@
import { app, shell, BrowserWindow, ipcMain, Tray, Menu, screen } from 'electron'
import { app, shell, BrowserWindow, ipcMain, screen } from 'electron'
import { configManager } from './services/ConfigManager'
import { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/logo.png?asset'
import path from 'node:path'
import pluginService from './services/plugin'
import aiEvents from './events/ai'
import './services/musicSdk/index'
import InitEventServices from './events'
import './events/musicCache'
import './events/songList'
import './events/directorySettings'
import './events/pluginNotice'
// 获取单实例锁
const gotTheLock = app.requestSingleInstanceLock()
@@ -24,71 +28,9 @@ if (!gotTheLock) {
}
})
}
// import wy from './utils/musicSdk/wy/index'
// import kg from './utils/musicSdk/kg/index'
// wy.hotSearch.getList().then((res) => {
// console.log(res)
// })
// kg.hotSearch.getList().then((res) => {
// console.log(res)
// })
let tray: Tray | null = null
let mainWindow: BrowserWindow | null = null
let isQuitting = false
function createTray(): void {
// 创建系统托盘
const trayIconPath = path.join(__dirname, '../../resources/logo.png')
tray = new Tray(trayIconPath)
// 创建托盘菜单
const contextMenu = Menu.buildFromTemplate([
{
label: '显示窗口',
click: () => {
if (mainWindow) {
mainWindow.show()
mainWindow.focus()
}
}
},
{
label: '播放/暂停',
click: () => {
// 这里可以添加播放控制逻辑
console.log('music-control')
mainWindow?.webContents.send('music-control')
}
},
{ type: 'separator' },
{
label: '退出',
click: () => {
isQuitting = true
app.quit()
}
}
])
tray.setContextMenu(contextMenu)
tray.setToolTip('Ceru Music')
// 双击托盘图标显示窗口
tray.on('click', () => {
if (mainWindow) {
if (mainWindow.isVisible()) {
mainWindow.hide()
} else {
mainWindow.show()
mainWindow.focus()
}
}
})
}
function createWindow(): void {
// return
// 获取保存的窗口位置和大小
const savedBounds = configManager.getWindowBounds()
@@ -173,29 +115,11 @@ function createWindow(): void {
mainWindow.on('ready-to-show', () => {
mainWindow?.show()
})
// 阻止窗口关闭,改为隐藏到系统托盘
mainWindow.on('close', (event) => {
if (!isQuitting) {
event.preventDefault()
mainWindow?.hide()
// 显示托盘通知
if (tray) {
tray.displayBalloon({
iconType: 'info',
title: 'Ceru Music',
content: '已最小化到系统托盘啦,点击托盘图标可重新打开~'
})
}
}
})
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url).then()
return { action: 'deny' }
})
InitEventServices(mainWindow)
// HMR for renderer base on electron-vite cli.
// Load the remote URL for development or the local html file for production.
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
@@ -205,80 +129,6 @@ function createWindow(): void {
}
}
ipcMain.handle('service-plugin-selectAndAddPlugin', async (_, type): Promise<any> => {
try {
return await pluginService.selectAndAddPlugin(type)
} catch (error: any) {
console.error('Error selecting and adding plugin:', error)
return { error: error.message }
}
})
ipcMain.handle('service-plugin-downloadAndAddPlugin', async (_, url, type): Promise<any> => {
try {
return await pluginService.downloadAndAddPlugin(url, type)
} catch (error: any) {
console.error('Error downloading and adding plugin:', error)
return { error: error.message }
}
})
ipcMain.handle('service-plugin-addPlugin', async (_, pluginCode, pluginName): Promise<any> => {
try {
return await pluginService.addPlugin(pluginCode, pluginName)
} catch (error: any) {
console.error('Error adding plugin:', error)
return { error: error.message }
}
})
ipcMain.handle('service-plugin-getPluginById', async (_, id): Promise<any> => {
try {
return pluginService.getPluginById(id)
} catch (error: any) {
console.error('Error getting plugin by id:', error)
return { error: error.message }
}
})
ipcMain.handle('service-plugin-loadAllPlugins', async (): Promise<any> => {
try {
// 使用新的 getPluginsList 方法,但保持 API 兼容性
return await pluginService.getPluginsList()
} catch (error: any) {
console.error('Error loading all plugins:', error)
return { error: error.message }
}
})
ipcMain.handle('service-plugin-getPluginLog', async (_, pluginId): Promise<any> => {
try {
return await pluginService.getPluginLog(pluginId)
} catch (error: any) {
console.error('Error getting plugin log:', error)
return { error: error.message }
}
})
ipcMain.handle('service-plugin-uninstallPlugin', async (_, pluginId): Promise<any> => {
try {
return await pluginService.uninstallPlugin(pluginId)
} catch (error: any) {
console.error('Error uninstalling plugin:', error)
return { error: error.message }
}
})
// 获取应用版本号
ipcMain.handle('get-app-version', () => {
return app.getVersion()
})
aiEvents(mainWindow)
import './events/musicCache'
import './events/songList'
import './events/directorySettings'
import './events/pluginNotice'
import { registerAutoUpdateEvents, initAutoUpdateForWindow } from './events/autoUpdate'
// This method will be called when Electron has finished
@@ -295,16 +145,6 @@ app.whenReady().then(() => {
app.setName('澜音')
}
setTimeout(async () => {
// 初始化插件系统
try {
await pluginService.initializePlugins()
console.log('插件系统初始化完成')
} catch (error) {
console.error('插件系统初始化失败:', error)
}
}, 1000)
// Default open or close DevTools by F12 in development
// and ignore CommandOrControl + R in production.
// see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
@@ -312,63 +152,7 @@ app.whenReady().then(() => {
optimizer.watchWindowShortcuts(window)
})
// 窗口控制 IPC 处理
ipcMain.on('window-minimize', () => {
const window = BrowserWindow.getFocusedWindow()
if (window) {
window.minimize()
}
})
ipcMain.on('window-maximize', () => {
const window = BrowserWindow.getFocusedWindow()
if (window) {
if (window.isMaximized()) {
window.unmaximize()
} else {
window.maximize()
}
}
})
ipcMain.on('window-close', () => {
const window = BrowserWindow.getFocusedWindow()
if (window) {
window.close()
}
})
// Mini 模式 IPC 处理 - 最小化到系统托盘
ipcMain.on('window-mini-mode', (_, isMini) => {
if (mainWindow) {
if (isMini) {
// 进入 Mini 模式:隐藏窗口到系统托盘
mainWindow.hide()
// 显示托盘通知(可选)
if (tray) {
tray.displayBalloon({
title: '澜音 Music',
content: '已最小化到系统托盘啦,点击托盘图标可重新打开~'
})
}
} else {
// 退出 Mini 模式:显示窗口
mainWindow.show()
mainWindow.focus()
}
}
})
// 全屏模式 IPC 处理
ipcMain.on('window-toggle-fullscreen', () => {
if (mainWindow) {
const isFullScreen = mainWindow.isFullScreen()
mainWindow.setFullScreen(!isFullScreen)
}
})
createWindow()
createTray()
// 注册自动更新事件
registerAutoUpdateEvents()
@@ -398,67 +182,19 @@ app.on('window-all-closed', () => {
// 在其他平台上,我们也保持应用运行,因为有系统托盘
})
// 应用退出前的清理
app.on('before-quit', () => {
isQuitting = true
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
let ping: NodeJS.Timeout
function startPing() {
let interval = 3000
// 已迁移到 Howler不再使用 DOM <audio> 轮询。
// 如需主进程感知播放结束,请在渲染进程通过 IPC 主动通知。
if (ping) {
clearInterval(ping)
}
ping = setInterval(() => {
if (mainWindow) {
mainWindow.webContents
.executeJavaScript(
`
(function() {
const audio = document.getElementById("globaAudio");
if(!audio) return { playing:false, ended: false };
if(audio.ended) return { playing:false, ended: true };
return { playing: !audio.paused, ended: false, currentTime: audio.currentTime, duration: audio.duration };
})()
`
)
.then((res) => {
console.log(res)
if (res.duration - res.currentTime <= 20) {
clearInterval(ping)
interval = 500
ping = setInterval(() => {
if (mainWindow) {
mainWindow.webContents
.executeJavaScript(
`
(function() {
const audio = document.getElementById("globaAudio");
if(!audio) return { playing:false, ended: false };
if(audio.ended) return { playing:false, ended: true };
return { playing: !audio.paused, ended: false, currentTime: audio.currentTime, duration: audio.duration };
})()
`
)
.then((res) => {
console.log(res)
if (res && res.ended) {
mainWindow?.webContents.send('song-ended')
console.log('next song')
clearInterval(ping)
}
})
.catch((err) => console.warn(err))
}
}, interval)
}
})
.catch((err) => console.warn(err))
}
}, interval)
// 保留占位,避免调用方报错;不再做任何轮询。
// 可在此处监听自定义 IPC 事件以扩展行为。
clearInterval(ping)
}, 1000)
}

View File

@@ -23,14 +23,14 @@ function main(source: string) {
},
async tipSearch({ keyword }: { keyword: string }) {
if (!Api.tipSearch?.tipSearchBySong) {
if (!Api.tipSearch?.search) {
// 如果音乐源没有实现tipSearch方法返回空结果
return [] as TipSearchResult
}
return (await Api.tipSearch.search(keyword)) as Promise<TipSearchResult>
},
async getMusicUrl({ pluginId, songInfo, quality }: GetMusicUrlArg) {
async getMusicUrl({ pluginId, songInfo, quality, isCache }: GetMusicUrlArg) {
try {
const usePlugin = pluginService.getPluginById(pluginId)
if (!pluginId || !usePlugin) return { error: '请配置音源来播放歌曲' }
@@ -38,18 +38,22 @@ function main(source: string) {
// 生成歌曲唯一标识
const songId = `${songInfo.name}-${songInfo.singer}-${source}-${quality}`
// 先检查缓存
const cachedUrl = await musicCacheService.getCachedMusicUrl(songId)
if (cachedUrl) {
return cachedUrl
// 先检查缓存isCache !== false 时)
if (isCache !== false) {
const cachedUrl = await musicCacheService.getCachedMusicUrl(songId)
if (cachedUrl) {
return cachedUrl
}
}
// 没有缓存时才发起网络请求
const originalUrl = await usePlugin.getMusicUrl(source, songInfo, quality)
// 异步缓存,不阻塞返回
musicCacheService.cacheMusic(songId, originalUrl).catch((error) => {
console.warn('缓存歌曲失败:', error)
})
// 按需异步缓存,不阻塞返回
if (isCache !== false) {
musicCacheService.cacheMusic(songId, originalUrl).catch((error) => {
console.warn('缓存歌曲失败:', error)
})
}
return originalUrl
} catch (e: any) {

View File

@@ -39,6 +39,7 @@ export interface GetMusicUrlArg {
pluginId: string
songInfo: MusicItem
quality: string
isCache?: boolean
}
export interface GetMusicPicArg {

View File

@@ -1,6 +1,4 @@
import NodeID3 from 'node-id3'
import ffmpegStatic from 'ffmpeg-static'
import ffmpeg from 'fluent-ffmpeg'
import { File, Picture, Id3v2Settings } from 'node-taglib-sharp'
import path from 'node:path'
import axios from 'axios'
import fs from 'fs'
@@ -75,6 +73,32 @@ function formatTimestamp(timeMs: number): string {
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}.${milliseconds.toString().padStart(3, '0')}`
}
// 根据图片 URL 和 Content-Type 解析扩展名,默认返回 .jpg
function resolveCoverExt(imgUrl: string, contentType?: string): string {
const validExts = new Set(['.jpg', '.jpeg', '.png', '.webp', '.bmp'])
let urlExt: string | undefined
try {
const pathname = new URL(imgUrl).pathname
const i = pathname.lastIndexOf('.')
if (i !== -1) {
urlExt = pathname.substring(i).toLowerCase()
}
} catch {}
if (urlExt && validExts.has(urlExt)) {
return urlExt === '.jpeg' ? '.jpg' : urlExt
}
if (contentType) {
if (contentType.includes('image/png')) return '.png'
if (contentType.includes('image/webp')) return '.webp'
if (contentType.includes('image/jpeg') || contentType.includes('image/jpg')) return '.jpg'
if (contentType.includes('image/bmp')) return '.bmp'
}
return '.jpg'
}
/**
* 转换新格式:[开始时间,持续时间](开始时间,字符持续时间,0)字符
*/
@@ -156,265 +180,6 @@ function convertOldFormat(timestamp: string, content: string): string {
return `[${timestamp}]${convertedContent}`
}
// 写入音频标签的辅助函数
async function writeAudioTags(filePath: string, songInfo: any, tagWriteOptions: any) {
try {
console.log('开始写入音频标签:', filePath, tagWriteOptions, songInfo)
// 获取文件扩展名来判断格式
const fileExtension = path.extname(filePath).toLowerCase().substring(1)
console.log('文件格式:', fileExtension)
// 根据文件格式选择不同的标签写入方法
if (fileExtension === 'mp3') {
await writeMP3Tags(filePath, songInfo, tagWriteOptions)
} else if (['flac', 'ogg', 'opus'].includes(fileExtension)) {
await writeVorbisCommentTags(filePath, songInfo, tagWriteOptions)
} else if (['m4a', 'mp4', 'aac'].includes(fileExtension)) {
await writeMP4Tags(filePath, songInfo, tagWriteOptions)
} else {
console.warn('不支持的音频格式:', fileExtension)
// 尝试使用 NodeID3 作为后备方案
await writeMP3Tags(filePath, songInfo, tagWriteOptions)
}
} catch (error) {
console.error('写入音频标签时发生错误:', error)
throw error
}
}
// MP3 格式标签写入
async function writeMP3Tags(filePath: string, songInfo: any, tagWriteOptions: any) {
const tags: any = {}
// 写入基础信息
if (tagWriteOptions.basicInfo) {
tags.title = songInfo.name || ''
tags.artist = songInfo.singer || ''
tags.album = songInfo.albumName || ''
tags.year = songInfo.year || ''
tags.genre = songInfo.genre || ''
}
// 写入歌词
if (tagWriteOptions.lyrics && songInfo.lrc) {
const convertedLrc = convertLrcFormat(songInfo.lrc)
tags.unsynchronisedLyrics = {
language: 'chi',
shortText: 'Lyrics',
text: convertedLrc
}
}
// 写入封面
if (tagWriteOptions.cover && songInfo.img) {
try {
const coverResponse = await axios({
method: 'GET',
url: songInfo.img,
responseType: 'arraybuffer',
timeout: 10000
})
if (coverResponse.data) {
tags.image = {
mime: 'image/jpeg',
type: {
id: 3,
name: 'front cover'
},
description: 'Cover',
imageBuffer: Buffer.from(coverResponse.data)
}
}
} catch (coverError) {
console.warn('获取封面失败:', coverError)
}
}
// 写入标签到文件
if (Object.keys(tags).length > 0) {
const success = NodeID3.write(tags, filePath)
if (success) {
console.log('MP3音频标签写入成功:', filePath)
} else {
console.warn('MP3音频标签写入失败:', filePath)
}
}
}
// FLAC/OGG 格式标签写入 (使用 Vorbis Comment)
async function writeVorbisCommentTags(filePath: string, songInfo: any, tagWriteOptions: any) {
try {
console.log('开始写入 FLAC 标签:', filePath)
// 准备新的标签数据
const newTags: any = {}
// 写入基础信息
if (tagWriteOptions.basicInfo) {
if (songInfo.name) newTags.TITLE = songInfo.name
if (songInfo.singer) newTags.ARTIST = songInfo.singer
if (songInfo.albumName) newTags.ALBUM = songInfo.albumName
if (songInfo.year) newTags.DATE = songInfo.year.toString()
if (songInfo.genre) newTags.GENRE = songInfo.genre
}
// 写入歌词
if (tagWriteOptions.lyrics && songInfo.lrc) {
const convertedLrc = convertLrcFormat(songInfo.lrc)
newTags.LYRICS = convertedLrc
}
console.log('准备写入的标签:', newTags)
// 使用 ffmpeg-static 写入 FLAC 标签
if (path.extname(filePath).toLowerCase() === '.flac') {
await writeFLACTagsWithFFmpeg(filePath, newTags, songInfo, tagWriteOptions)
} else {
console.warn('暂不支持该格式的标签写入:', path.extname(filePath))
}
} catch (error) {
console.error('写入 Vorbis Comment 标签失败:', error)
throw error
}
}
// 使用 fluent-ffmpeg 写入 FLAC 标签
async function writeFLACTagsWithFFmpeg(
filePath: string,
tags: any,
songInfo: any,
tagWriteOptions: any
) {
let tempOutputPath: string | null = null
let tempCoverPath: string | null = null
try {
if (!ffmpegStatic) {
throw new Error('ffmpeg-static 不可用')
}
ffmpeg.setFfmpegPath(ffmpegStatic.replace('app.asar', 'app.asar.unpacked'))
// 创建临时输出文件
tempOutputPath = filePath + '.temp.flac'
// 创建 fluent-ffmpeg 实例
let command = ffmpeg(filePath)
.audioCodec('copy') // 复制音频编解码器,不重新编码
.output(tempOutputPath)
// 添加元数据标签
for (const [key, value] of Object.entries(tags)) {
if (value) {
// fluent-ffmpeg 会自动处理特殊字符转义
command = command.outputOptions(['-metadata', `${key}=${value}`])
}
}
// 处理封面
if (tagWriteOptions.cover && songInfo.img) {
try {
console.log('开始下载封面:', songInfo.img)
const coverResponse = await axios({
method: 'GET',
url: songInfo.img,
responseType: 'arraybuffer',
timeout: 10000
})
if (coverResponse.data) {
// 保存临时封面文件
tempCoverPath = path.join(path.dirname(filePath), 'temp_cover.jpg')
await fsPromise.writeFile(tempCoverPath, Buffer.from(coverResponse.data))
// 添加封面作为输入
command = command.input(tempCoverPath).outputOptions([
'-map',
'0:a', // 映射原始文件的音频流
'-map',
'1:v', // 映射封面的视频流
'-c:v',
'copy', // 复制视频编解码器
'-disposition:v:0',
'attached_pic' // 设置为附加图片
])
console.log('封面已添加到命令中')
}
} catch (coverError) {
console.warn(
'下载封面失败,跳过封面写入:',
coverError instanceof Error ? coverError.message : coverError
)
}
}
// 执行 ffmpeg 命令
await new Promise<void>((resolve, reject) => {
command
.on('start', () => {
console.log('执行 ffmpeg 命令')
})
.on('progress', (progress) => {
if (progress.percent) {
console.log('处理进度:', Math.round(progress.percent) + '%')
}
})
.on('end', () => {
console.log('ffmpeg 处理完成')
resolve()
})
.on('error', (err, _, stderr) => {
console.error('ffmpeg 错误:', err.message)
if (stderr) {
console.error('ffmpeg stderr:', stderr)
}
reject(new Error(`ffmpeg 处理失败: ${err.message}`))
})
.run()
})
// 检查临时文件是否创建成功
if (!fs.existsSync(tempOutputPath)) {
throw new Error('ffmpeg 未能创建输出文件')
}
// 替换原文件
await fsPromise.rename(tempOutputPath, filePath)
tempOutputPath = null // 标记已处理,避免重复清理
console.log('使用 fluent-ffmpeg 写入 FLAC 标签成功:', filePath)
} catch (error) {
console.error('使用 fluent-ffmpeg 写入 FLAC 标签失败:', error)
throw error
} finally {
// 清理所有临时文件
const filesToClean = [tempOutputPath, tempCoverPath].filter(Boolean) as string[]
for (const tempFile of filesToClean) {
try {
if (fs.existsSync(tempFile)) {
await fsPromise.unlink(tempFile)
console.log('已清理临时文件:', tempFile)
}
} catch (cleanupError) {
console.warn(
'清理临时文件失败:',
tempFile,
cleanupError instanceof Error ? cleanupError.message : cleanupError
)
}
}
}
}
// MP4/M4A 格式标签写入
async function writeMP4Tags(filePath: string, _songInfo: any, _tagWriteOptions: any) {
console.log('MP4/M4A 格式标签写入暂未实现:', filePath)
// 可以使用 ffmpeg 或其他工具实现
}
// 获取自定义下载目录
const getDownloadDirectory = (): string => {
// 使用配置管理服务获取下载目录
@@ -492,13 +257,71 @@ export default async function download(
delete fileLock[songPath]
}
// 写入标签信息
// 写入标签信息(使用 node-taglib-sharp
if (tagWriteOptions && fs.existsSync(songPath)) {
try {
await writeAudioTags(songPath, songInfo, tagWriteOptions)
const baseName = path.basename(songPath, path.extname(songPath))
const dirName = path.dirname(songPath)
let coverExt = '.jpg'
let coverPath = path.join(dirName, `${baseName}${coverExt}`)
let coverDownloaded = false
// 下载封面仅当启用且有URL
if (tagWriteOptions.cover && songInfo?.img) {
try {
const coverRes = await axios.get(songInfo.img, {
responseType: 'arraybuffer',
timeout: 10000
})
const ct =
(coverRes.headers && (coverRes.headers['content-type'] as string | undefined)) ||
undefined
coverExt = resolveCoverExt(songInfo.img, ct)
coverPath = path.join(dirName, `${baseName}${coverExt}`)
await fsPromise.writeFile(coverPath, Buffer.from(coverRes.data))
coverDownloaded = true
} catch (e) {
console.warn('下载封面失败:', e instanceof Error ? e.message : e)
}
}
// 读取歌曲文件并设置标签
const songFile = File.createFromPath(songPath)
// 使用默认 ID3v2.3
Id3v2Settings.forceDefaultVersion = true
Id3v2Settings.defaultVersion = 3
songFile.tag.title = songInfo?.name || '未知曲目'
songFile.tag.album = songInfo?.albumName || '未知专辑'
const artists = songInfo?.singer ? [songInfo.singer] : ['未知艺术家']
songFile.tag.performers = artists
songFile.tag.albumArtists = artists
// 写入歌词(转换为标准 LRC
if (tagWriteOptions.lyrics && songInfo?.lrc) {
const convertedLrc = convertLrcFormat(songInfo.lrc)
songFile.tag.lyrics = convertedLrc
}
// 写入封面
if (tagWriteOptions.cover && coverDownloaded) {
const songCover = Picture.fromPath(coverPath)
songFile.tag.pictures = [songCover]
}
// 保存并释放
songFile.save()
songFile.dispose()
// 删除临时封面
if (coverDownloaded) {
try {
await fsPromise.unlink(coverPath)
} catch {}
}
} catch (error) {
console.warn('写入音频标签失败:', error)
throw ffmpegStatic
console.warn('写入音乐元信息失败:', error)
}
}
return {

View File

@@ -1,6 +1,7 @@
import { httpFetch } from '../../request'
import { decodeName, formatPlayTime, sizeFormate } from '../index'
import { decodeName, formatPlayTime } from '../../index'
import { formatSingerName } from '../utils'
import { getBatchMusicQualityInfo } from './quality_detail'
export default {
limit: 30,
@@ -9,87 +10,72 @@ export default {
allPage: 1,
musicSearch(str, page, limit) {
const searchRequest = httpFetch(
`https://songsearch.kugou.com/song_search_v2?keyword=${encodeURIComponent(str)}&page=${page}&pagesize=${limit}&userid=0&clientver=&platform=WebFilter&filter=2&iscorrection=1&privilege_filter=0&area_code=1`
`https://songsearch.kugou.com/song_search_v2?keyword=${encodeURIComponent(
str
)}&page=${page}&pagesize=${limit}&userid=0&clientver=&platform=WebFilter&filter=2&iscorrection=1&privilege_filter=0&area_code=1`
)
return searchRequest.promise.then(({ body }) => body)
},
filterData(rawData) {
const types = []
const _types = {}
if (rawData.FileSize !== 0) {
const size = sizeFormate(rawData.FileSize)
types.push({ type: '128k', size, hash: rawData.FileHash })
_types['128k'] = {
size,
hash: rawData.FileHash
}
}
if (rawData.HQFileSize !== 0) {
const size = sizeFormate(rawData.HQFileSize)
types.push({ type: '320k', size, hash: rawData.HQFileHash })
_types['320k'] = {
size,
hash: rawData.HQFileHash
}
}
if (rawData.SQFileSize !== 0) {
const size = sizeFormate(rawData.SQFileSize)
types.push({ type: 'flac', size, hash: rawData.SQFileHash })
_types.flac = {
size,
hash: rawData.SQFileHash
}
}
if (rawData.ResFileSize !== 0) {
const size = sizeFormate(rawData.ResFileSize)
types.push({ type: 'flac24bit', size, hash: rawData.ResFileHash })
_types.flac24bit = {
size,
hash: rawData.ResFileHash
}
}
return {
singer: decodeName(formatSingerName(rawData.Singers, 'name')),
name: decodeName(rawData.SongName),
albumName: decodeName(rawData.AlbumName),
albumId: rawData.AlbumID,
songmid: rawData.Audioid,
source: 'kg',
interval: formatPlayTime(rawData.Duration),
_interval: rawData.Duration,
img: null,
lrc: null,
otherSource: null,
hash: rawData.FileHash,
types,
_types,
typeUrl: {}
}
},
handleResult(rawData) {
const ids = new Set()
const list = []
async handleResult(rawData) {
let ids = new Set()
const items = []
rawData.forEach((item) => {
const key = item.Audioid + item.FileHash
if (ids.has(key)) return
ids.add(key)
list.push(this.filterData(item))
for (const childItem of item.Grp) {
const key = item.Audioid + item.FileHash
if (ids.has(key)) continue
if (!ids.has(key)) {
ids.add(key)
list.push(this.filterData(childItem))
items.push(item)
}
for (const childItem of item.Grp || []) {
const childKey = childItem.Audioid + childItem.FileHash
if (!ids.has(childKey)) {
ids.add(childKey)
items.push(childItem)
}
}
})
const hashList = items.map((item) => item.FileHash)
let qualityInfoMap = {}
try {
const qualityInfoRequest = getBatchMusicQualityInfo(hashList)
qualityInfoMap = await qualityInfoRequest.promise
} catch (error) {
console.error('Failed to fetch quality info:', error)
}
return items.map((item) => {
const { types = [], _types = {} } = qualityInfoMap[item.FileHash] || {}
return {
singer: decodeName(formatSingerName(item.Singers, 'name')),
name: decodeName(item.SongName),
albumName: decodeName(item.AlbumName),
albumId: item.AlbumID,
songmid: item.Audioid,
source: 'kg',
interval: formatPlayTime(item.Duration),
_interval: item.Duration,
img: null,
lrc: null,
otherSource: null,
hash: item.FileHash,
types,
_types,
typeUrl: {},
}
})
return list
},
search(str, page = 1, limit, retryNum = 0) {
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
if (limit == null) limit = this.limit
// http://newlyric.kuwo.cn/newlyric.lrc?62355680
return this.musicSearch(str, page, limit).then((result) => {
return this.musicSearch(str, page, limit).then(async (result) => {
if (!result || result.error_code !== 0) return this.search(str, page, limit, retryNum)
const list = this.handleResult(result.data.lists)
let list = await this.handleResult(result.data.lists)
if (list == null) return this.search(str, page, limit, retryNum)
@@ -102,8 +88,8 @@ export default {
allPage: this.allPage,
limit,
total: this.total,
source: 'kg'
source: 'kg',
})
})
}
}
},
}

View File

@@ -0,0 +1,190 @@
import { httpFetch } from '../../request'
import { dnsLookup } from '../utils'
import { headers, timeout } from '../options'
import { sizeFormate, decodeName, formatPlayTime } from '../../index'
import { formatSingerName } from '../utils'
console.log(headers);
export const getBatchMusicQualityInfo = (hashList) => {
const resources = hashList.map((hash) => ({
id: 0,
type: 'audio',
hash,
}))
const requestObj = httpFetch(
`https://gateway.kugou.com/goodsmstore/v1/get_res_privilege?appid=1005&clientver=20049&clienttime=${Date.now()}&mid=NeZha`,
{
method: 'post',
timeout,
headers,
body: {
behavior: 'play',
clientver: '20049',
resource: resources,
area_code: '1',
quality: '128',
qualities: [
'128',
'320',
'flac',
'high',
'dolby',
'viper_atmos',
'viper_tape',
'viper_clear',
],
},
lookup: dnsLookup,
family: 4,
}
)
const qualityInfoMap = {}
requestObj.promise = requestObj.promise.then(({ statusCode, body }) => {
if (statusCode != 200 || body.error_code != 0)
return Promise.reject(new Error('获取音质信息失败'))
body.data.forEach((songData, index) => {
const hash = hashList[index]
const types = []
const _types = {}
if (!songData || !songData.relate_goods) return
for (const quality_data of songData.relate_goods) {
if (quality_data.quality === '128') {
let size = sizeFormate(quality_data.info.filesize)
types.push({ type: '128k', size, hash: quality_data.hash })
_types['128k'] = {
size,
hash: quality_data.hash,
}
}
if (quality_data.quality === '320') {
let size = sizeFormate(quality_data.info.filesize)
types.push({ type: '320k', size, hash: quality_data.hash })
_types['320k'] = {
size,
hash: quality_data.hash,
}
}
if (quality_data.quality === 'flac') {
let size = sizeFormate(quality_data.info.filesize)
types.push({ type: 'flac', size, hash: quality_data.hash })
_types.flac = {
size,
hash: quality_data.hash,
}
}
if (quality_data.quality === 'high') {
let size = sizeFormate(quality_data.info.filesize)
types.push({ type: 'hires', size, hash: quality_data.hash })
_types.hires = {
size,
hash: quality_data.hash,
}
}
if (quality_data.quality === 'viper_clear') {
let size = sizeFormate(quality_data.info.filesize)
types.push({ type: 'master', size, hash: quality_data.hash })
_types.master = {
size,
hash: quality_data.hash,
}
}
if (quality_data.quality === 'viper_atmos') {
let size = sizeFormate(quality_data.info.filesize)
types.push({ type: 'atmos', size, hash: quality_data.hash })
_types.atmos = {
size,
hash: quality_data.hash,
}
}
}
qualityInfoMap[hash] = { types, _types }
})
return qualityInfoMap
})
return requestObj
}
export const getHashFromItem = (item) => {
if (item.hash) return item.hash
if (item.FileHash) return item.FileHash
if (item.audio_info && item.audio_info.hash) return item.audio_info.hash
return null
}
export const filterData = async (rawList, options = {}) => {
let processedList = rawList
if (options.removeDuplicates) {
let ids = new Set()
processedList = rawList.filter((item) => {
if (!item) return false
const audioId = item.audio_info?.audio_id || item.audio_id
if (ids.has(audioId)) return false
ids.add(audioId)
return true
})
}
const hashList = processedList.map((item) => getHashFromItem(item)).filter((hash) => hash)
const qualityInfoRequest = getBatchMusicQualityInfo(hashList)
let qualityInfoMap = {}
try {
qualityInfoMap = await qualityInfoRequest.promise
} catch (error) {
console.error('Failed to fetch quality info:', error)
}
return processedList.map((item) => {
const hash = getHashFromItem(item)
const { types = [], _types = {} } = qualityInfoMap[hash] || {}
if (item.audio_info) {
return {
name: decodeName(item.songname),
singer: decodeName(item.author_name),
albumName: decodeName(item.album_info?.album_name || item.remark),
albumId: item.album_info.album_id,
songmid: item.audio_info.audio_id,
source: 'kg',
interval: options.fix
? formatPlayTime(parseInt(item.audio_info.timelength) / 1000)
: formatPlayTime(parseInt(item.audio_info.timelength)),
img: null,
lrc: null,
hash: item.audio_info.hash,
otherSource: null,
types,
_types,
typeUrl: {},
}
}
return {
name: decodeName(item.songname),
singer: decodeName(item.singername) || formatSingerName(item.authors, 'author_name'),
albumName: decodeName(item.album_name || item.remark),
albumId: item.album_id,
songmid: item.audio_id,
source: 'kg',
interval: options.fix ? formatPlayTime(item.duration / 1000) : formatPlayTime(item.duration),
img: null,
lrc: null,
hash: item.hash,
types,
_types,
typeUrl: {},
}
})
}

View File

@@ -1,8 +1,8 @@
import { httpFetch } from '../../request'
import { decodeName, formatPlayTime, sizeFormate, dateFormat, formatPlayCount } from '../index'
import { decodeName, dateFormat, formatPlayCount } from '../../index'
import './vendors/infSign.min'
import { signatureParams } from './util'
import { filterData } from './quality_detail'
const handleSignature = (id, page, limit) =>
new Promise((resolve, reject) => {
@@ -14,7 +14,7 @@ const handleSignature = (id, page, limit) =>
isCDN: !0,
callback(i) {
resolve(i.signature)
}
},
}
)
})
@@ -27,36 +27,36 @@ export default {
listDetailLimit: 10000,
currentTagInfo: {
id: undefined,
info: undefined
info: undefined,
},
sortList: [
{
name: '推荐',
id: '5'
id: '5',
},
{
name: '最热',
id: '6'
id: '6',
},
{
name: '最新',
id: '7'
id: '7',
},
{
name: '热藏',
id: '3'
id: '3',
},
{
name: '飙升',
id: '8'
}
id: '8',
},
],
cache: new Map(),
regExps: {
listData: /global\.data = (\[.+\]);/,
listInfo: /global = {[\s\S]+?name: "(.+)"[\s\S]+?pic: "(.+)"[\s\S]+?};/,
// https://www.kugou.com/yy/special/single/1067062.html
listDetailLink: /^.+\/(\d+)\.html(?:\?.*|&.*$|#.*$|$)/
listDetailLink: /^.+\/(\d+)\.html(?:\?.*|&.*$|#.*$|$)/,
},
parseHtmlDesc(html) {
const prefix = '<div class="pc_specail_text pc_singer_tab_content" id="specailIntroduceWrap">'
@@ -71,18 +71,17 @@ export default {
if (tryNum > 2) throw new Error('try max num')
const { body } = await httpFetch(this.getSongListDetailUrl(id)).promise
const listData = body.match(this.regExps.listData)
const listInfo = body.match(this.regExps.listInfo)
let listData = body.match(this.regExps.listData)
let listInfo = body.match(this.regExps.listInfo)
if (!listData) return this.getListDetailBySpecialId(id, page, ++tryNum)
const list = await this.getMusicInfos(JSON.parse(listData[1]))
// listData = this.filterData(JSON.parse(listData[1]))
let list = await this.getMusicInfos(JSON.parse(listData[1]))
let name
let pic
if (listInfo) {
name = listInfo[1]
pic = listInfo[2]
}
const desc = this.parseHtmlDesc(body)
let desc = this.parseHtmlDesc(body)
return {
list,
@@ -93,10 +92,10 @@ export default {
info: {
name,
img: pic,
desc
desc,
// author: body.result.info.userinfo.username,
// play_count: formatPlayCount(body.result.listen_num),
}
},
}
},
getInfoUrl(tagId) {
@@ -116,11 +115,11 @@ export default {
const result = []
if (rawData.status !== 1) return result
for (const key of Object.keys(rawData.data)) {
const tag = rawData.data[key]
let tag = rawData.data[key]
result.push({
id: tag.special_id,
name: tag.special_name,
source: 'kg'
source: 'kg',
})
}
return result
@@ -135,8 +134,8 @@ export default {
parent_name: tag.pname,
id: tag.id,
name: tag.name,
source: 'kg'
}))
source: 'kg',
})),
})
}
return result
@@ -159,7 +158,7 @@ export default {
{
method: 'post',
headers: {
'User-Agent': 'KuGou2012-8275-web_browser_event_handler'
'User-Agent': 'KuGou2012-8275-web_browser_event_handler',
},
body: {
appid: 1001,
@@ -170,8 +169,8 @@ export default {
platform: 'pc',
userid: '262643156',
return_min: 6,
return_max: 15
}
return_max: 15,
},
}
)
return this._requestObj_listRecommend.promise.then(({ body }) => {
@@ -190,7 +189,7 @@ export default {
total: item.songcount,
grade: item.grade,
desc: item.intro,
source: 'kg'
source: 'kg',
}))
},
@@ -219,7 +218,7 @@ export default {
},
createTask(hashs) {
const data = {
let data = {
area_code: '1',
show_privilege: 1,
show_album_info: '1',
@@ -230,16 +229,16 @@ export default {
dfid: '-',
clienttime: Date.now(),
key: 'OIlwieks28dk2k092lksi2UIkp',
fields: 'album_info,author_name,audio_info,ori_audio_name,base,songname'
fields: 'album_info,author_name,audio_info,ori_audio_name,base,songname',
}
let list = hashs
const tasks = []
let tasks = []
while (list.length) {
tasks.push(Object.assign({ data: list.slice(0, 100) }, data))
if (list.length < 100) break
list = list.slice(100)
}
const url = 'http://gateway.kugou.com/v2/album_audio/audio'
let url = 'http://gateway.kugou.com/v2/album_audio/audio'
return tasks.map((task) =>
this.createHttp(url, {
method: 'POST',
@@ -250,13 +249,13 @@ export default {
'KG-Fake': '0',
'KG-RF': '00869891',
'User-Agent': 'Android712-AndroidPhone-11451-376-0-FeeCacheUpdate-wifi',
'x-router': 'kmr.service.kugou.com'
}
'x-router': 'kmr.service.kugou.com',
},
}).then((data) => data.map((s) => s[0]))
)
},
async getMusicInfos(list) {
return this.filterData2(
return await this.filterData(
await Promise.all(
this.createTask(this.deDuplication(list).map((item) => ({ hash: item.hash })))
).then(([...datas]) => datas.flat())
@@ -269,7 +268,7 @@ export default {
headers: {
'KG-RC': 1,
'KG-THash': 'network_super_call.cpp:3676261689:379',
'User-Agent': ''
'User-Agent': '',
},
body: {
appid: 1001,
@@ -277,13 +276,13 @@ export default {
mid: '21511157a05844bd085308bc76ef3343',
clienttime: 640612895,
key: '36164c4015e704673c588ee202b9ecb8',
data: id
}
data: id,
},
})
// console.log(songInfo)
// type 1单曲2歌单3电台4酷狗码5别人的播放队列
let songList
const info = songInfo.info
let info = songInfo.info
switch (info.type) {
case 2:
if (!info.global_collection_id) return this.getListDetailBySpecialId(info.id)
@@ -299,7 +298,7 @@ export default {
headers: {
'KG-RC': 1,
'KG-THash': 'network_super_call.cpp:3676261689:379',
'User-Agent': ''
'User-Agent': '',
},
body: {
appid: 1001,
@@ -313,13 +312,13 @@ export default {
userid: info.userid,
collect_type: 0,
page: 1,
pagesize: info.count
}
}
pagesize: info.count,
},
},
})
// console.log(songList)
}
const list = await this.getMusicInfos(songList || songInfo.list)
let list = await this.getMusicInfos(songList || songInfo.list)
return {
list,
page: 1,
@@ -330,9 +329,9 @@ export default {
name: info.name,
img: (info.img_size && info.img_size.replace('{size}', 240)) || info.img,
// desc: body.result.info.list_desc,
author: info.username
author: info.username,
// play_count: formatPlayCount(info.count),
}
},
}
},
@@ -342,8 +341,8 @@ export default {
{
headers: {
'User-Agent':
'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1'
}
'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
},
}
)
if (!songInfo.list) {
@@ -354,7 +353,7 @@ export default {
this.getUserListDetail5(chain)
)
}
const list = await this.getMusicInfos(songInfo.list)
let list = await this.getMusicInfos(songInfo.list)
// console.log(info, songInfo)
return {
list,
@@ -366,14 +365,14 @@ export default {
name: songInfo.info.name,
img: songInfo.info.img,
// desc: body.result.info.list_desc,
author: songInfo.info.username
author: songInfo.info.username,
// play_count: formatPlayCount(info.count),
}
},
}
},
deDuplication(datas) {
const ids = new Set()
let ids = new Set()
return datas.filter(({ hash }) => {
if (ids.has(hash)) return false
ids.add(hash)
@@ -388,29 +387,25 @@ export default {
data: [
{
id: gcid,
id_type: 2
}
]
}
const result = await this.createHttp(
`https://t.kugou.com/v1/songlist/batch_decode?${params}&signature=${signatureParams(params, 'android', JSON.stringify(body))}`,
{
method: 'POST',
headers: {
'User-Agent':
'Mozilla/5.0 (Linux; Android 10; HUAWEI HMA-AL00) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Mobile Safari/537.36',
Referer: 'https://m.kugou.com/'
id_type: 2,
},
body
}
)
],
}
const result = await this.createHttp(`https://t.kugou.com/v1/songlist/batch_decode?${params}&signature=${signatureParams(params, 'android', JSON.stringify(body))}`, {
method: 'POST',
headers: {
'User-Agent': 'Mozilla/5.0 (Linux; Android 10; HUAWEI HMA-AL00) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Mobile Safari/537.36',
Referer: 'https://m.kugou.com/',
},
body,
})
return result.list[0].global_collection_id
},
async getUserListDetailByLink({ info }, link) {
const listInfo = info['0']
let listInfo = info['0']
let total = listInfo.count
const tasks = []
let tasks = []
let page = 0
while (total) {
const limit = total > 90 ? 90 : total
@@ -423,8 +418,8 @@ export default {
headers: {
'User-Agent':
'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
Referer: link
}
Referer: link,
},
}
).then((data) => data.list.info)
)
@@ -442,13 +437,13 @@ export default {
name: listInfo.name,
img: listInfo.pic && listInfo.pic.replace('{size}', 240),
// desc: body.result.info.list_desc,
author: listInfo.list_create_username
author: listInfo.list_create_username,
// play_count: formatPlayCount(listInfo.count),
}
},
}
},
createGetListDetail2Task(id, total) {
const tasks = []
let tasks = []
let page = 0
while (total) {
const limit = total > 300 ? 300 : total
@@ -464,7 +459,10 @@ export default {
'&srcappid=2919&clientver=20000&clienttime=1586163263991&mid=1586163263991&uuid=1586163263991&dfid=-'
tasks.push(
this.createHttp(
`https://mobiles.kugou.com/api/v5/special/song_v2?${params}&signature=${signatureParams(params, 'web')}`,
`https://mobiles.kugou.com/api/v5/special/song_v2?${params}&signature=${signatureParams(
params,
'web'
)}`,
{
headers: {
mid: '1586163263991',
@@ -472,8 +470,8 @@ export default {
'User-Agent':
'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
dfid: '-',
clienttime: '1586163263991'
}
clienttime: '1586163263991',
},
}
).then((data) => data.info)
)
@@ -481,14 +479,17 @@ export default {
return Promise.all(tasks).then(([...datas]) => datas.flat())
},
async getUserListDetail2(global_collection_id) {
const id = global_collection_id
let id = global_collection_id
if (id.length > 1000) throw new Error('get list error')
const params =
'appid=1058&specialid=0&global_specialid=' +
id +
'&format=jsonp&srcappid=2919&clientver=20000&clienttime=1586163242519&mid=1586163242519&uuid=1586163242519&dfid=-'
const info = await this.createHttp(
`https://mobiles.kugou.com/api/v5/special/info_v2?${params}&signature=${signatureParams(params, 'web')}`,
let info = await this.createHttp(
`https://mobiles.kugou.com/api/v5/special/info_v2?${params}&signature=${signatureParams(
params,
'web'
)}`,
{
headers: {
mid: '1586163242519',
@@ -496,12 +497,12 @@ export default {
'User-Agent':
'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
dfid: '-',
clienttime: '1586163242519'
}
clienttime: '1586163242519',
},
}
)
const songInfo = await this.createGetListDetail2Task(id, info.songcount)
const list = await this.getMusicInfos(songInfo)
let list = await this.getMusicInfos(songInfo)
// console.log(info, songInfo, list)
return {
list,
@@ -514,8 +515,8 @@ export default {
img: info.imgurl && info.imgurl.replace('{size}', 240),
desc: info.intro,
author: info.nickname,
play_count: formatPlayCount(info.playcount)
}
play_count: formatPlayCount(info.playcount),
},
}
},
@@ -524,8 +525,8 @@ export default {
const { body } = await httpFetch(`https://m.kugou.com/share/?chain=${chain}&id=${chain}`, {
headers: {
'User-Agent':
'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'
}
'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',
},
}).promise
let result = body.match(/var\sphpParam\s=\s({.+?});/)
if (result) result = JSON.parse(result[1])
@@ -534,13 +535,13 @@ export default {
},
async getUserListDetailByPcChain(chain) {
const key = `${chain}_pc_list`
let key = `${chain}_pc_list`
if (this.cache.has(key)) return this.cache.get(key)
const { body } = await httpFetch(`http://www.kugou.com/share/${chain}.html`, {
headers: {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'
}
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36',
},
}).promise
let result = body.match(/var\sdataFromSmarty\s=\s(\[.+?\])/)
if (result) result = JSON.parse(result[1])
@@ -554,7 +555,7 @@ export default {
const limit = 100
const [listInfo, list] = await Promise.all([
this.getListInfoByChain(chain),
this.getUserListDetailById(songInfo.id, page, limit)
this.getUserListDetailById(songInfo.id, page, limit),
])
return {
list: list || [],
@@ -566,16 +567,16 @@ export default {
name: listInfo.specialname,
img: listInfo.imgurl && listInfo.imgurl.replace('{size}', 240),
// desc: body.result.info.list_desc,
author: listInfo.nickname
author: listInfo.nickname,
// play_count: formatPlayCount(info.count),
}
},
}
},
async getUserListDetail5(chain) {
const [listInfo, list] = await Promise.all([
this.getListInfoByChain(chain),
this.getUserListDetailByPcChain(chain)
this.getUserListDetailByPcChain(chain),
])
return {
list: list || [],
@@ -587,28 +588,28 @@ export default {
name: listInfo.specialname,
img: listInfo.imgurl && listInfo.imgurl.replace('{size}', 240),
// desc: body.result.info.list_desc,
author: listInfo.nickname
author: listInfo.nickname,
// play_count: formatPlayCount(info.count),
}
},
}
},
async getUserListDetailById(id, page, limit) {
const signature = await handleSignature(id, page, limit)
const info = await this.createHttp(
let info = await this.createHttp(
`https://pubsongscdn.kugou.com/v2/get_other_list_file?srcappid=2919&clientver=20000&appid=1058&type=0&module=playlist&page=${page}&pagesize=${limit}&specialid=${id}&signature=${signature}`,
{
headers: {
Referer: 'https://m3ws.kugou.com/share/index.php',
'User-Agent':
'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
dfid: '-'
}
dfid: '-',
},
}
)
// console.log(info)
const result = await this.getMusicInfos(info.info)
let result = await this.getMusicInfos(info.info)
// console.log(info, songInfo)
return result
},
@@ -616,19 +617,15 @@ export default {
async getUserListDetail(link, page, retryNum = 0) {
if (retryNum > 3) return Promise.reject(new Error('link try max num'))
if (link.includes('#')) link = link.replace(/#.*$/, '')
if (link.includes('global_collection_id'))
return this.getUserListDetail2(
link.replace(/^.*?global_collection_id=(\w+)(?:&.*$|#.*$|$)/, '$1')
)
if (link.includes('global_collection_id')) return this.getUserListDetail2(link.replace(/^.*?global_collection_id=(\w+)(?:&.*$|#.*$|$)/, '$1'))
if (link.includes('gcid_')) {
const gcid = link.match(/gcid_\w+/)?.[0]
let gcid = link.match(/gcid_\w+/)?.[0]
if (gcid) {
const global_collection_id = await this.decodeGcid(gcid)
if (global_collection_id) return this.getUserListDetail2(global_collection_id)
}
}
if (link.includes('chain='))
return this.getUserListDetail3(link.replace(/^.*?chain=(\w+)(?:&.*$|#.*$|$)/, '$1'), page)
if (link.includes('chain=')) return this.getUserListDetail3(link.replace(/^.*?chain=(\w+)(?:&.*$|#.*$|$)/, '$1'), page)
if (link.includes('.html')) {
if (link.includes('zlist.html')) {
link = link.replace(/^(.*)zlist\.html/, 'https://m3ws.kugou.com/zlist/list')
@@ -650,34 +647,27 @@ export default {
headers: {
'User-Agent':
'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
Referer: link
}
Referer: link,
},
})
const {
headers: { location },
statusCode,
body
body,
} = await requestObj_listDetailLink.promise
// console.log(body, location)
if (statusCode > 400) return this.getUserListDetail(link, page, ++retryNum)
if (location) {
// console.log(location)
if (location.includes('global_collection_id'))
return this.getUserListDetail2(
location.replace(/^.*?global_collection_id=(\w+)(?:&.*$|#.*$|$)/, '$1')
)
if (location.includes('global_collection_id')) return this.getUserListDetail2(location.replace(/^.*?global_collection_id=(\w+)(?:&.*$|#.*$|$)/, '$1'))
if (location.includes('gcid_')) {
const gcid = link.match(/gcid_\w+/)?.[0]
let gcid = link.match(/gcid_\w+/)?.[0]
if (gcid) {
const global_collection_id = await this.decodeGcid(gcid)
if (global_collection_id) return this.getUserListDetail2(global_collection_id)
}
}
if (location.includes('chain='))
return this.getUserListDetail3(
location.replace(/^.*?chain=(\w+)(?:&.*$|#.*$|$)/, '$1'),
page
)
if (location.includes('chain=')) return this.getUserListDetail3(location.replace(/^.*?chain=(\w+)(?:&.*$|#.*$|$)/, '$1'), page)
if (location.includes('.html')) {
if (location.includes('zlist.html')) {
let link = location.replace(/^(.*)zlist\.html/, 'https://m3ws.kugou.com/zlist/list')
@@ -698,7 +688,7 @@ export default {
// console.log('location', location)
return this.getUserListDetail(location, page, ++retryNum)
}
if (typeof body === 'string') {
if (typeof body == 'string') {
let global_collection_id = body.match(/"global_collection_id":"(\w+)"/)?.[1]
if (!global_collection_id) {
let gcid = body.match(/"encode_gic":"(\w+)"/)?.[1]
@@ -729,184 +719,9 @@ export default {
return this.getListDetailBySpecialId(id, page)
},
filterData(rawList) {
// console.log(rawList)
return rawList.map((item) => {
const types = []
const _types = {}
if (item.filesize !== 0) {
const size = sizeFormate(item.filesize)
types.push({ type: '128k', size, hash: item.hash })
_types['128k'] = {
size,
hash: item.hash
}
}
if (item.filesize_320 !== 0) {
const size = sizeFormate(item.filesize_320)
types.push({ type: '320k', size, hash: item.hash_320 })
_types['320k'] = {
size,
hash: item.hash_320
}
}
if (item.filesize_ape !== 0) {
const size = sizeFormate(item.filesize_ape)
types.push({ type: 'ape', size, hash: item.hash_ape })
_types.ape = {
size,
hash: item.hash_ape
}
}
if (item.filesize_flac !== 0) {
const size = sizeFormate(item.filesize_flac)
types.push({ type: 'flac', size, hash: item.hash_flac })
_types.flac = {
size,
hash: item.hash_flac
}
}
return {
singer: decodeName(item.singername),
name: decodeName(item.songname),
albumName: decodeName(item.album_name),
albumId: item.album_id,
songmid: item.audio_id,
source: 'kg',
interval: formatPlayTime(item.duration / 1000),
img: null,
lrc: null,
hash: item.hash,
types,
_types,
typeUrl: {}
}
})
},
// getSinger(singers) {
// let arr = []
// singers?.forEach(singer => {
// arr.push(singer.name)
// })
// return arr.join('、')
// },
// v9 API
// filterDatav9(rawList) {
// console.log(rawList)
// return rawList.map(item => {
// const types = []
// const _types = {}
// item.relate_goods.forEach(qualityObj => {
// if (qualityObj.level === 2) {
// let size = sizeFormate(qualityObj.size)
// types.push({ type: '128k', size, hash: qualityObj.hash })
// _types['128k'] = {
// size,
// hash: qualityObj.hash,
// }
// } else if (qualityObj.level === 4) {
// let size = sizeFormate(qualityObj.size)
// types.push({ type: '320k', size, hash: qualityObj.hash })
// _types['320k'] = {
// size,
// hash: qualityObj.hash,
// }
// } else if (qualityObj.level === 5) {
// let size = sizeFormate(qualityObj.size)
// types.push({ type: 'flac', size, hash: qualityObj.hash })
// _types.flac = {
// size,
// hash: qualityObj.hash,
// }
// } else if (qualityObj.level === 6) {
// let size = sizeFormate(qualityObj.size)
// types.push({ type: 'flac24bit', size, hash: qualityObj.hash })
// _types.flac24bit = {
// size,
// hash: qualityObj.hash,
// }
// }
// })
// const nameInfo = item.name.split(' - ')
// return {
// singer: this.getSinger(item.singerinfo),
// name: decodeName((nameInfo[1] ?? nameInfo[0]).trim()),
// albumName: decodeName(item.albuminfo.name),
// albumId: item.albuminfo.id,
// songmid: item.audio_id,
// source: 'kg',
// interval: formatPlayTime(item.timelen / 1000),
// img: null,
// lrc: null,
// hash: item.hash,
// types,
// _types,
// typeUrl: {},
// }
// })
// },
// hash list filter
filterData2(rawList) {
// console.log(rawList)
const ids = new Set()
const list = []
rawList.forEach((item) => {
if (!item) return
if (ids.has(item.audio_info.audio_id)) return
ids.add(item.audio_info.audio_id)
const types = []
const _types = {}
if (item.audio_info.filesize !== '0') {
const size = sizeFormate(parseInt(item.audio_info.filesize))
types.push({ type: '128k', size, hash: item.audio_info.hash })
_types['128k'] = {
size,
hash: item.audio_info.hash
}
}
if (item.audio_info.filesize_320 !== '0') {
const size = sizeFormate(parseInt(item.audio_info.filesize_320))
types.push({ type: '320k', size, hash: item.audio_info.hash_320 })
_types['320k'] = {
size,
hash: item.audio_info.hash_320
}
}
if (item.audio_info.filesize_flac !== '0') {
const size = sizeFormate(parseInt(item.audio_info.filesize_flac))
types.push({ type: 'flac', size, hash: item.audio_info.hash_flac })
_types.flac = {
size,
hash: item.audio_info.hash_flac
}
}
if (item.audio_info.filesize_high !== '0') {
const size = sizeFormate(parseInt(item.audio_info.filesize_high))
types.push({ type: 'flac24bit', size, hash: item.audio_info.hash_high })
_types.flac24bit = {
size,
hash: item.audio_info.hash_high
}
}
list.push({
singer: decodeName(item.author_name),
name: decodeName(item.songname),
albumName: decodeName(item.album_info.album_name),
albumId: item.album_info.album_id,
songmid: item.audio_info.audio_id,
source: 'kg',
interval: formatPlayTime(parseInt(item.audio_info.timelength) / 1000),
img: null,
lrc: null,
hash: item.audio_info.hash,
otherSource: null,
types,
_types,
typeUrl: {}
})
})
return list
async filterData(rawList) {
return await filterData(rawList, { removeDuplicates: true, fix: true })
},
// 获取列表信息
@@ -920,14 +735,14 @@ export default {
limit: body.data.params.pagesize,
page: body.data.params.p,
total: body.data.params.total,
source: 'kg'
source: 'kg',
}
})
},
// 获取列表数据
getList(sortId, tagId, page) {
const tasks = [this.getSongList(sortId, tagId, page)]
let tasks = [this.getSongList(sortId, tagId, page)]
tasks.push(
this.currentTagInfo.id === tagId
? Promise.resolve(this.currentTagInfo.info)
@@ -943,7 +758,7 @@ export default {
if (recommendList) list.unshift(...recommendList)
return {
list,
...info
...info,
}
})
},
@@ -958,13 +773,13 @@ export default {
return {
hotTag: this.filterInfoHotTag(body.data.hotTag),
tags: this.filterTagInfo(body.data.tagids),
source: 'kg'
source: 'kg',
}
})
},
getDetailPageUrl(id) {
if (typeof id === 'string') {
if (typeof id == 'string') {
if (/^https?:\/\//.test(id)) return id
id = id.replace('id_', '')
}
@@ -975,7 +790,9 @@ export default {
// http://msearchretry.kugou.com/api/v3/search/special?version=9209&keyword=%E5%91%A8%E6%9D%B0%E4%BC%A6&pagesize=20&filter=0&page=1&sver=2&with_res_tag=0
// return httpFetch(`http://ioscdn.kugou.com/api/v3/search/special?keyword=${encodeURIComponent(text)}&page=${page}&pagesize=${limit}&showtype=10&plat=2&version=7910&correct=1&sver=5`)
return httpFetch(
`http://msearchretry.kugou.com/api/v3/search/special?keyword=${encodeURIComponent(text)}&page=${page}&pagesize=${limit}&showtype=10&filter=0&version=7910&sver=2`
`http://msearchretry.kugou.com/api/v3/search/special?keyword=${encodeURIComponent(
text
)}&page=${page}&pagesize=${limit}&showtype=10&filter=0&version=7910&sver=2`
).promise.then(({ body }) => {
if (body.errcode != 0) throw new Error('filed')
// console.log(body.data.info)
@@ -991,17 +808,17 @@ export default {
grade: item.grade,
desc: item.intro,
total: item.songcount,
source: 'kg'
source: 'kg',
}
}),
limit,
total: body.data.total,
source: 'kg'
source: 'kg',
}
})
}
},
}
// getList
// getTags
// getListDetail
// getListDetail

View File

@@ -16,11 +16,28 @@ export default {
}
)
return this.requestObj.then((body) => {
return body[0].RecordDatas
return body
})
},
handleResult(rawData) {
return rawData.map((info) => info.HintInfo)
let list = {
order: [],
songs: [],
albums: []
}
if (rawData[0].RecordCount > 0) {
list.order.push('songs')
}
if (rawData[2].RecordCount > 0) {
list.order.push('albums')
}
list.songs = rawData[0].RecordDatas.map((info) => ({
name: info.HintInfo
}))
list.albums = rawData[2].RecordDatas.map((info) => ({
name: info.HintInfo
}))
return list
},
async search(str) {
return this.tipSearchBySong(str).then((result) => this.handleResult(result))

View File

@@ -1,13 +1,13 @@
// import '../../polyfill/array.find'
import { httpFetch } from '../../request'
import { formatPlayTime, decodeName } from '../index'
import { formatPlayTime, decodeName } from '../../index'
// import { debug } from '../../utils/env'
import { formatSinger } from './util'
export default {
regExps: {
mInfo: /level:(\w+),bitrate:(\d+),format:(\w+),size:([\w.]+)/
mInfo: /level:(\w+),bitrate:(\d+),format:(\w+),size:([\w.]+)/,
},
limit: 30,
total: 0,
@@ -32,7 +32,7 @@ export default {
// console.log(rawData)
for (let i = 0; i < rawData.length; i++) {
const info = rawData[i]
const songId = info.MUSICRID.replace('MUSIC_', '')
let songId = info.MUSICRID.replace('MUSIC_', '')
// const format = (info.FORMATS || info.formats).split('|')
if (!info.N_MINFO) {
@@ -43,33 +43,39 @@ export default {
const types = []
const _types = {}
const infoArr = info.N_MINFO.split(';')
let infoArr = info.N_MINFO.split(';')
for (let info of infoArr) {
info = info.match(this.regExps.mInfo)
if (info) {
switch (info[2]) {
case '20900':
types.push({ type: 'master', size: info[4] })
_types.master = {
size: info[4].toLocaleUpperCase(),
}
break
case '4000':
types.push({ type: 'flac24bit', size: info[4] })
_types.flac24bit = {
size: info[4].toLocaleUpperCase()
types.push({ type: 'hires', size: info[4] })
_types.hires = {
size: info[4].toLocaleUpperCase(),
}
break
case '2000':
types.push({ type: 'flac', size: info[4] })
_types.flac = {
size: info[4].toLocaleUpperCase()
size: info[4].toLocaleUpperCase(),
}
break
case '320':
types.push({ type: '320k', size: info[4] })
_types['320k'] = {
size: info[4].toLocaleUpperCase()
size: info[4].toLocaleUpperCase(),
}
break
case '128':
types.push({ type: '128k', size: info[4] })
_types['128k'] = {
size: info[4].toLocaleUpperCase()
size: info[4].toLocaleUpperCase(),
}
break
}
@@ -77,7 +83,7 @@ export default {
}
types.reverse()
const interval = parseInt(info.DURATION)
let interval = parseInt(info.DURATION)
result.push({
name: decodeName(info.SONGNAME),
@@ -95,7 +101,7 @@ export default {
otherSource: null,
types,
_types,
typeUrl: {}
typeUrl: {},
})
}
// console.log(result)
@@ -109,7 +115,7 @@ export default {
// console.log(result)
if (!result || (result.TOTAL !== '0' && result.SHOW === '0'))
return this.search(str, page, limit, ++retryNum)
const list = this.handleResult(result.abslist)
let list = this.handleResult(result.abslist)
if (list == null) return this.search(str, page, limit, ++retryNum)
@@ -122,8 +128,8 @@ export default {
allPage: this.allPage,
total: this.total,
limit,
source: 'kw'
source: 'kw',
})
})
}
}
},
}

View File

@@ -1,5 +1,5 @@
import { httpFetch } from '../../request'
import { formatPlayTime, decodeName } from '../index'
import { formatPlayTime, decodeName } from '../../index'
import { formatSinger, objStr2JSON } from './util'
import album from './album'
@@ -13,18 +13,18 @@ export default {
sortList: [
{
name: '最新',
id: 'new'
id: 'new',
},
{
name: '最热',
id: 'hot'
}
id: 'hot',
},
],
regExps: {
mInfo: /level:(\w+),bitrate:(\d+),format:(\w+),size:([\w.]+)/,
// http://www.kuwo.cn/playlist_detail/2886046289
// https://m.kuwo.cn/h5app/playlist/2736267853?t=qqfriend
listDetailLink: /^.+\/playlist(?:_detail)?\/(\d+)(?:\?.*|&.*$|#.*$|$)/
listDetailLink: /^.+\/playlist(?:_detail)?\/(\d+)(?:\?.*|&.*$|#.*$|$)/,
},
tagsUrl:
'http://wapi.kuwo.cn/api/pc/classify/playlist/getTagList?cmd=rcm_keyword_playlist&user=0&prod=kwplayer_pc_9.0.5.0&vipver=9.0.5.0&source=kwplayer_pc_9.0.5.0&loginUid=0&loginSid=0&appUid=76039576',
@@ -43,7 +43,9 @@ export default {
},
getListDetailUrl(id, page) {
// http://nplserver.kuwo.cn/pl.svc?op=getlistinfo&pid=2858093057&pn=0&rn=100&encode=utf8&keyset=pl2012&identity=kuwo&pcmp4=1&vipver=MUSIC_9.0.5.0_W1&newver=1
return `http://nplserver.kuwo.cn/pl.svc?op=getlistinfo&pid=${id}&pn=${page - 1}&rn=${this.limit_song}&encode=utf8&keyset=pl2012&identity=kuwo&pcmp4=1&vipver=MUSIC_9.0.5.0_W1&newver=1`
return `http://nplserver.kuwo.cn/pl.svc?op=getlistinfo&pid=${id}&pn=${page - 1}&rn=${
this.limit_song
}&encode=utf8&keyset=pl2012&identity=kuwo&pcmp4=1&vipver=MUSIC_9.0.5.0_W1&newver=1`
// http://mobileinterfaces.kuwo.cn/er.s?type=get_pc_qz_data&f=web&id=140&prod=pc
},
@@ -72,7 +74,7 @@ export default {
return rawList.map((item) => ({
id: `${item.id}-${item.digest}`,
name: item.name,
source: 'kw'
source: 'kw',
}))
},
filterTagInfo(rawList) {
@@ -83,8 +85,8 @@ export default {
parent_name: type.name,
id: `${item.id}-${item.digest}`,
name: item.name,
source: 'kw'
}))
source: 'kw',
})),
}))
},
@@ -95,7 +97,7 @@ export default {
let id
let type
if (tagId) {
const arr = tagId.split('-')
let arr = tagId.split('-')
id = arr[0]
type = arr[1]
} else {
@@ -110,7 +112,7 @@ export default {
total: body.data.total,
page: body.data.pn,
limit: body.data.rn,
source: 'kw'
source: 'kw',
}
} else if (!body.length) {
return this.getList(sortId, tagId, page, ++tryNum)
@@ -120,7 +122,7 @@ export default {
total: 1000,
page,
limit: 1000,
source: 'kw'
source: 'kw',
}
})
},
@@ -145,7 +147,7 @@ export default {
img: item.img,
grade: item.favorcnt / 10,
desc: item.desc,
source: 'kw'
source: 'kw',
}))
},
filterList2(rawData) {
@@ -164,7 +166,7 @@ export default {
img: item.img,
grade: item.favorcnt && item.favorcnt / 10,
desc: item.desc,
source: 'kw'
source: 'kw',
}))
)
})
@@ -188,8 +190,8 @@ export default {
img: body.pic,
desc: body.info,
author: body.uname,
play_count: this.formatPlayCount(body.playnum)
}
play_count: this.formatPlayCount(body.playnum),
},
}
})
},
@@ -207,7 +209,9 @@ export default {
getListDetailDigest5Music(id, page, tryNum = 0) {
if (tryNum > 2) return Promise.reject(new Error('try max num'))
const requestObj = httpFetch(
`http://nplserver.kuwo.cn/pl.svc?op=getlistinfo&pid=${id}&pn=${page - 1}}&rn=${this.limit_song}&encode=utf-8&keyset=pl2012&identity=kuwo&pcmp4=1`
`http://nplserver.kuwo.cn/pl.svc?op=getlistinfo&pid=${id}&pn=${page - 1}}&rn=${
this.limit_song
}&encode=utf-8&keyset=pl2012&identity=kuwo&pcmp4=1`
)
return requestObj.promise.then(({ body }) => {
// console.log(body)
@@ -223,8 +227,8 @@ export default {
img: body.pic,
desc: body.info,
author: body.uname,
play_count: this.formatPlayCount(body.playnum)
}
play_count: this.formatPlayCount(body.playnum),
},
}
})
},
@@ -235,33 +239,33 @@ export default {
filterBDListDetail(rawList) {
return rawList.map((item) => {
const types = []
const _types = {}
for (const info of item.audios) {
let types = []
let _types = {}
for (let info of item.audios) {
info.size = info.size?.toLocaleUpperCase()
switch (info.bitrate) {
case '4000':
types.push({ type: 'flac24bit', size: info.size })
_types.flac24bit = {
size: info.size
size: info.size,
}
break
case '2000':
types.push({ type: 'flac', size: info.size })
_types.flac = {
size: info.size
size: info.size,
}
break
case '320':
types.push({ type: '320k', size: info.size })
_types['320k'] = {
size: info.size
size: info.size,
}
break
case '128':
types.push({ type: '128k', size: info.size })
_types['128k'] = {
size: info.size
size: info.size,
}
break
}
@@ -282,7 +286,7 @@ export default {
otherSource: null,
types,
_types,
typeUrl: {}
typeUrl: {},
}
})
},
@@ -299,8 +303,8 @@ export default {
headers: {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36',
plat: 'h5'
}
plat: 'h5',
},
}
).promise.catch(() => ({ code: 0 }))
@@ -311,7 +315,7 @@ export default {
img: infoData.data.pic,
desc: infoData.data.description,
author: infoData.data.creatorName,
play_count: infoData.data.playNum
play_count: infoData.data.playNum,
}
},
async getListDetailMusicListByBDUserPub(id) {
@@ -321,8 +325,8 @@ export default {
headers: {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36',
plat: 'h5'
}
plat: 'h5',
},
}
).promise.catch(() => ({ code: 0 }))
@@ -334,18 +338,20 @@ export default {
img: infoData.data.userInfo.headImg,
desc: '',
author: infoData.data.userInfo.nickname,
play_count: ''
play_count: '',
}
},
async getListDetailMusicListByBDList(id, source, page, tryNum = 0) {
const { body: listData } = await httpFetch(
`https://bd-api.kuwo.cn/api/service/playlist/${id}/musicList?reqId=${this.getReqId()}&source=${source}&pn=${page}&rn=${this.limit_song}`,
`https://bd-api.kuwo.cn/api/service/playlist/${id}/musicList?reqId=${this.getReqId()}&source=${source}&pn=${page}&rn=${
this.limit_song
}`,
{
headers: {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36',
plat: 'h5'
}
plat: 'h5',
},
}
).promise.catch(() => {
if (tryNum > 2) return Promise.reject(new Error('try max num'))
@@ -359,7 +365,7 @@ export default {
page,
limit: listData.data.pageSize,
total: listData.data.total,
source: 'kw'
source: 'kw',
}
},
async getListDetailMusicListByBD(id, page) {
@@ -383,7 +389,7 @@ export default {
img: '',
desc: '',
author: '',
play_count: ''
play_count: '',
}
// console.log(listData)
return listData
@@ -415,35 +421,53 @@ export default {
filterListDetail(rawData) {
// console.log(rawData)
return rawData.map((item) => {
const infoArr = item.N_MINFO.split(';')
const types = []
const _types = {}
let infoArr = item.N_MINFO.split(';')
let types = []
let _types = {}
for (let info of infoArr) {
info = info.match(this.regExps.mInfo)
if (info) {
switch (info[2]) {
case '20900':
types.push({ type: 'master', size: info[4] })
_types.master = {
size: info[4].toLocaleUpperCase(),
}
break
case '20501':
types.push({ type: 'atmos_plus', size: info[4] })
_types.atmos_plus = {
size: info[4].toLocaleUpperCase(),
}
break
case '20201':
types.push({ type: 'atmos', size: info[4] })
_types.atmos = {
size: info[4].toLocaleUpperCase(),
}
break
case '4000':
types.push({ type: 'flac24bit', size: info[4] })
types.push({ type: 'hires', size: info[4] })
_types.flac24bit = {
size: info[4].toLocaleUpperCase()
size: info[4].toLocaleUpperCase(),
}
break
case '2000':
types.push({ type: 'flac', size: info[4] })
_types.flac = {
size: info[4].toLocaleUpperCase()
size: info[4].toLocaleUpperCase(),
}
break
case '320':
types.push({ type: '320k', size: info[4] })
_types['320k'] = {
size: info[4].toLocaleUpperCase()
size: info[4].toLocaleUpperCase(),
}
break
case '128':
types.push({ type: '128k', size: info[4] })
_types['128k'] = {
size: info[4].toLocaleUpperCase()
size: info[4].toLocaleUpperCase(),
}
break
}
@@ -464,7 +488,7 @@ export default {
otherSource: null,
types,
_types,
typeUrl: {}
typeUrl: {},
}
})
},
@@ -472,13 +496,13 @@ export default {
return Promise.all([this.getTag(), this.getHotTag()]).then(([tags, hotTag]) => ({
tags,
hotTag,
source: 'kw'
source: 'kw',
}))
},
getDetailPageUrl(id) {
if (/[?&:/]/.test(id)) id = id.replace(this.regExps.listDetailLink, '$1')
else if (/^digest-/.test(id)) {
const result = id.split('__')
let result = id.split('__')
id = result[1]
}
return `http://www.kuwo.cn/playlist_detail/${id}`
@@ -486,7 +510,9 @@ export default {
search(text, page, limit = 20) {
return httpFetch(
`http://search.kuwo.cn/r.s?all=${encodeURIComponent(text)}&pn=${page - 1}&rn=${limit}&rformat=json&encoding=utf8&ver=mbox&vipver=MUSIC_8.7.7.0_BCS37&plat=pc&devid=28156413&ft=playlist&pay=0&needliveshow=0`
`http://search.kuwo.cn/r.s?all=${encodeURIComponent(text)}&pn=${
page - 1
}&rn=${limit}&rformat=json&encoding=utf8&ver=mbox&vipver=MUSIC_8.7.7.0_BCS37&plat=pc&devid=28156413&ft=playlist&pay=0&needliveshow=0`
).promise.then(({ body }) => {
body = objStr2JSON(body)
// console.log(body)
@@ -501,17 +527,17 @@ export default {
// time: item.publish_time,
img: item.pic,
desc: decodeName(item.intro),
source: 'kw'
source: 'kw',
}
}),
limit,
total: parseInt(body.TOTAL),
source: 'kw'
source: 'kw',
}
})
}
},
}
// getList
// getTags
// getListDetail
// getListDetail

View File

@@ -24,7 +24,18 @@ export default {
})
},
handleResult(rawData) {
return rawData.map((item) => item.RELWORD)
let list = {
order: [],
songs: []
}
if (rawData.length > 0) {
list.order.push('songs')
}
list.songs = rawData.map((item) => ({
name: item.RELWORD,
artist: item.TAG_TYPE === 4 ? { name: '热搜' } : null
}))
return list
},
cancelTipSearch() {
if (this.requestObj && this.requestObj.cancelHttp) this.requestObj.cancelHttp()

View File

@@ -1,5 +1,5 @@
import { httpFetch } from '../../request'
import { sizeFormate, formatPlayTime } from '../index'
import { sizeFormate, formatPlayTime } from '../../index'
import { toMD5, formatSingerName } from '../utils'
export const createSignature = (time, str) => {
@@ -17,100 +17,6 @@ export default {
page: 0,
allPage: 1,
// 旧版API
// musicSearch(str, page, limit) {
// const searchRequest = httpFetch(`http://pd.musicapp.migu.cn/MIGUM2.0/v1.0/content/search_all.do?ua=Android_migu&version=5.0.1&text=${encodeURIComponent(str)}&pageNo=${page}&pageSize=${limit}&searchSwitch=%7B%22song%22%3A1%2C%22album%22%3A0%2C%22singer%22%3A0%2C%22tagSong%22%3A0%2C%22mvSong%22%3A0%2C%22songlist%22%3A0%2C%22bestShow%22%3A1%7D`, {
// searchRequest = httpFetch(`http://pd.musicapp.migu.cn/MIGUM2.0/v1.0/content/search_all.do?ua=Android_migu&version=5.0.1&text=${encodeURIComponent(str)}&pageNo=${page}&pageSize=${limit}&searchSwitch=%7B%22song%22%3A1%2C%22album%22%3A0%2C%22singer%22%3A0%2C%22tagSong%22%3A0%2C%22mvSong%22%3A0%2C%22songlist%22%3A0%2C%22bestShow%22%3A1%7D`, {
// searchRequest = httpFetch(`http://jadeite.migu.cn:7090/music_search/v2/search/searchAll?sid=4f87090d01c84984a11976b828e2b02c18946be88a6b4c47bcdc92fbd40762db&isCorrect=1&isCopyright=1&searchSwitch=%7B%22song%22%3A1%2C%22album%22%3A0%2C%22singer%22%3A0%2C%22tagSong%22%3A1%2C%22mvSong%22%3A0%2C%22bestShow%22%3A1%2C%22songlist%22%3A0%2C%22lyricSong%22%3A0%7D&pageSize=${limit}&text=${encodeURIComponent(str)}&pageNo=${page}&sort=0`, {
// searchRequest = httpFetch(`https://app.c.nf.migu.cn/MIGUM2.0/v1.0/content/search_all.do?isCopyright=1&isCorrect=1&pageNo=${page}&pageSize=${limit}&searchSwitch={%22song%22:1,%22album%22:0,%22singer%22:0,%22tagSong%22:0,%22mvSong%22:0,%22songlist%22:0,%22bestShow%22:0}&sort=0&text=${encodeURIComponent(str)}`)
// // searchRequest = httpFetch(`http://jadeite.migu.cn:7090/music_search/v2/search/searchAll?sid=4f87090d01c84984a11976b828e2b02c18946be88a6b4c47bcdc92fbd40762db&isCorrect=1&isCopyright=1&searchSwitch=%7B%22song%22%3A1%2C%22album%22%3A0%2C%22singer%22%3A0%2C%22tagSong%22%3A1%2C%22mvSong%22%3A0%2C%22bestShow%22%3A1%2C%22songlist%22%3A0%2C%22lyricSong%22%3A0%7D&pageSize=${limit}&text=${encodeURIComponent(str)}&pageNo=${page}&sort=0`, {
// headers: {
// // sign: 'c3b7ae985e2206e97f1b2de8f88691e2',
// // timestamp: 1578225871982,
// // appId: 'yyapp2',
// // mode: 'android',
// // ua: 'Android_migu',
// // version: '6.9.4',
// osVersion: 'android 7.0',
// 'User-Agent': 'okhttp/3.9.1',
// },
// })
// // searchRequest = httpFetch(`https://app.c.nf.migu.cn/MIGUM2.0/v1.0/content/search_all.do?isCopyright=1&isCorrect=1&pageNo=${page}&pageSize=${limit}&searchSwitch={%22song%22:1,%22album%22:0,%22singer%22:0,%22tagSong%22:0,%22mvSong%22:0,%22songlist%22:0,%22bestShow%22:0}&sort=0&text=${encodeURIComponent(str)}`)
// return searchRequest.promise.then(({ body }) => body)
// },
// handleResult(rawData) {
// // console.log(rawData)
// let ids = new Set()
// const list = []
// rawData.forEach(item => {
// if (ids.has(item.id)) return
// ids.add(item.id)
// const types = []
// const _types = {}
// item.newRateFormats && item.newRateFormats.forEach(type => {
// let size
// switch (type.formatType) {
// case 'PQ':
// size = sizeFormate(type.size ?? type.androidSize)
// types.push({ type: '128k', size })
// _types['128k'] = {
// size,
// }
// break
// case 'HQ':
// size = sizeFormate(type.size ?? type.androidSize)
// types.push({ type: '320k', size })
// _types['320k'] = {
// size,
// }
// break
// case 'SQ':
// size = sizeFormate(type.size ?? type.androidSize)
// types.push({ type: 'flac', size })
// _types.flac = {
// size,
// }
// break
// case 'ZQ':
// size = sizeFormate(type.size ?? type.androidSize)
// types.push({ type: 'flac24bit', size })
// _types.flac24bit = {
// size,
// }
// break
// }
// })
// const albumNInfo = item.albums && item.albums.length
// ? {
// id: item.albums[0].id,
// name: item.albums[0].name,
// }
// : {}
// list.push({
// singer: this.getSinger(item.singers),
// name: item.name,
// albumName: albumNInfo.name,
// albumId: albumNInfo.id,
// songmid: item.songId,
// copyrightId: item.copyrightId,
// source: 'mg',
// interval: null,
// img: item.imgItems && item.imgItems.length ? item.imgItems[0].img : null,
// lrc: null,
// lrcUrl: item.lyricUrl,
// mrcUrl: item.mrcurl,
// trcUrl: item.trcUrl,
// otherSource: null,
// types,
// _types,
// typeUrl: {},
// })
// })
// return list
// },
musicSearch(str, page, limit) {
const time = Date.now().toString()
const signData = createSignature(time, str)
@@ -124,8 +30,8 @@ export default {
sign: signData.sign,
channel: '0146921',
'User-Agent':
'Mozilla/5.0 (Linux; U; Android 11.0.0; zh-cn; MI 11 Build/OPR1.170623.032) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30'
}
'Mozilla/5.0 (Linux; U; Android 11.0.0; zh-cn; MI 11 Build/OPR1.170623.032) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
},
}
)
return searchRequest.promise.then(({ body }) => body)
@@ -150,28 +56,28 @@ export default {
size = sizeFormate(type.asize ?? type.isize)
types.push({ type: '128k', size })
_types['128k'] = {
size
size,
}
break
case 'HQ':
size = sizeFormate(type.asize ?? type.isize)
types.push({ type: '320k', size })
_types['320k'] = {
size
size,
}
break
case 'SQ':
size = sizeFormate(type.asize ?? type.isize)
types.push({ type: 'flac', size })
_types.flac = {
size
size,
}
break
case 'ZQ24':
size = sizeFormate(type.asize ?? type.isize)
types.push({ type: 'flac24bit', size })
_types.flac24bit = {
size
types.push({ type: 'hires', size })
_types.hires = {
size,
}
break
}
@@ -196,7 +102,7 @@ export default {
trcUrl: data.trcUrl,
types,
_types,
typeUrl: {}
typeUrl: {},
})
})
})
@@ -212,7 +118,7 @@ export default {
return Promise.reject(new Error(result ? result.info : '搜索失败'))
const songResultData = result.songResultData || { resultList: [], totalCount: 0 }
const list = this.filterData(songResultData.resultList)
let list = this.filterData(songResultData.resultList)
if (list == null) return this.search(str, page, limit, retryNum)
this.total = parseInt(songResultData.totalCount)
@@ -224,8 +130,8 @@ export default {
allPage: this.allPage,
limit,
total: this.total,
source: 'mg'
source: 'mg',
}
})
}
}
},
}

View File

@@ -1,5 +1,5 @@
import { httpFetch } from '../../request'
import { dateFormat, formatPlayCount } from '../index'
import { dateFormat, formatPlayCount } from '../../index'
import { filterMusicInfoList } from './musicInfo'
import { createSignature } from './musicSearch'
import { createHttpFetch } from './utils/index'
@@ -17,14 +17,14 @@ export default {
sortList: [
{
name: '推荐',
id: '15127315'
id: '15127315',
// id: '1',
},
{
name: '最新',
id: '15127272'
id: '15127272',
// id: '2',
}
},
],
regExps: {
list: /<li><div class="thumb">.+?<\/li>/g,
@@ -32,7 +32,7 @@ export default {
/.+data-original="(.+?)".*data-id="(\d+)".*<div class="song-list-name"><a\s.*?>(.+?)<\/a>.+<i class="iconfont cf-bofangliang"><\/i>(.+?)<\/div>/,
// https://music.migu.cn/v3/music/playlist/161044573?page=1
listDetailLink: /^.+\/playlist\/(\d+)(?:\?.*|&.*$|#.*$|$)/
listDetailLink: /^.+\/playlist\/(\d+)(?:\?.*|&.*$|#.*$|$)/,
},
tagsUrl: 'https://app.c.nf.migu.cn/MIGUM3.0/v1.0/template/musiclistplaza-taglist/release',
// tagsUrl: 'https://app.c.nf.migu.cn/MIGUM2.0/v1.0/content/indexTagPage.do?needAll=0',
@@ -58,7 +58,7 @@ export default {
defaultHeaders: {
'User-Agent':
'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',
Referer: 'https://m.music.migu.cn/'
Referer: 'https://m.music.migu.cn/',
// language: 'Chinese',
// ua: 'Android_migu',
// mode: 'android',
@@ -74,7 +74,7 @@ export default {
} else if (/[?&:/]/.test(id)) id = id.replace(this.regExps.listDetailLink, '$1')
const requestObj_listDetail = httpFetch(this.getSongListDetailUrl(id, page), {
headers: this.defaultHeaders
headers: this.defaultHeaders,
})
return requestObj_listDetail.promise.then(({ body }) => {
if (body.code !== this.successCode) return this.getListDetail(id, page, ++tryNum)
@@ -85,7 +85,7 @@ export default {
page,
limit: this.limit_song,
total: body.totalCount,
source: 'mg'
source: 'mg',
}
})
},
@@ -97,7 +97,7 @@ export default {
const requestObj_listDetailInfo = httpFetch(
`https://c.musicapp.migu.cn/MIGUM3.0/resource/playlist/v2.0?playlistId=${id}`,
{
headers: this.defaultHeaders
headers: this.defaultHeaders,
}
)
return requestObj_listDetailInfo.promise.then(({ body }) => {
@@ -109,7 +109,7 @@ export default {
img: body.data.imgItem.img,
desc: body.data.summary,
author: body.data.ownerName,
play_count: formatPlayCount(body.data.opNumItem.playNum)
play_count: formatPlayCount(body.data.opNumItem.playNum),
})
return cachedDetailInfo
})
@@ -122,12 +122,12 @@ export default {
headers: {
'User-Agent':
'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
Referer: link
}
Referer: link,
},
})
const {
headers: { location },
statusCode
statusCode,
} = await requestObj_listDetailLink.promise
// console.log(body, location)
if (statusCode > 400) return this.getDetailUrl(link, page, ++retryNum)
@@ -153,7 +153,7 @@ export default {
return Promise.all([
this.getListDetailList(id, page, retryNum),
this.getListDetailInfo(id, retryNum)
this.getListDetailInfo(id, retryNum),
]).then(([listData, info]) => {
listData.info = info
return listData
@@ -165,7 +165,7 @@ export default {
if (this._requestObj_list) this._requestObj_list.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num'))
this._requestObj_list = httpFetch(this.getSongListUrl(sortId, tagId, page), {
headers: this.defaultHeaders
headers: this.defaultHeaders,
// headers: {
// sign: 'c3b7ae985e2206e97f1b2de8f88691e2',
// timestamp: 1578225871982,
@@ -205,7 +205,7 @@ export default {
total: parseInt(body.retMsg.countSize),
page,
limit: this.limit_list,
source: 'mg'
source: 'mg',
}
})
// return this._requestObj_list.promise.then(({ body }) => {
@@ -233,7 +233,7 @@ export default {
grade: item.grade,
total: item.contentCount,
desc: item.summary,
source: 'mg'
source: 'mg',
}))
},
@@ -254,7 +254,7 @@ export default {
hotTag: rawList[0].content.map(({ texts: [name, id] }) => ({
id,
name,
source: 'mg'
source: 'mg',
})),
tags: rawList.slice(1).map(({ header, content }) => ({
name: header.title,
@@ -263,10 +263,10 @@ export default {
// parent_name: objectInfo.columnTitle,
id,
name,
source: 'mg'
}))
source: 'mg',
})),
})),
source: 'mg'
source: 'mg',
}
// return {
// hotTag: rawList[0].objectInfo.contents.map(item => ({
@@ -313,7 +313,7 @@ export default {
name: item.name,
img: item.musicListPicUrl,
total: item.musicNum,
source: 'mg'
source: 'mg',
})
})
return list
@@ -331,8 +331,8 @@ export default {
sign: signResult.sign,
channel: '0146921',
'User-Agent':
'Mozilla/5.0 (Linux; U; Android 11.0.0; zh-cn; MI 11 Build/OPR1.170623.032) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30'
}
'Mozilla/5.0 (Linux; U; Android 11.0.0; zh-cn; MI 11 Build/OPR1.170623.032) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
},
}
).then((body) => {
if (!body.songListResultData) throw new Error('get song list faild.')
@@ -342,12 +342,12 @@ export default {
list,
limit,
total: parseInt(body.songListResultData.totalCount),
source: 'mg'
source: 'mg',
}
})
}
},
}
// getList
// getTags
// getListDetail
// getListDetail

View File

@@ -8,7 +8,8 @@ export default {
tipSearchBySong(str) {
this.cancelTipSearch()
this.requestObj = createHttpFetch(
`https://music.migu.cn/v3/api/search/suggest?keyword=${encodeURIComponent(str)}`,
//https://app.u.nf.migu.cn/pc/resource/content/tone_search_suggest/v1.0?text=%E5%90%8E
`https://app.u.nf.migu.cn/pc/resource/content/tone_search_suggest/v1.0?text=${encodeURIComponent(str)}`,
{
headers: {
referer: 'https://music.migu.cn/v3'
@@ -16,11 +17,29 @@ export default {
}
)
return this.requestObj.then((body) => {
return body.songs
return body
})
},
handleResult(rawData) {
return rawData.map((info) => `${info.name} - ${info.singerName}`)
let list = {
order: [],
songs: [],
artists: []
}
if (rawData.songList.length > 0) {
list.order.push('songs')
}
if (rawData.singerList.length > 0) {
list.order.push('artists')
}
list.songs = rawData.songList.map((info) => ({
name: info.songName
}))
list.artists = rawData.singerList.map((info) => ({
name: info.singerName
}))
console.log(JSON.stringify(list))
return list
},
async search(str) {
return this.tipSearchBySong(str).then((result) => this.handleResult(result))

View File

@@ -1,8 +1,7 @@
export const bHh = '624868746c'
export const headers = {
'User-Agent': 'lx-music request',
[bHh]: [bHh]
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36',
}
export const timeout = 15000

View File

@@ -1,5 +1,5 @@
import { httpFetch } from '../../request'
import { formatPlayTime, sizeFormate } from '../index'
import { formatPlayTime, sizeFormate } from '../../index'
import { formatSingerName } from '../utils'
export default {
@@ -15,7 +15,7 @@ export default {
const searchRequest = httpFetch('https://u.y.qq.com/cgi-bin/musicu.fcg', {
method: 'post',
headers: {
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)'
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)',
},
body: {
comm: {
@@ -26,7 +26,7 @@ export default {
phonetype: '0',
devicelevel: '31',
tmeAppID: 'qqmusiclight',
nettype: 'NETWORK_WIFI'
nettype: 'NETWORK_WIFI',
},
req: {
module: 'music.search.SearchCgiService',
@@ -37,10 +37,10 @@ export default {
num_per_page: limit,
page_num: page,
nqc_flag: 0,
grp: 1
}
}
}
grp: 1,
},
},
},
})
// searchRequest = httpFetch(`http://ioscdn.kugou.com/api/v3/search/song?keyword=${encodeURIComponent(str)}&page=${page}&pagesize=${this.limit}&showtype=10&plat=2&version=7910&tag=1&correct=1&privilege=1&sver=5`)
return searchRequest.promise.then(({ body }) => {
@@ -56,35 +56,56 @@ export default {
rawList.forEach((item) => {
if (!item.file?.media_mid) return
const types = []
const _types = {}
let types = []
let _types = {}
const file = item.file
if (file.size_128mp3 != 0) {
const size = sizeFormate(file.size_128mp3)
let size = sizeFormate(file.size_128mp3)
types.push({ type: '128k', size })
_types['128k'] = {
size
size,
}
}
if (file.size_320mp3 !== 0) {
const size = sizeFormate(file.size_320mp3)
let size = sizeFormate(file.size_320mp3)
types.push({ type: '320k', size })
_types['320k'] = {
size
size,
}
}
if (file.size_flac !== 0) {
const size = sizeFormate(file.size_flac)
let size = sizeFormate(file.size_flac)
types.push({ type: 'flac', size })
_types.flac = {
size
size,
}
}
if (file.size_hires !== 0) {
const size = sizeFormate(file.size_hires)
types.push({ type: 'flac24bit', size })
_types.flac24bit = {
size
let size = sizeFormate(file.size_hires)
types.push({ type: 'hires', size })
_types.hires = {
size,
}
}
if (file.size_new[1] !== 0) {
let size = sizeFormate(file.size_new[1])
types.push({ type: 'atmos', size })
_types.atmos = {
size,
}
}
if (file.size_new[2] !== 0) {
let size = sizeFormate(file.size_new[2])
types.push({ type: 'atmos_plus', size })
_types.atmos_plus = {
size,
}
}
if (file.size_new[0] !== 0) {
let size = sizeFormate(file.size_new[0])
types.push({ type: 'master', size })
_types.master = {
size,
}
}
// types.reverse()
@@ -113,7 +134,7 @@ export default {
: `https://y.gtimg.cn/music/photo_new/T002R500x500M000${albumId}.jpg`,
types,
_types,
typeUrl: {}
typeUrl: {},
})
})
// console.log(list)
@@ -123,7 +144,7 @@ export default {
if (limit == null) limit = this.limit
// http://newlyric.kuwo.cn/newlyric.lrc?62355680
return this.musicSearch(str, page, limit).then(({ body, meta }) => {
const list = this.handleResult(body.item_song)
let list = this.handleResult(body.item_song)
this.total = meta.estimate_sum
this.page = page
@@ -134,8 +155,8 @@ export default {
allPage: this.allPage,
limit,
total: this.total,
source: 'tx'
source: 'tx',
})
})
}
}
},
}

View File

@@ -0,0 +1,86 @@
import { httpFetch } from '../../request'
import { sizeFormate } from '../../index'
export const getBatchMusicQualityInfo = (songList) => {
const songIds = songList.map((item) => item.id)
const requestObj = httpFetch('https://u.y.qq.com/cgi-bin/musicu.fcg', {
method: 'post',
headers: {
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)',
},
body: {
comm: {
ct: '19',
cv: '1859',
uin: '0',
},
req: {
module: 'music.trackInfo.UniformRuleCtrl',
method: 'CgiGetTrackInfo',
param: {
types: Array(songIds.length).fill(1),
ids: songIds,
ctx: 0,
},
},
},
})
const qualityInfoMap = {}
requestObj.promise = requestObj.promise.then(({ statusCode, body }) => {
if (statusCode != 200 || body.code != 0) return Promise.reject(new Error('获取音质信息失败'))
// Process each track from the response
body.req.data.tracks.forEach((track) => {
const file = track.file
const songId = track.id
const types = []
const _types = {}
if (file.size_128mp3 != 0) {
let size = sizeFormate(file.size_128mp3)
types.push({ type: '128k', size })
_types['128k'] = { size }
}
if (file.size_320mp3 !== 0) {
let size = sizeFormate(file.size_320mp3)
types.push({ type: '320k', size })
_types['320k'] = { size }
}
if (file.size_flac !== 0) {
let size = sizeFormate(file.size_flac)
types.push({ type: 'flac', size })
_types.flac = { size }
}
if (file.size_hires !== 0) {
let size = sizeFormate(file.size_hires)
types.push({ type: 'hires', size })
_types.hires = { size }
}
if (file.size_new[1] !== 0) {
let size = sizeFormate(file.size_new[1])
types.push({ type: 'atmos', size })
_types.atmos = { size }
}
if (file.size_new[2] !== 0) {
let size = sizeFormate(file.size_new[2])
types.push({ type: 'atmos_plus', size })
_types.atmos_plus = { size }
}
if (file.size_new[0] !== 0) {
let size = sizeFormate(file.size_new[0])
types.push({ type: 'master', size })
_types.master = { size }
}
qualityInfoMap[songId] = { types, _types }
})
return qualityInfoMap
})
return requestObj
}

View File

@@ -1,6 +1,7 @@
import { httpFetch } from '../../request'
import { decodeName, formatPlayTime, sizeFormate, dateFormat, formatPlayCount } from '../index'
import { decodeName, formatPlayTime, dateFormat, formatPlayCount } from '../../index'
import { formatSingerName } from '../utils'
import { getBatchMusicQualityInfo } from './quality_detail'
export default {
_requestObj_tags: null,
@@ -12,12 +13,12 @@ export default {
sortList: [
{
name: '最热',
id: 5
id: 5,
},
{
name: '最新',
id: 2
}
id: 2,
},
],
regExps: {
hotTagHtml: /class="c_bg_link js_tag_item" data-id="\w+">.+?<\/a>/g,
@@ -26,7 +27,7 @@ export default {
// https://y.qq.com/n/yqq/playlist/7217720898.html
// https://i.y.qq.com/n2/m/share/details/taoge.html?platform=11&appshare=android_qq&appversion=9050006&id=7217720898&ADTAG=qfshare
listDetailLink: /\/playlist\/(\d+)/,
listDetailLink2: /id=(\d+)/
listDetailLink2: /id=(\d+)/,
},
tagsUrl:
'https://u.y.qq.com/cgi-bin/musicu.fcg?loginUin=0&hostUin=0&format=json&inCharset=utf-8&outCharset=utf-8&notice=0&platform=wk_v15.json&needNewCode=0&data=%7B%22tags%22%3A%7B%22method%22%3A%22get_all_categories%22%2C%22param%22%3A%7B%22qq%22%3A%22%22%7D%2C%22module%22%3A%22playlist.PlaylistAllCategoriesServer%22%7D%7D',
@@ -45,10 +46,10 @@ export default {
category_id: id,
size: this.limit_list,
page: page - 1,
use_page: 1
use_page: 1,
},
module: 'playlist.PlayListCategoryServer'
}
module: 'playlist.PlayListCategoryServer',
},
})
)}`
}
@@ -62,10 +63,10 @@ export default {
sin: this.limit_list * (page - 1),
size: this.limit_list,
order: sortId,
cur_page: page
cur_page: page,
},
module: 'playlist.PlayListPlazaServer'
}
module: 'playlist.PlayListPlazaServer',
},
})
)}`
},
@@ -95,17 +96,17 @@ export default {
})
},
filterInfoHotTag(html) {
const hotTag = html.match(this.regExps.hotTagHtml)
let hotTag = html.match(this.regExps.hotTagHtml)
const hotTags = []
if (!hotTag) return hotTags
hotTag.forEach((tagHtml) => {
const result = tagHtml.match(this.regExps.hotTag)
let result = tagHtml.match(this.regExps.hotTag)
if (!result) return
hotTags.push({
id: parseInt(result[1]),
name: result[2],
source: 'tx'
source: 'tx',
})
})
return hotTags
@@ -118,8 +119,8 @@ export default {
parent_name: type.group_name,
id: item.id,
name: item.name,
source: 'tx'
}))
source: 'tx',
})),
}))
},
@@ -130,7 +131,9 @@ export default {
this._requestObj_list = httpFetch(this.getListUrl(sortId, tagId, page))
// console.log(this.getListUrl(sortId, tagId, page))
return this._requestObj_list.promise.then(({ body }) => {
if (body.code !== this.successCode) return this.getList(sortId, tagId, page, ++tryNum)
if (body.code !== this.successCode) {
return this.getList(sortId, tagId, page, ++tryNum)
}
return tagId
? this.filterList2(body.playlist.data, page)
: this.filterList(body.playlist.data, page)
@@ -149,12 +152,12 @@ export default {
// grade: item.favorcnt / 10,
total: item.song_ids?.length,
desc: decodeName(item.desc).replace(/<br>/g, '\n'),
source: 'tx'
source: 'tx',
})),
total: data.total,
page,
limit: this.limit_list,
source: 'tx'
source: 'tx',
}
},
filterList2({ content }, page) {
@@ -169,12 +172,12 @@ export default {
img: basic.cover.medium_url || basic.cover.default_url,
// grade: basic.favorcnt / 10,
desc: decodeName(basic.desc).replace(/<br>/g, '\n'),
source: 'tx'
source: 'tx',
})),
total: content.total_cnt,
page,
limit: this.limit_list,
source: 'tx'
source: 'tx',
}
},
@@ -184,7 +187,7 @@ export default {
const requestObj_listDetailLink = httpFetch(link)
const {
headers: { location },
statusCode
statusCode,
} = await requestObj_listDetailLink.promise
// console.log(headers)
if (statusCode > 400) return this.handleParseId(link, ++retryNum)
@@ -215,15 +218,15 @@ export default {
const requestObj_listDetail = httpFetch(this.getListDetailUrl(id), {
headers: {
Origin: 'https://y.qq.com',
Referer: `https://y.qq.com/n/yqq/playsquare/${id}.html`
}
Referer: `https://y.qq.com/n/yqq/playsquare/${id}.html`,
},
})
const { body } = await requestObj_listDetail.promise
if (body.code !== this.successCode) return this.getListDetail(id, ++tryNum)
const cdlist = body.cdlist[0]
return {
list: this.filterListDetail(cdlist.songlist),
list: await this.filterListDetail(cdlist.songlist),
page: 1,
limit: cdlist.songlist.length + 1,
total: cdlist.songlist.length,
@@ -233,44 +236,23 @@ export default {
img: cdlist.logo,
desc: decodeName(cdlist.desc).replace(/<br>/g, '\n'),
author: cdlist.nickname,
play_count: formatPlayCount(cdlist.visitnum)
}
play_count: formatPlayCount(cdlist.visitnum),
},
}
},
filterListDetail(rawList) {
// console.log(rawList)
async filterListDetail(rawList) {
const qualityInfoRequest = getBatchMusicQualityInfo(rawList)
let qualityInfoMap = {}
try {
qualityInfoMap = await qualityInfoRequest.promise
} catch (error) {
console.error('Failed to fetch quality info:', error)
}
return rawList.map((item) => {
const types = []
const _types = {}
if (item.file.size_128mp3 !== 0) {
const size = sizeFormate(item.file.size_128mp3)
types.push({ type: '128k', size })
_types['128k'] = {
size
}
}
if (item.file.size_320mp3 !== 0) {
const size = sizeFormate(item.file.size_320mp3)
types.push({ type: '320k', size })
_types['320k'] = {
size
}
}
if (item.file.size_flac !== 0) {
const size = sizeFormate(item.file.size_flac)
types.push({ type: 'flac', size })
_types.flac = {
size
}
}
if (item.file.size_hires !== 0) {
const size = sizeFormate(item.file.size_hires)
types.push({ type: 'flac24bit', size })
_types.flac24bit = {
size
}
}
// types.reverse()
const { types = [], _types = {} } = qualityInfoMap[item.id] || {}
return {
singer: formatSingerName(item.singer, 'name'),
name: item.title,
@@ -292,7 +274,7 @@ export default {
otherSource: null,
types,
_types,
typeUrl: {}
typeUrl: {},
}
})
},
@@ -300,7 +282,7 @@ export default {
return Promise.all([this.getTag(), this.getHotTag()]).then(([tags, hotTag]) => ({
tags,
hotTag,
source: 'tx'
source: 'tx',
}))
},
@@ -313,12 +295,16 @@ export default {
search(text, page, limit = 20, retryNum = 0) {
if (retryNum > 5) throw new Error('max retry')
return httpFetch(
`http://c.y.qq.com/soso/fcgi-bin/client_music_search_songlist?page_no=${page - 1}&num_per_page=${limit}&format=json&query=${encodeURIComponent(text)}&remoteplace=txt.yqq.playlist&inCharset=utf8&outCharset=utf-8`,
`http://c.y.qq.com/soso/fcgi-bin/client_music_search_songlist?page_no=${
page - 1
}&num_per_page=${limit}&format=json&query=${encodeURIComponent(
text
)}&remoteplace=txt.yqq.playlist&inCharset=utf8&outCharset=utf-8`,
{
headers: {
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)',
Referer: 'http://y.qq.com/portal/search.html'
}
Referer: 'http://y.qq.com/portal/search.html',
},
}
).promise.then(({ body }) => {
if (body.code != 0) return this.search(text, page, limit, ++retryNum)
@@ -335,17 +321,17 @@ export default {
// grade: item.favorcnt / 10,
total: item.song_count,
desc: decodeName(decodeName(item.introduction)).replace(/<br>/g, '\n'),
source: 'tx'
source: 'tx',
}
}),
limit,
total: body.data.sum,
source: 'tx'
source: 'tx',
}
})
}
},
}
// getList
// getTags
// getListDetail
// getListDetail

View File

@@ -21,12 +21,33 @@ export default {
})
},
handleResult(rawData) {
return rawData.map((info) => `${info.name} - ${info.singer}`)
let list = {
order: [],
songs: [],
artists: [],
albums: []
}
if (rawData.song.count > 0) {
list.order.push('songs')
}
if (rawData.singer.count > 0) {
list.order.push('artists')
}
if (rawData.album.count > 0) {
list.order.push('albums')
}
list.songs = rawData.song.itemlist.map((info) => ({
name: info.name,
artist: { name: info.singer }
}))
list.artists = rawData.singer.itemlist.map((info) => ({ name: info.name }))
list.albums = rawData.album.itemlist.map((info) => ({ name: info.name }))
return list
},
cancelTipSearch() {
if (this.requestObj && this.requestObj.cancelHttp) this.requestObj.cancelHttp()
},
async search(str) {
return this.tipSearch(str).then((result) => this.handleResult(result.song.itemlist))
return this.tipSearch(str).then((result) => this.handleResult(result))
}
}

View File

@@ -10,16 +10,13 @@ export const getHostIp = (hostname) => {
if (typeof result === 'object') return result
if (result === true) return
ipMap.set(hostname, true)
// console.log(hostname)
dns.lookup(
hostname,
{
// family: 4,
all: false
all: false,
},
(err, address, family) => {
if (err) return console.log(err)
// console.log(address, family)
ipMap.set(hostname, { address, family })
}
)
@@ -42,11 +39,11 @@ export const formatSingerName = (singers, nameKey = 'name', join = '、') => {
if (Array.isArray(singers)) {
const singer = []
singers.forEach((item) => {
const name = item[nameKey]
let name = item[nameKey]
if (!name) return
singer.push(name)
})
return decodeName(singer.join(join))
}
return decodeName(String(singers ?? ''))
}
}

View File

@@ -26,4 +26,4 @@ const wy = {
}
}
export default wy
export default wy

View File

@@ -1,57 +1,23 @@
import { httpFetch } from '../../request'
import { weapi } from './utils/crypto'
import { formatPlayTime, sizeFormate } from '../index'
// https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/module/song_detail.js
import { formatPlayTime } from '../../index'
import { getBatchMusicQualityInfo } from './quality_detail'
export default {
getSinger(singers) {
const arr = []
let arr = []
singers?.forEach((singer) => {
arr.push(singer.name)
})
return arr.join('、')
},
filterList({ songs, privileges }) {
// console.log(songs, privileges)
async filterList({ songs, privileges }) {
const list = []
const idList = songs.map((item) => item.id)
const qualityInfoMap = await getBatchMusicQualityInfo(idList)
songs.forEach((item, index) => {
const types = []
const _types = {}
let size
let privilege = privileges[index]
if (privilege.id !== item.id) privilege = privileges.find((p) => p.id === item.id)
if (!privilege) return
if (privilege.maxBrLevel == 'hires') {
size = item.hr ? sizeFormate(item.hr.size) : null
types.push({ type: 'flac24bit', size })
_types.flac24bit = {
size
}
}
switch (privilege.maxbr) {
case 999000:
size = item.sq ? sizeFormate(item.sq.size) : null
types.push({ type: 'flac', size })
_types.flac = {
size
}
case 320000:
size = item.h ? sizeFormate(item.h.size) : null
types.push({ type: '320k', size })
_types['320k'] = {
size
}
case 192000:
case 128000:
size = item.l ? sizeFormate(item.l.size) : null
types.push({ type: '128k', size })
_types['128k'] = {
size
}
}
types.reverse()
const { types, _types } = qualityInfoMap[item.id] || { types: [], _types: {} }
if (item.pc) {
list.push({
@@ -67,7 +33,7 @@ export default {
otherSource: null,
types,
_types,
typeUrl: {}
typeUrl: {},
})
} else {
list.push({
@@ -83,11 +49,10 @@ export default {
otherSource: null,
types,
_types,
typeUrl: {}
typeUrl: {},
})
}
})
// console.log(list)
return list
},
async getList(ids = [], retryNum = 0) {
@@ -98,16 +63,15 @@ export default {
headers: {
'User-Agent':
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36',
origin: 'https://music.163.com'
origin: 'https://music.163.com',
},
form: weapi({
c: '[' + ids.map((id) => '{"id":' + id + '}').join(',') + ']',
ids: '[' + ids.join(',') + ']'
})
ids: '[' + ids.join(',') + ']',
}),
})
const { body, statusCode } = await requestObj.promise
if (statusCode != 200 || body.code !== 200) throw new Error('获取歌曲详情失败')
// console.log(body)
return { source: 'wy', list: this.filterList(body) }
}
}
return { source: 'wy', list: await this.filterList(body) }
},
}

View File

@@ -1,7 +1,5 @@
// import { httpFetch } from '../../request'
// import { weapi } from './utils/crypto'
import { sizeFormate, formatPlayTime } from '../index'
// import musicDetailApi from './musicDetail'
import { httpFetch } from '../../request'
import { sizeFormate, formatPlayTime } from '../../index'
import { eapiRequest } from './utils/index'
export default {
@@ -9,101 +7,129 @@ export default {
total: 0,
page: 0,
allPage: 1,
musicSearch(str, page, limit) {
const searchRequest = eapiRequest('/api/cloudsearch/pc', {
s: str,
type: 1, // 1: 单曲, 10: 专辑, 100: 歌手, 1000: 歌单, 1002: 用户, 1004: MV, 1006: 歌词, 1009: 电台, 1014: 视频
limit,
total: page == 1,
offset: limit * (page - 1)
offset: limit * (page - 1),
})
return searchRequest.promise.then(({ body }) => body)
},
getSinger(singers) {
const arr = []
singers.forEach((singer) => {
arr.push(singer.name)
})
return arr.join('、')
return singers.map((singer) => singer.name).join('、')
},
handleResult(rawList) {
// console.log(rawList)
if (!rawList) return []
return rawList.map((item) => {
const types = []
const _types = {}
let size
if (item.privilege.maxBrLevel == 'hires') {
size = item.hr ? sizeFormate(item.hr.size) : null
types.push({ type: 'flac24bit', size })
_types.flac24bit = {
size
return Promise.all(
rawList.map(async (item) => {
const types = []
const _types = {}
let size
try {
const requestObj = httpFetch(
`https://music.163.com/api/song/music/detail/get?songId=${item.id}`,
{
method: 'get',
headers: {
'User-Agent':
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36',
origin: 'https://music.163.com',
},
}
)
const { body, statusCode } = await requestObj.promise
if (statusCode !== 200 || !body || body.code !== 200) {
throw new Error('Failed to get song quality information')
}
if (body.data.jm && body.data.jm.size) {
size = sizeFormate(body.data.jm.size)
types.push({ type: 'master', size })
_types.master = { size }
}
if (body.data.db && body.data.db.size) {
size = sizeFormate(body.data.db.size)
types.push({ type: 'dolby', size })
_types.dolby = { size }
}
if (body.data.hr && body.data.hr.size) {
size = sizeFormate(body.data.hr.size)
types.push({ type: 'hires', size })
_types.hires = { size }
}
if (body.data.sq && body.data.sq.size) {
size = sizeFormate(body.data.sq.size)
types.push({ type: 'flac', size })
_types.flac = { size }
}
if (body.data.h && body.data.h.size) {
size = sizeFormate(body.data.h.size)
types.push({ type: '320k', size })
_types['320k'] = { size }
}
if (body.data.m && body.data.m.size) {
size = sizeFormate(body.data.m.size)
types.push({ type: '128k', size })
_types['128k'] = { size }
} else if (body.data.l && body.data.l.size) {
size = sizeFormate(body.data.l.size)
types.push({ type: '128k', size })
_types['128k'] = { size }
}
types.reverse()
return {
singer: this.getSinger(item.ar),
name: item.name,
albumName: item.al.name,
albumId: item.al.id,
source: 'wy',
interval: formatPlayTime(item.dt / 1000),
songmid: item.id,
img: item.al.picUrl,
lrc: null,
types,
_types,
typeUrl: {},
}
} catch (error) {
console.error(error.message)
return null
}
}
switch (item.privilege.maxbr) {
case 999000:
size = item.sq ? sizeFormate(item.sq.size) : null
types.push({ type: 'flac', size })
_types.flac = {
size
}
case 320000:
size = item.h ? sizeFormate(item.h.size) : null
types.push({ type: '320k', size })
_types['320k'] = {
size
}
case 192000:
case 128000:
size = item.l ? sizeFormate(item.l.size) : null
types.push({ type: '128k', size })
_types['128k'] = {
size
}
}
types.reverse()
return {
singer: this.getSinger(item.ar),
name: item.name,
albumName: item.al.name,
albumId: item.al.id,
source: 'wy',
interval: formatPlayTime(item.dt / 1000),
songmid: item.id,
img: item.al.picUrl,
lrc: null,
types,
_types,
typeUrl: {}
}
})
})
)
},
search(str, page = 1, limit, retryNum = 0) {
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
if (limit == null) limit = this.limit
return this.musicSearch(str, page, limit).then((result) => {
// console.log(result)
if (!result || result.code !== 200) return this.search(str, page, limit, retryNum)
const list = this.handleResult(result.result.songs || [])
// console.log(list)
return this.handleResult(result.result.songs || []).then((list) => {
if (!list || list.length === 0) return this.search(str, page, limit, retryNum)
if (list == null) return this.search(str, page, limit, retryNum)
this.total = result.result.songCount || 0
this.page = page
this.allPage = Math.ceil(this.total / this.limit)
this.total = result.result.songCount || 0
this.page = page
this.allPage = Math.ceil(this.total / this.limit)
return {
list,
allPage: this.allPage,
limit: this.limit,
total: this.total,
source: 'wy'
}
// return result.data
return {
list,
allPage: this.allPage,
limit: this.limit,
total: this.total,
source: 'wy',
}
})
})
}
}
},
}

View File

@@ -0,0 +1,91 @@
import { httpFetch } from '../../request'
import { sizeFormate } from '../../index'
export const getMusicQualityInfo = (id) => {
const requestObj = httpFetch(`https://music.163.com/api/song/music/detail/get?songId=${id}`, {
method: 'get',
headers: {
'User-Agent':
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36',
origin: 'https://music.163.com',
},
})
const types = []
const _types = {}
requestObj.promise = requestObj.promise.then(({ statusCode, body }) => {
if (statusCode != 200 && body.code != 200) return Promise.reject(new Error('获取音质信息失败' + id))
const data = body.data
types.length = 0
Object.keys(_types).forEach((key) => delete _types[key])
if (data.l != null && data.l.size != null) {
let size = sizeFormate(data.l.size)
types.push({ type: '128k', size })
_types['128k'] = { size }
} else if (data.m != null && data.m.size != null) {
let size = sizeFormate(data.m.size)
types.push({ type: '128k', size })
_types['128k'] = { size }
}
if (data.h != null && data.h.size != null) {
let size = sizeFormate(data.h.size)
types.push({ type: '320k', size })
_types['320k'] = { size }
}
if (data.sq != null && data.sq.size != null) {
let size = sizeFormate(data.sq.size)
types.push({ type: 'flac', size })
_types.flac = { size }
}
if (data.hr != null && data.hr.size != null) {
let size = sizeFormate(data.hr.size)
types.push({ type: 'hires', size })
_types.hires = { size }
}
if (data.jm != null && data.jm.size != null) {
let size = sizeFormate(data.jm.size)
types.push({ type: 'master', size })
_types.master = { size }
}
if (data.je != null && data.je.size != null) {
let size = sizeFormate(data.je.size)
types.push({ type: 'atmos', size })
_types.atmos = { size }
}
return { types: [...types], _types: { ..._types } }
})
return { requestObj, types, _types }
}
export const getBatchMusicQualityInfo = async (idList) => {
const ids = idList.filter((id) => id)
const qualityPromises = ids.map((id) => {
const result = getMusicQualityInfo(id)
return result.requestObj.promise.catch((err) => {
console.error(`获取歌曲 ${id} 音质信息失败:`, err)
return { types: [], _types: {} }
})
})
const qualityResults = await Promise.all(qualityPromises)
const qualityInfoMap = {}
ids.forEach((id, index) => {
qualityInfoMap[id] = qualityResults[index] || { types: [], _types: {} }
})
return qualityInfoMap
}

View File

@@ -1,9 +1,9 @@
import { weapi, linuxapi } from './utils/crypto'
import { httpFetch } from '../../request'
import { formatPlayTime, sizeFormate, dateFormat, formatPlayCount } from '../index'
import { /* formatPlayTime, */ dateFormat, formatPlayCount } from '../../index'
import musicDetailApi from './musicDetail'
import { eapiRequest } from './utils/index'
import { formatSingerName } from '../utils'
// import { formatSingerName } from '../utils'
export default {
_requestObj_tags: null,
@@ -16,16 +16,12 @@ export default {
sortList: [
{
name: '最热',
id: 'hot'
}
// {
// name: '最新',
// id: 'new',
// },
id: 'hot',
},
],
regExps: {
listDetailLink: /^.+(?:\?|&)id=(\d+)(?:&.*$|#.*$|$)/,
listDetailLink2: /^.+\/playlist\/(\d+)\/\d+\/.+$/
listDetailLink2: /^.+\/playlist\/(\d+)\/\d+\/.+$/,
},
async handleParseId(link, retryNum = 0) {
@@ -34,9 +30,8 @@ export default {
const requestObj_listDetailLink = httpFetch(link)
const {
headers: { location },
statusCode
statusCode,
} = await requestObj_listDetailLink.promise
// console.log(statusCode)
if (statusCode > 400) return this.handleParseId(link, ++retryNum)
const url = location == null ? link : location
return this.regExps.listDetailLink.test(url)
@@ -59,13 +54,11 @@ export default {
} else {
id = await this.handleParseId(id)
}
// console.log(id)
}
return { id, cookie }
},
async getListDetail(rawId, page, tryNum = 0) {
// 获取歌曲列表内的音乐
if (tryNum > 2) return Promise.reject(new Error('try max num'))
if (tryNum > 1000) return Promise.reject(new Error('try max num'))
const { id, cookie } = await this.getListId(rawId)
if (cookie) this.cookie = cookie
@@ -75,7 +68,7 @@ export default {
headers: {
'User-Agent':
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36',
Cookie: this.cookie
Cookie: this.cookie,
},
form: linuxapi({
method: 'POST',
@@ -83,36 +76,30 @@ export default {
params: {
id,
n: this.limit_song,
s: 8
}
})
s: 8,
},
}),
})
const { statusCode, body } = await requestObj_listDetail.promise
if (statusCode !== 200 || body.code !== this.successCode)
return this.getListDetail(id, page, ++tryNum)
const limit = 1000
const rangeStart = (page - 1) * limit
// console.log(body)
let limit = 50
let rangeStart = (page - 1) * limit
let list
if (body.playlist.trackIds.length == body.privileges.length) {
list = this.filterListDetail(body)
} else {
try {
list = (
await musicDetailApi.getList(
body.playlist.trackIds.slice(rangeStart, limit * page).map((trackId) => trackId.id)
)
).list
} catch (err) {
console.log(err)
if (err.message == 'try max num') {
throw err
} else {
return this.getListDetail(id, page, ++tryNum)
}
try {
list = (
await musicDetailApi.getList(
body.playlist.trackIds.slice(rangeStart, limit * page).map((trackId) => trackId.id)
)
).list
} catch (err) {
console.log(err)
if (err.message == 'try max num') {
throw err
} else {
return this.getListDetail(id, page, ++tryNum)
}
}
// console.log(list)
return {
list,
page,
@@ -124,119 +111,79 @@ export default {
name: body.playlist.name,
img: body.playlist.coverImgUrl,
desc: body.playlist.description,
author: body.playlist.creator.nickname
}
author: body.playlist.creator.nickname,
},
}
},
filterListDetail({ playlist: { tracks }, privileges }) {
// console.log(tracks, privileges)
const list = []
tracks.forEach((item, index) => {
const types = []
const _types = {}
let size
let privilege = privileges[index]
if (privilege.id !== item.id) privilege = privileges.find((p) => p.id === item.id)
if (!privilege) return
if (privilege.maxBrLevel == 'hires') {
size = item.hr ? sizeFormate(item.hr.size) : null
types.push({ type: 'flac24bit', size })
_types.flac24bit = {
size
}
}
switch (privilege.maxbr) {
case 999000:
size = null
types.push({ type: 'flac', size })
_types.flac = {
size
}
// filterListDetail({ playlist: { tracks } }) {
// const list = []
// tracks.forEach((item) => {
// const types = []
// const _types = {}
case 320000:
size = item.h ? sizeFormate(item.h.size) : null
types.push({ type: '320k', size })
_types['320k'] = {
size
}
// if (item.pc) {
// list.push({
// singer: item.pc.ar ?? '',
// name: item.pc.sn ?? '',
// albumName: item.pc.alb ?? '',
// albumId: item.al?.id,
// source: 'wy',
// interval: formatPlayTime(item.dt / 1000),
// songmid: item.id,
// img: item.al?.picUrl ?? '',
// lrc: null,
// otherSource: null,
// types,
// _types,
// typeUrl: {},
// })
// } else {
// list.push({
// singer: formatSingerName(item.ar, 'name'),
// name: item.name ?? '',
// albumName: item.al?.name,
// albumId: item.al?.id,
// source: 'wy',
// interval: formatPlayTime(item.dt / 1000),
// songmid: item.id,
// img: item.al?.picUrl,
// lrc: null,
// otherSource: null,
// types,
// _types,
// typeUrl: {},
// })
// }
// })
// return list
// },
case 192000:
case 128000:
size = item.l ? sizeFormate(item.l.size) : null
types.push({ type: '128k', size })
_types['128k'] = {
size
}
}
types.reverse()
if (item.pc) {
list.push({
singer: item.pc.ar ?? '',
name: item.pc.sn ?? '',
albumName: item.pc.alb ?? '',
albumId: item.al?.id,
source: 'wy',
interval: formatPlayTime(item.dt / 1000),
songmid: item.id,
img: item.al?.picUrl ?? '',
lrc: null,
otherSource: null,
types,
_types,
typeUrl: {}
})
} else {
list.push({
singer: formatSingerName(item.ar, 'name'),
name: item.name ?? '',
albumName: item.al?.name,
albumId: item.al?.id,
source: 'wy',
interval: formatPlayTime(item.dt / 1000),
songmid: item.id,
img: item.al?.picUrl,
lrc: null,
otherSource: null,
types,
_types,
typeUrl: {}
})
}
})
return list
},
// 获取列表数据
getList(sortId, tagId, page, tryNum = 0) {
if (tryNum > 2) return Promise.reject(new Error('try max num'))
if (this._requestObj_list) this._requestObj_list.cancelHttp()
this._requestObj_list = httpFetch('https://music.163.com/weapi/playlist/list', {
method: 'post',
form: weapi({
cat: tagId || '全部', // 全部,华语,欧美,日语,韩语,粤语,小语种,流行,摇滚,民谣,电子,舞曲,说唱,轻音乐,爵士,乡村,R&B/Soul,古典,民族,英伦,金属,朋克,蓝调,雷鬼,世界音乐,拉丁,另类/独立,New Age,古风,后摇,Bossa Nova,清晨,夜晚,学习,工作,午休,下午茶,地铁,驾车,运动,旅行,散步,酒吧,怀旧,清新,浪漫,性感,伤感,治愈,放松,孤独,感动,兴奋,快乐,安静,思念,影视原声,ACG,儿童,校园,游戏,70后,80后,90后,网络歌曲,KTV,经典,翻唱,吉他,钢琴,器乐,榜单,00后
order: sortId, // hot,new
cat: tagId || '全部',
order: sortId,
limit: this.limit_list,
offset: this.limit_list * (page - 1),
total: true
})
total: true,
}),
})
return this._requestObj_list.promise.then(({ body }) => {
// console.log(body)
if (body.code !== this.successCode) return this.getList(sortId, tagId, page, ++tryNum)
return {
list: this.filterList(body.playlists),
total: parseInt(body.total),
page,
limit: this.limit_list,
source: 'wy'
source: 'wy',
}
})
},
filterList(rawData) {
// console.log(rawData)
return rawData.map((item) => ({
play_count: formatPlayCount(item.playCount),
id: String(item.id),
@@ -247,20 +194,18 @@ export default {
grade: item.grade,
total: item.trackCount,
desc: item.description,
source: 'wy'
source: 'wy',
}))
},
// 获取标签
getTag(tryNum = 0) {
if (this._requestObj_tags) this._requestObj_tags.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num'))
this._requestObj_tags = httpFetch('https://music.163.com/weapi/playlist/catalogue', {
method: 'post',
form: weapi({})
form: weapi({}),
})
return this._requestObj_tags.promise.then(({ body }) => {
// console.log(JSON.stringify(body))
if (body.code !== this.successCode) return this.getTag(++tryNum)
return this.filterTagInfo(body)
})
@@ -274,7 +219,7 @@ export default {
parent_name: categories[item.category],
id: item.name,
name: item.name,
source: 'wy'
source: 'wy',
})
}
@@ -283,22 +228,20 @@ export default {
list.push({
name: categories[key],
list: subList[key],
source: 'wy'
source: 'wy',
})
}
return list
},
// 获取热门标签
getHotTag(tryNum = 0) {
if (this._requestObj_hotTags) this._requestObj_hotTags.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num'))
this._requestObj_hotTags = httpFetch('https://music.163.com/weapi/playlist/hottags', {
method: 'post',
form: weapi({})
form: weapi({}),
})
return this._requestObj_hotTags.promise.then(({ body }) => {
// console.log(JSON.stringify(body))
if (body.code !== this.successCode) return this.getTag(++tryNum)
return this.filterHotTagInfo(body.tags)
})
@@ -307,7 +250,7 @@ export default {
return rawList.map((item) => ({
id: item.playlistTag.name,
name: item.playlistTag.name,
source: 'wy'
source: 'wy',
}))
},
@@ -315,7 +258,7 @@ export default {
return Promise.all([this.getTag(), this.getHotTag()]).then(([tags, hotTag]) => ({
tags,
hotTag,
source: 'wy'
source: 'wy',
}))
},
@@ -327,23 +270,18 @@ export default {
search(text, page, limit = 20) {
return eapiRequest('/api/cloudsearch/pc', {
s: text,
type: 1000, // 1: 单曲, 10: 专辑, 100: 歌手, 1000: 歌单, 1002: 用户, 1004: MV, 1006: 歌词, 1009: 电台, 1014: 视频
type: 1000,
limit,
total: page == 1,
offset: limit * (page - 1)
offset: limit * (page - 1),
}).promise.then(({ body }) => {
if (body.code != this.successCode) throw new Error('filed')
// console.log(body)
return {
list: this.filterList(body.result.playlists),
limit,
total: body.result.playlistCount,
source: 'wy'
source: 'wy',
}
})
}
}
// getList
// getTags
// getListDetail
},
}

View File

@@ -21,13 +21,13 @@ export default {
})
return this.requestObj.promise.then(({ statusCode, body }) => {
if (statusCode != 200 || body.code != 200) return Promise.reject(new Error('请求失败'))
return body.result.songs
return body.result
})
},
handleResult(rawData) {
return rawData.map((info) => `${info.name} - ${formatSingerName(info.artists, 'name')}`)
},
async search(str) {
return this.tipSearchBySong(str).then((result) => this.handleResult(result))
return this.tipSearchBySong(str)
}
}

View File

@@ -8,6 +8,11 @@ const api = {
console.log('preload: 发送 window-minimize 事件')
ipcRenderer.send('window-minimize')
},
// 阻止系统息屏
powerSaveBlocker: {
start: () => ipcRenderer.invoke('power-save-blocker:start'),
stop: () => ipcRenderer.invoke('power-save-blocker:stop')
},
maximize: () => {
console.log('preload: 发送 window-maximize 事件')
ipcRenderer.send('window-maximize')
@@ -30,7 +35,7 @@ const api = {
// 音乐相关方法
music: {
requestSdk: (api: string, args: any) =>
ipcRenderer.invoke('service-music-sdk-request', api, args),
ipcRenderer.invoke('service-music-sdk-request', api, args)
},
//音源插件
plugins: {

View File

@@ -18,7 +18,10 @@ declare module 'vue' {
GlobalAudio: typeof import('./src/components/Play/GlobalAudio.vue')['default']
HomeLayout: typeof import('./src/components/layout/HomeLayout.vue')['default']
MusicCache: typeof import('./src/components/Settings/MusicCache.vue')['default']
NBackTop: typeof import('naive-ui')['NBackTop']
NBadge: typeof import('naive-ui')['NBadge']
NCard: typeof import('naive-ui')['NCard']
NIcon: typeof import('naive-ui')['NIcon']
NScrollbar: typeof import('naive-ui')['NScrollbar']
NText: typeof import('naive-ui')['NText']
PlaylistActions: typeof import('./src/components/Play/PlaylistActions.vue')['default']
@@ -32,18 +35,16 @@ declare module 'vue' {
SearchSuggest: typeof import('./src/components/search/searchSuggest.vue')['default']
ShaderBackground: typeof import('./src/components/Play/ShaderBackground.vue')['default']
SongVirtualList: typeof import('./src/components/Music/SongVirtualList.vue')['default']
SvgIcon: typeof import('./src/components/SvgIcon.vue')['default']
TAlert: typeof import('tdesign-vue-next')['Alert']
TAside: typeof import('tdesign-vue-next')['Aside']
TBadge: typeof import('tdesign-vue-next')['Badge']
TButton: typeof import('tdesign-vue-next')['Button']
TCard: typeof import('tdesign-vue-next')['Card']
TCheckbox: typeof import('tdesign-vue-next')['Checkbox']
TContent: typeof import('tdesign-vue-next')['Content']
TDialog: typeof import('tdesign-vue-next')['Dialog']
TDivider: typeof import('tdesign-vue-next')['Divider']
TDrawer: typeof import('tdesign-vue-next')['Drawer']
TDropdown: typeof import('tdesign-vue-next')['Dropdown']
TEmpty: typeof import('tdesign-vue-next')['Empty']
TForm: typeof import('tdesign-vue-next')['Form']
TFormItem: typeof import('tdesign-vue-next')['FormItem']
ThemeSelector: typeof import('./src/components/ThemeSelector.vue')['default']
@@ -53,14 +54,10 @@ declare module 'vue' {
TitleBarControls: typeof import('./src/components/TitleBarControls.vue')['default']
TLayout: typeof import('tdesign-vue-next')['Layout']
TLoading: typeof import('tdesign-vue-next')['Loading']
TOption: typeof import('tdesign-vue-next')['Option']
TRadioButton: typeof import('tdesign-vue-next')['RadioButton']
TRadioGroup: typeof import('tdesign-vue-next')['RadioGroup']
TSelect: typeof import('tdesign-vue-next')['Select']
TSlider: typeof import('tdesign-vue-next')['Slider']
TSwitch: typeof import('tdesign-vue-next')['Switch']
TTabPanel: typeof import('tdesign-vue-next')['TabPanel']
TTabs: typeof import('tdesign-vue-next')['Tabs']
TTag: typeof import('tdesign-vue-next')['Tag']
TTextarea: typeof import('tdesign-vue-next')['Textarea']
TTooltip: typeof import('tdesign-vue-next')['Tooltip']

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M18 13h-5v5c0 .55-.45 1-1 1s-1-.45-1-1v-5H6c-.55 0-1-.45-1-1s.45-1 1-1h5V6c0-.55.45-1 1-1s1 .45 1 1v5h5c.55 0 1 .45 1 1s-.45 1-1 1"/></svg>

After

Width:  |  Height:  |  Size: 251 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M14 16h2v-2h2v-2h-2v-2h-2v2h-2v2h2zM4 20q-.825 0-1.412-.587T2 18V6q0-.825.588-1.412T4 4h6l2 2h8q.825 0 1.413.588T22 8v10q0 .825-.587 1.413T20 20z"/></svg>

After

Width:  |  Height:  |  Size: 266 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M12 16.5q1.875 0 3.188-1.312T16.5 12t-1.312-3.187T12 7.5T8.813 8.813T7.5 12t1.313 3.188T12 16.5m0-3.5q-.425 0-.712-.288T11 12t.288-.712T12 11t.713.288T13 12t-.288.713T12 13m0 9q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22"/></svg>

After

Width:  |  Height:  |  Size: 459 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M11 14c1 0 2.05.16 3.2.44c-.81.87-1.2 1.89-1.2 3.06c0 .89.25 1.73.78 2.5H3v-2c0-1.19.91-2.15 2.74-2.88C7.57 14.38 9.33 14 11 14m0-2c-1.08 0-2-.39-2.82-1.17C7.38 10.05 7 9.11 7 8c0-1.08.38-2 1.18-2.82C9 4.38 9.92 4 11 4c1.11 0 2.05.38 2.83 1.18C14.61 6 15 6.92 15 8c0 1.11-.39 2.05-1.17 2.83c-.78.78-1.72 1.17-2.83 1.17m7.5-2H22v2h-2v5.5a2.5 2.5 0 0 1-2.5 2.5a2.5 2.5 0 0 1-2.5-2.5a2.5 2.5 0 0 1 2.5-2.5c.36 0 .69.07 1 .21z"/></svg>

After

Width:  |  Height:  |  Size: 543 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M7.5 5.6L10 7L8.6 4.5L10 2L7.5 3.4L5 2l1.4 2.5L5 7zm12 9.8L17 14l1.4 2.5L17 19l2.5-1.4L22 19l-1.4-2.5L22 14zM22 2l-2.5 1.4L17 2l1.4 2.5L17 7l2.5-1.4L22 7l-1.4-2.5zm-7.63 5.29a.996.996 0 0 0-1.41 0L1.29 18.96a.996.996 0 0 0 0 1.41l2.34 2.34c.39.39 1.02.39 1.41 0L16.7 11.05a.996.996 0 0 0 0-1.41zm-1.03 5.49l-2.12-2.12l2.44-2.44l2.12 2.12z"/></svg>

After

Width:  |  Height:  |  Size: 459 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M10.85 12.65h2.3L12 9zM20 8.69V6c0-1.1-.9-2-2-2h-2.69l-1.9-1.9c-.78-.78-2.05-.78-2.83 0L8.69 4H6c-1.1 0-2 .9-2 2v2.69l-1.9 1.9c-.78.78-.78 2.05 0 2.83l1.9 1.9V18c0 1.1.9 2 2 2h2.69l1.9 1.9c.78.78 2.05.78 2.83 0l1.9-1.9H18c1.1 0 2-.9 2-2v-2.69l1.9-1.9c.78-.78.78-2.05 0-2.83zm-5.91 6.71L13.6 14h-3.2l-.49 1.4c-.13.36-.46.6-.84.6a.888.888 0 0 1-.84-1.19l2.44-6.86c.2-.57.73-.95 1.33-.95c.6 0 1.13.38 1.34.94l2.44 6.86a.888.888 0 0 1-.84 1.19a.874.874 0 0 1-.85-.59"/></svg>

After

Width:  |  Height:  |  Size: 583 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M11 8c0-.55-.45-1-1-1H3c-.55 0-1 .45-1 1s.45 1 1 1h7c.55 0 1-.45 1-1m0 8c0-.55-.45-1-1-1H3c-.55 0-1 .45-1 1s.45 1 1 1h7c.55 0 1-.45 1-1m6.05-5.71a.996.996 0 0 1-1.41 0l-2.12-2.12a.996.996 0 1 1 1.41-1.41l1.41 1.41l3.54-3.54a.996.996 0 1 1 1.41 1.41zm0 8a.996.996 0 0 1-1.41 0l-2.12-2.12a.996.996 0 1 1 1.41-1.41l1.41 1.41l3.54-3.54a.996.996 0 1 1 1.41 1.41z"/></svg>

After

Width:  |  Height:  |  Size: 478 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M17.75 3A3.25 3.25 0 0 1 21 6.25v11.5A3.25 3.25 0 0 1 17.75 21H6.25A3.25 3.25 0 0 1 3 17.75V6.25A3.25 3.25 0 0 1 6.25 3zm1.75 5.5h-15v9.25c0 .966.784 1.75 1.75 1.75h11.5a1.75 1.75 0 0 0 1.75-1.75zm-1.75-4H6.25A1.75 1.75 0 0 0 4.5 6.25V7h15v-.75a1.75 1.75 0 0 0-1.75-1.75"/></svg>

After

Width:  |  Height:  |  Size: 393 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="m6 18l-2.3 2.3q-.475.475-1.088.213T2 19.575V4q0-.825.588-1.412T4 2h16q.825 0 1.413.588T22 4v12q0 .825-.587 1.413T20 18zm1-4h6q.425 0 .713-.288T14 13t-.288-.712T13 12H7q-.425 0-.712.288T6 13t.288.713T7 14m0-3h10q.425 0 .713-.288T18 10t-.288-.712T17 9H7q-.425 0-.712.288T6 10t.288.713T7 11m0-3h10q.425 0 .713-.288T18 7t-.288-.712T17 6H7q-.425 0-.712.288T6 7t.288.713T7 8"/></svg>

After

Width:  |  Height:  |  Size: 489 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M19.35 10.04A7.49 7.49 0 0 0 12 4C9.11 4 6.6 5.64 5.35 8.04A5.994 5.994 0 0 0 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5c0-2.64-2.05-4.78-4.65-4.96"/></svg>

After

Width:  |  Height:  |  Size: 269 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M14.2 13.5v1.24c-.7.6-1.2 1.5-1.2 2.46V20H6.5c-1.5 0-2.81-.5-3.89-1.57C1.54 17.38 1 16.09 1 14.58c0-1.3.39-2.46 1.17-3.48S4 9.43 5.25 9.15c.42-1.53 1.25-2.77 2.5-3.72S10.42 4 12 4c1.95 0 3.6.68 4.96 2.04a6.74 6.74 0 0 1 1.78 2.99c-2.49.13-4.54 2.12-4.54 4.47m7.6 2.5h-4.3v-2.5c0-.8.7-1.3 1.5-1.3s1.5.5 1.5 1.3v.5h1.3v-.5c0-1.4-1.4-2.5-2.8-2.5s-2.8 1.1-2.8 2.5V16c-.6 0-1.2.6-1.2 1.2v3.5c0 .7.6 1.3 1.2 1.3h5.5c.7 0 1.3-.6 1.3-1.2v-3.5c0-.7-.6-1.3-1.2-1.3"/></svg>

After

Width:  |  Height:  |  Size: 575 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M8.7 15.9L4.8 12l3.9-3.9a.984.984 0 0 0 0-1.4a.984.984 0 0 0-1.4 0l-4.59 4.59a.996.996 0 0 0 0 1.41l4.59 4.6c.39.39 1.01.39 1.4 0a.984.984 0 0 0 0-1.4m6.6 0l3.9-3.9l-3.9-3.9a.984.984 0 0 1 0-1.4a.984.984 0 0 1 1.4 0l4.59 4.59c.39.39.39 1.02 0 1.41l-4.59 4.6a.984.984 0 0 1-1.4 0a.984.984 0 0 1 0-1.4"/></svg>

After

Width:  |  Height:  |  Size: 420 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M9 18q-.825 0-1.412-.587T7 16V4q0-.825.588-1.412T9 2h9q.825 0 1.413.588T20 4v12q0 .825-.587 1.413T18 18zm-4 4q-.825 0-1.412-.587T3 20V7q0-.425.288-.712T4 6t.713.288T5 7v13h10q.425 0 .713.288T16 21t-.288.713T15 22z"/></svg>

After

Width:  |  Height:  |  Size: 334 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M11.01 3.05C6.51 3.54 3 7.36 3 12a9 9 0 0 0 9 9c4.63 0 8.45-3.5 8.95-8c.09-.79-.78-1.42-1.54-.95A5.403 5.403 0 0 1 11.1 7.5c0-1.06.31-2.06.84-2.89c.45-.67-.04-1.63-.93-1.56"/></svg>

After

Width:  |  Height:  |  Size: 293 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6zM19 4h-3.5l-1-1h-5l-1 1H5v2h14z"/></svg>

After

Width:  |  Height:  |  Size: 193 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M5 19q-.825 0-1.412-.587T3 17V8q-.425 0-.712-.288T2 7t.288-.712T3 6h3v-.5q0-.425.288-.712T7 4.5h2q.425 0 .713.288T10 5.5V6h3q.425 0 .713.288T14 7t-.288.713T13 8v9q0 .825-.587 1.413T11 19zm11-1q-.425 0-.712-.288T15 17t.288-.712T16 16h2q.425 0 .713.288T19 17t-.288.713T18 18zm0-4q-.425 0-.712-.288T15 13t.288-.712T16 12h4q.425 0 .713.288T21 13t-.288.713T20 14zm0-4q-.425 0-.712-.288T15 9t.288-.712T16 8h5q.425 0 .713.288T22 9t-.288.713T21 10z"/></svg>

After

Width:  |  Height:  |  Size: 561 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M7 14h2q.425 0 .713-.288T10 13t-.288-.712T9 12H7q-.425 0-.712.288T6 13t.288.713T7 14m12-2q-1.25 0-2.125-.875T16 9t.875-2.125T19 6q.275 0 .525.05t.475.125V2q0-.425.288-.712T21 1h2q.425 0 .713.288T24 2t-.288.713T23 3h-1v6q0 1.25-.875 2.125T19 12M7 11h5q.425 0 .713-.288T13 10t-.288-.712T12 9H7q-.425 0-.712.288T6 10t.288.713T7 11m0-3h5q.425 0 .713-.288T13 7t-.288-.712T12 6H7q-.425 0-.712.288T6 7t.288.713T7 8M6 18l-2.3 2.3q-.475.475-1.088.213T2 19.575V4q0-.825.588-1.412T4 2h11q.775 0 1.363.475T16.95 3.7q0 .35-.162.625t-.438.45q-1.1.675-1.725 1.8T14 9q0 1.35.663 2.5t1.837 1.825q.55.325.875.863t.325 1.187q0 1.125-.788 1.875T15 18z"/></svg>

After

Width:  |  Height:  |  Size: 752 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" viewBox="0 0 24 24" focusable="false" aria-hidden="true"><g><g><path d="M5.9999978125,4.0176934375L18.0000078125,4.0176934375C18.4970078125,4.0176934375,18.9000078125,3.6147534375,18.9000078125,3.1176944375C18.9000078125,2.6206384375,18.4970078125,2.2176944613,18.0000078125,2.2176944613L5.9999978125,2.2176944613C5.5029478125,2.2176944613,5.0999978125,2.6206384375,5.0999978125,3.1176944375C5.0999978125,3.6147534375,5.5029478125,4.0176934375,5.9999978125,4.0176934375ZM3.1960448125,16.8951734375L2.8022507125,11.1750534375Q2.6749484125,9.3258934375,2.7196047125,8.6725834375Q2.8020806125,7.4659834375,3.4361238125,6.7867934375Q4.0701678125,6.1076034375,5.2682578125,5.9424434375Q5.9169578125,5.8530234375,7.7704578125,5.8530234375L16.2294078125,5.8530234375Q18.0829078125,5.8530234375,18.7316078125,5.9424434375Q19.9297078125,6.1076034375,20.5638078125,6.7867934375Q21.1978078125,7.4659934375,21.2803078125,8.6725834375Q21.3249078125,9.3259034375,21.1976078125,11.1750234375L20.8039078125,16.8951734375Q20.6913078125,18.5299734375,20.5753078125,19.1062734375Q20.3615078125,20.1678734375,19.7462078125,20.7422734375Q19.1309078125,21.3166734375,18.0572078125,21.4569734375Q17.474207812499998,21.5331734375,15.8356078125,21.5331734375L8.1642578125,21.5331734375Q6.5256978125,21.5331734375,5.9427178125,21.4569734375Q4.8689478125,21.3166734375,4.2536678125,20.7422734375Q3.6383718125,20.1678734375,3.4246308125000002,19.1062734375Q3.3085848125,18.5299734375,3.1960448125,16.8951734375ZM14.6375078125,8.530113437499999C14.7011078125,8.5104734375,14.7673078125,8.5004834375,14.8339078125,8.5004834375C15.2018078125,8.5004834375,15.4999078125,8.7986734375,15.4999078125,9.1665134375L15.4999078125,15.5220734375L15.4819078125,15.5220734375C15.4827078125,15.5418734375,15.4831078125,15.5618734375,15.4831078125,15.5818734375C15.4831078125,16.379873437500002,14.8362078125,17.0268734375,14.0382078125,17.0268734375C13.2402078125,17.0268734375,12.5932578125,16.379873437500002,12.5932578125,15.5818734375C12.5932578125,14.7838734375,13.2402078125,14.1369734375,14.0382078125,14.1369734375C14.2953078125,14.1369734375,14.5367078125,14.2040734375,14.7459078125,14.3218734375L14.7459078125,11.0771534375L10.3793178125,12.4171734375L10.3793178125,16.8837734375C10.386277812500001,16.9412734375,10.3898678125,16.9997734375,10.3898678125,17.0591734375C10.3898678125,17.857173437500002,9.742947812499999,18.5041734375,8.9449278125,18.5041734375C8.1469178125,18.5041734375,7.4999978125,17.857173437500002,7.4999978125,17.0591734375C7.4999978125,16.2611734375,8.1469178125,15.6142734375,8.9449278125,15.6142734375C9.190897812500001,15.6142734375,9.422507812500001,15.6756734375,9.6252478125,15.7840734375L9.6252478125,10.5687534375C9.6252478125,10.2765934375,9.8156578125,10.0185334375,10.0948278125,9.932363437500001L14.6375078125,8.530113437499999Z" fill-rule="evenodd" fill="currentColor" fill-opacity="1"></path></g></g></svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><g fill="none" fill-rule="evenodd"><path d="M24 0v24H0V0zM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035c-.01-.004-.019-.001-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427c-.002-.01-.009-.017-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093c.012.004.023 0 .029-.008l.004-.014l-.034-.614c-.003-.012-.01-.02-.02-.022m-.715.002a.023.023 0 0 0-.027.006l-.006.014l-.034.614c0 .012.007.02.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"/><path fill="currentColor" d="M13.06 16.06a1.5 1.5 0 0 1-2.12 0l-5.658-5.656a1.5 1.5 0 1 1 2.122-2.121L12 12.879l4.596-4.596a1.5 1.5 0 0 1 2.122 2.12l-5.657 5.658Z"/></g></svg>

After

Width:  |  Height:  |  Size: 848 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M16.59 9H15V4c0-.55-.45-1-1-1h-4c-.55 0-1 .45-1 1v5H7.41c-.89 0-1.34 1.08-.71 1.71l4.59 4.59c.39.39 1.02.39 1.41 0l4.59-4.59c.63-.63.19-1.71-.7-1.71M5 19c0 .55.45 1 1 1h12c.55 0 1-.45 1-1s-.45-1-1-1H6c-.55 0-1 .45-1 1"/></svg>

After

Width:  |  Height:  |  Size: 338 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="m11.565 13.873l-2.677-2.677q-.055-.055-.093-.129q-.037-.073-.037-.157q0-.168.11-.289q.112-.121.294-.121h5.677q.181 0 .292.124t.111.288q0 .042-.13.284l-2.677 2.677q-.093.093-.2.143t-.235.05t-.235-.05t-.2-.143"/></svg>

After

Width:  |  Height:  |  Size: 328 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M17.9 17.39c-.26-.8-1.01-1.39-1.9-1.39h-1v-3a1 1 0 0 0-1-1H8v-2h2a1 1 0 0 0 1-1V7h2a2 2 0 0 0 2-2v-.41a7.984 7.984 0 0 1 2.9 12.8M11 19.93c-3.95-.49-7-3.85-7-7.93c0-.62.08-1.22.21-1.79L9 15v1a2 2 0 0 0 2 2m1-16A10 10 0 0 0 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2"/></svg>

After

Width:  |  Height:  |  Size: 401 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M14 11c0 .55-.45 1-1 1H4c-.55 0-1-.45-1-1s.45-1 1-1h9c.55 0 1 .45 1 1M3 7c0 .55.45 1 1 1h9c.55 0 1-.45 1-1s-.45-1-1-1H4c-.55 0-1 .45-1 1m7 8c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1s.45 1 1 1h5c.55 0 1-.45 1-1m8.01-2.13l.71-.71a.996.996 0 0 1 1.41 0l.71.71c.39.39.39 1.02 0 1.41l-.71.71zm-.71.71l-5.16 5.16c-.09.09-.14.21-.14.35v1.41c0 .28.22.5.5.5h1.41c.13 0 .26-.05.35-.15l5.16-5.16z"/></svg>

After

Width:  |  Height:  |  Size: 500 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M19 3H5c-1.11 0-2 .89-2 2v4h2V5h14v14H5v-4H3v4a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2m-8.92 12.58L11.5 17l5-5l-5-5l-1.42 1.41L12.67 11H3v2h9.67z"/></svg>

After

Width:  |  Height:  |  Size: 273 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5M12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5s5 2.24 5 5s-2.24 5-5 5m0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3s3-1.34 3-3s-1.34-3-3-3"/></svg>

After

Width:  |  Height:  |  Size: 329 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M20.8 17v-1.5c0-1.4-1.4-2.5-2.8-2.5s-2.8 1.1-2.8 2.5V17c-.6 0-1.2.6-1.2 1.2v3.5c0 .7.6 1.3 1.2 1.3h5.5c.7 0 1.3-.6 1.3-1.2v-3.5c0-.7-.6-1.3-1.2-1.3m-1.3 0h-3v-1.5c0-.8.7-1.3 1.5-1.3s1.5.5 1.5 1.3zM15 12c-.9.7-1.5 1.6-1.7 2.7c-.4.2-.8.3-1.3.3c-1.7 0-3-1.3-3-3s1.3-3 3-3s3 1.3 3 3m-3 7.5c-5 0-9.3-3.1-11-7.5c1.7-4.4 6-7.5 11-7.5s9.3 3.1 11 7.5c-.2.5-.5 1-.7 1.5C21.5 12 19.8 11 18 11c-.4 0-.7.1-1.1.1C16.5 8.8 14.5 7 12 7c-2.8 0-5 2.2-5 5s2.2 5 5 5h.3q-.3.6-.3 1.2z"/></svg>

After

Width:  |  Height:  |  Size: 584 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M13.35 20.13c-.76.69-1.93.69-2.69-.01l-.11-.1C5.3 15.27 1.87 12.16 2 8.28c.06-1.7.93-3.33 2.34-4.29c2.64-1.8 5.9-.96 7.66 1.1c1.76-2.06 5.02-2.91 7.66-1.1c1.41.96 2.28 2.59 2.34 4.29c.14 3.88-3.3 6.99-8.55 11.76z"/></svg>

After

Width:  |  Height:  |  Size: 333 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M19.66 3.99c-2.64-1.8-5.9-.96-7.66 1.1c-1.76-2.06-5.02-2.91-7.66-1.1c-1.4.96-2.28 2.58-2.34 4.29c-.14 3.88 3.3 6.99 8.55 11.76l.1.09c.76.69 1.93.69 2.69-.01l.11-.1c5.25-4.76 8.68-7.87 8.55-11.75c-.06-1.7-.94-3.32-2.34-4.28M12.1 18.55l-.1.1l-.1-.1C7.14 14.24 4 11.39 4 8.5C4 6.5 5.5 5 7.5 5c1.54 0 3.04.99 3.57 2.36h1.87C13.46 5.99 14.96 5 16.5 5c2 0 3.5 1.5 3.5 3.5c0 2.89-3.14 5.74-7.9 10.05"/></svg>

After

Width:  |  Height:  |  Size: 513 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M17.66 11.2c-.23-.3-.51-.56-.77-.82c-.67-.6-1.43-1.03-2.07-1.66C13.33 7.26 13 4.85 13.95 3c-.95.23-1.78.75-2.49 1.32c-2.59 2.08-3.61 5.75-2.39 8.9c.04.1.08.2.08.33c0 .22-.15.42-.35.5c-.23.1-.47.04-.66-.12a.58.58 0 0 1-.14-.17c-1.13-1.43-1.31-3.48-.55-5.12C5.78 10 4.87 12.3 5 14.47c.06.5.12 1 .29 1.5c.14.6.41 1.2.71 1.73c1.08 1.73 2.95 2.97 4.96 3.22c2.14.27 4.43-.12 6.07-1.6c1.83-1.66 2.47-4.32 1.53-6.6l-.13-.26c-.21-.46-.77-1.26-.77-1.26m-3.16 6.3c-.28.24-.74.5-1.1.6c-1.12.4-2.24-.16-2.9-.82c1.19-.28 1.9-1.16 2.11-2.05c.17-.8-.15-1.46-.28-2.23c-.12-.74-.1-1.37.17-2.06c.19.38.39.76.63 1.06c.77 1 1.98 1.44 2.24 2.8c.04.14.06.28.06.43c.03.82-.33 1.72-.93 2.27"/></svg>

After

Width:  |  Height:  |  Size: 786 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M10.59 4.59C10.21 4.21 9.7 4 9.17 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8z"/></svg>

After

Width:  |  Height:  |  Size: 237 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M4 4c-1.11 0-2 .89-2 2v12a2 2 0 0 0 2 2h8.08a7 7 0 0 1-.08-1a7 7 0 0 1 7-7a7 7 0 0 1 3 .69V8a2 2 0 0 0-2-2h-8l-2-2zm14 10a.26.26 0 0 0-.26.21l-.19 1.32c-.3.13-.59.29-.85.47l-1.24-.5c-.11 0-.24 0-.31.13l-1 1.73c-.06.11-.04.24.06.32l1.06.82a4.193 4.193 0 0 0 0 1l-1.06.82a.26.26 0 0 0-.06.32l1 1.73c.06.13.19.13.31.13l1.24-.5c.26.18.54.35.85.47l.19 1.32c.02.12.12.21.26.21h2c.11 0 .22-.09.24-.21l.19-1.32c.3-.13.57-.29.84-.47l1.23.5c.13 0 .26 0 .33-.13l1-1.73a.26.26 0 0 0-.06-.32l-1.07-.82c.02-.17.04-.33.04-.5c0-.17-.01-.33-.04-.5l1.06-.82a.26.26 0 0 0 .06-.32l-1-1.73c-.06-.13-.19-.13-.32-.13l-1.23.5c-.27-.18-.54-.35-.85-.47l-.19-1.32A.236.236 0 0 0 20 14zm1 3.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5c-.84 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5"/></svg>

After

Width:  |  Height:  |  Size: 862 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M22 8v3h-5.5v5.11c-1.84.42-3.24 1.98-3.46 3.89H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h8a2 2 0 0 1 2 2m-3.5 5v5.21a2.5 2.5 0 1 0-1 4.79a2.5 2.5 0 0 0 2.5-2.5V15h2v-2z"/></svg>

After

Width:  |  Height:  |  Size: 287 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M13 19c0 .34.04.67.09 1H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h8a2 2 0 0 1 2 2v5.81c-.88-.51-1.9-.81-3-.81c-3.31 0-6 2.69-6 6m7-1v-3h-2v3h-3v2h3v3h2v-3h3v-2z"/></svg>

After

Width:  |  Height:  |  Size: 279 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M4 10.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5s1.5-.67 1.5-1.5s-.67-1.5-1.5-1.5m0-6c-.83 0-1.5.67-1.5 1.5S3.17 7.5 4 7.5S5.5 6.83 5.5 6S4.83 4.5 4 4.5m0 12c-.83 0-1.5.68-1.5 1.5s.68 1.5 1.5 1.5s1.5-.68 1.5-1.5s-.67-1.5-1.5-1.5M8 19h12c.55 0 1-.45 1-1s-.45-1-1-1H8c-.55 0-1 .45-1 1s.45 1 1 1m0-6h12c.55 0 1-.45 1-1s-.45-1-1-1H8c-.55 0-1 .45-1 1s.45 1 1 1M7 6c0 .55.45 1 1 1h12c.55 0 1-.45 1-1s-.45-1-1-1H8c-.55 0-1 .45-1 1"/></svg>

After

Width:  |  Height:  |  Size: 541 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M12 22q-1.875 0-3.512-.712t-2.85-1.925t-1.925-2.85T3 13t.713-3.512t1.924-2.85t2.85-1.925T12 4h.15l-.85-.85q-.3-.3-.288-.7t.288-.7q.3-.3.713-.312t.712.287L15.3 4.3q.3.3.3.7t-.3.7l-2.575 2.575q-.3.3-.712.288T11.3 8.25q-.275-.3-.288-.7t.288-.7l.85-.85H12Q9.075 6 7.038 8.038T5 13t2.038 4.963T12 20t4.963-2.037T19 13q0-.425.288-.712T20 12t.713.288T21 13q0 1.875-.712 3.513t-1.925 2.85t-2.85 1.925T12 22m1-6h-2.75q-.325 0-.537-.213T9.5 15.25t.213-.537t.537-.213h2.25v-1h-2.25q-.325 0-.537-.213T9.5 12.75v-2q0-.325.213-.537T10.25 10h3q.325 0 .538.213t.212.537t-.213.538t-.537.212H11v1h2.25q.325 0 .538.213t.212.537V15q0 .425-.288.713T13 16"/></svg>

After

Width:  |  Height:  |  Size: 754 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><g fill="none" fill-rule="evenodd"><path d="M24 0v24H0V0zM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035c-.01-.004-.019-.001-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427c-.002-.01-.009-.017-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093c.012.004.023 0 .029-.008l.004-.014l-.034-.614c-.003-.012-.01-.02-.02-.022m-.715.002a.023.023 0 0 0-.027.006l-.006.014l-.034.614c0 .012.007.02.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"/><path fill="currentColor" d="M18.5 5.5H16a1.5 1.5 0 0 1 0-3h3A2.5 2.5 0 0 1 21.5 5v3a1.5 1.5 0 0 1-3 0zM8 5.5H5.5V8a1.5 1.5 0 1 1-3 0V5A2.5 2.5 0 0 1 5 2.5h3a1.5 1.5 0 1 1 0 3m0 13H5.5V16a1.5 1.5 0 0 0-3 0v3A2.5 2.5 0 0 0 5 21.5h3a1.5 1.5 0 0 0 0-3m8 0h2.5V16a1.5 1.5 0 0 1 3 0v3a2.5 2.5 0 0 1-2.5 2.5h-3a1.5 1.5 0 0 1 0-3"/></g></svg>

After

Width:  |  Height:  |  Size: 1008 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><g fill="none" fill-rule="evenodd"><path d="M24 0v24H0V0zM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035c-.01-.004-.019-.001-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427c-.002-.01-.009-.017-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093c.012.004.023 0 .029-.008l.004-.014l-.034-.614c-.003-.012-.01-.02-.02-.022m-.715.002a.023.023 0 0 0-.027.006l-.006.014l-.034.614c0 .012.007.02.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"/><path fill="currentColor" d="M17.5 6.5H20a1.5 1.5 0 0 1 0 3h-3A2.5 2.5 0 0 1 14.5 7V4a1.5 1.5 0 0 1 3 0zM4 6.5h2.5V4a1.5 1.5 0 1 1 3 0v3A2.5 2.5 0 0 1 7 9.5H4a1.5 1.5 0 1 1 0-3m0 11h2.5V20a1.5 1.5 0 0 0 3 0v-3A2.5 2.5 0 0 0 7 14.5H4a1.5 1.5 0 0 0 0 3m16 0h-2.5V20a1.5 1.5 0 0 1-3 0v-3a2.5 2.5 0 0 1 2.5-2.5h3a1.5 1.5 0 0 1 0 3"/></g></svg>

After

Width:  |  Height:  |  Size: 1012 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M12 2A10 10 0 0 0 2 12c0 4.42 2.87 8.17 6.84 9.5c.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34c-.46-1.16-1.11-1.47-1.11-1.47c-.91-.62.07-.6.07-.6c1 .07 1.53 1.03 1.53 1.03c.87 1.52 2.34 1.07 2.91.83c.09-.65.35-1.09.63-1.34c-2.22-.25-4.55-1.11-4.55-4.92c0-1.11.38-2 1.03-2.71c-.1-.25-.45-1.29.1-2.64c0 0 .84-.27 2.75 1.02c.79-.22 1.65-.33 2.5-.33s1.71.11 2.5.33c1.91-1.29 2.75-1.02 2.75-1.02c.55 1.35.2 2.39.1 2.64c.65.71 1.03 1.6 1.03 2.71c0 3.82-2.34 4.66-4.57 4.91c.36.31.69.92.69 1.85V21c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0 0 12 2"/></svg>

After

Width:  |  Height:  |  Size: 679 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 256 256"><path fill="currentColor" d="M72 144H32a8 8 0 0 1 0-16h35.72l13.62-20.44a8 8 0 0 1 13.32 0l25.34 38l9.34-14A8 8 0 0 1 136 128h24a8 8 0 0 1 0 16h-19.72l-13.62 20.44a8 8 0 0 1-13.32 0L88 126.42l-9.34 14A8 8 0 0 1 72 144M178 40c-20.65 0-38.73 8.88-50 23.89C116.73 48.88 98.65 40 78 40a62.07 62.07 0 0 0-62 62v2.25a8 8 0 1 0 16-.5V102a46.06 46.06 0 0 1 46-46c19.45 0 35.78 10.36 42.6 27a8 8 0 0 0 14.8 0c6.82-16.67 23.15-27 42.6-27a46.06 46.06 0 0 1 46 46c0 53.61-77.76 102.15-96 112.8c-10.83-6.31-42.63-26-66.68-52.21a8 8 0 1 0-11.8 10.82c31.17 34 72.93 56.68 74.69 57.63a8 8 0 0 0 7.58 0C136.21 228.66 240 172 240 102a62.07 62.07 0 0 0-62-62"/></svg>

After

Width:  |  Height:  |  Size: 733 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M11.95 18q.525 0 .888-.363t.362-.887t-.362-.888t-.888-.362t-.887.363t-.363.887t.363.888t.887.362m.05 4q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22m.1-14.3q.625 0 1.088.4t.462 1q0 .55-.337.975t-.763.8q-.575.5-1.012 1.1t-.438 1.35q0 .35.263.588t.612.237q.375 0 .638-.25t.337-.625q.1-.525.45-.937t.75-.788q.575-.55.988-1.2t.412-1.45q0-1.275-1.037-2.087T12.1 6q-.95 0-1.812.4T8.975 7.625q-.175.3-.112.638t.337.512q.35.2.725.125t.625-.425q.275-.375.688-.575t.862-.2"/></svg>

After

Width:  |  Height:  |  Size: 699 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1723795622287" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8321" data-spm-anchor-id="a313x.search_index.0.i4.49233a81OKjI9r" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256"><path d="M917.661538 295.384615c59.076923 0 106.338462 47.261538 106.338462 108.307693v216.615384c0 61.046154-47.261538 108.307692-106.338462 108.307693H106.338462c-59.076923 0-106.338462-47.261538-106.338462-108.307693v-216.615384C0 342.646154 47.261538 295.384615 106.338462 295.384615h811.323076z m0 21.661539H106.338462c-45.292308 0-82.707692 37.415385-84.676924 82.707692v220.553846c0 45.292308 35.446154 84.676923 80.738462 86.646154h815.261538c45.292308 0 82.707692-37.415385 84.676924-82.707692V403.692308c0-45.292308-35.446154-84.676923-80.738462-86.646154h-3.938462z" p-id="8322"></path><path d="M151.630769 626.215385v-110.276923h82.707693v110.276923h45.292307V387.938462h-45.292307v92.553846H151.630769v-94.523077H106.338462v240.246154h45.292307z m185.107693-196.923077c19.692308 0 23.630769-1.969231 23.630769-23.63077s-3.938462-23.630769-23.630769-23.630769-23.630769 1.969231-23.63077 23.630769c-1.969231 19.692308 3.938462 23.630769 23.63077 23.63077z m21.661538 198.892307v-187.076923H315.076923v187.076923h43.323077z m129.969231-1.96923v-90.584616h21.661538l59.076923 90.584616h49.23077L551.384615 531.692308c31.507692-7.876923 45.292308-29.538462 45.292308-68.923077 0-55.138462-27.569231-72.861538-92.553846-72.861539h-61.046154V630.153846l45.292308-3.938461z m11.815384-122.092308h-11.815384v-80.738462h11.815384c37.415385 0 53.169231 5.907692 53.169231 37.415385 0 35.446154-15.753846 43.323077-53.169231 43.323077zM704.984615 630.153846c29.538462 0 57.107692-5.907692 68.923077-13.784615v-35.446154c-13.784615 5.907692-39.384615 13.784615-59.076923 13.784615-35.446154 0-51.2-15.753846-53.169231-45.292307l120.123077-1.969231c0-5.907692 1.969231-15.753846 1.969231-23.630769 0-43.323077-13.784615-86.646154-76.8-86.646154-55.138462 0-86.646154 21.661538-86.646154 96.492307s27.569231 96.492308 84.676923 96.492308z m-45.292307-110.276923c0-31.507692 9.846154-49.230769 45.292307-49.230769 31.507692 0 35.446154 25.6 35.446154 49.230769h-80.738461z m192.984615 110.276923c47.261538 0 72.861538-15.753846 72.861539-59.076923 0-35.446154-13.784615-47.261538-53.169231-57.107692-27.569231-5.907692-33.476923-7.876923-33.476923-23.630769 0-15.753846 7.876923-19.692308 31.507692-19.692308 17.723077 0 35.446154 1.969231 45.292308 5.907692l1.96923-35.446154c-7.876923-3.938462-29.538462-5.907692-49.230769-5.907692-51.2 0-70.892308 19.692308-70.892307 55.138462 0 29.538462 9.846154 43.323077 49.230769 55.138461 31.507692 5.907692 37.415385 11.815385 37.415384 27.569231 0 17.723077-9.846154 21.661538-33.476923 21.661538-17.723077 0-37.415385-1.969231-55.138461-7.876923v37.415385c15.753846 3.938462 39.384615 5.907692 57.107692 5.907692z" p-id="8323" data-spm-anchor-id="a313x.search_index.0.i3.49233a81OKjI9r" class=""></path></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M13.26 3C8.17 2.86 4 6.95 4 12H2.21c-.45 0-.67.54-.35.85l2.79 2.8c.2.2.51.2.71 0l2.79-2.8a.5.5 0 0 0-.36-.85H6c0-3.9 3.18-7.05 7.1-7c3.72.05 6.85 3.18 6.9 6.9c.05 3.91-3.1 7.1-7 7.1c-1.61 0-3.1-.55-4.28-1.48a.994.994 0 0 0-1.32.08c-.42.42-.39 1.13.08 1.49A8.858 8.858 0 0 0 13 21c5.05 0 9.14-4.17 9-9.26c-.13-4.69-4.05-8.61-8.74-8.74m-.51 5c-.41 0-.75.34-.75.75v3.68c0 .35.19.68.49.86l3.12 1.85c.36.21.82.09 1.03-.26c.21-.36.09-.82-.26-1.03l-2.88-1.71v-3.4c0-.4-.34-.74-.75-.74"/></svg>

After

Width:  |  Height:  |  Size: 598 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M4 19v-9q0-.475.213-.9t.587-.7l6-4.5q.525-.4 1.2-.4t1.2.4l6 4.5q.375.275.588.7T20 10v9q0 .825-.588 1.413T18 21h-3q-.425 0-.712-.288T14 20v-5q0-.425-.288-.712T13 14h-2q-.425 0-.712.288T10 15v5q0 .425-.288.713T9 21H6q-.825 0-1.412-.587T4 19"/></svg>

After

Width:  |  Height:  |  Size: 359 B

View File

@@ -0,0 +1,2 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M16 11h-2V9h2v2M3 5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5m7 2H8v10h2V7m2 10h2v-4h2a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-4v10Z",
"cloud": "M6.5 20q-2.28 0-3.89-1.57Q1 16.85 1 14.58q0-1.95 1.17-3.48q1.18-1.53 3.08-1.95q.63-2.3 2.5-3.72Q9.63 4 12 4q2.93 0 4.96 2.04Q19 8.07 19 11q1.73.2 2.86 1.5q1.14 1.28 1.14 3q0 1.88-1.31 3.19T18.5 20Z"/></svg>

After

Width:  |  Height:  |  Size: 481 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10s10-4.48 10-10S17.52 2 12 2m0 15c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1s1 .45 1 1v4c0 .55-.45 1-1 1m1-8h-2V7h2z"/></svg>

After

Width:  |  Height:  |  Size: 266 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M20 5H4c-1.1 0-1.99.9-1.99 2L2 17c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2m-9 3h2v2h-2zm0 3h2v2h-2zM8 8h2v2H8zm0 3h2v2H8zm-1 2H5v-2h2zm0-3H5V8h2zm8 7H9c-.55 0-1-.45-1-1s.45-1 1-1h6c.55 0 1 .45 1 1s-.45 1-1 1m1-4h-2v-2h2zm0-3h-2V8h2zm3 3h-2v-2h2zm0-3h-2V8h2z"/></svg>

After

Width:  |  Height:  |  Size: 386 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="m6.05 4.14l-.39-.39a.993.993 0 0 0-1.4 0l-.01.01a.984.984 0 0 0 0 1.4l.39.39c.39.39 1.01.39 1.4 0l.01-.01a.984.984 0 0 0 0-1.4M3.01 10.5H1.99c-.55 0-.99.44-.99.99v.01c0 .55.44.99.99.99H3c.56.01 1-.43 1-.98v-.01c0-.56-.44-1-.99-1m9-9.95H12c-.56 0-1 .44-1 .99v.96c0 .55.44.99.99.99H12c.56.01 1-.43 1-.98v-.97c0-.55-.44-.99-.99-.99m7.74 3.21c-.39-.39-1.02-.39-1.41-.01l-.39.39a.984.984 0 0 0 0 1.4l.01.01c.39.39 1.02.39 1.4 0l.39-.39a.984.984 0 0 0 0-1.4m-1.81 15.1l.39.39a.996.996 0 1 0 1.41-1.41l-.39-.39a.993.993 0 0 0-1.4 0c-.4.4-.4 1.02-.01 1.41M20 11.49v.01c0 .55.44.99.99.99H22c.55 0 .99-.44.99-.99v-.01c0-.55-.44-.99-.99-.99h-1.01c-.55 0-.99.44-.99.99M12 5.5c-3.31 0-6 2.69-6 6s2.69 6 6 6s6-2.69 6-6s-2.69-6-6-6m-.01 16.95H12c.55 0 .99-.44.99-.99v-.96c0-.55-.44-.99-.99-.99h-.01c-.55 0-.99.44-.99.99v.96c0 .55.44.99.99.99m-7.74-3.21c.39.39 1.02.39 1.41 0l.39-.39a.993.993 0 0 0 0-1.4l-.01-.01a.996.996 0 0 0-1.41 0l-.39.39c-.38.4-.38 1.02.01 1.41"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M17 7h-3c-.55 0-1 .45-1 1s.45 1 1 1h3c1.65 0 3 1.35 3 3s-1.35 3-3 3h-3c-.55 0-1 .45-1 1s.45 1 1 1h3c2.76 0 5-2.24 5-5s-2.24-5-5-5m-9 5c0 .55.45 1 1 1h6c.55 0 1-.45 1-1s-.45-1-1-1H9c-.55 0-1 .45-1 1m2 3H7c-1.65 0-3-1.35-3-3s1.35-3 3-3h3c.55 0 1-.45 1-1s-.45-1-1-1H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h3c.55 0 1-.45 1-1s-.45-1-1-1"/></svg>

After

Width:  |  Height:  |  Size: 444 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M4 10.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5s1.5-.67 1.5-1.5s-.67-1.5-1.5-1.5m0-6c-.83 0-1.5.67-1.5 1.5S3.17 7.5 4 7.5S5.5 6.83 5.5 6S4.83 4.5 4 4.5m0 12c-.83 0-1.5.68-1.5 1.5s.68 1.5 1.5 1.5s1.5-.68 1.5-1.5s-.67-1.5-1.5-1.5M8 19h12c.55 0 1-.45 1-1s-.45-1-1-1H8c-.55 0-1 .45-1 1s.45 1 1 1m0-6h12c.55 0 1-.45 1-1s-.45-1-1-1H8c-.55 0-1 .45-1 1s.45 1 1 1M7 6c0 .55.45 1 1 1h12c.55 0 1-.45 1-1s-.45-1-1-1H8c-.55 0-1 .45-1 1"/></svg>

After

Width:  |  Height:  |  Size: 541 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M23 17.3v3.5c0 .6-.6 1.2-1.3 1.2h-5.5c-.6 0-1.2-.6-1.2-1.3v-3.5c0-.6.6-1.2 1.2-1.2v-2.5c0-1.4 1.4-2.5 2.8-2.5s2.8 1.1 2.8 2.5v.5h-1.3v-.5c0-.8-.7-1.3-1.5-1.3s-1.5.5-1.5 1.3V16h4.3c.6 0 1.2.6 1.2 1.3M3 13v-2h12v2zm0-7h18v2H3zm0 12v-2h6v2z"/></svg>

After

Width:  |  Height:  |  Size: 358 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M11 21.95v-1q-3.125-.35-5.363-2.587T3.05 13h-1q-.425 0-.712-.288T1.05 12t.288-.712T2.05 11h1q.35-3.125 2.588-5.363T11 3.05v-1q0-.425.288-.712T12 1.05t.713.288t.287.712v1q3.125.35 5.363 2.588T20.95 11h1q.425 0 .713.288t.287.712t-.287.713t-.713.287h-1q-.35 3.125-2.587 5.363T13 20.95v1q0 .425-.288.713T12 22.95t-.712-.287T11 21.95M12 19q2.9 0 4.95-2.05T19 12t-2.05-4.95T12 5T7.05 7.05T5 12t2.05 4.95T12 19m0-3q-1.65 0-2.825-1.175T8 12t1.175-2.825T12 8t2.825 1.175T16 12t-1.175 2.825T12 16"/></svg>

After

Width:  |  Height:  |  Size: 607 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M14 9c0-2.04 1.24-3.79 3-4.57V4c0-1.1-.9-2-2-2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h9c1.1 0 2-.9 2-2v-2.42c-1.76-.78-3-2.53-3-4.58m-4 5H6v-2h4zm3-3H6V9h7zm0-3H6V6h7z"/><path fill="currentColor" d="M20 6.18c-.31-.11-.65-.18-1-.18c-1.66 0-3 1.34-3 3s1.34 3 3 3s3-1.34 3-3V3h2V1h-4z"/></svg>

After

Width:  |  Height:  |  Size: 395 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M4 18h16c.55 0 1-.45 1-1s-.45-1-1-1H4c-.55 0-1 .45-1 1s.45 1 1 1m0-5h16c.55 0 1-.45 1-1s-.45-1-1-1H4c-.55 0-1 .45-1 1s.45 1 1 1M3 7c0 .55.45 1 1 1h16c.55 0 1-.45 1-1s-.45-1-1-1H4c-.55 0-1 .45-1 1"/></svg>

After

Width:  |  Height:  |  Size: 316 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2m-3 12H7c-.55 0-1-.45-1-1s.45-1 1-1h10c.55 0 1 .45 1 1s-.45 1-1 1m0-3H7c-.55 0-1-.45-1-1s.45-1 1-1h10c.55 0 1 .45 1 1s-.45 1-1 1m0-3H7c-.55 0-1-.45-1-1s.45-1 1-1h10c.55 0 1 .45 1 1s-.45 1-1 1"/></svg>

After

Width:  |  Height:  |  Size: 384 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M12 5v8.55c-.94-.54-2.1-.75-3.33-.32c-1.34.48-2.37 1.67-2.61 3.07a4.007 4.007 0 0 0 4.59 4.65c1.96-.31 3.35-2.11 3.35-4.1V7h2c1.1 0 2-.9 2-2s-.9-2-2-2h-2c-1.1 0-2 .9-2 2"/></svg>

After

Width:  |  Height:  |  Size: 290 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M16 20q-1.25 0-2.125-.875T13 17t.875-2.125T16 14q.275 0 .525.038T17 14.2V7q0-.425.288-.712T18 6h3q.425 0 .713.288T22 7t-.288.713T21 8h-2v9q0 1.25-.875 2.125T16 20M4 16q-.425 0-.712-.288T3 15t.288-.712T4 14h6q.425 0 .713.288T11 15t-.288.713T10 16zm0-4q-.425 0-.712-.288T3 11t.288-.712T4 10h10q.425 0 .713.288T15 11t-.288.713T14 12zm0-4q-.425 0-.712-.288T3 7t.288-.712T4 6h10q.425 0 .713.288T15 7t-.288.713T14 8z"/></svg>

After

Width:  |  Height:  |  Size: 531 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M14.91 6.71a.996.996 0 0 0-1.41 0L8.91 11.3a.996.996 0 0 0 0 1.41l4.59 4.59a.996.996 0 1 0 1.41-1.41L11.03 12l3.88-3.88c.38-.39.38-1.03 0-1.41"/></svg>

After

Width:  |  Height:  |  Size: 263 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M9.31 6.71a.996.996 0 0 0 0 1.41L13.19 12l-3.88 3.88a.996.996 0 1 0 1.41 1.41l4.59-4.59a.996.996 0 0 0 0-1.41L10.72 6.7c-.38-.38-1.02-.38-1.41.01"/></svg>

After

Width:  |  Height:  |  Size: 266 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M3 17h18c.55 0 1 .45 1 1s-.45 1-1 1H3c-.55 0-1-.45-1-1s.45-1 1-1m-.5-4.43c.36.21.82.08 1.03-.28l.47-.82l.48.83c.21.36.67.48 1.03.28c.36-.21.48-.66.28-1.02l-.49-.84h.95c.41 0 .75-.34.75-.75s-.34-.75-.75-.75H5.3l.47-.82c.21-.36.09-.82-.27-1.03a.764.764 0 0 0-1.03.28L4 8.47l-.47-.82a.764.764 0 0 0-1.03-.28c-.36.21-.48.67-.27 1.03l.47.82h-.95c-.41 0-.75.34-.75.75s.34.75.75.75h.95l-.48.83c-.2.36-.08.82.28 1.02m8 0c.36.21.82.08 1.03-.28l.47-.82l.48.83c.21.36.67.48 1.03.28c.36-.21.48-.66.28-1.02l-.48-.83h.95c.41 0 .75-.34.75-.75s-.34-.75-.75-.75h-.96l.47-.82a.76.76 0 0 0-.27-1.03a.746.746 0 0 0-1.02.27l-.48.82l-.47-.82a.742.742 0 0 0-1.02-.27c-.36.21-.48.67-.27 1.03l.47.82h-.96a.74.74 0 0 0-.75.74c0 .41.34.75.75.75h.95l-.48.83c-.2.36-.08.82.28 1.02M23 9.97c0-.41-.34-.75-.75-.75h-.95l.47-.82a.76.76 0 0 0-.27-1.03a.746.746 0 0 0-1.02.27l-.48.83l-.47-.82a.742.742 0 0 0-1.02-.27c-.36.21-.48.67-.27 1.03l.47.82h-.95a.743.743 0 0 0-.76.74c0 .41.34.75.75.75h.95l-.48.83a.74.74 0 0 0 .28 1.02c.36.21.82.08 1.03-.28l.47-.82l.48.83c.21.36.67.48 1.03.28c.36-.21.48-.66.28-1.02l-.48-.83h.95c.4-.01.74-.35.74-.76"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M19 2h-4.18C14.4.84 13.3 0 12 0S9.6.84 9.18 2H5c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2m-7 0c.55 0 1 .45 1 1s-.45 1-1 1s-1-.45-1-1s.45-1 1-1m6 18H6c-.55 0-1-.45-1-1V5c0-.55.45-1 1-1h1v1c0 1.1.9 2 2 2h6c1.1 0 2-.9 2-2V4h1c.55 0 1 .45 1 1v14c0 .55-.45 1-1 1"/></svg>

After

Width:  |  Height:  |  Size: 404 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M8 19c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2s-2 .9-2 2v10c0 1.1.9 2 2 2m6-12v10c0 1.1.9 2 2 2s2-.9 2-2V7c0-1.1-.9-2-2-2s-2 .9-2 2"/></svg>

After

Width:  |  Height:  |  Size: 243 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10s10-4.48 10-10S17.52 2 12 2m-2 14c-.55 0-1-.45-1-1V9c0-.55.45-1 1-1s1 .45 1 1v6c0 .55-.45 1-1 1m4 0c-.55 0-1-.45-1-1V9c0-.55.45-1 1-1s1 .45 1 1v6c0 .55-.45 1-1 1"/></svg>

After

Width:  |  Height:  |  Size: 320 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4s-4 1.79-4 4s1.79 4 4 4m0 2c-2.67 0-8 1.34-8 4v1c0 .55.45 1 1 1h14c.55 0 1-.45 1-1v-1c0-2.66-5.33-4-8-4"/></svg>

After

Width:  |  Height:  |  Size: 260 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M16 1H8C6.34 1 5 2.34 5 4v16c0 1.66 1.34 3 3 3h8c1.66 0 3-1.34 3-3V4c0-1.66-1.34-3-3-3m-2 20h-4v-1h4zm3.25-3H6.75V4h10.5z"/></svg>

After

Width:  |  Height:  |  Size: 242 B

Some files were not shown because too many files have changed in this diff Show More