From a376ead1953f89ea2c9bf1a92318c6a9fd882a45 Mon Sep 17 00:00:00 2001 From: MoonLeeeaf <150461955+MoonLeeeaf@users.noreply.github.com> Date: Fri, 10 May 2024 21:14:50 +0800 Subject: [PATCH] chore: init --- .github/一些思想/聊天数据储存.md | 39 ++ .github/测试样例/test.html | 42 ++ .github/测试样例/聊天气泡实验.html | 68 +++ .github/项目创建时间.txt | 2 + .gitignore | 3 + final.md | 52 ++ license.txt | 13 + ling_chair_http/chat-message.css | 85 +++ ling_chair_http/config.json | 4 + ling_chair_http/default_head.png | Bin 0 -> 7722 bytes ling_chair_http/icon.ico | Bin 0 -> 109574 bytes ling_chair_http/index.css | 83 +++ ling_chair_http/index.html | 269 +++++++++ ling_chair_http/index.js | 644 +++++++++++++++++++++ ling_chair_http/license.txt | 13 + package-lock.json | 897 +++++++++++++++++++++++++++++ package.json | 8 + readme.md | 25 + run.bat | 3 + run.sh | 1 + server_src/api-msgs.js | 121 ++++ server_src/api-users.js | 195 +++++++ server_src/color.js | 13 + server_src/hashlib.js | 16 + server_src/httpApi.js | 17 + server_src/iolib.js | 48 ++ server_src/main.js | 93 +++ server_src/val.js | 56 ++ server_src/wsApi.js | 214 +++++++ 29 files changed, 3024 insertions(+) create mode 100644 .github/一些思想/聊天数据储存.md create mode 100644 .github/测试样例/test.html create mode 100644 .github/测试样例/聊天气泡实验.html create mode 100644 .github/项目创建时间.txt create mode 100644 .gitignore create mode 100644 final.md create mode 100644 license.txt create mode 100644 ling_chair_http/chat-message.css create mode 100644 ling_chair_http/config.json create mode 100644 ling_chair_http/default_head.png create mode 100644 ling_chair_http/icon.ico create mode 100644 ling_chair_http/index.css create mode 100644 ling_chair_http/index.html create mode 100644 ling_chair_http/index.js create mode 100644 ling_chair_http/license.txt create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 readme.md create mode 100644 run.bat create mode 100644 run.sh create mode 100644 server_src/api-msgs.js create mode 100644 server_src/api-users.js create mode 100644 server_src/color.js create mode 100644 server_src/hashlib.js create mode 100644 server_src/httpApi.js create mode 100644 server_src/iolib.js create mode 100644 server_src/main.js create mode 100644 server_src/val.js create mode 100644 server_src/wsApi.js diff --git a/.github/一些思想/聊天数据储存.md b/.github/一些思想/聊天数据储存.md new file mode 100644 index 0000000..f25dcdb --- /dev/null +++ b/.github/一些思想/聊天数据储存.md @@ -0,0 +1,39 @@ +### 关于聊天记录储存的方案概述 + +自 3月10日 开始,这个项目遇到了一些瓶颈,停止开发 + +我在这段时间的精力也被耗尽 + +故此,在今天(3月26日)重新思考,理清思路 + +预计3月28日左右实现此功能 + +#### 基本想法 + +##### 写入 + +对于储存来说,从 0 开始计次,一条消息对应一个 ID + +第一条消息的 ID 为 1, 计次文件储存为 1 + +为了性能,每个消息使用一个 JSON 文件储存 + +##### 读取 + +当读取时,从计次文件获取计次数,并从此数字递减,以此读取 + +然后储存到数组并直接返回给用户 + +客户端消息的 ID 必须要同步,这是为了范围读取以前的消息 + +#### 和以前的区别 + +在我的聊天软件第一版、第二版中,均由 PHP 作为后端 + +写入则直接追加,读取整个返回 + +这种做法的最大缺陷是浪费性能 + +我曾想过使用 SQL,不过潜在的风险以及我的技术都能符合我的要求 + +故此,便有了这个方法 diff --git a/.github/测试样例/test.html b/.github/测试样例/test.html new file mode 100644 index 0000000..5114813 --- /dev/null +++ b/.github/测试样例/test.html @@ -0,0 +1,42 @@ + + + + + + + + + + + + 铃之椅 API测试 + + + + + +
+ +
+ +
+ +
+ + + + + + + + \ No newline at end of file diff --git a/.github/测试样例/聊天气泡实验.html b/.github/测试样例/聊天气泡实验.html new file mode 100644 index 0000000..3e3e73e --- /dev/null +++ b/.github/测试样例/聊天气泡实验.html @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + 聊天气泡测试 + + + + + +
+
+ 小沫 +
+ 你好!这是一条较长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长的消息内容,测试消息长度自动填充效果。 +
+
+ Avatar +
+ + +
+ Avatar +
+ 小沫 +
+ 你好!这是一条较长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长的消息内容,测试消息长度自动填充效果。 +
+
+
+ + + + + + + \ No newline at end of file diff --git a/.github/项目创建时间.txt b/.github/项目创建时间.txt new file mode 100644 index 0000000..5b7f768 --- /dev/null +++ b/.github/项目创建时间.txt @@ -0,0 +1,2 @@ +Web客户端: 2024‎年‎1‎月‎23‎日,‏‎13:15:09 +Node.js服务端: ‎2024‎年‎1‎月‎24‎日,‏‎14:10:47 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..991afb3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +ling_chair_data/ +ling_chair_config/ diff --git a/final.md b/final.md new file mode 100644 index 0000000..4bda31b --- /dev/null +++ b/final.md @@ -0,0 +1,52 @@ +### 最终目标 + +铃之椅服务端的 Node 实现,目前最低目标如下 + +* 全 API 可用 +* 可配置 +* 开箱即用 + +如果有时间,可以完成下面的目标 + +* 尽可能使用异步 +* 最大利用 I/O 性能 + +#### API 实现 + +一般约定: ☘️=已完成 + +* 用户 + * 登录 ☘️ + * 注册 ☘️ + * 修改密码 + * 令牌机制 ☘️ + * 用户资料 + * 头像 ☘️ + * 昵称 ☘️ + * 安全 + * 账号冻结 + * 权限管理 + * 匿名 + +* 聊天 + * 双方私聊 ☘️ 完善中 + * 多人群聊 + * 临时对话 + * 多媒体 + * 文件 + * 群聊资料 + * 介绍 + * 头像 + * 群名称 + * 安全 + * 权限管理 + * 管理员 + * 入群权限 + * 可否被查找 + * 功能限制 + * 管理匿名 + +注: + 1. 为了保障管理员层安全,匿名的账号或管理员的信息仍然能被伺服器管理员所查询,不过请放心,一般来说不会有任何正常人能查询到你 + 2. 多媒体应该能够定时删除而非永久保存,否则伺服器会炸掉的 + 3. 每个功能都应该验证令牌 diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..5c226ee --- /dev/null +++ b/license.txt @@ -0,0 +1,13 @@ +Copyright 2024 MoonLeeeaf + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/ling_chair_http/chat-message.css b/ling_chair_http/chat-message.css new file mode 100644 index 0000000..46e0dcf --- /dev/null +++ b/ling_chair_http/chat-message.css @@ -0,0 +1,85 @@ +/* + * 铃之椅 - 把选择权还给用户, 让聊天权掌握在用户手中 + * Copyright 2024 满月叶 + * GitHub: https://github.com/MoonLeeeaf/LingChair-Web-Client + * 本项目使用 Apache 2.0 协议开源 + * + * Copyright 2024 MoonLeeeaf + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.chat-message-right { + display: flex; + justify-content: flex-end; + align-items: flex-start; + margin: 13px; +} + +.chat-message-left { + display: flex; + justify-content: flex-start; + align-items: flex-start; + margin: 13px; +} + +.message-content { + margin-top: 13px; + margin-bottom: 7px; + margin-left: 5px; + margin-right: 5px; + max-width: 100%; + white-space: normal; + word-break: break-all; + font-size: medium; + /* 使用了 CardView 就不需要边框了 */ + /* border: 1.3px solid; */ + padding: 15px; + border-radius: 15px; + /* 添加圆角样式 */ + /* 设置外边距为 7px */ +} + +.message-content-with-nickname-right { + display: flex; + align-items: center; + margin: 7px; + flex-direction: column; + /* 垂直排列元素 */ + align-items: flex-end; + /* 左对齐元素 */ +} + +.message-content-with-nickname-left { + display: flex; + align-items: center; + margin: 7px; + flex-direction: column; + /* 垂直排列元素 */ + align-items: flex-start; + /* 左对齐元素 */ +} + +.chat-message-left .message-content-with-nickname-left .nickname, +.chat-message-right .message-content-with-nickname-right .nickname { + margin-right: 5px; + font-size: medium; + margin-top: 3px; +} + +.chat-message-left > .avatar, +.chat-message-right > .avatar { + width: 45px; + height: 45px; + border-radius: 50%; +} \ No newline at end of file diff --git a/ling_chair_http/config.json b/ling_chair_http/config.json new file mode 100644 index 0000000..3e24632 --- /dev/null +++ b/ling_chair_http/config.json @@ -0,0 +1,4 @@ +{ + "appTitle": "", + "canChangeServer": false +} \ No newline at end of file diff --git a/ling_chair_http/default_head.png b/ling_chair_http/default_head.png new file mode 100644 index 0000000000000000000000000000000000000000..4f6f226fef17df30635ce72b79b0fd7c6bda4cfa GIT binary patch literal 7722 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 sm$D9#%>F-cYC}ea3{Kv-2WlJMMg-l`m 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 z%0D+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 literal 0 HcmV?d00001 diff --git a/ling_chair_http/index.css b/ling_chair_http/index.css new file mode 100644 index 0000000..5086f9d --- /dev/null +++ b/ling_chair_http/index.css @@ -0,0 +1,83 @@ +/* + * 铃之椅 - 把选择权还给用户, 让聊天权掌握在用户手中 + * Copyright 2024 满月叶 + * GitHub: https://github.com/MoonLeeeaf/LingChair-Web-Client + * 本项目使用 Apache 2.0 协议开源 + * + * Copyright 2024 MoonLeeeaf + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + html, body { + max-height: 100%; + margin: 0; + padding: 0; + /* overflow: hidden; */ + /*font: initial;*/ +} +body { + margin: 0; + padding: 0; +} +.container { + display: flex; + flex-direction: column; + overflow: auto; +} +.content { + flex: 1; +} + +/* 美化 MDUI */ +body { + font-family: -apple-system, system-ui, -webkit-system-font; +} +.round { + border-radius: 20px; +} +.mdui-dialog { + border-radius: 23px; +} +.mdui-snackbar { + border-radius: 10px; +} +.mdui-menu { + border-radius: 10px; +} +.mdui-menu-item > a { + padding-right: 3px; +} +.mdui-dialog-actions a, +.mdui-dialog-actions button { + border-radius: 30px; + padding: 15px; + text-align: center; + line-height: 8px; +} +.mdui-dialog-actions { + margin-right: 7px; + margin-top: -7px; +} + +/* 配色方案 */ + +.mdui-theme-color-auto { + background-color: #fff; +} + +@media (prefers-color-scheme: dark) { + .mdui-theme-color-auto { + background-color: #303030; + } +} diff --git a/ling_chair_http/index.html b/ling_chair_http/index.html new file mode 100644 index 0000000..08b1e72 --- /dev/null +++ b/ling_chair_http/index.html @@ -0,0 +1,269 @@ + + + + + + + + + + + + + + + + + + + + + + + + 铃之椅 + + + + +

+ +
+
    +
  • +
    + +
    +
    +
  • +
  • 个人
  • +
  • + account_circle +
    资料
    +
  • +
  • 客户端
  • +
  • + settings +
    设置
    +
  • +
  • + cloud_circle +
    更换服务器
    +
  • +
  • + exit_to_app +
    退出登录
    +
  • +
+
+ + +
+ + +
+ + +
+ + 常用 + 通讯录 + +
+
+
+ 欢迎回来! (^▽^。) +
+
+
+
    +
  • 好友
  • +
    +
    +
+
+ +
+ +
+
点我继续加载前面的聊天记录, 或者回到底部
+
+
+ + + +
+ + +
+
+ 登录到 铃之椅 +
+
+
+ cloud_circle + + +
+
+ account_circle + + +
+
+ lock + + +
+ 注:使用非已知的服务提供商提供的服务器时, 请注意个人信息保护哦 o(。・ω・。)o +
+
+ + +
+
+
+ + +
+
+
+ +
+
+
+
+
+ +
+
+ + +
+
+ 资料 +
+
+
    +
  • + edit +
    修改昵称
    +
  • +
  • + account_circle +
    上传头像
    +
  • +
+
+
+ +
+
+ + +
+
+ 修改昵称 +
+
+
+ + +
+
+
+ + +
+
+ + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/ling_chair_http/index.js b/ling_chair_http/index.js new file mode 100644 index 0000000..ae3f0e7 --- /dev/null +++ b/ling_chair_http/index.js @@ -0,0 +1,644 @@ +/* + * 铃之椅 - 把选择权还给用户, 让聊天权掌握在用户手中 + * Copyright 2024 满月叶 + * GitHub: https://github.com/MoonLeeeaf/LingChair-Web-Client + * 本项目使用 Apache 2.0 协议开源 + * + * Copyright 2024 MoonLeeeaf + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const UrlArgs = new URL(location.href).searchParams + +if (UrlArgs.get("debug")) { + let script = document.createElement('script') + script.src = "//cdn.jsdelivr.net/npm/eruda" + document.body.appendChild(script) + script.onload = () => eruda.init() +} + +// 经常会因为这个指定ID为位置导致一些莫名BUG +if (location.href.includes("#")) location.replace(location.href.substring(0, location.href.indexOf("#"))) + +const mdui_snackbar = mdui.snackbar +mdui.snackbar = (m) => { + let t = m + if (m instanceof Object) + t = JSON.stringify(m) + mdui_snackbar(t) +} + +const checkEmpty = (i) => { + if (i instanceof Array) { + for (let k of i) { + if (checkEmpty(k)) return true + } + } + + return (i == null) || ("" === i) || (0 === i) +} + +function escapeHTML(str) { + return str.replace(/[<>&"']/g, function (match) { + switch (match) { + case '<': + return '<' + case '>': + return '>' + case '&': + return '&' + case '"': + return '"' + case "'": + return ''' + default: + return match + } + }) +} + +class NData { + static mount(node) { + // 便捷获得指定组件 + let es = node.querySelectorAll("[n-id]") + let ls = {} + es.forEach((i) => ls[$(i).attr("n-id")] = $(i)) + + // input 组件与 localStorage 绑定 + es = node.querySelectorAll("[n-input-ls]") + es.forEach((e) => { + let j = $(e) + j.val(localStorage.getItem(j.attr("n-input-ls"))) + j.blur(() => localStorage.setItem(j.attr("n-input-ls"), j.val())) + }) + return ls + } +} + +// 快捷获取指定视图 +let viewBinding = NData.mount($("#app").get(0)) + +$.ajax({ + url: "config.json", + dataType: "json", + success: (c) => { + viewBinding.appTitle.text(c.appTitle) + if (!c.canChangeServer) { + viewBinding.dialogSignInServerLabel.hide() + viewBinding.drawerChangeServer.hide() + } + }, +}) + +// Toolbar 快捷按钮绑定 +viewBinding.contactsRefresh.hide() +viewBinding.tabChatList.on("show.mdui.tab", () => { + viewBinding.contactsRefresh.hide() +}) +viewBinding.tabContacts.on("show.mdui.tab", () => { + viewBinding.contactsRefresh.show() +}) +viewBinding.tabChatSeesion.on("show.mdui.tab", () => { + viewBinding.contactsRefresh.hide() +}) + +viewBinding.tabChatSeesion.hide() + +// 关于页面 +viewBinding.menuAbout.click(() => mdui.alert('GitHub: MoonLeeeaf

欢迎各位大佬访问我们的项目主页', '关于 铃之椅', () => { }, { confirmText: "关闭" })) + +viewBinding.drawerChangeServer.click(() => { + mdui.prompt('输入服务器地址...(为空则使用当前页面地址)', (value) => { + localStorage.server = value + mdui.snackbar("更新成功, 刷新页面生效") + }, () => { }, { + confirmText: "确定", + cancelText: "取消" + }) +}) + +viewBinding.drawerSignOut.click(() => { + mdui.confirm('确定要登出账号吗', () => { + localStorage.refreshToken = "" + localStorage.isSignIn = false + + setTimeout(() => location.reload(), 300) + }, () => { }, { + confirmText: "确定", + cancelText: "取消" + }) +}) + +viewBinding.sendMsg.click((a) => { + let text = viewBinding.inputMsg.val() + if (text.trim() !== "") + ChatMsgAdapter.send(text) +}) + +viewBinding.inputMsg.keydown((e) => { + if (e.ctrlKey && e.keyCode === 13) + viewBinding.sendMsg.click() +}) + +viewBinding.dialogSignInPasswd.keydown((e) => { + if (e.keyCode === 13) + viewBinding.dialogSignInEnter.click() +}) + +viewBinding.switchNotifications.click((a) => { + if (localStorage.useNotifications === "true" || localStorage.useNotifications != null) { + localStorage.useNotifications = false + viewBinding.switchNotificationsIcon.text("notifications_off") + } else { + localStorage.useNotifications = true + viewBinding.switchNotificationsIcon.text("notifications") + } +}) +if (localStorage.useNotifications === "true") + viewBinding.switchNotificationsIcon.text("notifications") + +// https://zhuanlan.zhihu.com/p/162910462 +Date.prototype.format = function (tms, format) { + let tmd = new Date(tms) + /* + * 例子: format="YYYY-MM-dd hh:mm:ss"; + */ + var o = { + "M+": tmd.getMonth() + 1, // month + "d+": tmd.getDate(), // day + "h+": tmd.getHours(), // hour + "m+": tmd.getMinutes(), // minute + "s+": tmd.getSeconds(), // second + "q+": Math.floor((tmd.getMonth() + 3) / 3), // quarter + "S": tmd.getMilliseconds() + // millisecond + } + if (/(y+)/.test(format)) { + format = format.replace(RegExp.$1, (tmd.getFullYear() + "") + .substr(4 - RegExp.$1.length)); + } + for (var k in o) { + if (new RegExp("(" + k + ")").test(format)) { + format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] + : ("00" + o[k]).substr(("" + o[k]).length)); + } + } + return format; +} + +// new mdui.Drawer('#main-drawer').close() + +class NickCache { + static data = {} + static async getNick(name) { + return await new Promise((res, rej) => { + // 这个this别摆着不放啊 不然两下就会去世 + let nick = this.data[name] + if (nick == null) + client.emit("user.getNick", { name: localStorage.userName }, (re) => { + let nk = re.data != null ? re.data.nick : name + if (nk == null) nk = name + this.data[name] = nk + res(nk) + }) + else + res(nick) + }) + } +} + +// 既然已经有 Notification 了, 那用回中文也不过分吧 :) +class 通知 { + constructor() { + this.args = {} + this.title = "" + } + static checkAvailable() { + return ("Notification" in window) + } + static async request() { + if (!this.checkAvailable()) return false + return (await Notification.requestPermission()) + } + setId(id) { + this.args.tag = id + return this + } + setTitle(t) { + this.title = t + return this + } + setMessage(m) { + this.args.body = m + return this + } + setIcon(i) { + this.args.icon = i + return this + } + setImage(i) { + this.args.image = i + return this + } + setData(data) { + this.args.data = data + } + show(onclick/*, onclose*/) { + if (!通知.checkAvailable()) return + if (localStorage.useNotifications !== "true") return + let n = new Notification(this.title, this.args) + n.onclick = onclick == null ? () => n.close() : (n) => onclick(n) + // n.onclose = onclose + // n.close() + return n + } +} + +class ContactsList { + static async reloadList() { + client.emit("user.getFriends", { + name: localStorage.userName, + accessToken: await User.getAccessToken(), + }, async (re) => { + if (re.code !== 0) + return mdui.snackbar(re.msg) + + viewBinding.contactsList.empty() + let ls = re.data.friends + for (let index in ls) { + let name = ls[index] + let dick = await NickCache.getNick(name) + /*client.emit("user.getNick", { name: localStorage.userName }, (re) => { + let nick = re.data == null ? re.data.nick : null + let name = ls[index]*/ + $($.parseHTML(`
  • ` + dick + `
  • `)).appendTo(viewBinding.contactsList).click(() => { + ChatMsgAdapter.switchTo(name, "single") + }) + //}) + } + + }) + } +} + +// 第一次写前端的消息加载, 代码很乱, 还请原谅~ + +class ChatMsgAdapter { + static type + static target + // static msgList + static minMsgId + static time + static bbn + // 切换聊天对象 + static async switchTo(name, type) { + viewBinding.tabChatSeesion.show() + viewBinding.tabChatSeesion.text(await NickCache.getNick(name)) + viewBinding.tabChatSeesion.get(0).click() + + this.type = type + this.target = name + // this.msgList = [] + this.minMsgId = null + + viewBinding.pageChatSeesion.empty() + + await this.loadMore() + this.scrollToBottom() + } + // 发送消息 + static async send(msg) { + client.emit("user.sendSingleMsg", { + name: localStorage.userName, + target: this.target, + msg: msg, + accessToken: await User.getAccessToken(), + }, async (re) => { + if (re.code !== 0) + return mdui.snackbar(re.msg) + + viewBinding.inputMsg.val("") + + // 微机课闲的没事干玩玩 发现私聊会多发一个(一个是本地的, 另一个是发送成功的) 选择一个关掉就好了 + // 这里我选择服务端不发送回调, 不然多设备同步会吵死 + // 错了 应该是客户端少发条才对 不然不能多设备同步 + if (ChatMsgAdapter.target !== localStorage.userName && ChatMsgAdapter.type === "single") { + let i = ChatMsgAdapter.isAtBottom() + await ChatMsgAdapter.addMsg(localStorage.userName, msg, re.data.time) + if (i) ChatMsgAdapter.scrollToBottom() + } + }) + } + static async getHistroy(start, limit) { + return new Promise(async (res, rej) => { + client.emit("user.getSingleChatHistroy", { + name: localStorage.userName, + target: this.target, + limit: limit, + accessToken: await User.getAccessToken(), + startId: start, + }, (re) => { + if (re.code !== 0) + return mdui.snackbar(re.msg) + res(re.data.histroy) + }) + }) + } + static async loadMore(limit) { + let histroy = await this.getHistroy(this.minMsgId, limit == null ? 13 : limit) + + if (histroy.length == 0) + return mdui.snackbar("已经加载完了~") + + let re = this.minMsgId != null + this.minMsgId = histroy[0].msgid - 1 + let sc = 0 + if (re) histroy = histroy.reverse() + for (let index in histroy) { + let i = histroy[index] + let e = await this.addMsg(i.name, i.msg, i.time, re, true) + // 因为某些因素直接DEBUG到吐血 断点继续都不报错 原因不明 + sc = sc + (e == null ? 20 : e.get(0).offsetTop) + } + window.scrollBy({ + top: sc, + behavior: 'smooth' + }) + } + static addSystemMsg(m, re) { + let e + if (re) + e = $($.parseHTML(m)).prependTo(viewBinding.pageChatSeesion) + else + e = $($.parseHTML(m)).appendTo(viewBinding.pageChatSeesion) + return e + } + static isAtBottom() { + let elementRect = viewBinding.pageChatSeesion.get(0).getBoundingClientRect(); + return (elementRect.bottom <= window.innerHeight); + } + // 不会压栈 只添加消息 返回消息的JQ对象 + static async addMsg(name, m, t, re) { + + let nick = await NickCache.getNick(name) // re.data == null ? name : re.data.nick + + let msg = escapeHTML(m) + + let temp + if (name === localStorage.userName) + temp = `
    +
    + ` + nick + ` +
    + ` + msg + ` +
    +
    + +
    ` + else + temp = `
    + +
    + ` + nick + ` +
    + ` + msg + ` +
    +
    +
    ` + + let bn = new Date(t).getMinutes() + let e + if (re) { + this.addSystemMsg(temp, re) + if (this.bbn != bn) { + e = this.addSystemMsg(`
    ` + new Date().format(t == null ? Date.parse("1000-1-1 00:00:00") : t, "yyyy年MM月dd日 hh:mm:ss") + `
    `, re) + this.time = bn + } + } else { + if (this.bbn != bn) { + e = this.addSystemMsg(`
    ` + new Date().format(t == null ? Date.parse("1000-1-1 00:00:00") : t, "yyyy年MM月dd日 hh:mm:ss") + `
    `, re) + this.time = bn + } + this.addSystemMsg(temp, re) + } + + this.bbn = new Date(t).getMinutes() + + return e + } + // 添加消息记录 作用在 UI 和 msgList + /* static async addMsgLocal(name, m, t, msgid) { + this.msgList.push({ + name: name, + msg: m, + msgid: msgid, + }) + + this.addMsg(name, m, t) + } */ + // 从服务器加载一些聊天记录, limit默认=13 + static async loadMsgs(limit) { + let histroy = await this.getHistroy(this.msgList[0] == null ? null : this.msgList[0].msgid - 1, limit == null ? 13 : limit) + this.msgList = histroy + } + /* static async loadMsgsFromList(lst) { + for (let index in lst) { + let i = lst[index] + await this.addMsg(i.name, i.msg, i.time) + } + } */ + static scrollToBottom() { + // 吐了啊 原来这样就行了 我何必在子element去整啊 + window.scrollBy({ + top: 1145141919810, + behavior: 'smooth' + }); + } + // 从本地加载 + /*static loadMsgsFromLocal(target) { + let data = localStorage["chat_msg_" + target] + if (data == null || data === "[]") + return [] + + return JSON.parse(data) + } + // 把当前聊天记录储存到本地 + static saveToLocal() { + localStorage["chat_msg_" + this.target] = JSON.stringify(this.msgList) + }*/ +} + +class Hash { + static md5(data) { + return CryptoJS.MD5(data).toString(CryptoJS.enc.Base64) + } + static sha256(data) { + return CryptoJS.SHA256(data).toString(CryptoJS.enc.Base64) + } +} + +class User { + static myAccessToken + // 登录账号 通过回调函数返回刷新令牌 + static signIn(name, passwd, cb) { + client.emit("user.signIn", { + name: name, + passwd: Hash.sha256(passwd) + Hash.md5(passwd), + }, (re) => { + if (re.code !== 0) + return mdui.snackbar(re.msg) + + cb(re) + }) + } + static signUp(name, passwd, cb) { + client.emit("user.signUp", { + name: name, + passwd: Hash.sha256(passwd) + Hash.md5(passwd), + }, (re) => { + if (re.code !== 0) + return mdui.snackbar(re.msg) + + cb(re) + }) + } + // 为登录对话框编写的 + static signInWithDialog(name, passwd) { + this.signIn(name, passwd, (re) => { + localStorage.refreshToken = re.data.refreshToken + localStorage.isSignIn = true + + location.reload() + }) + } + static async setNick(nick, cb) { + client.emit("user.setNick", { + name: localStorage.userName, + accessToken: await this.getAccessToken(), + nick: nick, + }, (re) => { + if (re.code !== 0) + return mdui.snackbar(re.msg) + if (cb) cb() + }) + } + // 获取头像链接 + static getUserHeadUrl(name) { + return client.io.uri + "/users_head/" + name + ".png" + } + static async getAccessToken(er) { + if (this.myAccessToken == null) + this.myAccessToken = await new Promise((res) => { + client.emit("user.getAccessToken", { name: localStorage.userName, refreshToken: localStorage.refreshToken }, (r) => { + if (r.data != null) res(r.data.accessToken) + if (er != null) er(r.msg) + }) + }) + return this.myAccessToken + } + static uploadHeadImage() { + viewBinding.uploadHeadImage.click() + } + static async uploadHeadImageCallback(self) { + let img = self.files[0] + client.emit("user.setHeadImage", { + name: localStorage.userName, + accessToken: await User.getAccessToken(), + headImage: img, + }, (re) => mdui.snackbar(re.msg)) + } + static auth() { + client.emit("user.auth", { name: localStorage.userName, refreshToken: localStorage.refreshToken }, (re) => { + if (re.code !== 0) { + console.error(re) + return mdui.snackbar("验证用户失败!") + } + }) + } + static registerCallback() { + client.on("msg.receive", async (a) => { + if (checkEmpty([a.target, a.msg, a.type])) + return + + if ((ChatMsgAdapter.target === a.target) && (ChatMsgAdapter.type === a.type)) { + let i = ChatMsgAdapter.isAtBottom() + await ChatMsgAdapter.addMsg(a.target, a.msg.msg, a.msg.time) + if (i) ChatMsgAdapter.scrollToBottom() + } + + let n = new 通知().setTitle("新消息 - " + await NickCache.getNick(a.target)).setMessage(a.msg.msg).setIcon(User.getUserHeadUrl(a.target)).show(async () => { + await ChatMsgAdapter.switchTo(a.target, a.type) + ChatMsgAdapter.scrollToBottom() + n.close() + }) + }) + } + static async openProfileDialog(name) { + viewBinding.dialogProfileHead.attr("src", User.getUserHeadUrl(name)) + viewBinding.dialogProfileNick.text(await NickCache.getNick(name)) + new mdui.Dialog(viewBinding.dialogProfile).open() + } +} + +// 没有刷新令牌需要重新登录 或者初始化 +if (!localStorage.refreshToken || localStorage.refreshToken === "") + localStorage.isSignIn = false + +let client +if (!localStorage.server || localStorage.server === "") + client = new io({ + auth: { + name: localStorage.isSignIn === "false" ? null : localStorage.userName + } + }) +else + client = new io(localStorage.server, { + auth: { + name: localStorage.isSignIn === "false" ? null : localStorage.userName + } + }) + +// 登录到账号 +let dialogSignIn +// 谨防 localStorage 字符串数据大坑 +if (localStorage.isSignIn === "false") + dialogSignIn = new mdui.Dialog(viewBinding.dialogSignIn.get(0), { + modal: true, + closeOnEsc: false, + history: false, + }).open() +else { + (async () => viewBinding.userNick.text(await NickCache.getNick(localStorage.userName)))() + let hello + let nowHour = new Date().getHours() + if (nowHour >= 6 && nowHour <= 11) hello = "早安" + else if (nowHour == 12) hello = "中午好" + else if (nowHour >= 13 && nowHour <= 18) hello = "下午好" + else if (nowHour >= 19 && nowHour < 22) hello = "晚上好" + else hello = "晚安" + viewBinding.helloText.text(hello) + + viewBinding.userHead.attr("src", User.getUserHeadUrl(localStorage.userName)) + + ContactsList.reloadList() + + client.on("connect", () => { + User.auth() + }) + + User.registerCallback() +} + +// 感谢AI的力量 +Stickyfill.add($("*").filter((a, b) => $(b).css('position') === 'sticky')) diff --git a/ling_chair_http/license.txt b/ling_chair_http/license.txt new file mode 100644 index 0000000..5c226ee --- /dev/null +++ b/ling_chair_http/license.txt @@ -0,0 +1,13 @@ +Copyright 2024 MoonLeeeaf + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9c64e96 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,897 @@ +{ + "name": "LingChair-Node.js", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "express": "^4.19.2", + "mime": "^4.0.1", + "socket.io": "^4.7.5" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "license": "MIT" + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "license": "MIT" + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "20.12.5", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/base64id": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.4.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/cors": { + "version": "2.8.5", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/engine.io": { + "version": "6.5.4", + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.2", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.1.tgz", + "integrity": "sha512-5lZ5tyrIfliMXzFtkYyekWbtRXObT9OWa8IwQ5uxTBDHucNNwniRqo0yInflj+iYi5CBa6qxadGzGarDfuEOxA==", + "funding": [ + "https://github.com/sponsors/broofa" + ], + "bin": { + "mime": "bin/cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/socket.io": { + "version": "4.7.5", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.4", + "license": "MIT", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.11.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ws": { + "version": "8.11.0", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..068d1a8 --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "dependencies": { + "express": "^4.19.2", + "mime": "^4.0.1", + "socket.io": "^4.7.5" + }, + "type": "commonjs" +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..63711a5 --- /dev/null +++ b/readme.md @@ -0,0 +1,25 @@ +### 铃之椅 Node.js + + + +#### 使用 + +服务端: + + 1. 克隆本仓库源代码到本地 + + 2. ~~(仅需要集成时) 复制 client_src 中全部文件到 ling_chair_http~~ 默认就是集成的 + + 3. 执行 run.sh + + 4. Enjoy it :) + +网页端: + +三种方法任选一个 + + * 直接使用和服务端集成的网页 (推荐) + + * 克隆本仓库到本地并运行本地 HTTP 服务端 + + * 使用本仓库提供的网页 diff --git a/run.bat b/run.bat new file mode 100644 index 0000000..96440bc --- /dev/null +++ b/run.bat @@ -0,0 +1,3 @@ +@echo off +node server_src/main.js +pause \ No newline at end of file diff --git a/run.sh b/run.sh new file mode 100644 index 0000000..0787755 --- /dev/null +++ b/run.sh @@ -0,0 +1 @@ +node server_src/main.js \ No newline at end of file diff --git a/server_src/api-msgs.js b/server_src/api-msgs.js new file mode 100644 index 0000000..b196304 --- /dev/null +++ b/server_src/api-msgs.js @@ -0,0 +1,121 @@ +/* + * ©2024 满月叶 + * Github: MoonLeeeaf + * 通讯辅助类 + */ + +const io = require("./iolib") +const hash = require("./hashlib") +const vals = require("./val") +const users = require("./api-users") + +let getSameHashedValue = (a, b) => { + let _a = [hash.md5(a) + hash.sha256(a), hash.md5(b) + hash.sha256(b)].sort() + let [_1, _2] = _a + return hash.sha256hex(hash.sha256hex(_1) + hash.sha256hex(_2)) +} + +let getSingleChatDir = (a, b) => { + return vals.LINGCHAIR_SINGLE_MESSAGE_DIR + "/" + getSameHashedValue(a, b) +} + +let apis = { + // 储存单聊消息: 操作者, 访问密钥, 发送至, 消息内容 + // 消息存储方式为计次直接储存, 每一个消息都有对应的 ID + // 读取某一段落时使用遍历方式 + // @API + sendSingleMsg: (name, accessToken, target, msg) => { + if (!users.checkAccessToken(name, accessToken)) + return { code: -1, msg: "访问令牌错误" } + + if (!users.isUserExists(target)) + return { code: -1, msg: "目标用户不存在" } + + if (msg.trim() == "") + return { code: -1, msg: "不是有内容的消息我不要" } + + let fileDir = getSingleChatDir(name, target) + io.mkdirs(fileDir) + + let countFile = io.open(fileDir + "/count.txt", "rw") + if (!io.exists(fileDir + "/count.txt")) + countFile.write("0") + + let count = parseInt(countFile.read()) + count += 1 + let time = Date.now() + io.open(fileDir + "/msg_" + count + ".json", "w").writeJson({ + name: name, + msg: msg, + msgid: count, + time: time, + }).close() + + countFile.write(count + "") + + return { code: 0, msg: "成功", msgid: count, time: time } + }, + // 读取消息记录 + // 从起始点到结束点读取,由最新到最老(计次越大越新) + // 不提供 startId 则默认从最新计次往前数 + // 若超过 limit 计次范围, 直接终止遍历 + // @API + getSingleMsgHistroy: (name, accessToken, target, sid, limit) => { + if (!users.checkAccessToken(name, accessToken)) + return { code: -1, msg: "访问令牌错误" } + + if (!users.isUserExists(target)) + return { code: -1, msg: "目标用户不存在" } + + let fileDir = getSingleChatDir(name, target) + io.mkdirs(fileDir) + let countFile = io.open(fileDir + "/count.txt", "rw") + + if (!io.exists(fileDir + "/count.txt")) + countFile.write("0") + + let startId = sid + if (startId == null) + startId = parseInt(countFile.read().toString()) + + let list = [] + let i = startId + let i2 = 0 + let cfn + while(true) { + cfn = fileDir + "/msg_" + i + ".json" + // 1. 超过界限 + // 2. 超过计次 + // 3. 超过最大限度 + if ((!io.exists(cfn)) || i2 > limit || i2 > 100) break + try { + let data = io.open(cfn, "r").readJson() + list.unshift(data) + } catch (e) { + return { code: -2, msg: e } + } + i-- + i2++ + } + + return { code: 0, msg: "成功", histroy: list } + }, + + // 上传图片: 操作者, 访问密钥, 发送至, 图片 + // 未来需要一些操作来删除未使用的图片文件 + // @API + uploadImage: (name, accessToken, target, msg) => { + if (!users.checkAccessToken(name, accessToken)) + return { code: -1, msg: "访问令牌错误" } + + if (!users.isUserExists(target)) + return { code: -1, msg: "目标用户不存在" } + + let fileDir = getSingleChatDir(name, target) + "/images/" + io.mkdirs(fileDir) + + + }, +} + +module.exports = apis diff --git a/server_src/api-users.js b/server_src/api-users.js new file mode 100644 index 0000000..e053449 --- /dev/null +++ b/server_src/api-users.js @@ -0,0 +1,195 @@ +/* + * ©2024 满月叶 + * Github: MoonLeeeaf + * 用户辅助类 + */ + +const io = require("./iolib") +const hash = require("./hashlib") +const vals = require("./val") + +// 获取用户资料所在的路径 +let getUserPath = (name) => { + return vals.LINGCHAIR_DATA_DIR + "/users/" + name +} + +// 用户是否存在 +let isUserExists = (name) => { + return io.exists(getUserPath(name)) +} + +let apis = { + isUserExists: isUserExists, + + // ================================ + // 无需令牌的 API + // ================================ + + // 创建账号: 账号, 密码 返回账号唯一 ID 和成功信息 失败返回 null 和原因 + // 账号文件结构: {uid: 10000, name: "GenShin", nick: "Impact", passwd: "SHA-256 + MD5"} + // 注意: 密码在客户端也应该经过哈希处理(SHA256 + MD5) + // @APi + signUp: (name, passwd) => { + if (passwd == null || name == null) + return { msg: "必须输入 账号和密码", code: -1 } + + let path = getUserPath(name) + if (isUserExists(name)) + return { msg: "用户账号名重复", code: -1 } + + io.mkdirs(path) + + let idCount = io.open(vals.LINGCHAIR_USERS_COUNT_FILE) + let uid = parseInt(idCount.read("*a")) + idCount.write((uid + 1) + "").close() + + io.open(path + "/user.json").writeJson({ + uid: uid, + name: name, + nick: null, + passwd: hash.sha256(passwd) + hash.md5(passwd), + }).close() + + return { uid: uid, msg: "成功", code: 0 } + }, + + // 登录账号: 账号, 密码 返回刷新令牌 失败返回 null 和原因 + // 注意: 密码在客户端应该经过哈希处理(SHA256 + MD5) + // @API + signIn: (name, passwd) => { + if (passwd == null || name == null) + return { msg: "必须输入 账号和密码", code: -1 } + + if (!isUserExists(name)) + return { msg: "用户不存在", code: -1 } + + if (apis.getPassWordHashedRaw(name) !== (hash.sha256(passwd) + hash.md5(passwd))) + return { msg: "账号所对应的密码错误", code: -1 } + + return { msg: "成功", code: 0, refreshToken: apis.getRefreshToken(name, apis.getPassWordHashed(name)) } + }, + + // 获取刷新令牌: 账号,密码 返回刷新令牌 + // 注意: 密码在客户端也应该经过哈希处理(SHA256 + MD5) + // 刷新令牌算法: 哈希(用户ID + 当前年 + 当前月 + 密码 + 盐) + // 有效期: 一个月 + getRefreshToken: (name, passwd) => { + let d = new Date() + let raw = name + d.getFullYear() + d.getMonth() + passwd + "LINGCHAIR-TEST-DEMO" + return hash.sha256(raw) + hash.md5(raw) + }, + + // 获取访问令牌: 账号,刷新令牌 返回访问令牌 + // 注意: 密码在客户端也应该经过哈希处理(SHA256 + MD5) + // 刷新令牌算法: 哈希(用户ID + 当前年 + 当前月 + 密码 + 盐) + // 有效期: 一天 + getAccessTokenNonApi: (name, rt) => { + if (!apis.checkRefreshToken(name, rt)) + return null + let date = new Date().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'numeric', day: 'numeric' }) + return hash.sha256(name + date) + hash.md5(rt + date + "LINGCHAIR-ACCESS-TEST-DEMO") + }, + + // 获取密码(已被哈希处理) 返回密码 + // 在密码被设置前已经被哈希过,不需要重复 + // 算法: (SHA256 + MD5) + // 警告: 这是经过二次哈希的 + getPassWordHashed: (name) => { + return hash.sha256(apis.getPassWordHashedRaw(name)) + hash.md5(apis.getPassWordHashedRaw(name)) + }, + + // 请勿与上面的混淆 + // 上面的是经过第二次哈希的 + getPassWordHashedRaw: (name) => { + return io.open(getUserPath(name) + "/user.json").readJson().passwd + }, + + // 检测刷新令牌是否正确: 账号, 刷新令牌 返回布尔值 + // 密码在服务端经过哈希保存 不需要重复输入密码 + checkRefreshToken: (name, rt) => { + return apis.getRefreshToken(name, apis.getPassWordHashed(name)) === rt + }, + + // 检测访问令牌是否正确: 账号, 访问令牌 返回布尔值 + // 密码在服务端经过哈希保存 不需要重复输入密码 + checkAccessToken: (name, at) => { + return apis.getAccessTokenNonApi(name, apis.getRefreshToken(name, apis.getPassWordHashed(name /* 就是你这个傻逼害得我找两年BUG */))) === at + }, + + // ================================ + // 需要令牌的 API + // ================================ + + // 获取访问令牌: 账号, 刷新令牌 返回访问令牌 失败返回 -1 和原因 + // 有效期: 一天 + // 算法: SHA256(name) + MD5(rt + 盐) + // @Api + getAccessToken: (name, rt) => { + if (!apis.checkRefreshToken(name, rt)) + return { msg: "刷新令牌不正确!", code: -1 } + + return { msg: "成功", code: 0, accessToken: apis.getAccessTokenNonApi(name, rt) } + }, + + // 设置头像: 账号, 访问令牌, 头像数据 返回结果 + // @API + setHeadImage: (name, at, head) => { + if (!apis.checkAccessToken(name, at)) + return { msg: "访问令牌不正确!", code: -1 } + + io.open(vals.LINGCHAIR_USERS_HEAD_DIR + "/" + name + ".png", "w").write(head).close() + + return { msg: "成功", code: 0 } + }, + + // 修改昵称 + // @APi + setNick: (name, at, nick) => { + if (!apis.checkAccessToken(name, at)) + return { msg: "访问令牌不正确!", code: -1 } + + let path = getUserPath(name) + let configIo = io.open(path + "/user.json", "rw") + + let config = configIo.readJson() + config.nick = nick + configIo.writeJson(config) + + configIo.close() + + return { msg: "成功", code: 0 } + }, + + // 取联系人列表(好友): 账号, 访问令牌 返回好友列表 + getFriendsNonApi: (name, at) => { + let file = getUserPath(name) + "/friends.json" + if (!io.exists(file)) + io.open(file, "w").writeJson({list: [name]}).close() + + return io.open(file, "r").readJson().list + }, + + // 取用户昵称: 账号 返回昵称 + getNickNonApi: (name) => { + let file = getUserPath(name) + "/user.json" + + return io.open(file, "r").readJson().nick + }, + + // 取昵称: 账号 返回昵称 + // @API + getNick: (name, at) => { + return { msg: "成功", code: 0, nick: apis.getNickNonApi(name)} + }, + + // 取联系人列表(好友): 账号, 访问令牌 返回好友列表 + // @API + getFriends: (name, at) => { + if (!apis.checkAccessToken(name, at)) + return { msg: "访问令牌不正确!", code: -1 } + + return { msg: "成功", code: 0, friends: apis.getFriendsNonApi(name, at)} + }, +} + +module.exports = apis diff --git a/server_src/color.js b/server_src/color.js new file mode 100644 index 0000000..f451b93 --- /dev/null +++ b/server_src/color.js @@ -0,0 +1,13 @@ +/* + * ©2024 满月叶 + * Github: MoonLeeeaf + * 控制台颜色辅助类 + */ + +module.exports = { + none: "\033[0m", + red: "\033[1;31m", + green: "\033[1;32m", + yellow: "\033[1;33m", + blue: "\033[1;34m", +} diff --git a/server_src/hashlib.js b/server_src/hashlib.js new file mode 100644 index 0000000..83a6350 --- /dev/null +++ b/server_src/hashlib.js @@ -0,0 +1,16 @@ +/* + * ©2024 满月叶 + * Github: MoonLeeeaf + * 哈希辅助类 + */ + +const crypto = require("crypto") + +let apis = { + sha256: (data) => crypto.createHash("sha256").update(data).digest("base64"), + md5: (data) => crypto.createHash("md5").update(data).digest("base64"), + sha256hex: (data) => crypto.createHash("sha256").update(data).digest("hex"), + md5hex: (data) => crypto.createHash("md5").update(data).digest("hex"), +} + +module.exports = apis diff --git a/server_src/httpApi.js b/server_src/httpApi.js new file mode 100644 index 0000000..199008e --- /dev/null +++ b/server_src/httpApi.js @@ -0,0 +1,17 @@ +/* + * ©2024 满月叶 + * Github: MoonLeeeaf + * 铃之椅 Node 服务端 + */ + +// 不得不说 express 太强了 + +const vals = require("./val") +const express = require("express") + +let api = express() + +api.use("/", express.static("ling_chair_http")) +api.use("/users_head/", express.static(vals.LINGCHAIR_DATA_DIR + "/users_head")) + +module.exports = api diff --git a/server_src/iolib.js b/server_src/iolib.js new file mode 100644 index 0000000..be9bc27 --- /dev/null +++ b/server_src/iolib.js @@ -0,0 +1,48 @@ +/* + * ©2024 满月叶 + * Github: MoonLeeeaf + * 更简单地使用 FileSystem 库 + */ + +const fs = require("fs") + +class IoImpl { + constructor(p, m) { + this.path = p + this.mode = m + } + write(byteOrString) { + fs.writeFileSync(this.path, byteOrString) + return this + } + read(type) { + // TODO: impentments this method + return fs.readFileSync(this.path) + } + close() { + delete this.path + delete this.mode + delete this.read + delete this.write + } + readJson() { + return JSON.parse(this.read("*a")) + } + writeJson(data) { + return this.write(JSON .stringify(data)) + } +} + +let apis = { + open: (path, mode) => { + return new IoImpl(path, mode) + }, + mkdirs: (path) => { + try {fs.mkdirSync(path, { recursive: true })}catch(e){} + }, + exists: (path) => { + return fs.existsSync(path) + }, +} + +module.exports = apis diff --git a/server_src/main.js b/server_src/main.js new file mode 100644 index 0000000..95abd27 --- /dev/null +++ b/server_src/main.js @@ -0,0 +1,93 @@ +/* + * ©2024 满月叶 + * Github: MoonLeeeaf + * 铃之椅 Node 服务端 + */ + +console.log("正在初始化...") + +const log = (t) => { + console.log("[" + new Date().toLocaleTimeString('en-US', { hour12: false }) + "] " + t) +} + +const sio = require("socket.io") +const http = require("http") +const https = require("https") +const fs = require("fs") +const process = require("process") +const vals = require("./val") +const color = require("./color") + +//定义 Http 服务器回调 +let httpServerCallback = require("./httpApi") + +// 定义 Socket.io 服务器回调 +let wsServerCallback = require("./wsApi") + +let httpServer +if (vals.LINGCHAIR_SERVER_CONFIG.useHttps) + httpServer = https.createServer({ + key: fs.readFileSync(vals.LINGCHAIR_SERVER_CONFIG.https.key), + cert: fs.readFileSync(vals.LINGCHAIR_SERVER_CONFIG.https.cert), + }, httpServerCallback) +else + httpServer = http.createServer(httpServerCallback) + +let wsServer = new sio.Server(httpServer) + +const cachedClients = {} + +let checkEmpty = (i) => { + if (i instanceof Array) { + for (k in i) { + if (checkEmpty(i[k])) return true + } + } + + return (i == null) || ("" === i) || (0 === i) +} + +wsServer.on("connect", (client) => { + + log("客户端 " + client.handshake.address + " 已连接, 用户名(未经验证): " + client.handshake.auth.name) + + for (const cb in wsServerCallback) { + client.on(cb, (...args) => { + log("客户端 " + client.handshake.address + " 对接口 [" + cb + "] 发起请求,参数为 " + JSON.stringify(args[0])) + let callback = args[args.length - 1] + try { + wsServerCallback[cb](args[0], (reArgs) => { + callback(reArgs) + log("返回接口 [" + cb + "] 到 " + client.handshake.address + ",参数为 " + JSON.stringify(reArgs)) + }, client, cachedClients) + } catch (e) { + log(color.yellow + "调用接口或返回数据时出错: " + e + color.none) + callback({ code: -1, msg: e }) + } + }) + } + + client.on("disconnect", () => { + if (!client.handshake.auth.passCheck) + return log("未验证的客户端 " + client.handshake.address + " 已断开, 未验证的用户名: " + client.handshake.auth.name) + + // 为了支持多客户端登录 我豁出去了 + if (cachedClients[client.handshake.auth.name].length === 1) + cachedClients[client.handshake.auth.name] = null + else + cachedClients[client.handshake.auth.name].forEach((item, index, arr) => { + if (item == client) { + arr.splice(index, 1) + } + }) + log("客户端 " + client.handshake.address + " 已断开, 用户名: " + client.handshake.auth.name) + }) + +}) + +httpServer.listen(vals.LINGCHAIR_SERVER_CONFIG.port) + +console.log(color.red + "=== 铃之椅 - Server ===" + color.none + "\n\r") +console.log(color.yellow + "Github: MoonLeeeaf" + color.none) +log(color.green + "运行服务于端口 " + vals.LINGCHAIR_SERVER_CONFIG.port + " 上," + (vals.LINGCHAIR_SERVER_CONFIG.useHttps == true ? "已" : "未") + "使用 HTTPS" + color.none) +log(color.green + "服务已启动..." + color.none) diff --git a/server_src/val.js b/server_src/val.js new file mode 100644 index 0000000..a75ff26 --- /dev/null +++ b/server_src/val.js @@ -0,0 +1,56 @@ +/* + * ©2024 满月叶 + * Github: MoonLeeeaf + * 铃之椅 Node 服务端 + */ + +const io = require("./iolib") + +let vals = {} + +// 配置目录 +vals.LINGCHAIR_CONFIG_DIR = "ling_chair_config" +// HTTP 服务器资源目录 +vals.LINGCHAIR_HTTP_DIR = "ling_chair_http" +// 服务端配置 +vals.LINGCHAIR_SERVER_CONFIG_FILE = vals.LINGCHAIR_CONFIG_DIR + "/server.json" + +// 主要数据目录 +vals.LINGCHAIR_DATA_DIR = "ling_chair_data" + +// 用户数据 +vals.LINGCHAIR_USERS_DATA_DIR = vals.LINGCHAIR_DATA_DIR + "/users" +// 用户头像 +vals.LINGCHAIR_USERS_HEAD_DIR = vals.LINGCHAIR_DATA_DIR + "/users_head" + +// 群聊消息 +vals.LINGCHAIR_GROUP_MESSAGE_DIR = vals.LINGCHAIR_DATA_DIR + "/messages/group" +// 单聊消息 +vals.LINGCHAIR_SINGLE_MESSAGE_DIR = vals.LINGCHAIR_DATA_DIR + "/messages/single" + +// 用户 ID 计次 +vals.LINGCHAIR_USERS_COUNT_FILE = vals.LINGCHAIR_USERS_DATA_DIR + "/count.txt" + +// 创建必备目录 +io.mkdirs(vals.LINGCHAIR_CONFIG_DIR) +io.mkdirs(vals.LINGCHAIR_USERS_DATA_DIR) +io.mkdirs(vals.LINGCHAIR_USERS_HEAD_DIR) +io.mkdirs(vals.LINGCHAIR_GROUP_MESSAGE_DIR) +io.mkdirs(vals.LINGCHAIR_SINGLE_MESSAGE_DIR) + +// 生成服务端配置文件 +if (!io.exists(vals.LINGCHAIR_SERVER_CONFIG_FILE)) io.open(vals.LINGCHAIR_SERVER_CONFIG_FILE, "w").write(JSON.stringify({ + useHttps: false, + port: 3601, + bindAddress: "", + https: { + key: "", + cert: "", + }, +})).close() +if (!io.exists(vals.LINGCHAIR_USERS_COUNT_FILE)) io.open(vals.LINGCHAIR_USERS_COUNT_FILE, "w").write("10000").close() + +// 加载服务端配置文件 +vals.LINGCHAIR_SERVER_CONFIG = JSON.parse(io.open(vals.LINGCHAIR_SERVER_CONFIG_FILE, "r").read("*a")) + +module.exports = vals diff --git a/server_src/wsApi.js b/server_src/wsApi.js new file mode 100644 index 0000000..cec1c23 --- /dev/null +++ b/server_src/wsApi.js @@ -0,0 +1,214 @@ +/* + * ©2024 满月叶 + * Github: MoonLeeeaf + * 铃之椅 Node 服务端 + */ + +const log = (t) => { + console.log("[" + new Date().toLocaleTimeString('en-US', { hour12: false }) + "] " + t) +} + +const msgs = require("./api-msgs") +const users = require("./api-users") +const color = require("./color") + +let checkEmpty = (i) => { + if (i instanceof Array) { + for (k in i) { + if (checkEmpty(i[k])) return true + } + } + + return (i == null) || ("" === i) || (0 === i) +} + +/* + * Api 规范: + * 1. 禁止中文 拼音 + * 2. 一个 Api 做一件事 同一组 Api 用注释行分隔 + * 3. 尽可能简单易懂 或者打注释 + * 4. 保证客户端可用 + */ + +// Api 调用: + +// 一般规定, code=0 正常, code=-1 异常, code=-2 运行时错误 另外还需要 msg="any" + +// 可以随便 return 进行函数中断 因为这里的调用不会取返回值 + +let api = { + // ---------- 用户 API ---------- + + // 验证 + // 调用方法自己看 + "user.auth": (a, cb, client, cachedClients) => { + if (checkEmpty([a.name, a.refreshToken])) + return cb({ msg: "参数缺失", code: -1 }) + + if (!users.checkRefreshToken(a.name, a.refreshToken)) + return cb({ code: -1, msg: "刷新令牌错误" }) + + log(color.yellow + "客户端 " + client.handshake.address + " 完成了用户 " + a.name + " 的验证" + color.none) + + // 更新映射 + client.handshake.auth.passCheck = true + if (cachedClients[a.name] == null) + cachedClients[a.name] = [] + cachedClients[a.name].push(client) + + cb({ code: 0, msg: "成功" }) + }, + + // 注册 + // {name: 账号, nick: 昵称, passwd: 密码} 返回 {data: {uid: 账号ID}} + // 密码在客户端应该经过哈希处理 算法为 SHA256+MD5 + // 客户端在注册成功之后应该引导用户登录 + "user.signUp": (a, cb) => { + if (checkEmpty([a.name, a.passwd])) + return cb({ msg: "参数缺失", code: -1 }) + + let { uid, msg, code } = users.signUp(a.name, a.passwd) + + if (code !== 0) + return cb({ msg: msg, code: code }) + + cb({ msg: msg, code: 0, data: { uid: uid } }) + }, + // 登录 + // {name: 账号, passwd: 密码} 返回 {data: {refreshToken: 刷新令牌}} + // 密码在客户端应该经过哈希处理 算法为 SHA256+MD5 + "user.signIn": (a, cb) => { + if (checkEmpty([a.name, a.passwd])) + return cb({ msg: "参数缺失", code: -1 }) + + + let { refreshToken, msg, code } = users.signIn(a.name, a.passwd) + + if (code !== 0) + return cb({ msg: msg, code: code }) + + + cb({ msg: msg, code: 0, data: { refreshToken: refreshToken } }) + }, + + // 获取访问令牌 + // {name: 账号, refreshToken: 刷新令牌} 返回 {data: {accessToken: 访问令牌}} + "user.getAccessToken": (a, cb) => { + if (checkEmpty([a.name, a.refreshToken])) + return cb({ msg: "参数缺失", code: -1 }) + + let { accessToken, msg, code } = users.getAccessToken(a.name, a.refreshToken) + + if (code !== 0) + return cb({ msg: msg, code: code }) + + cb({ msg: msg, code: 0, data: { accessToken: accessToken } }) + }, + + // 上传头像 + // {name: 账号, accessToken: 访问令牌, headImage: 头像数据} 返回 {} + "user.setHeadImage": (a, cb) => { + if (checkEmpty([a.name, a.accessToken, a.headImage])) + return cb({ msg: "参数缺失", code: -1 }) + + let { msg, code } = users.setHeadImage(a.name, a.accessToken, a.headImage) + + if (code !== 0) + return cb({ msg: msg, code: code }) + + cb({ msg: msg, code: 0 }) + }, + + // 修改昵称 + "user.setNick": (a, cb) => { + if (checkEmpty([a.name, a.accessToken, a.nick])) + return cb({ msg: "参数缺失", code: -1 }) + + let { msg, code } = users.setNick(a.name, a.accessToken, a.nick) + + if (code !== 0) + return cb({ msg: msg, code: code }) + + cb({ msg: msg, code: 0 }) + }, + + // ---------- 联系人 API -------- + + // 获取好友列表 + // {name: 账号, accessToken: 访问令牌} 返回 {friends: []} + "user.getFriends": (a, cb) => { + if (checkEmpty([a.name, a.accessToken])) + return cb({ msg: "参数缺失", code: -1 }) + + let { msg, code, friends } = users.getFriends(a.name, a.accessToken) + + if (code !== 0) + return cb({ msg: msg, code: code }) + + cb({ msg: msg, code: 0, data: { friends: friends } }) + }, + + "user.getNick": (a, cb) => { + if (checkEmpty([a.name])) + return cb({ msg: "参数缺失", code: -1 }) + + let { msg, code, nick } = users.getNick(a.name) + + if (code !== 0) + return cb({ msg: msg, code: code }) + + cb({ msg: msg, code: 0, data: { nick: nick } }) + }, + + // ---------- 通讯 API ---------- + + // 单聊发送消息 + // {name: 当前用户, target: 发送到, accessToken: 访问密钥, msg: 消息内容} + // 2024.3.30: 支持对方收到消息 + "user.sendSingleMsg": (a, cb, c, cache) => { + if (checkEmpty([a.name, a.target, a.accessToken, a.msg])) + return cb({ msg: "参数缺失", code: -1 }) + + let { msg, code, msgid, time } = msgs.sendSingleMsg(a.name, a.accessToken, a.target, a.msg) + + if (code !== 0) + return cb({ msg: msg, code: code }) + + // 微机课闲的没事干玩玩 发现私聊会多发一个(一个是本地的, 另一个是发送成功的) 选择一个关掉就好了 + // 这里我选择客户端, 否则没法多设备同步 + let args = { + target: a.name, + msg: { + msgid: msgid, + time: time, + msg: a.msg, + name: a.name, + }, + type: "single", + } + + if (cache[a.target] != null) + cache[a.target].forEach((v) => { + v.emit("msg.receive", args, () => { }) + log("尝试向客户端 " + v.handshake.address + " 发送事件 [msg.receive], 参数为 " + JSON.stringify(args)) + }) + + cb({ msg: msg, code: 0, data: { time: time } }) + }, + + // 单聊获取历史记录 + // {name: 当前用户, target: 聊天目标, accessToken: 访问密钥, startId: 计次开始的msgid, limit: 最大返回数(最大100)} + "user.getSingleChatHistroy": (a, cb) => { + if (checkEmpty([a.name, a.target, a.accessToken, a.limit])) + return cb({ msg: "参数缺失", code: -1 }) + + let { msg, code, histroy } = msgs.getSingleMsgHistroy(a.name, a.accessToken, a.target, a.startId, a.limit) + + if (code !== 0) + return cb({ msg: msg, code: code }) + + cb({ msg: msg, code: 0, data: { histroy: histroy } }) + }, +} + +module.exports = api