diff --git a/chat.js b/chat.js index 6579754..35ec748 100644 --- a/chat.js +++ b/chat.js @@ -4,8 +4,10 @@ const http = require('http'); const fs = require('fs'); const { once } = require("node:events"); const dateformat = require('@matteo.collina/dateformat'); +const WebSocket = require('ws'); const logger = client.logger +const steamUser = client.steamUser fs.mkdir("./logs", { recursive: true }, (err) => { if (err) { @@ -25,33 +27,64 @@ async function sendMsg(req, res) { console.log(requests) - client.steamUser.chat.sendFriendMessage(requests.id, requests.msg, (err, response) => { - if (err) { - console.log(err); - res.statusCode = 500; - res.setHeader('Content-Type', 'text/plain'); - res.end('Internal Server Error\n'); - return; - } - - res.statusCode = 200; + let [err, response] = sendFriendMessage(requests.id, requests.msg); + if (err) { + console.log(err); + res.statusCode = 500; res.setHeader('Content-Type', 'text/plain'); - res.end('Success\n'); + res.end('Internal Server Error\n'); + return; + } - getUserInfo(client.steamUser.steamID).then((sender) => { - fs.appendFile("./logs/chat.jsonl", JSON.stringify({ - date: dateToString(response.server_timestamp), - echo: true, - id: requests.id, - name: sender.player_name, - message: response.modified_message, - ordinal: response.ordinal, - }) + "\n", (e) => { - if (e) { - logger.error("an error occurred while writing chat log file: " + e); - } - }); + res.statusCode = 200; + res.setHeader('Content-Type', 'text/plain'); + res.end('Success\n'); + + logSendMsg(requests.id, response); +} + +function sendFriendMessage(uid, msg) { + return new Promise((resolve, reject) => { + client.steamUser.chat.sendFriendMessage(uid, msg, (err, response) => { + if (err) { + resolve([err, response]); + return + } + + client.getUserInfo(client.steamUser.steamID).then((sender) => { + fs.appendFile("./logs/chat.jsonl", JSON.stringify({ + date: dateToString(response.server_timestamp), + echo: true, + id: uid, + name: sender.player_name, + message: response.modified_message, + ordinal: response.ordinal, + }) + "\n", (e) => { + if (e) { + logger.error("an error occurred while writing chat log file: " + e); + } + }); + }) + + resolve([err, response]); }) + }); +} + +function logSendMsg(uid, response) { + client.getUserInfo(client.steamUser.steamID).then((sender) => { + fs.appendFile("./logs/chat.jsonl", JSON.stringify({ + date: dateToString(response.server_timestamp), + echo: true, + id: uid, + name: sender.player_name, + message: response.modified_message, + ordinal: response.ordinal, + }) + "\n", (e) => { + if (e) { + logger.error("an error occurred while writing chat log file: " + e); + } + }); }) } @@ -65,58 +98,53 @@ async function sendImg(req, res) { let requests = JSON.parse(body); - if (requests.img) { - console.log({ - id: requests.id, - img: requests.img.length, - }); - } else { - console.log(requests); + res.setHeader('Content-Type', 'text/plain'); + let err = await sendImageToUser(requests.id, requests.img, requests.url); + if (err) { + const { code = 500, message = "Internal Server Error" } = err; + res.statusCode = code; + res.end(message + '\n'); + return } - - if (requests.url) { - img = await readUrlAsBuffer(requests.url) - } else if (requests.img) { - img = Buffer.from(requests.img, 'base64'); - } else { - res.statusCode = 400; - res.setHeader('Content-Type', 'text/plain'); - res.end('Bad Request\n'); - return; - } - - client.steamCommunity.sendImageToUser(requests.id, img, function (err, imageUrl) { - if (err) { - logger.error("an error occurred while sending image: ", err); - - client.steamUser.webLogOn(); - - res.statusCode = 500; - res.setHeader('Content-Type', 'text/plain'); - res.end('Internal Server Error\n'); - return - } - - res.statusCode = 200; - res.setHeader('Content-Type', 'text/plain'); - res.end('Success\n'); - }); + res.statusCode = 200; + res.end('Success\n'); } -const server = http.createServer(async (req, res) => { - try { - if (req.url == "/img") { - await sendImg(req, res); +function sendImageToUser(uid, img, url) { + return new Promise(async (resolve, reject) => { + if (img) { + console.log({ + uid: uid, + img: img.length, + }); } else { - await sendMsg(req, res); + console.log({ + uid: uid, + url: url, + }); } - } catch (e) { - console.error("An error occurred while processing the request: ", e); - res.statusCode = 500; - res.setHeader('Content-Type', 'text/plain'); - res.end('Internal Server Error\n'); - } -}); + + if (url) { + img = await readUrlAsBuffer(url) + } else if (img) { + img = Buffer.from(img, 'base64'); + } else { + resolve({ code: 400, message: "Bad Request" }); + return; + } + + client.steamCommunity.sendImageToUser(uid, img, function (err, imageUrl) { + if (err) { + logger.error("an error occurred while sending image: ", err); + client.steamUser.webLogOn(); + resolve({}); + return + } + + resolve(); + }); + }) +} async function readUrlAsBuffer(url) { try { @@ -142,11 +170,116 @@ function dateToString(date) { return dateformat(date, "yyyy-mm-dd HH:MM:ss.l"); } -async function getUserInfo(steamID) { - return client.getUserInfo(steamID, (ignore) => { +async function handleHttp(req, res) { + try { + if (req.url == "/img") { + await sendImg(req, res); + } else { + await sendMsg(req, res); + } + } catch (e) { + console.error("An error occurred while processing the request: ", e); + res.statusCode = 500; + res.setHeader('Content-Type', 'text/plain'); + res.end('Internal Server Error\n'); + } +} + +function handleWs(ws) { + console.log('WebSocket connection established.'); + + ws.on('message', (message) => { + let data; + try { + data = JSON.parse(message); + } catch { + ws.close(1003, 'Invalid JSON'); + return; + } + logger.info('Received message:', data); + + switch (data.type) { + case "msg": + sendFriendMessage(data.id, data.msg).then(([err, response]) => { + if (err) { + return; + } + + onSteamMessage({ + server_timestamp: response.server_timestamp, + steamid_friend: data.id, + message: response.modified_message, + ordinal: response.ordinal, + }, true); + }); + break; + case "img": + sendImageToUser(data.id, data.img, data.url); + break + } + }); + + ws.on('close', () => { + logger.info('WebSocket connection closed.'); + }); + + ws.on('error', (err) => { + logger.error('WebSocket error:', err); }); } -server.listen(3000, '0.0.0.0', () => { - console.log('Server running at http://0.0.0.0:3000/'); +async function messageToJson(message, echo) { + let friendId = message.steamid_friend + if (typeof friendId !== 'string') { + friendId = friendId.getSteamID64(); + } + + let sender = await client.getUserInfo(echo ? steamUser.steamID : friendId, (ignore) => { }); + + return JSON.stringify({ + date: dateToString(message.server_timestamp), + echo: echo, + id: friendId, + name: sender.player_name, + message: message.message, + ordinal: message.ordinal, + }) +} + +async function onSteamMessage(message, echo) { + let messageEncoded = await messageToJson(message, echo); + + wss.clients.forEach((client) => { + if (client.readyState === WebSocket.OPEN) { + client.send(messageEncoded); + } + }); +} + +client.steamLoginPromise.then(() => { + steamUser.chat.on("friendMessage", (message) => { + // noinspection JSIgnoredPromiseFromCall + onSteamMessage(message, false); + }); + + steamUser.chat.on("friendMessageEcho", (message) => { + // noinspection JSIgnoredPromiseFromCall + onSteamMessage(message, true); + }); + + try { + const server = http.createServer(handleHttp); + + // 创建 WebSocket 服务器,并限制路径为 /ws + globalThis.wss = new WebSocket.Server({ server, path: '/ws' }); + + wss.on('connection', handleWs); + + server.listen(3000, '0.0.0.0', () => { + console.log('Server running at http://0.0.0.0:3000/'); + console.log('WebSocket server is also running.'); + }); + } catch (err) { + console.error('Error during initialization:', err); + } }); diff --git a/package-lock.json b/package-lock.json index 0f2d6f3..ba1f0da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,8 @@ "steam-totp": "^2.1.2", "steam-user": "^5.0.8", "steamcommunity": "^3.48.2", - "winston": "^3.12.0" + "winston": "^3.12.0", + "ws": "^8.18.1" } }, "node_modules/@bbob/parser": { @@ -1506,6 +1507,27 @@ "node": ">= 12.0.0" } }, + "node_modules/ws": { + "version": "8.18.1", + "resolved": "https://mvn.tursom.cn:20080/repository/npm/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "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 + } + } + }, "node_modules/xml2js": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", diff --git a/package.json b/package.json index 4dadff4..ba3726e 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "steam-chat", "version": "1.0.0", "description": "", + "type": "commonjs", "main": "logger.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" @@ -15,6 +16,7 @@ "steam-totp": "^2.1.2", "steam-user": "^5.0.8", "steamcommunity": "^3.48.2", - "winston": "^3.12.0" + "winston": "^3.12.0", + "ws": "^8.18.1" } }