├── .gitignore ├── 163Music.png ├── 163Music@2x.png ├── README.md ├── hack.js ├── index.html ├── main.js ├── mainWindowStyle.css ├── package.json └── screenShots ├── 1.png ├── 2.png ├── 3.png └── min.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /163Music.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yingDev/KissNemp/5e3d16bdb11c9ae7c437e75579a7171e9e9004ef/163Music.png -------------------------------------------------------------------------------- /163Music@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yingDev/KissNemp/5e3d16bdb11c9ae7c437e75579a7171e9e9004ef/163Music@2x.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://raw.githubusercontent.com/yingDev/KissNemp/master/screenShots/min.jpg) 2 | 3 | # KissNemp 4 | 5 | `简单粗暴`版的网易云音乐播放器 (for Linux) 6 | 7 | `Keep it Simple & Stupid` NetEase Music Player 8 | 9 | 基于 [Electron](https://github.com/atom/electron) 10 | 11 | # 原理 12 | 就是一个Web版网易云音乐的Wrapper (+各种hack)...基于Electron实现以下功能: 13 | - 独立运行 14 | - 迷你模式 15 | - 通过托盘菜单控制播放 16 | - 全局快捷键(TODO) 17 | - 播放MV(TODO) 18 | 19 | 20 | # 依赖 21 | - [NodeJs 5.x](https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions) 22 | - libappindicator1 (用于显示托盘图标) 23 | 24 | # 初始化 & 运行 25 | 目前没有打包,所以手动运行:(也许你需要cnpm--[npm淘宝镜像](http://npm.taobao.org/)) 26 | ```bash 27 | npm install 28 | npm start 29 | ``` 30 | 31 | # License 32 | ~~我也不知道~~ (自用为主) 33 | 34 | # 屏幕截图 35 | 在Ubuntu 15.10、 默认桌面 36 | 37 | ![](https://raw.githubusercontent.com/yingDev/KissNemp/master/screenShots/1.png) 38 | 39 | ![](https://raw.githubusercontent.com/yingDev/KissNemp/master/screenShots/2.png) 40 | 41 | ![](https://raw.githubusercontent.com/yingDev/KissNemp/master/screenShots/3.png) 42 | 43 | -------------------------------------------------------------------------------- /hack.js: -------------------------------------------------------------------------------- 1 | const ipc = require('electron').ipcRenderer; 2 | ipc.on("playControl", (e, msg)=> 3 | { 4 | var btnClassNames = {next: 'nxt', previous: 'prv', playPause: 'ply'}; 5 | 6 | var btn = document.getElementsByClassName(btnClassNames[msg])[0]; 7 | if(btn) 8 | { 9 | btn.click(); 10 | if(msg != 'playPause') 11 | { 12 | var playBtn = document.getElementsByClassName('ply')[0]; 13 | if( ! playBtn.classList.contains('pas')) //is playing ? 14 | { 15 | playBtn.click(); 16 | } 17 | } 18 | } 19 | }); 20 | 21 | ipc.on('windowControl', (e, msg)=> 22 | { 23 | if(msg == 'toggleMiniMode') 24 | { 25 | toggleMiniMode(); 26 | } 27 | }); 28 | 29 | 30 | window.addEventListener("DOMContentLoaded", topLevelStyleFix); 31 | 32 | window.addEventListener("hashchange", ()=> 33 | { 34 | var poll = setInterval(()=> 35 | { 36 | var contentFrame = document.getElementById("g_iframe"); 37 | if(contentFrame) 38 | { 39 | var doc = contentFrame.contentWindow.document; 40 | const id = "hackStyleNode"; 41 | 42 | if(doc.getElementById(id) || doc.getElementsByTagName('head').length == 0) 43 | { 44 | //clearInterval(poll); 45 | return; 46 | } 47 | 48 | var styleNode = doc.createElement('style'); 49 | styleNode.id = id; 50 | 51 | styleNode.type = "text/css"; 52 | var styleText = doc.createTextNode( 53 | '.g-sd3.u-scroll.n-musicsd.f-pr.j-flag { padding-top: 0; }' + 54 | '.f-ff1 { font-family: inherit; font-weight: normal; }' + 55 | '#flag_dl, .m-table .icn-dl, .u-btni.u-btni-dl, .m-multi, .download { display:none!important; }' + 56 | '.ban.f-pr { width: 100%; }' + 57 | '.n-ban .wrap { width: 800px; }' 58 | ); 59 | styleNode.appendChild(styleText); 60 | 61 | doc.getElementsByTagName('head')[0].appendChild(styleNode); 62 | } 63 | }, 500); 64 | }); 65 | 66 | function topLevelStyleFix() 67 | { 68 | var styleNode = document.createElement('style'); 69 | styleNode.id = "hackStyleNode"; 70 | 71 | styleNode.type = "text/css"; 72 | var styleText = document.createTextNode( 73 | '.m-top .wrap { width: 950px; }' + 74 | '.m-nav.j-tflag > li.lst{ display: none; }' + 75 | 'body { overflow: hidden; }' 76 | ); 77 | styleNode.appendChild(styleText); 78 | 79 | document.getElementsByTagName('head')[0].appendChild(styleNode); 80 | 81 | //fix topbar 82 | var topbar = document.getElementById("g-topbar"); 83 | //prevent style change 84 | Object.defineProperty(topbar, "style", {}); 85 | 86 | //play bar fix 87 | var playBar = document.getElementsByClassName("m-playbar")[0]; 88 | if(playBar) 89 | { 90 | var lockBtnContainer = playBar.getElementsByClassName("left f-fl")[0]; 91 | var lockBtn = lockBtnContainer.getElementsByClassName("btn")[0]; 92 | var bg = playBar.getElementsByClassName("bg")[0]; 93 | 94 | playBar.style.top = (-playBar.offsetHeight) + "px"; 95 | 96 | if (playBar.classList.contains("m-playbar-unlock")) 97 | { 98 | lockBtn.click(); 99 | } 100 | lockBtn.style.display = "none"; 101 | 102 | lockBtnContainer.style.background = bg.style.background.toString(); 103 | lockBtnContainer.style["background-position"] = "0 0"; 104 | lockBtnContainer.style.top = "0"; 105 | } 106 | //window.top.playControl['next'] = document.getElementsByClassName("nxt")[0].click; 107 | } 108 | 109 | function toggleMiniMode() 110 | { 111 | const id = "miniModeStyle"; 112 | var minStyle = document.getElementById(id); 113 | if(minStyle) 114 | { 115 | minStyle.remove(); 116 | return; 117 | } 118 | 119 | var styleNode = document.createElement('style'); 120 | styleNode.id = id; 121 | 122 | styleNode.type = "text/css"; 123 | var styleText = document.createTextNode( 124 | '#g_player { left:0;margin:0; }' + 125 | '.m-nav { display: none; }' + 126 | '.logo { transform: scale(0.5) translate(-100px, -40px) }' + 127 | '.m-playbar .btns { width: 125px;margin: -42px 120px; scale: 0.9; }' + 128 | '.src { display:none; }' + 129 | '.m-playbar .play { width: 350px; }' + 130 | '.m-playbar .words { width: 350px; text-align:center; }' + 131 | '.f-fl { float: none; }' + 132 | '.m-pbar, .m-pbar .barbg { width: 330px; margin-left: 7px; }' + 133 | '.m-playbar .head, .m-pbar .time { display: none; }' 134 | 135 | 136 | ); 137 | styleNode.appendChild(styleText); 138 | 139 | document.getElementsByTagName('head')[0].appendChild(styleNode); 140 | 141 | } 142 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | KissNemp 6 | 7 | 58 | 59 | 60 | 61 |
62 |
63 | 68 |
69 | 70 | 71 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const ipc = require('electron').ipcMain; 4 | 5 | const electron = require('electron'); 6 | const app = electron.app; 7 | const BrowserWindow = electron.BrowserWindow; 8 | const Menu = electron.Menu; 9 | const Tray = electron.Tray; 10 | 11 | let mainWindow; 12 | let appIcon; 13 | 14 | function createWindow () 15 | { 16 | mainWindow = new BrowserWindow({width: 982, height: 600, frame: false, resizable: false}); 17 | 18 | mainWindow.loadURL('file://' + __dirname + '/index.html'); 19 | 20 | //mainWindow.webContents.openDevTools(); 21 | 22 | mainWindow.on('closed', () => 23 | { 24 | mainWindow = null; 25 | app.quit(); 26 | }); 27 | } 28 | 29 | function createTray() 30 | { 31 | appIcon = new Tray(__dirname + '/163Music.png'); 32 | var contextMenu = Menu.buildFromTemplate([ 33 | { label: '⏯ Play / Pause', click: () => { playControl('playPause'); } }, 34 | { label: '⏩ Next', click: () => { playControl('next'); } }, 35 | { label: '⏪ Previous', click: () => { playControl('previous'); } }, 36 | { type: 'separator' }, 37 | { label: 'Main Window', click: ()=>{ windowControl('showMain'); } }, 38 | { label: 'Quit', click: () => { app.quit(); }} 39 | ]); 40 | appIcon.setToolTip('This is my application.'); 41 | appIcon.setContextMenu(contextMenu); 42 | } 43 | 44 | function playControl(cmd) 45 | { 46 | mainWindow.webContents.send("playControl", cmd); 47 | } 48 | 49 | function windowControl(cmd) 50 | { 51 | mainWindow.webContents.send("windowControl", cmd); 52 | } 53 | 54 | app.on('ready', () => 55 | { 56 | createWindow(); 57 | createTray(); 58 | }); 59 | 60 | app.on('window-all-closed', () => 61 | { 62 | if (process.platform !== 'darwin') { 63 | app.quit(); 64 | } 65 | }); 66 | 67 | app.on('activate', () => 68 | { 69 | if (mainWindow === null) { 70 | createWindow(); 71 | } 72 | }); 73 | -------------------------------------------------------------------------------- /mainWindowStyle.css: -------------------------------------------------------------------------------- 1 | body,html 2 | { 3 | width:100%;height:100%; 4 | overflow:hidden; 5 | } 6 | 7 | * 8 | { 9 | margin:0;padding:0; 10 | } 11 | 12 | #title-bar 13 | { 14 | -webkit-user-select: none; 15 | cursor: arrow; 16 | position:absolute; 17 | z-index:99; 18 | width:100%; 19 | height:23px; 20 | 21 | } 22 | 23 | #title-bar:hover 24 | { 25 | background:rgba(200,200,200, 0.05); 26 | } 27 | 28 | #window-btns 29 | { 30 | position: absolute; 31 | right:0; 32 | width:82px; 33 | overflow: hidden; 34 | } 35 | 36 | #title-text 37 | { 38 | -webkit-app-region: drag; 39 | -webkit-user-select: none; 40 | 41 | font-family: sans-serif; 42 | position: absolute; 43 | top:0; 44 | text-align: center; 45 | width: 900px; 46 | height: 100%; 47 | font-size:12px; 48 | line-height: 23px; 49 | color:rgba(255,255,255,0.8); 50 | opacity: 0.5; 51 | } 52 | 53 | #title-text:hover 54 | { 55 | opacity: 1; 56 | } 57 | 58 | #window-btns > li 59 | { 60 | opacity: 0.8; 61 | width:33%; 62 | box-sizing:border-box; 63 | font-family: sans-serif; 64 | display: block; 65 | float:right; 66 | padding:4.75px 8px; 67 | margin:0; 68 | font-size: 12px; 69 | line-height: 100%; 70 | color:white; 71 | -webkit-user-select: none; 72 | cursor: default; 73 | } 74 | #window-btns > li:hover 75 | { 76 | opacity: 1; 77 | } 78 | 79 | #webView 80 | { 81 | width:100%;height:100%;position:absolute; 82 | } 83 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "KissNemp", 3 | "version": "1.0.0", 4 | "description": "'Kiss it Simple & Stupid' Netease Music Player", 5 | "main": "main.js", 6 | "scripts": { 7 | "start": "electron main.js" 8 | }, 9 | "repository": { 10 | "type": "git" 11 | }, 12 | "keywords": [ 13 | "Electron", 14 | "quick", 15 | "start", 16 | "tutorial" 17 | ], 18 | "author": "GitHub", 19 | "license": "CC0-1.0", 20 | "devDependencies": { 21 | "electron-prebuilt": "^0.36.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /screenShots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yingDev/KissNemp/5e3d16bdb11c9ae7c437e75579a7171e9e9004ef/screenShots/1.png -------------------------------------------------------------------------------- /screenShots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yingDev/KissNemp/5e3d16bdb11c9ae7c437e75579a7171e9e9004ef/screenShots/2.png -------------------------------------------------------------------------------- /screenShots/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yingDev/KissNemp/5e3d16bdb11c9ae7c437e75579a7171e9e9004ef/screenShots/3.png -------------------------------------------------------------------------------- /screenShots/min.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yingDev/KissNemp/5e3d16bdb11c9ae7c437e75579a7171e9e9004ef/screenShots/min.jpg --------------------------------------------------------------------------------