From 93866551788b2395edd7165a6e34c499809aa988 Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Mon, 7 Jun 2021 17:44:12 +0800 Subject: [PATCH] =?UTF-8?q?feat(web):=20=E5=A2=9E=E5=8A=A0=E7=BD=91?= =?UTF-8?q?=E6=98=93=E4=BA=91=E9=9F=B3=E4=B9=90=E5=8D=A1=E7=89=87=E5=8F=91?= =?UTF-8?q?=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modals/NeteaseMusicSelector.tsx | 58 ++++++++++++++++--- .../netease-music/src/model/netease-music.ts | 47 +++++++++++++++ src/plugins/netease-music/src/reg.tsx | 11 +++- src/shared/i18n/langs/en-US/translation.json | 10 ++++ src/shared/i18n/langs/zh-CN/translation.json | 10 ++++ src/shared/redux/hooks/useMsgSend.ts | 36 ++++++++++-- .../chatBox/ChatSendBox/ChatMsgAddon.tsx | 5 +- .../components/chatBox/ChatSendBox/index.tsx | 2 +- src/web/plugin-loader.ts | 4 ++ src/web/reg/regChatSendBoxAction.ts | 2 +- 10 files changed, 166 insertions(+), 19 deletions(-) diff --git a/src/plugins/netease-music/src/components/modals/NeteaseMusicSelector.tsx b/src/plugins/netease-music/src/components/modals/NeteaseMusicSelector.tsx index 8d69022d1..4961c7dff 100644 --- a/src/plugins/netease-music/src/components/modals/NeteaseMusicSelector.tsx +++ b/src/plugins/netease-music/src/components/modals/NeteaseMusicSelector.tsx @@ -9,6 +9,7 @@ import { TMemo } from '@capital/shared/components/TMemo'; import { showToasts } from '@capital/shared/manager/ui'; import { Button, Divider, Empty, Input } from 'antd'; import { + fetchMusicDetail, NeteaseMusicSongInfo, searchMusicList, } from '../../model/netease-music'; @@ -16,6 +17,7 @@ import { ModalWrapper } from '@capital/web/components/Modal'; import { t } from '@capital/shared/i18n'; import styled from 'styled-components'; import _get from 'lodash/get'; +import { useMsgSend } from '@capital/shared/redux/hooks/useMsgSend'; const Search = Input.Search; @@ -31,21 +33,26 @@ const SongItem = styled.div` } `; -export const NeteaseMusicSelector: React.FC = TMemo(() => { +export const NeteaseMusicSelector: React.FC<{ + converseUUID: string; + onSendMusicCard?: () => void; +}> = TMemo((props) => { + const { converseUUID, onSendMusicCard } = props; const [loading, setLoading] = useState(false); const [searchedList, setSearchedList] = useState(null); + const { sendCardMsg } = useMsgSend(converseUUID); const onSearch = useCallback(async (text: string) => { try { setLoading(true); const res = await searchMusicList(text); - if (res.code === 200) { - setSearchedList(res.result.songs); - } else { + if (res.code !== 200) { showToasts(t('搜索音乐失败'), 'error'); + return; } + setSearchedList(res.result.songs); } catch (err) { console.error(err); showToasts(`${t('搜索失败')}: ${String(err)}`, 'error'); @@ -54,9 +61,46 @@ export const NeteaseMusicSelector: React.FC = TMemo(() => { } }, []); - const handleClick = useCallback((songId: number) => { - console.log('songId', songId); - }, []); + const handleClick = useCallback( + async (songId: number) => { + try { + setLoading(true); + + const res = await fetchMusicDetail(songId); + if (res.code !== 200) { + showToasts(`${t('发送失败')}: 网络异常`, 'error'); + return; + } + + const detail = _get(res, ['data', 0]); + const url = detail.url; + + if (typeof url === 'string' && url !== '') { + sendCardMsg('media', { + mediaType: 'audio', + mediaSource: 'netease', + mediaUrl: url, + neteaseSongId: songId, + }); + showToasts(t('发送成功'), 'success'); + + if (typeof onSendMusicCard === 'function') { + onSendMusicCard(); + } + } else { + showToasts( + `${t('发送失败')}: 该音乐可能是一个版权音乐, 无法获取播放地址`, + 'error' + ); + } + } catch (err) { + showToasts(`${t('请求失败')}: ${String(err)}`, 'error'); + } finally { + setLoading(false); + } + }, + [sendCardMsg, onSendMusicCard] + ); return ( diff --git a/src/plugins/netease-music/src/model/netease-music.ts b/src/plugins/netease-music/src/model/netease-music.ts index dec41b528..39db837ff 100644 --- a/src/plugins/netease-music/src/model/netease-music.ts +++ b/src/plugins/netease-music/src/model/netease-music.ts @@ -5,6 +5,11 @@ interface NeteaseMusicResponse { result: T; } +interface NeteaseMusicResponseData { + code: number; // 200 + data: T; +} + interface NeteaseMusicSearchResult { hasMore: boolean; songCount: number; @@ -51,6 +56,34 @@ interface NeteaseMusicSongAlbumArtist { trans: unknown; } +interface NeteaseMusicSongDetail { + br: number; + canExtend: boolean; + code: number; + encodeType: 'mp3'; // maybe have m4a? + expi: number; + fee: number; + flag: number; + freeTimeTrialPrivilege: { + resConsumable: boolean; + userConsumable: boolean; + type: number; + remainTime: number; + }; + freeTrialInfo: unknown; + freeTrialPrivilege: { resConsumable: boolean; userConsumable: boolean }; + gain: number; + id: number; + level: 'normal' | 'exhigh'; // normal, exhigh ... + md5: string; + payed: number; + size: number; + type: 'mp3'; + uf: unknown; + url: string; + urlSource: number; +} + /** * 搜索音乐 */ @@ -63,3 +96,17 @@ export async function searchMusicList(keywords: string) { return data; } + +/** + * 获取音乐Url + */ +export async function fetchMusicDetail(songId: number) { + const res = await fetch(`${neteaseMusicAPI}/song/url?id=${songId}`, { + credentials: 'include', + }); + const data = (await res.json()) as NeteaseMusicResponseData< + NeteaseMusicSongDetail[] + >; + + return data; +} diff --git a/src/plugins/netease-music/src/reg.tsx b/src/plugins/netease-music/src/reg.tsx index ab2d9fe02..cf8fa578b 100644 --- a/src/plugins/netease-music/src/reg.tsx +++ b/src/plugins/netease-music/src/reg.tsx @@ -1,12 +1,17 @@ import React from 'react'; import { regChatSendBoxAddonAction } from '@capital/web/reg/regChatSendBoxAction'; -import { openModal } from '@capital/web/components/Modal'; +import { closeModal, openModal } from '@capital/web/components/Modal'; import { NeteaseMusicSelector } from './components/modals/NeteaseMusicSelector'; import { t } from '@capital/shared/i18n'; regChatSendBoxAddonAction({ label: t('发送网易云音乐'), - onClick: () => { - openModal(); + onClick: ({ converseUUID }) => { + const key = openModal( + closeModal(key)} + /> + ); }, }); diff --git a/src/shared/i18n/langs/en-US/translation.json b/src/shared/i18n/langs/en-US/translation.json index bd59928b8..b4e47b185 100644 --- a/src/shared/i18n/langs/en-US/translation.json +++ b/src/shared/i18n/langs/en-US/translation.json @@ -9,6 +9,7 @@ "k1574405e": "Once it is determined that it cannot be revoked", "k15d1c82c": "This component has been discarded. If it affects your use, please contact the developer", "k16641ebe": "No information was found", + "k19a60a55": "搜索音乐失败", "k1a47edf3": "Please Type Account Password", "k1ba3a7e9": "Lawful evil", "k1c93fc91": "Action Message", @@ -135,6 +136,8 @@ "k6a365d01": "Operation failed", "k6b8145c2": "Login Result", "k6c2d3aa8": "Compact Mode", + "k6d4e5958": "发送网易云音乐", + "k6d58e46e": "发送失败", "k6dfdc625": "Maximize", "k6e23c48": "Other", "k6ec17240": "Note Title", @@ -149,6 +152,7 @@ "k77a44085": "Connecting...", "k77ee6a43": "Member Manage", "k78102887": "Group Members", + "k7862cd02": "发送成功", "k7917d72c": "Export Actor Card", "k7a89720": "Open in new window", "k7a9587f0": "Search Result", @@ -159,6 +163,7 @@ "k7ffd35dd": "Desktop notification permission is disabled", "k8004f31b": "Tick", "k802cc488": "Please enter 5-20 new passwords", + "k8040989e": "[卡片消息]", "k81662255": "Create Invite Code", "k81c7a6d6": "Request to add actor", "k821df934": "No Account? Register Now", @@ -180,12 +185,14 @@ "k8c717e61": "Lawful neutral", "k8f17d40": "The application is successful, please wait for the approval of the group administrator", "k8fd7fca5": "Update Actor", + "k901e9f6f": "请求失败", "k921f1c92": "My Account", "k93fb7d2d": "Group Not Found", "k950183bc": "New password cannot be empty", "k959638c1": "Welcome to TRPG World", "k960aa491": "Search Chat Log", "k96608ca7": "Group Sub Name", + "k97453f25": "网易云音乐", "k97464d0d": "All Actor Card: {{groupActorNum}}", "k98dadf2e": "Copy completed! Send to friend and let him entry!", "k98dff103": "Clear Cache", @@ -214,6 +221,7 @@ "ka7907771": "Save Success", "ka7c99495": "Are you ensure to delete note {{title}}?", "ka7f5a106": "Empty Username or Password Not Allowed", + "ka891a9ce": "消息不能为空", "ka93a38e0": "Voice Channel", "ka9a9cdc8": "Is Alpha User", "kaa9fd559": "Voice setting changes cannot take effect when using mic", @@ -251,6 +259,7 @@ "kc48f6f64": "All", "kc522e12c": "Login Type", "kc567b415": "Report Error", + "kc60e4ac2": "发送", "kc77d6c87": "Note Loading Failed", "kc79ba447": "Neutral good", "kc7d58c4c": "Repeat Password Incorrect", @@ -297,6 +306,7 @@ "ke456aed": "Agree", "ke4d28dd6": "Ensure to Quit Group", "ke4ed9d21": "Assign Members", + "ke5262837": "搜索失败", "ke6ae638": "True neutral", "ke8477173": "Yourself", "ke88afa8f": "Bot List", diff --git a/src/shared/i18n/langs/zh-CN/translation.json b/src/shared/i18n/langs/zh-CN/translation.json index f5f9406c2..ab8884db4 100644 --- a/src/shared/i18n/langs/zh-CN/translation.json +++ b/src/shared/i18n/langs/zh-CN/translation.json @@ -9,6 +9,7 @@ "k1574405e": "一旦确定无法撤销", "k15d1c82c": "该组件已被弃用, 如果影响到您的使用请联系开发者", "k16641ebe": "找不到相关信息", + "k19a60a55": "搜索音乐失败", "k1a47edf3": "请输入账号密码", "k1ba3a7e9": "守序中立", "k1c93fc91": "行动信息", @@ -135,6 +136,8 @@ "k6a365d01": "操作失败", "k6b8145c2": "登录结果", "k6c2d3aa8": "紧凑模式", + "k6d4e5958": "发送网易云音乐", + "k6d58e46e": "发送失败", "k6dfdc625": "最大化", "k6e23c48": "其他", "k6ec17240": "笔记标题", @@ -149,6 +152,7 @@ "k77a44085": "正在连接...", "k77ee6a43": "成员管理", "k78102887": "成员数", + "k7862cd02": "发送成功", "k7917d72c": "导出人物卡", "k7a89720": "在新窗口打开", "k7a9587f0": "搜索结果", @@ -159,6 +163,7 @@ "k7ffd35dd": "桌面通知权限已被禁止", "k8004f31b": "踢出团", "k802cc488": "请输入5~20位新密码", + "k8040989e": "[卡片消息]", "k81662255": "创建邀请码", "k81c7a6d6": "申请添加角色", "k821df934": "没有账号?现在注册", @@ -180,12 +185,14 @@ "k8c717e61": "守序中立", "k8f17d40": "申请成功, 请等待团管理员审批", "k8fd7fca5": "更新人物卡", + "k901e9f6f": "请求失败", "k921f1c92": "我的账号", "k93fb7d2d": "找不到该团", "k950183bc": "新密码不能为空", "k959638c1": "欢迎来到TRPG的世界", "k960aa491": "搜索聊天记录", "k96608ca7": "团副名", + "k97453f25": "网易云音乐", "k97464d0d": "共 {{groupActorNum}} 张角色卡", "k98dadf2e": "复制成功, 直接发送给好友让他加入吧", "k98dff103": "清理缓存", @@ -214,6 +221,7 @@ "ka7907771": "保存成功", "ka7c99495": "确定要删除笔记 {{title}} 么", "ka7f5a106": "用户名密码不能为空", + "ka891a9ce": "消息不能为空", "ka93a38e0": "语音频道", "ka9a9cdc8": "是否为内测用户", "kaa9fd559": "语音设置变更在使用麦克风时无法立即生效", @@ -251,6 +259,7 @@ "kc48f6f64": "所有", "kc522e12c": "登录类型", "kc567b415": "汇报错误", + "kc60e4ac2": "发送", "kc77d6c87": "笔记加载失败", "kc79ba447": "中立善良", "kc7d58c4c": "重复密码不正确", @@ -297,6 +306,7 @@ "ke456aed": "同意", "ke4d28dd6": "是否要退出群", "ke4ed9d21": "指定成员", + "ke5262837": "搜索失败", "ke6ae638": "绝对中立", "ke8477173": "你自己", "ke88afa8f": "机器人列表", diff --git a/src/shared/redux/hooks/useMsgSend.ts b/src/shared/redux/hooks/useMsgSend.ts index 846ec27d2..d6a00a998 100644 --- a/src/shared/redux/hooks/useMsgSend.ts +++ b/src/shared/redux/hooks/useMsgSend.ts @@ -1,5 +1,12 @@ import { useConverseDetail } from '@redux/hooks/chat'; -import { useCallback, useEffect, useContext, useState, useRef } from 'react'; +import { + useCallback, + useEffect, + useContext, + useState, + useRef, + useMemo, +} from 'react'; import { isUserUUID } from '@shared/utils/uuid'; import { sendStopWriting, sendStartWriting } from '@shared/api/event'; import { useTRPGDispatch } from '@redux/hooks/useTRPGSelector'; @@ -16,6 +23,7 @@ import { } from '@shared/context/GroupInfoContext'; import { showToasts } from '@shared/manager/ui'; import { useChatMsgTypeContext } from '@shared/context/ChatMsgTypeContext'; +import { t } from '@shared/i18n'; /** * 消息输入相关事件 @@ -45,12 +53,12 @@ export function useMsgSend(converseUUID: string) { * 发送消息到远程服务器 */ const sendMsg = useCallback( - (message: string, type: MsgType) => { + (message: string, type: MsgType, data?: {}) => { message = preProcessMessage(message); if (message === '') { // 如果处理后消息为空则跳出 - showToasts('消息不能为空', 'warning'); + showToasts(t('消息不能为空'), 'warning'); return; } @@ -66,6 +74,7 @@ export function useMsgSend(converseUUID: string) { is_public: false, is_group: false, type, + data, }) ); } else if (converseType === 'group' || converseType === 'channel') { @@ -74,6 +83,7 @@ export function useMsgSend(converseUUID: string) { const msgDataManager = new MsgDataManager(); // 选中的角色 + // TODO: Not good if (!_isNil(selectedGroupActorInfo)) { msgDataManager.setGroupActorInfo(selectedGroupActorInfo); } @@ -92,7 +102,10 @@ export function useMsgSend(converseUUID: string) { is_public: true, is_group: true, type, - data: msgDataManager.toJS(), + data: { + ...msgDataManager.toJS(), + ...data, + }, }) ); } @@ -113,6 +126,19 @@ export function useMsgSend(converseUUID: string) { ] ); + /** + * 发送卡片消息到远程服务器 + */ + const sendCardMsg = useCallback( + (cardType: string, otherData: {}, message: string = t('[卡片消息]')) => { + sendMsg(message, 'card', { + ...otherData, + type: cardType, + }); + }, + [sendMsg] + ); + const handleSendMsg = useCallback(() => { if (!!message) { sendMsg(message, msgType ?? 'normal'); @@ -129,5 +155,5 @@ export function useMsgSend(converseUUID: string) { } }, [converseType, message]); - return { message, setMessage, handleSendMsg, inputRef, sendMsg }; + return { message, setMessage, handleSendMsg, inputRef, sendMsg, sendCardMsg }; } diff --git a/src/web/components/chatBox/ChatSendBox/ChatMsgAddon.tsx b/src/web/components/chatBox/ChatSendBox/ChatMsgAddon.tsx index 6d75661aa..cc1dd3dc2 100644 --- a/src/web/components/chatBox/ChatSendBox/ChatMsgAddon.tsx +++ b/src/web/components/chatBox/ChatSendBox/ChatMsgAddon.tsx @@ -32,9 +32,10 @@ const ChatMsgAddonItemContainer = styled.div` export const ChatMsgAddon: React.FC<{ editorRef: React.MutableRefObject; + converseUUID: string; style?: React.CSSProperties; }> = TMemo((props) => { - const { editorRef } = props; + const { editorRef, converseUUID } = props; const [visible, setVisible] = useState(false); const { t } = useTranslation(); @@ -88,7 +89,7 @@ export const ChatMsgAddon: React.FC<{ key={item.label} onClick={() => { setVisible(false); - item.onClick(); + item.onClick({ converseUUID }); }} > {item.label} diff --git a/src/web/components/chatBox/ChatSendBox/index.tsx b/src/web/components/chatBox/ChatSendBox/index.tsx index a9322860a..8d2d07286 100644 --- a/src/web/components/chatBox/ChatSendBox/index.tsx +++ b/src/web/components/chatBox/ChatSendBox/index.tsx @@ -53,7 +53,7 @@ export const ChatSendBox: React.FC = TMemo((props) => { {...chatSendBoxRightAction.reverse()} - + diff --git a/src/web/plugin-loader.ts b/src/web/plugin-loader.ts index 6a93e9af8..52e86b04a 100644 --- a/src/web/plugin-loader.ts +++ b/src/web/plugin-loader.ts @@ -199,6 +199,10 @@ export function initPlugins(): Promise { '@capital/shared/redux/hooks/useCache', () => import('@shared/redux/hooks/useCache') ); + regSharedModule( + '@capital/shared/redux/hooks/useMsgSend', + () => import('@shared/redux/hooks/useMsgSend') + ); regSharedModule( '@capital/shared/redux/hooks/group', () => import('@shared/redux/hooks/group') diff --git a/src/web/reg/regChatSendBoxAction.ts b/src/web/reg/regChatSendBoxAction.ts index 8846587c5..b70348ac0 100644 --- a/src/web/reg/regChatSendBoxAction.ts +++ b/src/web/reg/regChatSendBoxAction.ts @@ -25,5 +25,5 @@ export const [chatSendBoxRightAction, regChatSendBoxRightAction] = export const [chatSendBoxAddonAction, regChatSendBoxAddonAction] = buildRegList<{ label: string; - onClick: () => void; + onClick: (context: { converseUUID: string }) => void; }>();