update 2023-02-01 13:59:14

This commit is contained in:
github-actions[bot] 2023-02-01 13:59:14 +08:00
parent af297b58fc
commit f98914b6d5
22 changed files with 480 additions and 650 deletions

View File

@ -67,7 +67,7 @@ plugins:
- tag: cache
type: cache
args:
size: cache_size
size: 200000
lazy_cache_ttl: 259200
# 转发至本地服务器

View File

@ -4,15 +4,14 @@
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI support for UnblockNeteaseMusic (JavaScript)
LUCI_DEPENDS:=+dnsmasq-full +node \
+PACKAGE_firewall:ipset \
LUCI_TITLE:=LuCI support for UnblockNeteaseMusic
LUCI_DEPENDS:=+dnsmasq-full +ipset +node \
@(PACKAGE_libustream-mbedtls||PACKAGE_libustream-openssl||PACKAGE_libustream-wolfssl)
LUCI_PKGARCH:=all
PKG_NAME:=luci-app-unblockneteasemusic
PKG_VERSION:=3.1
PKG_RELEASE:=4
PKG_VERSION:=2.13
PKG_RELEASE:=6
PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org>

View File

@ -19,7 +19,7 @@
#进入 OpenWrt 源码 package 目录
cd package
#克隆插件源码
git clone https://github.com/UnblockNeteaseMusic/luci-app-unblockneteasemusic.git
git clone --branch master https://github.com/UnblockNeteaseMusic/luci-app-unblockneteasemusic.git
#返回上一层目录
cd ..
#配置
@ -42,10 +42,11 @@
### 效果图
#### LuCI 界面
![Image text](https://raw.githubusercontent.com/UnblockNeteaseMusic/luci-app-unblockneteasemusic/js/views/view1.jpg)
![Image text](https://raw.githubusercontent.com/UnblockNeteaseMusic/luci-app-unblockneteasemusic/js/views/view2.jpg)
![Image text](https://raw.githubusercontent.com/UnblockNeteaseMusic/luci-app-unblockneteasemusic/master/views/view1.jpg)
![Image text](https://raw.githubusercontent.com/UnblockNeteaseMusic/luci-app-unblockneteasemusic/master/views/view2.jpg)
![Image text](https://raw.githubusercontent.com/UnblockNeteaseMusic/luci-app-unblockneteasemusic/master/views/view3.jpg)
#### UWP 网易云音乐客户端
![Image text](https://raw.githubusercontent.com/UnblockNeteaseMusic/luci-app-unblockneteasemusic/js/views/view3.jpg)
![Image text](https://raw.githubusercontent.com/UnblockNeteaseMusic/luci-app-unblockneteasemusic/master/views/view4.jpg)
### 鸣谢
[UnblockNeteaseMusic](https://github.com/UnblockNeteaseMusic/server)的开发者:[nondanee](https://github.com/nondanee)、[pan93412](https://github.com/pan93412)、[1715173329](https://github.com/1715173329)<br/>

View File

@ -1,334 +0,0 @@
/* 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 CBIStaticList = form.DynamicList.extend({
__name__: 'CBI.StaticList',
renderWidget: function(/* ... */) {
var dl = form.DynamicList.prototype.renderWidget.apply(this, arguments);
dl.querySelector('.add-item ul > li[data-value="-"]').remove();
return dl;
}
});
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 = '<em><span style="color:%s"><strong>%s %s</strong></span></em>';
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;
m = new form.Map('unblockneteasemusic', _('解除网易云音乐播放限制'),
_('原理:采用 [Bilibili/JOOX/酷狗/酷我/咪咕/pyncmd/QQ/Youtube] 等音源,替换网易云音乐 无版权/收费 歌曲链接<br/>' +
'具体使用方法参见:<a href="https://github.com/UnblockNeteaseMusic/luci-app-unblockneteasemusic" target="_blank">GitHub @UnblockNeteaseMusic/luci-app-unblockneteasemusic</a>'));
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(CBIStaticList, 'music_source', _('音源接口'),
_('留空以使用默认音源。'));
o.value('bilibili', _('Bilibili 音乐'));
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.value('ytdownload', _('Youtube 音乐ytdownload'));
o = s.option(form.Value, 'joox_cookie', _('JOOX Cookie'),
_('在 joox.com 获取,需要 wmid 和 session_key 值。'));
o.placeholder = 'wmid=; session_key=';
o.rmempty = false;
o.depends({'music_source': 'joox', '!contains': true});
o = s.option(form.Value, 'migu_cookie', _('Migu Cookie'),
_('通过抓包手机客户端请求获取,需要 aversionid 值。'));
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.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.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', _('附加专辑名'),
_('在其他音源搜索歌曲时携带专辑名称(默认搜索条件 <code>歌曲名 - 歌手</code>,启用后搜索条件 <code>歌曲名 - 歌手 专辑名</code>)。'));
o.default = o.disabled;
o = s.option(form.Flag, 'enable_flac', _('启用无损音质'),
_('目前仅支持酷狗、酷我、咪咕、pyncmd、QQ 音源。'));
o.default = o.disabled;
o = s.option(form.Flag, 'select_max_br', _('选取最高音质'),
_('选择所有音源中的最高码率替换音频。'));
o.default = o.disabled;
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', _('屏蔽广告'),
('启用后,可屏蔽应用内<strong>部分</strong>广告。'));
o.default = o.disabled;
o = s.option(form.Flag, 'local_vip', _('启用本地 VIP'),
_('启用后,可以使用去广告、个性换肤、鲸云音效等本地功能。'));
o.default = o.disabled;
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, 'keep_core_when_upgrade', _('升级时保留核心程序'));
o.default = o.disabled;
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', _('启用严格模式'),
_('若将服务部署到公网,则强烈建议使用严格模式,此模式下仅放行网易云音乐所属域名的请求。<br/>注意:该模式下不能使用全局代理。'));
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, 'ip_addr', _('IP 地址'));
o.datatype = 'ip4addr';
for (var i of Object.entries(data[1].hosts))
for (var v in i[1].ipaddrs)
if (i[1].ipaddrs[v]) {
var ip_addr = i[1].ipaddrs[v], ip_host = i[1].name;
o.value(ip_addr, ip_host ? String.format('%s (%s)', ip_host, ip_addr) : ip_addr)
}
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();
}
});

View File

@ -1,224 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-only
*
* Copyright (C) 2022 ImmortalWrt.org
*/
'use strict';
'require dom';
'require form';
'require fs';
'require poll';
'require rpc';
'require ui';
'require view';
return view.extend({
render: function() {
var m, s, o;
var unm_helper = '/usr/share/unblockneteasemusic/update.sh';
m = new form.Map('unblockneteasemusic');
s = m.section(form.NamedSection, 'config', 'unblockneteasemusic', _('核心管理'));
s.anonymous = true;
o = s.option(form.DummyValue, '_core_version', _('核心版本'));
o.cfgvalue = function() {
var _this = this;
var spanTemp = '<div style="color:%s;margin-top:5px;"><strong>%s</strong></div>';
return fs.exec(unm_helper, [ 'check_version' ]).then(function(res) {
if (res.code === 0)
_this.default = String.format(spanTemp, 'green', res.stdout.trim());
else if (res.code === 2)
_this.default = String.format(spanTemp, 'red', _('未安装'));
else {
ui.addNotification(null, E('p', [ _('获取版本信息失败:%s。').format(res) ]));
_this.default = String.format(spanTemp, 'red', _('未知错误'));
}
return null;
}).catch(function(err) {
ui.addNotification(null, E('p', [ _('未知错误:%s。').format(err) ]));
_this.default = String.format(spanTemp, 'red', _('未知错误'));
return null;
});
}
o.rawhtml = true;
o = s.option(form.Button, '_remove_core', _('删除核心'),
_('删除核心后,需手动点击下面的按钮重新下载,有助于解决版本冲突问题。'));
o.inputstyle = 'remove';
o.onclick = function() {
var _this = this;
return fs.exec(unm_helper, [ 'remove_core' ]).then(function(res) {
_this.description = '删除完毕。'
return _this.map.reset();
}).catch(function(err) {
ui.addNotification(null, E('p', [ _('未知错误:%s。').format(err) ]));
_this.description = '删除失败。'
return _this.map.reset();
});
}
o = s.option(form.Button, '_update_core', _('更新核心'),
_('更新完毕后会自动在后台重启插件,无需手动重启。'));
o.inputstyle = 'action';
o.onclick = function() {
var _this = this;
return fs.exec(unm_helper, [ 'update_core' ]).then(function (res) {
if (res.code === 0)
_this.description = _('更新成功。');
else if (res.code === 1)
_this.description = _('更新失败。');
else if (res.code === 2)
_this.description = _('更新程序正在运行中。');
else if (res.code === 3)
_this.description = _('当前已是最新版本。');
return _this.map.reset();
}).catch(function (err) {
ui.addNotification(null, E('p', [ _('未知错误:%s。').format(err) ]));
_this.description = _('更新失败。');
return _this.map.reset();
});
}
o = s.option(form.Button, '_debug_log', _('调试报告'),
_('若您遇到使用上的问题,请点此打印调试报告,并将其附在您的 issue 中。'));
o.inputstyle = 'action';
o.inputtitle = _('打印报告');
o.onclick = function() {
var log_modal = ui.showModal(_('打印调试报告'), [
E('p', { 'class': 'spinning' },
_('正在打印调试报告中...'))
]);
return fs.exec_direct('/usr/bin/unm-debug', 'text').then(function (res) {
log_modal.removeChild(log_modal.lastChild);
if (res) {
log_modal.appendChild(E('p', _('提交 issue 时,您只需附上最后的链接,无需提供整个输出。')));
log_modal.appendChild(E('textarea', {
'id': 'content_debugLog',
'class': 'cbi-input-textarea',
'style': 'font-size:13px; resize: none',
'readonly': 'readonly',
'wrap': 'soft',
'rows': '30'
}, [ res.trim() ])
);
} else {
log_modal.appendChild(E('p', _('错误')));
log_modal.appendChild(E('pre', { 'class': 'errors' }, [ _('无法打印调试报告。') ]));
}
var log_element = document.getElementById('content_debugLog') || null;
if (log_element)
log_element.scrollTop = log_element.scrollHeight;
log_modal.appendChild(E('div', { 'class': 'right' }, [
log_element ? E('button', {
'class': 'btn cbi-button-action',
'click': ui.createHandlerFn(this, function() {
var links = log_element.value.match(/https:\/\/(litter.catbox.moe|transfer.sh)\/.*.txt/g);
var textarea = document.createElement('textarea');
document.body.appendChild(textarea);
textarea.style.position = 'absolute';
textarea.style.clip = 'rect(0 0 0 0)';
textarea.value = links ? links.join('\n'): log_element.value;
textarea.select()
document.execCommand('copy', true);
document.body.removeChild(textarea);
})
}, _('复制')) : '',
E('button', {
'class': 'btn',
'click': ui.hideModal
}, _('关闭'))
]));
return null;
}).catch(function (err) {
ui.addNotification(null, E('p', _('无法打印调试报告:%s。').format(err)));
ui.hideModal();
return null;
});
}
o = s.option(form.DummyValue, '_logview');
o.render = function() {
/* Thanks to luci-app-aria2 */
var css = ' \
#log_textarea { \
padding: 10px; \
text-align: left; \
} \
#log_textarea pre { \
padding: .5rem; \
word-break: break-all; \
margin: 0; \
} \
.description { \
background-color: #33ccff; \
}';
var log_textarea = E('div', { 'id': 'log_textarea' },
E('img', {
'src': L.resource(['icons/loading.gif']),
'alt': _('Loading'),
'style': 'vertical-align:middle'
}, _('Collecting data...'))
);
poll.add(L.bind(function() {
return fs.read('/tmp/unblockneteasemusic.log', 'text')
.then(function(res) {
var log = E('pre', { 'wrap': 'pre' }, [
res.trim() || _('当前无日志。')
]);
dom.content(log_textarea, log);
}).catch(function(err) {
if (err.toString().includes('NotFoundError'))
var log = E('pre', { 'wrap': 'pre' }, [
_('日志文件不存在。')
]);
else
var log = E('pre', { 'wrap': 'pre' }, [
_('未知错误:%s。').format(err)
]);
dom.content(log_textarea, log);
});
}));
return E([
E('style', [ css ]),
E('div', {'class': 'cbi-map'}, [
E('h3', {'name': 'content'}, _('运行日志')),
E('div', {'class': 'cbi-section'}, [
log_textarea,
E('div', {'style': 'text-align:right'},
E('small', {}, _('每 %s 秒刷新。').format(L.env.pollinterval))
)
])
])
]);
}
return m.render();
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});

View File

@ -0,0 +1,69 @@
-- SPDX-License-Identifer: GPL-3.0-only
-- Copyright (C) 2019-2022 Tianling Shen <cnsztl@immortalwrt.org>
module("luci.controller.unblockneteasemusic", package.seeall)
function index()
if not nixio.fs.access("/etc/config/unblockneteasemusic") then
return
end
local page
page = entry({"admin", "services", "unblockneteasemusic"}, firstchild(), _("解除网易云音乐播放限制"), 50)
page.dependent = false
page.acl_depends = { "luci-app-unblockneteasemusic" }
entry({"admin", "services", "unblockneteasemusic", "general"}, cbi("unblockneteasemusic/main"), _("基本设定"), 1)
entry({"admin", "services", "unblockneteasemusic", "upgrade"}, form("unblockneteasemusic/upgrade"), _("更新组件"), 2).leaf = true
entry({"admin", "services", "unblockneteasemusic", "log"}, form("unblockneteasemusic/log"), _("日志"), 3)
entry({"admin", "services", "unblockneteasemusic", "status"}, call("act_status")).leaf = true
entry({"admin", "services", "unblockneteasemusic", "update_core"}, call("act_update_core"))
entry({"admin", "services", "unblockneteasemusic", "remove_core"}, call("act_remove_core"))
end
function act_status()
local stat = luci.util.ubus("service", "list", { name = "unblockneteasemusic" })
local running = next(stat) and next(stat.unblockneteasemusic) and stat.unblockneteasemusic.instances.unblockneteasemusic.running or false
local e = { running = running }
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
function update_core()
local core_cloud_ver = luci.sys.exec("uclient-fetch -qO- 'https://api.github.com/repos/UnblockNeteaseMusic/server/commits?sha=enhanced&path=precompiled' | jsonfilter -e '@[0].sha'")
local core_cloud_ver_mini = string.sub(core_cloud_ver, 1, 7)
local core_local_ver
if not core_cloud_ver or not core_cloud_ver_mini then
return "1"
else
core_local_ver = luci.sys.exec("cat '/usr/share/unblockneteasemusic/core_local_ver' 2>'/dev/null'")
if not core_local_ver or (core_local_ver ~= core_cloud_ver) then
luci.sys.call("rm -f /usr/share/unblockneteasemusic/update_core_successfully")
luci.sys.call("/usr/share/unblockneteasemusic/update.sh update_core_from_luci")
if not nixio.fs.access("/usr/share/unblockneteasemusic/update_core_successfully") then
return "2"
else
luci.sys.call("rm -f /usr/share/unblockneteasemusic/update_core_successfully")
return core_cloud_ver_mini
end
else
return "0"
end
end
end
function act_update_core()
luci.http.prepare_content("application/json")
luci.http.write_json({
ret = update_core();
})
end
function act_remove_core()
local ret = {}
ret.ret = luci.sys.call("cd /usr/share/unblockneteasemusic && rm -rf core/* && rm -f core_local_ver") == 0
luci.http.prepare_content("application/json")
luci.http.write_json(ret)
end

View File

@ -0,0 +1,14 @@
local fs = require "nixio.fs"
local conffile = "/tmp/unblockneteasemusic.log"
f = SimpleForm("logview")
t = f:field(TextValue, "conf")
t.rmempty = true
t.rows = 15
function t.cfgvalue()
return fs.readfile(conffile) or ""
end
t.readonly="readonly"
return f

View File

@ -0,0 +1,247 @@
local m, s, o
m = Map("unblockneteasemusic", translate("解除网易云音乐播放限制"))
m.description = translate("原理:采用 [Bilibili/JOOX/酷狗/酷我/咪咕/pyncmd/QQ/Youtube] 等音源,替换网易云音乐 无版权/收费 歌曲链接<br/>具体使用方法参见:<a href=\"https://github.com/UnblockNeteaseMusic/luci-app-unblockneteasemusic\" target=\"_blank\">GitHub @UnblockNeteaseMusic/luci-app-unblockneteasemusic</a>")
m:section(SimpleSection).template = "unblockneteasemusic/status"
s = m:section(NamedSection, "config", "unblockneteasemusic")
o = s:option(Flag, "enable", translate("启用本插件"))
o.description = translate("启用本插件以解除网易云音乐播放限制")
o.default = 0
o.rmempty = false
o = s:option(Value, "music_source", translate("音源接口"))
o:value("default", translate("默认"))
o:value("bilibili", translate("Bilibili音乐"))
o:value("joox", translate("JOOX音乐"))
o:value("kugou", translate("酷狗音乐"))
o:value("kuwo", translate("酷我音乐"))
o:value("migu", translate("咪咕音乐"))
o:value("pyncmd", translate("网易云音乐pyncmd"))
o:value("qq", translate("QQ音乐"))
o:value("youtube", translate("Youtube音乐"))
o:value("youtubedl", translate("Youtube音乐youtube-dl"))
o:value("ytdlp", translate("Youtube音乐yt-dlp"))
o:value("ytdownload", translate("Youtube音乐ytdownload"))
o.description = translate("自定义模式下,多个音源请用空格隔开")
o.default = "default"
o.rmempty = false
o = s:option(Flag, "follow_source_order", translate("顺序查询"))
o.description = translate("默认为并行查询并返回第一个结果,开启后将严格按照配置音源的顺序进行查询")
o.default = 0
o.rmempty = false
o = s:option(Flag, "search_album", translate("附加专辑名"))
o.description = translate("在其他音源搜索歌曲时携带专辑名称(默认搜索条件 歌曲名 - 歌手,启用后搜索条件 歌曲名 - 歌手 专辑名)")
o.default = 0
o.rmempty = false
o = s:option(Flag, "local_vip", translate("启用本地 VIP"))
o.description = translate("启用后,可以使用去广告、个性换肤、鲸云音效等本地功能")
o.default = 0
o.rmempty = false
o = s:option(Flag, "enable_flac", translate("启用无损音质"))
o.description = translate("目前仅支持酷狗、酷我、咪咕、pyncmd、QQ 音源")
o.default = 0
o.rmempty = false
o = s:option(Flag, "disable_upgrade_check", translate("禁用更新检查"))
o.description = translate("禁止客户端检查更新,全平台支持")
o.default = 1
o.rmempty = false
o = s:option(ListValue, "replace_music_source", translate("强制音乐音源替换"))
o:value("dont_replace", translate("不强制替换音乐音源"))
o:value("lower_than_192kbps", translate("当音质低于 192 Kbps"))
o:value("lower_than_320kbps", translate("当音质低于 320 Kbps"))
o:value("lower_than_999kbps", translate("当音质低于 999 Kbps无损"))
o:value("replace_all", translate("替换所有音乐音源"))
o.description = translate("当音乐音质低于指定数值时,尝试强制使用其他平台的高音质版本进行替换")
o.default = "dont_replace"
o.rmempty = false
o = s:option(Flag, "use_custom_cookie", translate("使用自定义 Cookie"))
o.description = translate("使用自定义 Cookie 请求音源接口")
o.default = 0
o.rmempty = false
o = s:option(Value, "joox_cookie", translate("JOOX Cookie"))
o.description = translate("在 joox.com 获取,需要 wmid 和 session_key 值")
o.placeholder = "wmid=; session_key="
o.datatype = "string"
o:depends("use_custom_cookie", 1)
o = s:option(Value, "migu_cookie", translate("Migu Cookie"))
o.description = translate("通过抓包手机客户端请求获取,需要 aversionid 值")
o.datatype = "string"
o:depends("use_custom_cookie", 1)
o = s:option(Value, "qq_cookie", translate("QQ Cookie"))
o.description = translate("在 y.qq.com 获取,需要 uin 和 qm_keyst值 ")
o.placeholder = "uin=; qm_keyst="
o.datatype = "string"
o:depends("use_custom_cookie", 1)
o = s:option(Value, "youtube_key", translate("Youtube API Key"))
o.description = translate("API Key 申请地址https://developers.google.com/youtube/v3/getting-started#before-you-start")
o.datatype = "string"
o:depends("use_custom_cookie", 1)
o = s:option(Flag, "auto_update", translate("启用自动更新"))
o.description = translate("启用后,每天将定时自动检查最新版本并更新")
o.default = 0
o.rmempty = false
o = s:option(ListValue, "update_time", translate("检查更新时间"))
for update_time_hour = 0,23 do
o:value(update_time_hour, update_time_hour..":00")
end
o.default = "3"
o.description = translate("设定每天自动检查更新时间")
o:depends("auto_update", 1)
o = s:option(Button,"certificate", translate("HTTPS 证书"))
o.inputtitle = translate("下载 CA 根证书")
o.description = translate("Linux/iOS/MacOSX 在信任根证书后方可正常使用")
o.inputstyle = "reload"
o.write = function()
act_download_cert()
end
function act_download_cert()
local t, e
t = nixio.open("/usr/share/unblockneteasemusic/core/ca.crt","r")
luci.http.header('Content-Disposition', 'attachment; filename="ca.crt"')
luci.http.prepare_content("application/octet-stream")
while true do
e = t:read(nixio.const.buffersize)
if (not e) or (#e == 0) then
break
else
luci.http.write(e)
end
end
t:close()
luci.http.close()
end
o = s:option(Flag, "advanced_mode", translate("启用进阶设置"))
o.description = translate("非必要不推荐使用")
o.default = 0
o.rmempty = false
o = s:option(ListValue, "log_level", translate("日志等级"))
o:value("debug", translate("'调试"));
o:value("info", translate("信息(默认)"))
o:value("silent", translate("静默"))
o.default = "info";
o:depends("advanced_mode", 1)
o = s:option(Value, "http_port", translate("HTTP 监听端口"))
o.description = translate("程序监听的 HTTP 端口,不可与 其他程序/HTTPS 共用一个端口")
o.placeholder = "5200"
o.default = "5200"
o.datatype = "port"
o:depends({advanced_mode = true, hijack_ways = "dont_hijack"})
o:depends({advanced_mode = true, hijack_ways = "use_ipset"})
o = s:option(Value, "https_port", translate("HTTPS 监听端口"))
o.description = translate("程序监听的 HTTPS 端口,不可与 其他程序/HTTP 共用一个端口")
o.placeholder = "5201"
o.default = "5201"
o.datatype = "port"
o:depends({advanced_mode = true, hijack_ways = "dont_hijack"})
o:depends({advanced_mode = true, hijack_ways = "use_ipset"})
o = s:option(Value, "endpoint_url", translate("EndPoint"))
o.description = translate("具体说明参见https://github.com/UnblockNeteaseMusic/server")
o.default = "https://music.163.com"
o.placeholder = "https://music.163.com"
o.datatype = "string"
o:depends("advanced_mode", 1)
o = s:option(Value, "cnrelay", translate("UNM bridge 服务器"))
o.description = translate("使用 UnblockNeteaseMusic 中继桥bridge以获取音源信息")
o.placeholder = "http(s)://host:port"
o.datatype = "string"
o:depends("advanced_mode", 1)
o = s:option(ListValue, "hijack_ways", translate("劫持方法"))
o:value("dont_hijack", translate("不开启劫持"))
o:value("use_ipset", translate("使用 IPSet 劫持"))
o:value("use_hosts", translate("使用 Hosts 劫持"))
o.description = translate("如果使用Hosts劫持程序监听的 HTTP/HTTPS 端口将被锁定为 80/443")
o.default = "dont_hijack"
o:depends("advanced_mode", 1)
o = s:option(Flag, "keep_core_when_upgrade", translate("升级时保留核心程序"))
o.description = translate("默认情况下,在系统升级后会导致核心程序丢失,开启此选项后会保留当前下载的核心程序")
o.default = 0
o.rmempty = false
o:depends("advanced_mode", 1)
o = s:option(Flag, "pub_access", translate("部署到公网"))
o.description = translate("默认仅监听局域网,如需提供公开访问请勾选此选项")
o.default = 0
o.rmempty = false
o:depends("advanced_mode", 1)
o = s:option(Flag, "strict_mode", translate("启用严格模式"))
o.description = translate("若将服务部署到公网,则强烈建议使用严格模式,此模式下仅放行网易云音乐所属域名的请求;注意:该模式下不能使用全局代理")
o.default = 0
o.rmempty = false
o:depends("advanced_mode", 1)
o = s:option(Value, "netease_server_ip", translate("网易云服务器 IP"))
o.description = translate("通过 ping music.163.com 即可获得 IP 地址,仅限填写一个")
o.placeholder = "59.111.181.38"
o.datatype = "ipaddr"
o:depends("advanced_mode", 1)
o = s:option(Value, "proxy_server_ip", translate("代理服务器地址"))
o.description = translate("使用代理服务器获取音乐信息")
o.placeholder = "http(s)://host:port"
o.datatype = "string"
o:depends("advanced_mode", 1)
o = s:option(Value, "self_issue_cert_crt", translate("自签发证书公钥位置"))
o.description = translate("[公钥] 默认使用 UnblockNeteaseMusic 项目提供的 CA 证书,您可以指定为您自己的证书")
o.placeholder = "/usr/share/unblockneteasemusic/core/server.crt"
o.datatype = "file"
o:depends("advanced_mode", 1)
o = s:option(Value, "self_issue_cert_key", translate("自签发证书私钥位置"))
o.description = translate("[私钥] 默认使用 UnblockNeteaseMusic 项目提供的 CA 证书,您可以指定为您自己的证书")
o.placeholder = "/usr/share/unblockneteasemusic/core/server.key"
o.datatype = "file"
o:depends("advanced_mode", 1)
s = m:section(TypedSection, "acl_rule", translate("例外客户端规则"), translate("可以为局域网客户端分别设置不同的例外模式,默认无需设置"))
s.template = "cbi/tblsection"
s.sortable = true
s.anonymous = true
s.addremove = true
o = s:option(Value, "ip_addr", translate("IP 地址"))
o.width = "40%"
o.datatype = "ip4addr"
o.placeholder = "0.0.0.0/0"
luci.ip.neighbors({ family = 4 }, function(entry)
if entry.reachable then
o:value(entry.dest:string())
end
end)
o = s:option(ListValue, "filter_mode", translate("规则"))
o.width = "40%"
o.default = "disable_all"
o.rmempty = false
o:value("disable_all", translate("不代理 HTTP 和 HTTPS"))
o:value("disable_http", translate("不代理 HTTP"))
o:value("disable_https", translate("不代理 HTTPS"))
return m

View File

@ -0,0 +1,19 @@
local m, o
m = SimpleForm("Version")
m.reset = false
m.submit = false
o = m:field(DummyValue, "remove_core", translate("删除核心"))
o.rawhtml = true
o.template = "unblockneteasemusic/remove_core"
o.value = translate("")
o.description = "删除核心后,需手动点击下面的按钮重新下载,有助于解决版本冲突问题"
o = m:field(DummyValue, "update_core", translate("更新核心"))
o.rawhtml = true
o.template = "unblockneteasemusic/update_core"
o.value = translate("")
o.description = "更新完毕后会自动在后台重启插件,无需手动重启"
return m

View File

@ -0,0 +1,32 @@
<%+cbi/valueheader%>
<script type="text/javascript">//<![CDATA[
function act_remove_core(btn, dataname)
{
btn.disabled = true;
btn.value = '<%:正在删除核心...%>';
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "unblockneteasemusic", "remove_core")%>',
status.ret,
function(x, status)
{
var s = document.getElementById(dataname + '-status');
if (s)
{
if(status.ret)
s.innerHTML = "<font style=\"color:green\">" + "<%:删除核心成功%>" + "</font>";
else
s.innerHTML = "<font style=\"color:red\">" + "<%:删除核心失败%>"+"</font>";
}
btn.disabled = false;
btn.value = '<%:点此删除核心%>';
}
);
return false;
}
//]]></script>
<input type="button" class="btn cbi-button cbi-input-reload" value="<%:点此删除核心%>" onclick="return act_remove_core(this, '<%=self.option%>')" />
<span id="<%=self.option%>-status"><em><%=self.value%></em></span>
<%+cbi/valuefooter%>

View File

@ -0,0 +1,21 @@
<script type="text/javascript">//<![CDATA[
XHR.poll(3, '<%=url([[admin]], [[services]], [[unblockneteasemusic]], [[status]])%>', null,
function(x, data) {
var tb = document.getElementById('unblockneteasemusic_status');
if (data && tb) {
if (data.running) {
tb.innerHTML = '<em><b style=color:green>UnblockNeteaseMusic <%:运行中%></b></em>';
} else {
tb.innerHTML = '<em><b style=color:red>UnblockNeteaseMusic <%:未在运行%></b></em>';
}
}
}
);
//]]>
</script>
<style>.mar-10 {margin-left: 50px; margin-right: 10px;}</style>
<fieldset class="cbi-section">
<p id="unblockneteasemusic_status">
<em><%:Collecting data...%></em>
</p>
</fieldset>

View File

@ -0,0 +1,36 @@
<%+cbi/valueheader%>
<script type="text/javascript">//<![CDATA[
function act_update_core(btn, dataname)
{
btn.disabled = true;
btn.value = '<%:正在更新核心...%>';
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "unblockneteasemusic", "update_core")%>',
status.ret,
function(x, status)
{
var s = document.getElementById(dataname + '-status');
if (s)
{
if(status.ret == "0")
s.innerHTML = "<font style=\"color:green\">" + "<%:当前已是最新版本%>" + "</font>";
else if (status.ret == "1")
s.innerHTML = "<font style=\"color:red\">" + "<%:无法检测最新版本%>" + "</font>";
else if(status.ret == "2")
s.innerHTML = "<font style=\"color:red\">" + "<%:更新失败,请稍后重试%>" + "</font>";
else
s.innerHTML = "<font style=\"color:green\">" + "<%:更新成功,当前版本号:%>" + status.ret + "</font>";
}
btn.disabled = false;
btn.value = '<%:点此更新核心%>';
}
);
return false;
}
//]]></script>
<input type="button" class="btn cbi-button cbi-input-reload" value="<%:点此更新核心%>" onclick="return act_update_core(this, '<%=self.option%>')" />
<span id="<%=self.option%>-status"><em><%=self.value%></em></span>
<%+cbi/valuefooter%>

View File

@ -1,7 +1,13 @@
config unblockneteasemusic 'config'
option enable '0'
option music_source 'default'
option follow_source_order '0'
option local_vip '0'
option enable_flac '0'
option disable_upgrade_check '1'
option replace_music_source 'dont_replace'
option use_custom_cookie '0'
option auto_update '1'
option update_time '3'
option advanced_mode '0'

View File

@ -50,8 +50,6 @@ append_param_boolenv() {
append_filter_client() {
local cfg="$1"
is_enabled "$cfg" "enable" || return 1
local ip_addr filter_mode
config_get ip_addr "$cfg" "ip_addr"
config_get filter_mode "$cfg" "filter_mode"
@ -106,7 +104,7 @@ start_service() {
config_get http_port "config" "http_port" "5200"
config_get https_port "config" "https_port" "5201"
config_get hijack_ways "config" "hijack_ways" "use_ipset"
[ "$hijack_ways" != "use_hosts" ] || { http_port="80"; https_port="443"; }
[ "${hijack_ways}" = "use_hosts" ] && { http_port="80"; https_port="443"; }
append_param "-p" "${http_port}":"${https_port}"
if [ -n "$FW4" ]; then
@ -130,7 +128,10 @@ start_service() {
fi
fi
append_param_arg "config" "music_source" "-o"
local music_source
config_get music_source "config" "music_source" "default"
[ "${music_source}" != "default" ] && append_param -o "${music_source}"
append_param_arg "config" "cnrelay" "-c"
append_param_arg "config" "endpoint_url" "-e" "https://music.163.com"
append_param_arg "config" "netease_server_ip" "-f"
@ -152,10 +153,8 @@ start_service() {
append_param_boolenv "config" "follow_source_order" "FOLLOW_SOURCE_ORDER"
append_param_boolenv "config" "search_album" "SEARCH_ALBUM"
append_param_boolenv "config" "enable_flac" "ENABLE_FLAC"
append_param_boolenv "config" "select_max_br" "SELECT_MAX_BR"
append_param_boolenv "config" "disable_upgrade_check" "DISABLE_UPGRADE_CHECK"
append_param_boolenv "config" "block_ads" "BLOCK_ADS"
append_param_boolenv "config" "local_vip" "ENABLE_LOCAL_VIP"
append_param_boolenv "config" "disable_upgrade_check" "DISABLE_UPGRADE_CHECK"
case "$(config_get "config" "replace_music_source")" in
"lower_than_192kbps") procd_append_param env MIN_BR="192000" ;;
"lower_than_320kbps") procd_append_param env MIN_BR="320000" ;;

View File

@ -1,23 +1,20 @@
#!/bin/sh
if [ "$(uci -q get unblockneteasemusic.config.music_source)" = "default" ]; then
uci -q delete "unblockneteasemusic.config.music_source"
uci -q commit "unblockneteasemusic"
fi
uci -q batch <<-EOF >"/dev/null"
uci -q batch <<-EOF >/dev/null
delete ucitrack.@unblockneteasemusic[-1]
add ucitrack unblockneteasemusic
set ucitrack.@unblockneteasemusic[-1].init=unblockneteasemusic
commit ucitrack
EOF
[ -e "$(command -v fw4)" ] || {
uci -q batch <<-EOF >"/dev/null"
delete firewall.unblockneteasemusic
set firewall.unblockneteasemusic=include
set firewall.unblockneteasemusic.type=script
set firewall.unblockneteasemusic.path=/var/etc/unblockneteasemusic.include
set firewall.unblockneteasemusic.reload=1
commit firewall
EOF
uci -q batch <<-EOF >/dev/null
delete firewall.unblockneteasemusic
set firewall.unblockneteasemusic=include
set firewall.unblockneteasemusic.type=script
set firewall.unblockneteasemusic.path=/var/etc/unblockneteasemusic.include
set firewall.unblockneteasemusic.reload=1
commit firewall
EOF
}
rm -f /tmp/luci-indexcache

View File

@ -1,29 +0,0 @@
{
"admin/services/unblockneteasemusic": {
"title": "解除网易云音乐播放限制",
"order": 50,
"action": {
"type": "firstchild"
},
"depends": {
"acl": [ "luci-app-unblockneteasemusic" ],
"uci": { "unblockneteasemusic": true }
}
},
"admin/services/unblockneteasemusic/config": {
"title": "基本设定",
"order": 10,
"action": {
"type": "view",
"path": "unblockneteasemusic/config"
}
},
"admin/services/unblockneteasemusic/status": {
"title": "状态信息",
"order": 20,
"action": {
"type": "view",
"path": "unblockneteasemusic/status"
}
}
}

View File

@ -1,24 +1,10 @@
{
"luci-app-unblockneteasemusic": {
"description": "Grant access to UnblockNeteaseMusic configuration",
"description": "Grant UCI access for luci-app-unblockneteasemusic",
"read": {
"file": {
"/etc/init.d/unblockneteasemusic": [ "exec" ],
"/tmp/unblockneteasemusic.log": [ "read" ],
"/usr/bin/unm-debug": [ "exec" ],
"/usr/share/unblockneteasemusic/update.sh": [ "exec" ],
"/usr/share/unblockneteasemusic/core/ca.crt": [ "read" ]
},
"ubus": {
"service": [ "list" ]
},
"uci": [ "unblockneteasemusic" ]
},
"write": {
"file": {
"/usr/share/unblockneteasemusic/server.crt": [ "write" ],
"/usr/share/unblockneteasemusic/server.key": [ "write" ]
},
"uci": [ "unblockneteasemusic" ]
}
}

View File

@ -1,6 +1,6 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-3.0-only
# Copyright (C) 2019-2023 Tianling Shen <cnsztl@immortalwrt.org>
# Copyright (C) 2019-2022 Tianling Shen <cnsztl@immortalwrt.org>
NAME="unblockneteasemusic"
LOCK="/tmp/$NAME.update_core.lock"
@ -65,6 +65,7 @@ update_core() {
}
done
[ -z "${update_core_from_luci}" ] || touch "/usr/share/$NAME/update_core_successfully"
echo -e "${core_latest_ver}" > "/usr/share/$NAME/core_local_ver"
[ -n "${non_restart}" ] || /etc/init.d/"$NAME" restart
@ -74,17 +75,6 @@ update_core() {
}
case "$1" in
"check_version")
if [ ! -e "/usr/share/$NAME/core_local_ver" ] || [ ! -e "/usr/share/$NAME/core/app.js" ]; then
echo -e "Not installed."
exit 2
else
version="$(node "/usr/share/$NAME/core/app.js" -v)"
commit="$(cat "/usr/share/$NAME/core_local_ver" | head -c7)"
echo "$version ($commit)"
exit 0
fi
;;
"update_core")
check_core_if_already_running
check_core_latest_version
@ -94,11 +84,12 @@ case "$1" in
check_core_if_already_running
check_core_latest_version
;;
"remove_core")
/etc/init.d/"$NAME" stop
rm -rf "/usr/share/$NAME/core" "/usr/share/$NAME/core_local_ver" "$LOCK"
"update_core_from_luci")
update_core_from_luci=1
check_core_if_already_running
check_core_latest_version
;;
*)
echo -e "Usage: $0/update.sh check_version | update_core | remove_core"
echo -e "Usage: $0/update.sh update_core"
;;
esac

Binary file not shown.

Before

Width:  |  Height:  |  Size: 776 KiB

After

Width:  |  Height:  |  Size: 786 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB