/* SPDX-License-Identifier: GPL-3.0-only * * Copyright (C) 2022-2023 ImmortalWrt.org */ 'use strict'; 'require form'; 'require fs'; 'require network'; 'require poll'; 'require rpc'; 'require uci'; 'require ui'; 'require view'; var callServiceList = rpc.declare({ object: 'service', method: 'list', params: ['name'], expect: { '': {} } }); function getServiceStatus() { return L.resolveDefault(callServiceList('unblockneteasemusic'), {}).then(function (res) { var isRunning = false; try { isRunning = res['unblockneteasemusic']['instances']['unblockneteasemusic']['running']; } catch (e) { } return isRunning; }); } function renderStatus(isRunning) { var spanTemp = '%s %s'; var renderHTML; if (isRunning) { renderHTML = spanTemp.format('green', _('UnblockNeteaseMusic'), _('运行中')); } else { renderHTML = spanTemp.format('red', _('UnblockNeteaseMusic'), _('未运行')); } return renderHTML; } function uploadCertificate(type, filename, ev) { return ui.uploadFile('/usr/share/unblockneteasemusic/' + filename, ev.target) .then(L.bind(function(btn, res) { btn.firstChild.data = _('检查 %s 中...').format(type); if (res.size <= 0) { ui.addNotification(null, E('p', _('上传的 %s 为空。').format(tyupe))); return fs.remove('/usr/share/unblockneteasemusic/' + filename); } ui.addNotification(null, E('p', _('您的 %s 已成功上传。大小:%sB。').format(type, res.size))); }, this, ev.target)) .catch(function(e) { ui.addNotification(null, E('p', e.message)) }) .finally(L.bind(function(btn, input) { btn.firstChild.data = _('上传...'); }, this, ev.target)); } return view.extend({ load: function() { return Promise.all([ uci.load('unblockneteasemusic'), network.getHostHints() ]); }, render: function(data) { var m, s, o; var hosts = data[1]?.hosts; m = new form.Map('unblockneteasemusic', _('解除网易云音乐播放限制'), _('原理:采用 [Bilibili/JOOX/酷狗/酷我/咪咕/pyncmd/QQ/Youtube] 等音源,替换网易云音乐 无版权/收费 歌曲链接
' + '具体使用方法参见:GitHub @UnblockNeteaseMusic/luci-app-unblockneteasemusic')); s = m.section(form.TypedSection); s.anonymous = true; s.render = function () { poll.add(function () { return L.resolveDefault(getServiceStatus()).then(function (res) { var view = document.getElementById('service_status'); view.innerHTML = renderStatus(res); }); }); return E('div', { class: 'cbi-section', id: 'status_bar' }, [ E('p', { id: 'service_status' }, _('收集数据中...')) ]); } s = m.section(form.NamedSection, 'config', 'unblockneteasemusic'); o = s.option(form.Flag, 'enable', _('启用服务')); o.default = o.disabled; o.rmempty = false; o = s.option(form.DynamicList, 'music_source', _('音源接口'), _('留空以使用默认音源。')); o.value('bilibili', _('Bilibili 音乐')); o.value('bilivideo', _('Bilibili 音乐 (bilivideo)')); o.value('bodian', _('波点音乐')); o.value('joox', _('JOOX 音乐')); o.value('kugou', _('酷狗音乐')); o.value('kuwo', _('酷我音乐')); o.value('migu', _('咪咕音乐')); o.value('pyncmd', _('网易云音乐(pyncmd)')); o.value('qq', _('QQ 音乐')); o.value('youtube', _('Youtube 音乐')); o.value('youtubedl', _('Youtube 音乐(youtube-dl)')); o.value('ytdlp', _('Youtube 音乐(yt-dlp)')); o.renderWidget = function(/* ... */) { let dl = form.DynamicList.prototype.renderWidget.apply(this, arguments); dl?.querySelector('.add-item ul > li[data-value="-"]')?.remove(); return dl; } o = s.option(form.Value, 'joox_cookie', _('JOOX Cookie'), _('在 joox.com 获取,需要 wmid 和 session_key 值。')); o.placeholder = 'wmid=; session_key='; o.password = true; o.rmempty = false; o.depends({'music_source': 'joox', '!contains': true}); o = s.option(form.Value, 'migu_cookie', _('Migu Cookie'), _('通过抓包手机客户端请求获取,需要 aversionid 值。')); o.password = true; o.depends({'music_source': 'migu', '!contains': true}); o = s.option(form.Value, 'qq_cookie', _('QQ Cookie'), _('在 y.qq.com 获取,需要 uin 和 qm_keyst 值。')); o.placeholder = 'uin=; qm_keyst='; o.password = true; o.rmempty = false; o.depends({'music_source': 'qq', '!contains': true}); o = s.option(form.Value, 'youtube_key', _('Youtube API Key'), _('API Key 申请地址:https://developers.google.com/youtube/v3/getting-started#before-you-start')); o.password = true; o.depends({'music_source': 'youtube', '!contains': true}); o = s.option(form.Flag, 'follow_source_order', _('顺序查询'), _('默认为并行查询并返回第一个结果,开启后将严格按照配置音源的顺序进行查询。')) o.default = o.disabled; o = s.option(form.Flag, 'search_album', _('附加专辑名'), _('在其他音源搜索歌曲时携带专辑名称(默认搜索条件 歌曲名 - 歌手,启用后搜索条件 歌曲名 - 歌手 专辑名)。')); o.default = o.disabled; o = s.option(form.Flag, 'enable_flac', _('启用无损音质'), _('目前仅支持酷狗、酷我、咪咕、pyncmd、QQ 音源。')); o.default = o.disabled; o.depends('music_source', []); o.depends({'music_source': 'kugou', '!contains': true}); o.depends({'music_source': 'kuwo', '!contains': true}); o.depends({'music_source': 'migu', '!contains': true}); o.depends({'music_source': 'pyncmd', '!contains': true}); o.depends({'music_source': 'qq', '!contains': true}); o = s.option(form.Flag, 'select_max_br', _('选取最高音质'), _('选择所有音源中的最高码率替换音频。')); o.default = o.disabled; o.depends('enable_flac', '1'); o = s.option(form.ListValue, 'replace_music_source', _('音源替换'), _('当源音乐音质低于指定数值时,尝试强制使用其他平台的高音质版本进行替换。')); o.value('dont_replace', _('不强制替换音乐音源')); o.value('lower_than_192kbps', _('当音质低于 192 Kbps(中)时')); o.value('lower_than_320kbps', _('当音质低于 320 Kbps(高)时')); o.value('lower_than_999kbps', _('当音质低于 999 Kbps(无损)时')); o.value('replace_all', _('替换所有音乐音源')); o.default = 'dont_replace'; o = s.option(form.Flag, 'disable_upgrade_check', _('禁用更新检查'), _('拦截网易云音乐客户端更新请求,全平台支持。')); o.default = o.disabled; o = s.option(form.Flag, 'block_ads', _('屏蔽广告'), ('启用后,可屏蔽应用内部分广告。')); o.default = o.disabled; o = s.option(form.ListValue, 'local_vip', _('伪装本地 VIP'), _('设置后,可以使用去广告、个性换肤、鲸云音效等本地功能。')); o.value('', _('禁用')); o.value('cvip', _('CVIP')); o.value('svip', _('SVIP')); o = s.option(form.Flag, 'auto_update', _('启用自动更新'), _('启用后,每天将定时自动检查最新核心版本并更新。')); o.default = o.disabled; o = s.option(form.ListValue, 'update_time', '检查更新时间', _('设定每天自动检查更新时间。')); for (var i = 0; i < 24; i++) o.value(i, i + ':00'); o.default = '3'; o.depends('auto_update', '1'); o = s.option(form.Button, '_download_cert', _('CA 根证书'), _('Linux / iOS / MacOSX 在信任根证书后方可正常使用。')); o.inputstyle = 'apply'; o.inputtitle = _('下载 ca.crt'); o.onclick = function() { return fs.read_direct('/usr/share/unblockneteasemusic/core/ca.crt', 'blob').then(function(blob) { var url = window.URL.createObjectURL(blob); var link = E('a', { 'style': 'display:none', 'href': url, 'download': 'ca.crt' }); document.body.appendChild(link); link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(url); }).catch(function(err) { ui.addNotification(null, E('p', [ _('下载文件失败:%s。').format(err.message) ])); }); } o = s.option(form.Flag, 'advanced_mode', _('启用进阶设置'), _('非必要不推荐启用。')); o.default = o.disabled, o = s.option(form.ListValue, 'log_level', _('日志等级')); o.value('debug', _('调试')); o.value('info', _('信息(默认)')); o.value('silent', _('静默')); o.default = 'info'; o.depends('advanced_mode', '1'); o = s.option(form.Value, 'http_port', _('HTTP 监听端口')); o.datatype = 'port'; o.default = '5200'; o.rmempty = false; o.depends('hijack_ways', 'dont_hijack'); o.depends('hijack_ways', 'use_ipset'); o = s.option(form.Value, 'https_port', _('HTTPS 监听端口')); o.datatype = 'port'; o.default = '5201'; o.rmempty = false; o.depends('hijack_ways', 'dont_hijack'); o.depends('hijack_ways', 'use_ipset'); o = s.option(form.Value, 'endpoint_url', _('EndPoint'), _('音源地址反代(包装)。')); o.default = 'https://music.163.com'; o.rmempty = false; o.depends('advanced_mode', '1'); o = s.option(form.Value, 'cnrelay', _('UNM Bridge 服务器'), _('使用 UnblockNeteaseMusic 中继桥(bridge)以获取音源信息。')); o.placeholder = 'http(s)://host:port' o.depends('advanced_mode', '1'); o = s.option(form.ListValue, 'hijack_ways', _('劫持方法'), _('如果使用 Hosts 劫持,监听端口将固定为 80/443,请注意更改您的 webUI 端口。')); o.value('dont_hijack', _('不开启劫持')); o.value('use_ipset', _('使用 IPSet 劫持')); o.value('use_hosts', _('使用 Hosts 劫持')); o.default = 'dont_hijack'; o.rmempty = false; o.depends('advanced_mode', '1'); o = s.option(form.Flag, 'pub_access', _('部署到公网'), _('默认仅放行局域网请求,如需提供公开访问请勾选此选项。')); o.default = o.disabled; o.depends('advanced_mode', '1'); o = s.option(form.Flag, 'strict_mode', _('启用严格模式'), _('若将服务部署到公网,则强烈建议使用严格模式,此模式下仅放行网易云音乐所属域名的请求。
注意:该模式下不能使用全局代理。')); o.default = o.disabled; o.depends('advanced_mode', '1'); o = s.option(form.Value, 'netease_server_ip', _('网易云服务器 IP')); o.datatype = 'ipaddr'; o.depends('advanced_mode', '1'); o = s.option(form.Value, 'proxy_server_ip', _('代理服务器地址'), _('使用代理服务器获取音乐信息。')); o.placeholder = 'http(s)://host:port'; o.depends('advanced_mode', '1'); o = s.option(form.Value, 'self_issue_cert_crt', _('自签发证书公钥位置')); o.value('/usr/share/unblockneteasemusic/core/server.crt', _('内置公钥')); o.value('/usr/share/unblockneteasemusic/server.crt'); o.default = '/usr/share/unblockneteasemusic/core/server.crt'; o.datatype = 'file'; o.depends('advanced_mode', '1'); o = s.option(form.Button, '_upload_cert', _('上传公钥')); o.inputstyle = 'action'; o.inputtitle = _('上传...'); o.depends('self_issue_cert_crt', '/usr/share/unblockneteasemusic/server.crt'); o.onclick = L.bind(uploadCertificate, this, _('公钥'), 'server.crt'); o.modalonly = true; o = s.option(form.Value, 'self_issue_cert_key', _('自签发证书私钥位置')); o.value('/usr/share/unblockneteasemusic/core/server.key', _('内置私钥')); o.value('/usr/share/unblockneteasemusic/server.key'); o.default = '/usr/share/unblockneteasemusic/core/server.key'; o.datatype = 'file' o.depends('advanced_mode', '1'); o = s.option(form.Button, '_upload_key', _('上传私钥')); o.inputstyle = 'action'; o.inputtitle = _('上传...'); o.depends('self_issue_cert_key', '/usr/share/unblockneteasemusic/server.key'); o.onclick = L.bind(uploadCertificate, this, _('私钥'), 'server.key'); o.modalonly = true; s = m.section(form.TableSection, 'acl_rule', _('例外客户端规则'), _('可以为局域网客户端分别设置不同的例外模式。')); s.addremove = true; s.anonymous = true; s.sortable = true; o = s.option(form.Flag, 'enable', _('启用')); o.default = o.enabled; o.rmempty = false; o = s.option(form.Value, 'mac_addr', _('MAC 地址')); o.datatype = 'macaddr'; Object.keys(hosts).forEach(function(mac) { var hint = hosts[mac].name || L.toArray(hosts[mac].ipaddrs || hosts[mac].ipv4)[0]; o.value(mac, hint ? '%s (%s)'.format(mac, hint) : mac); }); o.rmempty = false; o = s.option(form.ListValue, 'filter_mode', _('规则')); o.value('disable_all', _('不代理 HTTP 和 HTTPS')); o.value('disable_http', _('不代理 HTTP')); o.value('disable_https', _('不代理 HTTPS')); o.default = 'disable_all'; o.rmempty = false; return m.render(); } });