Compare commits
8 Commits
fabdd192dd
...
38c28c3fb6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38c28c3fb6 | ||
|
|
0df1149618 | ||
|
|
aeafcb5b97 | ||
|
|
324962b0fc | ||
|
|
f5f3774daf | ||
|
|
e666dc573a | ||
|
|
11362a5689 | ||
|
|
7c7e641d1f |
@@ -21,6 +21,7 @@ const _data_cached = JSON.parse(_dec)
|
|||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
data: {
|
data: {
|
||||||
|
refresh_token?: string
|
||||||
split_sizes: number[]
|
split_sizes: number[]
|
||||||
apply(): void
|
apply(): void
|
||||||
access_token?: string
|
access_token?: string
|
||||||
@@ -29,6 +30,7 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-ignore: 忽略...
|
||||||
// deno-lint-ignore no-window
|
// deno-lint-ignore no-window
|
||||||
(window.data == null) && (window.data = new Proxy({
|
(window.data == null) && (window.data = new Proxy({
|
||||||
apply() {}
|
apply() {}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export type CallMethod =
|
|||||||
"User.auth" |
|
"User.auth" |
|
||||||
"User.register" |
|
"User.register" |
|
||||||
"User.login" |
|
"User.login" |
|
||||||
|
"User.refreshAccessToken" |
|
||||||
|
|
||||||
"User.setAvatar" |
|
"User.setAvatar" |
|
||||||
"User.updateProfile" |
|
"User.updateProfile" |
|
||||||
@@ -17,6 +18,10 @@ export type CallMethod =
|
|||||||
|
|
||||||
"Chat.getInfo" |
|
"Chat.getInfo" |
|
||||||
|
|
||||||
|
"Chat.updateSettings" |
|
||||||
|
|
||||||
|
"Chat.createGroup" |
|
||||||
|
|
||||||
"Chat.getIdForPrivate" |
|
"Chat.getIdForPrivate" |
|
||||||
"Chat.getAnotherUserIdFromPrivate" |
|
"Chat.getAnotherUserIdFromPrivate" |
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class Client {
|
|||||||
})
|
})
|
||||||
this.socket!.on("connect", async () => {
|
this.socket!.on("connect", async () => {
|
||||||
this.connected = true
|
this.connected = true
|
||||||
const re = await this.auth(data.access_token)
|
const re = await this.auth(data.access_token as string)
|
||||||
if (re.code != 200)
|
if (re.code != 200)
|
||||||
checkApiSuccessOrSncakbar(re, "重连失败")
|
checkApiSuccessOrSncakbar(re, "重连失败")
|
||||||
})
|
})
|
||||||
@@ -42,7 +42,7 @@ class Client {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
static invoke(method: CallMethod, args: unknown = {}, timeout: number = 5000, refreshAndRetryLimit: number = 3, forceRefreshAndRetry: boolean = false): Promise<ApiCallbackMessage> {
|
static invoke(method: CallMethod, args: object = {}, timeout: number = 5000, 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) => {
|
||||||
@@ -87,7 +87,7 @@ class Client {
|
|||||||
const re = await this.invoke("User.refreshAccessToken", {
|
const re = await this.invoke("User.refreshAccessToken", {
|
||||||
refresh_token: data.refresh_token
|
refresh_token: data.refresh_token
|
||||||
})
|
})
|
||||||
return re.data?.access_token
|
return re.data?.access_token as string
|
||||||
}
|
}
|
||||||
static async auth(token: string, timeout: number = 5000) {
|
static async auth(token: string, timeout: number = 5000) {
|
||||||
const re = await this.invoke("User.auth", {
|
const re = await this.invoke("User.auth", {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export default class Chat {
|
|||||||
declare id: string
|
declare id: string
|
||||||
declare title: string
|
declare title: string
|
||||||
declare avatar?: string
|
declare avatar?: string
|
||||||
|
declare settings?: { [key: string]: unknown }
|
||||||
|
|
||||||
[key: string]: unknown
|
[key: string]: unknown
|
||||||
}
|
}
|
||||||
|
|||||||
10
client/api/client_data/GroupSettings.ts
Normal file
10
client/api/client_data/GroupSettings.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
interface GroupSettings {
|
||||||
|
allow_new_member_join?: boolean
|
||||||
|
allow_new_member_from_invitation?: boolean
|
||||||
|
new_member_join_method?: 'disabled' | 'allowed_by_admin' | 'answered_and_allowed_by_admin'
|
||||||
|
answered_and_allowed_by_admin_question?: string
|
||||||
|
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GroupSettings
|
||||||
@@ -19,8 +19,6 @@
|
|||||||
"@types/react": "npm:@types/react@18.3.1",
|
"@types/react": "npm:@types/react@18.3.1",
|
||||||
"@types/react-dom": "npm:@types/react-dom@18.3.1",
|
"@types/react-dom": "npm:@types/react-dom@18.3.1",
|
||||||
"@vitejs/plugin-react": "npm:@vitejs/plugin-react@4.7.0",
|
"@vitejs/plugin-react": "npm:@vitejs/plugin-react@4.7.0",
|
||||||
"vite-plugin-babel": "npm:vite-plugin-babel@1.3.2",
|
|
||||||
"@babel/preset-env": "npm:@babel/preset-env@7.28.3",
|
|
||||||
"react": "npm:react@18.3.1",
|
"react": "npm:react@18.3.1",
|
||||||
"react-dom": "npm:react-dom@18.3.1",
|
"react-dom": "npm:react-dom@18.3.1",
|
||||||
"vite": "npm:vite@7.0.6",
|
"vite": "npm:vite@7.0.6",
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import SwitchPreference from '../preference/SwitchPreference.tsx'
|
|||||||
import SelectPreference from '../preference/SelectPreference.tsx'
|
import SelectPreference from '../preference/SelectPreference.tsx'
|
||||||
import TextFieldPreference from '../preference/TextFieldPreference.tsx'
|
import TextFieldPreference from '../preference/TextFieldPreference.tsx'
|
||||||
import Preference from '../preference/Preference.tsx'
|
import Preference from '../preference/Preference.tsx'
|
||||||
|
import GroupSettings from "../../api/client_data/GroupSettings.ts"
|
||||||
|
|
||||||
interface Args extends React.HTMLAttributes<HTMLElement> {
|
interface Args extends React.HTMLAttributes<HTMLElement> {
|
||||||
target: string
|
target: string
|
||||||
@@ -64,6 +65,7 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
|
|||||||
} as Chat)
|
} as Chat)
|
||||||
|
|
||||||
const [tabItemSelected, setTabItemSelected] = React.useState('None')
|
const [tabItemSelected, setTabItemSelected] = React.useState('None')
|
||||||
|
const [groupPreferenceDefaultValue, setGroupPreferenceDefaultValue] = React.useState<GroupSettings>({})
|
||||||
const tabRef = React.useRef<Tab>(null)
|
const tabRef = React.useRef<Tab>(null)
|
||||||
const chatPanelRef = React.useRef<HTMLElement>(null)
|
const chatPanelRef = React.useRef<HTMLElement>(null)
|
||||||
useEventListener(tabRef, 'change', () => {
|
useEventListener(tabRef, 'change', () => {
|
||||||
@@ -82,6 +84,11 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
|
|||||||
await loadMore()
|
await loadMore()
|
||||||
|
|
||||||
setTabItemSelected("Chat")
|
setTabItemSelected("Chat")
|
||||||
|
if (re.data!.type == 'group') {
|
||||||
|
setGroupPreferenceDefaultValue(re.data!.settings as GroupSettings)
|
||||||
|
groupPreferenceStore.setter(re.data!.settings as GroupSettings)
|
||||||
|
console.log(re.data!.settings as GroupSettings)
|
||||||
|
}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
chatPanelRef.current!.scrollTo({
|
chatPanelRef.current!.scrollTo({
|
||||||
top: 10000000000,
|
top: 10000000000,
|
||||||
@@ -211,7 +218,20 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const groupPreferenceStore = new PreferenceStore()
|
const groupPreferenceStore = new PreferenceStore<GroupSettings>()
|
||||||
|
groupPreferenceStore.setOnUpdate(async (value) => {
|
||||||
|
const re = await Client.invoke("Chat.updateSettings", {
|
||||||
|
token: data.access_token,
|
||||||
|
target,
|
||||||
|
settings: value,
|
||||||
|
})
|
||||||
|
if (checkApiSuccessOrSncakbar(re, "更新设定失败")) return
|
||||||
|
|
||||||
|
setChatInfo(JSON.parse(JSON.stringify({
|
||||||
|
...chatInfo,
|
||||||
|
settings: value as object,
|
||||||
|
})))
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
@@ -448,14 +468,14 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
|
|||||||
<SwitchPreference
|
<SwitchPreference
|
||||||
title="允许入群"
|
title="允许入群"
|
||||||
icon="person_add"
|
icon="person_add"
|
||||||
defaultState={false}
|
defaultState={groupPreferenceDefaultValue.allow_new_member_join || false}
|
||||||
updater={groupPreferenceStore.updater('allow_new_member_join')} />
|
updater={groupPreferenceStore.updater('allow_new_member_join')} />
|
||||||
<SwitchPreference
|
<SwitchPreference
|
||||||
title="允许成员邀请"
|
title="允许成员邀请"
|
||||||
description="目前压根没有这项功能, 甚至还不能查看成员列表, 以后再说吧"
|
description="目前压根没有这项功能, 甚至还不能查看成员列表, 以后再说吧"
|
||||||
icon="_"
|
icon="_"
|
||||||
disabled={true}
|
disabled={true}
|
||||||
defaultState={false}
|
defaultState={groupPreferenceDefaultValue.allow_new_member_from_invitation || false}
|
||||||
updater={groupPreferenceStore.updater('allow_new_member_from_invitation')} />
|
updater={groupPreferenceStore.updater('allow_new_member_from_invitation')} />
|
||||||
<SelectPreference
|
<SelectPreference
|
||||||
title="入群验证方式"
|
title="入群验证方式"
|
||||||
@@ -467,14 +487,14 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
|
|||||||
}}
|
}}
|
||||||
disabled={!groupPreferenceStore.value.allow_new_member_join}
|
disabled={!groupPreferenceStore.value.allow_new_member_join}
|
||||||
updater={groupPreferenceStore.updater('new_member_join_method')}
|
updater={groupPreferenceStore.updater('new_member_join_method')}
|
||||||
defaultCheckedId="disabled" />
|
defaultCheckedId={groupPreferenceDefaultValue.new_member_join_method || 'disabled'} />
|
||||||
{
|
{
|
||||||
groupPreferenceStore.value.new_member_join_method == 'answered_and_allowed_by_admin'
|
groupPreferenceStore.value.new_member_join_method == 'answered_and_allowed_by_admin'
|
||||||
&& <TextFieldPreference
|
&& <TextFieldPreference
|
||||||
title="设置问题"
|
title="设置问题"
|
||||||
icon="_"
|
icon="_"
|
||||||
description="WIP"
|
description="WIP"
|
||||||
defaultState=""
|
defaultState={groupPreferenceDefaultValue.answered_and_allowed_by_admin_question || ''}
|
||||||
disabled={true}
|
disabled={true}
|
||||||
updater={groupPreferenceStore.updater('answered_and_allowed_by_admin_question')} />
|
updater={groupPreferenceStore.updater('answered_and_allowed_by_admin_question')} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
export default class PreferenceStore {
|
export default class PreferenceStore<T extends object> {
|
||||||
declare value: { [key: string]: unknown }
|
declare value: T
|
||||||
declare setter: React.Dispatch<React.SetStateAction<{ [key: string]: unknown }>>
|
declare setter: React.Dispatch<React.SetStateAction<T>>
|
||||||
declare onUpdate: (value: unknown) => void
|
declare onUpdate: (value: unknown) => void
|
||||||
constructor() {
|
constructor() {
|
||||||
const _ = React.useState<{ [key: string]: unknown }>({})
|
const _ = React.useState<T>({} as T)
|
||||||
this.value = _[0]
|
this.value = _[0]
|
||||||
this.setter = _[1]
|
this.setter = _[1]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,12 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import deno from '@deno/vite-plugin'
|
import deno from '@deno/vite-plugin'
|
||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
import pluginBabel from 'vite-plugin-babel'
|
|
||||||
import config from '../server/config.ts'
|
import config from '../server/config.ts'
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react(), pluginBabel({
|
plugins: [react(), deno()],
|
||||||
babelConfig: {
|
|
||||||
presets: [
|
|
||||||
[
|
|
||||||
'@babel/preset-env',
|
|
||||||
{
|
|
||||||
targets: {
|
|
||||||
android: '70'
|
|
||||||
},
|
|
||||||
}
|
|
||||||
],
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}), deno()],
|
|
||||||
build: {
|
build: {
|
||||||
cssTarget: 'chrome70',
|
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
outDir: "." + config.data_path + '/page_compiled',
|
outDir: "." + config.data_path + '/page_compiled',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export type CallMethod =
|
|||||||
"User.auth" |
|
"User.auth" |
|
||||||
"User.register" |
|
"User.register" |
|
||||||
"User.login" |
|
"User.login" |
|
||||||
|
"User.refreshAccessToken" |
|
||||||
|
|
||||||
"User.setAvatar" |
|
"User.setAvatar" |
|
||||||
"User.updateProfile" |
|
"User.updateProfile" |
|
||||||
@@ -17,6 +18,10 @@ export type CallMethod =
|
|||||||
|
|
||||||
"Chat.getInfo" |
|
"Chat.getInfo" |
|
||||||
|
|
||||||
|
"Chat.updateSettings" |
|
||||||
|
|
||||||
|
"Chat.createGroup" |
|
||||||
|
|
||||||
"Chat.getIdForPrivate" |
|
"Chat.getIdForPrivate" |
|
||||||
"Chat.getAnotherUserIdFromPrivate" |
|
"Chat.getAnotherUserIdFromPrivate" |
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import BaseApi from "./BaseApi.ts"
|
|||||||
import TokenManager from "./TokenManager.ts"
|
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"
|
||||||
|
|
||||||
export default class ChatApi extends BaseApi {
|
export default class ChatApi extends BaseApi {
|
||||||
override getName(): string {
|
override getName(): string {
|
||||||
@@ -53,7 +54,8 @@ export default class ChatApi extends BaseApi {
|
|||||||
id: args.target as string,
|
id: args.target as string,
|
||||||
type: chat.bean.type,
|
type: chat.bean.type,
|
||||||
title: chat.getTitle(mine),
|
title: chat.getTitle(mine),
|
||||||
avatar: chat.getAvatarFileHash(mine) ? "uploaded_files/" + chat.getAvatarFileHash(mine) : undefined
|
avatar: chat.getAvatarFileHash(mine) ? "uploaded_files/" + chat.getAvatarFileHash(mine) : undefined,
|
||||||
|
settings: JSON.parse(chat.bean.settings),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,7 +67,8 @@ export default class ChatApi extends BaseApi {
|
|||||||
id: args.target as string,
|
id: args.target as string,
|
||||||
type: chat.bean.type,
|
type: chat.bean.type,
|
||||||
title: chat.getTitle(),
|
title: chat.getTitle(),
|
||||||
avatar: chat.getAvatarFileHash() ? "uploaded_files/" + chat.getAvatarFileHash() : undefined
|
avatar: chat.getAvatarFileHash() ? "uploaded_files/" + chat.getAvatarFileHash() : undefined,
|
||||||
|
settings: JSON.parse(chat.bean.settings),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -267,7 +270,10 @@ 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 已被占用",
|
||||||
|
code: 403,
|
||||||
|
}
|
||||||
|
|
||||||
const chat = ChatGroup.createGroup(haveId ? undefined : args.id as string)
|
const chat = ChatGroup.createGroup(haveId ? undefined : args.id as string)
|
||||||
chat.setTitle(args.title as string)
|
chat.setTitle(args.title as string)
|
||||||
@@ -284,6 +290,39 @@ export default class ChatApi extends BaseApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
/**
|
||||||
|
* 更新设定
|
||||||
|
* @param token 令牌
|
||||||
|
* @param title 名称
|
||||||
|
* @param [id] 群组 ID
|
||||||
|
*/
|
||||||
|
this.registerEvent("Chat.updateSettings", (args, { deviceId }) => {
|
||||||
|
if (this.checkArgsMissing(args, ['token', 'target', 'settings'])) 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 (chat.bean.type == 'group')
|
||||||
|
ChatGroup.fromChat(chat).getSettings().update(args.settings as GroupSettingsBean)
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
msg: '成功',
|
||||||
|
}
|
||||||
|
})
|
||||||
/**
|
/**
|
||||||
* 从私聊获取对方的 UserId
|
* 从私聊获取对方的 UserId
|
||||||
* @param token 令牌
|
* @param token 令牌
|
||||||
|
|||||||
@@ -32,15 +32,15 @@ export default class TokenManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static make(user: User, time_: number | null | undefined, device_id: string, type: TokenType = "access_token") {
|
static make(user: User, time_: number | null | undefined, device_id: string, tokenType: TokenType = "access_token") {
|
||||||
const time = (time_ || Date.now())
|
const time = (time_ || Date.now())
|
||||||
return this.encode({
|
return this.encode({
|
||||||
author: user.bean.id,
|
author: user.bean.id,
|
||||||
auth: this.makeAuth(user),
|
auth: this.makeAuth(user),
|
||||||
made_time: time,
|
made_time: time,
|
||||||
expired_time: time + (type == 'access_token' ? (1000 * 60 * 60 * 2) : (40 * 1000 * 60 * 60 * 24)),
|
expired_time: time + (tokenType == 'access_token' ? (1000 * 60 * 60 * 2) : (40 * 1000 * 60 * 60 * 24)),
|
||||||
device_id: device_id,
|
device_id: device_id,
|
||||||
type
|
type: tokenType
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ export default class UserApi extends BaseApi {
|
|||||||
const chat = Chat.findById(id)
|
const chat = Chat.findById(id)
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
type: chat.bean.type,
|
type: chat?.bean.type,
|
||||||
title: chat?.getTitle(user) || "未知",
|
title: chat?.getTitle(user) || "未知",
|
||||||
avatar: chat?.getAvatarFileHash(user) ? "uploaded_files/" + chat?.getAvatarFileHash(user) : undefined
|
avatar: chat?.getAvatarFileHash(user) ? "uploaded_files/" + chat?.getAvatarFileHash(user) : undefined
|
||||||
}
|
}
|
||||||
|
|||||||
6
server/data/AdminPermissions.ts
Normal file
6
server/data/AdminPermissions.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export default class AdminPermissions {
|
||||||
|
static readonly OWNER = "OWNER"
|
||||||
|
static readonly MANAGE_ADMIN = "MANAGE_ADMIN"
|
||||||
|
static readonly MUTE_MEMBERS = "MUTE_MEMBERS"
|
||||||
|
static readonly REMOVE_MEMBERS = "REMOVE_MEMBERS"
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ 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
|
||||||
@@ -80,6 +81,10 @@ export default class Chat {
|
|||||||
this.bean[key] = value
|
this.bean[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addAdmin(userId: string, permission: string[] | string) {
|
||||||
|
ChatAdminLinker.linkAdminAndChat(userId, this.bean.id)
|
||||||
|
ChatAdminLinker.updatePermissions(userId, this.bean.id, permission instanceof Array ? JSON.stringify(permission) : permission)
|
||||||
|
}
|
||||||
getMembersList() {
|
getMembersList() {
|
||||||
return UserChatLinker.getChatMembers(this.bean.id)
|
return UserChatLinker.getChatMembers(this.bean.id)
|
||||||
}
|
}
|
||||||
|
|||||||
49
server/data/ChatAdminLinker.ts
Normal file
49
server/data/ChatAdminLinker.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,41 @@
|
|||||||
import chalk from "chalk"
|
import chalk from "chalk"
|
||||||
import Chat from "./Chat.ts"
|
import Chat from "./Chat.ts"
|
||||||
import User from "./User.ts"
|
import User from "./User.ts"
|
||||||
|
import GroupSettingsBean from "./GroupSettingsBean.ts"
|
||||||
|
|
||||||
|
class GroupSettings {
|
||||||
|
declare chat: ChatGroup
|
||||||
|
declare settings: GroupSettingsBean
|
||||||
|
constructor(chat: ChatGroup) {
|
||||||
|
this.chat = chat
|
||||||
|
this.settings = JSON.parse(chat.bean.settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
update(bean: GroupSettingsBean) {
|
||||||
|
const updateValue = (key: string) => {
|
||||||
|
if (key in bean)
|
||||||
|
this.settings[key] = bean[key]
|
||||||
|
}
|
||||||
|
for (const k of [
|
||||||
|
'allow_new_member_join',
|
||||||
|
'allow_new_member_from_invitation',
|
||||||
|
'new_member_join_method',
|
||||||
|
'answered_and_allowed_by_admin_question',
|
||||||
|
])
|
||||||
|
updateValue(k)
|
||||||
|
|
||||||
|
this.apply()
|
||||||
|
}
|
||||||
|
apply() {
|
||||||
|
this.chat.setAttr('settings', JSON.stringify(this.settings))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default class ChatGroup extends Chat {
|
export default class ChatGroup extends Chat {
|
||||||
|
getSettings() {
|
||||||
|
return new GroupSettings(this)
|
||||||
|
}
|
||||||
|
|
||||||
static fromChat(chat: Chat) {
|
static fromChat(chat: Chat) {
|
||||||
return new ChatGroup(chat.bean)
|
return new ChatGroup(chat.bean)
|
||||||
}
|
}
|
||||||
|
|||||||
10
server/data/GroupSettingsBean.ts
Normal file
10
server/data/GroupSettingsBean.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
interface GroupSettingsBean {
|
||||||
|
allow_new_member_join?: boolean
|
||||||
|
allow_new_member_from_invitation?: boolean
|
||||||
|
new_member_join_method?: 'disabled' | 'allowed_by_admin' | 'answered_and_allowed_by_admin'
|
||||||
|
answered_and_allowed_by_admin_question?: string
|
||||||
|
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GroupSettingsBean
|
||||||
@@ -2,7 +2,7 @@ import { DatabaseSync } from "node:sqlite"
|
|||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
|
|
||||||
import config from "../config.ts"
|
import config from "../config.ts"
|
||||||
import { SQLInputValue } from "node:sqlite";
|
import { SQLInputValue } from "node:sqlite"
|
||||||
export default class UserChatLinker {
|
export default class UserChatLinker {
|
||||||
static database: DatabaseSync = this.init()
|
static database: DatabaseSync = this.init()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user