Compare commits
12 Commits
ab1ef2c30b
...
ba71d66db8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba71d66db8 | ||
|
|
af55143292 | ||
|
|
b824186c37 | ||
|
|
5034eb1da5 | ||
|
|
5e44a273fc | ||
|
|
484381c6e5 | ||
|
|
349e0933c3 | ||
|
|
08556c9d40 | ||
|
|
687bc7a9aa | ||
|
|
5a34054024 | ||
|
|
306bfa2b82 | ||
|
|
506790aefa |
@@ -1,6 +1,3 @@
|
|||||||
type ErrorCausedBy =
|
|
||||||
'NOT_IN_THIS_CHAT_MEMBER_LIST'
|
|
||||||
|
|
||||||
type ApiCallbackMessage = {
|
type ApiCallbackMessage = {
|
||||||
msg: string,
|
msg: string,
|
||||||
/**
|
/**
|
||||||
@@ -14,6 +11,5 @@ type ApiCallbackMessage = {
|
|||||||
*/
|
*/
|
||||||
code: 200 | 400 | 401 | 403 | 404 | 500 | 501 | -1,
|
code: 200 | 400 | 401 | 403 | 404 | 500 | 501 | -1,
|
||||||
data?: { [key: string]: unknown },
|
data?: { [key: string]: unknown },
|
||||||
caused_by?: ErrorCausedBy,
|
|
||||||
}
|
}
|
||||||
export default ApiCallbackMessage
|
export default ApiCallbackMessage
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export type CallMethod =
|
|||||||
"User.getMyRecentChats" |
|
"User.getMyRecentChats" |
|
||||||
|
|
||||||
"Chat.getInfo" |
|
"Chat.getInfo" |
|
||||||
|
|
||||||
"Chat.updateSettings" |
|
"Chat.updateSettings" |
|
||||||
|
|
||||||
"Chat.createGroup" |
|
"Chat.createGroup" |
|
||||||
@@ -25,6 +25,10 @@ export type CallMethod =
|
|||||||
"Chat.getIdForPrivate" |
|
"Chat.getIdForPrivate" |
|
||||||
"Chat.getAnotherUserIdFromPrivate" |
|
"Chat.getAnotherUserIdFromPrivate" |
|
||||||
|
|
||||||
|
"Chat.processJoinRequest" |
|
||||||
|
"Chat.sendJoinRequest" |
|
||||||
|
"Chat.getJoinRequests" |
|
||||||
|
|
||||||
"Chat.sendMessage" |
|
"Chat.sendMessage" |
|
||||||
"Chat.getMessageHistory" |
|
"Chat.getMessageHistory" |
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class Client {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
static invoke(method: CallMethod, args: object = {}, timeout: number = 5000, refreshAndRetryLimit: number = 3, forceRefreshAndRetry: boolean = false): Promise<ApiCallbackMessage> {
|
static invoke(method: CallMethod, args: object = {}, timeout: number = 10000, refreshAndRetryLimit: number = 3, forceRefreshAndRetry: boolean = false): Promise<ApiCallbackMessage> {
|
||||||
// 在 未初始化 / 未建立连接且调用非可调用接口 的时候进行延迟
|
// 在 未初始化 / 未建立连接且调用非可调用接口 的时候进行延迟
|
||||||
if (this.socket == null || (!this.connected && !CallableMethodBeforeAuth.includes(method))) {
|
if (this.socket == null || (!this.connected && !CallableMethodBeforeAuth.includes(method))) {
|
||||||
return new Promise((reslove) => {
|
return new Promise((reslove) => {
|
||||||
@@ -89,7 +89,7 @@ class Client {
|
|||||||
})
|
})
|
||||||
return re.data?.access_token as string
|
return re.data?.access_token as string
|
||||||
}
|
}
|
||||||
static async auth(token: string, timeout: number = 5000) {
|
static async auth(token: string, timeout: number) {
|
||||||
const re = await this.invoke("User.auth", {
|
const re = await this.invoke("User.auth", {
|
||||||
access_token: token
|
access_token: token
|
||||||
}, timeout, 1, true)
|
}, timeout, 1, true)
|
||||||
|
|||||||
@@ -7,5 +7,8 @@ export default class Chat {
|
|||||||
declare avatar?: string
|
declare avatar?: string
|
||||||
declare settings?: { [key: string]: unknown }
|
declare settings?: { [key: string]: unknown }
|
||||||
|
|
||||||
|
declare is_member: boolean
|
||||||
|
declare is_admin: boolean
|
||||||
|
|
||||||
[key: string]: unknown
|
[key: string]: unknown
|
||||||
}
|
}
|
||||||
|
|||||||
8
client/api/client_data/JoinRequest.ts
Normal file
8
client/api/client_data/JoinRequest.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default class JoinRequest {
|
||||||
|
declare user_id: string
|
||||||
|
declare title: string
|
||||||
|
declare avatar?: string
|
||||||
|
declare reason?: string
|
||||||
|
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ import Preference from '../preference/Preference.tsx'
|
|||||||
import GroupSettings from "../../api/client_data/GroupSettings.ts"
|
import GroupSettings from "../../api/client_data/GroupSettings.ts"
|
||||||
import PreferenceUpdater from "../preference/PreferenceUpdater.ts"
|
import PreferenceUpdater from "../preference/PreferenceUpdater.ts"
|
||||||
import SystemMessage from "./SystemMessage.tsx"
|
import SystemMessage from "./SystemMessage.tsx"
|
||||||
|
import JoinRequestsList from "./JoinRequestsList.tsx";
|
||||||
|
|
||||||
interface Args extends React.HTMLAttributes<HTMLElement> {
|
interface Args extends React.HTMLAttributes<HTMLElement> {
|
||||||
target: string
|
target: string
|
||||||
@@ -63,7 +64,9 @@ const markedInstance = new marked.Marked({
|
|||||||
export default function ChatFragment({ target, showReturnButton, onReturnButtonClicked, openChatInfoDialog, openUserInfoDialog, ...props }: Args) {
|
export default function ChatFragment({ target, showReturnButton, onReturnButtonClicked, openChatInfoDialog, openUserInfoDialog, ...props }: Args) {
|
||||||
const [messagesList, setMessagesList] = React.useState([] as Message[])
|
const [messagesList, setMessagesList] = React.useState([] as Message[])
|
||||||
const [chatInfo, setChatInfo] = React.useState({
|
const [chatInfo, setChatInfo] = React.useState({
|
||||||
title: '加载中...'
|
title: '加载中...',
|
||||||
|
is_member: true,
|
||||||
|
is_admin: true,
|
||||||
} as Chat)
|
} as Chat)
|
||||||
|
|
||||||
const [tabItemSelected, setTabItemSelected] = React.useState('None')
|
const [tabItemSelected, setTabItemSelected] = React.useState('None')
|
||||||
@@ -80,13 +83,15 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
|
|||||||
})
|
})
|
||||||
if (re.code != 200)
|
if (re.code != 200)
|
||||||
return target != '' && checkApiSuccessOrSncakbar(re, "获取对话信息失败")
|
return target != '' && checkApiSuccessOrSncakbar(re, "获取对话信息失败")
|
||||||
setChatInfo(re.data as Chat)
|
const chatInfo = re.data as Chat
|
||||||
|
setChatInfo(chatInfo)
|
||||||
|
|
||||||
await loadMore()
|
if (chatInfo.is_member)
|
||||||
|
await loadMore()
|
||||||
|
|
||||||
setTabItemSelected("Chat")
|
setTabItemSelected(chatInfo.is_member ? "Chat" : "RequestJoin")
|
||||||
if (re.data!.type == 'group') {
|
if (re.data!.type == 'group') {
|
||||||
groupPreferenceStore.setState(re.data!.settings as GroupSettings)
|
groupPreferenceStore.setState(chatInfo.settings as GroupSettings)
|
||||||
}
|
}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
chatPanelRef.current!.scrollTo({
|
chatPanelRef.current!.scrollTo({
|
||||||
@@ -104,12 +109,8 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
|
|||||||
page: page.current,
|
page: page.current,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (re.caused_by == 'NOT_IN_THIS_CHAT_MEMBER_LIST')
|
if (checkApiSuccessOrSncakbar(re, "拉取对话记录失败"))
|
||||||
return setMessagesList([{
|
return
|
||||||
text: '您未在群成员之中, 请等待管理员审批...',
|
|
||||||
}] as Message[])
|
|
||||||
else if(checkApiSuccessOrSncakbar(re, "拉取对话记录失败"))
|
|
||||||
return
|
|
||||||
const returnMsgs = (re.data!.messages as Message[]).reverse()
|
const returnMsgs = (re.data!.messages as Message[]).reverse()
|
||||||
page.current++
|
page.current++
|
||||||
if (returnMsgs.length == 0) {
|
if (returnMsgs.length == 0) {
|
||||||
@@ -253,10 +254,13 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
|
|||||||
marginRight: '5px',
|
marginRight: '5px',
|
||||||
}}></mdui-button-icon>
|
}}></mdui-button-icon>
|
||||||
}
|
}
|
||||||
<mdui-tab value="Chat">{
|
{
|
||||||
chatInfo.title
|
chatInfo.is_member ? <>
|
||||||
}</mdui-tab>
|
<mdui-tab value="Chat">{chatInfo.title}</mdui-tab>
|
||||||
{chatInfo.type == 'group' && <mdui-tab value="NewMemberRequests">入群申请</mdui-tab>}
|
{chatInfo.type == 'group' && chatInfo.is_admin && <mdui-tab value="NewMemberRequests">加入请求</mdui-tab>}
|
||||||
|
</>
|
||||||
|
: <mdui-tab value="RequestJoin">{chatInfo.title}</mdui-tab>
|
||||||
|
}
|
||||||
<mdui-tab value="Settings">设置</mdui-tab>
|
<mdui-tab value="Settings">设置</mdui-tab>
|
||||||
<mdui-tab value="None" style={{ display: 'none' }}></mdui-tab>
|
<mdui-tab value="None" style={{ display: 'none' }}></mdui-tab>
|
||||||
<div style={{
|
<div style={{
|
||||||
@@ -268,11 +272,35 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
|
|||||||
marginRight: '5px',
|
marginRight: '5px',
|
||||||
}}></mdui-button-icon>
|
}}></mdui-button-icon>
|
||||||
|
|
||||||
|
<mdui-tab-panel slot="panel" value="RequestJoin" style={{
|
||||||
|
display: tabItemSelected == "RequestJoin" ? "flex" : "none",
|
||||||
|
flexDirection: "column",
|
||||||
|
height: "100%",
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}>
|
||||||
|
<div>
|
||||||
|
<mdui-button onClick={async () => {
|
||||||
|
const re = await Client.invoke("Chat.sendJoinRequest", {
|
||||||
|
token: data.access_token,
|
||||||
|
target: target,
|
||||||
|
})
|
||||||
|
if (re.code != 200)
|
||||||
|
return checkApiSuccessOrSncakbar(re, "发送加入请求失败")
|
||||||
|
|
||||||
|
snackbar({
|
||||||
|
message: '发送成功!',
|
||||||
|
placement: 'top',
|
||||||
|
})
|
||||||
|
}}>请求加入对话</mdui-button>
|
||||||
|
</div>
|
||||||
|
</mdui-tab-panel>
|
||||||
<mdui-tab-panel slot="panel" value="Chat" ref={chatPanelRef} style={{
|
<mdui-tab-panel slot="panel" value="Chat" ref={chatPanelRef} style={{
|
||||||
display: tabItemSelected == "Chat" ? "flex" : "none",
|
display: tabItemSelected == "Chat" ? "flex" : "none",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
}} onScroll={async (e) => {
|
}} onScroll={async (e) => {
|
||||||
|
if (!chatInfo.is_member) return
|
||||||
const scrollTop = (e.target as HTMLDivElement).scrollTop
|
const scrollTop = (e.target as HTMLDivElement).scrollTop
|
||||||
if (scrollTop == 0 && !showLoadingMoreMessagesTip) {
|
if (scrollTop == 0 && !showLoadingMoreMessagesTip) {
|
||||||
setShowLoadingMoreMessagesTip(true)
|
setShowLoadingMoreMessagesTip(true)
|
||||||
@@ -456,7 +484,7 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
|
|||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
}}>
|
}}>
|
||||||
未制作
|
{tabItemSelected == "NewMemberRequests" && <JoinRequestsList target={target} />}
|
||||||
</mdui-tab-panel>
|
</mdui-tab-panel>
|
||||||
}
|
}
|
||||||
<mdui-tab-panel slot="panel" value="Settings" style={{
|
<mdui-tab-panel slot="panel" value="Settings" style={{
|
||||||
@@ -472,7 +500,7 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
|
|||||||
<Preference
|
<Preference
|
||||||
title="群组成员列表"
|
title="群组成员列表"
|
||||||
icon="group"
|
icon="group"
|
||||||
disabled={true}
|
disabled={true || !chatInfo.is_admin}
|
||||||
description="别看了, 还没做" />
|
description="别看了, 还没做" />
|
||||||
<PreferenceHeader
|
<PreferenceHeader
|
||||||
title="入群设定" />
|
title="入群设定" />
|
||||||
@@ -480,13 +508,14 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
|
|||||||
title="允许入群"
|
title="允许入群"
|
||||||
icon="person_add"
|
icon="person_add"
|
||||||
id="allow_new_member_join"
|
id="allow_new_member_join"
|
||||||
|
disabled={!chatInfo.is_admin}
|
||||||
state={groupPreferenceStore.state.allow_new_member_join || false} />
|
state={groupPreferenceStore.state.allow_new_member_join || false} />
|
||||||
<SwitchPreference
|
<SwitchPreference
|
||||||
title="允许成员邀请"
|
title="允许成员邀请"
|
||||||
description="目前压根没有这项功能, 甚至还不能查看成员列表, 以后再说吧"
|
description="目前压根没有这项功能, 甚至还不能查看成员列表, 以后再说吧"
|
||||||
id="allow_new_member_from_invitation"
|
id="allow_new_member_from_invitation"
|
||||||
icon="_"
|
icon="_"
|
||||||
disabled={true}
|
disabled={true || !chatInfo.is_admin}
|
||||||
state={groupPreferenceStore.state.allow_new_member_from_invitation || false} />
|
state={groupPreferenceStore.state.allow_new_member_from_invitation || false} />
|
||||||
<SelectPreference
|
<SelectPreference
|
||||||
title="入群验证方式"
|
title="入群验证方式"
|
||||||
@@ -497,7 +526,7 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
|
|||||||
allowed_by_admin: "只需要管理员批准 (WIP)",
|
allowed_by_admin: "只需要管理员批准 (WIP)",
|
||||||
answered_and_allowed_by_admin: "需要回答问题并获得管理员批准 (WIP)",
|
answered_and_allowed_by_admin: "需要回答问题并获得管理员批准 (WIP)",
|
||||||
}}
|
}}
|
||||||
disabled={!groupPreferenceStore.state.allow_new_member_join}
|
disabled={!chatInfo.is_admin || !groupPreferenceStore.state.allow_new_member_join}
|
||||||
state={groupPreferenceStore.state.new_member_join_method || 'disabled'} />
|
state={groupPreferenceStore.state.new_member_join_method || 'disabled'} />
|
||||||
{
|
{
|
||||||
groupPreferenceStore.state.new_member_join_method == 'answered_and_allowed_by_admin'
|
groupPreferenceStore.state.new_member_join_method == 'answered_and_allowed_by_admin'
|
||||||
@@ -507,7 +536,7 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
|
|||||||
id="answered_and_allowed_by_admin_question"
|
id="answered_and_allowed_by_admin_question"
|
||||||
description="WIP"
|
description="WIP"
|
||||||
state={groupPreferenceStore.state.answered_and_allowed_by_admin_question || ''}
|
state={groupPreferenceStore.state.answered_and_allowed_by_admin_question || ''}
|
||||||
disabled={true} />
|
disabled={true || !chatInfo.is_admin} />
|
||||||
}
|
}
|
||||||
</PreferenceUpdater.Provider>
|
</PreferenceUpdater.Provider>
|
||||||
</PreferenceLayout>
|
</PreferenceLayout>
|
||||||
|
|||||||
104
client/ui/chat/JoinRequestsList.tsx
Normal file
104
client/ui/chat/JoinRequestsList.tsx
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import { TextField } from "mdui"
|
||||||
|
import RecentChat from "../../api/client_data/RecentChat.ts"
|
||||||
|
import useEventListener from "../useEventListener.ts"
|
||||||
|
import RecentsListItem from "./JoinRequestsListItem.tsx"
|
||||||
|
import React from "react"
|
||||||
|
import useAsyncEffect from "../useAsyncEffect.ts"
|
||||||
|
import Client from "../../api/Client.ts"
|
||||||
|
import { checkApiSuccessOrSncakbar } from "../snackbar.ts"
|
||||||
|
import data from "../../Data.ts"
|
||||||
|
import EventBus from "../../EventBus.ts"
|
||||||
|
import isMobileUI from "../isMobileUI.ts"
|
||||||
|
import JoinRequest from "../../api/client_data/JoinRequest.ts"
|
||||||
|
import JoinRequestsListItem from "./JoinRequestsListItem.tsx";
|
||||||
|
|
||||||
|
interface Args extends React.HTMLAttributes<HTMLElement> {
|
||||||
|
target: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function JoinRequestsList({
|
||||||
|
target,
|
||||||
|
...props
|
||||||
|
}: Args) {
|
||||||
|
const searchRef = React.useRef<HTMLElement>(null)
|
||||||
|
const [searchText, setSearchText] = React.useState('')
|
||||||
|
const [updateJoinRequests, setUpdateJoinRequests] = React.useState<JoinRequest[]>([])
|
||||||
|
|
||||||
|
useEventListener(searchRef, 'input', (e) => {
|
||||||
|
setSearchText((e.target as unknown as TextField).value)
|
||||||
|
})
|
||||||
|
|
||||||
|
useAsyncEffect(async () => {
|
||||||
|
async function updateJoinRequests() {
|
||||||
|
const re = await Client.invoke("Chat.getJoinRequests", {
|
||||||
|
token: data.access_token,
|
||||||
|
target: target,
|
||||||
|
})
|
||||||
|
if (re.code != 200)
|
||||||
|
return checkApiSuccessOrSncakbar(re, "获取加入请求列表失败")
|
||||||
|
|
||||||
|
setUpdateJoinRequests(re.data!.join_requests as JoinRequest[])
|
||||||
|
}
|
||||||
|
updateJoinRequests()
|
||||||
|
EventBus.on('JoinRequestsList.updateJoinRequests', () => updateJoinRequests())
|
||||||
|
setTimeout(() => updateJoinRequests(), 15 * 1000)
|
||||||
|
})
|
||||||
|
|
||||||
|
async function removeJoinRequest(userId: string) {
|
||||||
|
const re = await Client.invoke("Chat.processJoinRequest", {
|
||||||
|
token: data.access_token,
|
||||||
|
chat_id: target,
|
||||||
|
user_id: userId,
|
||||||
|
action: 'remove',
|
||||||
|
})
|
||||||
|
if (re.code != 200)
|
||||||
|
return checkApiSuccessOrSncakbar(re, "删除加入请求失败")
|
||||||
|
|
||||||
|
EventBus.emit('JoinRequestsList.updateJoinRequests')
|
||||||
|
}
|
||||||
|
async function acceptJoinRequest(userId: string) {
|
||||||
|
const re = await Client.invoke("Chat.processJoinRequest", {
|
||||||
|
token: data.access_token,
|
||||||
|
chat_id: target,
|
||||||
|
user_id: userId,
|
||||||
|
action: 'accept',
|
||||||
|
})
|
||||||
|
if (re.code != 200)
|
||||||
|
return checkApiSuccessOrSncakbar(re, "通过加入请求失败")
|
||||||
|
|
||||||
|
EventBus.emit('JoinRequestsList.updateJoinRequests')
|
||||||
|
}
|
||||||
|
|
||||||
|
return <mdui-list style={{
|
||||||
|
overflowY: 'auto',
|
||||||
|
paddingRight: '10px',
|
||||||
|
paddingLeft: '10px',
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
}} {...props}>
|
||||||
|
<mdui-text-field icon="search" type="search" clearable ref={searchRef} variant="outlined" placeholder="搜索..." style={{
|
||||||
|
marginTop: '5px',
|
||||||
|
marginBottom: '13px',
|
||||||
|
}}></mdui-text-field>
|
||||||
|
|
||||||
|
<mdui-list-item rounded style={{
|
||||||
|
width: '100%',
|
||||||
|
marginBottom: '15px',
|
||||||
|
}} icon="refresh" onClick={() => EventBus.emit('JoinRequestsList.updateJoinRequests')}>刷新</mdui-list-item>
|
||||||
|
|
||||||
|
{
|
||||||
|
updateJoinRequests.filter((joinRequest) =>
|
||||||
|
searchText == '' ||
|
||||||
|
joinRequest.title.includes(searchText) ||
|
||||||
|
joinRequest.reason?.includes(searchText) ||
|
||||||
|
joinRequest.user_id.includes(searchText)
|
||||||
|
).map((v) =>
|
||||||
|
<JoinRequestsListItem
|
||||||
|
key={v.user_id}
|
||||||
|
acceptJoinRequest={acceptJoinRequest}
|
||||||
|
removeJoinRequest={removeJoinRequest}
|
||||||
|
joinRequest={v} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</mdui-list>
|
||||||
|
}
|
||||||
41
client/ui/chat/JoinRequestsListItem.tsx
Normal file
41
client/ui/chat/JoinRequestsListItem.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { $ } from "mdui/jq"
|
||||||
|
import RecentChat from "../../api/client_data/RecentChat.ts"
|
||||||
|
import Avatar from "../Avatar.tsx"
|
||||||
|
import React from 'react'
|
||||||
|
import JoinRequest from "../../api/client_data/JoinRequest.ts"
|
||||||
|
|
||||||
|
interface Args extends React.HTMLAttributes<HTMLElement> {
|
||||||
|
joinRequest: JoinRequest
|
||||||
|
acceptJoinRequest: (userId: string) => any
|
||||||
|
removeJoinRequest: (userId: string) => any
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function JoinRequestsListItem({ joinRequest, acceptJoinRequest, removeJoinRequest }: Args) {
|
||||||
|
const { user_id, title, avatar, reason } = joinRequest
|
||||||
|
|
||||||
|
const itemRef = React.useRef<HTMLElement>(null)
|
||||||
|
React.useEffect(() => {
|
||||||
|
$(itemRef.current!.shadowRoot).find('.headline').css('margin-top', '3px')
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<mdui-list-item rounded style={{
|
||||||
|
marginTop: '3px',
|
||||||
|
marginBottom: '3px',
|
||||||
|
}} ref={itemRef}>
|
||||||
|
{title}
|
||||||
|
<Avatar src={avatar} text={title} slot="icon" />
|
||||||
|
<span slot="description"
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
display: "inline-block",
|
||||||
|
whiteSpace: "nowrap", /* 禁止换行 */
|
||||||
|
overflow: "hidden", /* 隐藏溢出内容 */
|
||||||
|
textOverflow: "ellipsis", /* 显示省略号 */
|
||||||
|
}}>请求原因: {reason || "无"}</span>
|
||||||
|
<div slot="end-icon">
|
||||||
|
<mdui-button-icon icon="check" onClick={() => acceptJoinRequest(user_id)}></mdui-button-icon>
|
||||||
|
<mdui-button-icon icon="delete" onClick={() => removeJoinRequest(user_id)}></mdui-button-icon>
|
||||||
|
</div>
|
||||||
|
</mdui-list-item>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,6 +1,3 @@
|
|||||||
type ErrorCausedBy =
|
|
||||||
'NOT_IN_THIS_CHAT_MEMBER_LIST'
|
|
||||||
|
|
||||||
type ApiCallbackMessage = {
|
type ApiCallbackMessage = {
|
||||||
msg: string,
|
msg: string,
|
||||||
/**
|
/**
|
||||||
@@ -14,6 +11,5 @@ type ApiCallbackMessage = {
|
|||||||
*/
|
*/
|
||||||
code: 200 | 400 | 401 | 403 | 404 | 500 | 501,
|
code: 200 | 400 | 401 | 403 | 404 | 500 | 501,
|
||||||
data?: { [key: string]: unknown },
|
data?: { [key: string]: unknown },
|
||||||
caused_by?: ErrorCausedBy,
|
|
||||||
}
|
}
|
||||||
export default ApiCallbackMessage
|
export default ApiCallbackMessage
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ export type CallMethod =
|
|||||||
"Chat.getIdForPrivate" |
|
"Chat.getIdForPrivate" |
|
||||||
"Chat.getAnotherUserIdFromPrivate" |
|
"Chat.getAnotherUserIdFromPrivate" |
|
||||||
|
|
||||||
|
"Chat.processJoinRequest" |
|
||||||
|
"Chat.sendJoinRequest" |
|
||||||
|
"Chat.getJoinRequests" |
|
||||||
|
|
||||||
"Chat.sendMessage" |
|
"Chat.sendMessage" |
|
||||||
"Chat.getMessageHistory" |
|
"Chat.getMessageHistory" |
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import TokenManager from "./TokenManager.ts"
|
|||||||
import ChatPrivate from "../data/ChatPrivate.ts"
|
import ChatPrivate from "../data/ChatPrivate.ts"
|
||||||
import ChatGroup from "../data/ChatGroup.ts"
|
import ChatGroup from "../data/ChatGroup.ts"
|
||||||
import GroupSettingsBean from "../data/GroupSettingsBean.ts"
|
import GroupSettingsBean from "../data/GroupSettingsBean.ts"
|
||||||
import ChatAdminLinker from "../data/ChatAdminLinker.ts"
|
|
||||||
import AdminPermissions from "../data/AdminPermissions.ts"
|
import AdminPermissions from "../data/AdminPermissions.ts"
|
||||||
|
|
||||||
export default class ChatApi extends BaseApi {
|
export default class ChatApi extends BaseApi {
|
||||||
@@ -19,63 +18,10 @@ export default class ChatApi extends BaseApi {
|
|||||||
}
|
}
|
||||||
override onInit(): void {
|
override onInit(): void {
|
||||||
/**
|
/**
|
||||||
* 獲取對話訊息
|
* ======================================================
|
||||||
* @param token 令牌
|
* 对话消息
|
||||||
* @param target 目標對話
|
* ======================================================
|
||||||
*/
|
*/
|
||||||
this.registerEvent("Chat.getInfo", (args, { deviceId }) => {
|
|
||||||
if (this.checkArgsMissing(args, ['token', 'target'])) return {
|
|
||||||
msg: "参数缺失",
|
|
||||||
code: 400,
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = TokenManager.decode(args.token as string)
|
|
||||||
if (!this.checkToken(token, deviceId)) return {
|
|
||||||
code: 401,
|
|
||||||
msg: "令牌无效",
|
|
||||||
}
|
|
||||||
|
|
||||||
const chat = Chat.findById(args.target as string)
|
|
||||||
if (chat == null) return {
|
|
||||||
code: 404,
|
|
||||||
msg: "对话不存在",
|
|
||||||
}
|
|
||||||
|
|
||||||
// 私聊
|
|
||||||
if (chat!.bean.type == 'private') {
|
|
||||||
const mine = User.findById(token.author) as User
|
|
||||||
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
msg: "成功",
|
|
||||||
data: {
|
|
||||||
id: args.target as string,
|
|
||||||
type: chat.bean.type,
|
|
||||||
title: chat.getTitle(mine),
|
|
||||||
avatar: chat.getAvatarFileHash(mine) ? "uploaded_files/" + chat.getAvatarFileHash(mine) : undefined,
|
|
||||||
settings: JSON.parse(chat.bean.settings),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (chat!.bean.type == 'group') {
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
msg: "成功",
|
|
||||||
data: {
|
|
||||||
id: args.target as string,
|
|
||||||
type: chat.bean.type,
|
|
||||||
title: chat.getTitle(),
|
|
||||||
avatar: chat.getAvatarFileHash() ? "uploaded_files/" + chat.getAvatarFileHash() : undefined,
|
|
||||||
settings: JSON.parse(chat.bean.settings),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
code: 501,
|
|
||||||
msg: "not implmented",
|
|
||||||
}
|
|
||||||
})
|
|
||||||
/**
|
/**
|
||||||
* 發送訊息
|
* 發送訊息
|
||||||
* @param token 令牌
|
* @param token 令牌
|
||||||
@@ -161,7 +107,6 @@ export default class ChatApi extends BaseApi {
|
|||||||
if (!UserChatLinker.checkUserIsLinkedToChat(token.author, chat!.bean.id)) return {
|
if (!UserChatLinker.checkUserIsLinkedToChat(token.author, chat!.bean.id)) return {
|
||||||
code: 403,
|
code: 403,
|
||||||
msg: "用户无权访问此对话",
|
msg: "用户无权访问此对话",
|
||||||
caused_by: 'NOT_IN_THIS_CHAT_MEMBER_LIST',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -211,6 +156,146 @@ export default class ChatApi extends BaseApi {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
/**
|
||||||
|
* ======================================================
|
||||||
|
* 加入对话申请
|
||||||
|
* ======================================================
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* 获取所有的加入对话申请
|
||||||
|
* @param token 令牌
|
||||||
|
* @param target ID
|
||||||
|
*/
|
||||||
|
this.registerEvent("Chat.getJoinRequests", (args, { deviceId }) => {
|
||||||
|
if (this.checkArgsMissing(args, ['token', 'target'])) return {
|
||||||
|
msg: "参数缺失",
|
||||||
|
code: 400,
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = TokenManager.decode(args.token as string)
|
||||||
|
if (!this.checkToken(token, deviceId)) return {
|
||||||
|
code: 401,
|
||||||
|
msg: "令牌无效",
|
||||||
|
}
|
||||||
|
|
||||||
|
const chat = Chat.findById(args.target as string)
|
||||||
|
if (chat == null) return {
|
||||||
|
code: 404,
|
||||||
|
msg: "对话不存在",
|
||||||
|
}
|
||||||
|
if (!chat.checkUserIsAdmin(token.author)) return {
|
||||||
|
code: 403,
|
||||||
|
msg: "没有此权限",
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
msg: '成功',
|
||||||
|
data: {
|
||||||
|
join_requests: chat.getJoinRequests().map((v) => {
|
||||||
|
const user = User.findById(v.user_id as string)
|
||||||
|
return {
|
||||||
|
user_id: user?.bean.id,
|
||||||
|
reason: v.reason,
|
||||||
|
title: user!.getNickName(),
|
||||||
|
avatar: user!.getAvatarFileHash() ? "uploaded_files/" + user!.getAvatarFileHash() : null,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* 处理加入对话申请
|
||||||
|
* @param token 令牌
|
||||||
|
*/
|
||||||
|
this.registerEvent("Chat.processJoinRequest", (args, { deviceId }) => {
|
||||||
|
if (this.checkArgsMissing(args, ['token', 'chat_id', 'user_id', 'action'])) return {
|
||||||
|
msg: "参数缺失",
|
||||||
|
code: 400,
|
||||||
|
}
|
||||||
|
const action = args.action as string
|
||||||
|
|
||||||
|
const token = TokenManager.decode(args.token as string)
|
||||||
|
if (!this.checkToken(token, deviceId)) return {
|
||||||
|
code: 401,
|
||||||
|
msg: "令牌无效",
|
||||||
|
}
|
||||||
|
|
||||||
|
const chat = Chat.findById(args.chat_id as string)
|
||||||
|
if (chat == null) return {
|
||||||
|
code: 404,
|
||||||
|
msg: "对话不存在",
|
||||||
|
}
|
||||||
|
if (!chat.checkUserIsAdmin(token.author)) return {
|
||||||
|
code: 403,
|
||||||
|
msg: "没有此权限",
|
||||||
|
}
|
||||||
|
|
||||||
|
const admin = User.findById(token.author)
|
||||||
|
|
||||||
|
if (chat.getJoinRequests().map((v) => v.user_id).indexOf(args.user_id as string) != -1) {
|
||||||
|
const user = User.findById(args.user_id as string)
|
||||||
|
if (user == null) {
|
||||||
|
chat.removeJoinRequests([
|
||||||
|
args.user_id as string,
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
if (action == 'accept') {
|
||||||
|
chat.addMembers([
|
||||||
|
args.user_id as string,
|
||||||
|
])
|
||||||
|
MessagesManager.getInstanceForChat(chat).addSystemMessage(`${user.getNickName()} 经 ${admin?.getNickName()} 批准加入了对话`)
|
||||||
|
}
|
||||||
|
if (action == 'accept' || action == 'remove')
|
||||||
|
chat.removeJoinRequests([
|
||||||
|
args.user_id as string,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
msg: '成功',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* 加入群组
|
||||||
|
* @param token 令牌
|
||||||
|
* @param target ID
|
||||||
|
*/
|
||||||
|
this.registerEvent("Chat.sendJoinRequest", (args, { deviceId }) => {
|
||||||
|
if (this.checkArgsMissing(args, ['token', 'target'])) return {
|
||||||
|
msg: "参数缺失",
|
||||||
|
code: 400,
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = TokenManager.decode(args.token as string)
|
||||||
|
if (!this.checkToken(token, deviceId)) return {
|
||||||
|
code: 401,
|
||||||
|
msg: "令牌无效",
|
||||||
|
}
|
||||||
|
|
||||||
|
const chat = Chat.findById(args.target as string)
|
||||||
|
if (chat == null) return {
|
||||||
|
code: 404,
|
||||||
|
msg: "对话不存在",
|
||||||
|
}
|
||||||
|
|
||||||
|
chat.addJoinRequest(token.author, args.reason as string)
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
msg: '成功',
|
||||||
|
data: {
|
||||||
|
chat_id: chat.bean.id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* ======================================================
|
||||||
|
* 创建对话
|
||||||
|
* ======================================================
|
||||||
|
*/
|
||||||
/**
|
/**
|
||||||
* 获取私聊的 ChatId
|
* 获取私聊的 ChatId
|
||||||
* @param token 令牌
|
* @param token 令牌
|
||||||
@@ -237,44 +322,6 @@ export default class ChatApi extends BaseApi {
|
|||||||
}
|
}
|
||||||
const chat = ChatPrivate.findOrCreateForPrivate(user, targetUser)
|
const chat = ChatPrivate.findOrCreateForPrivate(user, targetUser)
|
||||||
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
msg: '成功',
|
|
||||||
data: {
|
|
||||||
chat_id: chat.bean.id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
/**
|
|
||||||
* 加入群组
|
|
||||||
* @param token 令牌
|
|
||||||
* @param target ID
|
|
||||||
*/
|
|
||||||
this.registerEvent("Chat.requestJoinGroup", (args, { deviceId }) => {
|
|
||||||
if (this.checkArgsMissing(args, ['token', 'target'])) return {
|
|
||||||
msg: "参数缺失",
|
|
||||||
code: 400,
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = TokenManager.decode(args.token as string)
|
|
||||||
if (!this.checkToken(token, deviceId)) return {
|
|
||||||
code: 401,
|
|
||||||
msg: "令牌无效",
|
|
||||||
}
|
|
||||||
const user = User.findById(token.author) as User
|
|
||||||
|
|
||||||
const chat = Chat.findById(args.target as string)
|
|
||||||
if (chat == null) return {
|
|
||||||
code: 404,
|
|
||||||
msg: "对话不存在",
|
|
||||||
}
|
|
||||||
if (!UserChatLinker.checkUserIsLinkedToChat(token.author, chat!.bean.id)) return {
|
|
||||||
code: 403,
|
|
||||||
msg: "用户无权访问此对话",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
msg: '成功',
|
msg: '成功',
|
||||||
@@ -305,14 +352,14 @@ export default class ChatApi extends BaseApi {
|
|||||||
msg: "令牌无效",
|
msg: "令牌无效",
|
||||||
}
|
}
|
||||||
const user = User.findById(token.author) as User
|
const user = User.findById(token.author) as User
|
||||||
|
|
||||||
const haveId = args.id && (args.id as string) != ''
|
const haveId = args.id && ((args.id as string) != '')
|
||||||
if (haveId && Chat.findById(args.id as string) != null) return {
|
if (haveId && Chat.findById(args.id as string) != null) return {
|
||||||
msg: "对话 ID 已被占用",
|
msg: "对话 ID 已被占用",
|
||||||
code: 403,
|
code: 403,
|
||||||
}
|
}
|
||||||
|
|
||||||
const chat = ChatGroup.createGroup(haveId ? undefined : args.id as string)
|
const chat = ChatGroup.createGroup(haveId ? args.id as string : undefined)
|
||||||
chat.setTitle(args.title as string)
|
chat.setTitle(args.title as string)
|
||||||
chat.addMembers([
|
chat.addMembers([
|
||||||
user.bean.id,
|
user.bean.id,
|
||||||
@@ -331,6 +378,77 @@ export default class ChatApi extends BaseApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
/**
|
||||||
|
* ======================================================
|
||||||
|
* 对话信息
|
||||||
|
* ======================================================
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* 獲取對話訊息
|
||||||
|
* @param token 令牌
|
||||||
|
* @param target 目標對話
|
||||||
|
*/
|
||||||
|
this.registerEvent("Chat.getInfo", (args, { deviceId }) => {
|
||||||
|
if (this.checkArgsMissing(args, ['token', 'target'])) return {
|
||||||
|
msg: "参数缺失",
|
||||||
|
code: 400,
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = TokenManager.decode(args.token as string)
|
||||||
|
if (!this.checkToken(token, deviceId)) return {
|
||||||
|
code: 401,
|
||||||
|
msg: "令牌无效",
|
||||||
|
}
|
||||||
|
|
||||||
|
const chat = Chat.findById(args.target as string)
|
||||||
|
if (chat == null) return {
|
||||||
|
code: 404,
|
||||||
|
msg: "对话不存在",
|
||||||
|
}
|
||||||
|
|
||||||
|
// 私聊
|
||||||
|
if (chat!.bean.type == 'private') {
|
||||||
|
if (!UserChatLinker.checkUserIsLinkedToChat(token.author, chat!.bean.id)) return {
|
||||||
|
code: 403,
|
||||||
|
msg: "用户无权访问此对话",
|
||||||
|
}
|
||||||
|
const mine = User.findById(token.author) as User
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
msg: "成功",
|
||||||
|
data: {
|
||||||
|
id: args.target as string,
|
||||||
|
type: chat.bean.type,
|
||||||
|
title: chat.getTitle(mine),
|
||||||
|
avatar: chat.getAvatarFileHash(mine) ? "uploaded_files/" + chat.getAvatarFileHash(mine) : undefined,
|
||||||
|
settings: JSON.parse(chat.bean.settings),
|
||||||
|
is_member: true,
|
||||||
|
is_admin: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (chat!.bean.type == 'group') {
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
msg: "成功",
|
||||||
|
data: {
|
||||||
|
id: args.target as string,
|
||||||
|
type: chat.bean.type,
|
||||||
|
title: chat.getTitle(),
|
||||||
|
avatar: chat.getAvatarFileHash() ? "uploaded_files/" + chat.getAvatarFileHash() : undefined,
|
||||||
|
settings: JSON.parse(chat.bean.settings),
|
||||||
|
is_member: UserChatLinker.checkUserIsLinkedToChat(token.author, chat!.bean.id),
|
||||||
|
is_admin: chat.checkUserIsAdmin(token.author),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 404,
|
||||||
|
msg: "找不到对话",
|
||||||
|
}
|
||||||
|
})
|
||||||
/**
|
/**
|
||||||
* 更新设定
|
* 更新设定
|
||||||
* @param token 令牌
|
* @param token 令牌
|
||||||
@@ -349,7 +467,7 @@ export default class ChatApi extends BaseApi {
|
|||||||
msg: "令牌无效",
|
msg: "令牌无效",
|
||||||
}
|
}
|
||||||
const user = User.findById(token.author) as User
|
const user = User.findById(token.author) as User
|
||||||
|
|
||||||
const chat = Chat.findById(args.target as string)
|
const chat = Chat.findById(args.target as string)
|
||||||
if (chat == null) return {
|
if (chat == null) return {
|
||||||
code: 404,
|
code: 404,
|
||||||
@@ -357,7 +475,7 @@ export default class ChatApi extends BaseApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (chat.bean.type == 'group')
|
if (chat.bean.type == 'group')
|
||||||
if (ChatAdminLinker.checkAdminIsLinkedToChat(user.bean.id, chat.bean.id))
|
if (chat.checkUserIsAdmin(user.bean.id))
|
||||||
ChatGroup.fromChat(chat).getSettings().update(args.settings as GroupSettingsBean)
|
ChatGroup.fromChat(chat).getSettings().update(args.settings as GroupSettingsBean)
|
||||||
else
|
else
|
||||||
return {
|
return {
|
||||||
@@ -407,7 +525,7 @@ export default class ChatApi extends BaseApi {
|
|||||||
user_id: chat.getAnotherUserForPrivate(user)?.bean.id
|
user_id: chat.getAnotherUserForPrivate(user)?.bean.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: 403,
|
code: 403,
|
||||||
msg: "非私聊对话",
|
msg: "非私聊对话",
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import User from "./User.ts"
|
|||||||
import ChatType from "./ChatType.ts"
|
import ChatType from "./ChatType.ts"
|
||||||
import UserChatLinker from "./UserChatLinker.ts"
|
import UserChatLinker from "./UserChatLinker.ts"
|
||||||
import DataWrongError from '../api/DataWrongError.ts'
|
import DataWrongError from '../api/DataWrongError.ts'
|
||||||
import ChatAdminLinker from "./ChatAdminLinker.ts"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Chat.ts - Wrapper and manager
|
* Chat.ts - Wrapper and manager
|
||||||
@@ -18,12 +17,11 @@ import ChatAdminLinker from "./ChatAdminLinker.ts"
|
|||||||
* Manage the database by itself (static)
|
* Manage the database by itself (static)
|
||||||
*/
|
*/
|
||||||
export default class Chat {
|
export default class Chat {
|
||||||
static table_name: string = "Chat"
|
|
||||||
private static database: DatabaseSync = Chat.init()
|
private static database: DatabaseSync = Chat.init()
|
||||||
private static init(): DatabaseSync {
|
private static init(): DatabaseSync {
|
||||||
const db: DatabaseSync = new DatabaseSync(path.join(config.data_path, 'Chats.db'))
|
const db: DatabaseSync = new DatabaseSync(path.join(config.data_path, 'Chats.db'))
|
||||||
db.exec(`
|
db.exec(`
|
||||||
CREATE TABLE IF NOT EXISTS ${Chat.table_name} (
|
CREATE TABLE IF NOT EXISTS Chat (
|
||||||
/* 序号 */ count INTEGER PRIMARY KEY AUTOINCREMENT,
|
/* 序号 */ count INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
/* 类型 */ type TEXT NOT NULL,
|
/* 类型 */ type TEXT NOT NULL,
|
||||||
/* ID */ id TEXT NOT NULL,
|
/* ID */ id TEXT NOT NULL,
|
||||||
@@ -35,12 +33,12 @@ export default class Chat {
|
|||||||
return db
|
return db
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static findAllBeansByCondition(condition: string, ...args: SQLInputValue[]): ChatBean[] {
|
protected static findAllChatBeansByCondition(condition: string, ...args: SQLInputValue[]): ChatBean[] {
|
||||||
return this.database.prepare(`SELECT * FROM ${Chat.table_name} WHERE ${condition}`).all(...args) as unknown as ChatBean[]
|
return this.database.prepare(`SELECT * FROM Chat WHERE ${condition}`).all(...args) as unknown as ChatBean[]
|
||||||
}
|
}
|
||||||
|
|
||||||
static findById(id: string) {
|
static findById(id: string) {
|
||||||
const beans = this.findAllBeansByCondition('id = ?', id)
|
const beans = this.findAllChatBeansByCondition('id = ?', id)
|
||||||
if (beans.length == 0)
|
if (beans.length == 0)
|
||||||
return null
|
return null
|
||||||
else if (beans.length > 1)
|
else if (beans.length > 1)
|
||||||
@@ -49,12 +47,12 @@ export default class Chat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static create(chatId: string, type: ChatType) {
|
static create(chatId: string, type: ChatType) {
|
||||||
if (this.findAllBeansByCondition('id = ?', chatId).length > 0)
|
if (this.findAllChatBeansByCondition('id = ?', chatId).length > 0)
|
||||||
throw new DataWrongError(`对话 ID ${chatId} 已被使用`)
|
throw new DataWrongError(`对话 ID ${chatId} 已被使用`)
|
||||||
const chat = new Chat(
|
const chat = new Chat(
|
||||||
Chat.findAllBeansByCondition(
|
Chat.findAllChatBeansByCondition(
|
||||||
'count = ?',
|
'count = ?',
|
||||||
Chat.database.prepare(`INSERT INTO ${Chat.table_name} (
|
Chat.database.prepare(`INSERT INTO Chat (
|
||||||
type,
|
type,
|
||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
@@ -75,22 +73,101 @@ export default class Chat {
|
|||||||
declare bean: ChatBean
|
declare bean: ChatBean
|
||||||
constructor(bean: ChatBean) {
|
constructor(bean: ChatBean) {
|
||||||
this.bean = bean
|
this.bean = bean
|
||||||
|
|
||||||
|
Chat.database.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS ${this.getAdminsTableName()} (
|
||||||
|
/* 序号 */ count INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
/* 用戶 ID */ user_id TEXT NOT NULL,
|
||||||
|
/* 管理权限 */ permissions TEXT NOT NULL
|
||||||
|
);
|
||||||
|
`)
|
||||||
|
Chat.database.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS ${this.getJoinRequestsTableName()} (
|
||||||
|
/* 序号 */ count INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
/* 用戶 ID */ user_id TEXT NOT NULL,
|
||||||
|
/* 请求原因 */ reason TEXT
|
||||||
|
);
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
protected getAdminsTableName() {
|
||||||
|
return 'admins_' + this.bean.id.replaceAll('-', '_')
|
||||||
|
}
|
||||||
|
protected getJoinRequestsTableName() {
|
||||||
|
return 'join_requests_' + this.bean.id.replaceAll('-', '_')
|
||||||
}
|
}
|
||||||
setAttr(key: string, value: SQLInputValue): void {
|
setAttr(key: string, value: SQLInputValue): void {
|
||||||
Chat.database.prepare(`UPDATE ${Chat.table_name} SET ${key} = ? WHERE id = ?`).run(value, this.bean.id)
|
Chat.database.prepare(`UPDATE Chat SET ${key} = ? WHERE id = ?`).run(value, this.bean.id)
|
||||||
this.bean[key] = value
|
this.bean[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ======================================================
|
||||||
|
* 加入对话请求
|
||||||
|
* ======================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
addJoinRequest(userId: string, reason?: string) {
|
||||||
|
if (this.findAllJoinRequestsByCondition('user_id = ?', userId).length == 0)
|
||||||
|
Chat.database.prepare(`INSERT INTO ${this.getJoinRequestsTableName()} (
|
||||||
|
user_id,
|
||||||
|
reason
|
||||||
|
) VALUES (?, ?);`).run(
|
||||||
|
userId,
|
||||||
|
reason || null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
removeJoinRequests(userIds: string[]) {
|
||||||
|
userIds.forEach((userId) => Chat.database.prepare(`DELETE FROM ${this.getJoinRequestsTableName()} WHERE user_id = ?`).run(userId))
|
||||||
|
}
|
||||||
|
getJoinRequests() {
|
||||||
|
return Chat.database.prepare(`SELECT * FROM ${this.getJoinRequestsTableName()}`).all()
|
||||||
|
}
|
||||||
|
protected findAllJoinRequestsByCondition(condition: string, ...args: SQLInputValue[]) {
|
||||||
|
return Chat.database.prepare(`SELECT * FROM ${this.getAdminsTableName()} WHERE ${condition}`).all(...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ======================================================
|
||||||
|
* 对话管理员
|
||||||
|
* ======================================================
|
||||||
|
*/
|
||||||
|
|
||||||
addAdmin(userId: string, permission: string[] | string) {
|
addAdmin(userId: string, permission: string[] | string) {
|
||||||
ChatAdminLinker.linkAdminAndChat(userId, this.bean.id)
|
if (!this.checkUserIsAdmin(userId))
|
||||||
|
Chat.database.prepare(`INSERT INTO ${this.getAdminsTableName()} (
|
||||||
|
user_id,
|
||||||
|
permissions
|
||||||
|
) VALUES (?, ?);`).run(
|
||||||
|
userId,
|
||||||
|
'[]'
|
||||||
|
)
|
||||||
this.setAdminPermissions(userId, permission)
|
this.setAdminPermissions(userId, permission)
|
||||||
}
|
}
|
||||||
|
checkUserIsAdmin(userId: string) {
|
||||||
|
return this.findAllAdminsByCondition('user_id = ?', userId).length != 0
|
||||||
|
}
|
||||||
|
getAdmins() {
|
||||||
|
return Chat.database.prepare(`SELECT * FROM ${this.getAdminsTableName()}`).all().map((v) => v.user_id) as string[]
|
||||||
|
}
|
||||||
|
protected findAllAdminsByCondition(condition: string, ...args: SQLInputValue[]) {
|
||||||
|
return Chat.database.prepare(`SELECT * FROM ${this.getAdminsTableName()} WHERE ${condition}`).all(...args)
|
||||||
|
}
|
||||||
setAdminPermissions(userId: string, permission: string[] | string) {
|
setAdminPermissions(userId: string, permission: string[] | string) {
|
||||||
ChatAdminLinker.updatePermissions(userId, this.bean.id, permission instanceof Array ? JSON.stringify(permission) : permission)
|
Chat.database.prepare(`UPDATE ${this.getAdminsTableName()} SET permissions = ? WHERE user_id = ?`).run(
|
||||||
|
userId,
|
||||||
|
permission instanceof Array ? JSON.stringify(permission) : permission
|
||||||
|
)
|
||||||
}
|
}
|
||||||
removeAdmins(userIds: string[]) {
|
removeAdmins(userIds: string[]) {
|
||||||
userIds.forEach((v) => ChatAdminLinker.unlinkAdminAndChat(v, this.bean.id))
|
userIds.forEach((v) => Chat.database.prepare(`DELETE FROM ${this.getAdminsTableName()} WHERE user_id = ?`).run(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ======================================================
|
||||||
|
* 对话成员
|
||||||
|
* ======================================================
|
||||||
|
*/
|
||||||
|
|
||||||
getMembersList() {
|
getMembersList() {
|
||||||
return UserChatLinker.getChatMembers(this.bean.id)
|
return UserChatLinker.getChatMembers(this.bean.id)
|
||||||
}
|
}
|
||||||
@@ -100,6 +177,13 @@ export default class Chat {
|
|||||||
removeMembers(userIds: string[]) {
|
removeMembers(userIds: string[]) {
|
||||||
userIds.forEach((v) => UserChatLinker.unlinkUserAndChat(v, this.bean.id))
|
userIds.forEach((v) => UserChatLinker.unlinkUserAndChat(v, this.bean.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ======================================================
|
||||||
|
* 对话信息
|
||||||
|
* ======================================================
|
||||||
|
*/
|
||||||
|
|
||||||
getAnotherUserForPrivate(userMySelf: User) {
|
getAnotherUserForPrivate(userMySelf: User) {
|
||||||
const members = this.getMembersList()
|
const members = this.getMembersList()
|
||||||
const user_a_id = members[0]
|
const user_a_id = members[0]
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
import { DatabaseSync } from "node:sqlite"
|
|
||||||
import path from 'node:path'
|
|
||||||
|
|
||||||
import config from "../config.ts"
|
|
||||||
import { SQLInputValue } from "node:sqlite"
|
|
||||||
export default class ChatAdminLinker {
|
|
||||||
static database: DatabaseSync = this.init()
|
|
||||||
|
|
||||||
private static init(): DatabaseSync {
|
|
||||||
const db: DatabaseSync = new DatabaseSync(path.join(config.data_path, 'ChatAdminLinker.db'))
|
|
||||||
db.exec(`
|
|
||||||
CREATE TABLE IF NOT EXISTS ChatAdminLinker (
|
|
||||||
/* 序号 */ count INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
/* 用戶 ID */ user_id TEXT NOT NULL,
|
|
||||||
/* Chat ID */ chat_id TEXT NOT NULL,
|
|
||||||
/* 管理权限 */ permissions TEXT NOT NULL
|
|
||||||
);
|
|
||||||
`)
|
|
||||||
return db
|
|
||||||
}
|
|
||||||
|
|
||||||
static linkAdminAndChat(userId: string, chatId: string) {
|
|
||||||
if (!this.checkAdminIsLinkedToChat(userId, chatId))
|
|
||||||
this.database.prepare(`INSERT INTO ChatAdminLinker (
|
|
||||||
user_id,
|
|
||||||
chat_id,
|
|
||||||
permissions
|
|
||||||
) VALUES (?, ?, ?);`).run(
|
|
||||||
userId,
|
|
||||||
chatId,
|
|
||||||
'[]'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
static updatePermissions(userId: string, chatId: string, permissions: string) {
|
|
||||||
this.database.prepare(`UPDATE ChatAdminLinker SET permissions = ? WHERE user_id = ? AND chat_id = ?`).run(permissions, userId, chatId)
|
|
||||||
}
|
|
||||||
static unlinkAdminAndChat(userId: string, chatId: string) {
|
|
||||||
this.database.prepare(`DELETE FROM ChatAdminLinker WHERE user_id = ? AND chat_id = ?`).run(userId, chatId)
|
|
||||||
}
|
|
||||||
static checkAdminIsLinkedToChat(userId: string, chatId: string) {
|
|
||||||
return this.findAllByCondition('user_id = ? AND chat_id = ?', userId, chatId).length != 0
|
|
||||||
}
|
|
||||||
static getChatAdmins(chatId: string) {
|
|
||||||
return this.findAllByCondition('chat_id = ?', chatId).map((v) => v.user_id) as string[]
|
|
||||||
}
|
|
||||||
protected static findAllByCondition(condition: string, ...args: SQLInputValue[]) {
|
|
||||||
return this.database.prepare(`SELECT * FROM ChatAdminLinker WHERE ${condition}`).all(...args)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user