├── .babelrc ├── .ctrlpignore ├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .gitmodules ├── .travis.yml ├── NeteaseCloudMusicApi ├── .gitignore ├── .npmignore ├── .pre-commit-config.yaml ├── .travis.yml ├── CHANGELOG.MD ├── LICENSE ├── README.MD ├── app.js ├── docs │ ├── .nojekyll │ ├── README.md │ ├── _coverpage.md │ ├── favicon.ico │ ├── icon.png │ ├── index.html │ └── sw.js ├── issue_template.md ├── package.json ├── public │ └── index.html ├── router │ ├── album.js │ ├── artist_album.js │ ├── artists.js │ ├── artists_desc.js │ ├── artists_mv.js │ ├── banner.js │ ├── check_music.js │ ├── comment_album.js │ ├── comment_dj.js │ ├── comment_like.js │ ├── comment_music.js │ ├── comment_mv.js │ ├── comment_playlist.js │ ├── daily_signin.js │ ├── dj_catelist.js │ ├── dj_detail.js │ ├── dj_hot.js │ ├── dj_program.js │ ├── dj_program_detail.js │ ├── dj_recommend.js │ ├── dj_recommend_type.js │ ├── dj_sub.js │ ├── event.js │ ├── fm_trash.js │ ├── follow.js │ ├── like.js │ ├── likelist.js │ ├── logWeb.js │ ├── login.js │ ├── loginCellphone.js │ ├── login_refresh.js │ ├── lyric.js │ ├── musicUrl.js │ ├── mv.js │ ├── mv_first.js │ ├── mv_url.js │ ├── personal_fm.js │ ├── personalized.js │ ├── personalized_djprogram.js │ ├── personalized_mv.js │ ├── personalized_newsong.js │ ├── personalized_privatecontent.js │ ├── playlist_catlist.js │ ├── playlist_detail.js │ ├── playlist_hot.js │ ├── playlist_tracks.js │ ├── program_recommend.js │ ├── recommend_dislike.js │ ├── recommend_resource.js │ ├── recommend_songs.js │ ├── resource_like.js │ ├── search.js │ ├── search_multimatch.js │ ├── search_suggest.js │ ├── simi_artists.js │ ├── simi_mv.js │ ├── simi_playlist.js │ ├── simi_song.js │ ├── simi_user.js │ ├── song_detail.js │ ├── top_album.js │ ├── top_artists.js │ ├── top_list.js │ ├── top_mv.js │ ├── top_playlist.js │ ├── top_playlist_highquality.js │ ├── top_songs.js │ ├── toplist.js │ ├── toplist_artist.js │ ├── toplist_detail.js │ ├── user_audio.js │ ├── user_cloud.js │ ├── user_cloud_search.js │ ├── user_detail.js │ ├── user_dj.js │ ├── user_event.js │ ├── user_followeds.js │ ├── user_follows.js │ ├── user_playlist.js │ ├── user_playrecord.js │ └── user_subcount.js ├── static │ ├── artist_album.png │ ├── artists.png │ ├── banner.png │ ├── comment.png │ ├── docs.png │ ├── fm_trash.png │ ├── like.png │ ├── likeSuccess.png │ ├── mv.png │ ├── new_albums.png │ ├── personal_fm.png │ ├── play_mv.png │ ├── screenshot1.png │ ├── screenshot2.png │ ├── signinError.png │ ├── signinSuccess.png │ ├── songDetail.png │ ├── top_artists.png │ ├── top_list.png │ ├── top_playlist.png │ ├── 专辑.png │ ├── 推荐歌单.png │ ├── 推荐歌曲.png │ ├── 搜索.png │ ├── 歌单详情.png │ ├── 歌词.png │ ├── 用户歌单.png │ ├── 登录.png │ └── 音乐 url.png ├── test │ ├── album.test.js │ ├── comment.test.js │ ├── login.test.js │ ├── lyric.test.js │ ├── musicUrl.test.js │ └── search.test.js ├── util │ ├── crypto.js │ └── util.js └── yarn.lock ├── README.md ├── config ├── index.js ├── webpack.config.base.js ├── webpack.config.dev.js ├── webpack.config.electron.js └── webpack.config.production.js ├── main.js ├── package.json ├── publish.sh ├── resource ├── 128x128.png ├── 16x16.png ├── 24x24.png ├── 256x256.png ├── 32x32.png ├── 48x48.png ├── 64x64.png ├── 96x96.png ├── dock.icns ├── xbyjMusic.ico └── xbyjMusic.sketch ├── screenshots ├── 1.首页.gif ├── 10.歌单主页.png ├── 11.搜索页.png ├── 12.偏好设置.png ├── 13.公众号.jpg ├── 14.微信群.png ├── 15.支付宝.jpg ├── 16.微信支付.png ├── 2.每日推荐.png ├── 3.私人FM.png ├── 4.歌曲页.gif ├── 5.歌单页.gif ├── 6.歌手页.gif ├── 7.用户页.png ├── 8.加载页.png └── 9.排行榜.png ├── server ├── api.js ├── api │ ├── follow.js │ ├── hot_album.js │ ├── login.js │ ├── loginCellphone.js │ ├── login_refresh.js │ ├── scrobble.js │ ├── sub.js │ ├── subscribe.js │ ├── thumbsup.js │ ├── unfollow.js │ ├── unsub.js │ └── unsubscribe.js ├── dev.js ├── router │ ├── artist.js │ ├── comments.js │ ├── dailyplayer.js │ ├── fm.js │ ├── home.js │ ├── lyrics.js │ ├── player.js │ ├── playlist.js │ ├── search.js │ ├── top.js │ └── user.js └── search │ ├── Baidu.js │ ├── Kugou.js │ ├── QQ.js │ ├── Xiami.js │ ├── index.js │ └── test.js └── src ├── app.js ├── assets ├── WeChat.jpg ├── WeChatGroup.png ├── close-white.png ├── close.png ├── dock.png ├── github.png ├── loading.gif ├── notplaying.png └── playing.png ├── globalCss └── global.css ├── index.html ├── js ├── components │ ├── AboutMe │ │ ├── classes.js │ │ └── index.js │ ├── AudioPlayer │ │ └── index.js │ ├── Carousel │ │ └── index.js │ ├── Comments │ │ ├── classes.js │ │ └── index.js │ ├── Controller │ │ ├── classes.js │ │ └── index.js │ ├── Header │ │ ├── classes.js │ │ └── index.js │ ├── LeftMenu │ │ ├── classes.js │ │ └── index.js │ ├── Lyrics │ │ ├── classes.js │ │ └── index.js │ ├── Playing │ │ ├── classes.js │ │ └── index.js │ ├── Ripple │ │ ├── PlayerMode.js │ │ ├── PlayerNavigation.js │ │ ├── PlayerStatus.js │ │ ├── VolumeUpDown.js │ │ └── classes.js │ ├── Search │ │ ├── classes.js │ │ └── index.js │ └── SyncCarousel │ │ ├── classes.js │ │ └── index.js ├── pages │ ├── Artist │ │ ├── classes.js │ │ └── index.js │ ├── FM │ │ ├── classes.js │ │ └── index.js │ ├── Home │ │ ├── classes.js │ │ └── index.js │ ├── Layout.js │ ├── Login │ │ ├── classes.js │ │ └── index.js │ ├── Player │ │ ├── Search │ │ │ ├── classes.js │ │ │ └── index.js │ │ ├── classes.js │ │ └── index.js │ ├── Playlist │ │ ├── classes.js │ │ └── index.js │ ├── Preferences │ │ ├── classes.js │ │ └── index.js │ ├── Song │ │ ├── classes.js │ │ └── index.js │ ├── Top │ │ ├── classes.js │ │ └── index.js │ ├── User │ │ ├── classes.js │ │ └── index.js │ └── dailyPlayer │ │ ├── classes.js │ │ └── index.js ├── routes.js ├── stores │ ├── aboutme.js │ ├── artist.js │ ├── comments.js │ ├── controller.js │ ├── dailyplayer.js │ ├── fm.js │ ├── home.js │ ├── index.js │ ├── lyrics.js │ ├── me.js │ ├── player.js │ ├── playing.js │ ├── playlist.js │ ├── preferences.js │ ├── search.js │ ├── top.js │ └── user.js ├── ui │ ├── FadeImage │ │ ├── classes.js │ │ └── index.js │ ├── Loader │ │ ├── classes.js │ │ └── index.js │ ├── Offline │ │ ├── classes.js │ │ └── index.js │ ├── ProgressImage │ │ ├── classes.js │ │ └── index.js │ └── Switch │ │ ├── classes.js │ │ └── index.js └── utils │ ├── albumColors.js │ ├── colors.js │ ├── helper.js │ ├── lastfm.js │ ├── sine.js │ ├── storage.js │ └── wave.js └── theme.js /.babelrc: -------------------------------------------------------------------------------- 1 | // NOTE: These options are overriden by the babel-loader configuration 2 | // for webpack, which can be found in ~/build/webpack.config. 3 | // 4 | // Why? The react-transform-hmr plugin depends on HMR (and throws if 5 | // module.hot is disabled), so keeping it and related plugins contained 6 | // within webpack helps prevent unexpected errors. 7 | { 8 | "presets": ['es2015', "react", "stage-0"], 9 | "plugins": ["babel-polyfill", "transform-decorators-legacy"], 10 | "env": { 11 | "production": { 12 | "presets": ["react-optimize"] 13 | }, 14 | "development": { 15 | "presets": ["react-hmre"] 16 | }, 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /.ctrlpignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | ios 3 | DS_Store 4 | android 5 | git 6 | dist 7 | release 8 | __tests__ 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | insert_final_newnewline = true 11 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | 2 | dist/* 3 | __tests__/* 4 | src/assets/* 5 | server/api/* 6 | NeteaseCloudMusicApi/* 7 | 8 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 6, 4 | "sourceType": "module", 5 | "ecmaFeatures": { 6 | "impliedStrict": true, 7 | "jsx": true 8 | } 9 | }, 10 | "env": { 11 | "es6": true, 12 | "node": true 13 | }, 14 | "parser": "babel-eslint", 15 | "plugins": ["react"], 16 | "rules": { 17 | "semi": [2, "always"], 18 | "new-cap": [0], 19 | "prefer-promise-reject-errors": [0], 20 | "indent": [2, 4, { "SwitchCase": 1 }], 21 | "comma-dangle": [2, "only-multiline"], 22 | "space-before-function-paren": [2, "never"], 23 | "operator-linebreak": [2, "before"], 24 | "no-floating-decimal": [0], 25 | "react/jsx-indent": [2, 4], 26 | "react/jsx-indent-props": [2, 4], 27 | "react/jsx-boolean-value": [2, "always"], 28 | "react/prop-types": [0], 29 | "jsx-quotes": [2, "prefer-double"] 30 | }, 31 | "extends": ["standard", "standard-react"] 32 | } 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Other 6 | # 7 | dist/ 8 | release/ 9 | package-lock.json 10 | 11 | # Xcode 12 | # 13 | build/ 14 | *.pbxuser 15 | !default.pbxuser 16 | *.mode1v3 17 | !default.mode1v3 18 | *.mode2v3 19 | !default.mode2v3 20 | *.perspectivev3 21 | !default.perspectivev3 22 | xcuserdata 23 | *.xccheckout 24 | *.moved-aside 25 | DerivedData 26 | *.hmap 27 | *.ipa 28 | *.xcuserstate 29 | project.xcworkspace 30 | 31 | # Android/IntelliJ 32 | # 33 | build/ 34 | .idea 35 | .gradle 36 | local.properties 37 | *.iml 38 | 39 | # node.js 40 | # 41 | node_modules/ 42 | npm-debug.log 43 | 44 | # BUCK 45 | buck-out/ 46 | \.buckd/ 47 | android/app/libs 48 | *.keystore 49 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "NeteaseCloudMusicApi"] 2 | path = NeteaseCloudMusicApi 3 | url = https://github.com/trazyn/NeteaseCloudMusicApi 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | 5 | node_js: 6 | - 8 7 | - 7 8 | - 6 9 | 10 | script: npm run lint && npm run build 11 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | *.log -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/.npmignore: -------------------------------------------------------------------------------- 1 | static 2 | docs 3 | node_modules -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | - repo: https://github.com/Binaryify/NeteaseCloudMusicApi 2 | sha: '' # Use the sha or tag you want to point at 3 | hooks: 4 | - id: prettier -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - 6.2 5 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/CHANGELOG.MD: -------------------------------------------------------------------------------- 1 | # 更新日志 2 | ### 2.7.2 | 2017.9.7 3 | 修复搜索接口 offset 参数失效问题 4 | 5 | ### 2.7.0 | 2017.8.21 6 | 优化刷新登录代码 7 | 8 | ### 2.6.5 | 2017.7.16 9 | 优化 CORS 设置 10 | 11 | ### 2.6.4 | 2017.7.16 12 | 添加缓存机制和随机 UA 机制 感谢[@u3u](https://github.com/u3u) 13 | [issue:77](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/77) 14 | 优化请求代码 感谢 [@huhuime](https://github.com/huhuime) 15 | [issue:83](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/83) 16 | 17 | ### 2.6.2 | 2017.7.16 18 | 修复垃圾桶接口 19 | 20 | ### 2.6.1 | 2017.7.16 21 | 修复红心接口 22 | 23 | ### 2.6.0 | 2017.6.25 24 | 修复签到接口 25 | 26 | ### 2.5.9 | 2017.6.14 27 | 增加启动说明页 28 | 29 | ### 2.5.8 | 2017.6.1 30 | 修复若干细节问题 31 | 32 | ### 2.5.7 | 2017.5.22 33 | 修复若干问题 34 | 35 | ### 2.5.6 | 2017.5.14 36 | 增加动态消息接口 37 | 38 | ### 2.5.5 | 2017.5.10 39 | 修复 mv 排行榜接口崩溃问题 40 | 41 | ### 2.5.4 | 2017.5.5 42 | 新增点赞接口,更新文档 43 | 44 | ### 2.5.3 | 2017.5.2 45 | 修复歌手单曲数据空白问题和文档获取歌手单曲url 描述问题,更新文档 46 | 47 | ### 2.5.0 | 2017.4.29 48 | 增加 mv/专辑/歌单评论接口,增加云盘相关接口,增加获取用户动态/信息接口,增加关注/粉丝列表接口,增加收藏歌单接口,增加相似 mv/歌曲/用户接口,增加 banner 接口,增加刷新登录接口,增加电台相关接口,补充评论接口,更新文档 49 | 50 | ### 2.4.6 | 2017.4.21 51 | 增加播放 mv 接口,更新文档 52 | 53 | ### 2.4.5 | 2017.4.20 54 | 增加歌手专辑,歌手单曲等接口,修复/album 接口描述错误,更新文档 55 | 56 | ### 2.4.0 | 2017.4.20 57 | 增加歌单(网友精选碟),新碟上架,热门歌手等接口,更新文档 58 | 59 | ### 2.3.4 | 2017.4.20 60 | 增加歌曲详情接口,更新文档 61 | 62 | ### 2.3.0 | 2017.4.15 63 | 增加排行榜接口,更新文档 64 | 65 | ### 2.2.0 |2017.4.14 66 | 增加私人 FM, 喜欢歌曲,垃圾桶,每日签到等接口,更新文档 67 | 68 | ### 2.1.3 | 2017.4.6 69 | 改善文档 70 | 71 | ### 2.1.0 | 2017.4.6 72 | 增加获取评论接口以及对应单元测试,增加更新日志 73 | 74 | ### 2.0.0 | 2017.4.1 75 | 版本升级到 2.0.增加使用文档,完成项目重构,增加更完善的单元测试,升级 api 到 v2+,支持登录并获取用户信息和创建的歌单,可通过获取音乐 url 接口获取用户歌单里的的音乐,获取每日推荐歌单和每日推荐音乐 76 | 77 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2016 Binaryify 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/docs/.nojekyll -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/docs/_coverpage.md: -------------------------------------------------------------------------------- 1 | # 网易云音乐 API 2 | 3 | > 网易云音乐 NodeJS 版 API 4 | 5 | - 全部接口已升级到最新 6 | - 具备登录接口 7 | - 更完善的文档 8 | 9 | 10 | [GitHub](https://github.com/Binaryify/NeteaseCloudMusicApi) 11 | [Get Started](#neteasecloudmusicapi) 12 | 13 | ![color](#ffffff) -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/docs/favicon.ico -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/docs/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/docs/icon.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 网易云音乐 NodeJS 版 API 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 23 | 24 | 29 | 30 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/issue_template.md: -------------------------------------------------------------------------------- 1 | ### 环境 2 | 系统: 3 | 4 | nodejs 版本: 5 | 6 | ### 出现问题 7 | 8 | ### 重现步骤 9 | 10 | ### 期待效果 -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "NeteaseCloudMusicApi", 3 | "version": "2.7.2", 4 | "description": "网易云音乐 NodeJS 版 API", 5 | "scripts": { 6 | "start": "node app.js", 7 | "test": "mocha -r intelli-espower-loader -t 20000 test", 8 | "precommit": "lint-staged" 9 | }, 10 | "lint-staged": { 11 | "*.js": [ 12 | "prettier --write", 13 | "git add" 14 | ] 15 | }, 16 | "keywords": [ 17 | "网易云音乐", 18 | "网易云", 19 | "音乐", 20 | "网易云音乐nodejs" 21 | ], 22 | "author": "", 23 | "license": "MIT", 24 | "dependencies": { 25 | "apicache": "^0.9.0", 26 | "big-integer": "^1.6.17", 27 | "express": "^4.15.2", 28 | "request": "^2.81.0" 29 | }, 30 | "devDependencies": { 31 | "husky": "^0.14.3", 32 | "intelli-espower-loader": "^1.0.1", 33 | "lint-staged": "^4.0.3", 34 | "mocha": "^3.2.0", 35 | "power-assert": "^1.4.2", 36 | "prettier": "^1.5.3" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 网易云音乐 API 8 | 9 | 10 |

网易云音乐 API

11 | 当你看到这个页面时,这个服务已经成功跑起来了~ 12 | 查看文档 13 |

例子:

14 | 19 | 47 | 48 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/album.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | csrf_token: '' 9 | } 10 | const id = req.query.id 11 | createWebAPIRequest( 12 | 'music.163.com', 13 | `/weapi/v1/album/${id}`, 14 | 'POST', 15 | data, 16 | cookie, 17 | music_req => { 18 | res.send(music_req) 19 | }, 20 | err => res.status(502).send('fetch error') 21 | ) 22 | }) 23 | 24 | module.exports = router 25 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/artist_album.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const id = req.query.id 8 | const data = { 9 | offset: req.query.offset || 0, 10 | total: true, 11 | limit: req.query.limit || 30, 12 | csrf_token: '' 13 | } 14 | createWebAPIRequest( 15 | 'music.163.com', 16 | `/weapi/artist/albums/${id}`, 17 | 'POST', 18 | data, 19 | cookie, 20 | music_req => res.send(music_req), 21 | err => res.status(502).send('fetch error') 22 | ) 23 | }) 24 | 25 | module.exports = router 26 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/artists.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | csrf_token: '' 9 | } 10 | const id = req.query.id 11 | const offset = req.query.offset || 0 12 | const limit = req.query.limit || 50 13 | createWebAPIRequest( 14 | 'music.163.com', 15 | `/weapi/v1/artist/${id}?offset=${offset}&limit=${limit}`, 16 | 'POST', 17 | data, 18 | cookie, 19 | music_req => res.send(music_req), 20 | err => res.status(502).send('fetch error') 21 | ) 22 | }) 23 | 24 | module.exports = router 25 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/artists_desc.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const id = req.query.id 8 | const data = { 9 | id, 10 | csrf_token: '' 11 | } 12 | createWebAPIRequest( 13 | 'music.163.com', 14 | `/weapi/artist/introduction`, 15 | 'POST', 16 | data, 17 | cookie, 18 | music_req => res.send(music_req), 19 | err => res.status(502).send('fetch error') 20 | ) 21 | }) 22 | 23 | module.exports = router 24 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/artists_mv.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const id = req.query.id 8 | const data = { 9 | artistId: id, 10 | total: true, 11 | offset: req.query.offset, 12 | limit: req.query.limit, 13 | csrf_token: '' 14 | } 15 | createWebAPIRequest( 16 | 'music.163.com', 17 | `/weapi/artist/mvs`, 18 | 'POST', 19 | data, 20 | cookie, 21 | music_req => res.send(music_req), 22 | err => res.status(502).send('fetch error') 23 | ) 24 | }) 25 | 26 | module.exports = router 27 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/banner.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = {} 8 | createWebAPIRequest( 9 | 'music.163.com', 10 | '/weapi/v2/banner/get', 11 | 'POST', 12 | data, 13 | cookie, 14 | music_req => { 15 | res.send(music_req) 16 | }, 17 | err => res.status(502).send('fetch error') 18 | ) 19 | }) 20 | 21 | module.exports = router 22 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/check_music.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const request = require('request') 4 | const { createWebAPIRequest } = require('../util/util') 5 | router.get('/', (req, res) => { 6 | const id = parseInt(req.query.id) 7 | const br = parseInt(req.query.br || 999000) 8 | const data = { 9 | ids: [id], 10 | br: br, 11 | csrf_token: '' 12 | } 13 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 14 | 15 | createWebAPIRequest( 16 | 'music.163.com', 17 | '/weapi/song/enhance/player/url', 18 | 'POST', 19 | data, 20 | cookie, 21 | music_req => { 22 | if (JSON.parse(music_req).code == 200) { 23 | return res.send({ success: true, message: 'ok' }) 24 | } 25 | return res.send({ success: false, message: '亲爱的,暂无版权' }) 26 | }, 27 | err => { 28 | res.status(502).send('fetch error') 29 | } 30 | ) 31 | }) 32 | 33 | module.exports = router 34 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/comment_album.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const rid = req.query.id 7 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 8 | const data = { 9 | offset: req.query.offset || 0, 10 | rid: rid, 11 | limit: req.query.limit || 20, 12 | csrf_token: '' 13 | } 14 | createWebAPIRequest( 15 | 'music.163.com', 16 | `/weapi/v1/resource/comments/R_AL_3_${rid}/?csrf_token=`, 17 | 'POST', 18 | data, 19 | cookie, 20 | music_req => { 21 | res.send(music_req) 22 | }, 23 | err => res.status(502).send('fetch error') 24 | ) 25 | }) 26 | 27 | module.exports = router 28 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/comment_dj.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const rid = req.query.id 7 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 8 | const data = { 9 | offset: req.query.offset || 0, 10 | rid: rid, 11 | limit: req.query.limit || 20, 12 | csrf_token: '' 13 | } 14 | createWebAPIRequest( 15 | 'music.163.com', 16 | `/weapi/v1/resource/comments/A_DJ_1_${rid}/?csrf_token=`, 17 | 'POST', 18 | data, 19 | cookie, 20 | music_req => { 21 | res.send(music_req) 22 | }, 23 | err => res.status(502).send('fetch error') 24 | ) 25 | }) 26 | 27 | module.exports = router 28 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/comment_like.js: -------------------------------------------------------------------------------- 1 | //comment like 2 | const express = require('express') 3 | const router = express() 4 | const { createWebAPIRequest } = require('../util/util') 5 | 6 | router.get('/', (req, res) => { 7 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 8 | const cid = req.query.cid //评论 id 9 | const id = req.query.id // 歌曲 id 10 | const typeMap = { 11 | 0: 'R_SO_4_', //歌曲 12 | 1: 'R_MV_5_', //mv 13 | 2: 'A_PL_0_', //歌单 14 | 3: 'R_AL_3_', //专辑 15 | 4: 'A_DJ_1_' //电台 16 | } 17 | const type = typeMap[req.query.type] 18 | const data = { 19 | threadId: `${type}${id}`, 20 | commentId: cid, 21 | csrf_token: '' 22 | } 23 | const action = req.query.t == 1 ? 'like' : 'unlike' 24 | 25 | const url = `/weapi/v1/comment/${action}` 26 | createWebAPIRequest( 27 | 'music.163.com', 28 | url, 29 | 'POST', 30 | data, 31 | cookie, 32 | music_req => res.send(music_req), 33 | err => res.status(502).send('fetch error') 34 | ) 35 | }) 36 | 37 | module.exports = router 38 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/comment_music.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const rid = req.query.id 7 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 8 | const data = { 9 | offset: req.query.offset || 0, 10 | rid: rid, 11 | limit: req.query.limit || 20, 12 | csrf_token: '' 13 | } 14 | createWebAPIRequest( 15 | 'music.163.com', 16 | `/weapi/v1/resource/comments/R_SO_4_${rid}/?csrf_token=`, 17 | 'POST', 18 | data, 19 | cookie, 20 | music_req => { 21 | res.send(music_req) 22 | }, 23 | err => res.status(502).send('fetch error') 24 | ) 25 | }) 26 | 27 | module.exports = router 28 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/comment_mv.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const rid = req.query.id 7 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 8 | const data = { 9 | offset: req.query.offset || 0, 10 | rid: rid, 11 | limit: req.query.limit || 20, 12 | csrf_token: '' 13 | } 14 | createWebAPIRequest( 15 | 'music.163.com', 16 | `/weapi/v1/resource/comments/R_MV_5_${rid}/?csrf_token=`, 17 | 'POST', 18 | data, 19 | cookie, 20 | music_req => { 21 | res.send(music_req) 22 | }, 23 | err => res.status(502).send('fetch error') 24 | ) 25 | }) 26 | 27 | module.exports = router 28 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/comment_playlist.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const rid = req.query.id 7 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 8 | const data = { 9 | offset: req.query.offset || 0, 10 | rid: rid, 11 | limit: req.query.limit || 20, 12 | csrf_token: '' 13 | } 14 | createWebAPIRequest( 15 | 'music.163.com', 16 | `/weapi/v1/resource/comments/A_PL_0_${rid}/?csrf_token=`, 17 | 'POST', 18 | data, 19 | cookie, 20 | music_req => { 21 | res.send(music_req) 22 | }, 23 | err => res.status(502).send('fetch error') 24 | ) 25 | }) 26 | 27 | module.exports = router 28 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/daily_signin.js: -------------------------------------------------------------------------------- 1 | // 签到 2 | const express = require('express') 3 | const router = express() 4 | const { createWebAPIRequest } = require('../util/util') 5 | 6 | router.get('/', (req, res) => { 7 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 8 | let type = req.query.type || 0 //0为安卓端签到 3点经验,1为网页签到,2点经验 9 | const data = { 10 | csrf_token: '', 11 | type 12 | } 13 | // {'android': {'point': 3, 'code': 200}, 'web': {'point': 2, 'code': 200}} 14 | // {'android': {'code': -2, 'msg': '重复签到'}, 'web': {'code': -2, 'msg': '重复签到'}} 15 | // 'android': {'code': 301}, 'web': {'code': 301}} 16 | 17 | createWebAPIRequest( 18 | 'music.163.com', 19 | '/weapi/point/dailyTask', 20 | 'POST', 21 | data, 22 | cookie, 23 | music_req => res.send(music_req), 24 | err => res.status(502).send('fetch error') 25 | ) 26 | }) 27 | 28 | module.exports = router 29 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/dj_catelist.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | csrf_token: '' 9 | } 10 | createWebAPIRequest( 11 | 'music.163.com', 12 | '/weapi/djradio/category/get', 13 | 'POST', 14 | data, 15 | cookie, 16 | music_req => { 17 | res.send(music_req) 18 | }, 19 | err => res.status(502).send('fetch error') 20 | ) 21 | }) 22 | 23 | module.exports = router 24 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/dj_detail.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const rid = req.query.rid 7 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 8 | const data = { 9 | id: rid, 10 | csrf_token: '' 11 | } 12 | createWebAPIRequest( 13 | 'music.163.com', 14 | '/weapi/djradio/get', 15 | 'POST', 16 | data, 17 | cookie, 18 | music_req => { 19 | res.send(music_req) 20 | }, 21 | err => res.status(502).send('fetch error') 22 | ) 23 | }) 24 | 25 | module.exports = router 26 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/dj_hot.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | cat: req.query.type, 9 | cateId: req.query.type, 10 | type: req.query.type, 11 | categoryId: req.query.type, 12 | category: req.query.type, 13 | limit: req.query.limit, 14 | offset: req.query.offset, 15 | csrf_token: '' 16 | } 17 | createWebAPIRequest( 18 | 'music.163.com', 19 | '/weapi/djradio/hot/v1', 20 | 'POST', 21 | data, 22 | cookie, 23 | music_req => { 24 | res.send(music_req) 25 | }, 26 | err => res.status(502).send('fetch error') 27 | ) 28 | }) 29 | 30 | module.exports = router 31 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/dj_program.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const rid = req.query.rid 7 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 8 | const data = { 9 | asc: req.query.asc, 10 | radioId: rid, 11 | limit: req.query.limit, 12 | offset: req.query.offset, 13 | csrf_token: '' 14 | } 15 | createWebAPIRequest( 16 | 'music.163.com', 17 | '/weapi/dj/program/byradio', 18 | 'POST', 19 | data, 20 | cookie, 21 | music_req => { 22 | res.send(music_req) 23 | }, 24 | err => res.status(502).send('fetch error') 25 | ) 26 | }) 27 | 28 | module.exports = router 29 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/dj_program_detail.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | id: req.query.id, 9 | csrf_token: '' 10 | } 11 | createWebAPIRequest( 12 | 'music.163.com', 13 | '/weapi/dj/program/detail', 14 | 'POST', 15 | data, 16 | cookie, 17 | music_req => { 18 | res.send(music_req) 19 | }, 20 | err => res.status(502).send('fetch error') 21 | ) 22 | }) 23 | 24 | module.exports = router 25 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/dj_recommend.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | csrf_token: '' 9 | } 10 | createWebAPIRequest( 11 | 'music.163.com', 12 | '/weapi/djradio/recommend/v1', 13 | 'POST', 14 | data, 15 | cookie, 16 | music_req => { 17 | res.send(music_req) 18 | }, 19 | err => res.status(502).send('fetch error') 20 | ) 21 | }) 22 | 23 | module.exports = router 24 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/dj_recommend_type.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | cateId: req.query.type, 9 | csrf_token: '' 10 | } 11 | createWebAPIRequest( 12 | 'music.163.com', 13 | '/weapi/djradio/recommend', 14 | 'POST', 15 | data, 16 | cookie, 17 | music_req => { 18 | res.send(music_req) 19 | }, 20 | err => res.status(502).send('fetch error') 21 | ) 22 | }) 23 | 24 | module.exports = router 25 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/dj_sub.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | id: req.query.rid, 9 | csrf_token: '' 10 | } 11 | const action = req.query.t == 1 ? 'sub' : 'unsub' 12 | createWebAPIRequest( 13 | 'music.163.com', 14 | `/weapi/djradio/${action}`, 15 | 'POST', 16 | data, 17 | cookie, 18 | music_req => { 19 | res.send(music_req) 20 | }, 21 | err => res.status(502).send('fetch error') 22 | ) 23 | }) 24 | 25 | module.exports = router 26 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/event.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | csrf_token: '' 9 | } 10 | createWebAPIRequest( 11 | 'music.163.com', 12 | '/weapi/v1/event/get', 13 | 'POST', 14 | data, 15 | cookie, 16 | music_req => res.send(music_req), 17 | err => res.status(502).send('fetch error') 18 | ) 19 | }) 20 | 21 | module.exports = router 22 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/fm_trash.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const songId = req.query.id 8 | const alg = 'RT' 9 | const time = req.query.time || 25 10 | const data = { 11 | csrf_token: '', 12 | songId 13 | } 14 | 15 | createWebAPIRequest( 16 | 'music.163.com', 17 | `/weapi/radio/trash/add?alg=${alg}&songId=${songId}&time=${time}`, 18 | 'POST', 19 | data, 20 | cookie, 21 | music_req => res.send(music_req), 22 | err => res.status(502).send('fetch error') 23 | ) 24 | }) 25 | 26 | module.exports = router 27 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/follow.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | csrf_token: '' 9 | } 10 | const url = req.query.type == 'add' ? 'follow' : 'delfollow' 11 | const id = req.query.id 12 | createWebAPIRequest( 13 | 'music.163.com', 14 | `/weapi/user/${url}/${id}`, 15 | 'POST', 16 | data, 17 | cookie, 18 | music_req => { 19 | res.send(music_req) 20 | }, 21 | err => res.status(502).send('fetch error') 22 | ) 23 | }) 24 | 25 | module.exports = router 26 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/like.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const trackId = req.query.id 8 | const like = req.query.like || true 9 | const alg = req.query.alg || 'itembased' 10 | const time = req.query.time || 25 11 | const data = { 12 | csrf_token: '', 13 | trackId, 14 | like 15 | } 16 | createWebAPIRequest( 17 | 'music.163.com', 18 | `/weapi/radio/like?alg=${alg}&trackId=${trackId}&like=${like}&time=${time}`, 19 | 'POST', 20 | data, 21 | cookie, 22 | music_req => res.send(music_req), 23 | err => res.status(502).send('fetch error') 24 | ) 25 | }) 26 | 27 | module.exports = router 28 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/likelist.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | uid: req.query.uid, 9 | csrf_token: '' 10 | } 11 | createWebAPIRequest( 12 | 'music.163.com', 13 | `/weapi/song/like/get`, 14 | 'POST', 15 | data, 16 | cookie, 17 | music_req => { 18 | res.send(music_req) 19 | }, 20 | err => res.status(502).send('fetch error') 21 | ) 22 | }) 23 | 24 | module.exports = router 25 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/logWeb.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | csrf_token: '' 9 | } 10 | 11 | createWebAPIRequest( 12 | 'music.163.com', 13 | '/weapi/feedback/weblog', 14 | 'POST', 15 | data, 16 | cookie, 17 | music_req => res.send(music_req), 18 | err => res.status(502).send('fetch error') 19 | ) 20 | }) 21 | 22 | module.exports = router 23 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/login.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const crypto = require('crypto') 3 | const router = express() 4 | const { createWebAPIRequest } = require('../util/util') 5 | 6 | router.get('/', (req, res) => { 7 | const email = req.query.email 8 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 9 | const md5sum = crypto.createHash('md5') 10 | md5sum.update(req.query.password) 11 | const data = { 12 | username: email, 13 | password: md5sum.digest('hex'), 14 | rememberLogin: 'true', 15 | clientToken: 16 | '1_jVUMqWEPke0/1/Vu56xCmJpo5vP1grjn_SOVVDzOc78w8OKLVZ2JH7IfkjSXqgfmh' 17 | } 18 | console.log(email, req.query.password) 19 | 20 | createWebAPIRequest( 21 | 'music.163.com', 22 | '/weapi/login?csrf_token=', 23 | 'POST', 24 | data, 25 | cookie, 26 | (music_req, cookie) => { 27 | // console.log(music_req) 28 | res.set({ 29 | 'Set-Cookie': cookie 30 | }) 31 | res.send(music_req) 32 | }, 33 | err => res.status(502).send('fetch error') 34 | ) 35 | }) 36 | 37 | module.exports = router 38 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/loginCellphone.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const crypto = require('crypto') 3 | const router = express() 4 | const { createWebAPIRequest } = require('../util/util') 5 | 6 | router.get('/', (req, res) => { 7 | const phone = req.query.phone 8 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 9 | const md5sum = crypto.createHash('md5') 10 | md5sum.update(req.query.password) 11 | const data = { 12 | phone: phone, 13 | password: md5sum.digest('hex'), 14 | rememberLogin: 'true' 15 | } 16 | createWebAPIRequest( 17 | 'music.163.com', 18 | '/weapi/login/cellphone', 19 | 'POST', 20 | data, 21 | cookie, 22 | (music_req, cookie) => { 23 | // console.log(music_req) 24 | res.set({ 25 | 'Set-Cookie': cookie 26 | }) 27 | res.send(music_req) 28 | }, 29 | err => res.status(502).send('fetch error') 30 | ) 31 | }) 32 | 33 | module.exports = router 34 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/login_refresh.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | csrf_token: '' 9 | } 10 | console.log({ cookie }) 11 | createWebAPIRequest( 12 | 'music.163.com', 13 | `/weapi/login/token/refresh`, 14 | 'POST', 15 | data, 16 | cookie, 17 | (music_req, cookie) => { 18 | console.log({ cookie }) 19 | res.set({ 20 | 'Set-Cookie': cookie 21 | }) 22 | res.send(music_req) 23 | }, 24 | err => res.status(502).send('fetch error') 25 | ) 26 | }) 27 | 28 | module.exports = router 29 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/lyric.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = {} 8 | const id = req.query.id 9 | createWebAPIRequest( 10 | 'music.163.com', 11 | '/weapi/song/lyric?os=osx&id=' + id + '&lv=-1&kv=-1&tv=-1', 12 | 'POST', 13 | data, 14 | cookie, 15 | music_req => { 16 | res.send(music_req) 17 | }, 18 | err => res.status(502).send('fetch error') 19 | ) 20 | }) 21 | 22 | module.exports = router 23 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/musicUrl.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const id = req.query.id 7 | const br = req.query.br || 999000 8 | const data = { 9 | ids: [id], 10 | br: br, 11 | csrf_token: '' 12 | } 13 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 14 | 15 | createWebAPIRequest( 16 | 'music.163.com', 17 | '/weapi/song/enhance/player/url', 18 | 'POST', 19 | data, 20 | cookie, 21 | music_req => { 22 | res.setHeader('Content-Type', 'application/json') 23 | res.send(music_req) 24 | }, 25 | err => { 26 | res.status(502).send('fetch error') 27 | } 28 | ) 29 | }) 30 | 31 | module.exports = router 32 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/mv.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const mvid = req.query.mvid 8 | const data = { 9 | id: mvid 10 | } 11 | 12 | createWebAPIRequest( 13 | 'music.163.com', 14 | `/weapi/mv/detail`, 15 | 'POST', 16 | data, 17 | cookie, 18 | music_req => { 19 | res.send(music_req) 20 | }, 21 | err => res.status(502).send('fetch error') 22 | ) 23 | }) 24 | 25 | module.exports = router 26 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/mv_first.js: -------------------------------------------------------------------------------- 1 | //最新mv 2 | const express = require('express') 3 | const router = express() 4 | const { createWebAPIRequest } = require('../util/util') 5 | 6 | // type ALL, ZH,EA,KR,JP 7 | router.get('/', (req, res) => { 8 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 9 | const data = { 10 | // 'offset': req.query.offset || 0, 11 | total: true, 12 | limit: req.query.limit || 30, 13 | csrf_token: '' 14 | } 15 | createWebAPIRequest( 16 | 'music.163.com', 17 | '/weapi/mv/first', 18 | 'POST', 19 | data, 20 | cookie, 21 | music_req => res.send(music_req), 22 | err => res.status(502).send('fetch error') 23 | ) 24 | }) 25 | 26 | module.exports = router 27 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/mv_url.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const request = require('request') 4 | 5 | router.get('/', (req, res) => { 6 | const url = req.query.url 7 | const headers = { 8 | Referer: 'http://music.163.com/', 9 | Cookie: 'appver=1.5.0.75771;', 10 | 'Content-Type': 'video/mp4', 11 | Location: url 12 | } 13 | const options = { 14 | header: headers, 15 | url: url 16 | } 17 | request(options) 18 | .on('error', err => { 19 | res.send({ err }) 20 | }) 21 | .pipe(res) 22 | }) 23 | 24 | module.exports = router 25 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/personal_fm.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | csrf_token: '' 9 | } 10 | createWebAPIRequest( 11 | 'music.163.com', 12 | '/weapi/v1/radio/get', 13 | 'POST', 14 | data, 15 | cookie, 16 | music_req => res.send(music_req), 17 | err => res.status(502).send('fetch error') 18 | ) 19 | }) 20 | 21 | module.exports = router 22 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/personalized.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = {} 8 | createWebAPIRequest( 9 | 'music.163.com', 10 | '/weapi/personalized/playlist', 11 | 'POST', 12 | data, 13 | cookie, 14 | music_req => { 15 | res.send(music_req) 16 | }, 17 | err => res.status(502).send('fetch error') 18 | ) 19 | }) 20 | 21 | module.exports = router 22 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/personalized_djprogram.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = {} 8 | createWebAPIRequest( 9 | 'music.163.com', 10 | '/weapi/personalized/djprogram', 11 | 'POST', 12 | data, 13 | cookie, 14 | music_req => { 15 | res.send(music_req) 16 | }, 17 | err => res.status(502).send('fetch error') 18 | ) 19 | }) 20 | 21 | module.exports = router 22 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/personalized_mv.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = {} 8 | createWebAPIRequest( 9 | 'music.163.com', 10 | '/weapi/personalized/mv', 11 | 'POST', 12 | data, 13 | cookie, 14 | music_req => { 15 | res.send(music_req) 16 | }, 17 | err => res.status(502).send('fetch error') 18 | ) 19 | }) 20 | 21 | module.exports = router 22 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/personalized_newsong.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | type: 'recommend' 9 | } 10 | createWebAPIRequest( 11 | 'music.163.com', 12 | '/weapi/personalized/newsong', 13 | 'POST', 14 | data, 15 | cookie, 16 | music_req => { 17 | res.send(music_req) 18 | }, 19 | err => res.status(502).send('fetch error') 20 | ) 21 | }) 22 | 23 | module.exports = router 24 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/personalized_privatecontent.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = {} 8 | createWebAPIRequest( 9 | 'music.163.com', 10 | '/weapi/personalized/privatecontent', 11 | 'POST', 12 | data, 13 | cookie, 14 | music_req => { 15 | res.send(music_req) 16 | }, 17 | err => res.status(502).send('fetch error') 18 | ) 19 | }) 20 | 21 | module.exports = router 22 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/playlist_catlist.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | csrf_token: '' 9 | } 10 | createWebAPIRequest( 11 | 'music.163.com', 12 | '/weapi/playlist/catalogue', 13 | 'POST', 14 | data, 15 | cookie, 16 | music_req => { 17 | res.send(music_req) 18 | }, 19 | err => res.status(502).send('fetch error') 20 | ) 21 | }) 22 | 23 | module.exports = router 24 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/playlist_detail.js: -------------------------------------------------------------------------------- 1 | const http = require('http') 2 | const express = require('express') 3 | const router = express() 4 | const { createWebAPIRequest } = require('../util/util') 5 | 6 | router.get('/', (req, res) => { 7 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 8 | let detail, imgurl 9 | const data = { 10 | id: req.query.id, 11 | offset: 0, 12 | total: true, 13 | limit: 1000, 14 | n: 1000, 15 | csrf_token: '' 16 | } 17 | 18 | createWebAPIRequest( 19 | 'music.163.com', 20 | '/weapi/v3/playlist/detail', 21 | 'POST', 22 | data, 23 | cookie, 24 | music_req => { 25 | console.log(music_req) 26 | // detail = music_req 27 | res.send(music_req) 28 | // mergeRes() 29 | }, 30 | err => { 31 | res.status(502).send('fetch error') 32 | } 33 | ) 34 | 35 | // FIXME:i dont know the api to get coverimgurl 36 | // so i get it by parsing html 37 | // const http_client = http.get({ 38 | // hostname: 'music.163.com', 39 | // path: '/playlist?id=' + req.query.id, 40 | // headers: { 41 | // 'Referer': 'http://music.163.com', 42 | // }, 43 | // }, function (res) { 44 | // res.setEncoding('utf8') 45 | // let html = '' 46 | // res.on('data', function (chunk) { 47 | // html += chunk 48 | // }) 49 | // res.on('end', function () { 50 | // console.log('end', html) 51 | // const regImgCover = /\ { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = {} 8 | createWebAPIRequest( 9 | 'music.163.com', 10 | '/weapi/playlist/hottags', 11 | 'POST', 12 | data, 13 | cookie, 14 | music_req => { 15 | res.send(music_req) 16 | }, 17 | err => res.status(502).send('fetch error') 18 | ) 19 | }) 20 | 21 | module.exports = router 22 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/playlist_tracks.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | //收藏单曲到歌单,从歌单删除歌曲 op=del,add;pid=歌单id,tracks=歌曲id 5 | router.get('/', (req, res) => { 6 | const op = req.query.op 7 | const pid = req.query.pid 8 | const tracks = req.query.tracks 9 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 10 | // console.log('COOKIESS', cookie) 11 | const data = { 12 | op: op, 13 | pid: pid, 14 | tracks: tracks, 15 | trackIds: JSON.stringify([tracks]), 16 | csrf_token: '' 17 | } 18 | createWebAPIRequest( 19 | 'music.163.com', 20 | '/weapi/playlist/manipulate/tracks', 21 | 'POST', 22 | data, 23 | cookie, 24 | music_req => res.send(music_req), 25 | err => res.status(502).send('fetch error') 26 | ) 27 | }) 28 | 29 | module.exports = router 30 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/program_recommend.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | cateId: req.query.type, 9 | csrf_token: '' 10 | } 11 | createWebAPIRequest( 12 | 'music.163.com', 13 | '/weapi/program/recommend/v1', 14 | 'POST', 15 | data, 16 | cookie, 17 | music_req => { 18 | res.send(music_req) 19 | }, 20 | err => res.status(502).send('fetch error') 21 | ) 22 | }) 23 | 24 | module.exports = router 25 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/recommend_dislike.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | csrf_token: '' 9 | } 10 | 11 | createWebAPIRequest( 12 | 'music.163.com', 13 | '/weapi/v1/radio/get', 14 | 'POST', 15 | data, 16 | cookie, 17 | music_req => res.send(music_req), 18 | err => res.status(502).send('fetch error') 19 | ) 20 | }) 21 | 22 | module.exports = router 23 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/recommend_resource.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | csrf_token: '' 9 | } 10 | 11 | createWebAPIRequest( 12 | 'music.163.com', 13 | '/weapi/v1/discovery/recommend/resource', 14 | 'POST', 15 | data, 16 | cookie, 17 | music_req => res.send(music_req), 18 | err => res.status(502).send('fetch error') 19 | ) 20 | }) 21 | 22 | module.exports = router 23 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/recommend_songs.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | offset: 0, 9 | total: true, 10 | limit: 20, 11 | csrf_token: '' 12 | } 13 | 14 | createWebAPIRequest( 15 | 'music.163.com', 16 | '/weapi/v1/discovery/recommend/songs', 17 | 'POST', 18 | data, 19 | cookie, 20 | music_req => res.send(music_req), 21 | err => res.status(502).send('fetch error') 22 | ) 23 | }) 24 | 25 | module.exports = router 26 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/resource_like.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | threadId: req.query.id, 9 | csrf_token: '' 10 | } 11 | const action = req.query.t == 1 ? 'like' : 'unlike' 12 | createWebAPIRequest( 13 | 'music.163.com', 14 | `/weapi/resource/${action}`, 15 | 'POST', 16 | data, 17 | cookie, 18 | music_req => { 19 | res.send(music_req) 20 | }, 21 | err => res.status(502).send('fetch error') 22 | ) 23 | }) 24 | 25 | module.exports = router 26 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/search.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express(); 3 | const { createWebAPIRequest } = require("../util/util"); 4 | 5 | router.get("/", (req, res) => { 6 | const cookie = req.get("Cookie") ? req.get("Cookie") : ""; 7 | const keywords = req.query.keywords; 8 | const type = req.query.type || 1; 9 | const limit = req.query.limit || 30; 10 | const offset = req.query.offset || 0; 11 | // *(type)* 搜索单曲(1),歌手(100),专辑(10),歌单(1000),用户(1002) 12 | const data = { 13 | csrf_token: "", 14 | limit, 15 | type, 16 | s: keywords, 17 | offset 18 | }; 19 | 20 | createWebAPIRequest( 21 | "music.163.com", 22 | "/weapi/search/get", 23 | "POST", 24 | data, 25 | cookie, 26 | music_req => res.send(music_req), 27 | err => res.status(502).send("fetch error") 28 | ); 29 | }); 30 | 31 | module.exports = router; 32 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/search_multimatch.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | csrf_token: '', 9 | type: req.query.type || 1, 10 | s: req.query.keywords || '' 11 | } 12 | 13 | createWebAPIRequest( 14 | 'music.163.com', 15 | '/weapi/search/suggest/multimatch', 16 | 'POST', 17 | data, 18 | cookie, 19 | music_req => { 20 | res.send(music_req) 21 | }, 22 | err => res.status(502).send('fetch error') 23 | ) 24 | }) 25 | 26 | module.exports = router 27 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/search_suggest.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | csrf_token: '', 9 | s: req.query.keywords || '' 10 | } 11 | 12 | createWebAPIRequest( 13 | 'music.163.com', 14 | '/weapi/search/suggest/web', 15 | 'POST', 16 | data, 17 | cookie, 18 | music_req => { 19 | res.send(music_req) 20 | }, 21 | err => res.status(502).send('fetch error') 22 | ) 23 | }) 24 | 25 | module.exports = router 26 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/simi_artists.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const id = req.query.id 8 | const data = { 9 | artistid: id, 10 | csrf_token: '' 11 | } 12 | createWebAPIRequest( 13 | 'music.163.com', 14 | `/weapi/discovery/simiArtist`, 15 | 'POST', 16 | data, 17 | cookie, 18 | music_req => res.send(music_req), 19 | err => res.status(502).send('fetch error') 20 | ) 21 | }) 22 | 23 | module.exports = router 24 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/simi_mv.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | mvid: req.query.mvid 9 | } 10 | createWebAPIRequest( 11 | 'music.163.com', 12 | '/weapi/discovery/simiMV', 13 | 'POST', 14 | data, 15 | cookie, 16 | music_req => res.send(music_req), 17 | err => res.status(502).send('fetch error') 18 | ) 19 | }) 20 | 21 | module.exports = router 22 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/simi_playlist.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | songid: req.query.id, 9 | offset: req.query.offset || 0, 10 | limit: req.query.limit || 50 11 | } 12 | createWebAPIRequest( 13 | 'music.163.com', 14 | '/weapi/discovery/simiPlaylist', 15 | 'POST', 16 | data, 17 | cookie, 18 | music_req => { 19 | res.send(music_req) 20 | }, 21 | err => res.status(502).send('fetch error') 22 | ) 23 | }) 24 | 25 | module.exports = router 26 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/simi_song.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | songid: req.query.id, 9 | offset: req.query.offset || 0, 10 | limit: req.query.limit || 50 11 | } 12 | createWebAPIRequest( 13 | 'music.163.com', 14 | '/weapi/v1/discovery/simiSong', 15 | 'POST', 16 | data, 17 | cookie, 18 | music_req => { 19 | res.send(music_req) 20 | }, 21 | err => res.status(502).send('fetch error') 22 | ) 23 | }) 24 | 25 | module.exports = router 26 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/simi_user.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | songid: req.query.id, 9 | offset: req.query.offset || 0, 10 | limit: req.query.limit || 50 11 | } 12 | createWebAPIRequest( 13 | 'music.163.com', 14 | '/weapi/discovery/simiUser', 15 | 'POST', 16 | data, 17 | cookie, 18 | music_req => { 19 | res.send(music_req) 20 | }, 21 | err => res.status(502).send('fetch error') 22 | ) 23 | }) 24 | 25 | module.exports = router 26 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/song_detail.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const id = parseInt(req.query.ids) 8 | const data = { 9 | // "id": id, 10 | c: JSON.stringify([{ id: id }]), 11 | ids: '[' + id + ']', 12 | csrf_token: '' 13 | } 14 | console.log(data) 15 | createWebAPIRequest( 16 | 'music.163.com', 17 | '/weapi/v3/song/detail', 18 | 'POST', 19 | data, 20 | cookie, 21 | music_req => { 22 | res.send(music_req) 23 | }, 24 | err => res.status(502).send('fetch error') 25 | ) 26 | }) 27 | 28 | module.exports = router 29 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/top_album.js: -------------------------------------------------------------------------------- 1 | //最新mv 2 | const express = require('express') 3 | const router = express() 4 | const { createWebAPIRequest } = require('../util/util') 5 | 6 | // type ALL, ZH,EA,KR,JP 7 | router.get('/', (req, res) => { 8 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 9 | const data = { 10 | offset: req.query.offset || 0, 11 | total: true, 12 | limit: req.query.limit || 50, 13 | area: req.query.type, 14 | csrf_token: '' 15 | } 16 | createWebAPIRequest( 17 | 'music.163.com', 18 | '/weapi/album/new', 19 | 'POST', 20 | data, 21 | cookie, 22 | music_req => { 23 | res.send(music_req) 24 | }, 25 | err => res.status(502).send('fetch error') 26 | ) 27 | }) 28 | 29 | module.exports = router 30 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/top_artists.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | offset: req.query.offset || 0, 9 | total: true, 10 | limit: req.query.limit || 50, 11 | csrf_token: '' 12 | } 13 | createWebAPIRequest( 14 | 'music.163.com', 15 | `/weapi/artist/top`, 16 | 'POST', 17 | data, 18 | cookie, 19 | music_req => { 20 | res.send(music_req) 21 | }, 22 | err => res.status(502).send('fetch error') 23 | ) 24 | }) 25 | 26 | module.exports = router 27 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/top_list.js: -------------------------------------------------------------------------------- 1 | const top_list_all = { 2 | '0': ['云音乐新歌榜', '/api/playlist/detail?id=3779629'], 3 | '1': ['云音乐热歌榜', '/api/playlist/detail?id=3778678'], 4 | '2': ['网易原创歌曲榜', '/api/playlist/detail?id=2884035'], 5 | '3': ['云音乐飙升榜', '/api/playlist/detail?id=19723756'], 6 | '4': ['云音乐电音榜', '/api/playlist/detail?id=10520166'], 7 | '5': ['UK排行榜周榜', '/api/playlist/detail?id=180106'], 8 | '6': ['美国Billboard周榜', '/api/playlist/detail?id=60198'], 9 | '7': ['KTV嗨榜', '/api/playlist/detail?id=21845217'], 10 | '8': ['iTunes榜', '/api/playlist/detail?id=11641012'], 11 | '9': ['Hit FM Top榜', '/api/playlist/detail?id=120001'], 12 | '10': ['日本Oricon周榜', '/api/playlist/detail?id=60131'], 13 | '11': ['韩国Melon排行榜周榜', '/api/playlist/detail?id=3733003'], 14 | '12': ['韩国Mnet排行榜周榜', '/api/playlist/detail?id=60255'], 15 | '13': ['韩国Melon原声周榜', '/api/playlist/detail?id=46772709'], 16 | '14': ['中国TOP排行榜(港台榜)', '/api/playlist/detail?id=112504'], 17 | '15': ['中国TOP排行榜(内地榜)', '/api/playlist/detail?id=64016'], 18 | '16': ['香港电台中文歌曲龙虎榜', '/api/playlist/detail?id=10169002'], 19 | '17': ['华语金曲榜', '/api/playlist/detail?id=4395559'], 20 | '18': ['中国嘻哈榜', '/api/playlist/detail?id=1899724'], 21 | '19': ['法国 NRJ EuroHot 30周榜', '/api/playlist/detail?id=27135204'], 22 | '20': ['台湾Hito排行榜', '/api/playlist/detail?id=112463'], 23 | '21': ['Beatport全球电子舞曲榜', '/api/playlist/detail?id=3812895'] 24 | } 25 | const express = require('express') 26 | const router = express() 27 | const { createRequest } = require('../util/util') 28 | 29 | router.get('/', (req, res) => { 30 | const idx = req.query.idx 31 | const action = 'http://music.163.com' + top_list_all[idx][1] 32 | createRequest(`${action}`, 'GET', null) 33 | .then(result => { 34 | res.setHeader('Content-Type', 'application/json') 35 | res.send(result) 36 | }) 37 | .catch(err => { 38 | res.status(502).send('fetch error') 39 | }) 40 | }) 41 | 42 | module.exports = router 43 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/top_mv.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | offset: req.query.offset || 0, 9 | total: true, 10 | limit: req.query.limit || 30, 11 | csrf_token: '' 12 | } 13 | 14 | createWebAPIRequest( 15 | 'music.163.com', 16 | '/weapi/mv/toplist', 17 | 'POST', 18 | data, 19 | cookie, 20 | music_req => { 21 | res.setHeader('Content-Type', 'application/json') 22 | res.send(music_req) 23 | }, 24 | err => res.status(502).send('fetch error') 25 | ) 26 | }) 27 | 28 | module.exports = router 29 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/top_playlist.js: -------------------------------------------------------------------------------- 1 | //分类歌单 2 | const express = require('express') 3 | const router = express() 4 | const { createWebAPIRequest } = require('../util/util') 5 | 6 | router.get('/', (req, res) => { 7 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 8 | // order可为 'hot' 可为 'new' 9 | const data = { 10 | cat: req.query.cat || '全部', 11 | order: req.query.order || 'hot', 12 | offset: req.query.offset || 0, 13 | total: req.query.total ? 'true' : 'false', 14 | limit: req.query.limit || 50 15 | } 16 | createWebAPIRequest( 17 | 'music.163.com', 18 | '/weapi/playlist/list', 19 | 'POST', 20 | data, 21 | cookie, 22 | music_req => { 23 | res.send(music_req) 24 | }, 25 | err => res.status(502).send('fetch error') 26 | ) 27 | }) 28 | 29 | module.exports = router 30 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/top_playlist_highquality.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express(); 3 | const { createWebAPIRequest } = require("../util/util"); 4 | 5 | router.get("/", (req, res) => { 6 | const cookie = req.get("Cookie") ? req.get("Cookie") : ""; 7 | const data = { 8 | cat: req.query.cat || "全部", 9 | offset: req.query.offset || 0, 10 | limit: req.query.limit || 20, 11 | csrf_token: "" 12 | }; 13 | createWebAPIRequest( 14 | "music.163.com", 15 | "/weapi/playlist/highquality/list", 16 | "POST", 17 | data, 18 | cookie, 19 | music_req => { 20 | res.send(music_req); 21 | }, 22 | err => res.status(502).send("fetch error") 23 | ); 24 | }); 25 | 26 | module.exports = router; 27 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/top_songs.js: -------------------------------------------------------------------------------- 1 | //新歌上架 2 | const express = require('express') 3 | const router = express() 4 | const { createWebAPIRequest } = require('../util/util') 5 | 6 | // type ALL, ZH,EA,KR,JP 7 | router.get('/', (req, res) => { 8 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 9 | const data = { 10 | offset: req.query.offset, 11 | total: true, 12 | limit: req.query.limit, 13 | area: req.query.type, 14 | csrf_token: '' 15 | } 16 | createWebAPIRequest( 17 | 'music.163.com', 18 | '/weapi/v1/discovery/new/songs', 19 | 'POST', 20 | data, 21 | cookie, 22 | music_req => { 23 | res.send(music_req) 24 | }, 25 | err => res.status(502).send('fetch error') 26 | ) 27 | }) 28 | 29 | module.exports = router 30 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/toplist.js: -------------------------------------------------------------------------------- 1 | // 排行榜 2 | const express = require('express') 3 | const router = express() 4 | const { createWebAPIRequest } = require('../util/util') 5 | 6 | router.get('/', (req, res) => { 7 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 8 | const data = { 9 | csrf_token: '' 10 | } 11 | createWebAPIRequest( 12 | 'music.163.com', 13 | '/weapi/toplist', 14 | 'POST', 15 | data, 16 | cookie, 17 | music_req => { 18 | res.send(music_req) 19 | }, 20 | err => res.status(502).send('fetch error') 21 | ) 22 | }) 23 | 24 | module.exports = router 25 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/toplist_artist.js: -------------------------------------------------------------------------------- 1 | //艺术家分类 2 | const express = require('express') 3 | const router = express() 4 | const { createWebAPIRequest } = require('../util/util') 5 | 6 | router.get('/', (req, res) => { 7 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 8 | const data = { 9 | type: req.query.type, 10 | csrf_token: '' 11 | } 12 | createWebAPIRequest( 13 | 'music.163.com', 14 | '/weapi/toplist/artist', 15 | 'POST', 16 | data, 17 | cookie, 18 | music_req => { 19 | res.send(music_req) 20 | }, 21 | err => res.status(502).send('fetch error') 22 | ) 23 | }) 24 | 25 | module.exports = router 26 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/toplist_detail.js: -------------------------------------------------------------------------------- 1 | // 排行榜详情 2 | const express = require('express') 3 | const router = express() 4 | const { createWebAPIRequest } = require('../util/util') 5 | 6 | router.get('/', (req, res) => { 7 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 8 | const data = { 9 | id: req.query.id, 10 | limit: 20, 11 | csrf_token: '' 12 | } 13 | createWebAPIRequest( 14 | 'music.163.com', 15 | '/weapi/toplist/detail', 16 | 'POST', 17 | data, 18 | cookie, 19 | music_req => { 20 | res.send(music_req) 21 | }, 22 | err => res.status(502).send('fetch error') 23 | ) 24 | }) 25 | 26 | module.exports = router 27 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/user_audio.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const data = { 7 | userId: req.query.uid, 8 | csrf_token: '' 9 | } 10 | console.log(data) 11 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 12 | 13 | createWebAPIRequest( 14 | 'music.163.com', 15 | '/weapi/djradio/get/byuser', 16 | 'POST', 17 | data, 18 | cookie, 19 | music_req => { 20 | res.setHeader('Content-Type', 'application/json') 21 | res.send(music_req) 22 | }, 23 | err => { 24 | res.status(502).send('fetch error') 25 | } 26 | ) 27 | }) 28 | 29 | module.exports = router 30 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/user_cloud.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const data = { 7 | limit: req.query.limit || 10, 8 | offset: req.query.offset || 0, 9 | csrf_token: '' 10 | } 11 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 12 | createWebAPIRequest( 13 | 'music.163.com', 14 | '/weapi/v1/cloud/get', 15 | 'POST', 16 | data, 17 | cookie, 18 | music_req => { 19 | res.setHeader('Content-Type', 'application/json') 20 | res.send(music_req) 21 | }, 22 | err => { 23 | res.status(502).send('fetch error') 24 | } 25 | ) 26 | }) 27 | 28 | module.exports = router 29 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/user_cloud_search.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const data = { 7 | byids: req.query.id, 8 | id: req.query.id, 9 | csrf_token: '' 10 | } 11 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 12 | createWebAPIRequest( 13 | 'music.163.com', 14 | '/weapi/v1/cloud/get/byids', 15 | 'POST', 16 | data, 17 | cookie, 18 | music_req => { 19 | res.setHeader('Content-Type', 'application/json') 20 | res.send(music_req) 21 | }, 22 | err => { 23 | res.status(502).send('fetch error') 24 | } 25 | ) 26 | }) 27 | 28 | module.exports = router 29 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/user_detail.js: -------------------------------------------------------------------------------- 1 | // 用户详情 2 | const express = require('express') 3 | const router = express() 4 | const { createWebAPIRequest } = require('../util/util') 5 | 6 | router.get('/', (req, res) => { 7 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 8 | const id = req.query.uid 9 | const data = { 10 | csrf_token: '' 11 | } 12 | createWebAPIRequest( 13 | 'music.163.com', 14 | `/weapi/v1/user/detail/${id}`, 15 | 'POST', 16 | data, 17 | cookie, 18 | music_req => { 19 | res.send(music_req) 20 | }, 21 | err => res.status(502).send('fetch error') 22 | ) 23 | }) 24 | 25 | module.exports = router 26 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/user_dj.js: -------------------------------------------------------------------------------- 1 | // 用户电台 2 | const express = require('express') 3 | const router = express() 4 | const { createWebAPIRequest } = require('../util/util') 5 | 6 | router.get('/', (req, res) => { 7 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 8 | const id = req.query.uid 9 | const data = { 10 | offset: req.query.offset || '0', 11 | limit: req.query.limit || 30, 12 | csrf_token: '' 13 | } 14 | createWebAPIRequest( 15 | 'music.163.com', 16 | `/weapi/dj/program/${id}`, 17 | 'POST', 18 | data, 19 | cookie, 20 | music_req => { 21 | res.send(music_req) 22 | }, 23 | err => res.status(502).send('fetch error') 24 | ) 25 | }) 26 | 27 | module.exports = router 28 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/user_event.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const id = req.query.uid 8 | const data = { 9 | time: -1, 10 | getcounts: true, 11 | csrf_token: '' 12 | } 13 | createWebAPIRequest( 14 | 'music.163.com', 15 | `/weapi/event/get/${id}`, 16 | 'POST', 17 | data, 18 | cookie, 19 | music_req => { 20 | res.send(music_req) 21 | }, 22 | err => res.status(502).send('fetch error') 23 | ) 24 | }) 25 | 26 | module.exports = router 27 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/user_followeds.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | userId: req.query.uid, 9 | offset: req.query.offset || '0', 10 | limit: req.query.limit || 30, 11 | csrf_token: '' 12 | } 13 | createWebAPIRequest( 14 | 'music.163.com', 15 | `/weapi/user/getfolloweds/`, 16 | 'POST', 17 | data, 18 | cookie, 19 | music_req => { 20 | res.send(music_req) 21 | }, 22 | err => res.status(502).send('fetch error') 23 | ) 24 | }) 25 | 26 | module.exports = router 27 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/user_follows.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const id = req.query.uid 8 | const data = { 9 | offset: req.query.offset || '0', 10 | limit: req.query.limit || 30, 11 | order: true 12 | } 13 | createWebAPIRequest( 14 | 'music.163.com', 15 | `/weapi/user/getfollows/${id}`, 16 | 'POST', 17 | data, 18 | cookie, 19 | music_req => { 20 | res.send(music_req) 21 | }, 22 | err => res.status(502).send('fetch error') 23 | ) 24 | }) 25 | 26 | module.exports = router 27 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/user_playlist.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | offset: 0, 9 | uid: req.query.uid, 10 | limit: 1000, 11 | csrf_token: '' 12 | } 13 | createWebAPIRequest( 14 | 'music.163.com', 15 | '/weapi/user/playlist', 16 | 'POST', 17 | data, 18 | cookie, 19 | music_req => res.send(music_req), 20 | err => res.status(502).send('fetch error') 21 | ) 22 | }) 23 | 24 | module.exports = router 25 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/user_playrecord.js: -------------------------------------------------------------------------------- 1 | //播放记录 2 | const express = require('express') 3 | const router = express() 4 | const { createWebAPIRequest } = require('../util/util') 5 | 6 | router.get('/', (req, res) => { 7 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 8 | 9 | // type=1时只返回weekData, type=0时返回allData 10 | const data = { 11 | type: req.query.type || 0, 12 | uid: req.query.uid, //用户 id, 13 | csrf_token: '' 14 | } 15 | const action = `/weapi/v1/play/record` 16 | createWebAPIRequest( 17 | 'music.163.com', 18 | action, 19 | 'POST', 20 | data, 21 | cookie, 22 | music_req => res.send(music_req), 23 | err => res.status(502).send('fetch error') 24 | ) 25 | }) 26 | 27 | module.exports = router 28 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/router/user_subcount.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const data = { 8 | csrf_token: '' 9 | } 10 | createWebAPIRequest( 11 | 'music.163.com', 12 | '/weapi/subcount', 13 | 'POST', 14 | data, 15 | cookie, 16 | music_req => res.send(music_req), 17 | err => res.status(502).send('fetch error') 18 | ) 19 | }) 20 | 21 | module.exports = router 22 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/static/artist_album.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/static/artist_album.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/static/artists.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/static/artists.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/static/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/static/banner.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/static/comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/static/comment.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/static/docs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/static/docs.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/static/fm_trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/static/fm_trash.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/static/like.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/static/like.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/static/likeSuccess.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/static/likeSuccess.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/static/mv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/static/mv.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/static/new_albums.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/static/new_albums.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/static/personal_fm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/static/personal_fm.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/static/play_mv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/static/play_mv.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/static/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/static/screenshot1.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/static/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/static/screenshot2.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/static/signinError.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/static/signinError.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/static/signinSuccess.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/static/signinSuccess.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/static/songDetail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/static/songDetail.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/static/top_artists.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/static/top_artists.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/static/top_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/static/top_list.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/static/top_playlist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/static/top_playlist.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/static/专辑.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/static/专辑.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/static/推荐歌单.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/static/推荐歌单.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/static/推荐歌曲.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/static/推荐歌曲.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/static/搜索.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/static/搜索.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/static/歌单详情.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/static/歌单详情.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/static/歌词.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/static/歌词.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/static/用户歌单.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/static/用户歌单.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/static/登录.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/static/登录.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/static/音乐 url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/NeteaseCloudMusicApi/static/音乐 url.png -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/test/album.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const crypto = require('crypto') 3 | const { createRequest } = require('../util/util') 4 | 5 | describe('测试获取歌手专辑列表是否正常', () => { 6 | it('数据的 code 应该为200', done => { 7 | const id = 32311 8 | createRequest(`/api/album/${id}`, 'GET', null) 9 | .then(result => { 10 | const code = JSON.parse(result).code 11 | console.log('code:' + code) 12 | assert(code === 200) 13 | done() 14 | }) 15 | .catch(err => done(err)) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/test/comment.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const crypto = require('crypto') 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | describe('测试获取评论是否正常', () => { 6 | it('数据的 code 应该为200', done => { 7 | const rid = 32311 8 | const cookie = '' 9 | const data = { 10 | offset: 0, 11 | rid: rid, 12 | limit: 20, 13 | csrf_token: '' 14 | } 15 | createWebAPIRequest( 16 | 'music.163.com', 17 | `/weapi/v1/resource/comments/R_SO_4_${rid}/?csrf_token=`, 18 | 'POST', 19 | data, 20 | cookie, 21 | music_req => { 22 | console.log({ 23 | code: JSON.parse(music_req).code 24 | }) 25 | assert(JSON.parse(music_req).code === 200) 26 | done() 27 | }, 28 | err => done(err) 29 | ) 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/test/login.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const crypto = require('crypto') 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | console.log('注意:测试登陆需要替换这里的账号密码!!!') 6 | 7 | describe('测试登录是否正常', () => { 8 | it('手机登录 code 应该等于200', done => { 9 | const phone = '换成你的手机号' 10 | const password = '换成你的密码' 11 | let cookie = '' 12 | const md5sum = crypto.createHash('md5') 13 | md5sum.update(password) 14 | const data = { 15 | phone: phone, 16 | password: md5sum.digest('hex'), 17 | rememberLogin: 'true' 18 | } 19 | 20 | createWebAPIRequest( 21 | 'music.163.com', 22 | '/weapi/login/cellphone', 23 | 'POST', 24 | data, 25 | cookie, 26 | (music_req, cookie) => { 27 | const result = JSON.parse(music_req) 28 | console.log({ 29 | loginType: result.loginType, 30 | code: result.code, 31 | account: result.account 32 | }) 33 | assert(result.code === 200) 34 | done() 35 | }, 36 | err => done(err) 37 | ) 38 | }) 39 | 40 | it('邮箱登录 code 应该等于200', done => { 41 | const email = '换成你的163网易邮箱' 42 | const password = '换成你的密码' 43 | const cookie = '' 44 | const md5sum = crypto.createHash('md5') 45 | md5sum.update(password) 46 | const data = { 47 | username: email, 48 | password: md5sum.digest('hex'), 49 | rememberLogin: 'true' 50 | } 51 | 52 | createWebAPIRequest( 53 | 'music.163.com', 54 | '/weapi/login', 55 | 'POST', 56 | data, 57 | cookie, 58 | (music_req, cookie) => { 59 | const result = JSON.parse(music_req) 60 | console.log({ 61 | loginType: result.loginType, 62 | code: result.code, 63 | account: result.account 64 | }) 65 | assert(result.code === 200) 66 | done() 67 | }, 68 | err => done(err) 69 | ) 70 | }) 71 | }) 72 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/test/lyric.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const crypto = require('crypto') 3 | const { createRequest } = require('../util/util') 4 | 5 | describe('测试获取歌词是否正常', () => { 6 | it('数据应该有 lrc 字段', done => { 7 | const id = 347230 8 | createRequest( 9 | '/api/song/lyric?os=osx&id=' + id + '&lv=-1&kv=-1&tv=-1', 10 | 'GET', 11 | null 12 | ) 13 | .then(result => { 14 | // console.log(JSON.parse(result).lrc) 15 | assert(typeof JSON.parse(result).lrc !== 'undefined') 16 | done() 17 | }) 18 | .catch(err => { 19 | done(err) 20 | }) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/test/musicUrl.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const crypto = require('crypto') 3 | const { createWebAPIRequest } = require('../util/util') 4 | 5 | describe('测试获取歌曲是否正常', () => { 6 | it('歌曲的 url 不应该为空', done => { 7 | const id = 347230 8 | const br = 999000 9 | const data = { 10 | ids: [id], 11 | br: br, 12 | csrf_token: '' 13 | } 14 | const cookie = '' 15 | 16 | createWebAPIRequest( 17 | 'music.163.com', 18 | '/weapi/song/enhance/player/url', 19 | 'POST', 20 | data, 21 | cookie, 22 | music_req => { 23 | console.log(JSON.parse(music_req).data[0].url) 24 | assert(!!JSON.parse(music_req).data[0].url) 25 | done() 26 | }, 27 | err => { 28 | done(err) 29 | } 30 | ) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/test/search.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const crypto = require('crypto') 3 | const { createRequest } = require('../util/util') 4 | 5 | describe('测试搜索是否正常', () => { 6 | it('获取到的数据的 name 应该和搜索关键词一致', done => { 7 | const keywords = '海阔天空' 8 | const type = 1 9 | const limit = 30 10 | const data = 11 | 's=' + keywords + '&limit=' + limit + '&type=' + type + '&offset=0' 12 | createRequest('/api/search/pc/', 'POST', data) 13 | .then(result => { 14 | console.log(JSON.parse(result).result.songs[0].mp3Url) 15 | assert(JSON.parse(result).result.songs[0].name === '海阔天空') 16 | done() 17 | }) 18 | .catch(err => { 19 | done(err) 20 | }) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/util/crypto.js: -------------------------------------------------------------------------------- 1 | // 参考 https://github.com/darknessomi/musicbox/wiki/ 2 | 'use strict' 3 | const crypto = require('crypto') 4 | const bigInt = require('big-integer') 5 | const modulus = 6 | '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7' 7 | const nonce = '0CoJUm6Qyw8W8jud' 8 | const pubKey = '010001' 9 | 10 | String.prototype.hexEncode = function() { 11 | let hex, i 12 | 13 | let result = '' 14 | for (i = 0; i < this.length; i++) { 15 | hex = this.charCodeAt(i).toString(16) 16 | result += ('' + hex).slice(-4) 17 | } 18 | return result 19 | } 20 | 21 | function createSecretKey(size) { 22 | const keys = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' 23 | let key = '' 24 | for (let i = 0; i < size; i++) { 25 | let pos = Math.random() * keys.length 26 | pos = Math.floor(pos) 27 | key = key + keys.charAt(pos) 28 | } 29 | return key 30 | } 31 | 32 | function aesEncrypt(text, secKey) { 33 | const _text = text 34 | const lv = new Buffer('0102030405060708', 'binary') 35 | const _secKey = new Buffer(secKey, 'binary') 36 | const cipher = crypto.createCipheriv('AES-128-CBC', _secKey, lv) 37 | let encrypted = cipher.update(_text, 'utf8', 'base64') 38 | encrypted += cipher.final('base64') 39 | return encrypted 40 | } 41 | 42 | function zfill(str, size) { 43 | while (str.length < size) str = '0' + str 44 | return str 45 | } 46 | 47 | function rsaEncrypt(text, pubKey, modulus) { 48 | const _text = text.split('').reverse().join('') 49 | const biText = bigInt(new Buffer(_text).toString('hex'), 16), 50 | biEx = bigInt(pubKey, 16), 51 | biMod = bigInt(modulus, 16), 52 | biRet = biText.modPow(biEx, biMod) 53 | return zfill(biRet.toString(16), 256) 54 | } 55 | 56 | function Encrypt(obj) { 57 | const text = JSON.stringify(obj) 58 | const secKey = createSecretKey(16) 59 | const encText = aesEncrypt(aesEncrypt(text, nonce), secKey) 60 | const encSecKey = rsaEncrypt(secKey, pubKey, modulus) 61 | return { 62 | params: encText, 63 | encSecKey: encSecKey 64 | } 65 | } 66 | 67 | module.exports = Encrypt 68 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi/util/util.js: -------------------------------------------------------------------------------- 1 | const Encrypt = require('./crypto.js') 2 | const http = require('http') 3 | const querystring = require('querystring') 4 | 5 | function randomUserAgent() { 6 | return 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36' 7 | } 8 | function createWebAPIRequest( 9 | host, 10 | path, 11 | method, 12 | data, 13 | cookie, 14 | callback, 15 | errorcallback 16 | ) { 17 | let music_req = '' 18 | const cryptoreq = Encrypt(data) 19 | const http_client = http.request( 20 | { 21 | hostname: host, 22 | method: method, 23 | path: path, 24 | headers: { 25 | Accept: '*/*', 26 | 'Accept-Language': 'zh-CN,zh;q=0.8,gl;q=0.6,zh-TW;q=0.4', 27 | Connection: 'keep-alive', 28 | 'Content-Type': 'application/x-www-form-urlencoded', 29 | Referer: 'http://music.163.com', 30 | Host: 'music.163.com', 31 | Cookie: cookie, 32 | 'User-Agent': randomUserAgent() 33 | } 34 | }, 35 | function(res) { 36 | res.on('error', function(err) { 37 | errorcallback(err) 38 | }) 39 | res.setEncoding('utf8') 40 | if (res.statusCode != 200) { 41 | createWebAPIRequest(host, path, method, data, cookie, callback) 42 | return 43 | } else { 44 | res.on('data', function(chunk) { 45 | music_req += chunk 46 | }) 47 | res.on('end', function() { 48 | if (music_req == '') { 49 | createWebAPIRequest(host, path, method, data, cookie, callback) 50 | return 51 | } 52 | if (res.headers['set-cookie']) { 53 | callback(music_req, res.headers['set-cookie']) 54 | } else { 55 | callback(music_req) 56 | } 57 | }) 58 | } 59 | } 60 | ) 61 | http_client.write( 62 | querystring.stringify({ 63 | params: cryptoreq.params, 64 | encSecKey: cryptoreq.encSecKey 65 | }) 66 | ) 67 | http_client.end() 68 | } 69 | 70 | function createRequest(path, method, data, callback, errorcallback) { 71 | return new Promise((resolve, reject) => { 72 | let ne_req = '' 73 | const http_client = http.request( 74 | { 75 | hostname: 'music.163.com', 76 | method: method, 77 | path: path, 78 | headers: { 79 | Referer: 'http://music.163.com', 80 | Cookie: 'appver=1.5.2', 81 | 'Content-Type': 'application/x-www-form-urlencoded', 82 | 'User-Agent': randomUserAgent() 83 | } 84 | }, 85 | res => { 86 | res.setEncoding('utf8') 87 | res.on('error', err => { 88 | reject(err) 89 | }) 90 | res.on('data', chunk => { 91 | ne_req += chunk 92 | }) 93 | res.on('end', () => { 94 | resolve(ne_req) 95 | }) 96 | } 97 | ) 98 | if (method == 'POST') { 99 | http_client.write(data) 100 | } 101 | http_client.end() 102 | }) 103 | } 104 | module.exports = { 105 | createWebAPIRequest, 106 | createRequest 107 | } 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xbyjMusic 2 | 3 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com) 4 | [![GitHub license](https://img.shields.io/badge/license-GPL-blue.svg)]() 5 | 6 | 7 | 8 | > 跨平台 NeteaseMusic 桌面应用 9 | 10 | 11 | # 技术栈 12 | 13 | > electron + react + mobx + react-router + jss + webpack + express + ES6/7 + axios + flex + canvas 14 | 15 | 16 | 17 | # 项目运行 18 | 19 | ### 注意:由于涉及大量的 ES6/7 等新属性,node 需要 6.0 以上版本 20 | 21 | ``` 22 | git clone https://github.com/yllg/xbyjMusic.git   23 | cd xbyjMusic 24 | git submodule init 25 | git submodule update 26 | npm install 27 | npm run dev 28 | 29 | ``` 30 | 31 | 32 | # 目标功能 33 | ## 页面 34 | - [x] 首页 -- 完成 35 | - [x] 登陆 -- 完成 36 | - [x] 每日推荐 -- 完成 37 | - [x] 私人FM -- 完成 38 | - [x] 歌曲页 -- 完成 39 | - [x] 歌单页 -- 完成 40 | - [x] 歌手页 -- 完成 41 | - [x] 用户页 -- 完成 42 | - [x] 排行榜 -- 完成 43 | - [x] 歌单主页 -- 完成 44 | - [x] 偏好设置页/首选项 -- 完成 45 | - [ ] MV/视频页 46 | - [ ] 朋友页 47 | - [ ] 我的歌手/我的收藏 48 | - [ ] 主播电台 49 | - [ ] 最新音乐 50 | 51 | ## 组件 52 | - [x] header组件 -- 完成 53 | - [x] 左菜单组件 -- 完成 54 | - [x] 播放条组件 -- 完成 55 | - [x] audio组件 -- 完成 56 | - [x] 播放列表组件 -- 完成 57 | - [x] 歌词组件 -- 完成 58 | - [ ] 评论组件 -- 只完成分类显示 59 | - [x] 搜索组件 -- 完成 60 | - [ ] 首页轮播 -- 接口参数不明,拿不到最新数据哦 61 | - [x] 同步轮播组件 -- 关于我 62 | - [x] 提示组件 -- 完成 63 | 64 | 65 | ## 功能 66 | - [x] 喜欢 -- 完成 67 | - [x] 不喜欢 -- 完成 68 | - [x] 收藏歌单 -- 完成 69 | - [ ] 收藏歌曲 70 | - [x] 收藏歌手 -- 完成 71 | - [ ] 评论 72 | - [x] 评论点赞 -- 完成 73 | - [ ] 下载歌曲 74 | 75 | 76 | 77 | 78 | # 效果演示 79 | (LICEcap录制GIF时,渐变色会有点失真,动图将就看下哈~) 80 | 81 | ### 首页 82 | { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const id = req.query.id 8 | const csrf = req.cookies['__csrf'] 9 | const data = { 10 | csrf_token: csrf, 11 | followId: id, 12 | } 13 | 14 | createWebAPIRequest( 15 | 'music.163.com', 16 | `/weapi/user/follow/${id}?csrf_token=${csrf}`, 17 | 'POST', 18 | data, 19 | cookie, 20 | music_req => { 21 | res.send(music_req) 22 | }, 23 | err => res.status(502).send('fetch error') 24 | ) 25 | }) 26 | 27 | module.exports = router -------------------------------------------------------------------------------- /server/api/hot_album.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../../NeteaseCloudMusicApi/util/util') 4 | 5 | // type ALL, ZH,EA,KR,JP 6 | router.get('/', (req, res) => { 7 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 8 | const data = { 9 | 'csrf_token': '' 10 | } 11 | createWebAPIRequest( 12 | 'music.163.com', 13 | '/api/discovery/newAlbum', 14 | 'POST', 15 | data, 16 | cookie, 17 | music_req => { 18 | res.send(music_req) 19 | }, 20 | err => res.status(502).send('fetch error') 21 | ) 22 | }) 23 | 24 | module.exports = router 25 | -------------------------------------------------------------------------------- /server/api/login.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const crypto = require('crypto') 3 | const router = express() 4 | const { createWebAPIRequest } = require('../../NeteaseCloudMusicApi/util/util') 5 | 6 | router.get('/', (req, res) => { 7 | const email = req.query.email 8 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 9 | const md5sum = crypto.createHash('md5') 10 | const csrf = req.cookies['__csrf'] 11 | md5sum.update(req.query.password) 12 | const data = { 13 | csrf_token: csrf, 14 | username: email, 15 | password: md5sum.digest('hex'), 16 | rememberLogin: 'true', 17 | clientToken: 18 | '1_F7ocLT2vWrl5GOeGAMJAeo/dbaD6E3PI_yI7eXDqSmN+pLichDRZKIJ57l7VvXdOg' 19 | } 20 | console.log('Data: %O', data) 21 | 22 | createWebAPIRequest( 23 | 'music.163.com', 24 | `/weapi/login?csrf_token=${csrf}`, 25 | 'POST', 26 | data, 27 | cookie, 28 | (music_req, cookie) => { 29 | cookie = cookie.map(x => x.replace("Domain=.music.163.com", "")) 30 | res.set({ 31 | 'Set-Cookie': cookie 32 | }) 33 | res.send(music_req) 34 | }, 35 | err => res.status(502).send('fetch error') 36 | ) 37 | }) 38 | 39 | module.exports = router 40 | -------------------------------------------------------------------------------- /server/api/loginCellphone.js: -------------------------------------------------------------------------------- 1 | 2 | const express = require('express') 3 | const crypto = require('crypto') 4 | const router = express() 5 | const { createWebAPIRequest } = require('../../NeteaseCloudMusicApi/util/util') 6 | 7 | router.get('/', (req, res) => { 8 | const phone = req.query.phone 9 | const countrycode = req.query.countrycode 10 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 11 | const md5sum = crypto.createHash('md5') 12 | md5sum.update(req.query.password) 13 | const data = { 14 | countrycode: countrycode, 15 | phone: phone, 16 | password: md5sum.digest('hex'), 17 | rememberLogin: 'true' 18 | } 19 | createWebAPIRequest( 20 | 'music.163.com', 21 | '/weapi/login/cellphone', 22 | 'POST', 23 | data, 24 | cookie, 25 | (music_req, cookie) => { 26 | cookie = cookie.map(x => x.replace("Domain=.music.163.com", "")) 27 | res.set({ 28 | 'Set-Cookie': cookie 29 | }) 30 | res.send(music_req) 31 | }, 32 | err => res.status(502).send('fetch error') 33 | ) 34 | }) 35 | 36 | module.exports = router 37 | -------------------------------------------------------------------------------- /server/api/login_refresh.js: -------------------------------------------------------------------------------- 1 | 2 | const express = require('express') 3 | const router = express() 4 | const { createWebAPIRequest } = require('../../NeteaseCloudMusicApi/util/util') 5 | 6 | router.get('/', (req, res) => { 7 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 8 | const data = { 9 | csrf_token: '' 10 | } 11 | console.log({ cookie }) 12 | createWebAPIRequest( 13 | 'music.163.com', 14 | `/weapi/login/token/refresh`, 15 | 'POST', 16 | data, 17 | cookie, 18 | (music_req, cookie) => { 19 | if (cookie) { 20 | cookie = cookie.filter(x => !x.startsWith('MUSIC_U="";')) 21 | cookie = cookie.map(x => x.replace("Domain=.music.163.com", "")) 22 | 23 | res.set({ 24 | 'Set-Cookie': cookie 25 | }) 26 | } 27 | 28 | res.send(music_req) 29 | }, 30 | err => res.status(502).send('fetch error') 31 | ) 32 | }) 33 | 34 | module.exports = router 35 | -------------------------------------------------------------------------------- /server/api/scrobble.js: -------------------------------------------------------------------------------- 1 | 2 | const express = require('express') 3 | const router = express() 4 | const { createWebAPIRequest } = require('../../NeteaseCloudMusicApi/util/util') 5 | 6 | router.get('/', (req, res) => { 7 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 8 | const csrf = req.cookies['__csrf'] 9 | const songid = req.query.songid 10 | const sourceid = req.query.sourceid 11 | const time = +req.query.time 12 | const data = { 13 | csrf_token: csrf, 14 | logs: JSON.stringify([{ 15 | action: 'play', 16 | json: { 17 | download: 0, 18 | end: 'playend', 19 | id: +songid, 20 | sourceId: sourceid.toString(), 21 | time, 22 | type: 'song', 23 | wifi: 0, 24 | } 25 | }]) 26 | } 27 | 28 | createWebAPIRequest( 29 | 'music.163.com', 30 | `/weapi/feedback/weblog?csrf_token=${csrf}`, 31 | 'POST', 32 | data, 33 | cookie, 34 | music_req => { 35 | res.send(music_req) 36 | }, 37 | err => res.status(502).send('fetch error') 38 | ) 39 | }) 40 | 41 | module.exports = router 42 | -------------------------------------------------------------------------------- /server/api/sub.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../../NeteaseCloudMusicApi/util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const id = req.query.id 8 | const csrf = req.cookies['__csrf'] 9 | const data = { 10 | csrf_token: csrf, 11 | artistId: id, 12 | } 13 | 14 | createWebAPIRequest( 15 | 'music.163.com', 16 | `/weapi/artist/sub?csrf_token=${csrf}`, 17 | 'POST', 18 | data, 19 | cookie, 20 | music_req => { 21 | res.send(music_req) 22 | }, 23 | err => res.status(502).send('fetch error') 24 | ) 25 | }) 26 | 27 | module.exports = router -------------------------------------------------------------------------------- /server/api/subscribe.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../../NeteaseCloudMusicApi/util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const id = req.query.id 8 | const csrf = req.cookies['__csrf'] 9 | const data = { 10 | csrf_token: csrf, 11 | id, 12 | } 13 | 14 | createWebAPIRequest( 15 | 'music.163.com', 16 | `/weapi/playlist/subscribe?csrf_token=${csrf}`, 17 | 'POST', 18 | data, 19 | cookie, 20 | music_req => { 21 | res.send(music_req) 22 | }, 23 | err => res.status(502).send('fetch error') 24 | ) 25 | }) 26 | 27 | module.exports = router -------------------------------------------------------------------------------- /server/api/thumbsup.js: -------------------------------------------------------------------------------- 1 | 2 | const express = require('express') 3 | const router = express() 4 | const { createWebAPIRequest } = require('../../NeteaseCloudMusicApi/util/util') 5 | 6 | router.get('/', (req, res) => { 7 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 8 | const csrf = req.cookies['__csrf'] 9 | const cid = req.query.cid 10 | const tid = req.query.tid 11 | const like = !!+req.query.like 12 | const data = { 13 | csrf_token: csrf, 14 | commentId: cid, 15 | threadId: tid, 16 | like: like, 17 | } 18 | 19 | createWebAPIRequest( 20 | 'music.163.com', 21 | `/weapi/v1/comment/${like ? 'like' : 'unlike'}?csrf_token=${csrf}`, 22 | 'POST', 23 | data, 24 | `${cookie} osver=Version%2010.12.6%20(Build%2016G29); appver=1.5.9; os=osx; channel=netease;`, 25 | music_req => { 26 | res.send(music_req) 27 | }, 28 | err => res.status(502).send('fetch error') 29 | ) 30 | }) 31 | 32 | module.exports = router 33 | -------------------------------------------------------------------------------- /server/api/unfollow.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../../NeteaseCloudMusicApi/util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const id = req.query.id 8 | const csrf = req.cookies['__csrf'] 9 | const data = { 10 | csrf_token: csrf, 11 | followId: id, 12 | } 13 | 14 | createWebAPIRequest( 15 | 'music.163.com', 16 | `/weapi/user/delfollow/${id}?csrf_token=${csrf}`, 17 | 'POST', 18 | data, 19 | cookie, 20 | music_req => { 21 | res.send(music_req) 22 | }, 23 | err => res.status(502).send('fetch error') 24 | ) 25 | }) 26 | 27 | module.exports = router -------------------------------------------------------------------------------- /server/api/unsub.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../../NeteaseCloudMusicApi/util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const id = req.query.id 8 | const csrf = req.cookies['__csrf'] 9 | const data = { 10 | csrf_token: csrf, 11 | artistId: id, 12 | artistIds: [id], 13 | } 14 | 15 | console.log(data) 16 | 17 | createWebAPIRequest( 18 | 'music.163.com', 19 | `/weapi/artist/unsub?csrf_token=${csrf}`, 20 | 'POST', 21 | data, 22 | cookie, 23 | music_req => { 24 | res.send(music_req) 25 | }, 26 | err => res.status(502).send('fetch error') 27 | ) 28 | }) 29 | 30 | module.exports = router -------------------------------------------------------------------------------- /server/api/unsubscribe.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express() 3 | const { createWebAPIRequest } = require('../../NeteaseCloudMusicApi/util/util') 4 | 5 | router.get('/', (req, res) => { 6 | const cookie = req.get('Cookie') ? req.get('Cookie') : '' 7 | const id = req.query.id 8 | const csrf = req.cookies['__csrf'] 9 | const data = { 10 | csrf_token: csrf, 11 | pid: id, 12 | id, 13 | } 14 | 15 | createWebAPIRequest( 16 | 'music.163.com', 17 | `/weapi/playlist/unsubscribe?csrf_token=${csrf}`, 18 | 'POST', 19 | data, 20 | cookie, 21 | music_req => { 22 | res.send(music_req) 23 | }, 24 | err => res.status(502).send('fetch error') 25 | ) 26 | }) 27 | 28 | module.exports = router -------------------------------------------------------------------------------- /server/dev.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Setup and run the development server for Hot-Module-Replacement 4 | * https://webpack.github.io/docs/hot-module-replacement-with-webpack.html 5 | * @flow 6 | */ 7 | import express from 'express'; 8 | import webpack from 'webpack'; 9 | import webpackDevMiddleware from 'webpack-dev-middleware'; 10 | import webpackHotMiddleware from 'webpack-hot-middleware'; 11 | import _debug from 'debug'; 12 | 13 | import config from '../config'; 14 | import webpackConfig from '../config/webpack.config.dev'; 15 | 16 | const debug = _debug('dev:server'); 17 | const app = new express(); 18 | const compiler = webpack(webpackConfig); 19 | 20 | app.use( 21 | webpackDevMiddleware(compiler, { 22 | publicPath: webpackConfig.output.publicPath, 23 | stats: { 24 | colors: true 25 | }, 26 | }) 27 | ); 28 | app.use(webpackHotMiddleware(compiler)); 29 | 30 | app.listen(config.server.port, config.server.host, err => { 31 | if (err) { 32 | throw err; 33 | } 34 | 35 | debug(`Hot reload server is running with port ${config.server.port} 👏`); 36 | }); 37 | -------------------------------------------------------------------------------- /server/router/comments.js: -------------------------------------------------------------------------------- 1 | 2 | import express from 'express'; 3 | import axios from 'axios'; 4 | import _debug from 'debug'; 5 | 6 | const debug = _debug('dev:api'); 7 | const error = _debug('dev:error'); 8 | const router = express(); 9 | 10 | router.get('/like/:id/:songid/:like', async(req, res) => { 11 | debug('Handle request for /comments/like'); 12 | 13 | var result = { success: false }; 14 | var id = req.params.id; 15 | var songid = req.params.songid; 16 | var like = req.params.like; 17 | 18 | debug('Params \'id\': %s', id); 19 | debug('Params \'songid\': %s', songid); 20 | debug('Params \'liked\': %s', like); 21 | 22 | try { 23 | var response = await axios.get(`/thumbsup`, { 24 | params: { 25 | cid: id, 26 | tid: `R_SO_4_${songid}`, 27 | like: like, 28 | } 29 | }); 30 | var data = response.data; 31 | 32 | if (data.code !== 200) { 33 | throw data; 34 | } else { 35 | result = { 36 | success: true, 37 | liked: !!+like, 38 | }; 39 | } 40 | } catch (ex) { 41 | error('Failed to like comment: %O', ex); 42 | } 43 | 44 | res.send(result); 45 | }); 46 | 47 | router.get('/:id/:offset?', async(req, res) => { 48 | debug('Handle request for /comments'); 49 | 50 | var result = {}; 51 | var id = req.params.id; 52 | var offset = req.params.offset || 0; 53 | 54 | debug('Params \'id\': %s', id); 55 | debug('Params \'offset\': %s', offset); 56 | 57 | try { 58 | var response = await axios.get(`/comment/music`, { 59 | params: { 60 | id, 61 | limit: 30, 62 | offset, 63 | } 64 | }); 65 | var data = response.data; 66 | 67 | if (data.code !== 200) { 68 | throw data; 69 | } else { 70 | result = { 71 | newestList: data.comments, 72 | hotList: data.hotComments, 73 | total: data.total, 74 | nextHref: data.more ? `/api/comments/${id}/${+offset + 30}` : '', 75 | }; 76 | } 77 | } catch (ex) { 78 | error('Failed to get comments: %O', ex); 79 | } 80 | 81 | res.send(result); 82 | }); 83 | 84 | module.exports = router; 85 | -------------------------------------------------------------------------------- /server/router/dailyplayer.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import axios from 'axios'; 3 | import uuid from 'uuid'; 4 | import _debug from 'debug'; 5 | 6 | const debug = _debug('dev:api'); 7 | const error = _debug('dev:error'); 8 | const router = express(); 9 | 10 | // 获取用户的每日推荐歌曲 11 | async function getDaily() { 12 | var list = []; 13 | try { 14 | let response = await axios.get('/recommend/songs'); 15 | 16 | if (response.data.code !== 200) { 17 | throw response.data; 18 | } else { 19 | list = [{ 20 | id: uuid.v4(), 21 | name: '每日推荐歌曲', 22 | size: response.data.recommend.length, 23 | songs: response.data.recommend.map(e => { 24 | var { album, artists } = e; 25 | 26 | return { 27 | id: e.id.toString(), 28 | name: e.name, 29 | duration: e.duration, 30 | album: { 31 | id: album.id.toString(), 32 | name: album.name, 33 | cover: `${album.picUrl}?param=100y100`, 34 | link: `/player/1/${album.id}`, 35 | }, 36 | artists: artists.map(e => ({ 37 | id: e.id.toString(), 38 | name: e.name, 39 | // Broken link 40 | link: e.id ? `/artist/${e.id}` : '', 41 | })) 42 | }; 43 | }), 44 | }]; 45 | } 46 | } catch (ex) { 47 | error('Failed to get daily songs: %O', ex); 48 | } 49 | return list; 50 | } 51 | 52 | router.get('/:id?', async(req, res) => { 53 | debug('Handle request for /home'); 54 | 55 | var list = []; 56 | var unique = []; 57 | var id = req.params.id; 58 | 59 | if (id) { 60 | // 如果有用户id即登陆了,只取日推歌曲 61 | list = [ 62 | ...(await getDaily()), 63 | ]; 64 | } 65 | 66 | // debug('拿到日推歌曲信息', list); 67 | 68 | // Remove the duplicate items,去掉重复的歌曲 69 | list.map(e => { 70 | var index = unique.findIndex(item => item.id === e.id); 71 | // 没有相同的歌曲id才push 72 | if (index === -1) { 73 | unique.push(e); 74 | } 75 | }); 76 | 77 | res.send({ 78 | list: unique, 79 | }); 80 | }); 81 | 82 | module.exports = router; 83 | -------------------------------------------------------------------------------- /server/router/fm.js: -------------------------------------------------------------------------------- 1 | 2 | import express from 'express'; 3 | import axios from 'axios'; 4 | import _debug from 'debug'; 5 | 6 | const debug = _debug('dev:api'); 7 | const error = _debug('dev:error'); 8 | const router = express(); 9 | 10 | router.get('/', async(req, res) => { 11 | debug('Handle request for /fm'); 12 | 13 | var songs = []; 14 | 15 | try { 16 | let response = await axios.get(`/personal_fm`); 17 | let data = response.data; 18 | 19 | if (data.code !== 200) { 20 | throw data; 21 | } 22 | 23 | songs = data.data || []; 24 | } catch (ex) { 25 | error('Failed to get FM: %O', ex); 26 | } 27 | 28 | res.send({ 29 | id: 'PERSONAL_FM', 30 | name: 'My FM', 31 | link: '/fm', 32 | size: songs.length, 33 | songs: songs.map(e => { 34 | var { album, artists } = e; 35 | 36 | return { 37 | id: e.id.toString(), 38 | name: e.name, 39 | duration: e.duration, 40 | album: { 41 | id: album.id.toString(), 42 | name: album.name, 43 | cover: album.picUrl, 44 | link: `/player/1/${album.id}`, 45 | }, 46 | artists: artists.map(e => ({ 47 | id: e.id.toString(), 48 | name: e.name, 49 | // Broken link 50 | link: e.id ? `/artist/${e.id}` : '', 51 | })) 52 | }; 53 | }), 54 | }); 55 | }); 56 | 57 | module.exports = router; 58 | -------------------------------------------------------------------------------- /server/router/lyrics.js: -------------------------------------------------------------------------------- 1 | 2 | import express from 'express'; 3 | import axios from 'axios'; 4 | import _debug from 'debug'; 5 | 6 | const debug = _debug('dev:api'); 7 | const error = _debug('dev:error'); 8 | const router = express(); 9 | 10 | router.get('/:id', async(req, res) => { 11 | debug('Handle request for /lyrics'); 12 | 13 | var result = {}; 14 | var id = req.params.id; 15 | 16 | debug('Params \'id\': %s', id); 17 | 18 | try { 19 | var response = await axios.get(`/lyric`, { 20 | params: { 21 | id, 22 | } 23 | }); 24 | var data = response.data; 25 | 26 | if (data.code !== 200) { 27 | throw data; 28 | } else { 29 | let lyrics = data.lrc.lyric.split('\n'); 30 | 31 | lyrics.map(e => { 32 | let match = e.match(/\[.+\]/); 33 | 34 | if (!match) { 35 | return; 36 | } 37 | 38 | let timestamp = match[0].replace(/\D/g, ':').replace(/^:|:$/g, '').split(':'); 39 | let content = e.replace(/\[.+\]/, ''); 40 | let times = parseInt(+timestamp[0] * 60 * 1000) + parseInt(+timestamp[1] * 1000) + parseInt(timestamp[2]); 41 | 42 | result[times] = content; 43 | }); 44 | } 45 | } catch (ex) { 46 | error('Failed to get lyrics: %O', ex); 47 | } 48 | 49 | res.send(result); 50 | }); 51 | 52 | module.exports = router; 53 | -------------------------------------------------------------------------------- /server/router/playlist.js: -------------------------------------------------------------------------------- 1 | 2 | import express from 'express'; 3 | import axios from 'axios'; 4 | import _debug from 'debug'; 5 | 6 | const debug = _debug('dev:api'); 7 | const error = _debug('dev:error'); 8 | const router = express(); 9 | const limit = 50; 10 | 11 | router.get('/:type?/:offset?', async(req, res) => { 12 | debug('Handle request for /playlist'); 13 | 14 | var playlists = []; 15 | var type = req.params.type || '全部'; 16 | var offset = +req.params.offset || 0; 17 | var nextHref = ''; 18 | 19 | debug('Params \'type\': %s', type); 20 | 21 | try { 22 | let response = await axios.get('/top/playlist', { 23 | params: { 24 | cat: type, 25 | limit, 26 | offset, 27 | order: 'hot', 28 | }, 29 | }); 30 | let data = response.data; 31 | 32 | if (data.code !== 200) { 33 | throw data; 34 | } else { 35 | data.playlists.map(e => { 36 | var creator = e.creator; 37 | 38 | playlists.push({ 39 | id: e.id.toString(), 40 | name: e.name, 41 | played: e.playCount, 42 | size: e.trackCount, 43 | link: `/player/0/${e.id}`, 44 | cover: `${e.coverImgUrl}?param=100y100`, 45 | user: { 46 | id: creator.userId.toString(), 47 | name: creator.nickname, 48 | link: `/user/${creator.userId}`, 49 | }, 50 | }); 51 | }); 52 | } 53 | 54 | if (data.more) { 55 | offset += limit; 56 | nextHref = `/api/playlist/${type}/${offset}`; 57 | } 58 | } catch (ex) { 59 | error('Failed to get playlist: %O', ex); 60 | } 61 | 62 | res.send({ 63 | playlists, 64 | nextHref, 65 | }); 66 | }); 67 | 68 | module.exports = router; 69 | -------------------------------------------------------------------------------- /server/router/top.js: -------------------------------------------------------------------------------- 1 | 2 | import express from 'express'; 3 | import axios from 'axios'; 4 | import _debug from 'debug'; 5 | 6 | const debug = _debug('dev:api'); 7 | const error = _debug('dev:error'); 8 | const router = express(); 9 | 10 | router.get('/:ids', async(req, res) => { 11 | debug('Handle request for /top'); 12 | 13 | var list = []; 14 | var ids = req.params.ids; 15 | 16 | debug('Params \'ids\': %s', ids); 17 | 18 | ids = ids.split(','); 19 | 20 | try { 21 | list = await Promise.all(ids.map(async e => { 22 | let response = await axios.get(`/top/list?idx=${e}`); 23 | let data = response.data.result; 24 | 25 | if (response.data.code !== 200) { 26 | throw data; 27 | } 28 | 29 | return { 30 | name: data.name, 31 | played: data.playCount, 32 | updateTime: data.updateTime, 33 | size: data.trackCount, 34 | link: `/player/0/${data.id}`, 35 | cover: data.tracks[0]['album']['picUrl'], 36 | }; 37 | })); 38 | } catch (ex) { 39 | error('Failed to get top list: %O', ex); 40 | } 41 | 42 | res.send({ 43 | list, 44 | }); 45 | }); 46 | 47 | module.exports = router; 48 | -------------------------------------------------------------------------------- /server/search/Baidu.js: -------------------------------------------------------------------------------- 1 | 2 | import request from 'request-promise-native'; 3 | import _debug from 'debug'; 4 | 5 | const debug = _debug('dev:plugin:Baidu'); 6 | const error = _debug('dev:plugin:Baidu:error'); 7 | 8 | export default async(keyword, artists) => { 9 | debug(`Search '${keyword} - ${artists}' use Baidu library.`); 10 | 11 | var response = await request({ 12 | uri: 'http://sug.music.baidu.com/info/suggestion', 13 | qs: { 14 | word: [keyword].concat(artists.split(',')).join('+'), 15 | version: 2, 16 | from: 0, 17 | }, 18 | json: true, 19 | }); 20 | var songs = (response.data || {}).song; 21 | var song = (songs || []).find(e => artists.indexOf(e.artistname) > -1); 22 | 23 | if (!song) { 24 | debug('Nothing.'); 25 | return Promise.reject(); 26 | } 27 | response = await request({ 28 | uri: 'http://music.baidu.com/data/music/fmlink', 29 | qs: { 30 | songIds: song.songid, 31 | type: 'mp3', 32 | rate: '320', 33 | }, 34 | json: true, 35 | }); 36 | 37 | try { 38 | song = { 39 | src: response.data.songList[0].songLink, 40 | }; 41 | 42 | if (!song.src) { 43 | return Promise.reject(); 44 | } else { 45 | // debug('Got a result \n"%O"', song); 46 | } 47 | } catch (ex) { 48 | // Anti-warnning 49 | error('Failed to get song: %O', ex); 50 | return Promise.reject(); 51 | } 52 | 53 | return song; 54 | }; 55 | -------------------------------------------------------------------------------- /server/search/Kugou.js: -------------------------------------------------------------------------------- 1 | 2 | import request from 'request-promise-native'; 3 | import _debug from 'debug'; 4 | import md5 from 'md5'; 5 | 6 | const debug = _debug('dev:plugin:Kugou'); 7 | const error = _debug('dev:plugin:Kugou:error'); 8 | 9 | async function getURL(hash) { 10 | var key = md5(`${hash}kgcloud`); 11 | 12 | var response = await request({ 13 | url: `http://trackercdn.kugou.com/i/?acceptMp3=1&cmd=4&pid=6&hash=${hash}&key=${key}`, 14 | json: true, 15 | }); 16 | 17 | if (response.error 18 | || response.status !== 1) { 19 | debug('Nothing.'); 20 | return Promise.reject(); 21 | } else { 22 | return response.url; 23 | } 24 | } 25 | 26 | export default async(keyword, artists) => { 27 | debug(`Search '${keyword} - ${artists}' use Kugou library.`); 28 | 29 | var response = await request({ 30 | uri: 'http://mobilecdn.kugou.com/api/v3/search/song', 31 | qs: { 32 | format: 'json', 33 | keyword: [keyword].concat(artists.split(',')).join('+'), 34 | page: 1, 35 | pagesize: 1, 36 | showtype: 1, 37 | }, 38 | json: true, 39 | }); 40 | 41 | var data = response.data; 42 | 43 | if (response.status !== 1 44 | || data.info.length === 0) { 45 | error('Nothing.'); 46 | return Promise.reject(); 47 | } 48 | 49 | for (let e of data.info) { 50 | if (artists.split(',').find(artist => e.singername.indexOf(artist)) === -1) { 51 | continue; 52 | } 53 | 54 | // debug('Got a result \n"%O"', e); 55 | try { 56 | let song = { 57 | src: await getURL(e['320hash'] || e['hash']) 58 | }; 59 | 60 | // debug('%O', song); 61 | return song; 62 | } catch (ex) { 63 | error('Failed to get song: %O', ex); 64 | return Promise.reject(); 65 | } 66 | } 67 | }; 68 | -------------------------------------------------------------------------------- /server/search/Xiami.js: -------------------------------------------------------------------------------- 1 | 2 | import request from 'request-promise-native'; 3 | import _debug from 'debug'; 4 | 5 | const debug = _debug('dev:plugin:Xiami'); 6 | const error = _debug('dev:plugin:Xiami:error'); 7 | const headers = { 8 | cookie: 'user_from=2;XMPLAYER_addSongsToggler=0;XMPLAYER_isOpen=0;_xiamitoken=cb8bfadfe130abdbf5e2282c30f0b39a;', 9 | referer: 'http://h.xiami.com/', 10 | user_agent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36', 11 | }; 12 | 13 | export default async(keyword, artists) => { 14 | debug(`Search '${keyword} - ${artists}' use Xiami library.`); 15 | 16 | var response = await request({ 17 | uri: 'http://api.xiami.com/web', 18 | qs: { 19 | v: '2.0', 20 | key: [keyword].concat(artists.split(',')).join('+'), 21 | limit: 100, 22 | page: 1, 23 | r: 'search/songs', 24 | app_key: 1, 25 | }, 26 | json: true, 27 | headers, 28 | }); 29 | 30 | var data = response.data; 31 | 32 | if (response.state !== 0 33 | || data.songs.length === 0) { 34 | error('Nothing.'); 35 | return Promise.reject(); 36 | } 37 | 38 | for (let e of data.songs) { 39 | if (artists.split(',').find(artist => e.artist_name.indexOf(artist)) === -1) { 40 | continue; 41 | } 42 | 43 | let song = { 44 | src: e.listen_file, 45 | }; 46 | 47 | if (!song.src) { 48 | return Promise.reject(); 49 | } else { 50 | // debug('Got a result \n"%O"', song); 51 | return song; 52 | } 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /server/search/index.js: -------------------------------------------------------------------------------- 1 | 2 | import storage from 'electron-json-storage'; 3 | import QQ from './QQ'; 4 | import Kugou from './Kugou'; 5 | import Baidu from './Baidu'; 6 | import Xiami from './Xiami'; 7 | import _debug from 'debug'; 8 | 9 | const debug = _debug('dev:plugin'); 10 | 11 | async function getEnginers() { 12 | return new Promise(resolve => { 13 | storage.get('preferences', (err, data) => { 14 | var fallback = { 15 | 'QQ': true, 16 | 'Xiami': true, 17 | 'Kugou': false, 18 | 'Baidu': true, 19 | }; 20 | 21 | var enginers = fallback; 22 | 23 | if (!err) { 24 | enginers = data.enginers || fallback; 25 | } 26 | 27 | resolve(enginers); 28 | }); 29 | }); 30 | } 31 | 32 | export default async(keyword, artists) => { 33 | var enginers = await getEnginers(); 34 | var plugins = []; 35 | 36 | if (enginers['QQ']) { 37 | plugins.push(QQ); 38 | debug('Loaded plugin QQ'); 39 | } 40 | 41 | if (enginers['Xiami']) { 42 | plugins.push(Xiami); 43 | debug('Loaded plugin Xiami'); 44 | } 45 | 46 | if (enginers['Kugou']) { 47 | plugins.push(Kugou); 48 | debug('Loaded plugin Kugou'); 49 | } 50 | 51 | if (enginers['Baidu']) { 52 | plugins.push(Baidu); 53 | debug('Loaded plugin Baidu'); 54 | } 55 | 56 | debug('Plugin has loaded, search: \'%s\', \'%s\'', keyword, artists); 57 | 58 | return Promise.all( 59 | plugins.map(e => { 60 | // If a request failed will keep waiting for other possible successes, if a request successed, 61 | // treat it as a rejection so Promise.all immediate break. 62 | return e(keyword, artists).then( 63 | val => Promise.reject(val), 64 | err => Promise.resolve(err) 65 | ); 66 | }) 67 | ).then( 68 | errs => Promise.reject(errs), 69 | val => Promise.resolve(val), 70 | ); 71 | }; 72 | -------------------------------------------------------------------------------- /server/search/test.js: -------------------------------------------------------------------------------- 1 | 2 | import search from './index'; 3 | 4 | async function test() { 5 | var res = {}; 6 | try { 7 | res = await search('体面', '于文文'); 8 | console.log(res); 9 | } catch (ex) { 10 | console.error(ex); 11 | } 12 | return res; 13 | } 14 | 15 | test(); 16 | -------------------------------------------------------------------------------- /src/assets/WeChat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/src/assets/WeChat.jpg -------------------------------------------------------------------------------- /src/assets/WeChatGroup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/src/assets/WeChatGroup.png -------------------------------------------------------------------------------- /src/assets/close-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/src/assets/close-white.png -------------------------------------------------------------------------------- /src/assets/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/src/assets/close.png -------------------------------------------------------------------------------- /src/assets/dock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/src/assets/dock.png -------------------------------------------------------------------------------- /src/assets/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/src/assets/github.png -------------------------------------------------------------------------------- /src/assets/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/src/assets/loading.gif -------------------------------------------------------------------------------- /src/assets/notplaying.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/src/assets/notplaying.png -------------------------------------------------------------------------------- /src/assets/playing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/xbyjMusic/9232f35247bdc828805fd6485958b7ee0a7529e1/src/assets/playing.png -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | xbyjMusic 7 | 8 | 9 | 10 |
11 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/js/components/AboutMe/index.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { Component } from 'react'; 3 | import { inject, observer } from 'mobx-react'; 4 | import injectSheet from 'react-jss'; 5 | 6 | import classes from './classes'; 7 | import SyncCarousel from 'components/SyncCarousel'; 8 | 9 | @inject(stores => ({ 10 | show: stores.aboutme.show, 11 | close: () => stores.aboutme.toggle(false), 12 | })) 13 | @observer 14 | class AboutMe extends Component { 15 | componentWillReceiveProps(nextProps) { 16 | if (nextProps.show === true) { 17 | setTimeout(() => { 18 | this.refs.container.focus(); 19 | }); 20 | } 21 | } 22 | 23 | // 渲染同步轮播图SyncCarousel~ 24 | renderBannerlist() { 25 | var bannerList = [{pic: 'assets/github.png'}, {pic: 'assets/WeChat.jpg'}, {pic: 'assets/WeChatGroup.png'}]; 26 | return ( 27 | 28 | ); 29 | } 30 | 31 | render() { 32 | var { classes, show, close } = this.props; 33 | 34 | if (!show) { 35 | return false; 36 | } 37 | 38 | return ( 39 |
e.keyCode === 27 && this.props.close()} 43 | ref="container" 44 | tabIndex="-1"> 45 |
48 |
49 |
50 | { 51 | this.renderBannerlist() 52 | } 53 |
54 |
55 |
56 | ); 57 | } 58 | } 59 | 60 | export default injectSheet(classes)(AboutMe); 61 | -------------------------------------------------------------------------------- /src/js/components/Carousel/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Slider from 'react-slick'; 3 | // 引入react-slick轮播图的样式 4 | import '../../../../node_modules/slick-carousel/slick/slick.css'; 5 | import '../../../../node_modules/slick-carousel/slick/slick-theme.css'; 6 | 7 | // function SampleNextArrow(props) { 8 | // const { className, style, onClick } = props; 9 | // return ( 10 | //
15 | // ); 16 | // } 17 | 18 | // 首页的大轮播图 19 | export default class Carousel extends Component { 20 | render() { 21 | var bannerList = this.props.bannerList; 22 | var picWidth = this.props.picWidth; 23 | // console.log('picWidth', picWidth); 24 | const settings = { 25 | // className: 'center', 26 | centerMode: true, 27 | centerPadding: '85px', 28 | dots: true, 29 | infinite: true, 30 | swipeToSlide: true, 31 | // fade: true, 32 | slidesToShow: 1, 33 | slidesToScroll: 1, 34 | autoplay: true, 35 | autoplaySpeed: 5000, 36 | pauseOnHover: true, 37 | focusOnSelect: true, 38 | // nextArrow: , 39 | }; 40 | return ( 41 |
42 | 43 | { 44 | // 轮播图列表 45 | bannerList.map((e, index) => { 46 | return ( 47 |
49 | { 50 | 51 | } 52 | {/*

{index}

*/} 53 |
54 | ); 55 | }) 56 | } 57 |
58 |
59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/js/components/LeftMenu/classes.js: -------------------------------------------------------------------------------- 1 | 2 | export default theme => ({ 3 | 4 | aside: { 5 | display: 'inline-block', 6 | position: 'absolute', 7 | left: 0, 8 | top: 60, 9 | width: 200, 10 | height: 'calc(100vh - 124px)', 11 | color: '#333', 12 | borderRight: '1px solid #ddd', 13 | background: 'linear-gradient(to right,#e3e3e3,#f6f6f6)', 14 | overflow: 'hidden', 15 | overflowY: 'auto', 16 | 17 | '& a': { 18 | color: '#333', 19 | }, 20 | }, 21 | 22 | leftMenu: { 23 | margin: '4px 12px 18px' 24 | }, 25 | 26 | leftMenuSpan: { 27 | fontSize: 12, 28 | color: '#666', 29 | }, 30 | 31 | leftMenuItem: { 32 | fontSize: 14, 33 | margin: '10px 8px', 34 | 35 | '& i': { 36 | display: 'inline-block', 37 | width: 16, 38 | marginRight: 8, 39 | fontSize: 17, 40 | textAlign: 'center', 41 | }, 42 | 43 | '& p': { 44 | display: 'inline-block', 45 | width: 135, 46 | height: 16, 47 | margin: 0, 48 | overflow: 'hidden', 49 | whiteSpace: 'nowrap', 50 | textOverflow: 'ellipsis', 51 | }, 52 | }, 53 | 54 | }); 55 | -------------------------------------------------------------------------------- /src/js/components/Ripple/PlayerMode.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { Component } from 'react'; 3 | import { inject, observer } from 'mobx-react'; 4 | import injectSheet from 'react-jss'; 5 | 6 | import classes from './classes'; 7 | import { PLAYER_LOOP, PLAYER_SHUFFLE, PLAYER_REPEAT } from 'stores/controller'; 8 | 9 | @inject(stores => ({ 10 | mode: stores.controller.mode, 11 | })) 12 | @observer 13 | class PlayerMode extends Component { 14 | componentWillUpdate() { 15 | this.animationDone(); 16 | } 17 | 18 | componentDidUpdate() { 19 | this.refs.container.classList.add(this.props.classes.animated); 20 | } 21 | 22 | animationDone() { 23 | this.refs.container.classList.remove(this.props.classes.animated); 24 | } 25 | 26 | renderIndicator(mode) { 27 | switch (mode) { 28 | case PLAYER_SHUFFLE: 29 | return ; 30 | 31 | case PLAYER_REPEAT: 32 | return ; 33 | 34 | case PLAYER_LOOP: 35 | return ; 36 | } 37 | } 38 | 39 | render() { 40 | var { classes, mode } = this.props; 41 | 42 | return ( 43 |
this.animationDone()} 46 | ref="container"> 47 | { 48 | this.renderIndicator(mode) 49 | } 50 |
51 | ); 52 | } 53 | } 54 | 55 | export default injectSheet(classes)(PlayerMode); 56 | -------------------------------------------------------------------------------- /src/js/components/Ripple/PlayerNavigation.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { Component } from 'react'; 3 | import { ipcRenderer } from 'electron'; 4 | import injectSheet from 'react-jss'; 5 | 6 | import classes from './classes'; 7 | 8 | class PlayerNavigation extends Component { 9 | state = { 10 | // true: prev, false: next 11 | direction: true, 12 | }; 13 | 14 | componentWillUpdate() { 15 | this.animationDone(); 16 | } 17 | 18 | componentDidUpdate() { 19 | this.refs.container.classList.add(this.props.classes.animated); 20 | } 21 | 22 | animationDone() { 23 | this.shouldUpdate = false; 24 | this.refs.container.classList.remove(this.props.classes.animated); 25 | } 26 | 27 | shouldComponentUpdate() { 28 | return !!this.shouldUpdate; 29 | } 30 | 31 | componentDidMount() { 32 | ipcRenderer.on('player-previous', () => { 33 | this.shouldUpdate = true; 34 | this.setState({ 35 | direction: true, 36 | }); 37 | }); 38 | 39 | ipcRenderer.on('player-next', () => { 40 | this.shouldUpdate = true; 41 | this.setState({ 42 | direction: false, 43 | }); 44 | }); 45 | } 46 | 47 | render() { 48 | var classes = this.props.classes; 49 | 50 | return ( 51 |
this.animationDone()} 54 | ref="container"> 55 | { 56 | this.state.direction 57 | ? 58 | : 59 | } 60 |
61 | ); 62 | } 63 | } 64 | 65 | export default injectSheet(classes)(PlayerNavigation); 66 | -------------------------------------------------------------------------------- /src/js/components/Ripple/PlayerStatus.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { Component } from 'react'; 3 | import { inject, observer } from 'mobx-react'; 4 | import injectSheet from 'react-jss'; 5 | 6 | import classes from './classes'; 7 | 8 | @inject(stores => ({ 9 | playing: stores.controller.playing, 10 | })) 11 | @observer 12 | class PlayerStatus extends Component { 13 | componentWillUpdate() { 14 | this.animationDone(); 15 | } 16 | 17 | componentWillReceiveProps(nextProps) { 18 | if (nextProps.playing !== this.props.playing) { 19 | // Force show the animation 20 | this.animationDone(); 21 | } 22 | } 23 | 24 | componentDidUpdate() { 25 | this.refs.container.classList.add(this.props.classes.animated); 26 | } 27 | 28 | animationDone() { 29 | this.refs.container.classList.remove(this.props.classes.animated); 30 | } 31 | 32 | render() { 33 | var { classes, playing } = this.props; 34 | 35 | return ( 36 |
this.animationDone()} 39 | ref="container"> 40 | { 41 | playing 42 | ? 43 | : 44 | } 45 |
46 | ); 47 | } 48 | } 49 | 50 | export default injectSheet(classes)(PlayerStatus); 51 | -------------------------------------------------------------------------------- /src/js/components/Ripple/VolumeUpDown.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { Component } from 'react'; 3 | import { inject, observer } from 'mobx-react'; 4 | import { ipcRenderer } from 'electron'; 5 | import injectSheet from 'react-jss'; 6 | 7 | import classes from './classes'; 8 | 9 | @inject(stores => ({ 10 | isMuted: () => stores.preferences.volume === 0, 11 | })) 12 | @observer 13 | class VolumeUpDown extends Component { 14 | state = { 15 | // true: up, false: down 16 | direction: true, 17 | }; 18 | 19 | componentWillUpdate() { 20 | this.animationDone(); 21 | } 22 | 23 | componentDidUpdate() { 24 | this.refs.container.classList.add(this.props.classes.animated); 25 | } 26 | 27 | animationDone() { 28 | this.refs.container.classList.remove(this.props.classes.animated); 29 | } 30 | 31 | componentDidMount() { 32 | ipcRenderer.on('player-volume-up', () => { 33 | this.setState({ 34 | direction: true, 35 | }); 36 | }); 37 | 38 | ipcRenderer.on('player-volume-down', () => { 39 | this.setState({ 40 | direction: false, 41 | }); 42 | }); 43 | } 44 | 45 | render() { 46 | var { classes, isMuted } = this.props; 47 | 48 | return ( 49 |
this.animationDone()} 52 | ref="container"> 53 | { 54 | isMuted() 55 | ? 60 | : ( 61 | this.state.direction 62 | ? 63 | : 64 | ) 65 | } 66 |
67 | ); 68 | } 69 | } 70 | 71 | export default injectSheet(classes)(VolumeUpDown); 72 | -------------------------------------------------------------------------------- /src/js/components/Ripple/classes.js: -------------------------------------------------------------------------------- 1 | 2 | import helper from 'utils/helper'; 3 | 4 | export default theme => { 5 | var animationName = helper.randomName(); 6 | 7 | return { 8 | container: { 9 | position: 'fixed', 10 | right: '50%', 11 | top: '50%', 12 | width: 64, 13 | height: 64, 14 | lineHeight: '64px', 15 | borderRadius: 64, 16 | marginRight: -32, 17 | marginTop: -32, 18 | color: '#fff', 19 | fontSize: 24, 20 | textAlign: 'center', 21 | background: '#000', 22 | boxShadow: '0 30px 80px 0 rgba(97, 45, 45, .25)', 23 | zIndex: 999, 24 | opacity: 0, 25 | visibility: 'hidden', 26 | }, 27 | 28 | animated: { 29 | animationName, 30 | animationDuration: '800ms', 31 | animationIterationCount: 1, 32 | }, 33 | 34 | [`@keyframes ${animationName}`]: { 35 | '0%': { 36 | opacity: 0, 37 | transform: 'scale(.8)', 38 | visibility: 'hidden', 39 | }, 40 | 41 | '40%': { 42 | opacity: 1, 43 | transform: 'scale(1)', 44 | visibility: 'visible', 45 | }, 46 | 47 | '100%': { 48 | opacity: 0, 49 | transform: 'scale(1.2)', 50 | visibility: 'hidden', 51 | }, 52 | }, 53 | }; 54 | }; 55 | -------------------------------------------------------------------------------- /src/js/components/SyncCarousel/classes.js: -------------------------------------------------------------------------------- 1 | 2 | export default theme => ({ 3 | 4 | sliderOne: { 5 | 6 | '& img': { 7 | width: 200, 8 | cursor: 'pointer', 9 | margin: '0 auto', 10 | }, 11 | 12 | '& img:hover': { 13 | boxShadow: '0 1px 24px rgba(0, 0, 0, .24)', 14 | }, 15 | }, 16 | 17 | sliderTwo: { 18 | '& h3': { 19 | fontSize: 14, 20 | textAlign: 'center', 21 | color: '#555', 22 | fontFamily: 'monospace', 23 | } 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /src/js/components/SyncCarousel/index.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { Component } from 'react'; 3 | import Slider from 'react-slick'; 4 | import injectSheet from 'react-jss'; 5 | 6 | // 引入react-slick轮播图的样式 7 | import '../../../../node_modules/slick-carousel/slick/slick.css'; 8 | import '../../../../node_modules/slick-carousel/slick/slick-theme.css'; 9 | import classes from './classes'; 10 | 11 | // AboutMe组件的同步轮播图 12 | class AsNavFor extends Component { 13 | constructor(props) { 14 | super(props); 15 | this.state = { 16 | nav1: null, 17 | nav2: null 18 | }; 19 | } 20 | 21 | componentDidMount() { 22 | this.setState({ 23 | nav1: this.slider1, 24 | nav2: this.slider2 25 | }); 26 | } 27 | 28 | render() { 29 | var {bannerList, classes} = this.props; 30 | return ( 31 |
32 | (this.slider1 = slider)} 35 | autoplay={true} 36 | autoplaySpeed={5000} 37 | pauseOnHover={true} 38 | > 39 |
40 | 43 | 44 | 45 |
46 |
47 | { 48 | 49 | } 50 |
51 |
52 | { 53 | 54 | } 55 |
56 |
57 | (this.slider2 = slider)} 60 | autoplay={true} 61 | autoplaySpeed={5000} 62 | pauseOnHover={true} 63 | slidesToShow={1} 64 | > 65 |
66 |

欢迎star、fork一起完善本项目

67 |
68 |
69 |

关注微信公众号

70 |
71 |
72 |

加入技术交流群(备注自己GitHub账号)

73 |
74 |
75 |
76 | ); 77 | } 78 | } 79 | 80 | export default injectSheet(classes)(AsNavFor); 81 | -------------------------------------------------------------------------------- /src/js/pages/FM/classes.js: -------------------------------------------------------------------------------- 1 | 2 | import colors from 'utils/colors'; 3 | 4 | export default theme => { 5 | return { 6 | container: { 7 | backgroundColor: 'white', 8 | zIndex: 99, 9 | 10 | '& main': { 11 | position: 'fixed', 12 | top: 60, 13 | left: 200, 14 | width: 'calc(100vw - 200px)', 15 | height: 'calc(100vh - 124px)', 16 | overflowY: 'auto', 17 | }, 18 | }, 19 | 20 | unavailable: { 21 | display: 'flex', 22 | width: '100vw', 23 | height: 'calc(100vh - 50px)', 24 | justifyContent: 'center', 25 | flexDirection: 'column', 26 | alignItems: 'center', 27 | background: 'black', 28 | 29 | '& p': { 30 | fontWeight: 100, 31 | fontSize: 24, 32 | color: colors.pallet.dribbble 33 | }, 34 | 35 | '& a': { 36 | marginTop: 20, 37 | padding: '8px 12px', 38 | border: 0, 39 | fontSize: 14, 40 | fontWeight: 'lighter', 41 | color: 'white', 42 | background: 'transparent', 43 | letterSpacing: .5, 44 | textTransform: 'uppercase', 45 | borderBottom: 'thin solid white', 46 | outline: 0, 47 | } 48 | }, 49 | 50 | comments: { 51 | position: 'absolute', 52 | width: '100%', 53 | background: '#fafafa', 54 | zIndex: '-1', 55 | }, 56 | }; 57 | }; 58 | -------------------------------------------------------------------------------- /src/js/pages/Layout.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { Component } from 'react'; 3 | import injectSheet from 'react-jss'; 4 | import { inject, observer } from 'mobx-react'; 5 | import clazz from 'classname'; 6 | 7 | import Offline from 'ui/Offline'; 8 | import Loader from 'ui/Loader'; 9 | import lastfm from 'utils/lastfm'; 10 | import AudioPlayer from 'components/AudioPlayer'; 11 | import Search from 'components/Search'; 12 | import AboutMe from 'components/AboutMe'; 13 | import Playing from 'components/Playing'; 14 | import VolumeUpDown from 'components/Ripple/VolumeUpDown'; 15 | import PlayerNavigation from 'components/Ripple/PlayerNavigation'; 16 | import PlayerMode from 'components/Ripple/PlayerMode'; 17 | import PlayerStatus from 'components/Ripple/PlayerStatus'; 18 | 19 | const classes = { 20 | container: { 21 | position: 'fixed', 22 | left: 0, 23 | top: 0, 24 | width: '100vw', 25 | height: '100vh', 26 | }, 27 | 28 | mask: { 29 | filter: 'blur(10px)', 30 | }, 31 | }; 32 | 33 | @inject(stores => ({ 34 | initialized: stores.me.initialized, 35 | init: async() => { 36 | await stores.preferences.init(); 37 | await stores.me.init(); 38 | 39 | var { username, password } = stores.preferences.lastfm; 40 | 41 | await lastfm.initialize(username, password); 42 | }, 43 | hasLogin: stores.me.hasLogin, 44 | searching: stores.search.show, 45 | })) 46 | @observer 47 | class Layout extends Component { 48 | state = { 49 | offline: false, 50 | }; 51 | 52 | async componentWillMount() { 53 | await this.props.init(); 54 | } 55 | 56 | componentDidMount() { 57 | window.addEventListener('offline', () => { 58 | this.setState({ 59 | offline: true, 60 | }); 61 | }); 62 | 63 | window.addEventListener('online', () => { 64 | this.setState({ 65 | offline: false, 66 | }); 67 | }); 68 | } 69 | 70 | render() { 71 | var { classes, initialized, searching } = this.props; 72 | 73 | if (this.state.offline) { 74 | return ; 75 | } 76 | 77 | // Wait for app has initialized 78 | if (!initialized) { 79 | return ; 80 | } 81 | 82 | return ( 83 |
86 |
89 | {this.props.children} 90 |
91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 |
101 | ); 102 | } 103 | } 104 | 105 | export default injectSheet(classes)(Layout); 106 | -------------------------------------------------------------------------------- /src/js/pages/Player/Search/classes.js: -------------------------------------------------------------------------------- 1 | 2 | export default theme => ({ 3 | container: { 4 | position: 'absolute', 5 | top: 34, 6 | right: 0, 7 | width: '100%', 8 | height: '100%', 9 | background: '#fff', 10 | overflow: 'hidden', 11 | zIndex: 99, 12 | 13 | '& header': { 14 | display: 'flex', 15 | justifyContent: 'space-between', 16 | alignItems: 'center', 17 | height: 66, 18 | paddingLeft: 24, 19 | paddingRight: 16, 20 | boxShadow: 'inset 0 0 0.7px 0 rgba(0, 0, 0, .5)', 21 | }, 22 | 23 | '& header input': { 24 | height: 40, 25 | lineHeight: '40px', 26 | width: '100%', 27 | fontFamily: 'HelveticaNeue-UltraLight', 28 | fontSize: 14, 29 | border: 0, 30 | outline: 0, 31 | } 32 | }, 33 | 34 | close: { 35 | height: 32, 36 | cursor: 'pointer', 37 | }, 38 | 39 | list: { 40 | '& ul': { 41 | height: 'calc(100vh - 52px - 66px) !important', 42 | } 43 | } 44 | }); 45 | -------------------------------------------------------------------------------- /src/js/pages/Player/Search/index.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { Component } from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import injectSheet from 'react-jss'; 5 | 6 | import classes from './classes'; 7 | 8 | class Search extends Component { 9 | static propsTypes = { 10 | show: PropTypes.bool.isRequired, 11 | close: PropTypes.func.isRequired, 12 | }; 13 | 14 | pressEscExit(e) { 15 | if (e.keyCode === 27) { 16 | this.props.close(); 17 | } 18 | } 19 | 20 | render() { 21 | var { classes, show, close, filter, children } = this.props; 22 | 23 | if (!show) { 24 | return false; 25 | } 26 | 27 | return ( 28 |
this.pressEscExit(e)}> 31 |
32 | filter(e.target.value)} 36 | placeholder="歌名、歌手、专辑都可以搜哦~" /> 37 | Close 42 |
43 | 44 |
45 | {children} 46 |
47 |
48 | ); 49 | } 50 | } 51 | 52 | export default injectSheet(classes)(Search); 53 | -------------------------------------------------------------------------------- /src/js/pages/Song/classes.js: -------------------------------------------------------------------------------- 1 | 2 | export default theme => ({ 3 | container: { 4 | backgroundColor: 'white', 5 | zIndex: 99, 6 | 7 | '& main': { 8 | position: 'fixed', 9 | top: 60, 10 | width: 'calc(100vw)', 11 | height: 'calc(100vh - 124px)', 12 | overflowY: 'auto', 13 | }, 14 | }, 15 | 16 | comments: { 17 | position: 'absolute', 18 | width: '100%', 19 | background: '#fafafa', 20 | zIndex: '-1', 21 | }, 22 | 23 | recommend: { 24 | position: 'absolute', 25 | top: 60, 26 | right: 10, 27 | width: '35vw', 28 | height: '100%', 29 | color: '#000', 30 | }, 31 | 32 | recommendTitleWrap: { 33 | display: 'inline-block', 34 | width: 310, 35 | height: 31, 36 | borderBottom: '1px solid #ccc' 37 | }, 38 | 39 | recommendTitle: { 40 | color: '#222', 41 | fontSize: 20, 42 | marginRight: 10, 43 | paddingBottom: 5, 44 | borderBottom: '4px solid #ccc' 45 | }, 46 | 47 | recomPlayList: { 48 | position: 'absolute', 49 | left: 0, 50 | top: 0, 51 | height: 200, 52 | 53 | '& a': { 54 | color: '#666', 55 | width: 310, 56 | margin: '18px 0 0 0', 57 | 58 | '&:hover': { 59 | background: '#f0f0f0', 60 | } 61 | }, 62 | 63 | '& figure': { 64 | display: 'inline-block', 65 | }, 66 | }, 67 | 68 | recomPlayListContent: { 69 | position: 'absolute', 70 | fontSize: 12, 71 | lineHeight: '6px', 72 | margin: '-5px 0 0 10px', 73 | }, 74 | 75 | simiSong: { 76 | position: 'absolute', 77 | left: 0, 78 | top: 270, 79 | height: 350, 80 | 81 | '& a': { 82 | color: '#666', 83 | width: 310, 84 | margin: '18px 0 0 0', 85 | 86 | '&:hover': { 87 | background: '#f0f0f0', 88 | } 89 | }, 90 | 91 | '& figure': { 92 | display: 'inline-block', 93 | }, 94 | }, 95 | 96 | users: { 97 | position: 'absolute', 98 | left: 0, 99 | top: 670, 100 | height: 350, 101 | 102 | '& a': { 103 | color: '#666', 104 | width: 310, 105 | margin: '18px 0 0 0', 106 | 107 | '&:hover': { 108 | background: '#f0f0f0', 109 | } 110 | }, 111 | 112 | '& figure': { 113 | display: 'inline-block', 114 | borderRadius: 40, 115 | }, 116 | }, 117 | 118 | recomUserContent: { 119 | position: 'absolute', 120 | fontSize: 12, 121 | lineHeight: '6px', 122 | margin: '10px 0 0 10px', 123 | width: 260, 124 | }, 125 | }); 126 | -------------------------------------------------------------------------------- /src/js/pages/Top/classes.js: -------------------------------------------------------------------------------- 1 | 2 | export default theme => ({ 3 | container: { 4 | }, 5 | 6 | column: { 7 | display: 'inline-block', 8 | height: 'calc((100vh - 124px))', 9 | marginTop: 60, 10 | }, 11 | 12 | item: { 13 | position: 'relative', 14 | display: 'flex', 15 | height: 'calc((100vh - 124px) / 2)', 16 | width: 'calc((100vh - 124px) / 2)', 17 | alignItems: 'center', 18 | justifyContent: 'center', 19 | flexDirection: 'column', 20 | color: '#fff', 21 | textAlign: 'center', 22 | overflow: 'hidden', 23 | 24 | '& p': { 25 | fontSize: 24, 26 | }, 27 | 28 | '& img:first-of-type': { 29 | width: '100%', 30 | height: '100%', 31 | transition: '.2s ease-in', 32 | }, 33 | 34 | '&:after': { 35 | content: '""', 36 | position: 'absolute', 37 | left: 0, 38 | top: 0, 39 | display: 'block', 40 | height: '100%', 41 | width: '100%', 42 | background: 'rgba(0, 0, 0, .7)', 43 | visibility: 'visible', 44 | transition: '.2s', 45 | }, 46 | 47 | '&:hover img:first-of-type': { 48 | transform: 'scale(1.1)', 49 | }, 50 | 51 | '&:hover:after': { 52 | background: 'rgba(0, 0, 0, .3)', 53 | } 54 | }, 55 | 56 | info: { 57 | position: 'absolute', 58 | color: '#fff', 59 | zIndex: 1, 60 | }, 61 | 62 | line: { 63 | height: 4, 64 | width: 170, 65 | margin: '0 auto', 66 | marginTop: -10, 67 | marginBottom: 24, 68 | background: '#fff', 69 | } 70 | }); 71 | -------------------------------------------------------------------------------- /src/js/routes.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { IndexRoute, Route } from 'react-router'; 4 | 5 | import Layout from './pages/Layout'; 6 | import Home from './pages/Home'; 7 | import Login from './pages/Login'; 8 | import Player from './pages/Player'; 9 | import User from './pages/User'; 10 | import Artist from './pages/Artist'; 11 | import Top from './pages/Top'; 12 | import Playlist from './pages/Playlist'; 13 | import FM from './pages/FM'; 14 | import Preferences from './pages/Preferences'; 15 | import Song from './pages/Song'; 16 | import dailyPlayer from './pages/dailyPlayer'; 17 | import stores from 'stores'; 18 | 19 | function requireAuth(nextState, replace) { 20 | // console.warn(stores.me.hasLogin()); 21 | if (!stores.me.hasLogin()) { 22 | replace({ 23 | pathname: '/login/0' 24 | }); 25 | } 26 | } 27 | 28 | export default () => { 29 | return ( 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | ); 44 | }; 45 | -------------------------------------------------------------------------------- /src/js/stores/aboutme.js: -------------------------------------------------------------------------------- 1 | 2 | import { observable, action } from 'mobx'; 3 | 4 | class AboutMe { 5 | @observable show = false; 6 | 7 | @action toggle(show = !self.show) { 8 | self.show = show; 9 | } 10 | } 11 | 12 | const self = new AboutMe(); 13 | export default self; 14 | -------------------------------------------------------------------------------- /src/js/stores/artist.js: -------------------------------------------------------------------------------- 1 | 2 | import { observable, action } from 'mobx'; 3 | import axios from 'axios'; 4 | 5 | class Artist { 6 | @observable loading = true; 7 | 8 | // Profile of the artist 9 | @observable profile = {}; 10 | 11 | // All albums of artist 12 | @observable albums = []; 13 | 14 | // Similar artists 15 | @observable similar = []; 16 | 17 | // Contains 'id' and 'songs' 18 | @observable playlist = { 19 | songs: [], 20 | }; 21 | 22 | @action async getArtist(id) { 23 | self.loading = true; 24 | 25 | var response = await axios.get(`/api/artist/${id}`); 26 | var data = response.data; 27 | 28 | if (data) { 29 | self.profile = data.profile; 30 | self.playlist = data.playlist; 31 | self.albums = data.albums; 32 | self.similar = data.similar; 33 | } 34 | 35 | self.loading = false; 36 | } 37 | 38 | @action async follow(followed, id = self.profile.id) { 39 | var response = await axios.get( 40 | followed 41 | ? `/api/artist/unfollow/${id}` 42 | : `/api/artist/follow/${id}` 43 | ); 44 | var data = response.data; 45 | 46 | if (data.success) { 47 | self.profile = Object.assign({}, self.profile, { 48 | followed: !followed, 49 | }); 50 | } 51 | 52 | return data.success; 53 | } 54 | } 55 | 56 | const self = new Artist(); 57 | export default self; 58 | -------------------------------------------------------------------------------- /src/js/stores/comments.js: -------------------------------------------------------------------------------- 1 | 2 | import { observable, action } from 'mobx'; 3 | import axios from 'axios'; 4 | 5 | import controller from './controller'; 6 | 7 | class Comments { 8 | @observable loading = true; 9 | @observable show = false; 10 | @observable hotList = []; 11 | @observable newestList = []; 12 | @observable total = 0; 13 | @observable song = { 14 | album: {}, 15 | artist: [], 16 | }; 17 | 18 | nextHref = ''; 19 | 20 | @action toggle(show = !self.show) { 21 | self.show = show; 22 | } 23 | 24 | @action async getList(song) { 25 | self.loading = true; 26 | 27 | var response = await axios.get(`/api/comments/${song.id}`); 28 | var data = response.data; 29 | 30 | self.song = song; 31 | self.hotList = data.hotList; 32 | self.newestList = data.newestList; 33 | self.total = data.total; 34 | self.nextHref = data.nextHref; 35 | self.loading = false; 36 | } 37 | 38 | @action async like(id, liked) { 39 | var response = await axios.get(`/api/comments/like/${id}/${controller.song.id}/${+liked}`); 40 | var data = response.data; 41 | 42 | if (data.success === true) { 43 | let comment = [...self.hotList.slice(), ...self.newestList.slice()].find(e => e.commentId === id); 44 | 45 | comment.likedCount += liked ? 1 : -1; 46 | comment.liked = liked; 47 | } 48 | } 49 | 50 | @action async loadmore() { 51 | if (!self.nextHref) { 52 | return; 53 | } 54 | 55 | var response = await axios.get(self.nextHref); 56 | var data = response.data; 57 | 58 | self.newestList.push(...data.newestList); 59 | self.nextHref = data.nextHref; 60 | } 61 | } 62 | 63 | const self = new Comments(); 64 | export default self; 65 | -------------------------------------------------------------------------------- /src/js/stores/dailyplayer.js: -------------------------------------------------------------------------------- 1 | 2 | import { observable, action } from 'mobx'; 3 | import axios from 'axios'; 4 | 5 | import me from './me'; 6 | 7 | class dailyPlayer { 8 | @observable loading = true; 9 | @observable list = []; 10 | 11 | @action async load() { 12 | var res; 13 | 14 | try { 15 | res = await axios.get(`api/dailyplayer/${me.profile.userId}`); 16 | } catch (e) { 17 | console.log('请求日推歌曲数据出错', e); 18 | }; 19 | self.list = res.data.list[0]; 20 | return self.list; 21 | } 22 | 23 | @action async getList() { 24 | self.loading = true; 25 | await self.load(); 26 | 27 | // Just call once for init player 28 | self.getList = Function; 29 | self.loading = false; 30 | } 31 | } 32 | 33 | const self = new dailyPlayer(); 34 | export default self; 35 | -------------------------------------------------------------------------------- /src/js/stores/fm.js: -------------------------------------------------------------------------------- 1 | 2 | import { observable, action } from 'mobx'; 3 | import axios from 'axios'; 4 | 5 | import controller from './controller'; 6 | 7 | class FM { 8 | @observable loading = true; 9 | @observable song = {}; 10 | @observable playlist = { 11 | songs: [], 12 | }; 13 | 14 | preload() { 15 | controller.changeMode(); 16 | self.shuffle(); 17 | self.preload = Function; 18 | } 19 | 20 | @action async shuffle() { 21 | self.loading = true; 22 | 23 | var response = await axios.get(`/api/fm`); 24 | self.playlist = response.data; 25 | self.song = self.playlist.songs[0]; 26 | self.loading = false; 27 | } 28 | 29 | @action play() { 30 | if (controller.playlist.id === self.playlist.id) { 31 | controller.toggle(); 32 | return; 33 | } 34 | 35 | controller.setup(self.playlist); 36 | controller.play(); 37 | } 38 | 39 | // Ban a song 40 | @action async ban(id) { 41 | var response = await axios.get(`/fm_trash?id=${id}`); 42 | 43 | if (response.data.code === 200) { 44 | self.next(); 45 | } 46 | } 47 | 48 | @action async next() { 49 | var index = self.playlist.songs.findIndex(e => e.id === controller.song.id); 50 | 51 | if (controller.playlist.id !== self.playlist.id) { 52 | self.play(); 53 | return; 54 | } 55 | 56 | if (++index < self.playlist.songs.length) { 57 | let next = self.playlist.songs[index]; 58 | 59 | controller.play(next.id); 60 | return; 61 | } 62 | 63 | // Refresh the playlist 64 | await self.shuffle(); 65 | controller.setup(self.playlist); 66 | controller.play(); 67 | } 68 | } 69 | 70 | const self = new FM(); 71 | export default self; 72 | -------------------------------------------------------------------------------- /src/js/stores/home.js: -------------------------------------------------------------------------------- 1 | 2 | import { observable, action } from 'mobx'; 3 | import axios from 'axios'; 4 | 5 | import me from './me'; 6 | import preferences from './preferences'; 7 | import controller from './controller'; 8 | 9 | class Home { 10 | @observable loading = true; 11 | @observable list = []; 12 | @observable bannerList = []; 13 | 14 | @action async load() { 15 | var res; 16 | 17 | if (me.hasLogin()) { 18 | // 登陆时,获取到首页所有的歌曲数据 19 | res = await axios.get(`/api/home/${me.profile.userId}`); 20 | // 结果数组的第一个元素是“我喜欢的音乐”数组,第二个是“日推歌单” 21 | let favorite = res.data.list[0]; 22 | let recommend = res.data.list[1]; 23 | 24 | // Save the songs of red heart 25 | me.rocking(favorite); 26 | 27 | if (recommend.length) { 28 | // Play the recommend songs 29 | controller.setup(recommend); 30 | } else { 31 | controller.setup(favorite); 32 | } 33 | } else { 34 | // 未登陆时拿热门歌单 35 | res = await axios.get(`/api/home`); 36 | controller.setup(res.data.list[0]); 37 | } 38 | 39 | if (preferences.autoPlay) { 40 | controller.play(); 41 | } else { 42 | controller.song = controller.playlist.songs[0]; 43 | } 44 | 45 | self.list = res.data.list; 46 | 47 | return self.list; 48 | } 49 | 50 | @action async loadBanner() { 51 | var res; 52 | 53 | // 获取到首页banner轮播图信息,官方api参数不明,所以数据比较旧 54 | res = await axios.get(`/banner`); 55 | 56 | self.bannerList = res.data.banners; 57 | 58 | return self.bannerList; 59 | } 60 | 61 | @action async getList() { 62 | self.loading = true; 63 | 64 | await self.load(); 65 | await self.loadBanner(); 66 | 67 | // Just call once for init player 68 | self.getList = Function; 69 | self.loading = false; 70 | } 71 | } 72 | 73 | const self = new Home(); 74 | export default self; 75 | -------------------------------------------------------------------------------- /src/js/stores/index.js: -------------------------------------------------------------------------------- 1 | 2 | import me from './me'; 3 | import aboutme from './aboutme'; 4 | import home from './home'; 5 | import user from './user'; 6 | import controller from './controller'; 7 | import player from './player'; 8 | import artist from './artist'; 9 | import top from './top'; 10 | import playlist from './playlist'; 11 | import fm from './fm'; 12 | import playing from './playing'; 13 | import search from './search'; 14 | import comments from './comments'; 15 | import lyrics from './lyrics'; 16 | import preferences from './preferences'; 17 | import dailyplayer from './dailyplayer'; 18 | 19 | const stores = { 20 | me, 21 | aboutme, 22 | home, 23 | user, 24 | controller, 25 | player, 26 | artist, 27 | top, 28 | playlist, 29 | fm, 30 | playing, 31 | search, 32 | comments, 33 | lyrics, 34 | preferences, 35 | dailyplayer 36 | }; 37 | 38 | export default stores; 39 | -------------------------------------------------------------------------------- /src/js/stores/lyrics.js: -------------------------------------------------------------------------------- 1 | 2 | import { observable, action } from 'mobx'; 3 | import axios from 'axios'; 4 | 5 | import controller from './controller'; 6 | 7 | class Lyrics { 8 | @observable loading = true; 9 | @observable show = false; 10 | @observable list = {}; 11 | 12 | @action toggle(show = !self.show) { 13 | self.show = show; 14 | } 15 | 16 | @action async getLyrics() { 17 | self.loading = true; 18 | 19 | var response = await axios.get(`/api/lyrics/${controller.song.id}`); 20 | var data = response.data; 21 | 22 | self.list = data; 23 | self.loading = false; 24 | } 25 | } 26 | 27 | const self = new Lyrics(); 28 | export default self; 29 | -------------------------------------------------------------------------------- /src/js/stores/playing.js: -------------------------------------------------------------------------------- 1 | 2 | import { observable, action } from 'mobx'; 3 | import han from 'han'; 4 | 5 | import controller from './controller'; 6 | 7 | class Playing { 8 | @observable show = false; 9 | @observable filtered = []; 10 | 11 | @action toggle(show = !self.show) { 12 | self.show = show; 13 | } 14 | 15 | @action doFilter(text) { 16 | var songs = []; 17 | 18 | // Convert text to chinese pinyin 19 | text = han.letter(text.trim()); 20 | 21 | songs = controller.playlist.songs.filter(e => { 22 | return false 23 | // Fuzzy match the song name 24 | || han.letter(e.name).indexOf(text) > -1 25 | // Fuzzy match the album name 26 | || han.letter(e.album.name).indexOf(text) > -1 27 | // Mathc the artist name 28 | || e.artists.findIndex(e => han.letter(e.name).indexOf(text) > -1) !== -1 29 | ; 30 | }); 31 | 32 | self.filtered = songs; 33 | } 34 | 35 | filter(text = '') { 36 | clearTimeout(self.timer); 37 | self.timer = setTimeout(() => self.doFilter(text), 50); 38 | } 39 | } 40 | 41 | const self = new Playing(); 42 | export default self; 43 | -------------------------------------------------------------------------------- /src/js/stores/top.js: -------------------------------------------------------------------------------- 1 | 2 | import { observable, action } from 'mobx'; 3 | import axios from 'axios'; 4 | 5 | class Top { 6 | @observable loading = true; 7 | @observable list = []; 8 | 9 | @action async getList() { 10 | self.loading = true; 11 | 12 | /** 13 | "0": 云音乐新歌榜, 14 | "1": 云音乐热歌榜, 15 | "2": 网易原创歌曲榜, 16 | "3": 云音乐飙升榜, 17 | "4": 云音乐电音榜, 18 | "5": UK排行榜周榜, 19 | "6": 美国Billboard周榜 20 | "7": KTV嗨榜, 21 | "8": iTunes榜, 22 | "9": Hit FM Top榜, 23 | "10": 日本Oricon周榜 24 | "11": 韩国Melon排行榜周榜, 25 | "12": 韩国Mnet排行榜周榜, 26 | "13": 韩国Melon原声周榜, 27 | "14": 中国TOP排行榜(港台榜), 28 | "15": 中国TOP排行榜(内地榜) 29 | "16": 香港电台中文歌曲龙虎榜, 30 | "17": 华语金曲榜, 31 | "18": 中国嘻哈榜, 32 | "19": 法国 NRJ EuroHot 30周榜, 33 | "20": 台湾Hito排行榜, 34 | "21": Beatport全球电子舞曲榜 35 | * */ 36 | var response = await axios.get('/api/top/0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21'); 37 | 38 | self.list = response.data.list; 39 | self.loading = false; 40 | } 41 | } 42 | 43 | const self = new Top(); 44 | export default self; 45 | -------------------------------------------------------------------------------- /src/js/stores/user.js: -------------------------------------------------------------------------------- 1 | 2 | import { observable, action } from 'mobx'; 3 | import axios from 'axios'; 4 | 5 | class User { 6 | @observable loading = true; 7 | @observable profile = {}; 8 | @observable playlists = []; 9 | 10 | @action async getUser(userid) { 11 | self.loading = true; 12 | 13 | var response = await axios.get(`/api/user/${userid}`); 14 | 15 | self.profile = response.data.profile; 16 | self.playlists = response.data.playlists; 17 | self.loading = false; 18 | } 19 | 20 | @action async follow(followed) { 21 | var response = await axios.get( 22 | followed 23 | ? `/api/user/unfollow/${self.profile.id}` 24 | : `/api/user/follow/${self.profile.id}` 25 | ); 26 | var data = response.data; 27 | 28 | if (data.success) { 29 | self.profile = Object.assign({}, self.profile, { 30 | followed: !followed, 31 | }); 32 | } 33 | } 34 | } 35 | 36 | const self = new User(); 37 | export default self; 38 | -------------------------------------------------------------------------------- /src/js/ui/FadeImage/classes.js: -------------------------------------------------------------------------------- 1 | 2 | export default theme => ({ 3 | fade: { 4 | opacity: 1, 5 | transition: '.2s', 6 | }, 7 | 8 | fadein: { 9 | opacity: 0 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /src/js/ui/FadeImage/index.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { Component, PropTypes } from 'react'; 3 | import injectSheet from 'react-jss'; 4 | import clazz from 'classname'; 5 | 6 | import classes from './classes'; 7 | 8 | class FadeImage extends Component { 9 | static propTypes = { 10 | src: PropTypes.string, 11 | fallback: PropTypes.string, 12 | }; 13 | 14 | static defaultProps = { 15 | fallback: 'https://source.unsplash.com/random', 16 | }; 17 | 18 | componentWillReceiveProps(nextProps) { 19 | var ele = this.refs.image; 20 | 21 | if (ele 22 | && this.props.src !== nextProps.src) { 23 | ele.classList.add(nextProps.classes.fadein); 24 | } 25 | } 26 | 27 | handleError(e) { 28 | e.target.src = this.props.fallback; 29 | } 30 | 31 | handleLoad(e) { 32 | e.target.classList.remove(this.props.classes.fadein); 33 | } 34 | 35 | render() { 36 | var classes = this.props.classes; 37 | 38 | if (!this.props.src) return false; 39 | 40 | return ( 41 | this.handleLoad(e)} 46 | onError={e => this.handleError(e)} /> 47 | ); 48 | } 49 | } 50 | 51 | export default injectSheet(classes)(FadeImage); 52 | -------------------------------------------------------------------------------- /src/js/ui/Loader/classes.js: -------------------------------------------------------------------------------- 1 | 2 | export default theme => { 3 | return { 4 | container: { 5 | position: 'fixed', 6 | top: 60, 7 | left: 0, 8 | display: 'flex', 9 | width: '100vw', 10 | height: 'calc(100vh - 124px)', 11 | justifyContent: 'center', 12 | alignItems: 'center', 13 | background: 'white', 14 | fontFamily: 'HelveticaNeue-UltraLight', 15 | fontSize: 24, 16 | opacity: 0, 17 | visibility: 'hidden', 18 | transition: '.2s', 19 | 20 | '& span': { 21 | marginTop: '8%', 22 | maxWidth: '60vw', 23 | textAlign: 'center', 24 | lineHeight: '32px', 25 | color: '#ea4c89', 26 | }, 27 | 28 | '&:before': { 29 | content: 'url(assets/loading.gif)', 30 | position: 'absolute', 31 | width: '140px', 32 | height: '140px', 33 | top: '36%', 34 | left: '50%', 35 | display: 'block', 36 | transform: 'translateX(-25%)', 37 | }, 38 | }, 39 | 40 | show: { 41 | opacity: 1, 42 | visibility: 'visible', 43 | zIndex: 1000, 44 | }, 45 | }; 46 | }; 47 | -------------------------------------------------------------------------------- /src/js/ui/Loader/index.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { Component, PropTypes } from 'react'; 3 | import injectSheet from 'react-jss'; 4 | import clazz from 'classname'; 5 | 6 | import classes from './classes'; 7 | 8 | /** 9 | * FROM NGA: 10 | * http://bbs.ngacn.cc/read.php?tid=7803294&forder_by=postdatedesc&_ff=7 11 | * */ 12 | const randomText = [ 13 | '当你觉得孤独无助时,想一想还有十几亿的细胞只为了你一个人而活', 14 | '人要是矫情起来,听什么都像是听自己', 15 | '每个人的裂痕,最后都会变成故事的花纹 ', 16 | '你那么孤独,却说一个人真好', 17 | '我在最没有能力的年纪,碰见了最想照顾一生的人', 18 | '世界如此广阔,人类却走进了悲伤的墙角', 19 | '喜欢这种东西,捂住嘴巴,也会从眼睛里跑出来', 20 | '我听过一万首歌,看过一千部电影,读过一百本书,却从未俘获一个人的心', 21 | '最怕一生碌碌无为,还说平凡难能可贵 ', 22 | '年轻时我想变成任何人,除了我自己 ', 23 | '别人稍一注意你,你就敞开心扉,你觉得这是坦率,其实这是孤独……', 24 | '我已经过了餐桌上有只鸡就一定能吃到鸡腿的年纪了', 25 | '不在一起就不在一起吧,反正一辈子也没多长', 26 | '你别皱眉,我走就好', 27 | '那年上初中,夏天是好漫长的,西瓜是吃不完的,作业是最后两天才赶的', 28 | '小时候总是骗爸妈自己没钱了,现在骗他们自己还有钱', 29 | '你那么擅长安慰他人,一定度过了很多自己安慰自己的日子吧', 30 | '余生好长,你好难忘', 31 | '我从未拥有过你一秒钟,心里却失去过你千万次', 32 | '你是来和我告别的吗。那就隆重一点,等我眼里装满泪水', 33 | '机场比婚礼的殿堂见证了更多真诚的吻,医院的墙比教堂听到了更多的祈祷', 34 | '祝你们幸福是假的,祝你幸福是真的 ', 35 | '懒得重新认识一个人 再问名字 再问年龄 再聊天 再了解对方 再磨合 一想就烦', 36 | '不要太早为一个人倾尽全部,因为你太年轻了', 37 | ]; 38 | 39 | class Loader extends Component { 40 | static propTypes = { 41 | show: PropTypes.bool, 42 | text: PropTypes.string, 43 | }; 44 | 45 | static defaultProps = { 46 | show: false, 47 | }; 48 | 49 | shouldComponentUpdate(nextProps) { 50 | if (nextProps.show === this.props.show) { 51 | return false; 52 | } 53 | 54 | return true; 55 | } 56 | 57 | render() { 58 | var classes = this.props.classes; 59 | var text = this.props.text || randomText[Math.floor(Math.random() * randomText.length)]; 60 | 61 | return ( 62 |
66 | {text} 67 |
68 | ); 69 | } 70 | } 71 | 72 | export default injectSheet(classes)(Loader); 73 | -------------------------------------------------------------------------------- /src/js/ui/Offline/classes.js: -------------------------------------------------------------------------------- 1 | 2 | import colors from 'utils/colors'; 3 | 4 | export default theme => { 5 | return { 6 | container: { 7 | position: 'fixed', 8 | top: 0, 9 | left: 0, 10 | display: 'flex', 11 | width: '100vw', 12 | height: '100vh', 13 | justifyContent: 'center', 14 | flexDirection: 'column', 15 | alignItems: 'center', 16 | background: 'white', 17 | fontFamily: 'HelveticaNeue-UltraLight', 18 | 19 | '& h1': { 20 | fontSize: 24, 21 | fontWeight: '100', 22 | color: colors.pallet.dribbble 23 | }, 24 | 25 | '& button': { 26 | marginTop: 20, 27 | padding: '8px 12px', 28 | border: 0, 29 | fontSize: 14, 30 | fontWeight: 'lighter', 31 | color: '#278cf7', 32 | background: 'white', 33 | letterSpacing: .5, 34 | textTransform: 'uppercase', 35 | borderBottom: 'thin solid #278cf7', 36 | outline: 0, 37 | }, 38 | 39 | '&:before': { 40 | content: 'url(assets/loading.gif)', 41 | position: 'absolute', 42 | width: '140px', 43 | height: '140px', 44 | top: '30%', 45 | left: '50%', 46 | display: 'block', 47 | transform: 'translateX(-25%)', 48 | }, 49 | }, 50 | }; 51 | }; 52 | -------------------------------------------------------------------------------- /src/js/ui/Offline/index.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { Component, PropTypes } from 'react'; 3 | import injectSheet from 'react-jss'; 4 | 5 | import classes from './classes'; 6 | 7 | class Offline extends Component { 8 | static propTypes = { 9 | show: PropTypes.bool.isRequired, 10 | }; 11 | 12 | render() { 13 | var { classes, show } = this.props; 14 | 15 | if (!show) { 16 | return false; 17 | } 18 | 19 | return ( 20 |
21 |

┭┮﹏┭┮    网络连接失败,点击下方按钮重新加载。。。

22 | 23 | 24 |
25 | ); 26 | } 27 | } 28 | 29 | export default injectSheet(classes)(Offline); 30 | -------------------------------------------------------------------------------- /src/js/ui/ProgressImage/classes.js: -------------------------------------------------------------------------------- 1 | 2 | export default theme => ({ 3 | container: { 4 | position: 'relative', 5 | padding: 0, 6 | margin: 0, 7 | background: '#ddd', 8 | overflow: 'hidden', 9 | 10 | '& img': { 11 | height: 'auto', 12 | pointerEvents: 'none', 13 | }, 14 | }, 15 | 16 | main: { 17 | display: 'block', 18 | opacity: 0, 19 | transition: 'opacity 0.5s ease-out', 20 | }, 21 | 22 | thumb: { 23 | '& img': { 24 | position: 'absolute', 25 | top: 0, 26 | left: 0, 27 | transition: 'opacity 0.3s ease-out', 28 | filter: 'blur(30px)', 29 | }, 30 | 31 | '&$loaded img': { 32 | opacity: 1, 33 | }, 34 | }, 35 | 36 | loaded: { 37 | '& $main': { 38 | opacity: 1, 39 | }, 40 | 41 | '& $thumb img': { 42 | opacity: 0, 43 | } 44 | }, 45 | }); 46 | -------------------------------------------------------------------------------- /src/js/ui/ProgressImage/index.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { Component, PropTypes } from 'react'; 3 | import injectSheet from 'react-jss'; 4 | 5 | import classes from './classes'; 6 | 7 | class ProgressImage extends Component { 8 | static propTypes = { 9 | src: PropTypes.string, 10 | thumb: PropTypes.string, 11 | height: PropTypes.number, 12 | width: PropTypes.number, 13 | fallback: PropTypes.string, 14 | }; 15 | 16 | static defaultProps = { 17 | fallback: 'https://source.unsplash.com/random', 18 | }; 19 | 20 | componentWillReceiveProps(nextProps) { 21 | if (true 22 | && this.refs.container 23 | && nextProps.src !== this.props.src) { 24 | // Immediate render the new image 25 | this.refs.container.classList.remove(this.props.classes.loaded); 26 | } 27 | } 28 | 29 | handleError(e) { 30 | e.target.src = this.props.fallback; 31 | } 32 | 33 | handleLoad(e) { 34 | var ele = this.refs.container; 35 | this.refs.thumb.style.paddingBottom = '0%'; 36 | 37 | // Fix bug, sometiems this dom has been destroyed 38 | if (ele) { 39 | setTimeout(() => { 40 | ele.classList.add(this.props.classes.loaded); 41 | }, 50); 42 | } 43 | } 44 | 45 | render() { 46 | var { classes, src, thumb, height, width } = this.props; 47 | 48 | if (!src) return false; 49 | 50 | if (!thumb) { 51 | // Get the thumb image src 52 | thumb = src.replace(/\?.*$/, '') + '?param=20y20'; 53 | } 54 | 55 | return ( 56 |
63 | this.handleError(e)} 66 | onLoad={e => this.handleLoad(e)} 67 | ref="image" 68 | src={this.props.src} 69 | style={{ 70 | height, 71 | width, 72 | }} /> 73 |
80 | 91 |
92 |
93 | ); 94 | } 95 | } 96 | 97 | export default injectSheet(classes)(ProgressImage); 98 | -------------------------------------------------------------------------------- /src/js/ui/Switch/classes.js: -------------------------------------------------------------------------------- 1 | 2 | export default theme => ({ 3 | container: { 4 | display: 'inline-block', 5 | margin: 0, 6 | padding: 0, 7 | cursor: 'pointer', 8 | 9 | '& input': { 10 | display: 'none', 11 | 12 | '&:checked + $fake:before': { 13 | background: 'rgba(157, 166, 216, 1)', 14 | }, 15 | 16 | '&:checked + $fake:after': { 17 | left: 'auto', 18 | right: 0, 19 | background: '#3f51b5', 20 | } 21 | }, 22 | 23 | '& input:disabled + $fake:before': { 24 | background: 'rgba(0, 0, 0, .12)', 25 | }, 26 | 27 | '& input:disabled + $fake:after': { 28 | left: 'auto', 29 | right: 0, 30 | background: '#bdbdbd', 31 | } 32 | }, 33 | 34 | fake: { 35 | position: 'relative', 36 | display: 'inline-block', 37 | width: 35, 38 | height: 20, 39 | 40 | '&:before, &:after': { 41 | content: '""', 42 | }, 43 | 44 | '&:before': { 45 | display: 'block', 46 | width: 35, 47 | height: 14, 48 | marginTop: 3, 49 | background: 'rgba(0, 0, 0, .26)', 50 | borderRadius: 14, 51 | transition: '.5s ease-in-out', 52 | }, 53 | 54 | '&:after': { 55 | position: 'absolute', 56 | left: 0, 57 | top: 0, 58 | width: 20, 59 | height: 20, 60 | borderRadius: 20, 61 | background: '#fff', 62 | boxSizing: 'border-box', 63 | boxShadow: '0 3px 4px 0 rgba(0, 0, 0, .5)', 64 | transition: '.2s', 65 | } 66 | }, 67 | }); 68 | -------------------------------------------------------------------------------- /src/js/ui/Switch/index.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { Component } from 'react'; 3 | import injectSheet from 'react-jss'; 4 | 5 | import classes from './classes'; 6 | 7 | class Switch extends Component { 8 | render() { 9 | var { classes, id, checked, onChange } = this.props; 10 | 11 | return ( 12 | 13 | 18 | 19 | 20 | ); 21 | } 22 | } 23 | 24 | export default injectSheet(classes)(Switch); 25 | -------------------------------------------------------------------------------- /src/js/utils/colors.js: -------------------------------------------------------------------------------- 1 | 2 | const pallet = { 3 | primary: '#6496f0', 4 | sunflower: '#ffce54', 5 | grape: '#e0245e', 6 | coral: '#ff6470', 7 | mint: '#48cfad', 8 | dribbble: '#ea4c89', 9 | twitter: '#55acee', 10 | google: '#039be5', 11 | }; 12 | 13 | const gradients = [ 14 | 'linear-gradient(to right, #0099f7, #f11712)', 15 | 'linear-gradient(to bottom, #834d9b, #d04ed6)', 16 | 'linear-gradient(to left, #4da0b0, #d39d38)', 17 | 'linear-gradient(to left, #5614b0, #dbd65c)', 18 | 'linear-gradient(to right, #114357, #f29492)', 19 | 'linear-gradient(to right, #fd746c, #ff9068)', 20 | 'linear-gradient(to left, #6a3093, #a044ff)', 21 | 'linear-gradient(to left, #b24592, #f15f79)', 22 | 'linear-gradient(to top, #403a3e, #be5869)', 23 | 'linear-gradient(to right, #c2e59c, #64b3f4)', 24 | 'linear-gradient(to right, #00c9ff, #92fe9d)', 25 | 'linear-gradient(to right, #e53935, #e35d5b)', 26 | 'linear-gradient(to right, #fc00ff, #00dbde)', 27 | 'linear-gradient(to right, #7b4397, #dc2430)', 28 | ]; 29 | 30 | export default { 31 | pallet, 32 | 33 | randomGradient() { 34 | return gradients[Math.floor(Math.random() * gradients.length)]; 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /src/js/utils/lastfm.js: -------------------------------------------------------------------------------- 1 | 2 | import API from 'simple-lastfm'; 3 | 4 | const API_KEY = 'c1f9a819c03a17083eba0fe9ee41119e'; 5 | const SECRET = '3742198243c490bf333d0aa615e5d117'; 6 | 7 | var lastfm; 8 | 9 | async function getSession() { 10 | var success = await new Promise((resolve, reject) => { 11 | if (!lastfm) { 12 | resolve(false); 13 | return; 14 | } 15 | 16 | lastfm.getSessionKey(result => { 17 | resolve(result.success); 18 | }); 19 | }); 20 | 21 | if (!success) { 22 | return; 23 | } 24 | 25 | return lastfm; 26 | } 27 | 28 | async function initialize(username, password) { 29 | if (!username || !password) { 30 | return; 31 | } 32 | 33 | lastfm = new API({ 34 | api_key: API_KEY, 35 | api_secret: SECRET, 36 | username, 37 | password, 38 | }); 39 | 40 | return getSession(); 41 | } 42 | 43 | async function scrobble(song) { 44 | var session = await getSession(); 45 | 46 | if (!session) { 47 | return; 48 | } 49 | 50 | return new Promise((resolve, reject) => { 51 | session.scrobbleTrack({ 52 | artist: song.artists.map(e => e.name).join(','), 53 | track: song.name, 54 | callback: (result) => resolve(result) 55 | }); 56 | }); 57 | } 58 | 59 | async function playing(song) { 60 | var session = await getSession(); 61 | 62 | if (!session) { 63 | return; 64 | } 65 | 66 | return new Promise((resolve, reject) => { 67 | session.scrobbleNowPlayingTrack({ 68 | artist: song.artists.map(e => e.name).join(','), 69 | track: song.name, 70 | callback: (result) => resolve(result) 71 | }); 72 | }); 73 | } 74 | 75 | async function love(song) { 76 | var session = await getSession(); 77 | 78 | if (!session) { 79 | return; 80 | } 81 | 82 | return new Promise((resolve, reject) => { 83 | session.loveTrack({ 84 | artist: song.artists.map(e => e.name).join(','), 85 | track: song.name, 86 | callback: (result) => resolve(result) 87 | }); 88 | }); 89 | } 90 | 91 | async function unlove(song) { 92 | var session = await getSession(); 93 | 94 | if (!session) { 95 | return; 96 | } 97 | 98 | return new Promise((resolve, reject) => { 99 | session.unloveTrack({ 100 | artist: song.artists.map(e => e.name).join(','), 101 | track: song.name, 102 | callback: (result) => resolve(result) 103 | }); 104 | }); 105 | } 106 | 107 | export default { 108 | initialize, 109 | scrobble, 110 | playing, 111 | love, 112 | unlove, 113 | }; 114 | -------------------------------------------------------------------------------- /src/js/utils/storage.js: -------------------------------------------------------------------------------- 1 | 2 | import storage from 'electron-json-storage'; 3 | 4 | // 类似localStorage的API来保存和读取应用程序的用户设置 5 | export default { 6 | get: (key) => { 7 | return new Promise((resolve, reject) => { 8 | storage.get(key, (err, data) => { 9 | if (err) { 10 | reject(err); 11 | } else { 12 | resolve(data); 13 | } 14 | }); 15 | }); 16 | }, 17 | 18 | set: (key, data) => { 19 | return new Promise((resolve, reject) => { 20 | storage.set(key, data, err => { 21 | if (err) { 22 | reject(err); 23 | } else { 24 | resolve(data); 25 | } 26 | }); 27 | }); 28 | }, 29 | 30 | remove: (key) => { 31 | return new Promise((resolve, reject) => { 32 | storage.remove(key, err => { 33 | if (err) { 34 | reject(err); 35 | } else { 36 | resolve(); 37 | } 38 | }); 39 | }); 40 | } 41 | }; 42 | --------------------------------------------------------------------------------