Compare commits
10 Commits
86d68fd5e5
...
fb48c44655
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb48c44655 | ||
|
|
7378024235 | ||
|
|
1c985f28a2 | ||
|
|
449c0a8806 | ||
|
|
e1e42ea188 | ||
|
|
823eef76b0 | ||
|
|
3b0b5ff032 | ||
|
|
6112b4b207 | ||
|
|
9e8e967eb9 | ||
|
|
697082193f |
@@ -17,12 +17,6 @@
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<mdui-snackbar close-on-outside-click id="public_snackbar"></mdui-snackbar>
|
||||
|
||||
<mdui-dialog close-on-overlay-click id="ErrorDialog">
|
||||
<span slot="headline">错误</span>
|
||||
<span slot="description" id="ErrorDialog_Message"></span>
|
||||
</mdui-dialog>
|
||||
|
||||
<script nomodule>
|
||||
alert('很抱歉, 此应用无法在较旧的浏览器运行, 请使用基于 Chromium 89+ 的浏览器(内核)使用 :(')
|
||||
|
||||
@@ -10,17 +10,6 @@ import './ui/custom-elements/chat-image.ts'
|
||||
import './ui/custom-elements/chat-video.ts'
|
||||
import './ui/custom-elements/chat-file.ts'
|
||||
|
||||
const urlParams = new URL(location.href).searchParams
|
||||
|
||||
// deno-lint-ignore no-window no-window-prefix
|
||||
urlParams.get('debug') == 'true' && window.addEventListener('error', ({ message, filename, lineno, colno, error }) => {
|
||||
const m = $("#ErrorDialog_Message")
|
||||
const d = $("#ErrorDialog").get(0) as Dialog
|
||||
const s = d.open
|
||||
d.open = true
|
||||
m.html((s ? `${m.html()}<br/><br/>` : '') + `${message} (${filename || 'unknown'}:${lineno}:${colno})`)
|
||||
})
|
||||
|
||||
import App from './ui/App.tsx'
|
||||
import AppMobile from './ui/AppMobile.tsx'
|
||||
import isMobileUI from "./ui/isMobileUI.ts"
|
||||
|
||||
@@ -21,6 +21,7 @@ import useAsyncEffect from "./useAsyncEffect.ts"
|
||||
import ChatInfoDialog from "./dialog/ChatInfoDialog.tsx"
|
||||
import Chat from "../api/client_data/Chat.ts"
|
||||
import AddContactDialog from './dialog/AddContactDialog.tsx'
|
||||
import CreateGroupDialog from './dialog/CreateGroupDialog.tsx'
|
||||
import UserProfileDialog from "./dialog/UserProfileDialog.tsx"
|
||||
import DataCaches from "../api/DataCaches.ts"
|
||||
|
||||
@@ -62,6 +63,7 @@ export default function App() {
|
||||
const [userInfo, setUserInfo] = React.useState(null as unknown as User)
|
||||
|
||||
const addContactDialogRef = React.useRef<Dialog>(null)
|
||||
const createGroupDialogRef = React.useRef<Dialog>(null)
|
||||
|
||||
const chatInfoDialogRef = React.useRef<Dialog>(null)
|
||||
const [chatInfo, setChatInfo] = React.useState(null as unknown as Chat)
|
||||
@@ -152,6 +154,9 @@ export default function App() {
|
||||
|
||||
<AddContactDialog
|
||||
addContactDialogRef={addContactDialogRef} />
|
||||
|
||||
<CreateGroupDialog
|
||||
createGroupDialogRef={createGroupDialogRef} />
|
||||
|
||||
<mdui-navigation-rail contained value="Recents" ref={navigationRailRef}>
|
||||
<mdui-button-icon slot="top">
|
||||
@@ -179,6 +184,7 @@ export default function App() {
|
||||
<ContactsList
|
||||
openChatInfoDialog={openChatInfoDialog}
|
||||
addContactDialogRef={addContactDialogRef as any}
|
||||
createGroupDialogRef={createGroupDialogRef}
|
||||
display={navigationItemSelected == "Contacts"} />
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -20,6 +20,7 @@ import useAsyncEffect from "./useAsyncEffect.ts"
|
||||
import ChatInfoDialog from "./dialog/ChatInfoDialog.tsx"
|
||||
import Chat from "../api/client_data/Chat.ts"
|
||||
import AddContactDialog from './dialog/AddContactDialog.tsx'
|
||||
import CreateGroupDialog from './dialog/CreateGroupDialog.tsx'
|
||||
import UserProfileDialog from "./dialog/UserProfileDialog.tsx"
|
||||
import DataCaches from "../api/DataCaches.ts"
|
||||
|
||||
@@ -58,6 +59,7 @@ export default function AppMobile() {
|
||||
})
|
||||
|
||||
const addContactDialogRef = React.useRef<Dialog>(null)
|
||||
const createGroupDialogRef = React.useRef<Dialog>(null)
|
||||
|
||||
const chatInfoDialogRef = React.useRef<Dialog>(null)
|
||||
const [chatInfo, setChatInfo] = React.useState(null as unknown as Chat)
|
||||
@@ -176,6 +178,9 @@ export default function AppMobile() {
|
||||
|
||||
<AddContactDialog
|
||||
addContactDialogRef={addContactDialogRef} />
|
||||
|
||||
<CreateGroupDialog
|
||||
createGroupDialogRef={createGroupDialogRef} />
|
||||
|
||||
<mdui-top-app-bar style={{
|
||||
position: 'sticky',
|
||||
@@ -221,6 +226,7 @@ export default function AppMobile() {
|
||||
<ContactsList
|
||||
openChatInfoDialog={openChatInfoDialog}
|
||||
addContactDialogRef={addContactDialogRef as any}
|
||||
createGroupDialogRef={createGroupDialogRef}
|
||||
display={navigationItemSelected == "Contacts"} />
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -15,11 +15,11 @@ interface Refs {
|
||||
export default function AddContactDialog({
|
||||
addContactDialogRef,
|
||||
}: Refs) {
|
||||
const inputUserAccountRef = React.useRef<TextField>(null)
|
||||
const inputTargetRef = React.useRef<TextField>(null)
|
||||
|
||||
async function addContact() {
|
||||
const re = await Client.invoke("User.addContact", {
|
||||
account: inputUserAccountRef.current!.value,
|
||||
target: inputTargetRef.current!.value,
|
||||
token: data.access_token,
|
||||
})
|
||||
|
||||
@@ -30,14 +30,13 @@ export default function AddContactDialog({
|
||||
})
|
||||
EventBus.emit('ContactsList.updateContacts')
|
||||
|
||||
inputUserAccountRef.current!.value = ''
|
||||
inputTargetRef.current!.value = ''
|
||||
addContactDialogRef.current!.open = false
|
||||
}
|
||||
|
||||
return (
|
||||
<mdui-dialog close-on-overlay-click close-on-esc headline="添加對話" ref={addContactDialogRef}>
|
||||
現階段只支持添加用戶, 對話敬請期待...
|
||||
<mdui-text-field style={{ marginTop: "10px", }} clearable label="對方的 用戶 ID / 用戶名" ref={inputUserAccountRef as any} onKeyDown={(event) => {
|
||||
<mdui-text-field clearable label="对话 ID / 用戶 ID / 用戶名" ref={inputTargetRef as any} onKeyDown={(event) => {
|
||||
if (event.key == 'Enter')
|
||||
addContact()
|
||||
}}></mdui-text-field>
|
||||
|
||||
54
client/ui/dialog/CreateGroupDialog.tsx
Normal file
54
client/ui/dialog/CreateGroupDialog.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import * as React from 'react'
|
||||
import { Button, Dialog, TextField } from "mdui"
|
||||
import useEventListener from "../useEventListener.ts"
|
||||
import { checkApiSuccessOrSncakbar, snackbar } from "../snackbar.ts"
|
||||
import Client from "../../api/Client.ts"
|
||||
|
||||
import * as CryptoJS from 'crypto-js'
|
||||
import data from "../../Data.ts"
|
||||
import EventBus from "../../EventBus.ts"
|
||||
|
||||
interface Refs {
|
||||
createGroupDialogRef: React.MutableRefObject<Dialog | null>
|
||||
}
|
||||
|
||||
export default function CreateGroupDialog({
|
||||
createGroupDialogRef,
|
||||
}: Refs) {
|
||||
const inputGroupNameRef = React.useRef<TextField>(null)
|
||||
const inputGroupIdRef = React.useRef<TextField>(null)
|
||||
|
||||
async function createGroup() {
|
||||
const re = await Client.invoke("Chat.createGroup", {
|
||||
title: inputGroupNameRef.current!.value,
|
||||
id: inputGroupIdRef.current!.value,
|
||||
token: data.access_token,
|
||||
})
|
||||
|
||||
if (checkApiSuccessOrSncakbar(re, "添加失敗")) return
|
||||
snackbar({
|
||||
message: "创建成功!",
|
||||
placement: "top",
|
||||
})
|
||||
EventBus.emit('ContactsList.updateContacts')
|
||||
|
||||
inputGroupNameRef.current!.value = ''
|
||||
inputGroupIdRef.current!.value = ''
|
||||
createGroupDialogRef.current!.open = false
|
||||
}
|
||||
|
||||
return (
|
||||
<mdui-dialog close-on-overlay-click close-on-esc headline="创建群组" ref={createGroupDialogRef}>
|
||||
<mdui-text-field clearable label="群组名称" ref={inputGroupNameRef as any} onKeyDown={(event) => {
|
||||
if (event.key == 'Enter')
|
||||
inputGroupIdRef.current!.click()
|
||||
}}></mdui-text-field>
|
||||
<mdui-text-field style={{ marginTop: "10px", }} clearable label="群组 ID (可选)" ref={inputGroupIdRef as any} onKeyDown={(event) => {
|
||||
if (event.key == 'Enter')
|
||||
createGroup()
|
||||
}}></mdui-text-field>
|
||||
<mdui-button slot="action" variant="text" onClick={() => createGroupDialogRef.current!.open = false}>取消</mdui-button>
|
||||
<mdui-button slot="action" variant="text" onClick={() => createGroup()}>创建</mdui-button>
|
||||
</mdui-dialog>
|
||||
)
|
||||
}
|
||||
@@ -13,12 +13,14 @@ interface Args extends React.HTMLAttributes<HTMLElement> {
|
||||
display: boolean
|
||||
openChatInfoDialog: (chat: Chat) => void
|
||||
addContactDialogRef: React.MutableRefObject<Dialog>
|
||||
createGroupDialogRef: React.MutableRefObject<Dialog>
|
||||
}
|
||||
|
||||
export default function ContactsList({
|
||||
display,
|
||||
openChatInfoDialog,
|
||||
addContactDialogRef,
|
||||
createGroupDialogRef,
|
||||
...props
|
||||
}: Args) {
|
||||
const searchRef = React.useRef<HTMLElement>(null)
|
||||
@@ -62,6 +64,10 @@ export default function ContactsList({
|
||||
}} icon="person_add" onClick={() => addContactDialogRef.current!.open = true}>添加對話</mdui-list-item>
|
||||
<mdui-list-item rounded style={{
|
||||
width: '100%',
|
||||
}} icon="group_add" onClick={() => createGroupDialogRef.current!.open = true}>创建群组</mdui-list-item>
|
||||
<mdui-list-item rounded style={{
|
||||
width: '100%',
|
||||
marginTop: '13px',
|
||||
marginBottom: '15px',
|
||||
}} icon="refresh" onClick={() => EventBus.emit('ContactsList.updateContacts')}>刷新</mdui-list-item>
|
||||
{/* <mdui-list-item rounded style={{
|
||||
|
||||
@@ -7,7 +7,8 @@ import UserChatLinker from "../data/UserChatLinker.ts"
|
||||
import ApiManager from "./ApiManager.ts"
|
||||
import BaseApi from "./BaseApi.ts"
|
||||
import TokenManager from "./TokenManager.ts"
|
||||
import ChatPrivate from "../data/ChatPrivate.ts";
|
||||
import ChatPrivate from "../data/ChatPrivate.ts"
|
||||
import ChatGroup from "../data/ChatGroup.ts"
|
||||
|
||||
export default class ChatApi extends BaseApi {
|
||||
override getName(): string {
|
||||
@@ -56,6 +57,18 @@ export default class ChatApi extends BaseApi {
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
code: 501,
|
||||
@@ -230,6 +243,47 @@ export default class ChatApi extends BaseApi {
|
||||
}
|
||||
}
|
||||
})
|
||||
/**
|
||||
* 创建群组
|
||||
* @param token 令牌
|
||||
* @param title 名称
|
||||
* @param [id] 群组 ID
|
||||
*/
|
||||
this.registerEvent("Chat.createGroup", (args, { deviceId }) => {
|
||||
if (this.checkArgsMissing(args, ['token', 'title'])) return {
|
||||
msg: "參數缺失",
|
||||
code: 400,
|
||||
}
|
||||
if (this.checkArgsEmpty(args, ['title'])) 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 haveId = args.id && (args.id as string) != ''
|
||||
if (haveId && Chat.findById(args.id as string) != null) return
|
||||
|
||||
const chat = ChatGroup.createGroup(haveId ? undefined : args.id as string)
|
||||
chat.setTitle(args.title as string)
|
||||
chat.addMembers([
|
||||
user.bean.id,
|
||||
])
|
||||
user.addContact(chat.bean.id)
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
msg: '成功',
|
||||
data: {
|
||||
chat_id: chat.bean.id,
|
||||
}
|
||||
}
|
||||
})
|
||||
/**
|
||||
* 从私聊获取对方的 UserId
|
||||
* @param token 令牌
|
||||
|
||||
@@ -102,7 +102,7 @@ export default class UserApi extends BaseApi {
|
||||
const nickname: string = args.nickname as string
|
||||
const password: string = args.password as string
|
||||
|
||||
const user = User.createWithUserNameChecked(username, password, nickname, null)
|
||||
const user = User.create(username, password, nickname, null)
|
||||
|
||||
return {
|
||||
msg: "成功",
|
||||
@@ -251,6 +251,7 @@ export default class UserApi extends BaseApi {
|
||||
const chat = Chat.findById(id)
|
||||
return {
|
||||
id,
|
||||
type: chat.bean.type,
|
||||
title: chat?.getTitle(user) || "未知",
|
||||
avatar: chat?.getAvatarFileHash(user) ? "uploaded_files/" + chat?.getAvatarFileHash(user) : undefined
|
||||
}
|
||||
@@ -260,7 +261,7 @@ export default class UserApi extends BaseApi {
|
||||
})
|
||||
// 添加聯絡人
|
||||
this.registerEvent("User.addContact", (args, { deviceId }) => {
|
||||
if (this.checkArgsMissing(args, ['token']) || (args.chat_id == null && args.account == null)) return {
|
||||
if (this.checkArgsMissing(args, ['token', 'target'])) return {
|
||||
msg: "參數缺失",
|
||||
code: 400,
|
||||
}
|
||||
@@ -272,18 +273,18 @@ export default class UserApi extends BaseApi {
|
||||
}
|
||||
|
||||
const user = User.findById(token.author) as User
|
||||
if (args.chat_id)
|
||||
user!.addContact(args.chat_id as string)
|
||||
else if (args.account) {
|
||||
const targetUser = User.findByAccount(args.account as string) as User
|
||||
if (targetUser == null) {
|
||||
return {
|
||||
msg: "找不到用戶",
|
||||
code: 404,
|
||||
}
|
||||
}
|
||||
const chat = ChatPrivate.findOrCreateForPrivate(user, targetUser)
|
||||
const chat = Chat.findById(args.target as string)
|
||||
const targetUser = User.findByAccount(args.target as string) as User
|
||||
if (chat)
|
||||
user!.addContact(chat.bean.id)
|
||||
else if (targetUser) {
|
||||
const privChat = ChatPrivate.findOrCreateForPrivate(user, targetUser)
|
||||
user!.addContact(privChat.bean.id)
|
||||
} else {
|
||||
return {
|
||||
msg: "找不到目标",
|
||||
code: 404,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -9,6 +9,7 @@ import chalk from "chalk"
|
||||
import User from "./User.ts"
|
||||
import ChatType from "./ChatType.ts"
|
||||
import UserChatLinker from "./UserChatLinker.ts"
|
||||
import DataWrongError from '../api/DataWrongError.ts'
|
||||
|
||||
/**
|
||||
* Chat.ts - Wrapper and manager
|
||||
@@ -48,6 +49,8 @@ export default class Chat {
|
||||
}
|
||||
|
||||
static create(chatId: string, type: ChatType) {
|
||||
if (this.findAllBeansByCondition('id = ?', chatId).length > 0)
|
||||
throw new DataWrongError(`对话ID ${chatId} 已被使用`)
|
||||
const chat = new Chat(
|
||||
Chat.findAllBeansByCondition(
|
||||
'count = ?',
|
||||
@@ -101,6 +104,11 @@ export default class Chat {
|
||||
|
||||
return null
|
||||
}
|
||||
setTitle(title: string) {
|
||||
if (this.bean.type == 'private')
|
||||
throw new Error('不允许对私聊进行命名')
|
||||
this.setAttr('title', title)
|
||||
}
|
||||
getTitle(userMySelf?: User) {
|
||||
if (this.bean.type == 'group') return this.bean.title
|
||||
if (this.bean.type == 'private') return this.getAnotherUserForPrivate(userMySelf as User)?.getNickName()
|
||||
|
||||
@@ -10,11 +10,11 @@ import UserBean from './UserBean.ts'
|
||||
|
||||
import FileManager from './FileManager.ts'
|
||||
import { SQLInputValue } from "node:sqlite"
|
||||
import DataWrongError from "../api/DataWrongError.ts"
|
||||
import ChatPrivate from "./ChatPrivate.ts"
|
||||
import Chat from "./Chat.ts"
|
||||
import ChatBean from "./ChatBean.ts"
|
||||
import MapJson from "../MapJson.ts";
|
||||
import MapJson from "../MapJson.ts"
|
||||
import DataWrongError from '../api/DataWrongError.ts'
|
||||
|
||||
type UserBeanKey = keyof UserBean
|
||||
|
||||
@@ -45,18 +45,9 @@ export default class User {
|
||||
return db
|
||||
}
|
||||
|
||||
static createWithUserNameChecked(userName: string | null, password: string, nickName: string, avatar: Buffer | null) {
|
||||
static create(userName: string | null, password: string, nickName: string, avatar: Buffer | null) {
|
||||
if (userName && User.findAllBeansByCondition('username = ?', userName).length > 0)
|
||||
throw new DataWrongError(`用户名 ${userName} 已存在`)
|
||||
return User.create(
|
||||
userName,
|
||||
password,
|
||||
nickName,
|
||||
avatar
|
||||
)
|
||||
}
|
||||
|
||||
static create(userName: string | null, password: string, nickName: string, avatar: Buffer | null) {
|
||||
const user = new User(
|
||||
User.findAllBeansByCondition(
|
||||
'count = ?',
|
||||
@@ -124,6 +115,8 @@ export default class User {
|
||||
return this.bean.username
|
||||
}
|
||||
setUserName(userName: string) {
|
||||
if (User.findAllBeansByCondition('username = ?', userName).length > 0)
|
||||
throw new DataWrongError(`用户名 ${userName} 已存在`)
|
||||
this.setAttr("username", userName)
|
||||
}
|
||||
updateRecentChat(chatId: string, content: string) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import TokenManager from "./api/TokenManager.ts"
|
||||
import UserChatLinker from "./data/UserChatLinker.ts"
|
||||
import path from "node:path"
|
||||
import cookieParser from 'cookie-parser'
|
||||
import fs from 'node:fs/promises'
|
||||
|
||||
const app = express()
|
||||
app.use('/', express.static(config.data_path + '/page_compiled'))
|
||||
@@ -53,7 +54,11 @@ app.get('/uploaded_files/:hash', (req, res) => {
|
||||
|
||||
const httpServer: HttpServerLike = (
|
||||
((config.server.use == 'http') && http.createServer(app)) ||
|
||||
((config.server.use == 'https') && https.createServer(config.server.https, app)) ||
|
||||
((config.server.use == 'https') && https.createServer({
|
||||
...config.server.https,
|
||||
key: await fs.readFile(config.server.https.key),
|
||||
cert: await fs.readFile(config.server.https.cert),
|
||||
}, app)) ||
|
||||
http.createServer(app)
|
||||
)
|
||||
const io = new SocketIo.Server(httpServer, {
|
||||
|
||||
Reference in New Issue
Block a user