From baf69e397a0c55cb778254bd69b7f3d85c834f16 Mon Sep 17 00:00:00 2001 From: lyswhut Date: Mon, 4 Sep 2023 14:13:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E2=80=9C=E4=B8=8D=E5=96=9C?= =?UTF-8?q?=E6=AC=A2=E6=AD=8C=E6=9B=B2=E2=80=9D=E5=8A=9F=E8=83=BD&?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E2=80=9C=E6=94=B6=E8=97=8F=E6=AD=8C=E6=9B=B2?= =?UTF-8?q?=E2=80=9D=E3=80=81=E2=80=9C=E5=8F=96=E6=B6=88=E6=94=B6=E8=97=8F?= =?UTF-8?q?=E2=80=9D=E3=80=81=E2=80=9C=E4=B8=8D=E5=96=9C=E6=AC=A2=E8=AF=A5?= =?UTF-8?q?=E6=AD=8C=E6=9B=B2=E2=80=9D=E5=BF=AB=E6=8D=B7=E9=94=AE=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- publish/changeLog.md | 2 + src/common/constants.ts | 4 + src/common/hotKey.ts | 15 +++ src/common/ipcNames.ts | 4 + src/common/types/dislike_list.d.ts | 35 ++++++ src/lang/en-us.json | 10 ++ src/lang/zh-cn.json | 10 ++ src/lang/zh-tw.json | 10 ++ .../modules/winMain/rendererEvent/music.ts | 17 +++ src/main/types/common.d.ts | 1 + src/main/types/db_service.d.ts | 6 + src/main/worker/dbService/db.ts | 4 +- src/main/worker/dbService/index.ts | 5 +- src/main/worker/dbService/migrate.ts | 46 ++++--- .../modules/dislike_list/dbHelper.ts | 76 ++++++++++++ .../dbService/modules/dislike_list/index.ts | 100 ++++++++++++++++ .../modules/dislike_list/statements.ts | 62 ++++++++++ src/main/worker/dbService/modules/index.ts | 1 + src/main/worker/dbService/tables.ts | 7 ++ src/renderer/components/material/Modal.vue | 5 + .../components/material/OnlineList/index.vue | 2 + .../components/material/OnlineList/useMenu.js | 13 ++ .../material/OnlineList/useMusicActions.js | 13 ++ src/renderer/core/dislikeList.ts | 48 ++++++++ src/renderer/core/player/action.ts | 2 + src/renderer/core/player/utils.ts | 6 +- src/renderer/core/useApp/useDataInit.ts | 2 + .../core/useApp/usePlayer/usePlayer.ts | 27 +++++ src/renderer/store/dislikeList/action.ts | 83 +++++++++++++ src/renderer/store/dislikeList/index.ts | 3 + src/renderer/store/dislikeList/state.ts | 11 ++ src/renderer/store/player/action.ts | 2 +- src/renderer/types/common.d.ts | 1 + src/renderer/utils/ipc.ts | 38 ++++++ src/renderer/views/List/MusicList/index.vue | 2 + src/renderer/views/List/MusicList/useMenu.js | 13 ++ .../views/List/MusicList/useMusicActions.js | 12 ++ .../Setting/components/DislikeListModal.vue | 112 ++++++++++++++++++ .../views/Setting/components/SettingOther.vue | 24 ++++ src/renderer/worker/main/list.ts | 53 ++++++++- 40 files changed, 861 insertions(+), 26 deletions(-) create mode 100644 src/common/types/dislike_list.d.ts create mode 100644 src/main/worker/dbService/modules/dislike_list/dbHelper.ts create mode 100644 src/main/worker/dbService/modules/dislike_list/index.ts create mode 100644 src/main/worker/dbService/modules/dislike_list/statements.ts create mode 100644 src/renderer/core/dislikeList.ts create mode 100644 src/renderer/store/dislikeList/action.ts create mode 100644 src/renderer/store/dislikeList/index.ts create mode 100644 src/renderer/store/dislikeList/state.ts create mode 100644 src/renderer/views/Setting/components/DislikeListModal.vue diff --git a/publish/changeLog.md b/publish/changeLog.md index 5acd345d..da0b64de 100644 --- a/publish/changeLog.md +++ b/publish/changeLog.md @@ -9,6 +9,8 @@ - 新增我的列表名右键菜单-排序歌曲-随机乱序功能,使用它可以对选中列表内歌曲进行随机重排(#1440) - 新增数据同步服务端模式已认证设备列表管理,该功能位置:设置-数据同步-服务端模式-已认证设备列表 +- 新增“不喜欢歌曲”功能,可以在我的列表或者在线列表内歌曲的右击菜单使用,还可以去“设置-其他”手动编辑不喜欢规则,注:“上一曲”、“下一曲”功能将跳过符合“不喜欢歌曲”规则的歌曲,但你仍可以手动播放这些歌曲 +- 新增软件内快捷键“不喜欢该歌曲”设置,全局快捷键“收藏歌曲”、“取消收藏”、“不喜欢该歌曲”设置 ### 优化 diff --git a/src/common/constants.ts b/src/common/constants.ts index c16172e1..5702b50a 100644 --- a/src/common/constants.ts +++ b/src/common/constants.ts @@ -1,5 +1,9 @@ export const URL_SCHEME_RXP = /^lxmusic:\/\// +export const SPLIT_CHAR = { + DISLIKE_NAME: '@', + DISLIKE_NAME_ALIAS: '#', +} as const export const STORE_NAMES = { APP_SETTINGS: 'config_v2', diff --git a/src/common/hotKey.ts b/src/common/hotKey.ts index ab72719c..3756e9a2 100644 --- a/src/common/hotKey.ts +++ b/src/common/hotKey.ts @@ -66,6 +66,21 @@ const hotKey = { action: 'volume_mute', type: '', }, + music_love: { + name: 'music_love', + action: 'music_love', + type: '', + }, + music_unlove: { + name: 'music_unlove', + action: 'music_unlove', + type: '', + }, + music_dislike: { + name: 'music_dislike', + action: 'music_dislike', + type: '', + }, }, desktop_lyric: { toggle_visible: { diff --git a/src/common/ipcNames.ts b/src/common/ipcNames.ts index a40a9819..3f0a3b2d 100644 --- a/src/common/ipcNames.ts +++ b/src/common/ipcNames.ts @@ -126,6 +126,10 @@ const modules = { clear_music_url: 'clear_music_url', get_music_url_count: 'get_music_url_count', + get_dislike_music_infos: 'get_dislike_music_infos', + add_dislike_music_infos: 'add_dislike_music_infos', + overwrite_dislike_music_infos: 'overwrite_dislike_music_infos', + sync_action: 'sync_action', sync_get_server_devices: 'sync_get_server_devices', sync_remove_server_device: 'sync_remove_server_device', diff --git a/src/common/types/dislike_list.d.ts b/src/common/types/dislike_list.d.ts new file mode 100644 index 00000000..50361daa --- /dev/null +++ b/src/common/types/dislike_list.d.ts @@ -0,0 +1,35 @@ + + +declare namespace LX { + namespace Dislike { + // interface ListItemMusicText { + // id?: string + // // type: 'music' + // name: string | null + // singer: string | null + // } + // interface ListItemMusic { + // id?: number + // type: 'musicId' + // musicId: string + // meta: LX.Music.MusicInfo + // } + // type ListItem = ListItemMusicText + // type ListItem = string + // type ListItem = ListItemMusic | ListItemMusicText + + interface DislikeMusicInfo { + name: string + singer: string + } + + interface DislikeInfo { + // musicIds: Set + names: Set + musicNames: Set + singerNames: Set + // list: LX.Dislike.ListItem[] + rules: string + } + } +} diff --git a/src/lang/en-us.json b/src/lang/en-us.json index 7ea55b1e..15c836b2 100644 --- a/src/lang/en-us.json +++ b/src/lang/en-us.json @@ -81,6 +81,7 @@ "list__add_to": "Add to ...", "list__collect": "Collect", "list__copy_name": "Copy name", + "list__dislike": "Dislike", "list__download": "Download", "list__export_part_desc": "Choose where to save the list file", "list__file": "Locate the file", @@ -382,6 +383,9 @@ "setting__desktop_lyric_shadow_color": "Shadow color", "setting__desktop_lyric_show_taskbar": "Display lyrics progress on the taskbar (this setting is used as a workaround when the screen recording software cannot capture the lyrics window)", "setting__desktop_lyric_unplay_color": "Color not playing", + "setting__dislike_list_save_btn": "Save", + "setting__dislike_list_tips": "1. If there is a \"@\" symbol in the song or singer's name, you need to replace it with \"#\"\n2. Specify a song of a singer: @\n3. Specify a song: \n4. Specify a certain singer:@", + "setting__dislike_list_title": "List of Disliked Song Rules", "setting__download": "Download", "setting__download_data_embed": "Whether to embed the following content in the audio file", "setting__download_embed_lyric": "Embedding lyric", @@ -426,6 +430,9 @@ "setting__hot_key_player_toggle_play": "Play/Pause Control", "setting__hot_key_player_volume_down": "Reduce Volume", "setting__hot_key_player_volume_mute": "Mute Switch", + "setting__hot_key_player_music_love": "Favorites Song", + "setting__hot_key_player_music_unlove": "Cancel collection", + "setting__hot_key_player_music_dislike": "Dislike the song", "setting__hot_key_player_volume_up": "Increase Volume", "setting__hot_key_tip_input": "Please enter a new key", "setting__hot_key_unset_input": "Not Set", @@ -449,6 +456,9 @@ "setting__odc_clear_search_input": "Clear the search box when you are not searching", "setting__odc_clear_search_list": "Clear the search list when you are not searching", "setting__other": "Extras", + "setting__other_dislike_list": "dislike song rule", + "setting__other_dislike_list_label": "Number of rules:", + "setting__other_dislike_list_show_btn": "Edit dislike song rules", "setting__other_listdata": "List Data Cleanup", "setting__other_listdata_clear_btn": "Clear my list data", "setting__other_listdata_clear_tip_confirm": "This will clear all lists you have created and all songs in your favourites, do you really want to continue?", diff --git a/src/lang/zh-cn.json b/src/lang/zh-cn.json index 1ff58d98..3c773b7b 100644 --- a/src/lang/zh-cn.json +++ b/src/lang/zh-cn.json @@ -81,6 +81,7 @@ "list__add_to": "添加到...", "list__collect": "收藏", "list__copy_name": "复制歌曲名", + "list__dislike": "不喜欢", "list__download": "下载", "list__export_part_desc": "选择列表文件保存位置", "list__file": "定位文件", @@ -381,6 +382,9 @@ "setting__desktop_lyric_shadow_color": "阴影颜色", "setting__desktop_lyric_show_taskbar": "在任务栏显示歌词进程(此设置用于在录屏软件无法捕获歌词窗口时的变通解决方法)", "setting__desktop_lyric_unplay_color": "未播放颜色", + "setting__dislike_list_save_btn": "保存", + "setting__dislike_list_tips": "1. 每条一行,若歌曲或者歌手名字中存在“@”符号,需要将其替换成“#”\n2. 指定某歌手的某首歌:<歌曲名>@<歌手名>\n3. 指定某首歌:<歌曲名>\n4. 指定某歌手:@<歌手名>", + "setting__dislike_list_title": "不喜欢的歌曲规则列表", "setting__download": "下载设置", "setting__download_data_embed": "是否将以下内容嵌入到音频文件中", "setting__download_embed_lyric": "歌词嵌入", @@ -425,6 +429,9 @@ "setting__hot_key_player_toggle_play": "播放/暂停控制", "setting__hot_key_player_volume_down": "减少音量", "setting__hot_key_player_volume_mute": "静音切换", + "setting__hot_key_player_music_love": "收藏歌曲", + "setting__hot_key_player_music_unlove": "取消收藏", + "setting__hot_key_player_music_dislike": "不喜欢该歌曲", "setting__hot_key_player_volume_up": "增加音量", "setting__hot_key_tip_input": "请输入新的按键", "setting__hot_key_unset_input": "未设置", @@ -448,6 +455,9 @@ "setting__odc_clear_search_input": "离开搜索界面时清空搜索框", "setting__odc_clear_search_list": "离开搜索界面时清空搜索列表", "setting__other": "其他", + "setting__other_dislike_list": "不喜欢的歌曲规则", + "setting__other_dislike_list_label": "规则数量:", + "setting__other_dislike_list_show_btn": "编辑不喜欢歌曲规则", "setting__other_listdata": "列表数据清理", "setting__other_listdata_clear_btn": "清空我的列表数据", "setting__other_listdata_clear_tip_confirm": "这将清理你创建的 所有列表 及收藏的 所有歌曲,是否真的要继续?", diff --git a/src/lang/zh-tw.json b/src/lang/zh-tw.json index 8e6da925..02c11f97 100644 --- a/src/lang/zh-tw.json +++ b/src/lang/zh-tw.json @@ -81,6 +81,7 @@ "list__add_to": "添加到...", "list__collect": "收藏", "list__copy_name": "複製歌曲名", + "list__dislike": "不喜歡", "list__download": "下載", "list__export_part_desc": "選擇列表文件保存位置", "list__file": "定位文件", @@ -382,6 +383,9 @@ "setting__desktop_lyric_shadow_color": "陰影顏色", "setting__desktop_lyric_show_taskbar": "在任務欄顯示歌詞進程(此設置用於在錄屏軟件無法捕獲歌詞窗口時的變通解決方法)", "setting__desktop_lyric_unplay_color": "未播放顏色", + "setting__dislike_list_save_btn": "保存", + "setting__dislike_list_tips": "1. 每條一行,若歌曲或者歌手名字中存在“@”符號,需要將其替換成“#”\n2. 指定某歌手的某首歌:<歌曲名>@<歌手名>\n3. 指定某首歌:<歌曲名>\n4. 指定某歌手:@<歌手名>", + "setting__dislike_list_title": "不喜歡的歌曲規則列表", "setting__download": "下載設置", "setting__download_data_embed": "是否將以下內容嵌入到音頻文件中", "setting__download_embed_lyric": "歌詞嵌入", @@ -426,6 +430,9 @@ "setting__hot_key_player_toggle_play": "播放/暫停控制​​", "setting__hot_key_player_volume_down": "減少音量", "setting__hot_key_player_volume_mute": "靜音切換", + "setting__hot_key_player_music_love": "收藏歌曲", + "setting__hot_key_player_music_unlove": "取消收藏", + "setting__hot_key_player_music_dislike": "不喜歡該歌曲", "setting__hot_key_player_volume_up": "增加音量", "setting__hot_key_tip_input": "請輸入新的按鍵", "setting__hot_key_unset_input": "未設置", @@ -449,6 +456,9 @@ "setting__odc_clear_search_input": "離開搜索界面時清空搜索框", "setting__odc_clear_search_list": "離開搜索界面時清空搜索列表", "setting__other": "其他", + "setting__other_dislike_list": "不喜歡的歌曲規則", + "setting__other_dislike_list_label": "規則數量:", + "setting__other_dislike_list_show_btn": "編輯不喜歡歌曲規則", "setting__other_listdata": "列表數據清理", "setting__other_listdata_clear_btn": "清空我的列表數據", "setting__other_listdata_clear_tip_confirm": "這將清理你創建的 所有列表 及收藏的 所有歌曲,是否真的要繼續?", diff --git a/src/main/modules/winMain/rendererEvent/music.ts b/src/main/modules/winMain/rendererEvent/music.ts index 92bef88c..ee8dc611 100644 --- a/src/main/modules/winMain/rendererEvent/music.ts +++ b/src/main/modules/winMain/rendererEvent/music.ts @@ -70,6 +70,23 @@ export default () => { return global.lx.worker.dbService.musicInfoOtherSourceCount() }) + // =========================不喜欢的歌曲========================= + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.get_dislike_music_infos, async() => { + return global.lx.worker.dbService.getDislikeListInfo() + }) + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.add_dislike_music_infos, async({ params: infos }) => { + await global.lx.worker.dbService.dislikeInfoAdd(infos) + }) + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.overwrite_dislike_music_infos, async({ params: rules }) => { + await global.lx.worker.dbService.dislikeInfoOverwrite(rules) + }) + // mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.remove_dislike_music_infos, async({ params: ids }) => { + // await global.lx.worker.dbService.dislikeInfoRemove(ids) + // }) + // mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.clear_dislike_music_infos, async() => { + // await global.lx.worker.dbService.dislikeInfoClear() + // }) + // =========================我的列表========================= // mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.get_playlist, async({ params: isIgnoredError = false }) => { diff --git a/src/main/types/common.d.ts b/src/main/types/common.d.ts index bf7e8ca8..a21fa28d 100644 --- a/src/main/types/common.d.ts +++ b/src/main/types/common.d.ts @@ -12,3 +12,4 @@ import '@common/types/desktop_lyric' import '@common/types/theme' import '@common/types/ipc_main' import '@common/types/sound_effect' +import '@common/types/dislike_list' diff --git a/src/main/types/db_service.d.ts b/src/main/types/db_service.d.ts index 927efe31..c8e64a76 100644 --- a/src/main/types/db_service.d.ts +++ b/src/main/types/db_service.d.ts @@ -74,6 +74,12 @@ declare namespace LX { position: number } + interface DislikeInfo { + // type: 'music' + content: string + // meta: string | null + } + interface MusicInfoOtherSource extends Omit { source_id: string } diff --git a/src/main/worker/dbService/db.ts b/src/main/worker/dbService/db.ts index 62c3bbc9..a440e224 100644 --- a/src/main/worker/dbService/db.ts +++ b/src/main/worker/dbService/db.ts @@ -2,7 +2,7 @@ import Database from 'better-sqlite3' import path from 'path' import tables from './tables' import verifyDB from './verifyDB' -// import migrateData from './migrate' +import migrateData from './migrate' let db: Database.Database @@ -37,7 +37,7 @@ export const init = (lxDataPath: string): boolean | null => { dbFileExists = false } - // if (dbFileExists) migrateData(db) + if (dbFileExists) migrateData(db) // https://www.sqlite.org/pragma.html#pragma_optimize if (dbFileExists) db.exec('PRAGMA optimize;') diff --git a/src/main/worker/dbService/index.ts b/src/main/worker/dbService/index.ts index 04d6a753..9d58589a 100644 --- a/src/main/worker/dbService/index.ts +++ b/src/main/worker/dbService/index.ts @@ -1,13 +1,13 @@ import { init } from './db' import { exposeWorker } from '../utils/worker' -import { list, lyric, music_url, music_other_source, download } from './modules/index' +import { list, lyric, music_url, music_other_source, download, dislike_list } from './modules/index' const common = { init, } -exposeWorker(Object.assign(common, list, lyric, music_url, music_other_source, download)) +exposeWorker(Object.assign(common, list, lyric, music_url, music_other_source, download, dislike_list)) export type workerDBSeriveTypes = typeof common & typeof list @@ -15,3 +15,4 @@ export type workerDBSeriveTypes = typeof common & typeof music_url & typeof music_other_source & typeof download + & typeof dislike_list diff --git a/src/main/worker/dbService/migrate.ts b/src/main/worker/dbService/migrate.ts index df676b77..fd40a26f 100644 --- a/src/main/worker/dbService/migrate.ts +++ b/src/main/worker/dbService/migrate.ts @@ -1,24 +1,36 @@ import type Database from 'better-sqlite3' +// const migrateV1 = (db: Database.Database) => { +// const sql = ` +// DROP TABLE "main"."download_list"; + +// CREATE TABLE "download_list" ( +// "id" TEXT NOT NULL, +// "isComplate" INTEGER NOT NULL, +// "status" TEXT NOT NULL, +// "statusText" TEXT NOT NULL, +// "progress_downloaded" INTEGER NOT NULL, +// "progress_total" INTEGER NOT NULL, +// "url" TEXT, +// "quality" TEXT NOT NULL, +// "ext" TEXT NOT NULL, +// "fileName" TEXT NOT NULL, +// "filePath" TEXT NOT NULL, +// "musicInfo" TEXT NOT NULL, +// "position" INTEGER NOT NULL, +// PRIMARY KEY("id") +// ); +// ` +// db.exec(sql) +// db.prepare('UPDATE "main"."db_info" SET "field_value"=@value WHERE "field_name"=@name').run({ name: 'version', value: '2' }) +// } + const migrateV1 = (db: Database.Database) => { const sql = ` - DROP TABLE "main"."download_list"; - - CREATE TABLE "download_list" ( - "id" TEXT NOT NULL, - "isComplate" INTEGER NOT NULL, - "status" TEXT NOT NULL, - "statusText" TEXT NOT NULL, - "progress_downloaded" INTEGER NOT NULL, - "progress_total" INTEGER NOT NULL, - "url" TEXT, - "quality" TEXT NOT NULL, - "ext" TEXT NOT NULL, - "fileName" TEXT NOT NULL, - "filePath" TEXT NOT NULL, - "musicInfo" TEXT NOT NULL, - "position" INTEGER NOT NULL, - PRIMARY KEY("id") + CREATE TABLE "dislike_list" ( + "type" TEXT NOT NULL, + "content" TEXT NOT NULL, + "meta" TEXT ); ` db.exec(sql) diff --git a/src/main/worker/dbService/modules/dislike_list/dbHelper.ts b/src/main/worker/dbService/modules/dislike_list/dbHelper.ts new file mode 100644 index 00000000..3d5d2c05 --- /dev/null +++ b/src/main/worker/dbService/modules/dislike_list/dbHelper.ts @@ -0,0 +1,76 @@ +// import type Database from 'better-sqlite3' +import { getDB } from '../../db' +import { + createQueryStatement, + createInsertStatement, + // createDeleteStatement, + // createUpdateStatement, + createClearStatement, +} from './statements' + +/** + * 查询不喜欢歌曲列表 + */ +export const queryDislikeList = () => { + const queryStatement = createQueryStatement() + return queryStatement.all() as LX.DBService.DislikeInfo[] +} + +/** + * 批量插入不喜欢歌曲并刷新顺序 + * @param infos 列表 + */ +export const inertDislikeList = async(infos: LX.DBService.DislikeInfo[]) => { + const db = getDB() + const insertStatement = createInsertStatement() + db.transaction((infos: LX.DBService.DislikeInfo[]) => { + for (const info of infos) insertStatement.run(info) + })(infos) +} + +/** + * 覆盖并批量插入不喜欢歌曲并刷新顺序 + * @param infos 列表 + */ +export const overwirteDislikeList = async(infos: LX.DBService.DislikeInfo[]) => { + const db = getDB() + const clearStatement = createClearStatement() + const insertStatement = createInsertStatement() + db.transaction((infos: LX.DBService.DislikeInfo[]) => { + clearStatement.run() + for (const info of infos) insertStatement.run(info) + })(infos) +} + +// /** +// * 批量删除不喜欢歌曲 +// * @param ids 列表 +// */ +// export const deleteDislikeList = (ids: string[]) => { +// const db = getDB() +// const deleteStatement = createDeleteStatement() +// db.transaction((ids: string[]) => { +// for (const id of ids) deleteStatement.run(BigInt(id)) +// })(ids) +// } + +// /** +// * 批量更新不喜欢歌曲 +// * @param urlInfo 列表 +// */ +// export const updateDislikeList = async(infos: LX.DBService.DislikeInfo[]) => { +// const db = getDB() +// const updateStatement = createUpdateStatement() +// db.transaction((infos: LX.DBService.DislikeInfo[]) => { +// for (const info of infos) updateStatement.run(info) +// })(infos) +// } + +// /** +// * 清空不喜欢歌曲列表 +// */ +// export const clearDislikeList = () => { +// const clearStatement = createClearStatement() +// clearStatement.run() +// } + diff --git a/src/main/worker/dbService/modules/dislike_list/index.ts b/src/main/worker/dbService/modules/dislike_list/index.ts new file mode 100644 index 00000000..121ffb7e --- /dev/null +++ b/src/main/worker/dbService/modules/dislike_list/index.ts @@ -0,0 +1,100 @@ +import { SPLIT_CHAR } from '@common/constants' +import { + queryDislikeList, + inertDislikeList, + overwirteDislikeList, + // updateDislikeList, + // deleteDislikeList, + // clearDislikeList, +} from './dbHelper' + +// let dislikeInfo: LX.Dislike.DislikeInfo + +const toDBDislikeInfo = (musicInfos: string[]): LX.DBService.DislikeInfo[] => { + const list: LX.DBService.DislikeInfo[] = [] + for (const item of musicInfos) { + if (!item.trim()) continue + list.push({ + content: item, + }) + } + return list +} + +const initDislikeList = () => { + const dislikeInfo: LX.Dislike.DislikeInfo = { + // musicIds: new Set(), + names: new Set(), + singerNames: new Set(), + musicNames: new Set(), + rules: '', + } + const list: string[] = [] + for (const item of queryDislikeList()) { + if (!item) continue + let [name, singer] = item.content.split(SPLIT_CHAR.DISLIKE_NAME) + if (name) { + name = name.replaceAll(SPLIT_CHAR.DISLIKE_NAME, SPLIT_CHAR.DISLIKE_NAME_ALIAS).toLocaleLowerCase().trim() + if (singer) { + singer = singer.replaceAll(SPLIT_CHAR.DISLIKE_NAME, SPLIT_CHAR.DISLIKE_NAME_ALIAS).toLocaleLowerCase().trim() + const rule = `${name}${SPLIT_CHAR.DISLIKE_NAME}${singer}` + dislikeInfo.names.add(rule) + list.push(rule) + } else { + dislikeInfo.musicNames.add(name) + list.push(name) + } + } else if (singer) { + singer = singer.replaceAll(SPLIT_CHAR.DISLIKE_NAME, SPLIT_CHAR.DISLIKE_NAME_ALIAS).toLocaleLowerCase().trim() + dislikeInfo.singerNames.add(singer) + list.push(`${SPLIT_CHAR.DISLIKE_NAME}${singer}`) + } + } + + dislikeInfo.rules = list.join('\n') + '\n' + + return dislikeInfo +} + +/** + * 获取不喜欢列表信息 + * @returns 不喜欢列表信息 + */ +export const getDislikeListInfo = (): LX.Dislike.DislikeInfo => { + // if (!dislikeInfo) initDislikeList() + return initDislikeList() +} + + +/** + * 添加信息 + * @param lists 列表信息 + */ +export const dislikeInfoAdd = async(lists: LX.Dislike.DislikeMusicInfo[]) => { + await inertDislikeList(lists.map(info => ({ content: `${info.name}${SPLIT_CHAR.DISLIKE_NAME}${info.singer}` }))) +} + +/** + * 覆盖列表信息 + * @param rules 规则信息 + */ +export const dislikeInfoOverwrite = async(rules: string) => { + await overwirteDislikeList(toDBDislikeInfo(rules.split('\n'))) +} + + +// /** +// * 删除不喜欢列表 +// * @param ids 歌曲id +// */ +// export const dislikeInfoRemove = (ids: string[]) => { +// deleteDislikeList(ids) +// } + +// /** +// * 清空不喜欢列表 +// */ +// export const dislikeInfoClear = () => { +// clearDislikeList() +// } + diff --git a/src/main/worker/dbService/modules/dislike_list/statements.ts b/src/main/worker/dbService/modules/dislike_list/statements.ts new file mode 100644 index 00000000..7c2924cd --- /dev/null +++ b/src/main/worker/dbService/modules/dislike_list/statements.ts @@ -0,0 +1,62 @@ +import { getDB } from '../../db' + +/** + * 创建不喜欢列表查询语句 + * @returns 查询语句 + */ +export const createQueryStatement = () => { + const db = getDB() + return db.prepare<[]>(` + SELECT "content" + FROM dislike_list + WHERE "type"='music' + `) +} + +/** + * 创建不喜欢记录插入语句 + * @returns 插入语句 + */ +export const createInsertStatement = () => { + const db = getDB() + return db.prepare<[LX.DBService.DislikeInfo]>(` + INSERT INTO "main"."dislike_list" ("type", "content") + VALUES ('music', @content)`) +} + +/** + * 创建不喜欢记录清空语句 + * @returns 清空语句 + */ +export const createClearStatement = () => { + const db = getDB() + return db.prepare<[]>(` + DELETE FROM "main"."dislike_list" + `) +} + +// /** +// * 创建不喜欢记录删除语句 +// * @returns 删除语句 +// */ +// export const createDeleteStatement = () => { +// const db = getDB() +// return db.prepare<[bigint]>(` +// DELETE FROM "main"."dislike_list" +// WHERE "id"=? +// `) +// } + +// /** +// * 创建不喜欢记录更新语句 +// * @returns 更新语句 +// */ +// export const createUpdateStatement = () => { +// const db = getDB() +// return db.prepare<[LX.DBService.DislikeInfo]>(` +// UPDATE "main"."dislike_list" +// SET "name"=@name, "singer"=@singer +// WHERE "id"=@id +// `) +// } + diff --git a/src/main/worker/dbService/modules/index.ts b/src/main/worker/dbService/modules/index.ts index 4903bb9c..7d87157d 100644 --- a/src/main/worker/dbService/modules/index.ts +++ b/src/main/worker/dbService/modules/index.ts @@ -4,3 +4,4 @@ export * as lyric from './lyric' export * as music_url from './music_url' export * as music_other_source from './music_other_source' export * as download from './download' +export * as dislike_list from './dislike_list' diff --git a/src/main/worker/dbService/tables.ts b/src/main/worker/dbService/tables.ts index 7fe4cddb..bcca5a2f 100644 --- a/src/main/worker/dbService/tables.ts +++ b/src/main/worker/dbService/tables.ts @@ -205,5 +205,12 @@ tables.set('download_list', ` PRIMARY KEY("id") ); `) +tables.set('dislike_list', ` + CREATE TABLE "dislike_list" ( + "type" TEXT NOT NULL, + "content" TEXT NOT NULL, + "meta" TEXT + ); +`) export default tables diff --git a/src/renderer/components/material/Modal.vue b/src/renderer/components/material/Modal.vue index d0d903af..d9fe0093 100644 --- a/src/renderer/components/material/Modal.vue +++ b/src/renderer/components/material/Modal.vue @@ -60,6 +60,10 @@ export default { type: String, default: 'auto', }, + height: { + type: String, + default: 'auto', + }, }, emits: ['after-enter', 'after-leave', 'close'], data() { @@ -147,6 +151,7 @@ export default { maxWidth: this.maxWidth, minWidth: this.minWidth, width: this.width, + height: this.height, maxHeight: this.maxHeight, } }, diff --git a/src/renderer/components/material/OnlineList/index.vue b/src/renderer/components/material/OnlineList/index.vue index b5f96013..bc566feb 100644 --- a/src/renderer/components/material/OnlineList/index.vue +++ b/src/renderer/components/material/OnlineList/index.vue @@ -180,6 +180,7 @@ export default { const { handleSearch, handleOpenMusicDetail, + handleDislikeMusic, } = useMusicActions({ props }) const { @@ -199,6 +200,7 @@ export default { handleSearch, handleShowMusicAddModal, handleOpenMusicDetail, + handleDislikeMusic, }) const handleListItemClick = (event, index) => { diff --git a/src/renderer/components/material/OnlineList/useMenu.js b/src/renderer/components/material/OnlineList/useMenu.js index d60d47c8..e39c1bf9 100644 --- a/src/renderer/components/material/OnlineList/useMenu.js +++ b/src/renderer/components/material/OnlineList/useMenu.js @@ -1,6 +1,7 @@ import { computed, ref, reactive, nextTick } from '@common/utils/vueTools' import musicSdk from '@renderer/utils/musicSdk' import { useI18n } from '@renderer/plugins/i18n' +import { hasDislike } from '@renderer/core/dislikeList' export default ({ props, @@ -13,6 +14,7 @@ export default ({ handleSearch, handleShowMusicAddModal, handleOpenMusicDetail, + handleDislikeMusic, }) => { const itemMenuControl = reactive({ play: true, @@ -21,6 +23,7 @@ export default ({ download: true, search: true, sourceDetail: true, + dislike: true, }) const t = useI18n() const menuLocation = reactive({ x: 0, y: 0 }) @@ -58,6 +61,11 @@ export default ({ action: 'sourceDetail', disabled: !itemMenuControl.sourceDetail, }, + { + name: t('list__dislike'), + action: 'dislike', + disabled: !itemMenuControl.dislike, + }, ] }) @@ -67,6 +75,8 @@ export default ({ // this.listMenu.itemMenuControl.playLater = itemMenuControl.download = assertApiSupport(musicInfo.source) + itemMenuControl.dislike = !hasDislike(musicInfo) + if (props.checkApiSource) { itemMenuControl.playLater = itemMenuControl.play = @@ -110,6 +120,9 @@ export default ({ break case 'sourceDetail': handleOpenMusicDetail(index) + case 'dislike': + handleDislikeMusic(index) + break } } diff --git a/src/renderer/components/material/OnlineList/useMusicActions.js b/src/renderer/components/material/OnlineList/useMusicActions.js index 243d5474..fe3f1d12 100644 --- a/src/renderer/components/material/OnlineList/useMusicActions.js +++ b/src/renderer/components/material/OnlineList/useMusicActions.js @@ -2,6 +2,9 @@ import { useRouter } from '@common/utils/vueRouter' import musicSdk from '@renderer/utils/musicSdk' import { openUrl } from '@common/utils/electron' import { toOldMusicInfo } from '@renderer/utils' +import { addDislikeInfo, hasDislike } from '@renderer/core/dislikeList' +import { playNext } from '@renderer/core/player' +import { playMusicInfo } from '@renderer/store/player/state' export default ({ props }) => { @@ -24,8 +27,18 @@ export default ({ props }) => { openUrl(url) } + const handleDislikeMusic = async(index) => { + const minfo = props.list[index] + await addDislikeInfo([{ name: minfo.name, singer: minfo.singer }]) + if (!playMusicInfo.isTempPlay && hasDislike(playMusicInfo.musicInfo)) { + playNext(true) + } + } + + return { handleSearch, handleOpenMusicDetail, + handleDislikeMusic, } } diff --git a/src/renderer/core/dislikeList.ts b/src/renderer/core/dislikeList.ts new file mode 100644 index 00000000..cf4244f4 --- /dev/null +++ b/src/renderer/core/dislikeList.ts @@ -0,0 +1,48 @@ +// import { toRaw } from '@common/utils/vueTools' +import { action } from '@renderer/store/dislikeList' +import { + getDislikeListInfo, + addDislikeInfo as addDislikeInfoRemote, + overwirteDislikeInfo as overwirteDislikeInfoRemote, + // updateDislikeInfo as updateDislikeInfoRemote, + // removeDislikeInfo as removeDislikeInfoRemote, + // clearDislikeInfo as clearDislikeInfoRemote, +} from '@renderer/utils/ipc' + + +export const initDislikeInfo = async() => { + action.initDislikeInfo(await getDislikeListInfo()) +} + +export const addDislikeInfo = async(infos: LX.Dislike.DislikeMusicInfo[]) => { + await addDislikeInfoRemote(infos) + return action.addDislikeInfo(infos) +} + +export const overwirteDislikeInfo = async(rules: string) => { + await overwirteDislikeInfoRemote(rules) + return action.overwirteDislikeInfo(rules) +} + +// export const updateDislikeInfo = async(info: LX.Dislike.ListItem) => { +// await updateDislikeInfoRemote([toRaw(info)]) +// action.updateDislikeInfo(info) +// } + +// export const removeDislikeInfo = async(ids: string[]) => { +// await removeDislikeInfoRemote(toRaw(ids)) +// action.removeDislikeInfo(ids) +// } + + +// export const clearDislikeInfo = async() => { +// await clearDislikeInfoRemote() +// action.clearDislikeInfo() +// } + + +export const hasDislike = (info: LX.Music.MusicInfo | null) => { + if (!info) return false + return action.hasDislike(info) +} + diff --git a/src/renderer/core/player/action.ts b/src/renderer/core/player/action.ts index 204679e3..7a01874c 100644 --- a/src/renderer/core/player/action.ts +++ b/src/renderer/core/player/action.ts @@ -299,6 +299,7 @@ export const playNext = async(isAutoToggle = false): Promise => { list: currentList, playedList, playerMusicInfo: currentList[playInfo.playerPlayIndex], + isNext: true, }) if (!filteredList.length) { @@ -398,6 +399,7 @@ export const playPrev = async(isAutoToggle = false): Promise => { list: currentList, playedList, playerMusicInfo: currentList[playInfo.playerPlayIndex], + isNext: false, }) if (!filteredList.length) { handleToggleStop() diff --git a/src/renderer/core/player/utils.ts b/src/renderer/core/player/utils.ts index af1e13b7..ef32a889 100644 --- a/src/renderer/core/player/utils.ts +++ b/src/renderer/core/player/utils.ts @@ -2,6 +2,7 @@ import { toRaw, markRawList } from '@common/utils/vueTools' import { qualityList } from '@renderer/store' import { clearPlayedList } from '@renderer/store/player/action' import { appSetting } from '@renderer/store/setting' +import { dislikeInfo } from '@renderer/store/dislikeList' export const getPlayType = (highQuality: boolean, musicInfo: LX.Music.MusicInfo | LX.Download.ListItem): LX.Quality | null => { if ('progress' in musicInfo || musicInfo.source == 'local') return null @@ -14,11 +15,12 @@ export const getPlayType = (highQuality: boolean, musicInfo: LX.Music.MusicInfo /** * 过滤列表中已播放的歌曲 */ -export const filterList = async({ playedList, listId, list, playerMusicInfo }: { +export const filterList = async({ playedList, listId, list, playerMusicInfo, isNext }: { playedList: LX.Player.PlayMusicInfo[] listId: string list: Array playerMusicInfo?: LX.Music.MusicInfo | LX.Download.ListItem + isNext: boolean }) => { // if (this.list.listName === null) return // console.log(isCheckFile) @@ -28,6 +30,8 @@ export const filterList = async({ playedList, listId, list, playerMusicInfo }: { playedList: toRaw(playedList), savePath: appSetting['download.savePath'], playerMusicInfo: toRaw(playerMusicInfo), + dislikeInfo: { names: toRaw(dislikeInfo.names), musicNames: toRaw(dislikeInfo.musicNames), singerNames: toRaw(dislikeInfo.singerNames) }, + isNext, }) if (!filteredList.length && playedList.length) { diff --git a/src/renderer/core/useApp/useDataInit.ts b/src/renderer/core/useApp/useDataInit.ts index aca1cdc1..34d8b9cb 100644 --- a/src/renderer/core/useApp/useDataInit.ts +++ b/src/renderer/core/useApp/useDataInit.ts @@ -9,6 +9,7 @@ import { play, playList } from '@renderer/core/player' import { onBeforeUnmount } from '@common/utils/vueTools' import { appSetting } from '@renderer/store/setting' import { playMusicInfo } from '@renderer/store/player/state' +import { initDislikeInfo } from '@renderer/core/dislikeList' const initPrevPlayInfo = async() => { const info = await getPlayInfo() @@ -48,6 +49,7 @@ export default () => { window.app_event.myListUpdate(ids) }) window.lxData.userLists = await getUserLists() // 获取用户列表 + await initDislikeInfo() // 获取不喜欢列表 await initPrevPlayInfo().catch(err => { log.error(err) }) // 初始化上次的歌曲播放信息 diff --git a/src/renderer/core/useApp/usePlayer/usePlayer.ts b/src/renderer/core/useApp/usePlayer/usePlayer.ts index 135d5050..2c531eb9 100644 --- a/src/renderer/core/useApp/usePlayer/usePlayer.ts +++ b/src/renderer/core/useApp/usePlayer/usePlayer.ts @@ -32,6 +32,9 @@ import { HOTKEY_PLAYER } from '@common/hotKey' import { playNext, pause, playPrev, togglePlay } from '@renderer/core/player' import usePlaybackRate from './usePlaybackRate' import useSoundEffect from './useSoundEffect' +import { addListMusics, removeListMusics } from '@renderer/store/list/action' +import { loveList } from '@renderer/store/list/state' +import { addDislikeInfo } from '@renderer/core/dislikeList' export default () => { @@ -90,6 +93,23 @@ export default () => { setStop() } + const collectMusic = () => { + if (!playMusicInfo.musicInfo) return + void addListMusics(loveList.id, ['progress' in playMusicInfo.musicInfo ? playMusicInfo.musicInfo.metadata.musicInfo : playMusicInfo.musicInfo]) + } + const unCollectMusic = () => { + if (!playMusicInfo.musicInfo) return + void removeListMusics({ listId: loveList.id, ids: ['progress' in playMusicInfo.musicInfo ? playMusicInfo.musicInfo.metadata.musicInfo.id : playMusicInfo.musicInfo.id] }) + } + const dislikeMusic = async() => { + if (!playMusicInfo.musicInfo) return + const minfo = 'progress' in playMusicInfo.musicInfo ? playMusicInfo.musicInfo.metadata.musicInfo : playMusicInfo.musicInfo + await addDislikeInfo([{ name: minfo.name, singer: minfo.singer }]) + if (!playMusicInfo.isTempPlay) { + playNext(true) + } + } + watch(() => appSetting['player.togglePlayMethod'], newValue => { // setLoopPlay(newValue == 'singleLoop') if (playedList.length) clearPlayedList() @@ -102,6 +122,9 @@ export default () => { window.key_event.on(HOTKEY_PLAYER.next.action, handlePlayNext) window.key_event.on(HOTKEY_PLAYER.prev.action, handlePlayPrev) window.key_event.on(HOTKEY_PLAYER.toggle_play.action, togglePlay) + window.key_event.on(HOTKEY_PLAYER.music_love.action, collectMusic) + window.key_event.on(HOTKEY_PLAYER.music_unlove.action, unCollectMusic) + window.key_event.on(HOTKEY_PLAYER.music_dislike.action, dislikeMusic) window.app_event.on('play', setPlayStatus) window.app_event.on('pause', setPauseStatus) @@ -119,6 +142,10 @@ export default () => { // eslint-disable-next-line @typescript-eslint/no-misused-promises window.key_event.off(HOTKEY_PLAYER.prev.action, handlePlayPrev) window.key_event.off(HOTKEY_PLAYER.toggle_play.action, togglePlay) + window.key_event.off(HOTKEY_PLAYER.music_love.action, collectMusic) + window.key_event.off(HOTKEY_PLAYER.music_unlove.action, unCollectMusic) + window.key_event.off(HOTKEY_PLAYER.music_dislike.action, dislikeMusic) + window.app_event.off('play', setPlayStatus) window.app_event.off('pause', setPauseStatus) diff --git a/src/renderer/store/dislikeList/action.ts b/src/renderer/store/dislikeList/action.ts new file mode 100644 index 00000000..fa917ddc --- /dev/null +++ b/src/renderer/store/dislikeList/action.ts @@ -0,0 +1,83 @@ +import { markRaw } from '@common/utils/vueTools' + + +import { dislikeInfo } from './state' +import { SPLIT_CHAR } from '@common/constants' + + +export const hasDislike = (info: LX.Music.MusicInfo) => { + const name = info.name?.replaceAll(SPLIT_CHAR.DISLIKE_NAME, SPLIT_CHAR.DISLIKE_NAME_ALIAS).toLocaleLowerCase().trim() ?? '' + const singer = info.singer?.replaceAll(SPLIT_CHAR.DISLIKE_NAME, SPLIT_CHAR.DISLIKE_NAME_ALIAS).toLocaleLowerCase().trim() ?? '' + + return dislikeInfo.musicNames.has(name) || dislikeInfo.singerNames.has(singer) || + dislikeInfo.names.has(`${name}${SPLIT_CHAR.DISLIKE_NAME}${singer}`) +} + +export const initDislikeInfo = ({ musicNames, rules, names, singerNames }: LX.Dislike.DislikeInfo) => { + dislikeInfo.names = markRaw(names) + dislikeInfo.singerNames = markRaw(singerNames) + dislikeInfo.musicNames = markRaw(musicNames) + dislikeInfo.rules = rules +} + +const initNameSet = () => { + dislikeInfo.names.clear() + dislikeInfo.musicNames.clear() + dislikeInfo.singerNames.clear() + const list: string[] = [] + for (const item of dislikeInfo.rules.split('\n')) { + if (!item) continue + let [name, singer] = item.split(SPLIT_CHAR.DISLIKE_NAME) + if (name) { + name = name.replaceAll(SPLIT_CHAR.DISLIKE_NAME, SPLIT_CHAR.DISLIKE_NAME_ALIAS).toLocaleLowerCase().trim() + if (singer) { + singer = singer.replaceAll(SPLIT_CHAR.DISLIKE_NAME, SPLIT_CHAR.DISLIKE_NAME_ALIAS).toLocaleLowerCase().trim() + const rule = `${name}${SPLIT_CHAR.DISLIKE_NAME}${singer}` + dislikeInfo.names.add(rule) + list.push(rule) + } else { + dislikeInfo.musicNames.add(name) + list.push(name) + } + } else if (singer) { + singer = singer.replaceAll(SPLIT_CHAR.DISLIKE_NAME, SPLIT_CHAR.DISLIKE_NAME_ALIAS).toLocaleLowerCase().trim() + dislikeInfo.singerNames.add(singer) + list.push(`${SPLIT_CHAR.DISLIKE_NAME}${singer}`) + } + } + dislikeInfo.rules = list.join('\n') + '\n' +} + +export const addDislikeInfo = (infos: LX.Dislike.DislikeMusicInfo[]) => { + dislikeInfo.rules += '\n' + infos.map(info => `${info.name ?? ''}${SPLIT_CHAR.DISLIKE_NAME}${info.singer ?? ''}`).join('\n') + initNameSet() + return dislikeInfo.rules +} + +export const overwirteDislikeInfo = (rules: string) => { + dislikeInfo.rules = rules + initNameSet() + return dislikeInfo.rules +} + + +// export const updateDislikeInfo = (info: LX.Dislike.ListItem) => { +// const targetInfo = dislikeInfo.list.find(i => i.id == info.id) +// if (!targetInfo) return +// targetInfo.name = info.name +// targetInfo.singer = info.singer +// initNameSet() +// } + +// export const removeDislikeInfo = (ids: string[]) => { +// for (const id of ids) { +// dislikeInfo.list.splice(dislikeInfo.list.findIndex(info => info.id == id), 1) +// } +// initNameSet() +// } + +// export const clearDislikeInfo = () => { +// dislikeInfo.rules = '' +// initNameSet() +// } + diff --git a/src/renderer/store/dislikeList/index.ts b/src/renderer/store/dislikeList/index.ts new file mode 100644 index 00000000..b71e70d2 --- /dev/null +++ b/src/renderer/store/dislikeList/index.ts @@ -0,0 +1,3 @@ + +export * as action from './action' +export * from './state' diff --git a/src/renderer/store/dislikeList/state.ts b/src/renderer/store/dislikeList/state.ts new file mode 100644 index 00000000..379dab35 --- /dev/null +++ b/src/renderer/store/dislikeList/state.ts @@ -0,0 +1,11 @@ +import { markRaw } from '@common/utils/vueTools' + +// import { deduplicationList } from '@common/utils/renderer' + + +export const dislikeInfo: LX.Dislike.DislikeInfo = markRaw({ + names: markRaw(new Set()), + musicNames: markRaw(new Set()), + singerNames: markRaw(new Set()), + rules: '', +}) diff --git a/src/renderer/store/player/action.ts b/src/renderer/store/player/action.ts index bd2bad5a..7ab78d46 100644 --- a/src/renderer/store/player/action.ts +++ b/src/renderer/store/player/action.ts @@ -73,7 +73,7 @@ export const setPlayListId = (listId: string | null) => { playInfo.playerListId = listId } -export const getList = (listId: string | null): LX.Music.MusicInfo[] | LX.Download.ListItem[] => { +export const getList = (listId: string | null): Array => { return listId == LIST_IDS.DOWNLOAD ? downloadList : getListMusicsFromCache(listId) } diff --git a/src/renderer/types/common.d.ts b/src/renderer/types/common.d.ts index 595892de..0691cbb4 100644 --- a/src/renderer/types/common.d.ts +++ b/src/renderer/types/common.d.ts @@ -15,3 +15,4 @@ import '@common/types/ipc_renderer' import '@common/types/config_files' import '@common/types/music_metadata' import '@common/types/sound_effect' +import '@common/types/dislike_list' diff --git a/src/renderer/utils/ipc.ts b/src/renderer/utils/ipc.ts index 5c06374d..7a97cbe7 100644 --- a/src/renderer/utils/ipc.ts +++ b/src/renderer/utils/ipc.ts @@ -40,6 +40,24 @@ export const getOtherSourceCount = async() => { return rendererInvoke(WIN_MAIN_RENDERER_EVENT_NAME.get_other_source_count) } +export const getDislikeListInfo = async(): Promise => { + return rendererInvoke(WIN_MAIN_RENDERER_EVENT_NAME.get_dislike_music_infos) +} +export const addDislikeInfo = async(dislikeInfo: LX.Dislike.DislikeMusicInfo[]) => { + return rendererInvoke(WIN_MAIN_RENDERER_EVENT_NAME.add_dislike_music_infos, dislikeInfo) +} +export const overwirteDislikeInfo = async(dislikeInfo: string) => { + return rendererInvoke(WIN_MAIN_RENDERER_EVENT_NAME.overwrite_dislike_music_infos, dislikeInfo) +} +// export const updateDislikeInfo = async(dislikeInfo: LX.Dislike.ListItem[]) => { +// await rendererInvoke(WIN_MAIN_RENDERER_EVENT_NAME.update_dislike_music_infos, dislikeInfo) +// } +// export const removeDislikeInfo = async(ids: string[]) => { +// await rendererInvoke(WIN_MAIN_RENDERER_EVENT_NAME.remove_dislike_music_infos, ids) +// } +// export const clearDislikeInfo = async() => { +// await rendererInvoke(WIN_MAIN_RENDERER_EVENT_NAME.clear_dislike_music_infos) +// } export const getHotKeyConfig = async() => { return rendererInvoke(WIN_MAIN_RENDERER_EVENT_NAME.get_hot_key) @@ -332,6 +350,11 @@ export const allHotKeys = markRaw({ action: hotKeys.HOTKEY_PLAYER.next.action, type: APP_EVENT_NAMES.winMainName, }, + { + name: hotKeys.HOTKEY_PLAYER.music_dislike.name, + action: hotKeys.HOTKEY_PLAYER.music_dislike.action, + type: APP_EVENT_NAMES.winMainName, + }, { name: hotKeys.HOTKEY_COMMON.focusSearchInput.name, action: hotKeys.HOTKEY_COMMON.focusSearchInput.action, @@ -394,6 +417,21 @@ export const allHotKeys = markRaw({ action: hotKeys.HOTKEY_PLAYER.volume_mute.action, type: APP_EVENT_NAMES.winMainName, }, + { + name: hotKeys.HOTKEY_PLAYER.music_love.name, + action: hotKeys.HOTKEY_PLAYER.music_love.action, + type: APP_EVENT_NAMES.winMainName, + }, + { + name: hotKeys.HOTKEY_PLAYER.music_unlove.name, + action: hotKeys.HOTKEY_PLAYER.music_unlove.action, + type: APP_EVENT_NAMES.winMainName, + }, + { + name: hotKeys.HOTKEY_PLAYER.music_dislike.name, + action: hotKeys.HOTKEY_PLAYER.music_dislike.action, + type: APP_EVENT_NAMES.winMainName, + }, { name: hotKeys.HOTKEY_DESKTOP_LYRIC.toggle_visible.name, action: hotKeys.HOTKEY_DESKTOP_LYRIC.toggle_visible.action, diff --git a/src/renderer/views/List/MusicList/index.vue b/src/renderer/views/List/MusicList/index.vue index 88c10727..043fe4b2 100644 --- a/src/renderer/views/List/MusicList/index.vue +++ b/src/renderer/views/List/MusicList/index.vue @@ -201,6 +201,7 @@ export default { handleSearch, handleOpenMusicDetail, handleCopyName, + handleDislikeMusic, handleRemoveMusic, } = useMusicActions({ props, list, removeAllSelect, selectedList }) @@ -223,6 +224,7 @@ export default { handleShowSortModal, handleOpenMusicDetail, handleCopyName, + handleDislikeMusic, handleRemoveMusic, }) diff --git a/src/renderer/views/List/MusicList/useMenu.js b/src/renderer/views/List/MusicList/useMenu.js index 7562211e..88995cfb 100644 --- a/src/renderer/views/List/MusicList/useMenu.js +++ b/src/renderer/views/List/MusicList/useMenu.js @@ -1,6 +1,7 @@ import { computed, ref, shallowReactive, reactive, nextTick } from '@common/utils/vueTools' import musicSdk from '@renderer/utils/musicSdk' import { useI18n } from '@renderer/plugins/i18n' +import { hasDislike } from '@renderer/core/dislikeList' export default ({ assertApiSupport, @@ -15,6 +16,7 @@ export default ({ handleShowSortModal, handleOpenMusicDetail, handleCopyName, + handleDislikeMusic, handleRemoveMusic, }) => { const itemMenuControl = reactive({ @@ -26,6 +28,7 @@ export default ({ sort: true, download: true, search: true, + dislike: true, remove: true, sourceDetail: true, }) @@ -80,6 +83,11 @@ export default ({ action: 'search', disabled: !itemMenuControl.search, }, + { + name: t('list__dislike'), + action: 'dislike', + disabled: !itemMenuControl.dislike, + }, { name: t('list__remove'), action: 'remove', @@ -94,6 +102,8 @@ export default ({ // itemMenuControl.playLater = itemMenuControl.download = assertApiSupport(musicInfo.source) && musicInfo.source != 'local' + itemMenuControl.dislike = !hasDislike(musicInfo) + menuLocation.x = event.pageX menuLocation.y = event.pageY @@ -138,6 +148,9 @@ export default ({ case 'search': handleSearch(index) break + case 'dislike': + handleDislikeMusic(index) + break case 'remove': handleRemoveMusic(index) break diff --git a/src/renderer/views/List/MusicList/useMusicActions.js b/src/renderer/views/List/MusicList/useMusicActions.js index 42283ba1..34856935 100644 --- a/src/renderer/views/List/MusicList/useMusicActions.js +++ b/src/renderer/views/List/MusicList/useMusicActions.js @@ -6,6 +6,9 @@ import { useI18n } from '@renderer/plugins/i18n' import { removeListMusics } from '@renderer/store/list/action' import { appSetting } from '@renderer/store/setting' import { toOldMusicInfo } from '@renderer/utils/index' +import { addDislikeInfo, hasDislike } from '@renderer/core/dislikeList' +import { playNext } from '@renderer/core/player' +import { playMusicInfo } from '@renderer/store/player/state' export default ({ props, list, selectedList, removeAllSelect }) => { @@ -34,6 +37,14 @@ export default ({ props, list, selectedList, removeAllSelect }) => { clipboardWriteText(appSetting['download.fileName'].replace('歌名', minfo.name).replace('歌手', minfo.singer)) } + const handleDislikeMusic = async(index) => { + const minfo = list.value[index] + await addDislikeInfo([{ name: minfo.name, singer: minfo.singer }]) + if (!playMusicInfo.isTempPlay && hasDislike(playMusicInfo.musicInfo)) { + playNext(true) + } + } + const handleRemoveMusic = async(index, single) => { if (selectedList.value.length && !single) { const confirm = await (selectedList.value.length > 1 @@ -55,6 +66,7 @@ export default ({ props, list, selectedList, removeAllSelect }) => { handleSearch, handleOpenMusicDetail, handleCopyName, + handleDislikeMusic, handleRemoveMusic, } } diff --git a/src/renderer/views/Setting/components/DislikeListModal.vue b/src/renderer/views/Setting/components/DislikeListModal.vue new file mode 100644 index 00000000..ae7bfccb --- /dev/null +++ b/src/renderer/views/Setting/components/DislikeListModal.vue @@ -0,0 +1,112 @@ + + + + + diff --git a/src/renderer/views/Setting/components/SettingOther.vue b/src/renderer/views/Setting/components/SettingOther.vue index ce775356..3b254f66 100644 --- a/src/renderer/views/Setting/components/SettingOther.vue +++ b/src/renderer/views/Setting/components/SettingOther.vue @@ -34,6 +34,16 @@ dd base-btn.btn(min :disabled="isDisabledMusicUrlCacheClear" @click="handleClearMusicUrlCache") {{ $t('setting__other_music_url_clear_btn') }} base-btn.btn(min :disabled="isDisabledLyricRawCacheClear" @click="handleClearLyricRawCache") {{ $t('setting__other_lyric_raw_clear_btn') }} +dd + h3#other_lyric_edited {{ $t('setting__other_dislike_list') }} + div + .p + | {{ $t('setting__other_dislike_list_label') }} + span.auto-hidden {{ dislikeRuleCount }} + .p + base-btn.btn(min @click="isShowDislikeList = true") {{ $t('setting__other_dislike_list_show_btn') }} + DislikeListModal(v-model="isShowDislikeList" @on-rule-update="handleCountRules") + dd h3#other_lyric_edited {{ $t('setting__other_lyric_edited_cache') }} div @@ -65,9 +75,14 @@ import { dialog } from '@renderer/plugins/Dialog' import { useI18n } from '@renderer/plugins/i18n' import { appSetting, updateSetting } from '@renderer/store/setting' import { overwriteListFull } from '@renderer/store/list/listManage' +import { dislikeInfo } from '@renderer/store/dislikeList' +import DislikeListModal from './DislikeListModal.vue' export default { name: 'SettingOther', + components: { + DislikeListModal, + }, setup() { const t = useI18n() @@ -135,6 +150,11 @@ export default { } refreshMusicUrlCount() + const dislikeRuleCount = ref(dislikeInfo.musicNames.size + dislikeInfo.singerNames.size + dislikeInfo.names.size) + const isShowDislikeList = ref(false) + const handleCountRules = () => { + dislikeRuleCount.value = dislikeInfo.musicNames.size + dislikeInfo.singerNames.size + dislikeInfo.names.size + } const lyricRawCount = ref(0) const isDisabledLyricRawCacheClear = ref(false) @@ -204,6 +224,10 @@ export default { isDisabledMusicUrlCacheClear, handleClearMusicUrlCache, + dislikeRuleCount, + isShowDislikeList, + handleCountRules, + lyricRawCount, isDisabledLyricRawCacheClear, handleClearLyricRawCache, diff --git a/src/renderer/worker/main/list.ts b/src/renderer/worker/main/list.ts index 566442e6..cb1b7b38 100644 --- a/src/renderer/worker/main/list.ts +++ b/src/renderer/worker/main/list.ts @@ -1,5 +1,6 @@ // import { throttle } from '@common/utils' +import { SPLIT_CHAR } from '@common/constants' import { filterFileName, sortInsert, similar, arrPushByPosition, arrShuffle } from '@common/utils/common' import { joinPath, saveStrToFile } from '@common/utils/nodejs' import { createLocalMusicInfo } from '@renderer/utils/music' @@ -8,7 +9,7 @@ import { createLocalMusicInfo } from '@renderer/utils/music' /** * 过滤列表中已播放的歌曲 */ -export const filterMusicList = async({ playedList, listId, list, savePath, playerMusicInfo }: { +export const filterMusicList = async({ playedList, listId, list, savePath, playerMusicInfo, dislikeInfo, isNext }: { /** * 已播放列表 */ @@ -29,15 +30,34 @@ export const filterMusicList = async({ playedList, listId, list, savePath, playe * 播放器内当前歌曲(`playInfo.playerPlayIndex`指向的歌曲) */ playerMusicInfo?: LX.Music.MusicInfo | LX.Download.ListItem + /** + * 不喜欢的歌曲名字列表 + */ + dislikeInfo: Omit + + isNext: boolean }) => { let playerIndex = -1 let canPlayList: Array = [] const filteredPlayedList = playedList.filter(pmInfo => pmInfo.listId == listId && !pmInfo.isTempPlay).map(({ musicInfo }) => musicInfo) + const hasDislike = (info: LX.Music.MusicInfo) => { + const name = info.name?.replaceAll(SPLIT_CHAR.DISLIKE_NAME, SPLIT_CHAR.DISLIKE_NAME_ALIAS).toLocaleLowerCase().trim() ?? '' + const singer = info.singer?.replaceAll(SPLIT_CHAR.DISLIKE_NAME, SPLIT_CHAR.DISLIKE_NAME_ALIAS).toLocaleLowerCase().trim() ?? '' + return dislikeInfo.musicNames.has(name) || dislikeInfo.singerNames.has(singer) || + dislikeInfo.names.has(`${name}${SPLIT_CHAR.DISLIKE_NAME}${singer}`) + } + + let isDislike = false const filteredList: Array = list.filter(s => { // if (!assertApiSupport(s.source)) return false - if ('progress' in s && !s.isComplate) return false + if ('progress' in s) { + if (!s.isComplate) return false + } else if (hasDislike(s)) { + if (s.id != playerMusicInfo?.id) return false + isDislike = true + } canPlayList.push(s) @@ -49,7 +69,34 @@ export const filterMusicList = async({ playedList, listId, list, savePath, playe return true }) if (playerMusicInfo) { - playerIndex = (filteredList.length ? filteredList : canPlayList).findIndex(m => m.id == playerMusicInfo.id) + if (isDislike) { + if (filteredList.length <= 1) { + filteredList.splice(0, 1) + if (canPlayList.length > 1) { + let currentMusicIndex = canPlayList.findIndex(m => m.id == playerMusicInfo.id) + if (isNext) { + playerIndex = currentMusicIndex - 1 + if (playerIndex < 0 && canPlayList.length > 1) playerIndex = canPlayList.length - 2 + } else { + playerIndex = currentMusicIndex + if (canPlayList.length <= 1) playerIndex = -1 + } + canPlayList.splice(currentMusicIndex, 1) + } else canPlayList.splice(0, 1) + } else { + let currentMusicIndex = filteredList.findIndex(m => m.id == playerMusicInfo.id) + if (isNext) { + playerIndex = currentMusicIndex - 1 + if (playerIndex < 0 && filteredList.length > 1) playerIndex = filteredList.length - 2 + } else { + playerIndex = currentMusicIndex + if (filteredList.length <= 1) playerIndex = -1 + } + filteredList.splice(currentMusicIndex, 1) + } + } else { + playerIndex = (filteredList.length ? filteredList : canPlayList).findIndex(m => m.id == playerMusicInfo.id) + } } return { filteredList,