Compare commits
7 Commits
fb48c44655
...
059078ea8f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
059078ea8f | ||
|
|
674fe000f4 | ||
|
|
85477fe46e | ||
|
|
dced175d7a | ||
|
|
bd857b840b | ||
|
|
5d1c395340 | ||
|
|
0e17b37156 |
@@ -51,15 +51,33 @@ class Client {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
this.socket!.timeout(timeout).emit("The_White_Silk", method, args, (err: Error, res: ApiCallbackMessage) => {
|
this.socket!.timeout(timeout).emit("The_White_Silk", method, args, async (err: Error, res: ApiCallbackMessage) => {
|
||||||
if (err) return resolve({
|
if (err) return resolve({
|
||||||
code: -1,
|
code: -1,
|
||||||
msg: err.message.indexOf("timed out") != -1 ? "請求超時" : err.message,
|
msg: err.message.indexOf("timed out") != -1 ? "請求超時" : err.message,
|
||||||
})
|
})
|
||||||
resolve(res)
|
if (!["User.refreshAccessToken", ...CallableMethodBeforeAuth].includes(method) && res.code == 401) {
|
||||||
|
const token = await this.refreshAccessToken()
|
||||||
|
if (token) {
|
||||||
|
data.access_token = token
|
||||||
|
data.apply()
|
||||||
|
resolve(await this.invoke(method, {
|
||||||
|
...args,
|
||||||
|
token
|
||||||
|
}, timeout))
|
||||||
|
} else
|
||||||
|
resolve(res)
|
||||||
|
} else
|
||||||
|
resolve(res)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
static async refreshAccessToken() {
|
||||||
|
const re = await this.invoke("User.refreshAccessToken", {
|
||||||
|
refresh_token: data.refresh_token
|
||||||
|
})
|
||||||
|
return re.data?.access_token
|
||||||
|
}
|
||||||
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", {
|
||||||
access_token: token
|
access_token: token
|
||||||
|
|||||||
BIN
client/icon.ico
Normal file
BIN
client/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -10,7 +10,7 @@
|
|||||||
<link rel="icon" href="icon.ico" />
|
<link rel="icon" href="icon.ico" />
|
||||||
<link rel="stylesheet" href="./static/material_icons.css" />
|
<link rel="stylesheet" href="./static/material_icons.css" />
|
||||||
|
|
||||||
<title>TheWhiteSilk</title>
|
<title>LingChair</title>
|
||||||
|
|
||||||
<link rel="stylesheet" href="./style.css" />
|
<link rel="stylesheet" href="./style.css" />
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export default function App() {
|
|||||||
if (re.code == 401)
|
if (re.code == 401)
|
||||||
loginDialogRef.current!.open = true
|
loginDialogRef.current!.open = true
|
||||||
else if (re.code != 200) {
|
else if (re.code != 200) {
|
||||||
if (checkApiSuccessOrSncakbar(re, "驗證失敗")) return
|
if (checkApiSuccessOrSncakbar(re, "验证失败")) return
|
||||||
} else if (re.code == 200) {
|
} else if (re.code == 200) {
|
||||||
setMyUserProfileCache(Client.myUserProfile as User)
|
setMyUserProfileCache(Client.myUserProfile as User)
|
||||||
}
|
}
|
||||||
@@ -201,7 +201,7 @@ export default function App() {
|
|||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
alignSelf: 'center',
|
alignSelf: 'center',
|
||||||
}}>
|
}}>
|
||||||
選擇以開始對話...
|
选择以开始对话......
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export default function AppMobile() {
|
|||||||
if (re.code == 401)
|
if (re.code == 401)
|
||||||
loginDialogRef.current!.open = true
|
loginDialogRef.current!.open = true
|
||||||
else if (re.code != 200) {
|
else if (re.code != 200) {
|
||||||
if (checkApiSuccessOrSncakbar(re, "驗證失敗")) return
|
if (checkApiSuccessOrSncakbar(re, "验证失败")) return
|
||||||
} else if (re.code == 200) {
|
} else if (re.code == 200) {
|
||||||
setMyUserProfileCache(Client.myUserProfile as User)
|
setMyUserProfileCache(Client.myUserProfile as User)
|
||||||
}
|
}
|
||||||
@@ -191,8 +191,8 @@ export default function AppMobile() {
|
|||||||
}}>
|
}}>
|
||||||
<mdui-top-app-bar-title>{
|
<mdui-top-app-bar-title>{
|
||||||
({
|
({
|
||||||
Recents: "最近對話",
|
Recents: "最近对话",
|
||||||
Contacts: "所有對話"
|
Contacts: "所有对话"
|
||||||
})[navigationItemSelected]
|
})[navigationItemSelected]
|
||||||
}</mdui-top-app-bar-title>
|
}</mdui-top-app-bar-title>
|
||||||
<div style={{
|
<div style={{
|
||||||
@@ -234,8 +234,8 @@ export default function AppMobile() {
|
|||||||
position: 'sticky',
|
position: 'sticky',
|
||||||
bottom: '0',
|
bottom: '0',
|
||||||
}}>
|
}}>
|
||||||
<mdui-navigation-bar-item icon="watch_later--outlined" active-icon="watch_later--filled" value="Recents">最近</mdui-navigation-bar-item>
|
<mdui-navigation-bar-item icon="watch_later--outlined" active-icon="watch_later--filled" value="Recents">最近对话</mdui-navigation-bar-item>
|
||||||
<mdui-navigation-bar-item icon="chat--outlined" active-icon="chat--filled" value="Contacts">對話</mdui-navigation-bar-item>
|
<mdui-navigation-bar-item icon="chat--outlined" active-icon="chat--filled" value="Contacts">所有对话</mdui-navigation-bar-item>
|
||||||
</mdui-navigation-bar>
|
</mdui-navigation-bar>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ 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: '加载中...'
|
||||||
} as Chat)
|
} as Chat)
|
||||||
|
|
||||||
const [tabItemSelected, setTabItemSelected] = React.useState('None')
|
const [tabItemSelected, setTabItemSelected] = React.useState('None')
|
||||||
@@ -68,7 +68,7 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
|
|||||||
target: target,
|
target: target,
|
||||||
})
|
})
|
||||||
if (re.code != 200)
|
if (re.code != 200)
|
||||||
return target != '' && checkApiSuccessOrSncakbar(re, "對話錯誤")
|
return target != '' && checkApiSuccessOrSncakbar(re, "获取对话信息失败")
|
||||||
setChatInfo(re.data as Chat)
|
setChatInfo(re.data as Chat)
|
||||||
|
|
||||||
await loadMore()
|
await loadMore()
|
||||||
@@ -90,7 +90,7 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
|
|||||||
page: page.current,
|
page: page.current,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (checkApiSuccessOrSncakbar(re, "拉取歷史記錄失敗")) return
|
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) {
|
||||||
@@ -150,7 +150,7 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
|
|||||||
target,
|
target,
|
||||||
data: cachedFiles.current[fileName],
|
data: cachedFiles.current[fileName],
|
||||||
}, 5000)
|
}, 5000)
|
||||||
if (checkApiSuccessOrSncakbar(re, `文件[${fileName}] 上傳失敗`)) return setIsMessageSending(false)
|
if (checkApiSuccessOrSncakbar(re, `文件[${fileName}] 上传失败`)) return setIsMessageSending(false)
|
||||||
text = text.replaceAll('(' + fileName + ')', '(' + re.data!.file_path as string + ')')
|
text = text.replaceAll('(' + fileName + ')', '(' + re.data!.file_path as string + ')')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -160,12 +160,12 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
|
|||||||
target,
|
target,
|
||||||
text,
|
text,
|
||||||
}, 5000)
|
}, 5000)
|
||||||
if (checkApiSuccessOrSncakbar(re, "發送失敗")) return setIsMessageSending(false)
|
if (checkApiSuccessOrSncakbar(re, "发送失败")) return setIsMessageSending(false)
|
||||||
inputRef.current!.value = ''
|
inputRef.current!.value = ''
|
||||||
cachedFiles.current = {}
|
cachedFiles.current = {}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
snackbar({
|
snackbar({
|
||||||
message: '發送失敗: ' + (e as Error).message,
|
message: '发送失败: ' + (e as Error).message,
|
||||||
placement: 'top',
|
placement: 'top',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -188,7 +188,7 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
|
|||||||
cachedFiles.current[name] = await data.arrayBuffer()
|
cachedFiles.current[name] = await data.arrayBuffer()
|
||||||
cachedFileNamesCount.current[name] = 1
|
cachedFileNamesCount.current[name] = 1
|
||||||
if (type.startsWith('image/'))
|
if (type.startsWith('image/'))
|
||||||
insertText(``)
|
insertText(``)
|
||||||
else if (type.startsWith('video/'))
|
else if (type.startsWith('video/'))
|
||||||
insertText(``)
|
insertText(``)
|
||||||
else
|
else
|
||||||
@@ -227,7 +227,7 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
|
|||||||
<mdui-tab value="Chat">{
|
<mdui-tab value="Chat">{
|
||||||
chatInfo.title
|
chatInfo.title
|
||||||
}</mdui-tab>
|
}</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={{
|
||||||
flexGrow: '1',
|
flexGrow: '1',
|
||||||
@@ -265,7 +265,7 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
|
|||||||
<span style={{
|
<span style={{
|
||||||
alignSelf: 'center',
|
alignSelf: 'center',
|
||||||
paddingLeft: '12px',
|
paddingLeft: '12px',
|
||||||
}}>加載中...</span>
|
}}>加载中...</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{
|
<div style={{
|
||||||
display: showNoMoreMessagesTip ? undefined : 'none',
|
display: showNoMoreMessagesTip ? undefined : 'none',
|
||||||
@@ -372,7 +372,7 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
|
|||||||
addFile(type as string, getFileNameOrRandom(url), re)
|
addFile(type as string, getFileNameOrRandom(url), re)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
snackbar({
|
snackbar({
|
||||||
message: '無法解析連結: ' + (e as Error).message,
|
message: '无法解析链接: ' + (e as Error).message,
|
||||||
placement: 'top',
|
placement: 'top',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -385,7 +385,7 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
<mdui-text-field variant="outlined" placeholder="喵呜~" autosize ref={inputRef as any} max-rows={6} onChange={() => {
|
<mdui-text-field variant="outlined" placeholder="(。・ω・。)" autosize ref={inputRef as any} max-rows={6} onChange={() => {
|
||||||
if (inputRef.current?.value.trim() == '')
|
if (inputRef.current?.value.trim() == '')
|
||||||
cachedFiles.current = {}
|
cachedFiles.current = {}
|
||||||
}} onKeyDown={(event) => {
|
}} onKeyDown={(event) => {
|
||||||
@@ -415,7 +415,7 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
|
|||||||
<div style={{
|
<div style={{
|
||||||
display: 'none'
|
display: 'none'
|
||||||
}}>
|
}}>
|
||||||
<input accept="*/*" type="file" name="選擇附加文檔" multiple ref={attachFileInputRef}></input>
|
<input accept="*/*" type="file" name="添加文件" multiple ref={attachFileInputRef}></input>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</mdui-tab-panel>
|
</mdui-tab-panel>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Dropdown, Dialog } from "mdui"
|
import { Dropdown, Dialog, dialog } from "mdui"
|
||||||
import { $ } from "mdui/jq"
|
import { $ } from "mdui/jq"
|
||||||
import Client from "../../api/Client.ts"
|
import Client from "../../api/Client.ts"
|
||||||
import Data_Message from "../../api/client_data/Message.ts"
|
import Data_Message from "../../api/client_data/Message.ts"
|
||||||
@@ -131,9 +131,9 @@ export default function Message({ userId, rawData, renderHTML, message, openUser
|
|||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
setDropDownOpen(false)
|
setDropDownOpen(false)
|
||||||
}}>
|
}}>
|
||||||
<mdui-menu-item icon="content_copy" onClick={() => copyToClipboard($(dropDownRef.current as HTMLElement).find('#msg').text().trim())}>複製文字</mdui-menu-item>
|
<mdui-menu-item icon="content_copy" onClick={() => copyToClipboard($(dropDownRef.current as HTMLElement).find('#msg').text().trim())}>复制文字</mdui-menu-item>
|
||||||
<mdui-menu-item icon="content_copy" onClick={() => copyToClipboard(rawData)}>複製原文</mdui-menu-item>
|
<mdui-menu-item icon="content_copy" onClick={() => copyToClipboard(rawData)}>复制原文</mdui-menu-item>
|
||||||
<mdui-menu-item icon="info" onClick={() => messageJsonDialogRef.current!.open = true}>查看詳情</mdui-menu-item>
|
<mdui-menu-item icon="info" onClick={() => messageJsonDialogRef.current!.open = true}>JSON</mdui-menu-item>
|
||||||
</mdui-menu>
|
</mdui-menu>
|
||||||
</mdui-dropdown>
|
</mdui-dropdown>
|
||||||
</mdui-card>
|
</mdui-card>
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ customElements.define('chat-image', class extends HTMLElement {
|
|||||||
e.onerror = () => {
|
e.onerror = () => {
|
||||||
const src = $(this).attr('src')
|
const src = $(this).attr('src')
|
||||||
$(this).html(`<mdui-icon name="broken_image" style="font-size: 2rem;"></mdui-icon>`)
|
$(this).html(`<mdui-icon name="broken_image" style="font-size: 2rem;"></mdui-icon>`)
|
||||||
$(this).attr('alt', '無法加載圖像')
|
$(this).attr('alt', '无法加载: ' + $(this).attr('alt'))
|
||||||
$(this).on('click', () => {
|
$(this).on('click', () => {
|
||||||
snackbar({
|
snackbar({
|
||||||
message: `圖片 (${src}) 無法加載!`,
|
message: `图片 (${src}) 无法加载!`,
|
||||||
placement: 'top'
|
placement: 'top'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ customElements.define('chat-video', class extends HTMLElement {
|
|||||||
}
|
}
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
this.style.display = 'block'
|
this.style.display = 'block'
|
||||||
const e = new DOMParser().parseFromString(`<video controls>視頻無法播放</video>`, 'text/html').body.firstChild as HTMLVideoElement
|
const e = new DOMParser().parseFromString(`<video controls></video>`, 'text/html').body.firstChild as HTMLVideoElement
|
||||||
e.style.width = "100%"
|
e.style.width = "100%"
|
||||||
e.style.height = "100%"
|
e.style.height = "100%"
|
||||||
e.style.borderRadius = "var(--mdui-shape-corner-medium)"
|
e.style.borderRadius = "var(--mdui-shape-corner-medium)"
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ export default function AddContactDialog({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<mdui-dialog close-on-overlay-click close-on-esc headline="添加對話" ref={addContactDialogRef}>
|
<mdui-dialog close-on-overlay-click close-on-esc headline="添加对话" ref={addContactDialogRef}>
|
||||||
<mdui-text-field clearable label="对话 ID / 用戶 ID / 用戶名" ref={inputTargetRef as any} onKeyDown={(event) => {
|
<mdui-text-field clearable label="对话 ID / 用户 ID / 用户名" ref={inputTargetRef as any} onKeyDown={(event) => {
|
||||||
if (event.key == 'Enter')
|
if (event.key == 'Enter')
|
||||||
addContact()
|
addContact()
|
||||||
}}></mdui-text-field>
|
}}></mdui-text-field>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export default function ChatInfoDialog({ chat, chatInfoDialogRef, openChatFragme
|
|||||||
target: chat.id,
|
target: chat.id,
|
||||||
})
|
})
|
||||||
if (re.code != 200)
|
if (re.code != 200)
|
||||||
return checkApiSuccessOrSncakbar(re, '獲取對話訊息失敗')
|
return checkApiSuccessOrSncakbar(re, '获取对话信息失败')
|
||||||
setChatInfo(re.data!.chat_info as Chat)
|
setChatInfo(re.data!.chat_info as Chat)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -32,20 +32,21 @@ export default function LoginDialog({
|
|||||||
password: CryptoJS.SHA256(password).toString(CryptoJS.enc.Hex),
|
password: CryptoJS.SHA256(password).toString(CryptoJS.enc.Hex),
|
||||||
})
|
})
|
||||||
|
|
||||||
if (checkApiSuccessOrSncakbar(re, "登錄失敗")) return
|
if (checkApiSuccessOrSncakbar(re, "登录失败")) return
|
||||||
|
|
||||||
data.access_token = re.data!.access_token as string
|
data.access_token = re.data!.access_token as string
|
||||||
|
data.refresh_token = re.data!.refresh_token as string
|
||||||
data.apply()
|
data.apply()
|
||||||
location.reload()
|
location.reload()
|
||||||
})
|
})
|
||||||
return (
|
return (
|
||||||
<mdui-dialog headline="登錄" ref={loginDialogRef}>
|
<mdui-dialog headline="登录" ref={loginDialogRef}>
|
||||||
|
|
||||||
<mdui-text-field label="用戶 ID / 用戶名" ref={loginInputAccountRef as any}></mdui-text-field>
|
<mdui-text-field label="用户 ID / 用户名" ref={loginInputAccountRef as any}></mdui-text-field>
|
||||||
<div style={{
|
<div style={{
|
||||||
height: "10px",
|
height: "10px",
|
||||||
}}></div>
|
}}></div>
|
||||||
<mdui-text-field label="密碼" type="password" toggle-password ref={loginInputPasswordRef as any}></mdui-text-field>
|
<mdui-text-field label="密码" type="password" toggle-password ref={loginInputPasswordRef as any}></mdui-text-field>
|
||||||
|
|
||||||
<mdui-button slot="action" variant="text" ref={registerButtonRef}>注册</mdui-button>
|
<mdui-button slot="action" variant="text" ref={registerButtonRef}>注册</mdui-button>
|
||||||
<mdui-button slot="action" variant="text" ref={loginButtonRef}>登录</mdui-button>
|
<mdui-button slot="action" variant="text" ref={loginButtonRef}>登录</mdui-button>
|
||||||
|
|||||||
@@ -30,9 +30,9 @@ export default function MyProfileDialog({
|
|||||||
avatar: file
|
avatar: file
|
||||||
})
|
})
|
||||||
|
|
||||||
if (checkApiSuccessOrSncakbar(re, "修改失敗")) return
|
if (checkApiSuccessOrSncakbar(re, "修改失败")) return
|
||||||
snackbar({
|
snackbar({
|
||||||
message: "修改成功 (刷新頁面以更新)",
|
message: "修改成功 (刷新页面以更新)",
|
||||||
placement: "top",
|
placement: "top",
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -74,8 +74,8 @@ export default function MyProfileDialog({
|
|||||||
marginBottom: "10px",
|
marginBottom: "10px",
|
||||||
}}></mdui-divider>
|
}}></mdui-divider>
|
||||||
<mdui-list-item icon="logout" rounded onClick={() => dialog({
|
<mdui-list-item icon="logout" rounded onClick={() => dialog({
|
||||||
headline: "退出登錄",
|
headline: "退出登录",
|
||||||
description: "確定要退出登錄嗎? (若您的賬號未設定 用戶名, 請無務必複製 用戶 ID, 以免丟失賬號!)",
|
description: "请确保在退出登录前, 设定了用户名或者已经记录下了用户 ID, 以免无法登录账号",
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
text: "取消",
|
text: "取消",
|
||||||
@@ -84,7 +84,7 @@ export default function MyProfileDialog({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "確定",
|
text: "确定",
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
data.access_token = ''
|
data.access_token = ''
|
||||||
data.apply()
|
data.apply()
|
||||||
@@ -93,7 +93,7 @@ export default function MyProfileDialog({
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
})}>退出登錄</mdui-list-item>
|
})}>退出登录</mdui-list-item>
|
||||||
</mdui-list>
|
</mdui-list>
|
||||||
</mdui-dialog>
|
</mdui-dialog>
|
||||||
{
|
{
|
||||||
@@ -103,7 +103,7 @@ export default function MyProfileDialog({
|
|||||||
<div style={{
|
<div style={{
|
||||||
display: "none"
|
display: "none"
|
||||||
}}>
|
}}>
|
||||||
<input type="file" name="選擇頭像" ref={chooseAvatarFileRef}
|
<input type="file" name="选择头像" ref={chooseAvatarFileRef}
|
||||||
accept="image/*" />
|
accept="image/*" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ export default function MyProfileDialog({
|
|||||||
width: '50px',
|
width: '50px',
|
||||||
height: '50px',
|
height: '50px',
|
||||||
}} />
|
}} />
|
||||||
<mdui-text-field variant="outlined" placeholder="昵稱" ref={editNickNameRef as any} style={{
|
<mdui-text-field variant="outlined" placeholder="昵称" ref={editNickNameRef as any} style={{
|
||||||
marginLeft: "15px",
|
marginLeft: "15px",
|
||||||
}} value={user?.nickname}></mdui-text-field>
|
}} value={user?.nickname}></mdui-text-field>
|
||||||
</div>
|
</div>
|
||||||
@@ -123,12 +123,12 @@ export default function MyProfileDialog({
|
|||||||
marginTop: "10px",
|
marginTop: "10px",
|
||||||
}}></mdui-divider>
|
}}></mdui-divider>
|
||||||
|
|
||||||
<mdui-text-field style={{ marginTop: "10px", }} variant="outlined" label="用戶 ID" value={user?.id || ''} readonly onClick={(e) => {
|
<mdui-text-field style={{ marginTop: "10px", }} variant="outlined" label="用户 ID" value={user?.id || ''} readonly onClick={(e) => {
|
||||||
const input = e.target as HTMLInputElement
|
const input = e.target as HTMLInputElement
|
||||||
input.select()
|
input.select()
|
||||||
input.setSelectionRange(0, 1145141919810)
|
input.setSelectionRange(0, 1145141919810)
|
||||||
}}></mdui-text-field>
|
}}></mdui-text-field>
|
||||||
<mdui-text-field style={{ marginTop: "20px", }} variant="outlined" label="用戶名" value={user?.username || ''} ref={editUserNameRef as any}></mdui-text-field>
|
<mdui-text-field style={{ marginTop: "20px", }} variant="outlined" label="用户名" value={user?.username || ''} ref={editUserNameRef as any}></mdui-text-field>
|
||||||
|
|
||||||
<mdui-button slot="action" variant="text" onClick={() => userProfileEditDialogRef.current!.open = false}>取消</mdui-button>
|
<mdui-button slot="action" variant="text" onClick={() => userProfileEditDialogRef.current!.open = false}>取消</mdui-button>
|
||||||
<mdui-button slot="action" variant="text" onClick={async () => {
|
<mdui-button slot="action" variant="text" onClick={async () => {
|
||||||
@@ -138,9 +138,9 @@ export default function MyProfileDialog({
|
|||||||
username: editUserNameRef.current?.value,
|
username: editUserNameRef.current?.value,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (checkApiSuccessOrSncakbar(re, "修改失敗")) return
|
if (checkApiSuccessOrSncakbar(re, "修改失败")) return
|
||||||
snackbar({
|
snackbar({
|
||||||
message: "修改成功 (刷新頁面以更新)",
|
message: "修改成功 (刷新页面以更新)",
|
||||||
placement: "top",
|
placement: "top",
|
||||||
})
|
})
|
||||||
userProfileEditDialogRef.current!.open = false
|
userProfileEditDialogRef.current!.open = false
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export default function RegisterDialog({
|
|||||||
password: CryptoJS.SHA256(registerInputPasswordRef.current!.value).toString(CryptoJS.enc.Hex),
|
password: CryptoJS.SHA256(registerInputPasswordRef.current!.value).toString(CryptoJS.enc.Hex),
|
||||||
})
|
})
|
||||||
|
|
||||||
if (checkApiSuccessOrSncakbar(re, "注冊失敗")) return
|
if (checkApiSuccessOrSncakbar(re, "注册失败")) return
|
||||||
loginInputAccountRef.current!.value = username == "" ? re.data!.userid as string : username
|
loginInputAccountRef.current!.value = username == "" ? re.data!.userid as string : username
|
||||||
loginInputPasswordRef.current!.value = registerInputPasswordRef.current!.value
|
loginInputPasswordRef.current!.value = registerInputPasswordRef.current!.value
|
||||||
|
|
||||||
@@ -43,25 +43,25 @@ export default function RegisterDialog({
|
|||||||
registerInputPasswordRef.current!.value = ""
|
registerInputPasswordRef.current!.value = ""
|
||||||
registerDialogRef.current!.open = false
|
registerDialogRef.current!.open = false
|
||||||
snackbar({
|
snackbar({
|
||||||
message: "注冊成功!",
|
message: "注册成功!",
|
||||||
placement: "top",
|
placement: "top",
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
return (
|
return (
|
||||||
<mdui-dialog headline="注冊" ref={registerDialogRef}>
|
<mdui-dialog headline="注册" ref={registerDialogRef}>
|
||||||
|
|
||||||
<mdui-text-field label="用戶名 (可選)" ref={registerInputUserNameRef as any}></mdui-text-field>
|
<mdui-text-field label="用户名 (可选)" ref={registerInputUserNameRef as any}></mdui-text-field>
|
||||||
<div style={{
|
<div style={{
|
||||||
height: "10px",
|
height: "10px",
|
||||||
}}></div>
|
}}></div>
|
||||||
<mdui-text-field label="昵稱" ref={registerInputNickNameRef as any}></mdui-text-field>
|
<mdui-text-field label="昵称" ref={registerInputNickNameRef as any}></mdui-text-field>
|
||||||
<div style={{
|
<div style={{
|
||||||
height: "10px",
|
height: "10px",
|
||||||
}}></div>
|
}}></div>
|
||||||
<mdui-text-field label="密码" type="password" toggle-password ref={registerInputPasswordRef as any}></mdui-text-field>
|
<mdui-text-field label="密码" type="password" toggle-password ref={registerInputPasswordRef as any}></mdui-text-field>
|
||||||
|
|
||||||
<mdui-button slot="action" variant="text" ref={registerBackButtonRef}>返回</mdui-button>
|
<mdui-button slot="action" variant="text" ref={registerBackButtonRef}>返回</mdui-button>
|
||||||
<mdui-button slot="action" variant="text" ref={doRegisterButtonRef}>注冊</mdui-button>
|
<mdui-button slot="action" variant="text" ref={doRegisterButtonRef}>注册</mdui-button>
|
||||||
</mdui-dialog>
|
</mdui-dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,7 @@ export default function ContactsList({
|
|||||||
token: data.access_token,
|
token: data.access_token,
|
||||||
})
|
})
|
||||||
if (re.code != 200)
|
if (re.code != 200)
|
||||||
return checkApiSuccessOrSncakbar(re, "获取對話列表失败")
|
return checkApiSuccessOrSncakbar(re, "获取所有对话列表失败")
|
||||||
|
|
||||||
setContactsList(re.data!.contacts_list as Chat[])
|
setContactsList(re.data!.contacts_list as Chat[])
|
||||||
}
|
}
|
||||||
@@ -61,7 +61,7 @@ export default function ContactsList({
|
|||||||
<mdui-list-item rounded style={{
|
<mdui-list-item rounded style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
marginTop: '13px',
|
marginTop: '13px',
|
||||||
}} icon="person_add" onClick={() => addContactDialogRef.current!.open = true}>添加對話</mdui-list-item>
|
}} icon="person_add" onClick={() => addContactDialogRef.current!.open = true}>添加对话</mdui-list-item>
|
||||||
<mdui-list-item rounded style={{
|
<mdui-list-item rounded style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
}} icon="group_add" onClick={() => createGroupDialogRef.current!.open = true}>创建群组</mdui-list-item>
|
}} icon="group_add" onClick={() => createGroupDialogRef.current!.open = true}>创建群组</mdui-list-item>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export default function RecentsList({
|
|||||||
token: data.access_token,
|
token: data.access_token,
|
||||||
})
|
})
|
||||||
if (re.code != 200)
|
if (re.code != 200)
|
||||||
return checkApiSuccessOrSncakbar(re, "获取最近對話列表失败")
|
return checkApiSuccessOrSncakbar(re, "获取最近对话列表失败")
|
||||||
|
|
||||||
setRecentsList(re.data!.recent_chats as RecentChat[])
|
setRecentsList(re.data!.recent_chats as RecentChat[])
|
||||||
}
|
}
|
||||||
|
|||||||
23
readme.md
23
readme.md
@@ -1,8 +1,8 @@
|
|||||||
## TheWhiteSilk
|
## 铃之椅
|
||||||
|
|
||||||
這一個即時通訊項目————簡單, 輕量, 純粹, 時而天真
|
一个即时通讯项目——简单, 轻量, 纯粹, 时而天真
|
||||||
|
|
||||||
後續會考慮改名為月之鴿
|
_仍在积极开发中. 目前是第四代, 版本代号为: the_white_silk_
|
||||||
|
|
||||||
### 目前實現了什麽?
|
### 目前實現了什麽?
|
||||||
|
|
||||||
@@ -13,16 +13,17 @@
|
|||||||
- [x] 收發消息
|
- [x] 收發消息
|
||||||
- [x] 富文本 (based on Marked)
|
- [x] 富文本 (based on Marked)
|
||||||
- [x] 圖片
|
- [x] 圖片
|
||||||
- [ ] 視頻
|
- [x] 視頻
|
||||||
- [ ] 文件
|
- [x] 文件
|
||||||
- [ ] 撤回消息
|
- [ ] 撤回消息
|
||||||
- [ ] 修改消息
|
- [ ] 修改消息
|
||||||
|
|
||||||
- 對話
|
- 對話
|
||||||
- [ ] _**最近對話**_
|
- [x] 最近對話
|
||||||
- [x] 添加對話
|
- [x] 添加對話
|
||||||
- [x] 添加用戶
|
- [x] 添加用戶
|
||||||
- [ ] 添加群組 (伺服器端群組都還沒做, 想什麽呢)
|
- [x] 添加群組
|
||||||
|
- [ ] 群组管理
|
||||||
|
|
||||||
- 賬號
|
- 賬號
|
||||||
- [x] 登錄注冊 (廢話)
|
- [x] 登錄注冊 (廢話)
|
||||||
@@ -41,7 +42,7 @@
|
|||||||
|
|
||||||
- 基本對話類型
|
- 基本對話類型
|
||||||
- [x] 雙用戶私聊
|
- [x] 雙用戶私聊
|
||||||
- [ ] 群組
|
- [x] 群組
|
||||||
|
|
||||||
- 消息
|
- 消息
|
||||||
- [x] 收發消息
|
- [x] 收發消息
|
||||||
@@ -49,7 +50,7 @@
|
|||||||
- [ ] 修改消息
|
- [ ] 修改消息
|
||||||
|
|
||||||
- 對話
|
- 對話
|
||||||
- [ ] _**最近對話**_
|
- [x] 最近對話
|
||||||
- [x] 添加對話
|
- [x] 添加對話
|
||||||
|
|
||||||
- 賬號
|
- 賬號
|
||||||
@@ -64,8 +65,8 @@
|
|||||||
### 伺服器端運行
|
### 伺服器端運行
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://codeberg.org/CrescentLeaf/TheWhiteSilk
|
git clone https://codeberg.org/LingChair/LingChair
|
||||||
cd TheWhiteSilk
|
cd LingChair
|
||||||
# 編譯前端網頁
|
# 編譯前端網頁
|
||||||
deno task build
|
deno task build
|
||||||
# 運行服務
|
# 運行服務
|
||||||
|
|||||||
@@ -75,17 +75,17 @@ export default class ApiManager {
|
|||||||
|
|
||||||
socket.on('disconnect', (_reason) => {
|
socket.on('disconnect', (_reason) => {
|
||||||
if (clientInfo.userId == '')
|
if (clientInfo.userId == '')
|
||||||
console.log(chalk.yellow('[斷]') + ` ${ip} disconnected`)
|
console.log(chalk.yellow('[断]') + ` ${ip} disconnected`)
|
||||||
else {
|
else {
|
||||||
console.log(chalk.green('[斷]') + ` ${ip} disconnected`)
|
console.log(chalk.green('[断]') + ` ${ip} disconnected`)
|
||||||
delete this.clients[clientInfo.userId][deviceId + '_' + sessionId]
|
delete this.clients[clientInfo.userId][deviceId + '_' + sessionId]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
console.log(chalk.yellow('[連]') + ` ${ip} connected`)
|
console.log(chalk.yellow('[连]') + ` ${ip} connected`)
|
||||||
|
|
||||||
socket.on("The_White_Silk", async (name: string, args: { [key: string]: unknown }, callback_: (ret: ApiCallbackMessage) => void) => {
|
socket.on("The_White_Silk", async (name: string, args: { [key: string]: unknown }, callback_: (ret: ApiCallbackMessage) => void) => {
|
||||||
function callback(ret: ApiCallbackMessage) {
|
function callback(ret: ApiCallbackMessage) {
|
||||||
console.log(chalk.blue('[發]') + ` ${ip} <- ${ret.code == 200 ? chalk.green(ret.msg) : chalk.red(ret.msg)} [${ret.code}]${ret.data ? (' <extras: ' + stringifyNotIncludeArrayBuffer(ret.data) + '>') : ''}`)
|
console.log(chalk.blue('[发]') + ` ${ip} <- ${ret.code == 200 ? chalk.green(ret.msg) : chalk.red(ret.msg)} [${ret.code}]${ret.data ? (' <extras: ' + stringifyNotIncludeArrayBuffer(ret.data) + '>') : ''}`)
|
||||||
return callback_(ret)
|
return callback_(ret)
|
||||||
}
|
}
|
||||||
async function checkIsPromiseAndAwait(value: Promise<unknown> | unknown) {
|
async function checkIsPromiseAndAwait(value: Promise<unknown> | unknown) {
|
||||||
@@ -106,11 +106,11 @@ export default class ApiManager {
|
|||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const err = e as Error
|
const err = e as Error
|
||||||
console.log(chalk.yellow('[壞]') + ` ${err.message} (${err.stack})`)
|
console.log(chalk.yellow('[坏]') + ` ${err.message} (${err.stack})`)
|
||||||
try {
|
try {
|
||||||
callback({
|
callback({
|
||||||
code: err instanceof DataWrongError ? 400 : 500,
|
code: err instanceof DataWrongError ? 400 : 500,
|
||||||
msg: "錯誤: " + err.message
|
msg: "错误: " + err.message
|
||||||
})
|
})
|
||||||
} catch (_e) { }
|
} catch (_e) { }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,24 +22,24 @@ export default class ChatApi extends BaseApi {
|
|||||||
*/
|
*/
|
||||||
this.registerEvent("Chat.getInfo", (args, { deviceId }) => {
|
this.registerEvent("Chat.getInfo", (args, { deviceId }) => {
|
||||||
if (this.checkArgsMissing(args, ['token', 'target'])) return {
|
if (this.checkArgsMissing(args, ['token', 'target'])) return {
|
||||||
msg: "參數缺失",
|
msg: "参数缺失",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = TokenManager.decode(args.token as string)
|
const token = TokenManager.decode(args.token as string)
|
||||||
if (!this.checkToken(token, deviceId)) return {
|
if (!this.checkToken(token, deviceId)) return {
|
||||||
code: 401,
|
code: 401,
|
||||||
msg: "令牌無效",
|
msg: "令牌无效",
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
msg: "對話不存在",
|
msg: "对话不存在",
|
||||||
}
|
}
|
||||||
if (!UserChatLinker.checkUserIsLinkedToChat(token.author, chat!.bean.id)) return {
|
if (!UserChatLinker.checkUserIsLinkedToChat(token.author, chat!.bean.id)) return {
|
||||||
code: 400,
|
code: 400,
|
||||||
msg: "用戶無權訪問該對話",
|
msg: "用户无权访问此对话",
|
||||||
}
|
}
|
||||||
|
|
||||||
// 私聊
|
// 私聊
|
||||||
@@ -83,24 +83,24 @@ export default class ChatApi extends BaseApi {
|
|||||||
*/
|
*/
|
||||||
this.registerEvent("Chat.sendMessage", (args, { deviceId }) => {
|
this.registerEvent("Chat.sendMessage", (args, { deviceId }) => {
|
||||||
if (this.checkArgsMissing(args, ['token', 'target', 'text'])) return {
|
if (this.checkArgsMissing(args, ['token', 'target', 'text'])) return {
|
||||||
msg: "參數缺失",
|
msg: "参数缺失",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = TokenManager.decode(args.token as string)
|
const token = TokenManager.decode(args.token as string)
|
||||||
if (!this.checkToken(token, deviceId)) return {
|
if (!this.checkToken(token, deviceId)) return {
|
||||||
code: 401,
|
code: 401,
|
||||||
msg: "令牌無效",
|
msg: "令牌无效",
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
msg: "對話不存在",
|
msg: "对话不存在",
|
||||||
}
|
}
|
||||||
if (!UserChatLinker.checkUserIsLinkedToChat(token.author, chat!.bean.id)) return {
|
if (!UserChatLinker.checkUserIsLinkedToChat(token.author, chat!.bean.id)) return {
|
||||||
code: 400,
|
code: 400,
|
||||||
msg: "用戶無權訪問該對話",
|
msg: "用户无权访问此对话",
|
||||||
}
|
}
|
||||||
|
|
||||||
const msg = {
|
const msg = {
|
||||||
@@ -142,24 +142,24 @@ export default class ChatApi extends BaseApi {
|
|||||||
*/
|
*/
|
||||||
this.registerEvent("Chat.getMessageHistory", (args, { deviceId }) => {
|
this.registerEvent("Chat.getMessageHistory", (args, { deviceId }) => {
|
||||||
if (this.checkArgsMissing(args, ['token', 'target', 'page'])) return {
|
if (this.checkArgsMissing(args, ['token', 'target', 'page'])) return {
|
||||||
msg: "參數缺失",
|
msg: "参数缺失",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = TokenManager.decode(args.token as string)
|
const token = TokenManager.decode(args.token as string)
|
||||||
if (!this.checkToken(token, deviceId)) return {
|
if (!this.checkToken(token, deviceId)) return {
|
||||||
code: 401,
|
code: 401,
|
||||||
msg: "令牌無效",
|
msg: "令牌无效",
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
msg: "對話不存在",
|
msg: "对话不存在",
|
||||||
}
|
}
|
||||||
if (!UserChatLinker.checkUserIsLinkedToChat(token.author, chat!.bean.id)) return {
|
if (!UserChatLinker.checkUserIsLinkedToChat(token.author, chat!.bean.id)) return {
|
||||||
code: 400,
|
code: 400,
|
||||||
msg: "用戶無權訪問該對話",
|
msg: "用户无权访问此对话",
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -179,24 +179,24 @@ export default class ChatApi extends BaseApi {
|
|||||||
*/
|
*/
|
||||||
this.registerEvent("Chat.uploadFile", async (args, { deviceId }) => {
|
this.registerEvent("Chat.uploadFile", async (args, { deviceId }) => {
|
||||||
if (this.checkArgsMissing(args, ['token', 'target', 'data', 'file_name'])) return {
|
if (this.checkArgsMissing(args, ['token', 'target', 'data', 'file_name'])) return {
|
||||||
msg: "參數缺失",
|
msg: "参数缺失",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = TokenManager.decode(args.token as string)
|
const token = TokenManager.decode(args.token as string)
|
||||||
if (!this.checkToken(token, deviceId)) return {
|
if (!this.checkToken(token, deviceId)) return {
|
||||||
code: 401,
|
code: 401,
|
||||||
msg: "令牌無效",
|
msg: "令牌无效",
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
msg: "對話不存在",
|
msg: "对话不存在",
|
||||||
}
|
}
|
||||||
if (!UserChatLinker.checkUserIsLinkedToChat(token.author, chat!.bean.id)) return {
|
if (!UserChatLinker.checkUserIsLinkedToChat(token.author, chat!.bean.id)) return {
|
||||||
code: 400,
|
code: 400,
|
||||||
msg: "用戶無權訪問該對話",
|
msg: "用户无权访问此对话",
|
||||||
}
|
}
|
||||||
|
|
||||||
const file = await FileManager.uploadFile(args.file_name as string, args.data as Buffer<ArrayBufferLike>, args.target as string)
|
const file = await FileManager.uploadFile(args.file_name as string, args.data as Buffer<ArrayBufferLike>, args.target as string)
|
||||||
@@ -216,20 +216,20 @@ export default class ChatApi extends BaseApi {
|
|||||||
*/
|
*/
|
||||||
this.registerEvent("Chat.getIdForPrivate", (args, { deviceId }) => {
|
this.registerEvent("Chat.getIdForPrivate", (args, { deviceId }) => {
|
||||||
if (this.checkArgsMissing(args, ['token', 'target'])) return {
|
if (this.checkArgsMissing(args, ['token', 'target'])) return {
|
||||||
msg: "參數缺失",
|
msg: "参数缺失",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = TokenManager.decode(args.token as string)
|
const token = TokenManager.decode(args.token as string)
|
||||||
if (!this.checkToken(token, deviceId)) return {
|
if (!this.checkToken(token, deviceId)) return {
|
||||||
code: 401,
|
code: 401,
|
||||||
msg: "令牌無效",
|
msg: "令牌无效",
|
||||||
}
|
}
|
||||||
const user = User.findById(token.author) as User
|
const user = User.findById(token.author) as User
|
||||||
const targetUser = User.findById(args.target as string) as User
|
const targetUser = User.findById(args.target as string) as User
|
||||||
if (targetUser == null) {
|
if (targetUser == null) {
|
||||||
return {
|
return {
|
||||||
msg: "找不到用戶",
|
msg: "找不到用户",
|
||||||
code: 404,
|
code: 404,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -251,18 +251,18 @@ export default class ChatApi extends BaseApi {
|
|||||||
*/
|
*/
|
||||||
this.registerEvent("Chat.createGroup", (args, { deviceId }) => {
|
this.registerEvent("Chat.createGroup", (args, { deviceId }) => {
|
||||||
if (this.checkArgsMissing(args, ['token', 'title'])) return {
|
if (this.checkArgsMissing(args, ['token', 'title'])) return {
|
||||||
msg: "參數缺失",
|
msg: "参数缺失",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
if (this.checkArgsEmpty(args, ['title'])) return {
|
if (this.checkArgsEmpty(args, ['title'])) return {
|
||||||
msg: "參數不得為空",
|
msg: "参数不得为空",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = TokenManager.decode(args.token as string)
|
const token = TokenManager.decode(args.token as string)
|
||||||
if (!this.checkToken(token, deviceId)) return {
|
if (!this.checkToken(token, deviceId)) return {
|
||||||
code: 401,
|
code: 401,
|
||||||
msg: "令牌無效",
|
msg: "令牌无效",
|
||||||
}
|
}
|
||||||
const user = User.findById(token.author) as User
|
const user = User.findById(token.author) as User
|
||||||
|
|
||||||
@@ -291,14 +291,14 @@ export default class ChatApi extends BaseApi {
|
|||||||
*/
|
*/
|
||||||
this.registerEvent("Chat.getAnotherUserIdFromPrivate", (args, { deviceId }) => {
|
this.registerEvent("Chat.getAnotherUserIdFromPrivate", (args, { deviceId }) => {
|
||||||
if (this.checkArgsMissing(args, ['token', 'target'])) return {
|
if (this.checkArgsMissing(args, ['token', 'target'])) return {
|
||||||
msg: "參數缺失",
|
msg: "参数缺失",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = TokenManager.decode(args.token as string)
|
const token = TokenManager.decode(args.token as string)
|
||||||
if (!this.checkToken(token, deviceId)) return {
|
if (!this.checkToken(token, deviceId)) return {
|
||||||
code: 401,
|
code: 401,
|
||||||
msg: "令牌無效",
|
msg: "令牌无效",
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = User.findById(token.author) as User
|
const user = User.findById(token.author) as User
|
||||||
@@ -306,11 +306,11 @@ export default class ChatApi extends BaseApi {
|
|||||||
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,
|
||||||
msg: "對話不存在",
|
msg: "对话不存在",
|
||||||
}
|
}
|
||||||
if (!UserChatLinker.checkUserIsLinkedToChat(token.author, chat!.bean.id)) return {
|
if (!UserChatLinker.checkUserIsLinkedToChat(token.author, chat!.bean.id)) return {
|
||||||
code: 400,
|
code: 400,
|
||||||
msg: "用戶無權訪問該對話",
|
msg: "用户无权访问此对话",
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chat.bean.type == 'private')
|
if (chat.bean.type == 'private')
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export default class EventStorer {
|
|||||||
CREATE TABLE IF NOT EXISTS ${this.getTableName()} (
|
CREATE TABLE IF NOT EXISTS ${this.getTableName()} (
|
||||||
/* 序号 */ count INTEGER PRIMARY KEY AUTOINCREMENT,
|
/* 序号 */ count INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
/* 事件 */ event_name TEXT NOT NULL,
|
/* 事件 */ event_name TEXT NOT NULL,
|
||||||
/* 數據 */ data TEXT NOT NULL,
|
/* 数据 */ data TEXT NOT NULL,
|
||||||
);
|
);
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
|
import TokenType from "./TokenType.ts"
|
||||||
|
|
||||||
export default interface Token {
|
export default interface Token {
|
||||||
author: string
|
author: string
|
||||||
auth: string
|
auth: string
|
||||||
made_time: number
|
made_time: number
|
||||||
expired_time: number
|
expired_time: number
|
||||||
device_id: string
|
device_id: string
|
||||||
|
type: TokenType
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ import config from "../config.ts"
|
|||||||
import User from "../data/User.ts"
|
import User from "../data/User.ts"
|
||||||
import crypto from 'node:crypto'
|
import crypto from 'node:crypto'
|
||||||
import Token from "./Token.ts"
|
import Token from "./Token.ts"
|
||||||
|
import TokenType from "./TokenType.ts"
|
||||||
|
|
||||||
function normalizeKey(key: string, keyLength = 32) {
|
function normalizeKey(key: string, keyLength = 32) {
|
||||||
const hash = crypto.createHash('sha256')
|
const hash = crypto.createHash('sha256')
|
||||||
@@ -31,38 +32,28 @@ export default class TokenManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static make(user: User, time_: number | null | undefined, device_id: string) {
|
static make(user: User, time_: number | null | undefined, device_id: string, type: 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 + (1 * 1000 * 60 * 60 * 24),
|
expired_time: time + (type == 'access_token' ? (1000 * 60 * 60 * 2) : (40 * 1000 * 60 * 60 * 24)),
|
||||||
device_id: device_id
|
device_id: device_id,
|
||||||
|
type
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* 獲取新令牌
|
|
||||||
* 注意: 只驗證用戶, 不驗證令牌有效性!
|
|
||||||
*/
|
|
||||||
static makeNewer(user: User, token: string) {
|
|
||||||
if (this.check(user, token))
|
|
||||||
return this.make(user, Date.now() + (1 * 1000 * 60 * 60 * 24), this.decode(token).device_id)
|
|
||||||
}
|
|
||||||
static check(user: User, token: string) {
|
|
||||||
const tk = this.decode(token)
|
|
||||||
|
|
||||||
return this.makeAuth(user) == tk.auth
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* 嚴格檢驗令牌: 時間, 用戶, (設備 ID)
|
* 嚴格檢驗令牌: 時間, 用戶, (設備 ID)
|
||||||
*/
|
*/
|
||||||
static checkToken(token: Token, deviceId?: string) {
|
static checkToken(token: Token, deviceId?: string, type: TokenType = 'access_token') {
|
||||||
if (token.expired_time < Date.now()) return false
|
if (token.expired_time < Date.now()) return false
|
||||||
if (!token.author || !User.findById(token.author)) return false
|
if (!token.author || !User.findById(token.author)) return false
|
||||||
if (deviceId != null)
|
if (deviceId != null)
|
||||||
if (token.device_id != deviceId)
|
if (token.device_id != deviceId)
|
||||||
return false
|
return false
|
||||||
|
if (token.type != type)
|
||||||
|
return false
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
server/api/TokenType.ts
Normal file
3
server/api/TokenType.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
type TokenType = "access_token" | "refresh_token"
|
||||||
|
|
||||||
|
export default TokenType
|
||||||
@@ -15,7 +15,7 @@ export default class UserApi extends BaseApi {
|
|||||||
// 驗證
|
// 驗證
|
||||||
this.registerEvent("User.auth", (args, clientInfo) => {
|
this.registerEvent("User.auth", (args, clientInfo) => {
|
||||||
if (this.checkArgsMissing(args, ['access_token'])) return {
|
if (this.checkArgsMissing(args, ['access_token'])) return {
|
||||||
msg: "參數缺失",
|
msg: "参数缺失",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
const { deviceId, ip, socket, sessionId } = clientInfo
|
const { deviceId, ip, socket, sessionId } = clientInfo
|
||||||
@@ -23,20 +23,20 @@ export default class UserApi extends BaseApi {
|
|||||||
const access_token = TokenManager.decode(args.access_token as string)
|
const access_token = TokenManager.decode(args.access_token as string)
|
||||||
|
|
||||||
if (access_token.expired_time < Date.now()) return {
|
if (access_token.expired_time < Date.now()) return {
|
||||||
msg: "登錄令牌失效",
|
msg: "登录令牌失效",
|
||||||
code: 401,
|
code: 401,
|
||||||
}
|
}
|
||||||
if (!access_token.author || !User.findById(access_token.author)) return {
|
if (!access_token.author || !User.findById(access_token.author)) return {
|
||||||
msg: "賬號不存在",
|
msg: "账号不存在",
|
||||||
code: 401,
|
code: 401,
|
||||||
}
|
}
|
||||||
if (access_token.device_id != deviceId) return {
|
if (access_token.device_id != deviceId) return {
|
||||||
msg: "驗證失敗",
|
msg: "验证失败",
|
||||||
code: 401,
|
code: 401,
|
||||||
}
|
}
|
||||||
|
|
||||||
clientInfo.userId = access_token.author
|
clientInfo.userId = access_token.author
|
||||||
console.log(chalk.green('[驗]') + ` ${access_token.author} authed on Client ${deviceId} (ip = ${ip})`)
|
console.log(chalk.green('[验]') + ` ${access_token.author} authed on Client ${deviceId} (ip = ${ip})`)
|
||||||
if (ApiManager.clients[clientInfo.userId] == null) ApiManager.clients[clientInfo.userId] = {
|
if (ApiManager.clients[clientInfo.userId] == null) ApiManager.clients[clientInfo.userId] = {
|
||||||
[deviceId + '_' + sessionId]: socket
|
[deviceId + '_' + sessionId]: socket
|
||||||
}
|
}
|
||||||
@@ -50,7 +50,50 @@ export default class UserApi extends BaseApi {
|
|||||||
const err = e as Error
|
const err = e as Error
|
||||||
if (err.message.indexOf("JSON") != -1)
|
if (err.message.indexOf("JSON") != -1)
|
||||||
return {
|
return {
|
||||||
msg: "無效的登錄令牌",
|
msg: "无效的用户令牌",
|
||||||
|
code: 401,
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 刷新访问令牌
|
||||||
|
this.registerEvent("User.refreshAccessToken", (args, clientInfo) => {
|
||||||
|
if (this.checkArgsMissing(args, ['refresh_token'])) return {
|
||||||
|
msg: "参数缺失",
|
||||||
|
code: 400,
|
||||||
|
}
|
||||||
|
const { deviceId } = clientInfo
|
||||||
|
try {
|
||||||
|
const refresh_token = TokenManager.decode(args.refresh_token as string)
|
||||||
|
|
||||||
|
if (refresh_token.expired_time < Date.now()) return {
|
||||||
|
msg: "登录令牌失效",
|
||||||
|
code: 401,
|
||||||
|
}
|
||||||
|
if (!refresh_token.author || !User.findById(refresh_token.author)) return {
|
||||||
|
msg: "账号不存在",
|
||||||
|
code: 401,
|
||||||
|
}
|
||||||
|
if (refresh_token.device_id != deviceId) return {
|
||||||
|
msg: "验证失败",
|
||||||
|
code: 401,
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = User.findById(refresh_token.author) as User
|
||||||
|
|
||||||
|
return {
|
||||||
|
msg: "成功",
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
access_token: TokenManager.make(user, null, deviceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
const err = e as Error
|
||||||
|
if (err.message.indexOf("JSON") != -1)
|
||||||
|
return {
|
||||||
|
msg: "无效的用户令牌",
|
||||||
code: 401,
|
code: 401,
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -60,17 +103,17 @@ export default class UserApi extends BaseApi {
|
|||||||
// 登錄
|
// 登錄
|
||||||
this.registerEvent("User.login", (args, { deviceId }) => {
|
this.registerEvent("User.login", (args, { deviceId }) => {
|
||||||
if (this.checkArgsMissing(args, ['account', 'password'])) return {
|
if (this.checkArgsMissing(args, ['account', 'password'])) return {
|
||||||
msg: "參數缺失",
|
msg: "参数缺失",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
if (this.checkArgsEmpty(args, ['account', 'password'])) return {
|
if (this.checkArgsEmpty(args, ['account', 'password'])) return {
|
||||||
msg: "參數不得為空",
|
msg: "参数不得为空",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = User.findByAccount(args.account as string) as User
|
const user = User.findByAccount(args.account as string) as User
|
||||||
if (user == null) return {
|
if (user == null) return {
|
||||||
msg: "賬號或密碼錯誤",
|
msg: "账号或密码错误",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,23 +121,24 @@ export default class UserApi extends BaseApi {
|
|||||||
msg: "成功",
|
msg: "成功",
|
||||||
code: 200,
|
code: 200,
|
||||||
data: {
|
data: {
|
||||||
access_token: TokenManager.make(user, null, deviceId)
|
refresh_token: TokenManager.make(user, null, deviceId, 'refresh_token'),
|
||||||
|
access_token: TokenManager.make(user, null, deviceId),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
msg: "賬號或密碼錯誤",
|
msg: "账号或密码错误",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// 注冊
|
// 注冊
|
||||||
this.registerEvent("User.register", (args, { deviceId }) => {
|
this.registerEvent("User.register", (args, { deviceId }) => {
|
||||||
if (this.checkArgsMissing(args, ['nickname', 'password'])) return {
|
if (this.checkArgsMissing(args, ['nickname', 'password'])) return {
|
||||||
msg: "參數缺失",
|
msg: "参数缺失",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
if (this.checkArgsEmpty(args, ['nickname', 'password'])) return {
|
if (this.checkArgsEmpty(args, ['nickname', 'password'])) return {
|
||||||
msg: "參數不得為空",
|
msg: "参数不得为空",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,17 +164,17 @@ export default class UserApi extends BaseApi {
|
|||||||
// 更新頭像
|
// 更新頭像
|
||||||
this.registerEvent("User.setAvatar", (args, { deviceId }) => {
|
this.registerEvent("User.setAvatar", (args, { deviceId }) => {
|
||||||
if (this.checkArgsMissing(args, ['avatar', 'token'])) return {
|
if (this.checkArgsMissing(args, ['avatar', 'token'])) return {
|
||||||
msg: "參數缺失",
|
msg: "参数缺失",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
if (!(args.avatar instanceof Buffer)) return {
|
if (!(args.avatar instanceof Buffer)) return {
|
||||||
msg: "參數不合法",
|
msg: "参数不合法",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
const token = TokenManager.decode(args.token as string)
|
const token = TokenManager.decode(args.token as string)
|
||||||
if (!this.checkToken(token, deviceId)) return {
|
if (!this.checkToken(token, deviceId)) return {
|
||||||
code: 401,
|
code: 401,
|
||||||
msg: "令牌無效",
|
msg: "令牌无效",
|
||||||
}
|
}
|
||||||
|
|
||||||
const avatar: Buffer = args.avatar as Buffer
|
const avatar: Buffer = args.avatar as Buffer
|
||||||
@@ -145,14 +189,14 @@ export default class UserApi extends BaseApi {
|
|||||||
// 更新資料
|
// 更新資料
|
||||||
this.registerEvent("User.updateProfile", (args, { deviceId }) => {
|
this.registerEvent("User.updateProfile", (args, { deviceId }) => {
|
||||||
if (this.checkArgsMissing(args, ['token'])) return {
|
if (this.checkArgsMissing(args, ['token'])) return {
|
||||||
msg: "參數缺失",
|
msg: "参数缺失",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = TokenManager.decode(args.token as string)
|
const token = TokenManager.decode(args.token as string)
|
||||||
if (!this.checkToken(token, deviceId)) return {
|
if (!this.checkToken(token, deviceId)) return {
|
||||||
code: 401,
|
code: 401,
|
||||||
msg: "令牌無效",
|
msg: "令牌无效",
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = User.findById(token.author)
|
const user = User.findById(token.author)
|
||||||
@@ -169,14 +213,14 @@ export default class UserApi extends BaseApi {
|
|||||||
// 獲取用戶信息
|
// 獲取用戶信息
|
||||||
this.registerEvent("User.getMyInfo", (args, { deviceId }) => {
|
this.registerEvent("User.getMyInfo", (args, { deviceId }) => {
|
||||||
if (this.checkArgsMissing(args, ['token'])) return {
|
if (this.checkArgsMissing(args, ['token'])) return {
|
||||||
msg: "參數缺失",
|
msg: "参数缺失",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = TokenManager.decode(args.token as string)
|
const token = TokenManager.decode(args.token as string)
|
||||||
if (!this.checkToken(token, deviceId)) return {
|
if (!this.checkToken(token, deviceId)) return {
|
||||||
code: 401,
|
code: 401,
|
||||||
msg: "令牌無效",
|
msg: "令牌无效",
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = User.findById(token.author)
|
const user = User.findById(token.author)
|
||||||
@@ -195,14 +239,14 @@ export default class UserApi extends BaseApi {
|
|||||||
// 獲取最近对话列表
|
// 獲取最近对话列表
|
||||||
this.registerEvent("User.getMyRecentChats", (args, { deviceId }) => {
|
this.registerEvent("User.getMyRecentChats", (args, { deviceId }) => {
|
||||||
if (this.checkArgsMissing(args, ['token'])) return {
|
if (this.checkArgsMissing(args, ['token'])) return {
|
||||||
msg: "參數缺失",
|
msg: "参数缺失",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = TokenManager.decode(args.token as string)
|
const token = TokenManager.decode(args.token as string)
|
||||||
if (!this.checkToken(token, deviceId)) return {
|
if (!this.checkToken(token, deviceId)) return {
|
||||||
code: 401,
|
code: 401,
|
||||||
msg: "令牌無效",
|
msg: "令牌无效",
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = User.findById(token.author) as User
|
const user = User.findById(token.author) as User
|
||||||
@@ -229,14 +273,14 @@ export default class UserApi extends BaseApi {
|
|||||||
// 獲取聯絡人列表
|
// 獲取聯絡人列表
|
||||||
this.registerEvent("User.getMyContacts", (args, { deviceId }) => {
|
this.registerEvent("User.getMyContacts", (args, { deviceId }) => {
|
||||||
if (this.checkArgsMissing(args, ['token'])) return {
|
if (this.checkArgsMissing(args, ['token'])) return {
|
||||||
msg: "參數缺失",
|
msg: "参数缺失",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = TokenManager.decode(args.token as string)
|
const token = TokenManager.decode(args.token as string)
|
||||||
if (!this.checkToken(token, deviceId)) return {
|
if (!this.checkToken(token, deviceId)) return {
|
||||||
code: 401,
|
code: 401,
|
||||||
msg: "令牌無效",
|
msg: "令牌无效",
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = User.findById(token.author) as User
|
const user = User.findById(token.author) as User
|
||||||
@@ -262,14 +306,14 @@ export default class UserApi extends BaseApi {
|
|||||||
// 添加聯絡人
|
// 添加聯絡人
|
||||||
this.registerEvent("User.addContact", (args, { deviceId }) => {
|
this.registerEvent("User.addContact", (args, { deviceId }) => {
|
||||||
if (this.checkArgsMissing(args, ['token', 'target'])) return {
|
if (this.checkArgsMissing(args, ['token', 'target'])) return {
|
||||||
msg: "參數缺失",
|
msg: "参数缺失",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = TokenManager.decode(args.token as string)
|
const token = TokenManager.decode(args.token as string)
|
||||||
if (!this.checkToken(token, deviceId)) return {
|
if (!this.checkToken(token, deviceId)) return {
|
||||||
code: 401,
|
code: 401,
|
||||||
msg: "令牌無效",
|
msg: "令牌无效",
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = User.findById(token.author) as User
|
const user = User.findById(token.author) as User
|
||||||
@@ -300,14 +344,14 @@ export default class UserApi extends BaseApi {
|
|||||||
// 獲取用戶信息
|
// 獲取用戶信息
|
||||||
this.registerEvent("User.getInfo", (args, { deviceId }) => {
|
this.registerEvent("User.getInfo", (args, { deviceId }) => {
|
||||||
if (this.checkArgsMissing(args, ['token', 'target'])) return {
|
if (this.checkArgsMissing(args, ['token', 'target'])) return {
|
||||||
msg: "參數缺失",
|
msg: "参数缺失",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = TokenManager.decode(args.token as string)
|
const token = TokenManager.decode(args.token as string)
|
||||||
if (!this.checkToken(token, deviceId)) return {
|
if (!this.checkToken(token, deviceId)) return {
|
||||||
code: 401,
|
code: 401,
|
||||||
msg: "令牌無效",
|
msg: "令牌无效",
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = User.findById(args.target as string)
|
const user = User.findById(args.target as string)
|
||||||
|
|||||||
@@ -24,11 +24,10 @@ export default class Chat {
|
|||||||
db.exec(`
|
db.exec(`
|
||||||
CREATE TABLE IF NOT EXISTS ${Chat.table_name} (
|
CREATE TABLE IF NOT EXISTS ${Chat.table_name} (
|
||||||
/* 序号 */ count INTEGER PRIMARY KEY AUTOINCREMENT,
|
/* 序号 */ count INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
/* 類型 */ type TEXT NOT NULL,
|
/* 类型 */ type TEXT NOT NULL,
|
||||||
/* Chat ID */ id TEXT NOT NULL,
|
/* ID */ id TEXT NOT NULL,
|
||||||
/* 標題 (群組) */ title TEXT,
|
/* 标题 */ title TEXT,
|
||||||
/* 頭像 (群組) */ avatar BLOB,
|
/* 头像 */ avatar BLOB,
|
||||||
/* 成員 */ members_list TEXT,
|
|
||||||
/* 设置 */ settings TEXT NOT NULL
|
/* 设置 */ settings TEXT NOT NULL
|
||||||
);
|
);
|
||||||
`)
|
`)
|
||||||
@@ -50,7 +49,7 @@ 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.findAllBeansByCondition('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.findAllBeansByCondition(
|
||||||
'count = ?',
|
'count = ?',
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export default class FileManager {
|
|||||||
/* 文件名称 */ name TEXT NOT NULL,
|
/* 文件名称 */ name TEXT NOT NULL,
|
||||||
/* 文件哈希 */ hash TEXT NOT NULL,
|
/* 文件哈希 */ hash TEXT NOT NULL,
|
||||||
/* MIME 类型 */ mime TEXT NOT NULL,
|
/* MIME 类型 */ mime TEXT NOT NULL,
|
||||||
/* 来源 Chat, 可為空 */ chatid TEXT,
|
/* 来源对话 */ chatid TEXT,
|
||||||
/* 上传时间 */ upload_time INT8 NOT NULL,
|
/* 上传时间 */ upload_time INT8 NOT NULL,
|
||||||
/* 最后使用时间 */ last_used_time INT8 NOT NULL
|
/* 最后使用时间 */ last_used_time INT8 NOT NULL
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export default class MessagesManager {
|
|||||||
/* 序号, MessageId */ id INTEGER PRIMARY KEY AUTOINCREMENT,
|
/* 序号, MessageId */ id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
/* 消息文本 */ text TEXT NOT NULL,
|
/* 消息文本 */ text TEXT NOT NULL,
|
||||||
/* 发送者 */ user_id TEXT NOT NULL,
|
/* 发送者 */ user_id TEXT NOT NULL,
|
||||||
/* 發送時間 */ time INT8 NOT NULL
|
/* 发送时间 */ time INT8 NOT NULL
|
||||||
);
|
);
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,13 +32,13 @@ export default class User {
|
|||||||
CREATE TABLE IF NOT EXISTS ${User.table_name} (
|
CREATE TABLE IF NOT EXISTS ${User.table_name} (
|
||||||
/* 序号 */ count INTEGER PRIMARY KEY AUTOINCREMENT,
|
/* 序号 */ count INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
/* 用户 ID, UUID */ id TEXT,
|
/* 用户 ID, UUID */ id TEXT,
|
||||||
/* 密碼, 哈希 */ password TEXT,
|
/* 密码摘要 */ password TEXT,
|
||||||
/* 注册时间, 时间戳 */ registered_time INT8 NOT NULL,
|
/* 注册时间 */ registered_time INT8 NOT NULL,
|
||||||
/* 用戶名, 可選 */ username TEXT,
|
/* 用户名 */ username TEXT,
|
||||||
/* 昵称 */ nickname TEXT NOT NULL,
|
/* 昵称 */ nickname TEXT NOT NULL,
|
||||||
/* 头像, 可选 */ avatar_file_hash TEXT,
|
/* 头像, 可选 */ avatar_file_hash TEXT,
|
||||||
/* 聯絡人列表 */ contacts_list TEXT NOT NULL,
|
/* 对话列表 */ contacts_list TEXT NOT NULL,
|
||||||
/* 最近對話 */ recent_chats TEXT NOT NULL,
|
/* 最近对话 */ recent_chats TEXT NOT NULL,
|
||||||
/* 设置 */ settings TEXT NOT NULL
|
/* 设置 */ settings TEXT NOT NULL
|
||||||
);
|
);
|
||||||
`)
|
`)
|
||||||
@@ -147,7 +147,7 @@ export default class User {
|
|||||||
try {
|
try {
|
||||||
return JSON.parse(this.bean.contacts_list) as string[]
|
return JSON.parse(this.bean.contacts_list) as string[]
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(chalk.yellow(`警告: 聯絡人組解析失敗: ${(e as Error).message}`))
|
console.log(chalk.yellow(`警告: 所有对话解析失败: ${(e as Error).message}`))
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,10 +70,10 @@ ApiManager.initEvents()
|
|||||||
ApiManager.initAllApis()
|
ApiManager.initAllApis()
|
||||||
|
|
||||||
httpServer.listen(config.server.listen)
|
httpServer.listen(config.server.listen)
|
||||||
console.log(chalk.green("API & Web 服務已經開始運作"))
|
console.log(chalk.green("API & Web 服务已启动"))
|
||||||
function help() {
|
function help() {
|
||||||
console.log(chalk.yellow("===== TheWhiteSilk Server ====="))
|
console.log(chalk.yellow("===== LingChair Server ====="))
|
||||||
console.log(chalk.yellow("b - 重新編譯前端"))
|
console.log(chalk.yellow("b - 重新编译前端"))
|
||||||
}
|
}
|
||||||
help()
|
help()
|
||||||
|
|
||||||
@@ -83,11 +83,10 @@ const rl = readline.createInterface({
|
|||||||
})
|
})
|
||||||
rl.on('line', (text) => {
|
rl.on('line', (text) => {
|
||||||
if (text == 'b') {
|
if (text == 'b') {
|
||||||
console.log(chalk.green("重新編譯..."))
|
console.log(chalk.green("重新编译..."))
|
||||||
child_process.spawnSync("deno", ["task", "build"], {
|
child_process.spawnSync("deno", ["task", "build"], {
|
||||||
stdio: [process.stdin, process.stdout, process.stderr]
|
stdio: [process.stdin, process.stdout, process.stderr]
|
||||||
})
|
})
|
||||||
console.log(chalk.green("✓ 編譯完畢"))
|
|
||||||
help()
|
help()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
import { DatabaseSync } from "node:sqlite"
|
|
||||||
import fs from 'node:fs/promises'
|
|
||||||
|
|
||||||
await fs.mkdir('data', { recursive: true })
|
|
||||||
|
|
||||||
const db = new DatabaseSync("data/users.db")
|
|
||||||
const TABEL_NAME = "Users"
|
|
||||||
|
|
||||||
// 初始化表格
|
|
||||||
db.exec(
|
|
||||||
`
|
|
||||||
CREATE TABLE IF NOT EXISTS ${TABEL_NAME} (
|
|
||||||
/* 伺服器中 ID */ id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
/* 用戶名, 可選 */ username TEXT,
|
|
||||||
/* 姓名 */ nickname TEXT NOT NULL,
|
|
||||||
/* 头像, 可选 */ avatar BLOB
|
|
||||||
);
|
|
||||||
`,
|
|
||||||
)
|
|
||||||
|
|
||||||
// 插入测试数据
|
|
||||||
db.prepare(
|
|
||||||
`
|
|
||||||
INSERT INTO ${TABEL_NAME} (username, nickname, avatar) VALUES (?, ?, ?);
|
|
||||||
`,
|
|
||||||
).run("SisterWen", "文姐", null)
|
|
||||||
|
|
||||||
let rows = db.prepare(`SELECT id, username, nickname, avatar FROM ${TABEL_NAME}`).all();
|
|
||||||
for (const row of rows) {
|
|
||||||
console.log(row)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新用户名
|
|
||||||
// 用户名要合规, 以免导致 SQL 注入!
|
|
||||||
db.prepare(`UPDATE ${TABEL_NAME} SET username = ? WHERE id = ?`).run("文姐", 1)
|
|
||||||
|
|
||||||
rows = db.prepare(`SELECT id, username, nickname, avatar FROM ${TABEL_NAME} WHERE username = ?`).all("文姐")
|
|
||||||
for (const row of rows) {
|
|
||||||
console.log(row)
|
|
||||||
}
|
|
||||||
|
|
||||||
db.close()
|
|
||||||
Reference in New Issue
Block a user