diff --git a/FAQ.md b/FAQ.md index 23759d43..71c061bd 100644 --- a/FAQ.md +++ b/FAQ.md @@ -445,6 +445,7 @@ send(EVENT_NAMES.inited, { | --- | --- | `inited` | 脚本初始化完成后发送给应用的事件名,发送该事件时需要传入以下信息:`{status, sources, openDevTools}`
`status`:初始化结果(`true`成功,`false`失败)
`openDevTools`:是否打开DevTools,此选项可用于开发脚本时的调试
`sources`:支持的源信息对象,
`sources[kw/kg/tx/wy/mg].name`:源的名字(目前非必须)
`sources[kw/kg/tx/wy/mg].type`:源类型,目前固定值需为`music`
`sources[kw/kg/tx/wy/mg].actions`:支持的actions,由于目前只支持`musicUrl`,所以固定传`['musicUrl']`即可
`sources[kw/kg/tx/wy/mg].qualitys`:该源支持的音质列表,有效的值为`['128k', '320k', 'flac']`,该字段用于控制应用可用的音质类型 | `request` | 应用API请求事件名,回调入参:`handler({ source, action, info})`,回调必须返回`Promise`对象
`source`:音乐源,可能的值取决于初始化时传入的`sources`对象的源key值
`info`:请求附加信息,内容根据`action`变化
`action`:请求操作类型,目前只有`musicUrl`,即获取音乐URL链接,需要在 Promise 返回歌曲 url,`info`的结构:`{type, musicInfo}`,`info.type`:音乐质量,可能的值有`128k` / `320k` / `flac`(取决于初始化时对应源传入的`qualitys`值中的一个),`info.musicInfo`:音乐信息对象,里面有音乐ID、名字等信息 +| `updateAlert` | 显示源更新弹窗,发送此事件时,需要传入一个字符串(更新日志),内容可以使用`\n`换行,最大长度1024,超过此长度后将被截取超出的部分,此事件每次运行脚本只能调用一次(源版本v1.2.0新增)
例子:`lx.send(lx.EVENT_NAMES.updateAlert, 'hello world')` #### `window.lx.on` diff --git a/publish/changeLog.md b/publish/changeLog.md index a99e3bd0..05989f72 100644 --- a/publish/changeLog.md +++ b/publish/changeLog.md @@ -5,6 +5,7 @@ - 新增定时暂停播放功能,由于此功能大多数人可能不常用,所以将其放在设置-基本设置中 - 新增Scheme URL对音乐搜索的调用支持,详情看常见问题-Scheme URL支持 - 新增Scheme URL以url传参的方式调用,详情看常见问题-Scheme URL支持 +- 自定义源新增更新弹窗方法,同时自定义源管理新增是否允许源显示更新弹窗设置(出于防止滥用考虑),当源作者想要通知用户源已更新时,可以调用此方法弹窗告诉用户,调用说明看常见问题-自定义源部分 ### 优化 diff --git a/src/common/ipcNames.js b/src/common/ipcNames.js index 2293a819..afd62c7a 100644 --- a/src/common/ipcNames.js +++ b/src/common/ipcNames.js @@ -69,6 +69,8 @@ const names = { request_user_api_cancel: 'request_user_api_cancel', get_user_api_status: 'get_user_api_status', user_api_status: 'user_api_status', + user_api_show_update_alert: 'user_api_show_update_alert', + user_api_set_allow_update_alert: 'user_api_set_allow_update_alert', get_lyric: 'get_lyric', save_lyric: 'save_lyric', diff --git a/src/lang/en-us.json b/src/lang/en-us.json index 32af9b11..b83b3bb0 100644 --- a/src/lang/en-us.json +++ b/src/lang/en-us.json @@ -410,6 +410,7 @@ "theme_purple": "Purple", "theme_red": "Red", "theme_yellow": "Yellow", + "user_api__allow_show_update_alert": "Allow update popup to show", "user_api__btn_export": "Export", "user_api__btn_import": "Import", "user_api__btn_remove": "Remove", @@ -417,5 +418,6 @@ "user_api__noitem": "There is nothing here...😲", "user_api__note": "Tip: Although we have isolated the script's running environment as much as possible, importing scripts containing malicious behaviors may still affect your system. Please import them carefully.", "user_api__readme": "Source writing instructions: ", - "user_api__title": "Custom Source Management" + "user_api__title": "Custom Source Management", + "user_api__update_alert": "Custom source [{name}] found new version:" } diff --git a/src/lang/zh-cn.json b/src/lang/zh-cn.json index 4bd878a7..fdf2c8c5 100644 --- a/src/lang/zh-cn.json +++ b/src/lang/zh-cn.json @@ -410,6 +410,7 @@ "theme_purple": "重斤球紫", "theme_red": "热情似火", "theme_yellow": "信口雌黄", + "user_api__allow_show_update_alert": "允许显示更新弹窗", "user_api__btn_export": "导出", "user_api__btn_import": "导入", "user_api__btn_remove": "移除", @@ -417,5 +418,6 @@ "user_api__noitem": "这里竟然是空的 😲", "user_api__note": "提示:虽然我们已经尽可能地隔离了脚本的运行环境,但导入包含恶意行为的脚本仍可能会影响你的系统,请谨慎导入。", "user_api__readme": "源编写说明:", - "user_api__title": "自定义源管理" + "user_api__title": "自定义源管理", + "user_api__update_alert": "自定义源 [{name}] 发现新版本:" } diff --git a/src/lang/zh-tw.json b/src/lang/zh-tw.json index 93769b46..10febd01 100644 --- a/src/lang/zh-tw.json +++ b/src/lang/zh-tw.json @@ -410,6 +410,7 @@ "theme_purple": "重斤球紫", "theme_red": "熱情似火", "theme_yellow": "信口雌黃", + "user_api__allow_show_update_alert": "允許顯示更新彈窗", "user_api__btn_export": "導出", "user_api__btn_import": "導入", "user_api__btn_remove": "移除", @@ -417,5 +418,6 @@ "user_api__noitem": "這裡竟然是空的 😲", "user_api__note": "提示:雖然我們已經盡可能地隔離了腳本的運行環境,但導入包含惡意行為的腳本仍可能會影響你的系統,請謹慎導入。", "user_api__readme": "源編寫說明:", - "user_api__title": "自定義源管理" + "user_api__title": "自定義源管理", + "user_api__update_alert": "自定義源 [{name}] 發現新版本:" } diff --git a/src/main/modules/userApi/event/event.js b/src/main/modules/userApi/event/event.js index b0c3a2cc..ff384904 100644 --- a/src/main/modules/userApi/event/event.js +++ b/src/main/modules/userApi/event/event.js @@ -5,6 +5,10 @@ class UserApi extends EventEmitter { status(info) { this.emit(USER_API_EVENT_NAME.status, info) } + + showUpdateAlert(info) { + this.emit(USER_API_EVENT_NAME.showUpdateAlert, info) + } } module.exports = UserApi diff --git a/src/main/modules/userApi/event/name.js b/src/main/modules/userApi/event/name.js index 5e57ccbf..865ce773 100644 --- a/src/main/modules/userApi/event/name.js +++ b/src/main/modules/userApi/event/name.js @@ -1,3 +1,4 @@ module.exports = { status: 'status', + showUpdateAlert: 'showUpdateAlert', } diff --git a/src/main/modules/userApi/index.js b/src/main/modules/userApi/index.js index 261675df..3a9bba72 100644 --- a/src/main/modules/userApi/index.js +++ b/src/main/modules/userApi/index.js @@ -1,8 +1,8 @@ const Event = require('./event/event') const eventNames = require('./event/name') const { closeWindow } = require('./main') -const { getUserApis, importApi, removeApi } = require('./utils') -const { request, cancelRequest, getStatus, loadApi } = require('./rendererEvent/rendererEvent') +const { getUserApis, importApi, removeApi, setAllowShowUpdateAlert: saveAllowShowUpdateAlert } = require('./utils') +const { request, cancelRequest, getStatus, loadApi, setAllowShowUpdateAlert } = require('./rendererEvent/rendererEvent') // const { getApiList, importApi, removeApi, setApi, getStatus, request, eventNames } let userApiId @@ -41,4 +41,7 @@ exports.setApi = async id => { await loadApi(id) } - +exports.setAllowShowUpdateAlert = (id, enable) => { + saveAllowShowUpdateAlert(id, enable) + setAllowShowUpdateAlert(id, enable) +} diff --git a/src/main/modules/userApi/main.js b/src/main/modules/userApi/main.js index f844eace..7b30f679 100644 --- a/src/main/modules/userApi/main.js +++ b/src/main/modules/userApi/main.js @@ -15,7 +15,6 @@ fs.readFile(path.join(dir, 'renderer/user-api.html'), 'utf8', (err, data) => { }) const denyEvents = [ - 'new-window', 'will-navigate', 'will-redirect', 'will-attach-webview', @@ -49,11 +48,13 @@ exports.createWindow = async userApi => { contextIsolation: true, // worldSafeExecuteJavaScript: true, nodeIntegration: false, + nodeIntegrationInWorker: false, spellcheck: false, autoplayPolicy: 'document-user-activation-required', enableWebSQL: false, disableDialogs: true, + nativeWindowOpen: false, webgl: false, images: false, @@ -70,6 +71,9 @@ exports.createWindow = async userApi => { // eslint-disable-next-line node/no-callback-literal callback(false) }) + global.modules.userApiWindow.webContents.setWindowOpenHandler(() => { + return { action: 'deny' } + }) winEvent(global.modules.userApiWindow) diff --git a/src/main/modules/userApi/renderer/preload.js b/src/main/modules/userApi/renderer/preload.js index 8e520e4c..8bc4e4d9 100644 --- a/src/main/modules/userApi/renderer/preload.js +++ b/src/main/modules/userApi/renderer/preload.js @@ -3,14 +3,16 @@ const needle = require('needle') const { createCipheriv, publicEncrypt, constants, randomBytes, createHash } = require('crypto') const USER_API_RENDERER_EVENT_NAME = require('../rendererEvent/name') -const sendMessage = (action, status, data, message) => { - ipcRenderer.send(action, { status, data, message }) +const sendMessage = (action, data, status, message) => { + ipcRenderer.send(action, { data, status, message }) } let isInitedApi = false +let isShowedUpdateAlert = false const EVENT_NAMES = { request: 'request', inited: 'inited', + updateAlert: 'updateAlert', } const eventNames = Object.values(EVENT_NAMES) const events = { @@ -35,7 +37,7 @@ const supportActions = { const handleRequest = (context, { requestKey, data }) => { // console.log(data) - if (!events.request) return sendMessage(USER_API_RENDERER_EVENT_NAME.response, false, { requestKey }, 'Request event is not defined') + if (!events.request) return sendMessage(USER_API_RENDERER_EVENT_NAME.response, { requestKey }, false, 'Request event is not defined') try { events.request.call(context, { source: data.source, action: data.action, info: data.info }).then(response => { let sendData = { @@ -53,12 +55,12 @@ const handleRequest = (context, { requestKey, data }) => { } break } - sendMessage(USER_API_RENDERER_EVENT_NAME.response, true, sendData) + sendMessage(USER_API_RENDERER_EVENT_NAME.response, sendData, true) }).catch(err => { - sendMessage(USER_API_RENDERER_EVENT_NAME.response, false, { requestKey }, err.message) + sendMessage(USER_API_RENDERER_EVENT_NAME.response, { requestKey }, false, err.message) }) } catch (err) { - sendMessage(USER_API_RENDERER_EVENT_NAME.response, false, { requestKey }, err.message) + sendMessage(USER_API_RENDERER_EVENT_NAME.response, { requestKey }, false, err.message) } } @@ -80,7 +82,7 @@ const handleRequest = (context, { requestKey, data }) => { */ const handleInit = (context, info) => { if (!info) { - sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, 'Init failed') + sendMessage(USER_API_RENDERER_EVENT_NAME.init, null, false, 'Init failed') // sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, typeof info.message === 'string' ? info.message.substring(0, 100) : '') return } @@ -88,7 +90,7 @@ const handleInit = (context, info) => { sendMessage(USER_API_RENDERER_EVENT_NAME.openDevTools) } if (!info.status) { - sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, 'Init failed') + sendMessage(USER_API_RENDERER_EVENT_NAME.init, null, false, 'Init failed') // sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, typeof info.message === 'string' ? info.message.substring(0, 100) : '') return } @@ -109,16 +111,22 @@ const handleInit = (context, info) => { } } catch (error) { console.log(error) - sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, error.message) + sendMessage(USER_API_RENDERER_EVENT_NAME.init, null, false, error.message) return } - sendMessage(USER_API_RENDERER_EVENT_NAME.init, true, sourceInfo) + sendMessage(USER_API_RENDERER_EVENT_NAME.init, sourceInfo, true) ipcRenderer.on(USER_API_RENDERER_EVENT_NAME.request, (event, data) => { handleRequest(context, data) }) } +const handleShowUpdateAlert = (message) => { + if (!message || typeof message != 'string') return + if (message.length > 1024) message = message.substring(0, 1024) + '...' + sendMessage(USER_API_RENDERER_EVENT_NAME.showUpdateAlert, message) +} + contextBridge.exposeInMainWorld('lx', { EVENT_NAMES, request(url, { method = 'get', timeout, headers, body, form, formData }, callback) { @@ -165,12 +173,19 @@ contextBridge.exposeInMainWorld('lx', { if (!eventNames.includes(eventName)) return reject(new Error('The event is not supported: ' + eventName)) switch (eventName) { case EVENT_NAMES.inited: - if (isInitedApi) return + if (isInitedApi) return reject(new Error('Script is inited')) isInitedApi = true handleInit(this, data) + resolve() + break + case EVENT_NAMES.updateAlert: + if (isShowedUpdateAlert) return reject(new Error('The update alert can only be called once')) + isShowedUpdateAlert = true + handleShowUpdateAlert(data) + resolve() break default: - resolve(new Error('Unknown event name: ' + eventName)) + reject(new Error('Unknown event name: ' + eventName)) } }) }, @@ -180,7 +195,9 @@ contextBridge.exposeInMainWorld('lx', { case EVENT_NAMES.request: events.request = handler break + default: return Promise.reject(new Error('The event is not supported: ' + eventName)) } + return Promise.resolve() }, utils: { crypto: { @@ -208,7 +225,7 @@ contextBridge.exposeInMainWorld('lx', { }, }, }, - version: '1.1.0', + version: '1.2.0', // removeEvent(eventName, handler) { // if (!eventNames.includes(eventName)) return Promise.reject(new Error('The event is not supported: ' + eventName)) // let handlers diff --git a/src/main/modules/userApi/rendererEvent/name.js b/src/main/modules/userApi/rendererEvent/name.js index c95c019e..d3040c7f 100644 --- a/src/main/modules/userApi/rendererEvent/name.js +++ b/src/main/modules/userApi/rendererEvent/name.js @@ -3,6 +3,7 @@ const names = { request: '', response: '', openDevTools: '', + showUpdateAlert: '', } diff --git a/src/main/modules/userApi/rendererEvent/rendererEvent.js b/src/main/modules/userApi/rendererEvent/rendererEvent.js index 076a80bb..5a7ab10b 100644 --- a/src/main/modules/userApi/rendererEvent/rendererEvent.js +++ b/src/main/modules/userApi/rendererEvent/rendererEvent.js @@ -37,9 +37,18 @@ const handleOpenDevTools = () => { }) } } +const handleShowUpdateAlert = (event, { data }) => { + if (!userApi.allowShowUpdateAlert) return + global.lx_event.userApi.showUpdateAlert({ + name: userApi.name, + description: userApi.description, + message: data, + }) +} mainOn(USER_API_RENDERER_EVENT_NAME.init, handleInit) mainOn(USER_API_RENDERER_EVENT_NAME.response, handleResponse) mainOn(USER_API_RENDERER_EVENT_NAME.openDevTools, handleOpenDevTools) +mainOn(USER_API_RENDERER_EVENT_NAME.showUpdateAlert, handleShowUpdateAlert) exports.loadApi = async apiId => { if (!apiId) return global.lx_event.userApi.status(status = { status: false, message: 'api id is null' }) @@ -82,3 +91,8 @@ exports.request = ({ requestKey, data }) => new Promise((resolve, reject) => { }) exports.getStatus = () => status + +exports.setAllowShowUpdateAlert = (id, enable) => { + if (!userApi || userApi.id != id) return + userApi.allowShowUpdateAlert = enable +} diff --git a/src/main/modules/userApi/utils/index.js b/src/main/modules/userApi/utils/index.js index 27aa183e..7d8de509 100644 --- a/src/main/modules/userApi/utils/index.js +++ b/src/main/modules/userApi/utils/index.js @@ -11,6 +11,9 @@ exports.getUserApis = () => { userApis = defaultUserApis electronStore_userApi.set('userApis', userApis) } + for (const api of userApis) { + if (api.allowShowUpdateAlert == null) api.allowShowUpdateAlert = false + } return userApis } @@ -27,6 +30,7 @@ exports.importApi = script => { name, description, script, + allowShowUpdateAlert: true, } userApis.push(apiInfo) getStore('userApi').set('userApis', userApis) @@ -42,3 +46,10 @@ exports.removeApi = ids => { } getStore('userApi').set('userApis', userApis) } + +exports.setAllowShowUpdateAlert = (id, enable) => { + const targetApi = userApis.find(api => api.id == id) + if (!targetApi) return + targetApi.allowShowUpdateAlert = enable + getStore('userApi').set('userApis', userApis) +} diff --git a/src/main/rendererEvents/userApi.js b/src/main/rendererEvents/userApi.js index 0f1599b6..559386c0 100644 --- a/src/main/rendererEvents/userApi.js +++ b/src/main/rendererEvents/userApi.js @@ -1,11 +1,15 @@ const { mainSend, mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('@common/ipc') -const { getApiList, importApi, removeApi, setApi, getStatus, request, cancelRequest, eventNames } = require('../modules/userApi') +const { getApiList, importApi, removeApi, setApi, getStatus, request, cancelRequest, eventNames, setAllowShowUpdateAlert } = require('../modules/userApi') const handleStatusChange = status => { mainSend(global.modules.mainWindow, ipcMainWindowNames.user_api_status, status) } +const handleShowUpdateAlert = info => { + mainSend(global.modules.mainWindow, ipcMainWindowNames.user_api_show_update_alert, info) +} global.lx_event.userApi.on(eventNames.status, handleStatusChange) +global.lx_event.userApi.on(eventNames.showUpdateAlert, handleShowUpdateAlert) mainHandle(ipcMainWindowNames.import_user_api, async(event, script) => { return importApi(script) @@ -27,6 +31,10 @@ mainHandle(ipcMainWindowNames.get_user_api_status, event => { return getStatus() }) +mainHandle(ipcMainWindowNames.user_api_set_allow_update_alert, (event, { id, enable }) => { + return setAllowShowUpdateAlert(id, enable) +}) + mainHandle(ipcMainWindowNames.request_user_api, (event, musicInfo) => { return request(musicInfo) }) diff --git a/src/renderer/components/base/Checkbox.vue b/src/renderer/components/base/Checkbox.vue index dfbe0af4..b80cc807 100644 --- a/src/renderer/components/base/Checkbox.vue +++ b/src/renderer/components/base/Checkbox.vue @@ -41,6 +41,7 @@ export default { default: false, }, }, + emits: ['update:modelValue', 'change'], data() { return { bool: false, diff --git a/src/renderer/core/useApp/useInitUserApi.js b/src/renderer/core/useApp/useInitUserApi.js index 550c5f1b..020f8ca3 100644 --- a/src/renderer/core/useApp/useInitUserApi.js +++ b/src/renderer/core/useApp/useInitUserApi.js @@ -1,18 +1,21 @@ -import { onBeforeUnmount } from '@renderer/utils/vueTools' -import { onUserApiStatus, setUserApi, getUserApiList, userApiRequest, userApiRequestCancel } from '@renderer/utils/tools' +import { onBeforeUnmount, useI18n } from '@renderer/utils/vueTools' +import { onUserApiStatus, setUserApi, getUserApiList, userApiRequest, userApiRequestCancel, onShowUserApiUpdateAlert } from '@renderer/utils/tools' import apiSourceInfo from '@renderer/utils/music/api-source-info' import music from '@renderer/utils/music' import { apiSource, qualityList, userApi } from '@renderer/core/share' +import { dialog } from '@renderer/plugins/Dialog' export default ({ setting }) => { + const { t } = useI18n() + if (/^user_api/.test(setting.value.apiSource)) { setUserApi(setting.value.apiSource) } else { qualityList.value = music.supportQuality[setting.value.apiSource] } - const rUserApiStatus = onUserApiStatus(({ status, message, apiInfo }) => { + const rUserApiStatus = onUserApiStatus((event, { status, message, apiInfo }) => { userApi.status = status userApi.message = message @@ -66,8 +69,16 @@ export default ({ setting }) => { } }) + const rUserApiShowUpdateAlert = onShowUserApiUpdateAlert((event, { name, message }) => { + dialog({ + message: `${t('user_api__update_alert', { name })}\n${message}`, + confirmButtonText: t('ok'), + }) + }) + onBeforeUnmount(() => { rUserApiStatus() + rUserApiShowUpdateAlert() }) return () => { diff --git a/src/renderer/utils/tools.js b/src/renderer/utils/tools.js index 39902415..218e6e2d 100644 --- a/src/renderer/utils/tools.js +++ b/src/renderer/utils/tools.js @@ -13,6 +13,17 @@ export const setUserApi = source => { }) } +export const onShowUserApiUpdateAlert = callback => { + rendererOn(NAMES.mainWindow.user_api_show_update_alert, callback) + return () => { + rendererOff(callback) + } +} + +export const setAllowShowUserApiUpdateAlert = (id, enable) => { + return rendererInvoke(NAMES.mainWindow.user_api_set_allow_update_alert, { id, enable }) +} + export const saveMyList = data => { rendererSend(NAMES.mainWindow.save_playlist, { type: 'myList', @@ -105,7 +116,7 @@ export const getSearchHistoryList = () => { } export const onUserApiStatus = callback => { - rendererOn(NAMES.mainWindow.user_api_status, (event, { status, message, apiInfo }) => callback({ status, message, apiInfo })) + rendererOn(NAMES.mainWindow.user_api_status, callback) return () => { rendererOff(callback) } @@ -133,7 +144,7 @@ export const setDesktopLyricInfo = (type, data, info) => { }) } export const onGetDesktopLyricInfo = callback => { - rendererOn(NAMES.mainWindow.get_lyric_info, (event, info) => callback(event, info)) + rendererOn(NAMES.mainWindow.get_lyric_info, callback) return () => { rendererOff(callback) } diff --git a/src/renderer/views/setting/components/UserApiModal.vue b/src/renderer/views/setting/components/UserApiModal.vue index a8862999..64fa1649 100644 --- a/src/renderer/views/setting/components/UserApiModal.vue +++ b/src/renderer/views/setting/components/UserApiModal.vue @@ -7,6 +7,8 @@ material-modal(:show="modelValue" bg-close @close="handleClose" teleport="#view" div(:class="$style.listLeft") h3 {{api.name}} p {{api.description}} + div + base-checkbox(:class="$style.checkbox" :id="`user_api_${api.id}`" v-model="api.allowShowUpdateAlert" @change="handleChangeAllowUpdateAlert(api, $event)" :label="$t('user_api__allow_show_update_alert')") base-btn(:class="$style.listBtn" outline :tips="$t('user_api__btn_remove')" @click.stop="handleRemove(index)") svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 212.982 212.982' space='preserve' v-once) use(xlink:href='#icon-delete') @@ -29,6 +31,7 @@ import { promises as fsPromises } from 'fs' import { selectDir, openUrl } from '@renderer/utils' import apiSourceInfo from '@renderer/utils/music/api-source-info' import { userApi, apiSource } from '@renderer/core/share' +import { setAllowShowUserApiUpdateAlert } from '@renderer/utils/tools' export default { props: { @@ -84,6 +87,9 @@ export default { handleOpenUrl(url) { openUrl(url) }, + handleChangeAllowUpdateAlert(api, enable) { + setAllowShowUserApiUpdateAlert(api.id, enable) + }, }, } @@ -114,6 +120,12 @@ export default { color: @color-theme; } +.checkbox { + margin-top: 3px; + font-size: 14px; + opacity: .86; +} + .content { flex: auto; min-height: 100px;