feat: 加入对话请求
This commit is contained in:
@@ -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" |
|
||||||
|
|
||||||
|
|||||||
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
|
||||||
@@ -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,6 +272,29 @@ 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",
|
||||||
@@ -457,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={{
|
||||||
|
|||||||
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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" |
|
||||||
|
|
||||||
|
|||||||
@@ -18,71 +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') {
|
|
||||||
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 令牌
|
||||||
@@ -217,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 令牌
|
||||||
@@ -243,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: '成功',
|
||||||
@@ -312,13 +353,13 @@ export default class ChatApi extends BaseApi {
|
|||||||
}
|
}
|
||||||
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,
|
||||||
@@ -337,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 令牌
|
||||||
|
|||||||
@@ -17,12 +17,11 @@ import DataWrongError from '../api/DataWrongError.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,
|
||||||
@@ -30,24 +29,16 @@ export default class Chat {
|
|||||||
/* 头像 */ avatar BLOB,
|
/* 头像 */ avatar BLOB,
|
||||||
/* 设置 */ settings TEXT NOT NULL
|
/* 设置 */ settings TEXT NOT NULL
|
||||||
);
|
);
|
||||||
`)
|
|
||||||
db.exec(`
|
|
||||||
CREATE TABLE IF NOT EXISTS ChatAdmin (
|
|
||||||
/* 序号 */ count INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
/* 用戶 ID */ user_id TEXT NOT NULL,
|
|
||||||
/* Chat ID */ chat_id TEXT NOT NULL,
|
|
||||||
/* 管理权限 */ permissions TEXT NOT NULL
|
|
||||||
);
|
|
||||||
`)
|
`)
|
||||||
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)
|
||||||
@@ -56,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,
|
||||||
@@ -82,46 +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) {
|
||||||
if (!this.checkUserIsAdmin(userId))
|
if (!this.checkUserIsAdmin(userId))
|
||||||
Chat.database.prepare(`INSERT INTO ChatAdmin (
|
Chat.database.prepare(`INSERT INTO ${this.getAdminsTableName()} (
|
||||||
user_id,
|
user_id,
|
||||||
chat_id,
|
|
||||||
permissions
|
permissions
|
||||||
) VALUES (?, ?, ?);`).run(
|
) VALUES (?, ?);`).run(
|
||||||
userId,
|
userId,
|
||||||
this.bean.id,
|
|
||||||
'[]'
|
'[]'
|
||||||
)
|
)
|
||||||
this.setAdminPermissions(userId, permission)
|
this.setAdminPermissions(userId, permission)
|
||||||
}
|
}
|
||||||
|
|
||||||
checkUserIsAdmin(userId: string) {
|
checkUserIsAdmin(userId: string) {
|
||||||
return Chat.findAllAdminsByCondition('user_id = ? AND chat_id = ?', userId, this.bean.id).length != 0
|
return this.findAllAdminsByCondition('user_id = ?', userId).length != 0
|
||||||
}
|
}
|
||||||
getAdmins() {
|
getAdmins() {
|
||||||
return Chat.findAllAdminsByCondition('chat_id = ?', this.bean.id).map((v) => v.user_id) as string[]
|
return Chat.database.prepare(`SELECT * FROM ${this.getAdminsTableName()}`).all().map((v) => v.user_id) as string[]
|
||||||
}
|
}
|
||||||
protected static findAllAdminsByCondition(condition: string, ...args: SQLInputValue[]) {
|
protected findAllAdminsByCondition(condition: string, ...args: SQLInputValue[]) {
|
||||||
return this.database.prepare(`SELECT * FROM ChatAdmin WHERE ${condition}`).all(...args)
|
return Chat.database.prepare(`SELECT * FROM ${this.getAdminsTableName()} WHERE ${condition}`).all(...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
setAdminPermissions(userId: string, permission: string[] | string) {
|
setAdminPermissions(userId: string, permission: string[] | string) {
|
||||||
Chat.database.prepare(`UPDATE ChatAdmin SET permissions = ? WHERE user_id = ? AND chat_id = ?`).run(
|
Chat.database.prepare(`UPDATE ${this.getAdminsTableName()} SET permissions = ? WHERE user_id = ?`).run(
|
||||||
userId,
|
userId,
|
||||||
this.bean.id,
|
|
||||||
permission instanceof Array ? JSON.stringify(permission) : permission
|
permission instanceof Array ? JSON.stringify(permission) : permission
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
removeAdmins(userIds: string[]) {
|
removeAdmins(userIds: string[]) {
|
||||||
userIds.forEach((v) => Chat.database.prepare(`DELETE FROM ChatAdmin WHERE user_id = ? AND chat_id = ?`).run(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)
|
||||||
}
|
}
|
||||||
@@ -131,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]
|
||||||
|
|||||||
Reference in New Issue
Block a user