From a485bd94ced4634761a678c6f8a1e48f90cb12d0 Mon Sep 17 00:00:00 2001 From: Zuxi Date: Fri, 7 Jun 2024 02:03:17 -0400 Subject: [PATCH] Push Files --- .env.example | 3 + .gitignore | 103 ++++++++++ config.js | 46 +++++ gamecache.json | 3 + main.js | 127 ++++++++++++ package-lock.json | 491 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 20 ++ readme.md | 5 + sniffeddata.json | 44 +++++ temp.txt | 0 utils.js | 216 ++++++++++++++++++++ 11 files changed, 1058 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 config.js create mode 100644 gamecache.json create mode 100644 main.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 readme.md create mode 100644 sniffeddata.json delete mode 100644 temp.txt create mode 100644 utils.js diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..2a2b964 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +SECRET=discord bot token because this isnt a self bot +USERTOKENSECRET=DISCORDTOKEN you win need to get this yourself this is only used to generate the images and request are cached to not spam the api +REDIS_CONNECTION_URI=Only if you use redis will you need this \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3b797e2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,103 @@ +### VSCODE DIDNT CREATE THIS FILE SO CHATGPT DID :) + +# Node.js dependency directories +node_modules/ + +# Optional npm cache directory +.npm/ + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Dependency directories +jspm_packages/ + +# Logs +logs/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/en/knowledge/errors/diagnostic-reports/) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids/ +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov/ + +# Coverage directory used by tools like istanbul +coverage/ +*.lcov + +# nyc test coverage +.nyc_output/ + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt/ + +# Bower dependency directory (https://bower.io/) +bower_components/ + +# Editor directories and files +.idea/ +.vscode/ +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# IDE or Editor specific files +*.iml +*.sublime-project +*.sublime-workspace + +# Optional eslint cache +.eslintcache + +# dotenv environment variables file +.env + +# Next.js build output +.next/ + +# Nuxt.js build output +.nuxt/ + +# Gatsby files +.cache/ +public/ + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# Docusaurus cache +.docusaurus/ + +# Temporary files +*.tmp +*.temp + +# MacOS files +.DS_Store +*.DS_Store + +# Windows files +Thumbs.db +ehthumbs.db \ No newline at end of file diff --git a/config.js b/config.js new file mode 100644 index 0000000..c3518f7 --- /dev/null +++ b/config.js @@ -0,0 +1,46 @@ +require("dotenv").config(); + +module.exports = { + OP: { + // only store the important opcodes only. + // https://discord.com/developers/docs/topics/opcodes-and-status-codes + dispatch: 0, + heartbeat: 1, + identify: 2, + presenceUpdate: 3, + resume: 6, + reconnect: 7, + invalidSession: 9, + HELLO: 10, + heartbeatACK: 11 + }, + + Constants: { + // i still have no idea. + // dont change this unless you know what you're doing. + seq: 13373333, + + // default, discord will figure it out. + heartbeatTimeout: 1000 * 30, + + // https://www.remote.tools/remote-work/how-to-find-discord-id + userMonitoredID: "459742547305299981", + + // for resuming. so your app wont ded immediately, so you have to send the request again. (afaik) + sessionID: null, + + // setinterval func + heartbeatInterval: null + }, +}; + +module.exports.Identification = { + token: process.env.SECRET, // your bot token. + v: 9, // https://discord.com/developers/docs/topics/gateway#gateways-gateway-versions + intents: 1 << 1 | 1 << 8, // https://discord.com/developers/docs/topics/gateway#list-of-intents + properties: { + "$os": process.platform, + "$browser": "ZUXI_EXPOSED_SECRETS", + "$device": "13373333" + } +} \ No newline at end of file diff --git a/gamecache.json b/gamecache.json new file mode 100644 index 0000000..31b7ee1 --- /dev/null +++ b/gamecache.json @@ -0,0 +1,3 @@ +{ + "383226320970055681": "bc45e1c85351ce0bafcb9245b3762e75" +} \ No newline at end of file diff --git a/main.js b/main.js new file mode 100644 index 0000000..9fb2364 --- /dev/null +++ b/main.js @@ -0,0 +1,127 @@ +const WebSocket = require("ws"); +const config = require("./config"); +const { extractTopActivity } = require("./utils"); + +// supported non-browser (for Server-side) only. +let ws = new WebSocket(`wss://gateway.discord.gg/?v=${config.Identification.v}&encoding=json`); + + +ws.on("message", (raw) => { + // raw Websocket data is made out of Buffer. so you have to convert it into JSON. devhuman-readable. + + if (!raw) return; + + let data; + try { + data = JSON.parse(Buffer.from(raw).toString("utf-8")); + } catch (err) { + data = null; + return console.error(err); + }; + + // failed parsing the JSON, well just break it. + if (!data) return; + // console.log(data); + + let identifyRequest = JSON.stringify({op: config.OP.identify, d: config.Identification}); + let heartbeat = JSON.stringify({op: config.OP.heartbeat, d: config.Constants.seq}); + + switch(data.op) { + case config.OP.dispatch: + if (data.t === "READY") { + config.Constants.sessionID = data.d.session_id; + console.log("ready."); + }; + break; + + case config.OP.heartbeat: + ws.send(heartbeat); + console.log("heartbeat."); + break; + + case config.OP.invalidSession: + config.Constants.seq = 0; + config.Constants.sessionID = null; + ws.send(identifyRequest); + console.warn("invalid session, identifying."); + break; + + case config.OP.reconnect: + if (!ws) return; + + clearInterval(config.Constants.heartbeatInterval); + config.Constants.heartbeatInterval = null; + + if (ws.readyState !== ws.CLOSED /*&& ws.readyState !== ws.CLOSING*/) { + try { + if (config.Constants.sessionID) { + if (ws.readyState === ws.OPEN) { + console.log("reconnecting."); + ws.close(4901, "reconnect."); + } + + else { + console.warn("terminated."); + ws.terminate(); + } + } else { + console.log("session continued."); + ws.close(1000, "continue."); + }; + } catch (error) { + console.error(error); + }; + }; + + ws = null; + break; + + case config.OP.HELLO: + if (data.d.heartbeat_interval > 0) { + if (config.Constants.heartbeatInterval) clearInterval(config.Constants.heartbeatInterval); + config.Constants.heartbeatInterval = setInterval(() => ws.send(heartbeat), data.d.heartbeat_interval); + }; + + if (config.Constants.sessionID) { + console.log("resuming the connection."); + ws.send(JSON.stringify({ + op: config.OP.resume, + d: { + token: config.Identification.token, + session_id: config.Constants.sessionID, + seq: config.Constants.seq + } + })); + } else { + ws.send(identifyRequest); + ws.send(heartbeat); + }; + break; + + // you and i dont need to know this. + case config.OP.heartbeatACK: + break; + + default: + console.log(data); + break; + }; + + if ( + data.t === "PRESENCE_UPDATE" && + data.d.user.id === config.Constants.userMonitoredID && + data.op === config.OP.dispatch + ) { + // for lurking + //console.log(require("util").inspect(data, false, null, false)); + extractTopActivity(JSON.stringify(data.d)) + // return redis.set("activity.ray1337", result) + // .catch(console.error); + }; +}); + +ws.on("error", console.error); + +ws.on("close", (code, reason) => { + console.log(code, Buffer.from(reason).toString()); +}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..5427744 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,491 @@ +{ + "name": "useractivity", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "useractivity", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "discord.js": "^14.14.1", + "dotenv": "^16.3.1", + "node-fetch": "^3.3.2", + "querystring": "^0.2.1", + "redis": "^4.6.14", + "ws": "^8.15.1" + } + }, + "node_modules/@discordjs/builders": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.7.0.tgz", + "integrity": "sha512-GDtbKMkg433cOZur8Dv6c25EHxduNIBsxeHrsRoIM8+AwmEZ8r0tEpckx/sHwTLwQPOF3e2JWloZh9ofCaMfAw==", + "dependencies": { + "@discordjs/formatters": "^0.3.3", + "@discordjs/util": "^1.0.2", + "@sapphire/shapeshift": "^3.9.3", + "discord-api-types": "0.37.61", + "fast-deep-equal": "^3.1.3", + "ts-mixer": "^6.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/collection": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", + "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/formatters": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.3.3.tgz", + "integrity": "sha512-wTcI1Q5cps1eSGhl6+6AzzZkBBlVrBdc9IUhJbijRgVjCNIIIZPgqnUj3ntFODsHrdbGU8BEG9XmDQmgEEYn3w==", + "dependencies": { + "discord-api-types": "0.37.61" + }, + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/rest": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.2.0.tgz", + "integrity": "sha512-nXm9wT8oqrYFRMEqTXQx9DUTeEtXUDMmnUKIhZn6O2EeDY9VCdwj23XCPq7fkqMPKdF7ldAfeVKyxxFdbZl59A==", + "dependencies": { + "@discordjs/collection": "^2.0.0", + "@discordjs/util": "^1.0.2", + "@sapphire/async-queue": "^1.5.0", + "@sapphire/snowflake": "^3.5.1", + "@vladfrangu/async_event_emitter": "^2.2.2", + "discord-api-types": "0.37.61", + "magic-bytes.js": "^1.5.0", + "tslib": "^2.6.2", + "undici": "5.27.2" + }, + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/rest/node_modules/@discordjs/collection": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.0.0.tgz", + "integrity": "sha512-YTWIXLrf5FsrLMycpMM9Q6vnZoR/lN2AWX23/Cuo8uOOtS8eHB2dyQaaGnaF8aZPYnttf2bkLMcXn/j6JUOi3w==", + "engines": { + "node": ">=18" + } + }, + "node_modules/@discordjs/util": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.0.2.tgz", + "integrity": "sha512-IRNbimrmfb75GMNEjyznqM1tkI7HrZOf14njX7tCAAUetyZM1Pr8hX/EK2lxBCOgWDRmigbp24fD1hdMfQK5lw==", + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/ws": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.0.2.tgz", + "integrity": "sha512-+XI82Rm2hKnFwAySXEep4A7Kfoowt6weO6381jgW+wVdTpMS/56qCvoXyFRY0slcv7c/U8My2PwIB2/wEaAh7Q==", + "dependencies": { + "@discordjs/collection": "^2.0.0", + "@discordjs/rest": "^2.1.0", + "@discordjs/util": "^1.0.2", + "@sapphire/async-queue": "^1.5.0", + "@types/ws": "^8.5.9", + "@vladfrangu/async_event_emitter": "^2.2.2", + "discord-api-types": "0.37.61", + "tslib": "^2.6.2", + "ws": "^8.14.2" + }, + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/ws/node_modules/@discordjs/collection": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.0.0.tgz", + "integrity": "sha512-YTWIXLrf5FsrLMycpMM9Q6vnZoR/lN2AWX23/Cuo8uOOtS8eHB2dyQaaGnaF8aZPYnttf2bkLMcXn/j6JUOi3w==", + "engines": { + "node": ">=18" + } + }, + "node_modules/@fastify/busboy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.0.tgz", + "integrity": "sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@redis/bloom": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", + "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/client": { + "version": "1.5.16", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.16.tgz", + "integrity": "sha512-X1a3xQ5kEMvTib5fBrHKh6Y+pXbeKXqziYuxOUo1ojQNECg4M5Etd1qqyhMap+lFUOAh8S7UYevgJHOm4A+NOg==", + "dependencies": { + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@redis/graph": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", + "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/json": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.6.tgz", + "integrity": "sha512-rcZO3bfQbm2zPRpqo82XbW8zg4G/w4W3tI7X8Mqleq9goQjAGLL7q/1n1ZX4dXEAmORVZ4s1+uKLaUOg7LrUhw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/search": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.6.tgz", + "integrity": "sha512-mZXCxbTYKBQ3M2lZnEddwEAks0Kc7nauire8q20oA0oA/LoA+E/b5Y5KZn232ztPb1FkIGqo12vh3Lf+Vw5iTw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/time-series": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.5.tgz", + "integrity": "sha512-IFjIgTusQym2B5IZJG3XKr5llka7ey84fw/NOYqESP5WUfQs9zz1ww/9+qoz4ka/S6KcGBodzlCeZ5UImKbscg==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@sapphire/async-queue": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.1.tgz", + "integrity": "sha512-1RdpsmDQR/aWfp8oJzPtn4dNQrbpqSL5PIA0uAB/XwerPXUf994Ug1au1e7uGcD7ei8/F63UDjr5GWps1g/HxQ==", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@sapphire/shapeshift": { + "version": "3.9.5", + "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.9.5.tgz", + "integrity": "sha512-AGdHe+51gF7D3W8hBfuSFLBocURDCXVQczScTHXDS3RpNjNgrktIx/amlz5y8nHhm8SAdFt/X8EF8ZSfjJ0tnA==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@sapphire/snowflake": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.1.tgz", + "integrity": "sha512-BxcYGzgEsdlG0dKAyOm0ehLGm2CafIrfQTZGWgkfKYbj+pNNsorZ7EotuZukc2MT70E0UbppVbtpBrqpzVzjNA==", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@types/node": { + "version": "20.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz", + "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/ws": { + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.9.tgz", + "integrity": "sha512-jbdrY0a8lxfdTp/+r7Z4CkycbOFN8WX+IOchLJr3juT/xzbJ8URyTVSJ/hvNdadTgM1mnedb47n+Y31GsFnQlg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vladfrangu/async_event_emitter": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.2.4.tgz", + "integrity": "sha512-ButUPz9E9cXMLgvAW8aLAKKJJsPu1dY1/l/E8xzLFuysowXygs6GBcyunK9rnGC4zTsnIc2mQo71rGw9U+Ykug==", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/discord-api-types": { + "version": "0.37.61", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.61.tgz", + "integrity": "sha512-o/dXNFfhBpYHpQFdT6FWzeO7pKc838QeeZ9d91CfVAtpr5XLK4B/zYxQbYgPdoMiTDvJfzcsLW5naXgmHGDNXw==" + }, + "node_modules/discord.js": { + "version": "14.14.1", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.14.1.tgz", + "integrity": "sha512-/hUVzkIerxKHyRKopJy5xejp4MYKDPTszAnpYxzVVv4qJYf+Tkt+jnT2N29PIPschicaEEpXwF2ARrTYHYwQ5w==", + "dependencies": { + "@discordjs/builders": "^1.7.0", + "@discordjs/collection": "1.5.3", + "@discordjs/formatters": "^0.3.3", + "@discordjs/rest": "^2.1.0", + "@discordjs/util": "^1.0.2", + "@discordjs/ws": "^1.0.2", + "@sapphire/snowflake": "3.5.1", + "@types/ws": "8.5.9", + "discord-api-types": "0.37.61", + "fast-deep-equal": "3.1.3", + "lodash.snakecase": "4.1.1", + "tslib": "2.6.2", + "undici": "5.27.2", + "ws": "8.14.2" + }, + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/discord.js/node_modules/ws": { + "version": "8.14.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "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/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/generic-pool": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", + "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==" + }, + "node_modules/magic-bytes.js": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.7.0.tgz", + "integrity": "sha512-YzVU2+/hrjwx8xcgAw+ffNq3jkactpj+f1iSL4LonrFKhvnwDzHSqtFdk/MMRP53y9ScouJ7cKEnqYsJwsHoYA==" + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/querystring": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz", + "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/redis": { + "version": "4.6.14", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.14.tgz", + "integrity": "sha512-GrNg/e33HtsQwNXL7kJT+iNFPSwE1IPmd7wzV3j4f2z0EYxZfZE7FVTmUysgAtqQQtg5NXF5SNLR9OdO/UHOfw==", + "workspaces": [ + "./packages/*" + ], + "dependencies": { + "@redis/bloom": "1.2.0", + "@redis/client": "1.5.16", + "@redis/graph": "1.1.1", + "@redis/json": "1.0.6", + "@redis/search": "1.1.6", + "@redis/time-series": "1.0.5" + } + }, + "node_modules/ts-mixer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.3.tgz", + "integrity": "sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ==" + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/undici": { + "version": "5.27.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.27.2.tgz", + "integrity": "sha512-iS857PdOEy/y3wlM3yRp+6SNQQ6xU0mmZcwRSriqk+et/cwWAtwmIGf6WkoDN2EK/AMdCO/dfXzIwi+rFMrjjQ==", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/ws": { + "version": "8.15.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.15.1.tgz", + "integrity": "sha512-W5OZiCjXEmk0yZ66ZN82beM5Sz7l7coYxpRkzS+p9PP+ToQry8szKh+61eNktr7EA9DOwvFGhfC605jDHbP6QQ==", + "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/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..29598df --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "useractivity", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "discord.js": "^14.14.1", + "dotenv": "^16.3.1", + "node-fetch": "^3.3.2", + "querystring": "^0.2.1", + "redis": "^4.6.14", + "ws": "^8.15.1" + } +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..56aae49 --- /dev/null +++ b/readme.md @@ -0,0 +1,5 @@ +# Website Backend + +This is the backend for my website, developed before I discovered Lanyard. It powers status updates and stores data in Redis for low-latency caching and retrieval. The website never interacts directly with the backend but communicates through the Redis API. + +I've decided to make this open source since there is no point in keeping it closed source. It includes additional features like automatic processing of Discord-related content, such as converting discord url images to return their raw URLs. \ No newline at end of file diff --git a/sniffeddata.json b/sniffeddata.json new file mode 100644 index 0000000..2fb1940 --- /dev/null +++ b/sniffeddata.json @@ -0,0 +1,44 @@ +{ + "user": { + "id": "459742547305299981" + }, + "status": "idle", + "guild_id": "459742962549653505", + "client_status": { + "desktop": "idle" + }, + "broadcast": null, + "activities": [ + { + "type": 0, + "timestamps": { + "start": 1717727050531 + }, + "name": "Grand Theft Auto IV", + "id": "fecea5708d8facc", + "created_at": 1717727060771, + "application_id": "450005834672373760" + }, + { + "type": 0, + "timestamps": { + "start": 1717727164209, + "end": 1717727412796 + }, + "state": "by Lil Wayne", + "session_id": "9744923bf85124073df45360e85e0016", + "name": "Apple Music", + "id": "fd7622c6b65416ec", + "details": "6 Foot 7 Foot (feat. Cory Gunz)", + "created_at": 1717727164971, + "buttons": [ + "Open in Apple Music" + ], + "assets": { + "large_text": "Tha Carter IV (Complete Edition)", + "large_image": "mp:external/WLk7aKqikt6GRPnbrwhP-3I6yXKqp1LluX8NqeUDsgE/https/is1-ssl.mzstatic.com/image/thumb/Music125/v4/6a/74/e5/6a74e5c4-398e-90e9-0b2e-ec96d780316e/21UMGIM75384.rgb.jpg/1024x1024bb.jpg" + }, + "application_id": "886578863147192350" + } + ] +} \ No newline at end of file diff --git a/temp.txt b/temp.txt deleted file mode 100644 index e69de29..0000000 diff --git a/utils.js b/utils.js new file mode 100644 index 0000000..9cf36f7 --- /dev/null +++ b/utils.js @@ -0,0 +1,216 @@ +const fetch = (...args) => import('node-fetch').then(({ default: fetch }) => fetch(...args)); +const querystring = require('querystring'); +const fs = require('fs/promises') +const path = require('path') +const redis = require('redis'); + +let offlinefor = Date.now() +let laststatus = "idle"; + +const client = redis.createClient({ + url: process.env.REDIS_CONNECTION_URI +}); +client.on('error', (err) => { + console.error(`Error connecting to Redis server`, err); + process.exit(1); // Exit the process on Redis connection error +}); + +client.on('connect', () => { + console.log(`Connected to Redis server`); +}); + +client.connect() + +async function extractTopActivity(rawdata) { + + if (laststatus != rawdata.status) { + offlinefor = Date.now() + laststatus = rawdata.status; + } + + // Sniff incoming data for debugging + + // console.log(require("util").inspect(rawdata, false, null, false)); + // fs.writeFile(path.join(__dirname, 'sniffeddata.json'), require("util").inspect(rawdata, false, null, false)); + + let data = JSON.parse(rawdata); + + // force changes status to idle since we dont want to stte when in outher modes you can change this if you want to + let status = "idle"; + if (data.status === "offline") { + status = "offline"; + } + + let userobject = { + status: data.status, + spotify: { + song_name: "zuxi <3", + song_artist: "zuxi", + song_image: null, + song_length: 100, + song_start: 50, + song_end: 100 + }, + game: { + game_name: status, + game_image: null, + game_details: null, + playing_since: offlinefor + } + } + + // console.log(data.activities) + + // this may break against outher apps that use the listining activity + const spotifyActivity = data.activities.find(activity => activity.type === 2); + if (spotifyActivity) { + let spotify = { + song_name: spotifyActivity.details, + song_artist: spotifyActivity.state, + song_image: 'https://i.scdn.co/image/' + spotifyActivity.assets.large_image.split(':')[1], + song_start: spotifyActivity.timestamps.start, + song_end: spotifyActivity.timestamps.end, + song_length: 0 + }; + userobject.spotify = spotify; + } + + // use Cider Music discord RPC for Apple Music intergration + const AppleMusicObject = data.activities.find(activity => activity.name === "Apple Music"); + if (AppleMusicObject) { + console.log(AppleMusicObject.assets.large_image) + let spotify = { + song_name: AppleMusicObject.details, + song_artist: AppleMusicObject.state.replace("by ", ""), + song_image: "", + song_start: AppleMusicObject.timestamps.start, + song_end: AppleMusicObject.timestamps.end, + song_length: AppleMusicObject.timestamps.end - AppleMusicObject.timestamps.start + }; + await discordimagesniffer(AppleMusicObject.application_id, AppleMusicObject.assets.large_image).then(datab => spotify.song_image = datab) + userobject.spotify = spotify; + } + + + const topGameActivity = data.activities.find(activity => activity.type === 0 && activity.name != "Apple Music"); + + if (topGameActivity) { + + let game = { + game_name: topGameActivity.name, + game_image: null, + game_details: topGameActivity.details || null, + playing_since: topGameActivity.created_at + } + + try { + discordimagesniffer(topGameActivity.application_id, topGameActivity.assets.large_image).then(datab => game.game_image = datab) + } catch (thisError) { + console.error(thisError) + } + + if (!game.game_image) { + await discordimagesniffer(topGameActivity.application_id, null).then(datab => game.game_image = datab) + } + + try { + } catch { }; + userobject.game = game + + } + console.log(JSON.stringify(userobject)); + + // Set to redis key + client.set("zuxistatus", JSON.stringify(userobject), (err, reply) => { + if (err) { + console.error(`Error setting Redis key: ${err}`); + } else { + console.log(`Redis key set successfully. Reply: ${reply}`); + } + + // Close the Redis connection + + }); + // Alt Write to File + // fs.writeFileSync(path.join(__dirname, '../src/utils/zuxijsondata'), JSON.stringify(userobject)); +} + +async function discordimagesniffer(applicationid, image_uri) { + if (isDiscordSnowflake(image_uri)) { + return `https://cdn.discordapp.com/app-assets/${applicationid}/${image_uri}`; + } else { + if (image_uri && image_uri.startsWith("mp:external/") && image_uri.includes("https")) { + // Extract the part after "https:" + const startIndex = image_uri.indexOf("https/"); + + if (startIndex !== -1) { + return "https://" + image_uri.substring(startIndex + 6); + } + } else { + if (!image_uri) { + let img; + await cache(applicationid).then(datab => img = `https://cdn.discordapp.com/app-icons/${applicationid}/${datab}`) + return img + } + } + return image_uri; + } +} + +function isDiscordSnowflake(input) { + const snowflakeRegex = /^[0-9]{18,19}$/; + return snowflakeRegex.test(input); +} + +async function cache(name, value) { + let cacheData = {}; + + // Load cache from file if it exists + try { + if (await fs.access(cacheFilePath).then(() => true).catch(() => false)) { + const fileData = await fs.readFile(cacheFilePath, 'utf8'); + cacheData = JSON.parse(fileData); + } + } catch (e) { + console.error('Failed to load cache file:', e); + } + + // If value is provided, update the cache + if (value !== undefined) { + cacheData[name] = value; + await fs.writeFile(cacheFilePath, JSON.stringify(cacheData, null, 2)); + } + + // If the cache does not contain the requested name, fetch the data + if (!cacheData[name] && !lookupid.includes(name)) { + lookupid.push(name) + try { + const response = await fetch(`https://discord.com/api/v9/applications/public?application_ids=${name}`, { + method: 'get', + headers: { + 'Authorization': process.env.USERTOKENSECRET, + 'Content-Type': 'application/json', + }, + }); + + const data = await response.json(); + cacheData[name] = data[0].icon; + + await fs.writeFile(cacheFilePath, JSON.stringify(cacheData, null, 2)); + return data.icon; + } catch (e) { + console.error('Failed to fetch data:', e); + throw e; + } + } + + // Return the requested value + return cacheData[name]; +} +// yes discord can fire so fast we need to add it to a list +let lookupid = []; +const cacheFilePath = path.join(__dirname, 'gamecache.json'); +module.exports = { + extractTopActivity +} +