import { ArrowLeftOutlined, CommentOutlined, CloseCircleOutlined, EditOutlined, EnterOutlined, ArrowRightOutlined, DeleteOutlined, LoadingOutlined, ReloadOutlined, SendOutlined } from '@ant-design/icons' import { Avatar, Button, Empty, Form, Input, Layout, List, Menu, notification, Spin, Tabs, Typography } from 'antd' import prettyBytes from 'pretty-bytes' import React, { useEffect, useRef, useState } from 'react' import { useThemeSwitcher } from 'react-css-theme-switcher' import { ChatItem, ChatList, MessageBox } from 'react-chat-elements' import ReactMarkdown from 'react-markdown' import { useHistory, useLocation } from 'react-router' import remarkGfm from 'remark-gfm' import useSWR from 'swr' import { apiUrl, fetcher, req } from '../../../utils/Fetcher' import 'react-chat-elements/dist/main.css' interface Props { me?: any, collapsed?: boolean, parent?: any, setCollapsed: (data: boolean) => void } const Messaging: React.FC = ({ me, collapsed, parent, setCollapsed }) => { const [qVal, setQVal] = useState() const [q, setQ] = useState() const [messageText, setMessageText] = useState() const [messageReply, setMessageReply] = useState() const [messageForward, setMessageForward] = useState() const [messageId, setMessageId] = useState() const [loadingSend, setLoadingSend] = useState() const [message, setMessage] = useState() const [chatList, setChatLists] = useState() const [chatListOffset, setChatListOffset] = useState() const [searchMessageList, setSearchMessageList] = useState() const [searcGlobalList, setSearcGlobalList] = useState() const [searchAccountList, setSearchAccountList] = useState() const [messages, setMessages] = useState() const [messagesParsed, setMessagesParsed] = useState() const [messagesOffset, setMessagesOffset] = useState() const [width, setWidth] = useState() const [popup, setPopup] = useState<{ visible: boolean, x?: number, y?: number, row?: any }>() const inputSend = useRef() const history = useHistory() const { search: searchParams } = useLocation() const { currentTheme } = useThemeSwitcher() const { data: dialogs, mutate: refetchDialogs } = useSWR(!collapsed && !q && !message ? `/dialogs?limit=10${chatListOffset ? `&offset=${chatListOffset}` : ''}` : null, fetcher, { onSuccess: data => { setChatListOffset(undefined) setChatLists([...chatList?.filter((dialog: any) => !data.dialogs.find((d: any) => d.id === dialog.id)) || [], ...data.dialogs || []]) } }) const { data: searchMessages } = useSWR(q ? `/messages/search?q=${q}&limit=10` : null, fetcher, { onSuccess: data => setSearchMessageList(data.messages || []) }) const { data: searchGlobal } = useSWR(q ? `/messages/globalSearch?q=${q}&limit=5` : null, fetcher, { onSuccess: data => setSearcGlobalList(data.messages || []) }) const { data: searchAccounts } = useSWR(q ? `/users/search?username=${q}&limit=10` : null, fetcher, { onSuccess: data => setSearchAccountList(data.users || []) }) const { data: messageHistory, mutate: refetchHistory } = useSWR(message && messagesOffset !== undefined ? `/messages/history/${message.id}&limit=10&offset=${messagesOffset}` : null, fetcher, { onSuccess: data => { // setMessagesOffset(0) const res = { ...messages, ...data.messages, messages: [...messages?.messages.filter((msg: any) => !data.messages.messages.find((newMsg: any) => newMsg.id === msg.id)) || [], ...data.messages.messages], users: [...messages?.users.filter((user: any) => !data.messages.users.find((newUser: any) => newUser.id === user.id)) || [], ...data.messages.users], chats: [...messages?.chats.filter((chat: any) => !data.messages.chats.find((newChat: any) => newChat.id === chat.id)) || [], ...data.messages.chats] } setMessages(res) } }) useEffect(() => { setSearchMessageList(undefined) setSearcGlobalList(undefined) setSearchAccountList(undefined) }, [q]) useEffect(() => { setMessages(undefined) if (message) { setMessagesOffset(0) req.post(`/messages/read/${message.id}`).catch(() => {}) // inputSend.current.focus() } }, [message]) useEffect(() => { const msg = new URLSearchParams(location.search).get('msg') || null const chat = new URLSearchParams(location.search).get('chat') || null if (msg) { setMessage(JSON.parse(Buffer.from(decodeURIComponent(msg), 'base64').toString())) } setCollapsed(chat !== 'open') }, []) useEffect(() => { if (messageHistory?.messages) { // const sidebar = document.querySelector('.ant-layout-sider.ant-layout-sider-light.messaging') // if (sidebar) { // sidebar.scroll({ top: sidebar.clientHeight, behavior: 'smooth' }) // // lastMessage.current.focus = true // // lastMessage.current.refs.message.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'start' }) // } } }, [messageHistory]) useEffect(() => { const setDataMessages = (dialog?: any, sponsoredMessages?: { messages: any[], chats: any[], users: any[] }) => { setMessagesParsed(messages?.messages.reduce((res: any[], msg: any) => { let user = messages?.users.find((user: any) => user.id === (msg.fromId || msg.peerId)?.userId) if (!user) { user = messages?.chats.find((user: any) => user.id === (msg.fromId || msg.peerId)?.channelId) } const replyMsg = messages?.messages.find((m: any) => m.id === msg.replyTo?.replyToMsgId) let replyUser = replyMsg ? messages?.users.find((user: any) => user.id === (replyMsg.fromId || replyMsg.peerId)?.userId) : null if (!replyUser && replyMsg) { replyUser = messages?.chats.find((user: any) => user.id === (replyMsg.fromId || replyMsg.peerId)?.channelId) } let fileTitle: string | null = null let size: number = 0 if (msg?.media?.photo || msg?.media?.document) { const mimeType = msg?.media?.photo ? 'image/jpeg' : msg?.media?.document.mimeType || 'unknown' fileTitle = msg?.media?.photo ? `${msg?.media?.photo.id}.jpg` : msg?.media?.document.attributes?.find((atr: any) => atr.fileName)?.fileName || `${msg?.media?.document.id}.${mimeType.split('/').pop()}` const getSizes = (data: any) => data?.sizes ? data?.sizes.pop() : data?.size size = msg?.media?.photo ? getSizes(msg?.media?.photo.sizes.pop()) : msg?.media?.document?.size } return [ ...res, fileTitle ? { id: `${message?.id.replace(/\?.*$/gi, '')}/${msg.id}`, messageId: message?.id, key: msg.id, position: me?.user.tg_id == user?.id ? 'right' : 'left', type: 'file', title: user ? user.title || `${user.firstName || ''} ${user.lastName || ''}`.trim() : 'Unknown', onTitleClick: () => user?.username ? window.open(`https://t.me/${user?.username}`, '_blank') : undefined, titleColor: '#0088CC', text: `${fileTitle.slice(0, 16)}${fileTitle.length > 16 ? '...' : ''}`, message: `${fileTitle.slice(0, 16)}${fileTitle.length > 16 ? '...' : ''}`, status: me?.user.tg_id == user?.id ? msg.id <= dialog?.dialog?.readOutboxMaxId ? 'read' : 'received' : undefined, date: msg.date * 1000, user, onDownload: () => download(msg), data: { size: size ? prettyBytes(Number(size)) : undefined, status: { error: false, download: false, click: false } } } : null, msg.action?.className === 'MessageActionContactSignUp' ? { key: msg.id, type: 'system', date: msg.date * 1000, text: <>{user ? user.title || `${user.firstName || ''} ${user.lastName || ''}`.trim() : 'Unknown'} joined Telegram! } : msg.action?.className === 'MessageActionChatAddUser' ? { key: msg.id, type: 'system', date: msg.date * 1000, text: <>{user ? user.title || `${user.firstName || ''} ${user.lastName || ''}`.trim() : 'Unknown'} joined the group } : msg.message ? { id: `${message?.id.replace(/\?.*$/gi, '')}/${msg.id}`, messageId: message?.id, key: msg.id, position: me?.user.tg_id == user?.id ? 'right' : 'left', type: 'text', status: me?.user.tg_id == user?.id ? msg.id <= dialog?.dialog?.readOutboxMaxId ? 'read' : 'received' : undefined, title: user ? user.title || `${user.firstName || ''} ${user.lastName || ''}`.trim() : 'Unknown', onTitleClick: () => user?.username ? window.open(`https://t.me/${user?.username}`, '_blank') : undefined, text: {msg.message ? `${msg.message.replaceAll('\n', ' \n')}${msg.editDate && !msg.editHide ? '\n\n_(edited)_' : ''}${msg.fwdFrom ? '\n\n_(forwarded)_' : ''}` : 'Unknown message'}, message: msg.message, fwdFrom: msg.fwdFrom, date: msg.date * 1000, titleColor: '#0088CC', user, reply: replyMsg ? { title: replyUser ? replyUser.title || `${replyUser.firstName || ''} ${replyUser.lastName || ''}`.trim() : 'Unknown', titleColor: '#0088CC', message: replyMsg.message || 'Unknown message' } : undefined } : null ] }, sponsoredMessages?.messages?.map((msg: any) => { let user = sponsoredMessages?.users.find((user: any) => user.id === (msg.fromId || msg.peerId)?.userId) if (!user) { user = sponsoredMessages?.chats.find((user: any) => user.id === (msg.fromId || msg.peerId)?.channelId) } return { id: `${message?.id.replace(/\?.*$/gi, '')}/sponsor`, messageId: message?.id, key: 'sponsor', position: 'left', type: 'text', // status: me?.user.tg_id == user?.id ? msg.id <= dialog?.dialog?.readOutboxMaxId ? 'read' : 'received' : undefined, title: user ? user.title || `${user.firstName || ''} ${user.lastName || ''}`.trim() : 'Unknown', onTitleClick: () => user?.username ? window.open(`https://t.me/${user?.username}`, '_blank') : undefined, text: {msg.message ? `${msg.message.replaceAll('\n', ' \n')}\n\n_(sponsored message)_` : 'Unknown message'}, message: msg.message, fwdFrom: msg.fwdFrom, date: new Date().getTime(), // titleColor: `#${`${user?.id.toString(16)}000000`.slice(0, 6)}`, user } }) || []).filter(Boolean).sort((a: any, b: any) => a.date - b.date) || []) // messageList.current?.scrollToRow = 50 } if (message) { req.get(`/dialogs/${message.id}`).then(({ data }) => { req.get(`/messages/sponsoredMessages/${message.id}`).then(({ data: sponsoredData }) => { setDataMessages(data.dialog, sponsoredData?.messages) sponsoredData.messages?.messages.map((msg: any) => req.post(`/messages/readSponsoredMessages/${message.id}`, { random_id: msg.randomId?.data })) // const sidebar = document.querySelector('.ant-layout-sider.ant-layout-sider-light.messaging') // if (sidebar) { // sidebar.scroll({ top: sidebar.scrollHeight, behavior: 'smooth' }) // } }).catch(_ => { setDataMessages(data.dialog) }) }) } else { setDataMessages() } }, [messages]) useEffect(() => { const params = new URLSearchParams(searchParams) const chat = params.get('chat') const msg = params.get('msg') const q = params.get('qmsg') setCollapsed(chat !== 'open') if (msg) { const msgObj = JSON.parse(Buffer.from(decodeURIComponent(msg), 'base64').toString()) setMessage(msgObj) if (messageForward) { const [typeFrom, othersFrom] = messageForward.messageId.replace('?t=1', '').split('/') const [idFrom, accessHashFrom] = othersFrom.split('?accessHash=') const [typeTo, othersTo] = msgObj.id.replace('?t=1', '').split('/') const [idTo, accessHashTo] = othersTo.split('?accessHash=') req.post(`/messages/forward/${messageForward.key}`, { from: { type: typeFrom, id: idFrom, accessHash: accessHashFrom }, to: { type: typeTo, id: idTo, accessHash: accessHashTo } }).then(refetchHistory) setMessageForward(undefined) } } else { setMessage(undefined) setMessagesOffset(undefined) // const sidebar = document.querySelector('.ant-layout-sider.ant-layout-sider-light.messaging') // sidebar?.scroll({ top: 0, behavior: 'smooth' }) } if (q) { setQ(q) } else { setQ(undefined) setQVal(undefined) } if (chat === 'open') { setTimeout(() => { const base = document.querySelector('.ant-layout-sider.messaging') if (base) { setWidth(base.clientWidth) } }, 1000) } }, [searchParams]) const open = () => { const searchParams = new URLSearchParams(window.location.search) if (collapsed) { searchParams.set('chat', 'open') } else { searchParams.delete('chat') } history.push(`${window.location.pathname}?${searchParams.toString()}`) } const openMessage = (message: any) => { const searchParams = new URLSearchParams(window.location.search) searchParams.set('msg', encodeURIComponent(Buffer.from(JSON.stringify(message)).toString('base64'))) history.push(`${window.location.pathname}?${searchParams.toString()}`) } const search = (val?: string) => { const searchParams = new URLSearchParams(window.location.search) if (val) { searchParams.set('qmsg', val) } else { searchParams.delete('qmsg') } history.push(`${window.location.pathname}?${searchParams.toString()}`) } const back = () => { history.goBack() } const download = async (msg: any) => { const [type, others] = message.id.replace('?t=1', '').split('/') const [id, accessHash] = others.split('?accessHash=') const forwardKey = `${type}/${id}/${msg.id}${accessHash ? `/${accessHash}` : ''}` const { data: files } = await req.get('/files', { params: { forward_info: forwardKey } }) if (files?.files?.[0]) { const file = files?.files?.[0] return history.push(`/view/${file.id}`) } const { data: file } = await req.post('/files', { file: { parent_id: parent?.link_id || parent?.id, forward_info: forwardKey, id: undefined } }, { params: { messageId: msg.id } }) return history.push(`/view/${file.file.id}`) } const remove = async (msg: any) => { await req.delete(`/messages/${msg.id}`) setMessages({ ...messages, messages: messages?.messages.filter((message: any) => message.id != msg.id.split('/')[msg.id.split('/').length - 1]) }) notification.success({ message: 'Message deleted!' }) } const sendMessage = async () => { if (!messageText) { return notification.error({ message: 'Error', description: 'Please write your message first' }) } setLoadingSend(true) try { if (messageId) { const [type, others] = message.id.replace('?t=1', '').split('/') const [id, accessHash] = others.split('?accessHash=') await req.patch(`/messages/${type}/${id}/${messageId}`, { message: messageText }, { params: accessHash ? { accessHash } : {} }) setMessagesParsed(messagesParsed?.map((message: any) => message.key == messageId ? { ...message, message: messageText, text: {messageText ? `${messageText.replaceAll('\n', ' \n')}\n\n_(edited)_` : 'Unknown message'} } : message)) setMessageId(undefined) } else { await req.post(`/messages/send/${message?.id}`, { message: messageText, ...messageReply?.key ? { replyToMsgId: messageReply.key } : {} }) refetchHistory() } setMessageText(undefined) setMessageReply(undefined) } catch (error: any) { setLoadingSend(false) return notification.error({ message: 'Error', description: <> {error?.response?.data?.error || error.message || 'Something error'} {JSON.stringify(error?.response?.data || error?.data || error, null, 2)} }) } return setLoadingSend(false) } const ContextMenu = () => { const baseProps = { style: { margin: 0 } } if (!popup?.visible) return <> if (popup?.row) { return } key="forward" onClick={() => { setMessageForward(popup.row) const searchParams = new URLSearchParams(window.location.search) searchParams.delete('msg') history.push(`${window.location.pathname}?${searchParams.toString()}`) }}>Forward } key="reply" onClick={() => setMessageReply(popup.row)}>Reply {me?.user.tg_id == popup.row.user?.id && <> {popup.row.type === 'text' && !popup.row.fwdFrom && } key="edit" onClick={() => { console.log(popup.row.key) setMessageId(popup.row.key) setMessageText(popup.row.message) }}>Edit} } key="delete" danger onClick={() => remove(popup.row)}>Delete } } return <> } return
{message ?
  {message?.title}
:
Quick Message
} {(!q || message) &&
}
{message ? <> setPopup({ visible: false })} onContextMenu={e => { if (item.type !== 'system') { e.preventDefault() if (!popup?.visible) { document.addEventListener('click', function onClickOutside() { setPopup({ visible: false }) document.removeEventListener('click', onClickOutside) }) } const parent = document.querySelector('.ant-layout-content.container') setPopup({ visible: true, x: e.clientX - (parent?.getBoundingClientRect().left || 0) - 100, y: e.clientY - (parent?.getBoundingClientRect().top || 0), row: item }) } }}> } /> : <> setQVal(e.target.value)} className="input-search-round" placeholder="Search by username or message..." enterButton onSearch={search} allowClear /> {q && !message && {searchAccounts && !searchAccountList?.length && } { const title = `${user.firstName || ''} ${user.lastName || ''}`.trim() return { id: `user/${user.id}${user?.accessHash ? `?accessHash=${user?.accessHash}` : '?t=1'}`, key: user.id, avatar: `${apiUrl}/messages/user/${user.id}/avatar.jpg${user?.accessHash ? `?accessHash=${user?.accessHash}` : '?t=1'}`, alt: title?.split(' ')?.map((word: string) => word[0]).slice(0, 2).join('').toUpperCase(), title: title, subtitle: user.username ? `@${user.username}` : user.phone, date: Date.now(), unread: 0 } }) || []} /> {!searchAccounts && } {searchMessages && !searchMessageList?.messages?.length && } { const user = message.peerId?.userId ? searchMessageList?.users.find((user: any) => user.id === message.peerId?.userId) : null const chat = message.peerId?.chatId ? searchMessageList?.chats.find((chat: any) => chat.id === message.peerId?.chatId) : null const title = user ? `${user.firstName || ''} ${user.lastName || ''}`.trim() : chat?.title || '' return { id: `${user ? 'user' : 'chat'}/${message.peerId?.userId || message.peerId?.chatId}${user?.accessHash || chat?.accessHash || chat?.migratedTo?.accessHash ? `?accessHash=${user?.accessHash || chat?.accessHash || chat?.migratedTo?.accessHash}` : '?t=1'}`, key: message.id, avatar: `${apiUrl}/messages/${user ? 'user' : 'chat'}/${message.peerId?.userId || message.peerId?.chatId}/avatar.jpg${user?.accessHash || chat?.accessHash || chat?.migratedTo?.accessHash ? `?accessHash=${user?.accessHash || chat?.accessHash || chat?.migratedTo?.accessHash}` : '?t=1'}`, alt: title?.split(' ')?.map((word: string) => word[0]).slice(0, 2).join('').toUpperCase(), title: title, subtitle: message.message || 'Send Media', date: message.date * 1000, unread: 0 } }) || []} /> {!searchMessages && } {searchGlobal && !searcGlobalList?.messages?.length && } { const user = message.peerId?.userId ? searcGlobalList?.users.find((user: any) => user.id === message.peerId?.userId) : null const channel = message.peerId?.channelId || message.peerId?.chatId ? searcGlobalList?.chats.find((channel: any) => channel.id === (message.peerId?.channelId || message.peerId?.chatId)) : null const title = user ? `${user.firstName || ''} ${user.lastName || ''}`.trim() : channel?.title || '' return { id: `${user ? 'user' : 'channel'}/${message.peerId?.userId || message.peerId?.channelId}${user?.accessHash || channel?.accessHash || channel?.migratedTo?.accessHash ? `?accessHash=${user?.accessHash || channel?.accessHash || channel?.migratedTo?.accessHash}` : '?t=1'}`, key: message.id, avatar: `${apiUrl}/messages/${user ? 'user' : message.peerId?.chatId ? 'chat' : 'channel'}/${message.peerId?.userId || message.peerId?.channelId || message.peerId?.chatId}/avatar.jpg${user?.accessHash || channel?.accessHash || channel?.migratedTo?.accessHash ? `?accessHash=${user?.accessHash || channel?.accessHash || channel?.migratedTo?.accessHash}` : '?t=1'}`, alt: title?.split(' ')?.map((word: string) => word[0]).slice(0, 2).join('').toUpperCase(), title: title, subtitle: message.message || 'Send Media', date: message.date * 1000, unread: 0 } }) || []} /> {!searchGlobal && } } {!q && !message && <> } dataSource={chatList?.sort((a: any, b: any) => b.pinned === a.pinned ? b.date - a.date : b.pinned - a.pinned).map((dialog: any) => { const peerType = dialog.isUser ? 'user' : dialog.isChannel ? 'channel' : 'chat' return { id: `${peerType}/${dialog.entity?.id}${dialog.entity?.accessHash ? `?accessHash=${dialog.entity?.accessHash}` : '?t=1'}`, key: dialog.id, avatar: `${apiUrl}/dialogs/${peerType}/${dialog.entity?.id}/avatar.jpg${dialog.entity?.accessHash ? `?accessHash=${dialog.entity?.accessHash}` : '?t=1'}`, alt: dialog.title?.split(' ')?.map((word: string) => word[0]).slice(0, 2).join('').toUpperCase(), title: me?.user.tg_id == dialog.entity?.id ? 'Saved Messages' : dialog.title, subtitle: dialog.message.message || 'Unknown message', date: dialog.date * 1000, unread: dialog.dialog.unreadCount } }) || []} renderItem={(item: any) => openMessage(item)} />} /> } } {message && {messageReply?.message &&