diff --git a/node-session-server.zip b/node-session-server.zip new file mode 100644 index 0000000..227ebba Binary files /dev/null and b/node-session-server.zip differ diff --git a/package.json b/package.json index 37ec10a..d7db51c 100644 --- a/package.json +++ b/package.json @@ -10,12 +10,15 @@ "dependencies": { "bcrypt": "^5.1.1", "cors": "^2.8.5", + "date-fns": "^4.1.0", "dotenv": "^16.4.5", "express": "^4.18.2", "jsonwebtoken": "^9.0.2", "mongoose": "^8.2.0", "node-cron": "^3.0.3", "nodemailer": "^6.9.15", + "systeminformation": "^5.31.0", + "telegraf": "^4.16.3", "tree-kill": "^1.2.2" }, "devDependencies": { diff --git a/src/index.ts b/src/index.ts index 53287a3..02ba372 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,41 +1,76 @@ import "dotenv/config"; -import connectDB from "./config/db"; -import express, { json } from "express"; +import { exec, execFile } from "child_process"; import cors from "cors"; +import express, { json } from "express"; import os, { networkInterfaces } from "os"; import util from "util"; -import { exec, execFile } from "child_process"; -import treeKill from "tree-kill"; -import SessionServer from "./models/SessionServer"; -import ActiveSession from "./models/ActiveSession"; import { schedule } from "node-cron"; +import si from "systeminformation"; +import treeKill from "tree-kill"; + +import connectDB from "./config/db"; +import ActiveSession from "./models/ActiveSession"; +import SessionServer from "./models/SessionServer"; import sendMessageRoute from "./routes/sendMessage"; -import ServerStatusLog from "./models/ServerStatusLog"; + +// Constants +const BUILDS_PATH = "C:/pixel-streaming/builds"; +const CIRRUS_SERVER_PATH = "C:/pixel-streaming/signalling-server/cirrus.js"; +const CIRRUS_PORTS = [14000, 14001, 14002] as const; +const PORT_OFFSET = 10; +const GPU_POLL_INTERVAL_MS = 1000; + +const UE_PROCESS_ARGS = [ + "-RenderOffScreen", + "-ForceRes", + "-ResX=1920", + "-ResY=1080", + "-Unattended", + "-PixelStreamingWebRTCMinBitrate=5000000", + "-PixelStreamingWebRTCMaxBitrate=20000000", + "-PixelStreamingH264Profile=HIGH", + "-PixelStreamingWebRTCDisableReceiveAudio=true", + "-PixelStreamingEncoderRateControl=VBR", +] as const; + +function getUeProcessArgs(uePort: number): string[] { + return [ + "-PixelStreamingIP=127.0.0.1", + `-PixelStreamingPort=${uePort}`, + ...UE_PROCESS_ARGS, + ]; +} connectDB(); const app = express(); -const port = process.env.PORT; +const port = process.env.PORT ?? 3000; app.use(json()); app.use(cors()); - app.use("/sendMessage", sendMessageRoute); -const serverLocation = process.env.SERVER_LOCATION; -const serverName = process.env.SERVER_NAME; -const serverType = process.env.SERVER_TYPE; -const serverHostname = os.hostname(); +const serverConfig = { + location: process.env.SERVER_LOCATION, + name: process.env.SERVER_NAME, + type: process.env.SERVER_TYPE, + hostname: os.hostname(), +}; const nets = networkInterfaces(); -const localIP = Object.entries(nets) - .find((net) => net[0].includes("Ethernet"))?.[1] - ?.find((obj) => obj.family === "IPv4")?.address; +function getLocalIP(): string | undefined { + return Object.entries(nets) + .find((net) => net[0].includes("Ethernet"))?.[1] + ?.find((obj) => obj.family === "IPv4")?.address; +} -async function updateSessionServerData(data: any) { +async function updateSessionServerData(data: Record) { try { - await SessionServer.findOneAndUpdate({ hostname: serverHostname }, data); + await SessionServer.findOneAndUpdate( + { hostname: serverConfig.hostname }, + data + ); } catch (error) { if (error instanceof Error) { console.log("Error: ", error.message); @@ -45,42 +80,66 @@ async function updateSessionServerData(data: any) { const execAsync = util.promisify(exec); -async function getGpuMemoryFree() { +async function getSystemStats() { try { - const { stdout } = await execAsync( - "nvidia-smi --query-gpu=memory.free --format=csv,noheader,nounits", - { windowsHide: true } - ); + const data: Record = {}; - const gpuMemoryFree = stdout.trimEnd(); + // RAM (os) + const ramTotalMB = Math.round(os.totalmem() / 1024 / 1024); + const ramUsedMB = ramTotalMB - Math.round(os.freemem() / 1024 / 1024); + data.ramTotal = ramTotalMB; + data.ramUsed = ramUsedMB; - await updateSessionServerData({ gpuMemoryFree }); + // CPU (systeminformation) + const cpuLoad = await si.currentLoad(); + data.cpuUtilization = Math.round(cpuLoad.currentLoad); + + // GPU (nvidia-smi) + try { + const { stdout } = await execAsync( + "nvidia-smi --query-gpu=memory.total,memory.used,utilization.gpu --format=csv,noheader,nounits", + { windowsHide: true } + ); + + const values = stdout + .trim() + .split("\n")[0] + .split(",") + .map((v) => parseInt(v.trim(), 10)); + const [gpuTotal, gpuUsed, gpuUtilization] = values; + + if (!Number.isNaN(gpuTotal) && !Number.isNaN(gpuUsed)) { + data.gpuMemoryTotal = gpuTotal; + data.gpuMemoryUsed = gpuUsed; + if (!Number.isNaN(gpuUtilization)) { + data.gpuUtilization = gpuUtilization; + } + } + } catch { + // GPU unavailable, CPU/RAM still updated + } + + await updateSessionServerData(data); } catch (error) { if (error instanceof Error) { console.log("Error: ", error.message); } } - setTimeout(async () => { - await getGpuMemoryFree(); - }, 1000); + setTimeout(getSystemStats, GPU_POLL_INTERVAL_MS); } async function getAvailablePorts() { - const cirrusPorts = [14000, 14001, 14002]; - - for (const cirrusPort of cirrusPorts) { + for (const cirrusPort of CIRRUS_PORTS) { const activeSession = await ActiveSession.exists({ - location: serverLocation, - name: serverName, + location: serverConfig.location, + name: serverConfig.name, cirrusPort, }); if (activeSession) continue; - const uePort = cirrusPort + 10; - - return { cirrusPort, uePort }; + return { cirrusPort, uePort: cirrusPort + PORT_OFFSET }; } } @@ -89,8 +148,7 @@ async function startSession( ownerIp: string, endAt?: string ) { - const filePath = `C:/pixel-streaming/builds/${buildName}/${buildName}.exe`; - + const filePath = `${BUILDS_PATH}/${buildName}/${buildName}.exe`; const availablePorts = await getAvailablePorts(); if (!availablePorts) { @@ -101,11 +159,11 @@ async function startSession( const { cirrusPort, uePort } = availablePorts; const cirrusProcess = execFile("node", [ - `C:/pixel-streaming/signalling-server/cirrus.js`, - `--StreamerPort`, - `${uePort}`, - `--HttpPort`, - `${cirrusPort}`, + CIRRUS_SERVER_PATH, + "--StreamerPort", + String(uePort), + "--HttpPort", + String(cirrusPort), ]); const cirrusProcessId = cirrusProcess.pid; @@ -115,38 +173,24 @@ async function startSession( return; } - const ueProcess = execFile(filePath, [ - "-PixelStreamingIP=127.0.0.1", - `-PixelStreamingPort=${uePort}`, - "-RenderOffScreen", - "-ForceRes", - "-ResX=1920", - "-ResY=1080", - "-Unattended", - "-PixelStreamingWebRTCMinBitrate=5000000", - "-PixelStreamingWebRTCMaxBitrate=20000000", - "-PixelStreamingH264Profile=HIGH", - "-PixelStreamingWebRTCDisableReceiveAudio=true", - "-PixelStreamingEncoderRateControl=VBR", - // "-PixelStreamingHudStats=true", - // `-SessionID=${session.id}`, - ]); + const ueProcess = execFile(filePath, getUeProcessArgs(uePort)); const ueProcessId = ueProcess.pid; if (!ueProcessId) { console.log("UE application was not started"); + treeKill(cirrusProcessId); return; } - const type = serverType; + const localIP = getLocalIP(); try { const activeSession = await ActiveSession.create({ - location: serverLocation, - name: serverName, + location: serverConfig.location, + name: serverConfig.name, buildName, - type, + type: serverConfig.type, cirrusPort, uePort, cirrusProcessId, @@ -187,24 +231,26 @@ async function endSession(activeSessionId: string) { async function init() { try { - await ServerStatusLog.create({ - hostname: serverHostname, - action: "online", - }); + // await ServerStatusLog.create({ + // hostname: serverHostname, + // action: "online", + // }); + + const localIP = getLocalIP(); await SessionServer.findOneAndUpdate( - { hostname: serverHostname }, + { hostname: serverConfig.hostname }, { - location: serverLocation, - name: serverName, - type: serverType, - hostname: serverHostname, + location: serverConfig.location, + name: serverConfig.name, + type: serverConfig.type, + hostname: serverConfig.hostname, localIP, }, { upsert: true, new: true } ); - getGpuMemoryFree(); + getSystemStats(); } catch (error) { if (error instanceof Error) { console.log("Error: ", error.message); @@ -247,52 +293,39 @@ app.listen(port, () => { init(); }); -schedule("*/3 * * * * *", async () => { - // TODO - hostname +async function checkAndRestartUeProcesses() { try { const activeSessions = await ActiveSession.find({ - location: serverLocation, - name: serverName, + location: serverConfig.location, + name: serverConfig.name, }); for (const activeSession of activeSessions) { const { ueProcessId, buildName, uePort, id } = activeSession; + if (uePort == null || !buildName) continue; + const { stdout } = await execAsync( `wmic process where processId=${ueProcessId}`, { windowsHide: true } ); const ueProcessInfo = stdout.trim(); + if (ueProcessInfo) continue; - if (ueProcessInfo) return; + const filePath = `${BUILDS_PATH}/${buildName}/${buildName}.exe`; + const newUeProcess = execFile(filePath, getUeProcessArgs(uePort)); + const newUeProcessId = newUeProcess.pid; - const filePath = `C:/pixel-streaming/builds/${buildName}/${buildName}.exe`; - - const newUeProcess = execFile(filePath, [ - "-PixelStreamingIP=127.0.0.1", - `-PixelStreamingPort=${uePort}`, - "-RenderOffScreen", - "-ForceRes", - "-ResX=1920", - "-ResY=1080", - "-Unattended", - "-PixelStreamingWebRTCMinBitrate=5000000", - "-PixelStreamingWebRTCMaxBitrate=20000000", - "-PixelStreamingH264Profile=HIGH", - "-PixelStreamingWebRTCDisableReceiveAudio=true", - "-PixelStreamingEncoderRateControl=VBR", - // "-PixelStreamingHudStats=true", - // `-SessionID=${session.id}`, - ]); - - const newUeProcesscessId = newUeProcess.pid; - - await ActiveSession.findByIdAndUpdate(id, { - ueProcessId: newUeProcesscessId, - }); + if (newUeProcessId) { + await ActiveSession.findByIdAndUpdate(id, { + ueProcessId: newUeProcessId, + }); + } } } catch (error) { - console.log(error); + console.error("checkAndRestartUeProcesses:", error); } -}); +} + +schedule("*/3 * * * * *", checkAndRestartUeProcesses); diff --git a/src/models/SessionServer.ts b/src/models/SessionServer.ts index 428fc18..f207b7c 100644 --- a/src/models/SessionServer.ts +++ b/src/models/SessionServer.ts @@ -15,7 +15,22 @@ const sessionServerSchema = new Schema( type: String, unique: true, }, - gpuMemoryFree: { + gpuMemoryTotal: { + type: Number, + }, + gpuMemoryUsed: { + type: Number, + }, + gpuUtilization: { + type: Number, + }, + cpuUtilization: { + type: Number, + }, + ramTotal: { + type: Number, + }, + ramUsed: { type: Number, }, localIP: { diff --git a/src/routes/sendMessage.ts b/src/routes/sendMessage.ts index 27892fd..872cd68 100644 --- a/src/routes/sendMessage.ts +++ b/src/routes/sendMessage.ts @@ -13,7 +13,7 @@ router.post("/", async (req, res) => { port: req.body.port, secure: true, // true for 465, false for other ports auth: { - user: req.body.from, // generated ethereal user + user: req.body.login, // generated ethereal user pass: req.body.password, // generated ethereal password }, }); @@ -21,7 +21,7 @@ router.post("/", async (req, res) => { const files = fs.readdirSync(req.body.path); // send mail with defined transport object - await transporter.sendMail({ + const result = await transporter.sendMail({ from: req.body.from, // sender address to: req.body.to, // list of receivers subject: req.body.subject, // Subject line @@ -29,6 +29,8 @@ router.post("/", async (req, res) => { attachments: files.map((file) => ({ path: `${req.body.path}/${file}` })), // attachment files }); + console.log(result); + return res.json({ ok: 1 }); } catch (error) { console.log(error); diff --git a/yarn.lock b/yarn.lock index b435781..c7ade91 100644 --- a/yarn.lock +++ b/yarn.lock @@ -49,6 +49,11 @@ dependencies: sparse-bitfield "^3.0.3" +"@telegraf/types@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@telegraf/types/-/types-7.1.0.tgz#d8bd9b2f5070b4de46971416e890338cd89fc23d" + integrity sha512-kGevOIbpMcIlCDeorKGpwZmdH7kHbqlk/Yj6dEpJMKEQw5lk0KVQY0OLXaCswy8GqlIVLd5625OB+rAntP9xVw== + "@tsconfig/node10@^1.0.7": version "1.0.9" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" @@ -203,6 +208,13 @@ abbrev@1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + accepts@~1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" @@ -320,11 +332,29 @@ bson@^6.2.0: resolved "https://registry.yarnpkg.com/bson/-/bson-6.4.0.tgz#99c2e37b515e6766ce8b5929e01fa79de5767d50" integrity sha512-6/gSSEdbkuFlSb+ufj5jUSU4+wo8xQOwm2bDSqwmxiPE17JTpsP63eAwoN8iF8Oy4gJYj+PAL3zdRCTdaw5Y1g== +buffer-alloc-unsafe@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" + integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg== + +buffer-alloc@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec" + integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow== + dependencies: + buffer-alloc-unsafe "^1.1.0" + buffer-fill "^1.0.0" + buffer-equal-constant-time@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== +buffer-fill@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" + integrity sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ== + bytes@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" @@ -411,6 +441,11 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== +date-fns@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-4.1.0.tgz#64b3d83fff5aa80438f5b1a633c2e83b8a1c2d14" + integrity sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg== + debug@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -425,6 +460,13 @@ debug@4, debug@4.x, debug@^4: dependencies: ms "2.1.2" +debug@^4.3.4: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + define-data-property@^1.1.2: version "1.1.4" resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" @@ -508,6 +550,11 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + express@^4.18.2: version "4.18.3" resolved "https://registry.yarnpkg.com/express/-/express-4.18.3.tgz#6870746f3ff904dee1819b82e4b51509afffb0d4" @@ -964,6 +1011,11 @@ mquery@5.0.0: dependencies: debug "4.x" +mri@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" + integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -974,7 +1026,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.1.1: +ms@2.1.3, ms@^2.1.1, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -996,7 +1048,7 @@ node-cron@^3.0.3: dependencies: uuid "8.3.2" -node-fetch@^2.6.7: +node-fetch@^2.6.7, node-fetch@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== @@ -1077,6 +1129,11 @@ once@^1.3.0: dependencies: wrappy "1" +p-timeout@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-4.1.0.tgz#788253c0452ab0ffecf18a62dff94ff1bd09ca0a" + integrity sha512-+/wmHtzJuWii1sXn3HCuH/FTwGhrp4tmJTxSKJbfS+vkipci6osxXM5mY0jUiRzWKMTgUT8l7HFbeSwZAynqHw== + parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" @@ -1165,11 +1222,23 @@ safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== +safe-compare@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/safe-compare/-/safe-compare-1.1.4.tgz#5e0128538a82820e2e9250cd78e45da6786ba593" + integrity sha512-b9wZ986HHCo/HbKrRpBJb2kqXMK9CEWIE1egeEvZsYn69ay3kdfl9nG3RyOcR+jInTDf7a86WQ1d4VJX7goSSQ== + dependencies: + buffer-alloc "^1.2.0" + "safer-buffer@>= 2.1.2 < 3": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +sandwich-stream@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/sandwich-stream/-/sandwich-stream-2.0.2.tgz#6d1feb6cf7e9fe9fadb41513459a72c2e84000fa" + integrity sha512-jLYV0DORrzY3xaz/S9ydJL6Iz7essZeAfnAavsJ+zsJGZ1MOnsS52yRjU3uF3pJa/lla7+wisp//fxOwOH8SKQ== + semver@^6.0.0: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" @@ -1302,6 +1371,11 @@ supports-color@^5.5.0: dependencies: has-flag "^3.0.0" +systeminformation@^5.31.0: + version "5.31.0" + resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.31.0.tgz#fce21969b080e6971accf2d601b78f17271d7715" + integrity sha512-z5pjzvC8UnQJ/iu34z+mo3lAeMzTGdArjPQoG5uPyV5XY4BY+M6ZcRTl4XnZqudz6sP713LhWMKv6e0kGFGCgQ== + tar@^6.1.11: version "6.2.0" resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.0.tgz#b14ce49a79cb1cd23bc9b016302dea5474493f73" @@ -1314,6 +1388,20 @@ tar@^6.1.11: mkdirp "^1.0.3" yallist "^4.0.0" +telegraf@^4.16.3: + version "4.16.3" + resolved "https://registry.yarnpkg.com/telegraf/-/telegraf-4.16.3.tgz#f03fa30482b540a7f9895af8f13ec8f432840a66" + integrity sha512-yjEu2NwkHlXu0OARWoNhJlIjX09dRktiMQFsM678BAH/PEPVwctzL67+tvXqLCRQQvm3SDtki2saGO9hLlz68w== + dependencies: + "@telegraf/types" "^7.1.0" + abort-controller "^3.0.0" + debug "^4.3.4" + mri "^1.2.0" + node-fetch "^2.7.0" + p-timeout "^4.1.0" + safe-compare "^1.1.4" + sandwich-stream "^2.0.2" + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"