config.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. /* SPDX-License-Identifier: GPL-3.0-only
  2. *
  3. * Copyright (C) 2022 ImmortalWrt.org
  4. */
  5. 'use strict';
  6. 'require form';
  7. 'require fs';
  8. 'require network';
  9. 'require poll';
  10. 'require rpc';
  11. 'require uci';
  12. 'require ui';
  13. 'require view';
  14. var CBIStaticList = form.DynamicList.extend({
  15. __name__: 'CBI.StaticList',
  16. renderWidget: function(/* ... */) {
  17. var dl = form.DynamicList.prototype.renderWidget.apply(this, arguments);
  18. dl.querySelector('.add-item ul > li[data-value="-"]').remove();
  19. return dl;
  20. }
  21. });
  22. var callServiceList = rpc.declare({
  23. object: 'service',
  24. method: 'list',
  25. params: ['name'],
  26. expect: { '': {} }
  27. });
  28. function getServiceStatus() {
  29. return L.resolveDefault(callServiceList('unblockneteasemusic'), {}).then(function (res) {
  30. var isRunning = false;
  31. try {
  32. isRunning = res['unblockneteasemusic']['instances']['unblockneteasemusic']['running'];
  33. } catch (e) { }
  34. return isRunning;
  35. });
  36. }
  37. function renderStatus(isRunning) {
  38. var spanTemp = '<em><span style="color:%s"><strong>%s %s</strong></span></em>';
  39. var renderHTML;
  40. if (isRunning) {
  41. renderHTML = spanTemp.format('green', _('UnblockNeteaseMusic'), _('运行中'));
  42. } else {
  43. renderHTML = spanTemp.format('red', _('UnblockNeteaseMusic'), _('未运行'));
  44. }
  45. return renderHTML;
  46. }
  47. function uploadCertificate(type, filename, ev) {
  48. return ui.uploadFile('/usr/share/unblockneteasemusic/' + filename, ev.target)
  49. .then(L.bind(function(btn, res) {
  50. btn.firstChild.data = _('检查 %s 中...').format(type);
  51. if (res.size <= 0) {
  52. ui.addNotification(null, E('p', _('上传的 %s 为空。').format(tyupe)));
  53. return fs.remove('/usr/share/unblockneteasemusic/' + filename);
  54. }
  55. ui.addNotification(null, E('p', _('您的 %s 已成功上传。大小:%sB。').format(type, res.size)));
  56. }, this, ev.target))
  57. .catch(function(e) { ui.addNotification(null, E('p', e.message)) })
  58. .finally(L.bind(function(btn, input) {
  59. btn.firstChild.data = _('上传...');
  60. }, this, ev.target));
  61. }
  62. return view.extend({
  63. load: function() {
  64. return Promise.all([
  65. uci.load('unblockneteasemusic'),
  66. network.getHostHints()
  67. ]);
  68. },
  69. render: function(data) {
  70. var m, s, o;
  71. m = new form.Map('unblockneteasemusic', _('解除网易云音乐播放限制'),
  72. _('原理:采用 [Bilibili/JOOX/酷狗/酷我/咪咕/pyncmd/QQ/Youtube] 等音源,替换网易云音乐 无版权/收费 歌曲链接<br/>' +
  73. '具体使用方法参见:<a href="https://github.com/UnblockNeteaseMusic/luci-app-unblockneteasemusic" target="_blank">GitHub @UnblockNeteaseMusic/luci-app-unblockneteasemusic</a>'));
  74. s = m.section(form.TypedSection);
  75. s.anonymous = true;
  76. s.render = function () {
  77. poll.add(function () {
  78. return L.resolveDefault(getServiceStatus()).then(function (res) {
  79. var view = document.getElementById('service_status');
  80. view.innerHTML = renderStatus(res);
  81. });
  82. });
  83. return E('div', { class: 'cbi-section', id: 'status_bar' }, [
  84. E('p', { id: 'service_status' }, _('收集数据中...'))
  85. ]);
  86. }
  87. s = m.section(form.NamedSection, 'config', 'unblockneteasemusic');
  88. o = s.option(form.Flag, 'enable', _('启用服务'));
  89. o.default = o.disabled;
  90. o.rmempty = false;
  91. o = s.option(CBIStaticList, 'music_source', _('音源接口'),
  92. _('留空以使用默认音源。'));
  93. o.value('bilibili', _('Bilibili 音乐'));
  94. o.value('joox', _('JOOX 音乐'));
  95. o.value('kugou', _('酷狗音乐'));
  96. o.value('kuwo', _('酷我音乐'));
  97. o.value('migu', _('咪咕音乐'));
  98. o.value('pyncmd', _('网易云音乐(pyncmd)'));
  99. o.value('qq', _('QQ 音乐'));
  100. o.value('youtube', _('Youtube 音乐'));
  101. o.value('youtubedl', _('Youtube 音乐(youtube-dl)'));
  102. o.value('ytdlp', _('Youtube 音乐(yt-dlp)'));
  103. o.value('ytdownload', _('Youtube 音乐(ytdownload)'));
  104. o = s.option(form.Value, 'joox_cookie', _('JOOX Cookie'),
  105. _('在 joox.com 获取,需要 wmid 和 session_key 值。'));
  106. o.placeholder = 'wmid=; session_key=';
  107. o.depends({'music_source': 'joox', '!contains': true});
  108. o = s.option(form.Value, 'migu_cookie', _('Migu Cookie'),
  109. _('通过抓包手机客户端请求获取,需要 aversionid 值。'));
  110. o.depends({'music_source': 'migu', '!contains': true});
  111. o = s.option(form.Value, 'qq_cookie', _('QQ Cookie'),
  112. _('在 y.qq.com 获取,需要 uin 和 qm_keyst 值。'));
  113. o.placeholder = 'uin=; qm_keyst=';
  114. o.depends({'music_source': 'qq', '!contains': true});
  115. o = s.option(form.Value, 'youtube_key', _('Youtube API Key'),
  116. _('API Key 申请地址:https://developers.google.com/youtube/v3/getting-started#before-you-start'));
  117. o.depends({'music_source': 'youtube', '!contains': true});
  118. o = s.option(form.Flag, 'follow_source_order', _('顺序查询'),
  119. _('默认为并行查询并返回第一个结果,开启后将严格按照配置音源的顺序进行查询。'))
  120. o.default = o.disabled;
  121. o.rmempty = false;
  122. o = s.option(form.Flag, 'local_vip', _('启用本地 VIP'),
  123. _('启用后,可以使用去广告、个性换肤、鲸云音效等本地功能。'));
  124. o.default = o.disabled;
  125. o.rmempty = false;
  126. o = s.option(form.Flag, 'enable_flac', _('启用无损音质'),
  127. _('目前仅支持酷狗、酷我、咪咕、pyncmd、QQ 音源。'));
  128. o.default = o.disabled;
  129. o.rmempty = false;
  130. o = s.option(form.Flag, 'disable_upgrade_check', _('禁用更新检查'),
  131. _('禁止客户端检查更新,全平台支持。'));
  132. o.default = o.enabled;
  133. o.rmempty = false;
  134. o = s.option(form.ListValue, 'replace_music_source', _('音源替换'),
  135. _('当音乐音质低于指定数值时,尝试强制使用其他平台的高音质版本进行替换。'));
  136. o.value('dont_replace', _('不强制替换音乐音源'));
  137. o.value('lower_than_192kbps', _('当音质低于 192 Kbps(中)时'));
  138. o.value('lower_than_320kbps', _('当音质低于 320 Kbps(高)时'));
  139. o.value('lower_than_999kbps', _('当音质低于 999 Kbps(无损)时'));
  140. o.value('replace_all', _('替换所有音乐音源'));
  141. o.default = 'dont_replace';
  142. o.rmempty = false;
  143. o = s.option(form.Flag, 'auto_update', _('启用自动更新'),
  144. _('启用后,每天将定时自动检查最新版本并更新。'));
  145. o.default = o.disabled;
  146. o.rmempty = false;
  147. o = s.option(form.ListValue, 'update_time', '检查更新时间',
  148. _('设定每天自动检查更新时间。'));
  149. for (var i = 0; i < 24; i++)
  150. o.value(i, i + ':00');
  151. o.default = '3';
  152. o.depends('auto_update', '1');
  153. o = s.option(form.Button, '_download_cert', _('CA 根证书'),
  154. _('Linux / iOS / MacOSX 在信任根证书后方可正常使用。'));
  155. o.inputstyle = 'apply';
  156. o.inputtitle = _('下载 ca.crt');
  157. o.onclick = function() {
  158. return fs.read_direct('/usr/share/unblockneteasemusic/core/ca.crt', 'blob').then(function(blob) {
  159. var url = window.URL.createObjectURL(blob);
  160. var link = E('a', { 'style': 'display:none', 'href': url, 'download': 'ca.crt' });
  161. document.body.appendChild(link);
  162. link.click();
  163. document.body.removeChild(link);
  164. window.URL.revokeObjectURL(url);
  165. }).catch(function(err) {
  166. ui.addNotification(null, E('p', [ _('下载文件失败:%s。').format(err.message) ]));
  167. });
  168. }
  169. o = s.option(form.Flag, 'advanced_mode', _('启用进阶设置'),
  170. _('非必要不推荐使用。'));
  171. o.default = o.disabled,
  172. o.rmempty = false;
  173. o = s.option(form.ListValue, 'log_level', _('日志等级'));
  174. o.value('debug', _('调试'));
  175. o.value('info', _('信息(默认)'));
  176. o.value('silent', _('静默'));
  177. o.default = 'info';
  178. o.rmempty = false;
  179. o.depends('advanced_mode', '1');
  180. o = s.option(form.Value, 'http_port', _('HTTP 监听端口'));
  181. o.datatype = 'port';
  182. o.default = '5200';
  183. o.rmempty = false;
  184. o.depends({'advanced_mode': '1', 'hijack_ways': 'dont_hijack'});
  185. o.depends({'advanced_mode': '1', 'hijack_ways': 'use_ipset'});
  186. o = s.option(form.Value, 'https_port', _('HTTPS 监听端口'));
  187. o.datatype = 'port';
  188. o.default = '5201';
  189. o.rmempty = false;
  190. o.depends({'advanced_mode': '1', 'hijack_ways': 'dont_hijack'});
  191. o.depends({'advanced_mode': '1', 'hijack_ways': 'use_ipset'});
  192. o = s.option(form.Value, 'endpoint_url', _('EndPoint'),
  193. _('音源地址反代(包装)。'));
  194. o.default = 'https://music.163.com';
  195. o.rmempty = false;
  196. o.depends('advanced_mode', '1');
  197. o = s.option(form.Value, 'cnrelay', _('UNM Bridge 服务器'),
  198. _('使用 UnblockNeteaseMusic 中继桥(bridge)以获取音源信息。'));
  199. o.placeholder = 'http(s)://host:port'
  200. o.depends('advanced_mode', '1');
  201. o = s.option(form.ListValue, 'hijack_ways', _('劫持方法'),
  202. _('如果使用 Hosts 劫持,程序监听的 HTTP/HTTPS 端口将被锁定为 80/443。'));
  203. o.value('dont_hijack', _('不开启劫持'));
  204. o.value('use_ipset', _('使用 IPSet 劫持'));
  205. o.value('use_hosts', _('使用 Hosts 劫持'));
  206. o.default = 'dont_hijack';
  207. o.rmempty = false;
  208. o.depends('advanced_mode', '1');
  209. o = s.option(form.Flag, 'keep_core_when_upgrade', _('升级时保留核心程序'));
  210. o.default = o.disabled;
  211. o.rmempty = false;
  212. o.depends('advanced_mode', '1');
  213. o = s.option(form.Flag, 'pub_access', _('部署到公网'),
  214. _('默认仅放行局域网请求,如需提供公开访问请勾选此选项。'));
  215. o.default = o.disabled;
  216. o.rmempty = false;
  217. o.depends('advanced_mode', '1');
  218. o = s.option(form.Flag, 'strict_mode', _('启用严格模式'),
  219. _('若将服务部署到公网,则强烈建议使用严格模式,此模式下仅放行网易云音乐所属域名的请求。<br/>注意:该模式下不能使用全局代理。'));
  220. o.default = o.disabled;
  221. o.rmempty = false;
  222. o.depends('advanced_mode', '1');
  223. o = s.option(form.Value, 'netease_server_ip', _('网易云服务器 IP'));
  224. o.datatype = 'ipaddr';
  225. o.depends('advanced_mode', '1');
  226. o = s.option(form.Value, 'proxy_server_ip', _('代理服务器地址'),
  227. _('使用代理服务器获取音乐信息。'));
  228. o.placeholder = 'http(s)://host:port';
  229. o.depends('advanced_mode', '1');
  230. o = s.option(form.Value, 'self_issue_cert_crt', _('自签发证书公钥位置'));
  231. o.value('/usr/share/unblockneteasemusic/core/server.crt', _('内置公钥'));
  232. o.value('/usr/share/unblockneteasemusic/server.crt');
  233. o.default = '/usr/share/unblockneteasemusic/core/server.crt';
  234. o.datatype = 'file';
  235. o.depends('advanced_mode', '1');
  236. o = s.option(form.Button, '_upload_cert', _('上传公钥'));
  237. o.inputstyle = 'action';
  238. o.inputtitle = _('上传...');
  239. o.depends('self_issue_cert_crt', '/usr/share/unblockneteasemusic/server.crt');
  240. o.onclick = L.bind(uploadCertificate, this, _('公钥'), 'server.crt');
  241. o.modalonly = true;
  242. o = s.option(form.Value, 'self_issue_cert_key', _('自签发证书私钥位置'));
  243. o.value('/usr/share/unblockneteasemusic/core/server.key', _('内置私钥'));
  244. o.value('/usr/share/unblockneteasemusic/server.key');
  245. o.default = '/usr/share/unblockneteasemusic/core/server.key';
  246. o.datatype = 'file'
  247. o.depends('advanced_mode', '1');
  248. o = s.option(form.Button, '_upload_key', _('上传私钥'));
  249. o.inputstyle = 'action';
  250. o.inputtitle = _('上传...');
  251. o.depends('self_issue_cert_key', '/usr/share/unblockneteasemusic/server.key');
  252. o.onclick = L.bind(uploadCertificate, this, _('私钥'), 'server.key');
  253. o.modalonly = true;
  254. s = m.section(form.TableSection, 'acl_rule', _('例外客户端规则'),
  255. _('可以为局域网客户端分别设置不同的例外模式。'));
  256. s.addremove = true;
  257. s.anonymous = true;
  258. s.sortable = true;
  259. o = s.option(form.Flag, 'enable', _('启用'));
  260. o.default = o.enabled;
  261. o.rmempty = false;
  262. o = s.option(form.Value, 'ip_addr', _('IP 地址'));
  263. o.datatype = 'ip4addr';
  264. for (var i of Object.entries(data[1].hosts))
  265. for (var v in i[1].ipaddrs)
  266. if (i[1].ipaddrs[v]) {
  267. var ip_addr = i[1].ipaddrs[v], ip_host = i[1].name;
  268. o.value(ip_addr, ip_host ? String.format('%s (%s)', ip_host, ip_addr) : ip_addr)
  269. }
  270. o.rmempty = false;
  271. o = s.option(form.ListValue, 'filter_mode', _('规则'));
  272. o.value('disable_all', _('不代理 HTTP 和 HTTPS'));
  273. o.value('disable_http', _('不代理 HTTP'));
  274. o.value('disable_https', _('不代理 HTTPS'));
  275. o.default = 'disable_all';
  276. o.rmempty = false;
  277. return m.render();
  278. }
  279. });