From 9b8bf162b8898316f5e9ade4d3b73445d3635121 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sat, 27 Aug 2022 22:01:00 +0000 Subject: [PATCH 01/92] Add LICENSE --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bc127de --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Cynthia Foxwell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From ff0e34f83657d46ac0f597252e64abf93b3338ed Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sat, 27 Aug 2022 17:27:05 -0600 Subject: [PATCH 02/92] initial commit --- .gitignore | 1 + README.md | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 12 ++++++++++++ pnpm-lock.yaml | 49 +++++++++++++++++++++++++++++++++++++++++++++++ src/index.js | 31 ++++++++++++++++++++++++++++++ 5 files changed, 145 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 src/index.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..122bfc6 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# comcord +A CLI-based client for Discord inspired by [SDF](https://sdf.org)'s [commode](https://sdf.org/?tutorials/comnotirc). + +## Why? +1. All CLI/TUI Discord clients are outdated/unmaintained or have flaws. +2. I've been spending more time in commode on SDF and have been accustomed to the experience. + +## Usage +1. `pnpm i` +2. `node src/index.js ` + +Currently only bot accounts are supported, and that is unlikely to change anytime soon. +Eris has a lot of user-only endpoints implemented, but it would require hacking apart Eris to do the things nessicary to spoof being the actual client. +I also don't want to give skids an easy point of reference of how to spoof the client. :^) + +You **MUST** grant your bot all Privileged Gateway Intents. + +## Design Decisions +* Node.js was chosen currently due to familiarity. +* Eris was chosen due to familiarity and the nature of everything not being abstracted out to 200 different classes unlike discord.js. +* "Jank" by design. While I don't expect anyone to actually use comcord on serial terminals or teletypes other than for meme factor, the option is still there. + +## TODO +- [ ] Commands + - [ ] Quit (q) + - [ ] Switch guilds (G) + - [ ] Switch channels (g) + - [ ] List online users in guild (w) + - [ ] Emote (e) + - Just sends message surrounded in `*`'s + - [ ] Finger (f) + - [ ] Shows presence data if available + - [ ] Creation date, join date, ID, etc + - [ ] Room history (r) + - [ ] Extended room history (R) +- [ ] Message Receiving + - [ ] Markdown styling + - [ ] Common markdown (bold, italic, etc) + - [ ] Figure out how spoilers would work + - [ ] Emotes????? + - [ ] Timestamp parsing + - [ ] Embeds in the style of commode's posted links + - [ ] Messages wrapped in `*`'s or `_`'s parsed as emotes + - [ ] Inline DMs to replicate commode's private messages + - [ ] Replies +- [ ] Message sending + - [ ] Puts incoming messages into queue whilst in send mode + - [ ] Mentions + - [ ] Replies +- [ ] Configuration + - [ ] Default guild/channel +- [ ] Threads diff --git a/package.json b/package.json new file mode 100644 index 0000000..95f6409 --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "name": "comcord", + "version": "1.0.0", + "description": "", + "main": "index.js", + "keywords": [], + "author": "Cynosphere", + "license": "MIT", + "dependencies": { + "eris": "github:abalabahaha/eris#dev" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..e1e4dc1 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,49 @@ +lockfileVersion: 5.4 + +specifiers: + eris: github:abalabahaha/eris#dev + +dependencies: + eris: github.com/abalabahaha/eris/eb403730855714eafa36c541dbe2cb84c9979158 + +packages: + + /opusscript/0.0.8: + resolution: {integrity: sha512-VSTi1aWFuCkRCVq+tx/BQ5q9fMnQ9pVZ3JU4UHKqTkf0ED3fKEPdr+gKAAl3IA2hj9rrP6iyq3hlcJq3HELtNQ==} + requiresBuild: true + dev: false + optional: true + + /tweetnacl/1.0.3: + resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==} + requiresBuild: true + dev: false + optional: true + + /ws/8.8.1: + resolution: {integrity: sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==} + 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 + dev: false + + github.com/abalabahaha/eris/eb403730855714eafa36c541dbe2cb84c9979158: + resolution: {tarball: https://codeload.github.com/abalabahaha/eris/tar.gz/eb403730855714eafa36c541dbe2cb84c9979158} + name: eris + version: 0.17.2-dev + engines: {node: '>=10.4.0'} + dependencies: + ws: 8.8.1 + optionalDependencies: + opusscript: 0.0.8 + tweetnacl: 1.0.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..1f518d9 --- /dev/null +++ b/src/index.js @@ -0,0 +1,31 @@ +const Eris = require("eris"); +const token = process.argv[2]; + +let currentGuild, + currentChannel, + inSendMode = false; + +const messageQueue = []; + +const client = new Eris("Bot " + token, { + defaultImageFormat: "png", + defaultImageSize: 1024, + intents: Eris.Constants.Intents.all, +}); + +client.once("ready", function () { + console.log( + `Logged in as: ${client.user.username}#${client.user.discriminator} (${client.user.id})` + ); +}); + +client.on("messageCreate", function (msg) { + if (msg.channel.id == currentChannel) { + if (inSendMode) { + messageQueue.push(msg); + } else { + } + } +}); + +client.connect(); From c1fcde1b3bf37cabdb79158297cdfb566306b378 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sat, 27 Aug 2022 17:29:13 -0600 Subject: [PATCH 03/92] i eat chalk --- package.json | 1 + pnpm-lock.yaml | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/package.json b/package.json index 95f6409..862a3bc 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "author": "Cynosphere", "license": "MIT", "dependencies": { + "chalk": "^5.0.1", "eris": "github:abalabahaha/eris#dev" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e1e4dc1..2c0aa94 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,13 +1,20 @@ lockfileVersion: 5.4 specifiers: + chalk: ^5.0.1 eris: github:abalabahaha/eris#dev dependencies: + chalk: 5.0.1 eris: github.com/abalabahaha/eris/eb403730855714eafa36c541dbe2cb84c9979158 packages: + /chalk/5.0.1: + resolution: {integrity: sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + dev: false + /opusscript/0.0.8: resolution: {integrity: sha512-VSTi1aWFuCkRCVq+tx/BQ5q9fMnQ9pVZ3JU4UHKqTkf0ED3fKEPdr+gKAAl3IA2hj9rrP6iyq3hlcJq3HELtNQ==} requiresBuild: true From bd8936ad132da8bc689b2b83c8d4cd5032b963f3 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sat, 27 Aug 2022 18:14:10 -0600 Subject: [PATCH 04/92] command system, message sending and receiving (untested) --- README.md | 13 +++--- src/index.js | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 135 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 122bfc6..59069c3 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,8 @@ You **MUST** grant your bot all Privileged Gateway Intents. * "Jank" by design. While I don't expect anyone to actually use comcord on serial terminals or teletypes other than for meme factor, the option is still there. ## TODO -- [ ] Commands - - [ ] Quit (q) +- [x] Commands + - [x] Quit (q) - [ ] Switch guilds (G) - [ ] Switch channels (g) - [ ] List online users in guild (w) @@ -33,20 +33,21 @@ You **MUST** grant your bot all Privileged Gateway Intents. - [ ] Creation date, join date, ID, etc - [ ] Room history (r) - [ ] Extended room history (R) -- [ ] Message Receiving +- [x] Message Receiving - [ ] Markdown styling - [ ] Common markdown (bold, italic, etc) - [ ] Figure out how spoilers would work - [ ] Emotes????? - [ ] Timestamp parsing - [ ] Embeds in the style of commode's posted links - - [ ] Messages wrapped in `*`'s or `_`'s parsed as emotes + - [x] Messages wrapped in `*`'s or `_`'s parsed as emotes - [ ] Inline DMs to replicate commode's private messages - [ ] Replies -- [ ] Message sending - - [ ] Puts incoming messages into queue whilst in send mode +- [x] Message sending + - [x] Puts incoming messages into queue whilst in send mode - [ ] Mentions - [ ] Replies - [ ] Configuration - [ ] Default guild/channel - [ ] Threads +- [ ] Not have the token just be in argv diff --git a/src/index.js b/src/index.js index 1f518d9..0601c3d 100644 --- a/src/index.js +++ b/src/index.js @@ -1,9 +1,19 @@ const Eris = require("eris"); +const chalk = require("chalk"); +const {inspect} = require("util"); + const token = process.argv[2]; +const stdin = process.stdin; +const stdout = process.stdout; + +stdin.setRawMode(true); +stdin.resume(); +stdin.setEncoding("utf8"); let currentGuild, currentChannel, - inSendMode = false; + inSendMode = false, + nameLength = 2; const messageQueue = []; @@ -15,15 +25,131 @@ const client = new Eris("Bot " + token, { client.once("ready", function () { console.log( - `Logged in as: ${client.user.username}#${client.user.discriminator} (${client.user.id})` + "Logged in as: " + + chalk.yellow( + `${client.user.username}#${client.user.discriminator} (${client.user.id})` + ) ); + nameLength = client.user.username.length + 2; }); +function processMessage({name, content, bot}) { + if (name.length + 2 > nameLength) nameLength = name.length + 2; + + if ( + (content.startsWith("*") && content.endsWith("*")) || + (content.startsWith("_") && content.endsWith("_")) + ) { + console.log(chalk.bold.green(`<${name} ${content}>`)); + } else { + // TODO: markdown + console.log( + chalk.bold.cyan(`[${name}]` + " ".rep(nameLength - (name.length + 2))) + + chalk.reset(" " + content) + ); + } +} + +function processQueue() { + for (const msg of messageQueue) { + if (msg.content.indexOf("\n") > -1) { + const lines = msg.content.split("\n"); + for (const line of lines) { + processMessage({ + name: msg.author.name, + bot: msg.author.bot, + content: line, + }); + } + } else { + processMessage({ + name: msg.author.name, + bot: msg.author.bot, + content: msg.content, + }); + } + } +} + client.on("messageCreate", function (msg) { if (msg.channel.id == currentChannel) { if (inSendMode) { messageQueue.push(msg); } else { + if (msg.content.indexOf("\n") > -1) { + const lines = msg.content.split("\n"); + for (const line of lines) { + processMessage({ + name: msg.author.name, + bot: msg.author.bot, + content: line, + }); + } + } else { + processMessage({ + name: msg.author.name, + bot: msg.author.bot, + content: msg.content, + }); + } + } + } +}); + +let toSend = ""; +function setupSendMode() { + inSendMode = true; + toSend = ""; + const name = `[${client.user.username}]`; + stdout.write( + chalk.bold.cyan(name) + " ".rep(nameLength - name.length) + chalk.reset(" ") + ); +} +function sendMessage() { + toSend = toSend.trim(); + if (toSend === "") { + stdout.write("\n"); + } else { + client.createMessage(currentChannel, toSend); + } + inSendMode = false; + processQueue(); +} + +stdin.on("data", function (key) { + if (inSendMode) { + if (key === "\r") { + sendMessage(); + } else { + if (key === "\b") { + if (toSend.length > 0) { + stdout.moveCursor(-1); + stdout.write(" "); + stdout.moveCursor(-1); + toSend = toSend.substring(0, toSend.length - 1); + } + } else { + stdout.write(key); + toSend += key; + } + } + } else { + switch (key) { + case "\u0003": + case "q": + client.disconnect(false); + process.exit(0); + break; + case " ": + case "\r": + default: { + if (currentChannel == null) { + console.log("not in a channel"); + break; + } + setupSendMode(); + break; + } } } }); From a13afe939568a95e7c372a6f8825fa867f537f23 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sat, 27 Aug 2022 18:36:44 -0600 Subject: [PATCH 05/92] downgrade chalk (i am the anti-ESM) --- package.json | 2 +- pnpm-lock.yaml | 43 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 862a3bc..b09e63d 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "author": "Cynosphere", "license": "MIT", "dependencies": { - "chalk": "^5.0.1", + "chalk": "4.1.2", "eris": "github:abalabahaha/eris#dev" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2c0aa94..353a918 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,18 +1,44 @@ lockfileVersion: 5.4 specifiers: - chalk: ^5.0.1 + chalk: 4.1.2 eris: github:abalabahaha/eris#dev dependencies: - chalk: 5.0.1 + chalk: 4.1.2 eris: github.com/abalabahaha/eris/eb403730855714eafa36c541dbe2cb84c9979158 packages: - /chalk/5.0.1: - resolution: {integrity: sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + /ansi-styles/4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + dev: false + + /chalk/4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: false + + /color-convert/2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + dev: false + + /color-name/1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + dev: false + + /has-flag/4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} dev: false /opusscript/0.0.8: @@ -21,6 +47,13 @@ packages: dev: false optional: true + /supports-color/7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + dev: false + /tweetnacl/1.0.3: resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==} requiresBuild: true From b6a4e5edfff8b094780c108a78196c5912281d49 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sat, 27 Aug 2022 18:49:02 -0600 Subject: [PATCH 06/92] add help command --- .eslintrc.js | 30 +++++++++++++++++++++++++ .prettierrc | 5 +++++ src/index.js | 63 +++++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 92 insertions(+), 6 deletions(-) create mode 100644 .eslintrc.js create mode 100644 .prettierrc diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..4b9468b --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,30 @@ +const OFF = 0; +// const WARN = 1; +const ERROR = 2; + +module.exports = { + extends: ["eslint:recommended"], + parserOptions: { + ecmaVersion: 2020, + }, + env: { + es6: true, + node: true, + }, + rules: { + indent: OFF, + semi: ERROR, + quotes: [ERROR, "double", {avoidEscape: true, allowTemplateLiterals: true}], + "no-empty": ERROR, + "array-callback-return": ERROR, + "consistent-return": ERROR, + eqeqeq: OFF, + "prefer-const": ERROR, + "no-unused-vars": [ERROR, {args: "none", varsIgnorePattern: "^_"}], + "no-console": OFF, + "no-debugger": OFF, + "require-atomic-updates": OFF, + }, + globals: { + }, +}; diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..15520fe --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "semi": true, + "bracketSpacing": false, + "endOfLine": "lf" +} diff --git a/src/index.js b/src/index.js index 0601c3d..59bb210 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,5 @@ const Eris = require("eris"); const chalk = require("chalk"); -const {inspect} = require("util"); const token = process.argv[2]; const stdin = process.stdin; @@ -17,6 +16,21 @@ let currentGuild, const messageQueue = []; +const commands = { + q: "quit comcord", + e: "emote", + g: "goto a channel", + G: "goto a guild", + l: "list channels", + L: "list guilds", + w: "who is in guild", + f: "finger", + r: "channel history", + R: "extended channel history", + h: "command help", + c: "clear", +}; + const client = new Eris("Bot " + token, { defaultImageFormat: "png", defaultImageSize: 1024, @@ -44,8 +58,9 @@ function processMessage({name, content, bot}) { } else { // TODO: markdown console.log( - chalk.bold.cyan(`[${name}]` + " ".rep(nameLength - (name.length + 2))) + - chalk.reset(" " + content) + chalk.bold.cyan( + `[${name}]` + " ".repeat(nameLength - (name.length + 2)) + ) + chalk.reset(" " + content) ); } } @@ -102,7 +117,9 @@ function setupSendMode() { toSend = ""; const name = `[${client.user.username}]`; stdout.write( - chalk.bold.cyan(name) + " ".rep(nameLength - name.length) + chalk.reset(" ") + chalk.bold.cyan(name) + + " ".repeat(nameLength - name.length) + + chalk.reset(" ") ); } function sendMessage() { @@ -116,6 +133,32 @@ function sendMessage() { processQueue(); } +function showHelp() { + console.log("\nCOMcord (c)left 2022\n"); + + const keys = Object.keys(commands); + keys.sort(); + + let index = 0; + for (const key of keys) { + const desc = commands[key]; + const length = ` ${key} - ${desc}`.length; + + stdout.write( + " " + + chalk.bold.yellow(key) + + chalk.reset(" - " + desc) + + " ".repeat(Math.abs(25 - length)) + ); + + index++; + if (index % 3 == 0) stdout.write("\n"); + } + if (index % 3 != 0) stdout.write("\n"); + + console.log("\nTo begin TALK MODE, press [SPACE]\n"); +} + stdin.on("data", function (key) { if (inSendMode) { if (key === "\r") { @@ -136,15 +179,20 @@ stdin.on("data", function (key) { } else { switch (key) { case "\u0003": - case "q": + case "q": { client.disconnect(false); process.exit(0); break; + } + case "h": { + showHelp(); + break; + } case " ": case "\r": default: { if (currentChannel == null) { - console.log("not in a channel"); + console.log(""); break; } setupSendMode(); @@ -155,3 +203,6 @@ stdin.on("data", function (key) { }); client.connect(); + +console.log("COMcord (c)left 2022"); +console.log("Type 'h' for Commands"); From 8591f3e002e6141e8a00829f2d5a42005e740637 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sat, 27 Aug 2022 19:53:55 -0600 Subject: [PATCH 07/92] guild switching and user listing --- README.md | 5 +- src/index.js | 223 +++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 211 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 59069c3..b8e6c17 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,9 @@ You **MUST** grant your bot all Privileged Gateway Intents. ## TODO - [x] Commands - [x] Quit (q) - - [ ] Switch guilds (G) + - [x] Switch guilds (G) - [ ] Switch channels (g) - - [ ] List online users in guild (w) + - [x] List online users in guild (w) - [ ] Emote (e) - Just sends message surrounded in `*`'s - [ ] Finger (f) @@ -51,3 +51,4 @@ You **MUST** grant your bot all Privileged Gateway Intents. - [ ] Default guild/channel - [ ] Threads - [ ] Not have the token just be in argv +- [ ] Not have everything in one file diff --git a/src/index.js b/src/index.js index 59bb210..d5ac664 100644 --- a/src/index.js +++ b/src/index.js @@ -12,6 +12,8 @@ stdin.setEncoding("utf8"); let currentGuild, currentChannel, inSendMode = false, + guildSwitch = false, + channelSwitch = false, nameLength = 2; const messageQueue = []; @@ -45,6 +47,8 @@ client.once("ready", function () { ) ); nameLength = client.user.username.length + 2; + + listGuilds(); }); function processMessage({name, content, bot}) { @@ -58,9 +62,8 @@ function processMessage({name, content, bot}) { } else { // TODO: markdown console.log( - chalk.bold.cyan( - `[${name}]` + " ".repeat(nameLength - (name.length + 2)) - ) + chalk.reset(" " + content) + chalk.bold.cyan(`[${name}]`).padEnd(nameLength, " ") + + chalk.reset(" " + content) ); } } @@ -112,22 +115,32 @@ client.on("messageCreate", function (msg) { }); let toSend = ""; -function setupSendMode() { +async function setupSendMode() { inSendMode = true; toSend = ""; - const name = `[${client.user.username}]`; stdout.write( - chalk.bold.cyan(name) + - " ".repeat(nameLength - name.length) + + chalk.bold.cyan(`[${client.user.username}]`).padEnd(nameLength, " ") + chalk.reset(" ") ); + try { + await client.guilds + .get(currentGuild) + .channels.get(currentChannel) + .sendTyping(); + } catch (err) { + // + } } -function sendMessage() { +async function sendMessage() { toSend = toSend.trim(); if (toSend === "") { stdout.write("\n"); } else { - client.createMessage(currentChannel, toSend); + try { + await client.createMessage(currentChannel, toSend); + } catch (err) { + console.log(""); + } } inSendMode = false; processQueue(); @@ -142,13 +155,12 @@ function showHelp() { let index = 0; for (const key of keys) { const desc = commands[key]; - const length = ` ${key} - ${desc}`.length; stdout.write( - " " + - chalk.bold.yellow(key) + - chalk.reset(" - " + desc) + - " ".repeat(Math.abs(25 - length)) + (" " + chalk.bold.yellow(key) + chalk.reset(" - " + desc)).padEnd( + 25, + " " + ) ); index++; @@ -159,9 +171,160 @@ function showHelp() { console.log("\nTo begin TALK MODE, press [SPACE]\n"); } +function listGuilds() { + let longest = 0; + const guilds = []; + + for (const guild of client.guilds.values()) { + if (guild.name.length > longest) longest = guild.name.length; + + const online = [...guild.members.values()].filter((m) => m.status).length; + guilds.push({name: guild.name, members: guild.memberCount, online}); + } + + console.log(""); + console.log(" " + "guild-name".padStart(longest, " ") + " online total"); + console.log("-".repeat(80)); + for (const guild of guilds) { + console.log( + " " + + guild.name.padStart(longest, " ") + + " " + + guild.online.toString().padStart(6, " ") + + " " + + guild.members.toString().padStart(5, " ") + ); + } + console.log(""); +} + +let targetGuild = ""; +function gotoGuild() { + targetGuild = ""; + guildSwitch = true; + + stdout.write(":guild> "); +} + +function findTopChannel(guildId) { + const guild = client.guilds.get(guildId); + const channels = [...guild.channels.values()].filter((c) => c.type == 0); + channels.sort((a, b) => a.position - b.position); + + return channels[0]; +} + +function getStatus(status) { + let color; + switch (status) { + case "online": + color = chalk.bold.green; + break; + case "idle": + color = chalk.bold.yellow; + break; + case "dnd": + color = chalk.bold.red; + break; + default: + color = chalk.bold; + break; + } + + return color(" \u2022 "); +} + +function listUsers() { + const guild = client.guilds.get(currentGuild); + const channel = guild.channels.get(currentChannel); + + console.log( + `\n[you are in '${guild.name}' in '${channel.name}' among ${guild.memberCount}]\n` + ); + + const online = [...guild.members.values()].filter((m) => m.status); + online.sort((a, b) => a.name - b.name); + + let longest = 0; + for (const member of online) { + const name = member.user.username + "#" + member.user.discriminator; + if (name.length + 3 > longest) longest = name.length + 3; + } + + const columns = Math.ceil(stdout.columns / longest); + + let index = 0; + for (const member of online) { + const name = member.user.username + "#" + member.user.discriminator; + const status = getStatus(member.status); + const nameAndStatus = chalk.reset(name) + status; + + index++; + stdout.write( + nameAndStatus + + " ".repeat( + index % columns == 0 ? 0 : Math.abs(longest - (name.length + 3)) + ) + ); + + if (index % columns == 0) stdout.write("\n"); + } + if (index % columns != 0) stdout.write("\n"); + console.log(""); + + if (channel.topic != null) { + console.log("--Topic".padEnd(80, "-")); + console.log(channel.topic); + console.log("-".repeat(80)); + console.log(""); + } +} + +function switchGuild() { + let target; + + for (const guild of client.guilds.values()) { + if (guild.name.toLowerCase().indexOf(targetGuild.toLowerCase()) > -1) { + target = guild.id; + break; + } + } + + if (target == null) { + console.log(""); + } else { + currentGuild = target; + // TODO: store last visited channel + const topChannel = findTopChannel(target); + currentChannel = topChannel.id; + + listUsers(); + } + + guildSwitch = false; +} + stdin.on("data", function (key) { - if (inSendMode) { + if (guildSwitch) { if (key === "\r") { + console.log(""); + switchGuild(); + } else { + if (key === "\b") { + if (targetGuild.length > 0) { + stdout.moveCursor(-1); + stdout.write(" "); + stdout.moveCursor(-1); + targetGuild = targetGuild.substring(0, targetGuild.length - 1); + } + } else { + stdout.write(key); + targetGuild += key; + } + } + } else if (inSendMode) { + if (key === "\r") { + console.log(""); sendMessage(); } else { if (key === "\b") { @@ -188,6 +351,36 @@ stdin.on("data", function (key) { showHelp(); break; } + case "g": { + if (currentGuild == null) { + console.log(""); + break; + } + break; + } + case "G": { + gotoGuild(); + break; + } + case "l": { + if (currentGuild == null) { + console.log(""); + break; + } + break; + } + case "L": { + listGuilds(); + break; + } + case "w": { + if (currentGuild == null) { + console.log(""); + break; + } + listUsers(); + break; + } case " ": case "\r": default: { From f85ac8138dd69b64421278d83a5af4c374f91556 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sat, 27 Aug 2022 20:13:25 -0600 Subject: [PATCH 08/92] channel switching --- src/index.js | 82 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 75 insertions(+), 7 deletions(-) diff --git a/src/index.js b/src/index.js index d5ac664..c7aed73 100644 --- a/src/index.js +++ b/src/index.js @@ -60,9 +60,13 @@ function processMessage({name, content, bot}) { ) { console.log(chalk.bold.green(`<${name} ${content}>`)); } else { + let nameColor = chalk.bold.cyan; + if (bot) nameColor = chalk.bold.yellow; + // TODO: markdown console.log( - chalk.bold.cyan(`[${name}]`).padEnd(nameLength, " ") + + nameColor(`[${name}]`) + + " ".repeat(nameLength - (name.length + 2)) + chalk.reset(" " + content) ); } @@ -74,14 +78,14 @@ function processQueue() { const lines = msg.content.split("\n"); for (const line of lines) { processMessage({ - name: msg.author.name, + name: msg.author.username, bot: msg.author.bot, content: line, }); } } else { processMessage({ - name: msg.author.name, + name: msg.author.username, bot: msg.author.bot, content: msg.content, }); @@ -90,6 +94,8 @@ function processQueue() { } client.on("messageCreate", function (msg) { + if (msg.author.id === client.user.id) return; + if (msg.channel.id == currentChannel) { if (inSendMode) { messageQueue.push(msg); @@ -98,14 +104,14 @@ client.on("messageCreate", function (msg) { const lines = msg.content.split("\n"); for (const line of lines) { processMessage({ - name: msg.author.name, + name: msg.author.username, bot: msg.author.bot, content: line, }); } } else { processMessage({ - name: msg.author.name, + name: msg.author.username, bot: msg.author.bot, content: msg.content, }); @@ -119,7 +125,8 @@ async function setupSendMode() { inSendMode = true; toSend = ""; stdout.write( - chalk.bold.cyan(`[${client.user.username}]`).padEnd(nameLength, " ") + + chalk.bold.cyan(`[${client.user.username}]`) + + " ".repeat(nameLength - (client.user.username.length + 2)) + chalk.reset(" ") ); try { @@ -281,6 +288,12 @@ function listUsers() { } function switchGuild() { + if (targetGuild == "") { + listUsers(); + guildSwitch = false; + return; + } + let target; for (const guild of client.guilds.values()) { @@ -294,7 +307,7 @@ function switchGuild() { console.log(""); } else { currentGuild = target; - // TODO: store last visited channel + // TODO: store last visited channel and switch to it if we've been to this guild before const topChannel = findTopChannel(target); currentChannel = topChannel.id; @@ -304,6 +317,43 @@ function switchGuild() { guildSwitch = false; } +let targetChannel = ""; +function gotoChannel() { + targetChannel = ""; + channelSwitch = true; + + stdout.write(":channel> "); +} + +function switchChannel() { + if (targetChannel == "") { + listUsers(); + channelSwitch = false; + return; + } + let target; + + const guild = client.guilds.get(currentGuild); + const channels = [...guild.channels.values()].filter((c) => c.type == 0); + + for (const channel of channels) { + if (channel.name.toLowerCase().indexOf(targetChannel.toLowerCase()) > -1) { + target = channel.id; + break; + } + } + + if (target == null) { + console.log(""); + } else { + currentChannel = target; + + listUsers(); + } + + channelSwitch = false; +} + stdin.on("data", function (key) { if (guildSwitch) { if (key === "\r") { @@ -322,6 +372,23 @@ stdin.on("data", function (key) { targetGuild += key; } } + } else if (channelSwitch) { + if (key === "\r") { + console.log(""); + switchChannel(); + } else { + if (key === "\b") { + if (targetChannel.length > 0) { + stdout.moveCursor(-1); + stdout.write(" "); + stdout.moveCursor(-1); + targetChannel = targetChannel.substring(0, targetChannel.length - 1); + } + } else { + stdout.write(key); + targetChannel += key; + } + } } else if (inSendMode) { if (key === "\r") { console.log(""); @@ -356,6 +423,7 @@ stdin.on("data", function (key) { console.log(""); break; } + gotoChannel(); break; } case "G": { From 3d55bdf31c80c81338103507aa7bc3dd596b4aa0 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sat, 27 Aug 2022 20:43:00 -0600 Subject: [PATCH 09/92] emote, edit support, attachment support --- src/index.js | 106 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 100 insertions(+), 6 deletions(-) diff --git a/src/index.js b/src/index.js index c7aed73..9a821ca 100644 --- a/src/index.js +++ b/src/index.js @@ -12,6 +12,7 @@ stdin.setEncoding("utf8"); let currentGuild, currentChannel, inSendMode = false, + inEmoteMode = false, guildSwitch = false, channelSwitch = false, nameLength = 2; @@ -51,14 +52,16 @@ client.once("ready", function () { listGuilds(); }); -function processMessage({name, content, bot}) { +function processMessage({name, content, bot, attachments}) { if (name.length + 2 > nameLength) nameLength = name.length + 2; if ( (content.startsWith("*") && content.endsWith("*")) || (content.startsWith("_") && content.endsWith("_")) ) { - console.log(chalk.bold.green(`<${name} ${content}>`)); + console.log( + chalk.bold.green(`<${name} ${content.substring(1, content.length - 1)}>`) + ); } else { let nameColor = chalk.bold.cyan; if (bot) nameColor = chalk.bold.yellow; @@ -69,6 +72,9 @@ function processMessage({name, content, bot}) { " ".repeat(nameLength - (name.length + 2)) + chalk.reset(" " + content) ); + for (const attachment of attachments) { + console.log(chalk.bold.yellow(``)); + } } } @@ -76,11 +82,13 @@ function processQueue() { for (const msg of messageQueue) { if (msg.content.indexOf("\n") > -1) { const lines = msg.content.split("\n"); - for (const line of lines) { + for (const index in lines) { + const line = lines[index]; processMessage({ name: msg.author.username, bot: msg.author.bot, content: line, + attachments: index == lines.length - 1 ? msg.attachments : null, }); } } else { @@ -88,25 +96,30 @@ function processQueue() { name: msg.author.username, bot: msg.author.bot, content: msg.content, + attachments: msg.attachments, }); } } + + messageQueue.splice(0, messageQueue.length); } client.on("messageCreate", function (msg) { if (msg.author.id === client.user.id) return; if (msg.channel.id == currentChannel) { - if (inSendMode) { + if (inSendMode || inEmoteMode) { messageQueue.push(msg); } else { if (msg.content.indexOf("\n") > -1) { const lines = msg.content.split("\n"); - for (const line of lines) { + for (const index in lines) { + const line = lines[index]; processMessage({ name: msg.author.username, bot: msg.author.bot, content: line, + attachments: index == lines.length - 1 ? msg.attachments : null, }); } } else { @@ -114,6 +127,38 @@ client.on("messageCreate", function (msg) { name: msg.author.username, bot: msg.author.bot, content: msg.content, + attachments: msg.attachments, + }); + } + } + } +}); +client.on("messageUpdate", function (msg, old) { + if (msg.author.id === client.user.id) return; + + if (msg.channel.id == currentChannel) { + if (msg.content == old.content) return; + + if (inSendMode || inEmoteMode) { + messageQueue.push(msg); + } else { + if (msg.content.indexOf("\n") > -1) { + const lines = msg.content.split("\n"); + for (const index in lines) { + const line = lines[index]; + processMessage({ + name: msg.author.username, + bot: msg.author.bot, + content: line + index == lines.length - 1 ? " (edited)" : null, + attachments: index == lines.length - 1 ? msg.attachments : null, + }); + } + } else { + processMessage({ + name: msg.author.username, + bot: msg.author.bot, + content: msg.content + " (edited)", + attachments: msg.attachments, }); } } @@ -144,6 +189,7 @@ async function sendMessage() { stdout.write("\n"); } else { try { + stdout.write("\n"); await client.createMessage(currentChannel, toSend); } catch (err) { console.log(""); @@ -335,6 +381,7 @@ function switchChannel() { const guild = client.guilds.get(currentGuild); const channels = [...guild.channels.values()].filter((c) => c.type == 0); + channels.sort((a, b) => a.position - b.position); for (const channel of channels) { if (channel.name.toLowerCase().indexOf(targetChannel.toLowerCase()) > -1) { @@ -354,6 +401,29 @@ function switchChannel() { channelSwitch = false; } +function startEmote() { + toSend = ""; + inEmoteMode = true; + + stdout.write(":emote> "); +} + +async function sendEmote() { + toSend = toSend.trim(); + if (toSend === "") { + console.log(""); + } else { + try { + await client.createMessage(currentChannel, "*" + toSend + "*"); + console.log(`<${client.user.username} ${toSend}>`); + } catch (err) { + console.log(""); + } + } + inEmoteMode = false; + processQueue(); +} + stdin.on("data", function (key) { if (guildSwitch) { if (key === "\r") { @@ -391,7 +461,6 @@ stdin.on("data", function (key) { } } else if (inSendMode) { if (key === "\r") { - console.log(""); sendMessage(); } else { if (key === "\b") { @@ -406,6 +475,23 @@ stdin.on("data", function (key) { toSend += key; } } + } else if (inEmoteMode) { + if (key === "\r") { + console.log(""); + sendEmote(); + } else { + if (key === "\b") { + if (toSend.length > 0) { + stdout.moveCursor(-1); + stdout.write(" "); + stdout.moveCursor(-1); + toSend = toSend.substring(0, toSend.length - 1); + } + } else { + stdout.write(key); + toSend += key; + } + } } else { switch (key) { case "\u0003": @@ -449,6 +535,14 @@ stdin.on("data", function (key) { listUsers(); break; } + case "e": { + if (currentChannel == null) { + console.log(""); + break; + } + startEmote(); + break; + } case " ": case "\r": default: { From aeca9294afac9ba388c9f674916b9c0838cb4852 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sat, 27 Aug 2022 20:57:40 -0600 Subject: [PATCH 10/92] support for replies --- README.md | 4 ++-- src/index.js | 37 +++++++++++++++++++++++++++++++------ 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index b8e6c17..06342b2 100644 --- a/README.md +++ b/README.md @@ -24,9 +24,9 @@ You **MUST** grant your bot all Privileged Gateway Intents. - [x] Commands - [x] Quit (q) - [x] Switch guilds (G) - - [ ] Switch channels (g) + - [x] Switch channels (g) - [x] List online users in guild (w) - - [ ] Emote (e) + - [x] Emote (e) - Just sends message surrounded in `*`'s - [ ] Finger (f) - [ ] Shows presence data if available diff --git a/src/index.js b/src/index.js index 9a821ca..f9914f1 100644 --- a/src/index.js +++ b/src/index.js @@ -52,9 +52,28 @@ client.once("ready", function () { listGuilds(); }); -function processMessage({name, content, bot, attachments}) { +function processMessage({name, content, bot, attachments, reply}) { if (name.length + 2 > nameLength) nameLength = name.length + 2; + if (reply) { + const nameColor = reply.author.bot ? chalk.bold.yellow : chalk.bold.cyan; + + const headerLength = 5 + reply.author.username.length; + const length = headerLength + reply.content.length; + + console.log( + chalk.bold.white(" \u250d ") + + nameColor(`[${reply.author.username}] `) + + chalk.reset( + `${ + length > 79 + ? reply.content.substring(0, length - headerLength) + "\u2026" + : reply.content + }` + ) + ); + } + if ( (content.startsWith("*") && content.endsWith("*")) || (content.startsWith("_") && content.endsWith("_")) @@ -63,8 +82,7 @@ function processMessage({name, content, bot, attachments}) { chalk.bold.green(`<${name} ${content.substring(1, content.length - 1)}>`) ); } else { - let nameColor = chalk.bold.cyan; - if (bot) nameColor = chalk.bold.yellow; + const nameColor = bot ? chalk.bold.yellow : chalk.bold.cyan; // TODO: markdown console.log( @@ -72,9 +90,10 @@ function processMessage({name, content, bot, attachments}) { " ".repeat(nameLength - (name.length + 2)) + chalk.reset(" " + content) ); - for (const attachment of attachments) { - console.log(chalk.bold.yellow(``)); - } + } + + for (const attachment of attachments) { + console.log(chalk.bold.yellow(``)); } } @@ -89,6 +108,7 @@ function processQueue() { bot: msg.author.bot, content: line, attachments: index == lines.length - 1 ? msg.attachments : null, + reply: index == 0 ? msg.referencedMessage : null, }); } } else { @@ -97,6 +117,7 @@ function processQueue() { bot: msg.author.bot, content: msg.content, attachments: msg.attachments, + reply: msg.referencedMessage, }); } } @@ -120,6 +141,7 @@ client.on("messageCreate", function (msg) { bot: msg.author.bot, content: line, attachments: index == lines.length - 1 ? msg.attachments : null, + reply: index == 0 ? msg.referencedMessage : null, }); } } else { @@ -128,6 +150,7 @@ client.on("messageCreate", function (msg) { bot: msg.author.bot, content: msg.content, attachments: msg.attachments, + reply: msg.referencedMessage, }); } } @@ -151,6 +174,7 @@ client.on("messageUpdate", function (msg, old) { bot: msg.author.bot, content: line + index == lines.length - 1 ? " (edited)" : null, attachments: index == lines.length - 1 ? msg.attachments : null, + reply: index == 0 ? msg.referencedMessage : null, }); } } else { @@ -159,6 +183,7 @@ client.on("messageUpdate", function (msg, old) { bot: msg.author.bot, content: msg.content + " (edited)", attachments: msg.attachments, + reply: msg.referencedMessage, }); } } From daeed08a6aadb88e98ae8b9f76ec5459b71c20cd Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sat, 27 Aug 2022 21:22:06 -0600 Subject: [PATCH 11/92] implement history --- README.md | 6 +- src/index.js | 180 ++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 160 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 06342b2..ef81180 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,8 @@ You **MUST** grant your bot all Privileged Gateway Intents. - [ ] Finger (f) - [ ] Shows presence data if available - [ ] Creation date, join date, ID, etc - - [ ] Room history (r) - - [ ] Extended room history (R) + - [x] Room history (r) + - [x] Extended room history (R) - [x] Message Receiving - [ ] Markdown styling - [ ] Common markdown (bold, italic, etc) @@ -42,7 +42,7 @@ You **MUST** grant your bot all Privileged Gateway Intents. - [ ] Embeds in the style of commode's posted links - [x] Messages wrapped in `*`'s or `_`'s parsed as emotes - [ ] Inline DMs to replicate commode's private messages - - [ ] Replies + - [x] Replies - [x] Message sending - [x] Puts incoming messages into queue whilst in send mode - [ ] Mentions diff --git a/src/index.js b/src/index.js index f9914f1..9becf8d 100644 --- a/src/index.js +++ b/src/index.js @@ -15,6 +15,7 @@ let currentGuild, inEmoteMode = false, guildSwitch = false, channelSwitch = false, + extendedHistory = false, nameLength = 2; const messageQueue = []; @@ -52,7 +53,14 @@ client.once("ready", function () { listGuilds(); }); -function processMessage({name, content, bot, attachments, reply}) { +function processMessage({ + name, + content, + bot, + attachments, + reply, + isHistory = false, +}) { if (name.length + 2 > nameLength) nameLength = name.length + 2; if (reply) { @@ -61,39 +69,65 @@ function processMessage({name, content, bot, attachments, reply}) { const headerLength = 5 + reply.author.username.length; const length = headerLength + reply.content.length; - console.log( - chalk.bold.white(" \u250d ") + - nameColor(`[${reply.author.username}] `) + - chalk.reset( - `${ - length > 79 - ? reply.content.substring(0, length - headerLength) + "\u2026" - : reply.content - }` - ) - ); + if (isHistory) { + console.log( + ` \u250d [${reply.author.username}] ${ + length > 79 + ? reply.content.substring(0, length - headerLength) + "\u2026" + : reply.content + }` + ); + } else { + console.log( + chalk.bold.white(" \u250d ") + + nameColor(`[${reply.author.username}] `) + + chalk.reset( + `${ + length > 79 + ? reply.content.substring(0, length - headerLength) + "\u2026" + : reply.content + }` + ) + ); + } } if ( (content.startsWith("*") && content.endsWith("*")) || (content.startsWith("_") && content.endsWith("_")) ) { - console.log( - chalk.bold.green(`<${name} ${content.substring(1, content.length - 1)}>`) - ); + if (isHistory) { + console.log(`<${name} ${content.subString(1, content.length - 1)}>`); + } else { + console.log( + chalk.bold.green( + `<${name} ${content.substring(1, content.length - 1)}>` + ) + ); + } } else { - const nameColor = bot ? chalk.bold.yellow : chalk.bold.cyan; + if (isHistory) { + console.log( + `[${name}]${" ".repeat(nameLength - (name.length + 2))} ${content}` + ); + } else { + const nameColor = bot ? chalk.bold.yellow : chalk.bold.cyan; - // TODO: markdown - console.log( - nameColor(`[${name}]`) + - " ".repeat(nameLength - (name.length + 2)) + - chalk.reset(" " + content) - ); + // TODO: markdown + console.log( + nameColor(`[${name}]`) + + " ".repeat(nameLength - (name.length + 2)) + + chalk.reset(" " + content) + ); + } } for (const attachment of attachments) { - console.log(chalk.bold.yellow(``)); + if (isHistory) { + console.log(``); + } else { + console.log(chalk.bold.yellow(``)); + } } } @@ -359,6 +393,7 @@ function listUsers() { } function switchGuild() { + targetGuild = targetGuild.trim(); if (targetGuild == "") { listUsers(); guildSwitch = false; @@ -397,6 +432,7 @@ function gotoChannel() { } function switchChannel() { + targetChannel = targetChannel.trim(); if (targetChannel == "") { listUsers(); channelSwitch = false; @@ -449,6 +485,71 @@ async function sendEmote() { processQueue(); } +async function getHistory(limit = 20) { + const messages = await client.getMessages(currentChannel, {limit}); + messages.reverse(); + + console.log("--Beginning-Review".padEnd(72, "-")); + + for (const msg of messages) { + if (msg.content.indexOf("\n") > -1) { + const lines = msg.content.split("\n"); + for (const index in lines) { + const line = lines[index]; + processMessage({ + name: msg.author.username, + bot: msg.author.bot, + content: + line + + (msg.editedTimestamp != null && index == lines.length - 1 + ? " (edited)" + : ""), + attachments: index == lines.length - 1 ? msg.attachments : null, + reply: index == 0 ? msg.referencedMessage : null, + isHistory: true, + }); + } + } else { + processMessage({ + name: msg.author.username, + bot: msg.author.bot, + content: msg.content + (msg.editedTimestamp != null ? " (edited)" : ""), + attachments: msg.attachments, + reply: msg.referencedMessage, + isHistory: true, + }); + } + } + + console.log("--Review-Complete".padEnd(73, "-")); +} + +let numLines = ""; +function startExtendedHistory() { + numLines = ""; + extendedHistory = true; + + stdout.write(":lines> "); +} + +async function getExtendedHistory() { + numLines = numLines.trim(); + numLines = parseInt(numLines); + if (isNaN(numLines)) { + console.log(""); + extendedHistory = false; + return; + } + + try { + await getHistory(numLines); + } catch (err) { + console.log(""); + } + + extendedHistory = false; +} + stdin.on("data", function (key) { if (guildSwitch) { if (key === "\r") { @@ -517,6 +618,23 @@ stdin.on("data", function (key) { toSend += key; } } + } else if (extendedHistory) { + if (key === "\r") { + console.log(""); + getExtendedHistory(); + } else { + if (key === "\b") { + if (numLines.length > 0) { + stdout.moveCursor(-1); + stdout.write(" "); + stdout.moveCursor(-1); + numLines = numLines.substring(0, numLines.length - 1); + } + } else { + stdout.write(key); + numLines += key; + } + } } else { switch (key) { case "\u0003": @@ -568,6 +686,22 @@ stdin.on("data", function (key) { startEmote(); break; } + case "r": { + if (currentChannel == null) { + console.log(""); + break; + } + getHistory(); + break; + } + case "R": { + if (currentChannel == null) { + console.log(""); + break; + } + startExtendedHistory(); + break; + } case " ": case "\r": default: { From 080d20396bd9998eeb596741bfff9ea9bda07931 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sat, 27 Aug 2022 21:40:53 -0600 Subject: [PATCH 12/92] fix help spacing, add clear --- README.md | 5 +++++ src/index.js | 25 +++++++++++++++++++------ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ef81180..fe7ddad 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,11 @@ You **MUST** grant your bot all Privileged Gateway Intents. - [ ] Creation date, join date, ID, etc - [x] Room history (r) - [x] Extended room history (R) + - [ ] List channels (l) + - [x] List guilds (L) + - [x] Clear (c) + - [ ] Surf channels forwards (>) + - [ ] Surf channels backwards (<) - [x] Message Receiving - [ ] Markdown styling - [ ] Common markdown (bold, italic, etc) diff --git a/src/index.js b/src/index.js index 9becf8d..0bf8eed 100644 --- a/src/index.js +++ b/src/index.js @@ -30,9 +30,11 @@ const commands = { w: "who is in guild", f: "finger", r: "channel history", - R: "extended channel history", + R: "extended history", h: "command help", c: "clear", + "<": "surf backwards", + ">": "surf forwards", }; const client = new Eris("Bot " + token, { @@ -262,17 +264,18 @@ function showHelp() { console.log("\nCOMcord (c)left 2022\n"); const keys = Object.keys(commands); - keys.sort(); + keys.sort((a, b) => a.localeCompare(b)); let index = 0; for (const key of keys) { const desc = commands[key]; + const length = ` ${key} - ${desc}`.length; stdout.write( - (" " + chalk.bold.yellow(key) + chalk.reset(" - " + desc)).padEnd( - 25, - " " - ) + " " + + chalk.bold.yellow(key) + + chalk.reset(" - " + desc) + + " ".repeat(Math.abs(25 - length)) ); index++; @@ -702,6 +705,16 @@ stdin.on("data", function (key) { startExtendedHistory(); break; } + case "c": { + console.clear(); + break; + } + case "<": { + break; + } + case ">": { + break; + } case " ": case "\r": default: { From 0b1387f7b9cea9c2420437b17b7f314baa32cfb5 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sat, 27 Aug 2022 21:56:02 -0600 Subject: [PATCH 13/92] add channel listing --- README.md | 2 +- src/index.js | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fe7ddad..2531bae 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ You **MUST** grant your bot all Privileged Gateway Intents. - [ ] Creation date, join date, ID, etc - [x] Room history (r) - [x] Extended room history (R) - - [ ] List channels (l) + - [x] List channels (l) - [x] List guilds (L) - [x] Clear (c) - [ ] Surf channels forwards (>) diff --git a/src/index.js b/src/index.js index 0bf8eed..04fd441 100644 --- a/src/index.js +++ b/src/index.js @@ -313,6 +313,42 @@ function listGuilds() { console.log(""); } +function listChannels() { + let longest = 0; + let longestTopic = 0; + const guild = client.guilds.get(currentGuild); + const channels = [...guild.channels.values()].filter((c) => c.type == 0); + channels.sort((a, b) => a.position - b.position); + + for (const channel of channels) { + if (channel.name.length > longest) longest = channel.name.length; + if (channel.topic != null && channel.topic.length > longestTopic) + longestTopic = channel.topic.length; + } + + console.log(""); + console.log( + " " + + "channel-name".padStart(longest, " ") + + " " + + "topic".padStart(Math.min(80 - (longest + 5), longestTopic), " ") + ); + console.log("-".repeat(80)); + for (const channel of channels) { + const topic = + channel.topic != null ? channel.topic.replace(/\n/g, " ") : ""; + console.log( + " " + + channel.name.padStart(longest, " ") + + " " + + (topic.length > 80 - longest + 9 + ? topic.substring(0, 79 - (longest + 5)) + "\u2026" + : topic.padStart(Math.min(80 - (longest + 5), longestTopic), " ")) + ); + } + console.log(""); +} + let targetGuild = ""; function gotoGuild() { targetGuild = ""; @@ -398,6 +434,7 @@ function listUsers() { function switchGuild() { targetGuild = targetGuild.trim(); if (targetGuild == "") { + listChannels(); listUsers(); guildSwitch = false; return; @@ -420,6 +457,7 @@ function switchGuild() { const topChannel = findTopChannel(target); currentChannel = topChannel.id; + listChannels(); listUsers(); } @@ -667,6 +705,7 @@ stdin.on("data", function (key) { console.log(""); break; } + listChannels(); break; } case "L": { From bc9bf221637049a347057dd4e850fb7b833e01f3 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sat, 27 Aug 2022 22:06:26 -0600 Subject: [PATCH 14/92] private channel indicator for channel list --- src/index.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/index.js b/src/index.js index 04fd441..41d5c5b 100644 --- a/src/index.js +++ b/src/index.js @@ -321,7 +321,11 @@ function listChannels() { channels.sort((a, b) => a.position - b.position); for (const channel of channels) { - if (channel.name.length > longest) longest = channel.name.length; + const perms = channel.permissionsOf(client.user.id); + const private = !perms.has(Eris.Constants.Permissions.readMessages); + + if (channel.name.length + (private ? 1 : 0) > longest) + longest = channel.name.length + (private ? 1 : 0); if (channel.topic != null && channel.topic.length > longestTopic) longestTopic = channel.topic.length; } @@ -337,9 +341,12 @@ function listChannels() { for (const channel of channels) { const topic = channel.topic != null ? channel.topic.replace(/\n/g, " ") : ""; + const perms = channel.permissionsOf(client.user.id); + const private = !perms.has(Eris.Constants.Permissions.readMessages); + console.log( " " + - channel.name.padStart(longest, " ") + + ((private ? "*" : "") + channel.name).padStart(longest, " ") + " " + (topic.length > 80 - longest + 9 ? topic.substring(0, 79 - (longest + 5)) + "\u2026" From e8860722f0b67146014b6cf50a6eeb5f871c7c0c Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sun, 28 Aug 2022 09:12:26 -0600 Subject: [PATCH 15/92] quick fixes --- src/index.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/index.js b/src/index.js index 41d5c5b..ec62eb5 100644 --- a/src/index.js +++ b/src/index.js @@ -99,7 +99,7 @@ function processMessage({ (content.startsWith("_") && content.endsWith("_")) ) { if (isHistory) { - console.log(`<${name} ${content.subString(1, content.length - 1)}>`); + console.log(`<${name} ${content.substring(1, content.length - 1)}>`); } else { console.log( chalk.bold.green( @@ -124,11 +124,13 @@ function processMessage({ } } - for (const attachment of attachments) { - if (isHistory) { - console.log(``); - } else { - console.log(chalk.bold.yellow(``)); + if (attachments) { + for (const attachment of attachments) { + if (isHistory) { + console.log(``); + } else { + console.log(chalk.bold.yellow(``)); + } } } } @@ -143,7 +145,7 @@ function processQueue() { name: msg.author.username, bot: msg.author.bot, content: line, - attachments: index == lines.length - 1 ? msg.attachments : null, + attachments: index == lines.length - 1 ? msg.attachments : [], reply: index == 0 ? msg.referencedMessage : null, }); } @@ -176,7 +178,7 @@ client.on("messageCreate", function (msg) { name: msg.author.username, bot: msg.author.bot, content: line, - attachments: index == lines.length - 1 ? msg.attachments : null, + attachments: index == lines.length - 1 ? msg.attachments : [], reply: index == 0 ? msg.referencedMessage : null, }); } @@ -208,8 +210,8 @@ client.on("messageUpdate", function (msg, old) { processMessage({ name: msg.author.username, bot: msg.author.bot, - content: line + index == lines.length - 1 ? " (edited)" : null, - attachments: index == lines.length - 1 ? msg.attachments : null, + content: line + (index == lines.length - 1 ? " (edited)" : ""), + attachments: index == lines.length - 1 ? msg.attachments : [], reply: index == 0 ? msg.referencedMessage : null, }); } From adf3e5fe4d6283d2f26b87a500446caccf3274c2 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sun, 28 Aug 2022 16:01:31 -0600 Subject: [PATCH 16/92] refactor everything out of one file, add channel rememberance --- .eslintrc.js | 1 + README.md | 2 +- src/commands/clear.js | 5 + src/commands/emote.js | 26 ++ src/commands/help.js | 29 ++ src/commands/history.js | 72 ++++ src/commands/listChannels.js | 62 +++ src/commands/listGuilds.js | 34 ++ src/commands/listUsers.js | 86 ++++ src/commands/quit.js | 6 + src/commands/send.js | 35 ++ src/commands/switchChannel.js | 41 ++ src/commands/switchGuild.js | 51 +++ src/index.js | 738 +++------------------------------- src/lib/command.js | 17 + src/lib/messages.js | 117 ++++++ src/lib/prompt.js | 25 ++ 17 files changed, 661 insertions(+), 686 deletions(-) create mode 100644 src/commands/clear.js create mode 100644 src/commands/emote.js create mode 100644 src/commands/help.js create mode 100644 src/commands/history.js create mode 100644 src/commands/listChannels.js create mode 100644 src/commands/listGuilds.js create mode 100644 src/commands/listUsers.js create mode 100644 src/commands/quit.js create mode 100644 src/commands/send.js create mode 100644 src/commands/switchChannel.js create mode 100644 src/commands/switchGuild.js create mode 100644 src/lib/command.js create mode 100644 src/lib/messages.js create mode 100644 src/lib/prompt.js diff --git a/.eslintrc.js b/.eslintrc.js index 4b9468b..e78c339 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -26,5 +26,6 @@ module.exports = { "require-atomic-updates": OFF, }, globals: { + comcord: true, }, }; diff --git a/README.md b/README.md index 2531bae..aaaf716 100644 --- a/README.md +++ b/README.md @@ -56,4 +56,4 @@ You **MUST** grant your bot all Privileged Gateway Intents. - [ ] Default guild/channel - [ ] Threads - [ ] Not have the token just be in argv -- [ ] Not have everything in one file +- [x] Not have everything in one file diff --git a/src/commands/clear.js b/src/commands/clear.js new file mode 100644 index 0000000..db973c5 --- /dev/null +++ b/src/commands/clear.js @@ -0,0 +1,5 @@ +const {addCommand} = require("../lib/command"); + +addCommand("c", "clear", function () { + console.clear(); +}); diff --git a/src/commands/emote.js b/src/commands/emote.js new file mode 100644 index 0000000..aef4b7d --- /dev/null +++ b/src/commands/emote.js @@ -0,0 +1,26 @@ +const {addCommand} = require("../lib/command"); +const {startPrompt} = require("../lib/prompt"); + +addCommand("e", "emote", function () { + if (!comcord.state.currentChannel) { + console.log(""); + return; + } + + startPrompt(":emote> ", async function (input) { + if (input == "") { + console.log(""); + } else { + try { + process.stdout.write("\n"); + await comcord.client.createMessage( + comcord.state.currentChannel, + `*${input}*` + ); + console.log(`<${comcord.client.user.username} ${input}>`); + } catch (err) { + console.log(""); + } + } + }); +}); diff --git a/src/commands/help.js b/src/commands/help.js new file mode 100644 index 0000000..27047cf --- /dev/null +++ b/src/commands/help.js @@ -0,0 +1,29 @@ +const chalk = require("chalk"); + +const {addCommand} = require("../lib/command"); + +addCommand("h", "command help", function () { + console.log("\nCOMcord (c)left 2022\n"); + + const keys = Object.keys(comcord.commands); + keys.sort((a, b) => a.localeCompare(b)); + + let index = 0; + for (const key of keys) { + const desc = comcord.commands[key].name; + const length = ` ${key} - ${desc}`.length; + + process.stdout.write( + " " + + chalk.bold.yellow(key) + + chalk.reset(" - " + desc) + + " ".repeat(Math.abs(25 - length)) + ); + + index++; + if (index % 3 == 0) process.stdout.write("\n"); + } + if (index % 3 != 0) process.stdout.write("\n"); + + console.log("\nTo begin TALK MODE, press [SPACE]\n"); +}); diff --git a/src/commands/history.js b/src/commands/history.js new file mode 100644 index 0000000..b22b52b --- /dev/null +++ b/src/commands/history.js @@ -0,0 +1,72 @@ +const {addCommand} = require("../lib/command"); +const {startPrompt} = require("../lib/prompt"); +const {processMessage} = require("../lib/messages"); + +async function getHistory(limit = 20) { + if (!comcord.state.currentChannel) { + console.log(""); + return; + } + + const messages = await comcord.client.getMessages( + comcord.state.currentChannel, + {limit} + ); + messages.reverse(); + + console.log("--Beginning-Review".padEnd(72, "-")); + + for (const msg of messages) { + if (msg.content.indexOf("\n") > -1) { + const lines = msg.content.split("\n"); + for (const index in lines) { + const line = lines[index]; + processMessage({ + name: msg.author.username, + bot: msg.author.bot, + content: + line + + (msg.editedTimestamp != null && index == lines.length - 1 + ? " (edited)" + : ""), + attachments: index == lines.length - 1 ? msg.attachments : null, + reply: index == 0 ? msg.referencedMessage : null, + noColor: true, + }); + } + } else { + processMessage({ + name: msg.author.username, + bot: msg.author.bot, + content: msg.content + (msg.editedTimestamp != null ? " (edited)" : ""), + attachments: msg.attachments, + reply: msg.referencedMessage, + noColor: true, + }); + } + } + + console.log("--Review-Complete".padEnd(73, "-")); +} + +async function getExtendedHistory(input) { + input = parseInt(input); + if (isNaN(input)) { + console.log(""); + return; + } + + try { + await getHistory(input); + } catch (err) { + console.log(""); + } +} + +addCommand("r", "channel history", getHistory); +addCommand("R", "extended history", function () { + startPrompt(":lines> ", async function (input) { + process.stdout.write("\n"); + await getExtendedHistory(input); + }); +}); diff --git a/src/commands/listChannels.js b/src/commands/listChannels.js new file mode 100644 index 0000000..7d12c3e --- /dev/null +++ b/src/commands/listChannels.js @@ -0,0 +1,62 @@ +const Eris = require("eris"); + +const {addCommand} = require("../lib/command"); + +function listChannels() { + if (!comcord.state.currentGuild) { + console.log(""); + return; + } + + let longest = 0; + let longestTopic = 0; + const guild = comcord.client.guilds.get(comcord.state.currentGuild); + const channels = [...guild.channels.values()].filter((c) => c.type == 0); + channels.sort((a, b) => a.position - b.position); + + for (const channel of channels) { + const perms = channel.permissionsOf(comcord.client.user.id); + const private = !perms.has(Eris.Constants.Permissions.readMessages); + + if (channel.name.length + (private ? 1 : 0) > longest) + longest = Math.min(25, channel.name.length + (private ? 1 : 0)); + if (channel.topic != null && channel.topic.length > longestTopic) + longestTopic = channel.topic.length; + } + + console.log(""); + console.log( + " " + + "channel-name".padStart(longest, " ") + + " " + + "topic".padStart(Math.min(80 - (longest + 5), longestTopic), " ") + ); + console.log("-".repeat(80)); + for (const channel of channels) { + const topic = + channel.topic != null ? channel.topic.replace(/\n/g, " ") : ""; + const perms = channel.permissionsOf(comcord.client.user.id); + const private = !perms.has(Eris.Constants.Permissions.readMessages); + + const name = (private ? "*" : "") + channel.name; + + console.log( + " " + + (name.length > 24 ? name.substring(0, 24) + "\u2026" : name).padStart( + longest, + " " + ) + + " " + + (topic.length > 80 - longest + 9 + ? topic.substring(0, 79 - (longest + 5)) + "\u2026" + : topic.padStart(Math.min(80 - (longest + 5), longestTopic), " ")) + ); + } + console.log(""); +} + +addCommand("l", "list channels", listChannels); + +module.exports = { + listChannels, +}; diff --git a/src/commands/listGuilds.js b/src/commands/listGuilds.js new file mode 100644 index 0000000..fc9fd3a --- /dev/null +++ b/src/commands/listGuilds.js @@ -0,0 +1,34 @@ +const {addCommand} = require("../lib/command"); + +function listGuilds() { + let longest = 0; + const guilds = []; + + for (const guild of comcord.client.guilds.values()) { + if (guild.name.length > longest) longest = guild.name.length; + + const online = [...guild.members.values()].filter((m) => m.status).length; + guilds.push({name: guild.name, members: guild.memberCount, online}); + } + + console.log(""); + console.log(" " + "guild-name".padStart(longest, " ") + " online total"); + console.log("-".repeat(80)); + for (const guild of guilds) { + console.log( + " " + + guild.name.padStart(longest, " ") + + " " + + guild.online.toString().padStart(6, " ") + + " " + + guild.members.toString().padStart(5, " ") + ); + } + console.log(""); +} + +addCommand("L", "list guilds", listGuilds); + +module.exports = { + listGuilds, +}; diff --git a/src/commands/listUsers.js b/src/commands/listUsers.js new file mode 100644 index 0000000..acaa2f5 --- /dev/null +++ b/src/commands/listUsers.js @@ -0,0 +1,86 @@ +const chalk = require("chalk"); + +const {addCommand} = require("../lib/command"); + +function getStatus(status) { + let color; + switch (status) { + case "online": + color = chalk.bold.green; + break; + case "idle": + color = chalk.bold.yellow; + break; + case "dnd": + color = chalk.bold.red; + break; + default: + color = chalk.bold; + break; + } + + return color(" \u2022 "); +} + +function listUsers() { + if (!comcord.state.currentGuild) { + console.log(""); + return; + } + if (!comcord.state.currentChannel) { + console.log(""); + return; + } + + const guild = comcord.client.guilds.get(comcord.state.currentGuild); + const channel = guild.channels.get(comcord.state.currentChannel); + + console.log( + `\n[you are in '${guild.name}' in '${channel.name}' among ${guild.memberCount}]\n` + ); + + const online = [...guild.members.values()].filter((m) => m.status); + online.sort((a, b) => a.name - b.name); + + let longest = 0; + for (const member of online) { + const name = member.user.username + "#" + member.user.discriminator; + if (name.length + 3 > longest) longest = name.length + 3; + } + + const columns = Math.ceil(process.stdout.columns / longest); + + let index = 0; + for (const member of online) { + const name = member.user.username + "#" + member.user.discriminator; + const status = getStatus(member.status); + const nameAndStatus = chalk.reset(name) + status; + + index++; + process.stdout.write( + nameAndStatus + + " ".repeat( + index % columns == 0 ? 0 : Math.abs(longest - (name.length + 3)) + ) + ); + + if (index % columns == 0) process.stdout.write("\n"); + } + if (index % columns != 0) process.stdout.write("\n"); + console.log(""); + + if (channel.topic != null) { + console.log("--Topic".padEnd(80, "-")); + console.log(channel.topic); + console.log("-".repeat(80)); + console.log(""); + } +} + +if (!comcord.commands.w) { + addCommand("w", "who is in guild", listUsers); +} + +module.exports = { + listUsers, +}; diff --git a/src/commands/quit.js b/src/commands/quit.js new file mode 100644 index 0000000..aafaac5 --- /dev/null +++ b/src/commands/quit.js @@ -0,0 +1,6 @@ +const {addCommand} = require("../lib/command"); + +addCommand("q", "quit comcord", function () { + comcord.client.disconnect(false); + process.exit(0); +}); diff --git a/src/commands/send.js b/src/commands/send.js new file mode 100644 index 0000000..b067c17 --- /dev/null +++ b/src/commands/send.js @@ -0,0 +1,35 @@ +const chalk = require("chalk"); + +const {startPrompt} = require("../lib/prompt"); + +function sendMode() { + if (!comcord.state.currentChannel) { + console.log(""); + return; + } + + startPrompt( + chalk.bold.cyan(`[${comcord.client.user.username}]`) + + " ".repeat( + comcord.state.nameLength - (comcord.client.user.username.length + 2) + ) + + chalk.reset(" "), + async function (input) { + if (input == "") { + console.log(""); + } else { + try { + process.stdout.write("\n"); + await comcord.client.createMessage( + comcord.state.currentChannel, + input + ); + } catch (err) { + console.log(""); + } + } + } + ); +} + +module.exports = {sendMode}; diff --git a/src/commands/switchChannel.js b/src/commands/switchChannel.js new file mode 100644 index 0000000..41bf69b --- /dev/null +++ b/src/commands/switchChannel.js @@ -0,0 +1,41 @@ +const {addCommand} = require("../lib/command"); +const {startPrompt} = require("../lib/prompt"); + +const {listUsers} = require("./listUsers"); + +function switchChannel(input) { + if (input == "") { + listUsers(); + comcord.state.channelSwitch = false; + return; + } + let target; + + const guild = comcord.client.guilds.get(comcord.state.currentGuild); + const channels = [...guild.channels.values()].filter((c) => c.type == 0); + channels.sort((a, b) => a.position - b.position); + + for (const channel of channels) { + if (channel.name.toLowerCase().indexOf(input.toLowerCase()) > -1) { + target = channel.id; + break; + } + } + + if (target == null) { + console.log(""); + } else { + comcord.state.currentChannel = target; + comcord.state.lastChannel.set(comcord.state.currentGuild, target); + + listUsers(); + } +} + +addCommand("g", "goto channel", function () { + if (!comcord.state.currentGuild) { + console.log(""); + return; + } + startPrompt(":channel> ", switchChannel); +}); diff --git a/src/commands/switchGuild.js b/src/commands/switchGuild.js new file mode 100644 index 0000000..844a561 --- /dev/null +++ b/src/commands/switchGuild.js @@ -0,0 +1,51 @@ +const {addCommand} = require("../lib/command"); +const {startPrompt} = require("../lib/prompt"); + +const {listChannels} = require("./listChannels"); +const {listUsers} = require("./listUsers"); + +function findTopChannel(guildId) { + const guild = comcord.client.guilds.get(guildId); + const channels = [...guild.channels.values()].filter((c) => c.type == 0); + channels.sort((a, b) => a.position - b.position); + + return channels[0]; +} + +function switchGuild(input) { + if (input == "") { + listChannels(); + listUsers(); + return; + } + + let target; + + for (const guild of comcord.client.guilds.values()) { + if (guild.name.toLowerCase().indexOf(input.toLowerCase()) > -1) { + target = guild.id; + break; + } + } + + if (target == null) { + console.log(""); + } else { + comcord.state.currentGuild = target; + // TODO: store last visited channel and switch to it if we've been to this guild before + if (!comcord.state.lastChannel.has(target)) { + const topChannel = findTopChannel(target); + comcord.state.currentChannel = topChannel.id; + comcord.state.lastChannel.set(target, topChannel.id); + } else { + comcord.state.currentChannel = comcord.state.lastChannel.get(target); + } + + listChannels(); + listUsers(); + } +} + +addCommand("G", "goto guild", function () { + startPrompt(":guild> ", switchGuild); +}); diff --git a/src/index.js b/src/index.js index ec62eb5..781799c 100644 --- a/src/index.js +++ b/src/index.js @@ -2,46 +2,41 @@ const Eris = require("eris"); const chalk = require("chalk"); const token = process.argv[2]; -const stdin = process.stdin; -const stdout = process.stdout; -stdin.setRawMode(true); -stdin.resume(); -stdin.setEncoding("utf8"); - -let currentGuild, - currentChannel, - inSendMode = false, - inEmoteMode = false, - guildSwitch = false, - channelSwitch = false, - extendedHistory = false, - nameLength = 2; - -const messageQueue = []; - -const commands = { - q: "quit comcord", - e: "emote", - g: "goto a channel", - G: "goto a guild", - l: "list channels", - L: "list guilds", - w: "who is in guild", - f: "finger", - r: "channel history", - R: "extended history", - h: "command help", - c: "clear", - "<": "surf backwards", - ">": "surf forwards", +global.comcord = { + state: { + currentGuild: null, + currentChannel: null, + nameLength: 2, + inPrompt: false, + messageQueue: [], + lastChannel: new Map(), + }, + commands: {}, }; - const client = new Eris("Bot " + token, { defaultImageFormat: "png", defaultImageSize: 1024, intents: Eris.Constants.Intents.all, }); +comcord.client = client; + +const {finalizePrompt} = require("./lib/prompt"); +const {processMessage, processQueue} = require("./lib/messages"); + +require("./commands/quit"); +require("./commands/clear"); +require("./commands/help"); +const {sendMode} = require("./commands/send"); +require("./commands/emote"); +const {listGuilds} = require("./commands/listGuilds"); +require("./commands/switchGuild"); // loads listChannels and listUsers +require("./commands/switchChannel"); //loads listUsers +require("./commands/history"); // includes extended history + +process.stdin.setRawMode(true); +process.stdin.resume(); +process.stdin.setEncoding("utf8"); client.once("ready", function () { console.log( @@ -50,125 +45,17 @@ client.once("ready", function () { `${client.user.username}#${client.user.discriminator} (${client.user.id})` ) ); - nameLength = client.user.username.length + 2; + comcord.state.nameLength = client.user.username.length + 2; listGuilds(); }); -function processMessage({ - name, - content, - bot, - attachments, - reply, - isHistory = false, -}) { - if (name.length + 2 > nameLength) nameLength = name.length + 2; - - if (reply) { - const nameColor = reply.author.bot ? chalk.bold.yellow : chalk.bold.cyan; - - const headerLength = 5 + reply.author.username.length; - const length = headerLength + reply.content.length; - - if (isHistory) { - console.log( - ` \u250d [${reply.author.username}] ${ - length > 79 - ? reply.content.substring(0, length - headerLength) + "\u2026" - : reply.content - }` - ); - } else { - console.log( - chalk.bold.white(" \u250d ") + - nameColor(`[${reply.author.username}] `) + - chalk.reset( - `${ - length > 79 - ? reply.content.substring(0, length - headerLength) + "\u2026" - : reply.content - }` - ) - ); - } - } - - if ( - (content.startsWith("*") && content.endsWith("*")) || - (content.startsWith("_") && content.endsWith("_")) - ) { - if (isHistory) { - console.log(`<${name} ${content.substring(1, content.length - 1)}>`); - } else { - console.log( - chalk.bold.green( - `<${name} ${content.substring(1, content.length - 1)}>` - ) - ); - } - } else { - if (isHistory) { - console.log( - `[${name}]${" ".repeat(nameLength - (name.length + 2))} ${content}` - ); - } else { - const nameColor = bot ? chalk.bold.yellow : chalk.bold.cyan; - - // TODO: markdown - console.log( - nameColor(`[${name}]`) + - " ".repeat(nameLength - (name.length + 2)) + - chalk.reset(" " + content) - ); - } - } - - if (attachments) { - for (const attachment of attachments) { - if (isHistory) { - console.log(``); - } else { - console.log(chalk.bold.yellow(``)); - } - } - } -} - -function processQueue() { - for (const msg of messageQueue) { - if (msg.content.indexOf("\n") > -1) { - const lines = msg.content.split("\n"); - for (const index in lines) { - const line = lines[index]; - processMessage({ - name: msg.author.username, - bot: msg.author.bot, - content: line, - attachments: index == lines.length - 1 ? msg.attachments : [], - reply: index == 0 ? msg.referencedMessage : null, - }); - } - } else { - processMessage({ - name: msg.author.username, - bot: msg.author.bot, - content: msg.content, - attachments: msg.attachments, - reply: msg.referencedMessage, - }); - } - } - - messageQueue.splice(0, messageQueue.length); -} - client.on("messageCreate", function (msg) { if (msg.author.id === client.user.id) return; - if (msg.channel.id == currentChannel) { - if (inSendMode || inEmoteMode) { - messageQueue.push(msg); + if (msg.channel.id == comcord.state.currentChannel) { + if (comcord.state.inPrompt) { + comcord.state.messageQueue.push(msg); } else { if (msg.content.indexOf("\n") > -1) { const lines = msg.content.split("\n"); @@ -197,11 +84,11 @@ client.on("messageCreate", function (msg) { client.on("messageUpdate", function (msg, old) { if (msg.author.id === client.user.id) return; - if (msg.channel.id == currentChannel) { + if (msg.channel.id == comcord.state.currentChannel) { if (msg.content == old.content) return; - if (inSendMode || inEmoteMode) { - messageQueue.push(msg); + if (comcord.state.inPrompt) { + comcord.state.messageQueue.push(msg); } else { if (msg.content.indexOf("\n") > -1) { const lines = msg.content.split("\n"); @@ -228,551 +115,32 @@ client.on("messageUpdate", function (msg, old) { } }); -let toSend = ""; -async function setupSendMode() { - inSendMode = true; - toSend = ""; - stdout.write( - chalk.bold.cyan(`[${client.user.username}]`) + - " ".repeat(nameLength - (client.user.username.length + 2)) + - chalk.reset(" ") - ); - try { - await client.guilds - .get(currentGuild) - .channels.get(currentChannel) - .sendTyping(); - } catch (err) { - // - } -} -async function sendMessage() { - toSend = toSend.trim(); - if (toSend === "") { - stdout.write("\n"); - } else { - try { - stdout.write("\n"); - await client.createMessage(currentChannel, toSend); - } catch (err) { - console.log(""); - } - } - inSendMode = false; - processQueue(); -} - -function showHelp() { - console.log("\nCOMcord (c)left 2022\n"); - - const keys = Object.keys(commands); - keys.sort((a, b) => a.localeCompare(b)); - - let index = 0; - for (const key of keys) { - const desc = commands[key]; - const length = ` ${key} - ${desc}`.length; - - stdout.write( - " " + - chalk.bold.yellow(key) + - chalk.reset(" - " + desc) + - " ".repeat(Math.abs(25 - length)) - ); - - index++; - if (index % 3 == 0) stdout.write("\n"); - } - if (index % 3 != 0) stdout.write("\n"); - - console.log("\nTo begin TALK MODE, press [SPACE]\n"); -} - -function listGuilds() { - let longest = 0; - const guilds = []; - - for (const guild of client.guilds.values()) { - if (guild.name.length > longest) longest = guild.name.length; - - const online = [...guild.members.values()].filter((m) => m.status).length; - guilds.push({name: guild.name, members: guild.memberCount, online}); - } - - console.log(""); - console.log(" " + "guild-name".padStart(longest, " ") + " online total"); - console.log("-".repeat(80)); - for (const guild of guilds) { - console.log( - " " + - guild.name.padStart(longest, " ") + - " " + - guild.online.toString().padStart(6, " ") + - " " + - guild.members.toString().padStart(5, " ") - ); - } - console.log(""); -} - -function listChannels() { - let longest = 0; - let longestTopic = 0; - const guild = client.guilds.get(currentGuild); - const channels = [...guild.channels.values()].filter((c) => c.type == 0); - channels.sort((a, b) => a.position - b.position); - - for (const channel of channels) { - const perms = channel.permissionsOf(client.user.id); - const private = !perms.has(Eris.Constants.Permissions.readMessages); - - if (channel.name.length + (private ? 1 : 0) > longest) - longest = channel.name.length + (private ? 1 : 0); - if (channel.topic != null && channel.topic.length > longestTopic) - longestTopic = channel.topic.length; - } - - console.log(""); - console.log( - " " + - "channel-name".padStart(longest, " ") + - " " + - "topic".padStart(Math.min(80 - (longest + 5), longestTopic), " ") - ); - console.log("-".repeat(80)); - for (const channel of channels) { - const topic = - channel.topic != null ? channel.topic.replace(/\n/g, " ") : ""; - const perms = channel.permissionsOf(client.user.id); - const private = !perms.has(Eris.Constants.Permissions.readMessages); - - console.log( - " " + - ((private ? "*" : "") + channel.name).padStart(longest, " ") + - " " + - (topic.length > 80 - longest + 9 - ? topic.substring(0, 79 - (longest + 5)) + "\u2026" - : topic.padStart(Math.min(80 - (longest + 5), longestTopic), " ")) - ); - } - console.log(""); -} - -let targetGuild = ""; -function gotoGuild() { - targetGuild = ""; - guildSwitch = true; - - stdout.write(":guild> "); -} - -function findTopChannel(guildId) { - const guild = client.guilds.get(guildId); - const channels = [...guild.channels.values()].filter((c) => c.type == 0); - channels.sort((a, b) => a.position - b.position); - - return channels[0]; -} - -function getStatus(status) { - let color; - switch (status) { - case "online": - color = chalk.bold.green; - break; - case "idle": - color = chalk.bold.yellow; - break; - case "dnd": - color = chalk.bold.red; - break; - default: - color = chalk.bold; - break; - } - - return color(" \u2022 "); -} - -function listUsers() { - const guild = client.guilds.get(currentGuild); - const channel = guild.channels.get(currentChannel); - - console.log( - `\n[you are in '${guild.name}' in '${channel.name}' among ${guild.memberCount}]\n` - ); - - const online = [...guild.members.values()].filter((m) => m.status); - online.sort((a, b) => a.name - b.name); - - let longest = 0; - for (const member of online) { - const name = member.user.username + "#" + member.user.discriminator; - if (name.length + 3 > longest) longest = name.length + 3; - } - - const columns = Math.ceil(stdout.columns / longest); - - let index = 0; - for (const member of online) { - const name = member.user.username + "#" + member.user.discriminator; - const status = getStatus(member.status); - const nameAndStatus = chalk.reset(name) + status; - - index++; - stdout.write( - nameAndStatus + - " ".repeat( - index % columns == 0 ? 0 : Math.abs(longest - (name.length + 3)) - ) - ); - - if (index % columns == 0) stdout.write("\n"); - } - if (index % columns != 0) stdout.write("\n"); - console.log(""); - - if (channel.topic != null) { - console.log("--Topic".padEnd(80, "-")); - console.log(channel.topic); - console.log("-".repeat(80)); - console.log(""); - } -} - -function switchGuild() { - targetGuild = targetGuild.trim(); - if (targetGuild == "") { - listChannels(); - listUsers(); - guildSwitch = false; - return; - } - - let target; - - for (const guild of client.guilds.values()) { - if (guild.name.toLowerCase().indexOf(targetGuild.toLowerCase()) > -1) { - target = guild.id; - break; - } - } - - if (target == null) { - console.log(""); - } else { - currentGuild = target; - // TODO: store last visited channel and switch to it if we've been to this guild before - const topChannel = findTopChannel(target); - currentChannel = topChannel.id; - - listChannels(); - listUsers(); - } - - guildSwitch = false; -} - -let targetChannel = ""; -function gotoChannel() { - targetChannel = ""; - channelSwitch = true; - - stdout.write(":channel> "); -} - -function switchChannel() { - targetChannel = targetChannel.trim(); - if (targetChannel == "") { - listUsers(); - channelSwitch = false; - return; - } - let target; - - const guild = client.guilds.get(currentGuild); - const channels = [...guild.channels.values()].filter((c) => c.type == 0); - channels.sort((a, b) => a.position - b.position); - - for (const channel of channels) { - if (channel.name.toLowerCase().indexOf(targetChannel.toLowerCase()) > -1) { - target = channel.id; - break; - } - } - - if (target == null) { - console.log(""); - } else { - currentChannel = target; - - listUsers(); - } - - channelSwitch = false; -} - -function startEmote() { - toSend = ""; - inEmoteMode = true; - - stdout.write(":emote> "); -} - -async function sendEmote() { - toSend = toSend.trim(); - if (toSend === "") { - console.log(""); - } else { - try { - await client.createMessage(currentChannel, "*" + toSend + "*"); - console.log(`<${client.user.username} ${toSend}>`); - } catch (err) { - console.log(""); - } - } - inEmoteMode = false; - processQueue(); -} - -async function getHistory(limit = 20) { - const messages = await client.getMessages(currentChannel, {limit}); - messages.reverse(); - - console.log("--Beginning-Review".padEnd(72, "-")); - - for (const msg of messages) { - if (msg.content.indexOf("\n") > -1) { - const lines = msg.content.split("\n"); - for (const index in lines) { - const line = lines[index]; - processMessage({ - name: msg.author.username, - bot: msg.author.bot, - content: - line + - (msg.editedTimestamp != null && index == lines.length - 1 - ? " (edited)" - : ""), - attachments: index == lines.length - 1 ? msg.attachments : null, - reply: index == 0 ? msg.referencedMessage : null, - isHistory: true, - }); - } - } else { - processMessage({ - name: msg.author.username, - bot: msg.author.bot, - content: msg.content + (msg.editedTimestamp != null ? " (edited)" : ""), - attachments: msg.attachments, - reply: msg.referencedMessage, - isHistory: true, - }); - } - } - - console.log("--Review-Complete".padEnd(73, "-")); -} - -let numLines = ""; -function startExtendedHistory() { - numLines = ""; - extendedHistory = true; - - stdout.write(":lines> "); -} - -async function getExtendedHistory() { - numLines = numLines.trim(); - numLines = parseInt(numLines); - if (isNaN(numLines)) { - console.log(""); - extendedHistory = false; - return; - } - - try { - await getHistory(numLines); - } catch (err) { - console.log(""); - } - - extendedHistory = false; -} - -stdin.on("data", function (key) { - if (guildSwitch) { +process.stdin.on("data", async function (key) { + if (comcord.state.inPrompt) { if (key === "\r") { - console.log(""); - switchGuild(); + await finalizePrompt(); + processQueue(); } else { if (key === "\b") { - if (targetGuild.length > 0) { - stdout.moveCursor(-1); - stdout.write(" "); - stdout.moveCursor(-1); - targetGuild = targetGuild.substring(0, targetGuild.length - 1); + if (comcord.state.promptInput.length > 0) { + process.stdout.moveCursor(-1); + process.stdout.write(" "); + process.stdout.moveCursor(-1); + comcord.state.promptInput = comcord.state.promptInput.substring( + 0, + comcord.state.promptInput.length - 1 + ); } } else { - stdout.write(key); - targetGuild += key; - } - } - } else if (channelSwitch) { - if (key === "\r") { - console.log(""); - switchChannel(); - } else { - if (key === "\b") { - if (targetChannel.length > 0) { - stdout.moveCursor(-1); - stdout.write(" "); - stdout.moveCursor(-1); - targetChannel = targetChannel.substring(0, targetChannel.length - 1); - } - } else { - stdout.write(key); - targetChannel += key; - } - } - } else if (inSendMode) { - if (key === "\r") { - sendMessage(); - } else { - if (key === "\b") { - if (toSend.length > 0) { - stdout.moveCursor(-1); - stdout.write(" "); - stdout.moveCursor(-1); - toSend = toSend.substring(0, toSend.length - 1); - } - } else { - stdout.write(key); - toSend += key; - } - } - } else if (inEmoteMode) { - if (key === "\r") { - console.log(""); - sendEmote(); - } else { - if (key === "\b") { - if (toSend.length > 0) { - stdout.moveCursor(-1); - stdout.write(" "); - stdout.moveCursor(-1); - toSend = toSend.substring(0, toSend.length - 1); - } - } else { - stdout.write(key); - toSend += key; - } - } - } else if (extendedHistory) { - if (key === "\r") { - console.log(""); - getExtendedHistory(); - } else { - if (key === "\b") { - if (numLines.length > 0) { - stdout.moveCursor(-1); - stdout.write(" "); - stdout.moveCursor(-1); - numLines = numLines.substring(0, numLines.length - 1); - } - } else { - stdout.write(key); - numLines += key; + process.stdout.write(key); + comcord.state.promptInput += key; } } } else { - switch (key) { - case "\u0003": - case "q": { - client.disconnect(false); - process.exit(0); - break; - } - case "h": { - showHelp(); - break; - } - case "g": { - if (currentGuild == null) { - console.log(""); - break; - } - gotoChannel(); - break; - } - case "G": { - gotoGuild(); - break; - } - case "l": { - if (currentGuild == null) { - console.log(""); - break; - } - listChannels(); - break; - } - case "L": { - listGuilds(); - break; - } - case "w": { - if (currentGuild == null) { - console.log(""); - break; - } - listUsers(); - break; - } - case "e": { - if (currentChannel == null) { - console.log(""); - break; - } - startEmote(); - break; - } - case "r": { - if (currentChannel == null) { - console.log(""); - break; - } - getHistory(); - break; - } - case "R": { - if (currentChannel == null) { - console.log(""); - break; - } - startExtendedHistory(); - break; - } - case "c": { - console.clear(); - break; - } - case "<": { - break; - } - case ">": { - break; - } - case " ": - case "\r": - default: { - if (currentChannel == null) { - console.log(""); - break; - } - setupSendMode(); - break; - } + if (comcord.commands[key]) { + comcord.commands[key].callback(); + } else { + sendMode(); } } }); diff --git a/src/lib/command.js b/src/lib/command.js new file mode 100644 index 0000000..842ea3f --- /dev/null +++ b/src/lib/command.js @@ -0,0 +1,17 @@ +function addCommand(key, name, callback) { + if (comcord.commands[key]) { + console.error( + `Registering duplicate key for "${key}": "${name}" wants to overwrite "${comcord.commands[key].name}"!` + ); + return; + } + + comcord.commands[key] = { + name, + callback, + }; +} + +module.exports = { + addCommand, +}; diff --git a/src/lib/messages.js b/src/lib/messages.js new file mode 100644 index 0000000..27ca9b5 --- /dev/null +++ b/src/lib/messages.js @@ -0,0 +1,117 @@ +const chalk = require("chalk"); + +function processMessage({ + name, + content, + bot, + attachments, + reply, + noColor = false, +}) { + if (name.length + 2 > comcord.state.nameLength) + comcord.statenameLength = name.length + 2; + + if (reply) { + const nameColor = reply.author.bot ? chalk.bold.yellow : chalk.bold.cyan; + + const headerLength = 5 + reply.author.username.length; + const length = headerLength + reply.content.length; + + if (noColor) { + console.log( + ` \u250d [${reply.author.username}] ${ + length > 79 + ? reply.content.substring(0, length - headerLength) + "\u2026" + : reply.content + }` + ); + } else { + console.log( + chalk.bold.white(" \u250d ") + + nameColor(`[${reply.author.username}] `) + + chalk.reset( + `${ + length > 79 + ? reply.content.substring(0, length - headerLength) + "\u2026" + : reply.content + }` + ) + ); + } + } + + if ( + (content.startsWith("*") && content.endsWith("*")) || + (content.startsWith("_") && content.endsWith("_")) + ) { + if (noColor) { + console.log(`<${name} ${content.substring(1, content.length - 1)}>`); + } else { + console.log( + chalk.bold.green( + `<${name} ${content.substring(1, content.length - 1)}>` + ) + ); + } + } else { + if (noColor) { + console.log( + `[${name}]${" ".repeat( + Math.abs(comcord.state.nameLength - (name.length + 2)) + )} ${content}` + ); + } else { + const nameColor = bot ? chalk.bold.yellow : chalk.bold.cyan; + + // TODO: markdown + console.log( + nameColor(`[${name}]`) + + " ".repeat(Math.abs(comcord.state.nameLength - (name.length + 2))) + + chalk.reset(" " + content) + ); + } + } + + if (attachments) { + for (const attachment of attachments) { + if (noColor) { + console.log(``); + } else { + console.log(chalk.bold.yellow(``)); + } + } + } +} + +function processQueue() { + for (const msg of comcord.state.messageQueue) { + if (msg.content.indexOf("\n") > -1) { + const lines = msg.content.split("\n"); + for (const index in lines) { + const line = lines[index]; + processMessage({ + name: msg.author.username, + bot: msg.author.bot, + content: line, + attachments: index == lines.length - 1 ? msg.attachments : [], + reply: index == 0 ? msg.referencedMessage : null, + }); + } + } else { + processMessage({ + name: msg.author.username, + bot: msg.author.bot, + content: msg.content, + attachments: msg.attachments, + reply: msg.referencedMessage, + }); + } + } + + comcord.state.messageQueue.splice(0, comcord.state.messageQueue.length); +} + +module.exports = { + processMessage, + processQueue, +}; diff --git a/src/lib/prompt.js b/src/lib/prompt.js new file mode 100644 index 0000000..0c882ed --- /dev/null +++ b/src/lib/prompt.js @@ -0,0 +1,25 @@ +function startPrompt(display, callback) { + comcord.state.inPrompt = true; + comcord.state.promptText = display; + comcord.state.promptInput = ""; + + comcord.state.promptCallback = callback; + + process.stdout.write(display); +} + +async function finalizePrompt() { + comcord.state.inPrompt = false; + comcord.state.promptText = null; + + const input = comcord.state.promptInput.trim(); + await comcord.state.promptCallback(input); + + comcord.state.promptInput = null; + comcord.state.promptCallback = null; +} + +module.exports = { + startPrompt, + finalizePrompt, +}; From 985d651309ba7e50e79f342194da3b9183b27d46 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sun, 28 Aug 2022 17:12:03 -0600 Subject: [PATCH 17/92] fix name length not being set properly --- src/lib/messages.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/messages.js b/src/lib/messages.js index 27ca9b5..b19aa1f 100644 --- a/src/lib/messages.js +++ b/src/lib/messages.js @@ -9,7 +9,7 @@ function processMessage({ noColor = false, }) { if (name.length + 2 > comcord.state.nameLength) - comcord.statenameLength = name.length + 2; + comcord.state.nameLength = name.length + 2; if (reply) { const nameColor = reply.author.bot ? chalk.bold.yellow : chalk.bold.cyan; From bf1a94bc19a8a8e2726db13a026c21d4bf35241a Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sun, 28 Aug 2022 17:20:01 -0600 Subject: [PATCH 18/92] fix backspace on linux --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 781799c..1355832 100644 --- a/src/index.js +++ b/src/index.js @@ -121,7 +121,7 @@ process.stdin.on("data", async function (key) { await finalizePrompt(); processQueue(); } else { - if (key === "\b") { + if (key === "\b" || key === "\u007f") { if (comcord.state.promptInput.length > 0) { process.stdout.moveCursor(-1); process.stdout.write(" "); From 7c692758ee999cf664d691ef1fab1399aea2798b Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sun, 28 Aug 2022 17:59:33 -0600 Subject: [PATCH 19/92] set process title --- src/commands/switchChannel.js | 5 ++++- src/commands/switchGuild.js | 6 +++++- src/index.js | 2 ++ src/lib/messages.js | 10 ++++++---- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/commands/switchChannel.js b/src/commands/switchChannel.js index 41bf69b..ded227f 100644 --- a/src/commands/switchChannel.js +++ b/src/commands/switchChannel.js @@ -6,7 +6,6 @@ const {listUsers} = require("./listUsers"); function switchChannel(input) { if (input == "") { listUsers(); - comcord.state.channelSwitch = false; return; } let target; @@ -29,6 +28,10 @@ function switchChannel(input) { comcord.state.lastChannel.set(comcord.state.currentGuild, target); listUsers(); + + const channel = guild.channels.get(comcord.state.currentChannel); + + process.title = `${guild.name} - ${channel.name} - comcord`; } } diff --git a/src/commands/switchGuild.js b/src/commands/switchGuild.js index 844a561..77b2243 100644 --- a/src/commands/switchGuild.js +++ b/src/commands/switchGuild.js @@ -32,7 +32,6 @@ function switchGuild(input) { console.log(""); } else { comcord.state.currentGuild = target; - // TODO: store last visited channel and switch to it if we've been to this guild before if (!comcord.state.lastChannel.has(target)) { const topChannel = findTopChannel(target); comcord.state.currentChannel = topChannel.id; @@ -43,6 +42,11 @@ function switchGuild(input) { listChannels(); listUsers(); + + const guild = comcord.client.guilds.get(comcord.state.currentGuild); + const channel = guild.channels.get(comcord.state.currentChannel); + + process.title = `${guild.name} - ${channel.name} - comcord`; } } diff --git a/src/index.js b/src/index.js index 1355832..9361e8f 100644 --- a/src/index.js +++ b/src/index.js @@ -3,6 +3,8 @@ const chalk = require("chalk"); const token = process.argv[2]; +process.title = "comcord"; + global.comcord = { state: { currentGuild: null, diff --git a/src/lib/messages.js b/src/lib/messages.js index b19aa1f..3685522 100644 --- a/src/lib/messages.js +++ b/src/lib/messages.js @@ -17,12 +17,14 @@ function processMessage({ const headerLength = 5 + reply.author.username.length; const length = headerLength + reply.content.length; + const replyContent = reply.content.replace(/\n/g, " "); + if (noColor) { console.log( ` \u250d [${reply.author.username}] ${ length > 79 - ? reply.content.substring(0, length - headerLength) + "\u2026" - : reply.content + ? replyContent.substring(0, length - headerLength) + "\u2026" + : replyContent }` ); } else { @@ -32,8 +34,8 @@ function processMessage({ chalk.reset( `${ length > 79 - ? reply.content.substring(0, length - headerLength) + "\u2026" - : reply.content + ? replyContent.substring(0, length - headerLength) + "\u2026" + : replyContent }` ) ); From 3017a23be73cd3769a5bfde295c0d05c0cbacc4a Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Fri, 2 Sep 2022 18:21:00 -0600 Subject: [PATCH 20/92] strip escape codes from prompts --- src/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.js b/src/index.js index 9361e8f..1ac9ac3 100644 --- a/src/index.js +++ b/src/index.js @@ -134,6 +134,7 @@ process.stdin.on("data", async function (key) { ); } } else { + key = key.replace("\u001b", ""); process.stdout.write(key); comcord.state.promptInput += key; } From 3a8d099e9e2d2495f11adc7849aa05cb2288290a Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Fri, 2 Sep 2022 18:21:25 -0600 Subject: [PATCH 21/92] timestamps every 15 minutes --- src/index.js | 50 +++++++++++++++++++++++++++++++++++++++++++++ src/lib/messages.js | 4 +++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 1ac9ac3..6d54c27 100644 --- a/src/index.js +++ b/src/index.js @@ -152,3 +152,53 @@ client.connect(); console.log("COMcord (c)left 2022"); console.log("Type 'h' for Commands"); + +const dateObj = new Date(); +let sentTime = false; + +const weekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; +const months = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", +]; + +setInterval(function () { + dateObj.setTime(Date.now()); + + const hour = dateObj.getUTCHours(), + minutes = dateObj.getUTCMinutes(), + seconds = dateObj.getUTCSeconds(), + day = dateObj.getUTCDate(), + month = dateObj.getUTCMonth(), + year = dateObj.getUTCFullYear(), + weekDay = dateObj.getUTCDay(); + + const timeString = `[${weekdays[weekDay]} ${day + .toString() + .padStart(2, "0")}-${months[month]}-${year + .toString() + .substring(2, 4)} ${hour.toString().padStart(2, "0")}:${minutes + .toString() + .padStart(2, "0")}:${seconds.toString().padStart(2, "0")}]`; + + if (minutes % 15 == 0 && seconds < 2 && !sentTime) { + if (comcord.state.inPrompt == true) { + comcord.state.messageQueue.push({time: true, content: timeString}); + } else { + console.log(timeString); + } + sentTime = true; + } else if (seconds > 2 && sentTime) { + sentTime = false; + } +}, 500); diff --git a/src/lib/messages.js b/src/lib/messages.js index 3685522..bf95132 100644 --- a/src/lib/messages.js +++ b/src/lib/messages.js @@ -87,7 +87,9 @@ function processMessage({ function processQueue() { for (const msg of comcord.state.messageQueue) { - if (msg.content.indexOf("\n") > -1) { + if (msg.time) { + console.log(msg.content); + } else if (msg.content.indexOf("\n") > -1) { const lines = msg.content.split("\n"); for (const index in lines) { const line = lines[index]; From 1b86352f693e4518a09d74554cfb35652c9cc0c6 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sun, 4 Sep 2022 10:42:35 -0600 Subject: [PATCH 22/92] noop errors from eris for things like being reconnected --- src/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.js b/src/index.js index 6d54c27..277c122 100644 --- a/src/index.js +++ b/src/index.js @@ -51,6 +51,7 @@ client.once("ready", function () { listGuilds(); }); +client.on("error", function () {}); client.on("messageCreate", function (msg) { if (msg.author.id === client.user.id) return; From 145a163e53bf9d12ebbd6aefb91f9582341110e2 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sun, 4 Sep 2022 10:44:33 -0600 Subject: [PATCH 23/92] eliminate async race condition from prompts by not nulling input/callback, prevents erroring when sending messages fast --- src/lib/prompt.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/lib/prompt.js b/src/lib/prompt.js index 0c882ed..931455d 100644 --- a/src/lib/prompt.js +++ b/src/lib/prompt.js @@ -14,9 +14,6 @@ async function finalizePrompt() { const input = comcord.state.promptInput.trim(); await comcord.state.promptCallback(input); - - comcord.state.promptInput = null; - comcord.state.promptCallback = null; } module.exports = { From 353afbc23cc6300e8c02c4eb0181c2c668f28a30 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Mon, 19 Sep 2022 13:19:31 -0600 Subject: [PATCH 24/92] fix replies not getting truncated properly --- src/lib/messages.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/messages.js b/src/lib/messages.js index bf95132..0ebc109 100644 --- a/src/lib/messages.js +++ b/src/lib/messages.js @@ -23,7 +23,7 @@ function processMessage({ console.log( ` \u250d [${reply.author.username}] ${ length > 79 - ? replyContent.substring(0, length - headerLength) + "\u2026" + ? replyContent.substring(0, 79 - headerLength) + "\u2026" : replyContent }` ); @@ -34,7 +34,7 @@ function processMessage({ chalk.reset( `${ length > 79 - ? replyContent.substring(0, length - headerLength) + "\u2026" + ? replyContent.substring(0, 79 - headerLength) + "\u2026" : replyContent }` ) From 3a7675ab6236add65f8a8769fc69267e87477b90 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Thu, 29 Sep 2022 21:28:29 -0600 Subject: [PATCH 25/92] parse mentions and emotes --- src/lib/messages.js | 80 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 2 deletions(-) diff --git a/src/lib/messages.js b/src/lib/messages.js index 0ebc109..cfe0fb6 100644 --- a/src/lib/messages.js +++ b/src/lib/messages.js @@ -1,10 +1,55 @@ const chalk = require("chalk"); +const REGEX_MENTION = /<@!?(\d+)>/g; +const REGEX_ROLE_MENTION = /<@&?(\d+)>/g; +const REGEX_CHANNEL = /<#(\d+)>/g; +const REGEX_EMOTE = /<(?:\u200b|&)?a?:(\w+):(\d+)>/g; +const REGEX_COMMAND = /<\/([^\s]+?):(\d+)>/g; + +function replaceMentions(_, id) { + const user = comcord.client.users.get(id); + if (user) { + return `@${user.username}`; + } else { + return "@Unknown User"; + } +} +function replaceRoles(_, id) { + const role = comcord.client.guilds + .get(comcord.state.currentGuild) + .roles.get(id); + if (role) { + return `[@${role.name}]`; + } else { + return "[@Unknown Role]"; + } +} +function replaceChannels(_, id) { + const guildForChannel = comcord.client.channelGuildMap[id]; + if (guildForChannel) { + const channel = comcord.client.guilds.get(guildForChannel).channels.get(id); + if (channel) { + return `#${channel.name}`; + } else { + return "#unknown-channel"; + } + } else { + return "#unknown-channel"; + } +} +function replaceEmotes(_, name, id) { + return `:${name}:`; +} +function replaceCommands(_, name, id) { + return `/${name}`; +} + function processMessage({ name, content, bot, attachments, + stickers, reply, noColor = false, }) { @@ -17,7 +62,13 @@ function processMessage({ const headerLength = 5 + reply.author.username.length; const length = headerLength + reply.content.length; - const replyContent = reply.content.replace(/\n/g, " "); + let replyContent = reply.content.replace(/\n/g, " "); + replyContent = replyContent + .replace(REGEX_MENTION, replaceMentions) + .replace(REGEX_ROLE_MENTION, replaceRoles) + .replace(REGEX_CHANNEL, replaceChannels) + .replace(REGEX_EMOTE, replaceEmotes) + .replace(REGEX_COMMAND, replaceCommands); if (noColor) { console.log( @@ -42,8 +93,15 @@ function processMessage({ } } + content = content + .replace(REGEX_MENTION, replaceMentions) + .replace(REGEX_ROLE_MENTION, replaceRoles) + .replace(REGEX_CHANNEL, replaceChannels) + .replace(REGEX_EMOTE, replaceEmotes) + .replace(REGEX_COMMAND, replaceCommands); + if ( - (content.startsWith("*") && content.endsWith("*")) || + (content.length > 1 && content.startsWith("*") && content.endsWith("*")) || (content.startsWith("_") && content.endsWith("_")) ) { if (noColor) { @@ -83,6 +141,22 @@ function processMessage({ } } } + + if (stickers) { + for (const sticker of stickers) { + if (noColor) { + console.log( + `` + ); + } else { + console.log( + chalk.bold.yellow( + `` + ) + ); + } + } + } } function processQueue() { @@ -98,6 +172,7 @@ function processQueue() { bot: msg.author.bot, content: line, attachments: index == lines.length - 1 ? msg.attachments : [], + stickers: index == lines.length - 1 ? msg.stickerItems : [], reply: index == 0 ? msg.referencedMessage : null, }); } @@ -107,6 +182,7 @@ function processQueue() { bot: msg.author.bot, content: msg.content, attachments: msg.attachments, + stickers: msg.stickerItems, reply: msg.referencedMessage, }); } From ae5f412d4bfdeebd1719bcc608d88590647b4aa5 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sat, 1 Oct 2022 13:58:15 -0600 Subject: [PATCH 26/92] implement timestamps (without moment.js) --- src/lib/messages.js | 111 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 109 insertions(+), 2 deletions(-) diff --git a/src/lib/messages.js b/src/lib/messages.js index cfe0fb6..b09597c 100644 --- a/src/lib/messages.js +++ b/src/lib/messages.js @@ -6,6 +6,108 @@ const REGEX_CHANNEL = /<#(\d+)>/g; const REGEX_EMOTE = /<(?:\u200b|&)?a?:(\w+):(\d+)>/g; const REGEX_COMMAND = /<\/([^\s]+?):(\d+)>/g; +function readableTime(time) { + const seconds = time / 1000; + const minutes = seconds / 60; + const hours = minutes / 60; + const days = hours / 24; + const weeks = days / 7; + const months = days / 30; + const years = days / 365.25; + + if (years >= 1) { + return `${years.toFixed(0)} year${years > 1 ? "s" : ""}`; + } else if (weeks > 5 && months < 13) { + return `${months.toFixed(0)} month${months > 1 ? "s" : ""}`; + } else if (days > 7 && weeks < 5) { + return `${weeks.toFixed(0)} week${weeks > 1 ? "s" : ""}`; + } else if (hours > 24 && days < 7) { + return `${days.toFixed(0)} day${days > 1 ? "s" : ""}`; + } else if (minutes > 60 && hours < 24) { + return `${hours.toFixed(0)} hour${hours > 1 ? "s" : ""}`; + } else if (seconds > 60 && minutes < 60) { + return `${minutes.toFixed(0)} minute${minutes > 1 ? "s" : ""}`; + } else { + return `${seconds.toFixed(0)} second${seconds > 1 ? "s" : ""}`; + } +} + +const MONTH_NAMES = [ + "January", + "Feburary", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", +]; +const DAY_NAMES = [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", +]; +const TIME_FORMATS = { + t: function (time) { + const timeObj = new Date(time); + return timeObj.getUTCHours() + 1 + ":" + timeObj.getUTCMinutes(); + }, + T: function (time) { + const timeObj = new Date(time); + return TIME_FORMATS.t(time) + ":" + timeObj.getUTCSeconds(); + }, + d: function (time) { + const timeObj = new Date(time); + return ( + timeObj.getUTCFullYear() + + "/" + + (timeObj.getUTCMonth() + 1).toString().padStart(2, "0") + + "/" + + timeObj.getUTCDate().toString().padStart(2, "0") + ); + }, + D: function (time) { + const timeObj = new Date(time); + return ( + timeObj.getUTCDate() + + " " + + MONTH_NAMES[timeObj.getUTCMonth()] + + " " + + timeObj.getUTCFullYear() + ); + }, + f: function (time) { + return TIME_FORMATS.D(time) + " " + TIME_FORMATS.t(time); + }, + F: function (time) { + const timeObj = new Date(time); + return DAY_NAMES[timeObj.getUTCDay()] + ", " + TIME_FORMATS.f(time); + }, + R: function (time) { + const now = Date.now(); + + if (time > now) { + const delta = time - now; + return "in " + readableTime(delta); + } else { + const delta = now - time; + return readableTime(delta) + " ago"; + } + }, +}; +const REGEX_TIMESTAMP = new RegExp( + ``, + "g" +); + function replaceMentions(_, id) { const user = comcord.client.users.get(id); if (user) { @@ -43,6 +145,9 @@ function replaceEmotes(_, name, id) { function replaceCommands(_, name, id) { return `/${name}`; } +function replaceTimestamps(_, time, format = "f") { + return TIME_FORMATS[format](time * 1000); +} function processMessage({ name, @@ -68,7 +173,8 @@ function processMessage({ .replace(REGEX_ROLE_MENTION, replaceRoles) .replace(REGEX_CHANNEL, replaceChannels) .replace(REGEX_EMOTE, replaceEmotes) - .replace(REGEX_COMMAND, replaceCommands); + .replace(REGEX_COMMAND, replaceCommands) + .replace(REGEX_TIMESTAMP, replaceTimestamps); if (noColor) { console.log( @@ -98,7 +204,8 @@ function processMessage({ .replace(REGEX_ROLE_MENTION, replaceRoles) .replace(REGEX_CHANNEL, replaceChannels) .replace(REGEX_EMOTE, replaceEmotes) - .replace(REGEX_COMMAND, replaceCommands); + .replace(REGEX_COMMAND, replaceCommands) + .replace(REGEX_TIMESTAMP, replaceTimestamps); if ( (content.length > 1 && content.startsWith("*") && content.endsWith("*")) || From 1ec0eb8a6bdba54ef29025fb3f5bf720aeff60d8 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sun, 2 Oct 2022 10:17:39 -0600 Subject: [PATCH 27/92] process codeblocks as dumps, minor internal message processing changes --- src/commands/history.js | 28 +------ src/index.js | 44 +---------- src/lib/messages.js | 167 ++++++++++++++++++++++++++++------------ 3 files changed, 120 insertions(+), 119 deletions(-) diff --git a/src/commands/history.js b/src/commands/history.js index b22b52b..343b46a 100644 --- a/src/commands/history.js +++ b/src/commands/history.js @@ -17,33 +17,7 @@ async function getHistory(limit = 20) { console.log("--Beginning-Review".padEnd(72, "-")); for (const msg of messages) { - if (msg.content.indexOf("\n") > -1) { - const lines = msg.content.split("\n"); - for (const index in lines) { - const line = lines[index]; - processMessage({ - name: msg.author.username, - bot: msg.author.bot, - content: - line + - (msg.editedTimestamp != null && index == lines.length - 1 - ? " (edited)" - : ""), - attachments: index == lines.length - 1 ? msg.attachments : null, - reply: index == 0 ? msg.referencedMessage : null, - noColor: true, - }); - } - } else { - processMessage({ - name: msg.author.username, - bot: msg.author.bot, - content: msg.content + (msg.editedTimestamp != null ? " (edited)" : ""), - attachments: msg.attachments, - reply: msg.referencedMessage, - noColor: true, - }); - } + processMessage(msg, {noColor: true, history: true}); } console.log("--Review-Complete".padEnd(73, "-")); diff --git a/src/index.js b/src/index.js index 277c122..a5c0de4 100644 --- a/src/index.js +++ b/src/index.js @@ -60,27 +60,7 @@ client.on("messageCreate", function (msg) { if (comcord.state.inPrompt) { comcord.state.messageQueue.push(msg); } else { - if (msg.content.indexOf("\n") > -1) { - const lines = msg.content.split("\n"); - for (const index in lines) { - const line = lines[index]; - processMessage({ - name: msg.author.username, - bot: msg.author.bot, - content: line, - attachments: index == lines.length - 1 ? msg.attachments : [], - reply: index == 0 ? msg.referencedMessage : null, - }); - } - } else { - processMessage({ - name: msg.author.username, - bot: msg.author.bot, - content: msg.content, - attachments: msg.attachments, - reply: msg.referencedMessage, - }); - } + processMessage(msg); } } }); @@ -93,27 +73,7 @@ client.on("messageUpdate", function (msg, old) { if (comcord.state.inPrompt) { comcord.state.messageQueue.push(msg); } else { - if (msg.content.indexOf("\n") > -1) { - const lines = msg.content.split("\n"); - for (const index in lines) { - const line = lines[index]; - processMessage({ - name: msg.author.username, - bot: msg.author.bot, - content: line + (index == lines.length - 1 ? " (edited)" : ""), - attachments: index == lines.length - 1 ? msg.attachments : [], - reply: index == 0 ? msg.referencedMessage : null, - }); - } - } else { - processMessage({ - name: msg.author.username, - bot: msg.author.bot, - content: msg.content + " (edited)", - attachments: msg.attachments, - reply: msg.referencedMessage, - }); - } + processMessage(msg); } } }); diff --git a/src/lib/messages.js b/src/lib/messages.js index b09597c..296cfd8 100644 --- a/src/lib/messages.js +++ b/src/lib/messages.js @@ -1,5 +1,9 @@ const chalk = require("chalk"); +const REGEX_CODEBLOCK = /```(?:([a-z0-9_+\-.]+?)\n)?\n*([^\n][^]*?)\n*```/i; +const REGEX_CODEBLOCK_GLOBAL = + /```(?:([a-z0-9_+\-.]+?)\n)?\n*([^\n][^]*?)\n*```/gi; + const REGEX_MENTION = /<@!?(\d+)>/g; const REGEX_ROLE_MENTION = /<@&?(\d+)>/g; const REGEX_CHANNEL = /<#(\d+)>/g; @@ -149,7 +153,7 @@ function replaceTimestamps(_, time, format = "f") { return TIME_FORMATS[format](time * 1000); } -function processMessage({ +function formatMessage({ name, content, bot, @@ -157,6 +161,8 @@ function processMessage({ stickers, reply, noColor = false, + dump = false, + history = false, }) { if (name.length + 2 > comcord.state.nameLength) comcord.state.nameLength = name.length + 2; @@ -165,7 +171,6 @@ function processMessage({ const nameColor = reply.author.bot ? chalk.bold.yellow : chalk.bold.cyan; const headerLength = 5 + reply.author.username.length; - const length = headerLength + reply.content.length; let replyContent = reply.content.replace(/\n/g, " "); replyContent = replyContent @@ -175,6 +180,14 @@ function processMessage({ .replace(REGEX_EMOTE, replaceEmotes) .replace(REGEX_COMMAND, replaceCommands) .replace(REGEX_TIMESTAMP, replaceTimestamps); + if (reply.attachments.length > 0) { + replyContent += `<${reply.attachments.length} attachment${ + reply.attachments.length > 1 ? "s" : "" + }>`; + replyContent = replyContent.trim(); + } + + const length = headerLength + replyContent.length; if (noColor) { console.log( @@ -199,43 +212,74 @@ function processMessage({ } } - content = content - .replace(REGEX_MENTION, replaceMentions) - .replace(REGEX_ROLE_MENTION, replaceRoles) - .replace(REGEX_CHANNEL, replaceChannels) - .replace(REGEX_EMOTE, replaceEmotes) - .replace(REGEX_COMMAND, replaceCommands) - .replace(REGEX_TIMESTAMP, replaceTimestamps); - - if ( - (content.length > 1 && content.startsWith("*") && content.endsWith("*")) || - (content.startsWith("_") && content.endsWith("_")) - ) { - if (noColor) { - console.log(`<${name} ${content.substring(1, content.length - 1)}>`); + if (dump) { + if (history) { + const headerLength = 80 - (name.length + 5); + console.log(`--- ${name} ${"-".repeat(headerLength)}`); + console.log(content); + console.log(`--- ${name} ${"-".repeat(headerLength)}`); } else { - console.log( - chalk.bold.green( - `<${name} ${content.substring(1, content.length - 1)}>` - ) - ); + const wordCount = content.split(" ").length; + const lineCount = content.split("\n").length; + if (noColor) { + console.log( + `<${name} DUMPs in ${content.length} characters of ${wordCount} word${ + wordCount > 1 ? "s" : "" + } in ${lineCount} line${lineCount > 1 ? "s" : ""}>` + ); + } else { + console.log( + chalk.bold.yellow( + `<${name} DUMPs in ${ + content.length + } characters of ${wordCount} word${ + wordCount > 1 ? "s" : "" + } in ${lineCount} line${lineCount > 1 ? "s" : ""}>` + ) + ); + } } } else { - if (noColor) { - console.log( - `[${name}]${" ".repeat( - Math.abs(comcord.state.nameLength - (name.length + 2)) - )} ${content}` - ); - } else { - const nameColor = bot ? chalk.bold.yellow : chalk.bold.cyan; + content = content + .replace(REGEX_MENTION, replaceMentions) + .replace(REGEX_ROLE_MENTION, replaceRoles) + .replace(REGEX_CHANNEL, replaceChannels) + .replace(REGEX_EMOTE, replaceEmotes) + .replace(REGEX_COMMAND, replaceCommands) + .replace(REGEX_TIMESTAMP, replaceTimestamps); - // TODO: markdown - console.log( - nameColor(`[${name}]`) + - " ".repeat(Math.abs(comcord.state.nameLength - (name.length + 2))) + - chalk.reset(" " + content) - ); + if ( + (content.length > 1 && + content.startsWith("*") && + content.endsWith("*")) || + (content.startsWith("_") && content.endsWith("_")) + ) { + if (noColor) { + console.log(`<${name} ${content.substring(1, content.length - 1)}>`); + } else { + console.log( + chalk.bold.green( + `<${name} ${content.substring(1, content.length - 1)}>` + ) + ); + } + } else { + if (noColor) { + console.log( + `[${name}]${" ".repeat( + Math.abs(comcord.state.nameLength - (name.length + 2)) + )} ${content}` + ); + } else { + const nameColor = bot ? chalk.bold.yellow : chalk.bold.cyan; + + // TODO: markdown + console.log( + nameColor(`[${name}]`) + + " ".repeat(Math.abs(comcord.state.nameLength - (name.length + 2))) + + chalk.reset(" " + content) + ); + } } } @@ -266,33 +310,55 @@ function processMessage({ } } -function processQueue() { - for (const msg of comcord.state.messageQueue) { - if (msg.time) { - console.log(msg.content); - } else if (msg.content.indexOf("\n") > -1) { +function processMessage(msg, options) { + if (msg.time) { + console.log(msg.content); + } else if (msg.content.indexOf("\n") > -1) { + if (msg.content.match(REGEX_CODEBLOCK)) { + formatMessage({ + name: msg.author.username, + bot: msg.author.bot, + content: msg.content.replace( + REGEX_CODEBLOCK_GLOBAL, + (_, lang, content) => content + ), + attachments: msg.attachments, + stickers: msg.stickerItems, + reply: msg.referencedMessage, + dump: true, + ...options, + }); + } else { const lines = msg.content.split("\n"); for (const index in lines) { const line = lines[index]; - processMessage({ + formatMessage({ name: msg.author.username, bot: msg.author.bot, content: line, attachments: index == lines.length - 1 ? msg.attachments : [], stickers: index == lines.length - 1 ? msg.stickerItems : [], reply: index == 0 ? msg.referencedMessage : null, + ...options, }); } - } else { - processMessage({ - name: msg.author.username, - bot: msg.author.bot, - content: msg.content, - attachments: msg.attachments, - stickers: msg.stickerItems, - reply: msg.referencedMessage, - }); } + } else { + formatMessage({ + name: msg.author.username, + bot: msg.author.bot, + content: msg.content, + attachments: msg.attachments, + stickers: msg.stickerItems, + reply: msg.referencedMessage, + ...options, + }); + } +} + +function processQueue() { + for (const msg of comcord.state.messageQueue) { + processMessage(msg); } comcord.state.messageQueue.splice(0, comcord.state.messageQueue.length); @@ -301,4 +367,5 @@ function processQueue() { module.exports = { processMessage, processQueue, + formatMessage, }; From 5cf3ef01efc579fbe22ee263f0690d138523b223 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sun, 2 Oct 2022 10:25:59 -0600 Subject: [PATCH 28/92] afk command --- src/commands/afk.js | 15 +++++++++++++++ src/commands/send.js | 7 +++++++ src/index.js | 13 +++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 src/commands/afk.js diff --git a/src/commands/afk.js b/src/commands/afk.js new file mode 100644 index 0000000..565e793 --- /dev/null +++ b/src/commands/afk.js @@ -0,0 +1,15 @@ +const {addCommand} = require("../lib/command"); + +addCommand("A", "toggles AFK mode", function () { + if (comcord.state.afk == true) { + comcord.state.afk = false; + comcord.client.editStatus("online"); + comcord.client.editAFK(false); + console.log(""); + } else { + comcord.state.afk = true; + comcord.client.editStatus("idle"); + comcord.client.editAFK(true); + console.log(""); + } +}); diff --git a/src/commands/send.js b/src/commands/send.js index b067c17..b53d96c 100644 --- a/src/commands/send.js +++ b/src/commands/send.js @@ -24,6 +24,13 @@ function sendMode() { comcord.state.currentChannel, input ); + + if (comcord.state.afk == true) { + comcord.state.afk = false; + comcord.client.editStatus("online"); + comcord.client.editAFK(false); + console.log(""); + } } catch (err) { console.log(""); } diff --git a/src/index.js b/src/index.js index a5c0de4..284fc9d 100644 --- a/src/index.js +++ b/src/index.js @@ -7,12 +7,14 @@ process.title = "comcord"; global.comcord = { state: { + startTime: Date.now(), currentGuild: null, currentChannel: null, nameLength: 2, inPrompt: false, messageQueue: [], lastChannel: new Map(), + afk: false, }, commands: {}, }; @@ -35,6 +37,7 @@ const {listGuilds} = require("./commands/listGuilds"); require("./commands/switchGuild"); // loads listChannels and listUsers require("./commands/switchChannel"); //loads listUsers require("./commands/history"); // includes extended history +require("./commands/afk"); process.stdin.setRawMode(true); process.stdin.resume(); @@ -50,6 +53,16 @@ client.once("ready", function () { comcord.state.nameLength = client.user.username.length + 2; listGuilds(); + + client.editStatus("online", [ + { + application_id: "1026163285877325874", + name: "comcord", + timestamps: { + start: comcord.state.startTime, + }, + }, + ]); }); client.on("error", function () {}); From 1eeaec78e3aa8f13d2f32959712f5dc2b921307e Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sun, 2 Oct 2022 12:11:08 -0600 Subject: [PATCH 29/92] rich presence for the hell of it --- package.json | 1 + pnpm-lock.yaml | 83 +++++++++++++++++++++++++++++++++++ src/commands/afk.js | 3 ++ src/commands/send.js | 3 ++ src/commands/switchChannel.js | 3 ++ src/commands/switchGuild.js | 3 ++ src/index.js | 19 +++++++- src/lib/presence.js | 36 +++++++++++++++ 8 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 src/lib/presence.js diff --git a/package.json b/package.json index b09e63d..a35a080 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "license": "MIT", "dependencies": { "chalk": "4.1.2", + "discord-rpc": "^4.0.1", "eris": "github:abalabahaha/eris#dev" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 353a918..fd57a7f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,10 +2,12 @@ lockfileVersion: 5.4 specifiers: chalk: 4.1.2 + discord-rpc: ^4.0.1 eris: github:abalabahaha/eris#dev dependencies: chalk: 4.1.2 + discord-rpc: 4.0.1 eris: github.com/abalabahaha/eris/eb403730855714eafa36c541dbe2cb84c9979158 packages: @@ -17,6 +19,13 @@ packages: color-convert: 2.0.1 dev: false + /bindings/1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + dependencies: + file-uri-to-path: 1.0.0 + dev: false + optional: true + /chalk/4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -36,11 +45,46 @@ packages: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} dev: false + /discord-rpc/4.0.1: + resolution: {integrity: sha512-HOvHpbq5STRZJjQIBzwoKnQ0jHplbEWFWlPDwXXKm/bILh4nzjcg7mNqll0UY7RsjFoaXA7e/oYb/4lvpda2zA==} + dependencies: + node-fetch: 2.6.7 + ws: 7.5.9 + optionalDependencies: + register-scheme: github.com/devsnek/node-register-scheme/e7cc9a63a1f512565da44cb57316d9fb10750e17 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + dev: false + + /file-uri-to-path/1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + dev: false + optional: true + /has-flag/4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} dev: false + /node-addon-api/1.7.2: + resolution: {integrity: sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==} + dev: false + optional: true + + /node-fetch/2.6.7: + resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 + dev: false + /opusscript/0.0.8: resolution: {integrity: sha512-VSTi1aWFuCkRCVq+tx/BQ5q9fMnQ9pVZ3JU4UHKqTkf0ED3fKEPdr+gKAAl3IA2hj9rrP6iyq3hlcJq3HELtNQ==} requiresBuild: true @@ -54,12 +98,40 @@ packages: has-flag: 4.0.0 dev: false + /tr46/0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + dev: false + /tweetnacl/1.0.3: resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==} requiresBuild: true dev: false optional: true + /webidl-conversions/3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + dev: false + + /whatwg-url/5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + dev: false + + /ws/7.5.9: + resolution: {integrity: sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: false + /ws/8.8.1: resolution: {integrity: sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==} engines: {node: '>=10.0.0'} @@ -87,3 +159,14 @@ packages: - bufferutil - utf-8-validate dev: false + + github.com/devsnek/node-register-scheme/e7cc9a63a1f512565da44cb57316d9fb10750e17: + resolution: {tarball: https://codeload.github.com/devsnek/node-register-scheme/tar.gz/e7cc9a63a1f512565da44cb57316d9fb10750e17} + name: register-scheme + version: 0.0.2 + requiresBuild: true + dependencies: + bindings: 1.5.0 + node-addon-api: 1.7.2 + dev: false + optional: true diff --git a/src/commands/afk.js b/src/commands/afk.js index 565e793..88931d0 100644 --- a/src/commands/afk.js +++ b/src/commands/afk.js @@ -1,4 +1,5 @@ const {addCommand} = require("../lib/command"); +const {updatePresence} = require("../lib/presence"); addCommand("A", "toggles AFK mode", function () { if (comcord.state.afk == true) { @@ -12,4 +13,6 @@ addCommand("A", "toggles AFK mode", function () { comcord.client.editAFK(true); console.log(""); } + + updatePresence(); }); diff --git a/src/commands/send.js b/src/commands/send.js index b53d96c..2402a5e 100644 --- a/src/commands/send.js +++ b/src/commands/send.js @@ -1,6 +1,7 @@ const chalk = require("chalk"); const {startPrompt} = require("../lib/prompt"); +const {updatePresence} = require("../lib/presence"); function sendMode() { if (!comcord.state.currentChannel) { @@ -30,6 +31,8 @@ function sendMode() { comcord.client.editStatus("online"); comcord.client.editAFK(false); console.log(""); + + updatePresence(); } } catch (err) { console.log(""); diff --git a/src/commands/switchChannel.js b/src/commands/switchChannel.js index ded227f..b59f1ab 100644 --- a/src/commands/switchChannel.js +++ b/src/commands/switchChannel.js @@ -1,5 +1,6 @@ const {addCommand} = require("../lib/command"); const {startPrompt} = require("../lib/prompt"); +const {updatePresence} = require("../lib/presence"); const {listUsers} = require("./listUsers"); @@ -32,6 +33,8 @@ function switchChannel(input) { const channel = guild.channels.get(comcord.state.currentChannel); process.title = `${guild.name} - ${channel.name} - comcord`; + + updatePresence(); } } diff --git a/src/commands/switchGuild.js b/src/commands/switchGuild.js index 77b2243..542b954 100644 --- a/src/commands/switchGuild.js +++ b/src/commands/switchGuild.js @@ -1,5 +1,6 @@ const {addCommand} = require("../lib/command"); const {startPrompt} = require("../lib/prompt"); +const {updatePresence} = require("../lib/presence"); const {listChannels} = require("./listChannels"); const {listUsers} = require("./listUsers"); @@ -47,6 +48,8 @@ function switchGuild(input) { const channel = guild.channels.get(comcord.state.currentChannel); process.title = `${guild.name} - ${channel.name} - comcord`; + + updatePresence(); } } diff --git a/src/index.js b/src/index.js index 284fc9d..ea9c917 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,8 @@ const Eris = require("eris"); const chalk = require("chalk"); +const DiscordRPC = require("discord-rpc"); + +const CLIENT_ID = "1026163285877325874"; const token = process.argv[2]; @@ -24,9 +27,12 @@ const client = new Eris("Bot " + token, { intents: Eris.Constants.Intents.all, }); comcord.client = client; +const rpc = new DiscordRPC.Client({transport: "ipc"}); +comcord.rpc = rpc; const {finalizePrompt} = require("./lib/prompt"); const {processMessage, processQueue} = require("./lib/messages"); +const {updatePresence} = require("./lib/presence"); require("./commands/quit"); require("./commands/clear"); @@ -56,16 +62,27 @@ client.once("ready", function () { client.editStatus("online", [ { - application_id: "1026163285877325874", + application_id: CLIENT_ID, name: "comcord", timestamps: { start: comcord.state.startTime, }, }, ]); + + rpc + .login({ + clientId: CLIENT_ID, + }) + .catch(function () {}); }); client.on("error", function () {}); +rpc.on("ready", function () { + updatePresence(); +}); +rpc.on("error", function () {}); + client.on("messageCreate", function (msg) { if (msg.author.id === client.user.id) return; diff --git a/src/lib/presence.js b/src/lib/presence.js new file mode 100644 index 0000000..3aac056 --- /dev/null +++ b/src/lib/presence.js @@ -0,0 +1,36 @@ +function updatePresence() { + let guild, channel; + if (comcord.state.currentGuild != null) { + guild = comcord.client.guilds.get(comcord.state.currentGuild); + } + if (comcord.state.currentChannel != null && guild != null) { + channel = guild.channels.get(comcord.state.currentChannel); + } + + try { + const activity = { + startTimestamp: comcord.state.startTime, + smallImageKey: `https://cdn.discordapp.com/avatars/${comcord.client.user.id}/${comcord.client.user.avatar}.png?size=1024`, + smallImageText: `${comcord.client.user.username}#${comcord.client.user.discriminator}`, + buttons: [ + {label: "comcord Repo", url: "https://github.com/Cynosphere/comcord"}, + ], + }; + + if (guild != null) { + activity.largeImageKey = `https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}.png?size=1024`; + activity.largeImageText = guild.name; + if (channel != null) { + activity.details = `#${channel.name} - ${guild.name}`; + } + } + if (comcord.state.afk == true) { + activity.state = "AFK"; + } + comcord.rpc.setActivity(activity); + } catch (err) { + // + } +} + +module.exports = {updatePresence}; From 9dc3f1276ded39ea506be8c8ddce2c78abe76476 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sun, 2 Oct 2022 13:17:31 -0600 Subject: [PATCH 30/92] add reconnect logic to rich presence --- src/index.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index ea9c917..ebab8bb 100644 --- a/src/index.js +++ b/src/index.js @@ -78,9 +78,18 @@ client.once("ready", function () { }); client.on("error", function () {}); -rpc.on("ready", function () { +rpc.on("connected", function () { updatePresence(); }); +rpc.once("ready", function () { + rpc.transport.on("close", async function () { + try { + await rpc.transport.connect(); + } catch (err) { + rpc.transport.emit("close"); + } + }); +}); rpc.on("error", function () {}); client.on("messageCreate", function (msg) { From 6b90fbd8d4c686e5784ca6e0cec109d6c1f934ed Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sun, 2 Oct 2022 16:32:04 -0600 Subject: [PATCH 31/92] fix edits not being marked --- src/lib/messages.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib/messages.js b/src/lib/messages.js index 296cfd8..55c035d 100644 --- a/src/lib/messages.js +++ b/src/lib/messages.js @@ -335,7 +335,11 @@ function processMessage(msg, options) { formatMessage({ name: msg.author.username, bot: msg.author.bot, - content: line, + content: + line + + (msg.editedTimestamp != null && index == lines.length - 1 + ? " (edited)" + : ""), attachments: index == lines.length - 1 ? msg.attachments : [], stickers: index == lines.length - 1 ? msg.stickerItems : [], reply: index == 0 ? msg.referencedMessage : null, @@ -347,7 +351,7 @@ function processMessage(msg, options) { formatMessage({ name: msg.author.username, bot: msg.author.bot, - content: msg.content, + content: msg.content + (msg.editedTimestamp != null ? " (edited)" : ""), attachments: msg.attachments, stickers: msg.stickerItems, reply: msg.referencedMessage, From 9d0588d9a919fd25b8a2ebef9f1ebc09ec7aa8aa Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sun, 9 Oct 2022 13:36:59 -0600 Subject: [PATCH 32/92] better rich presence retry logic --- src/index.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/index.js b/src/index.js index ebab8bb..1d3f4ed 100644 --- a/src/index.js +++ b/src/index.js @@ -81,12 +81,22 @@ client.on("error", function () {}); rpc.on("connected", function () { updatePresence(); }); +let retryingRPC = false; rpc.once("ready", function () { - rpc.transport.on("close", async function () { - try { - await rpc.transport.connect(); - } catch (err) { - rpc.transport.emit("close"); + rpc.transport.on("close", function () { + if (!retryingRPC) { + retryingRPC = true; + setTimeout(function () { + rpc.transport + .connect() + .then(() => { + retryingRPC = false; + }) + .catch((err) => { + retryingRPC = false; + rpc.transport.emit("close"); + }); + }, 5000); } }); }); From 7a27a7f1b9bdaaa9fca4228e1efeaba891143c13 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sun, 9 Oct 2022 14:39:15 -0600 Subject: [PATCH 33/92] move from eris to oceanic --- README.md | 12 ++-- package.json | 2 +- pnpm-lock.yaml | 120 +++++++++++++++++++++++++++-------- src/commands/afk.js | 5 +- src/commands/emote.js | 8 +-- src/commands/history.js | 8 +-- src/commands/listChannels.js | 21 ++---- src/commands/listGuilds.js | 3 +- src/commands/listUsers.js | 8 +-- src/commands/send.js | 8 +-- src/index.js | 34 +++++----- src/lib/messages.js | 4 +- 12 files changed, 148 insertions(+), 85 deletions(-) diff --git a/README.md b/README.md index aaaf716..8330e0d 100644 --- a/README.md +++ b/README.md @@ -10,14 +10,15 @@ A CLI-based client for Discord inspired by [SDF](https://sdf.org)'s [commode](ht 2. `node src/index.js ` Currently only bot accounts are supported, and that is unlikely to change anytime soon. -Eris has a lot of user-only endpoints implemented, but it would require hacking apart Eris to do the things nessicary to spoof being the actual client. +~~Eris has a lot of user-only endpoints implemented, but it would require hacking apart Eris to do the things nessicary to spoof being the actual client.~~ +(I have no idea currently what the state of user support in Oceanic is) I also don't want to give skids an easy point of reference of how to spoof the client. :^) You **MUST** grant your bot all Privileged Gateway Intents. ## Design Decisions * Node.js was chosen currently due to familiarity. -* Eris was chosen due to familiarity and the nature of everything not being abstracted out to 200 different classes unlike discord.js. +* ~~Eris~~ Oceanic was chosen due to familiarity and the nature of everything not being abstracted out to 200 different classes unlike discord.js. * "Jank" by design. While I don't expect anyone to actually use comcord on serial terminals or teletypes other than for meme factor, the option is still there. ## TODO @@ -39,11 +40,12 @@ You **MUST** grant your bot all Privileged Gateway Intents. - [ ] Surf channels forwards (>) - [ ] Surf channels backwards (<) - [x] Message Receiving - - [ ] Markdown styling + - [x] Markdown styling - [ ] Common markdown (bold, italic, etc) - [ ] Figure out how spoilers would work - - [ ] Emotes????? - - [ ] Timestamp parsing + - [x] Emotes????? + - [x] Timestamp parsing + - [x] Mentions parsing - [ ] Embeds in the style of commode's posted links - [x] Messages wrapped in `*`'s or `_`'s parsed as emotes - [ ] Inline DMs to replicate commode's private messages diff --git a/package.json b/package.json index a35a080..923a5ad 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,6 @@ "dependencies": { "chalk": "4.1.2", "discord-rpc": "^4.0.1", - "eris": "github:abalabahaha/eris#dev" + "oceanic.js": "^1.1.2" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fd57a7f..274b347 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3,15 +3,47 @@ lockfileVersion: 5.4 specifiers: chalk: 4.1.2 discord-rpc: ^4.0.1 - eris: github:abalabahaha/eris#dev + oceanic.js: ^1.1.2 dependencies: chalk: 4.1.2 discord-rpc: 4.0.1 - eris: github.com/abalabahaha/eris/eb403730855714eafa36c541dbe2cb84c9979158 + oceanic.js: 1.1.2 packages: + /@discordjs/voice/0.11.0: + resolution: {integrity: sha512-6+9cj1dxzBJm7WJ9qyG2XZZQ8rcLl6x2caW0C0OxuTtMLAaEDntpb6lqMTFiBg/rDc4Rd59g1w0gJmib33CuHw==} + engines: {node: '>=16.9.0'} + requiresBuild: true + dependencies: + '@types/ws': 8.5.3 + discord-api-types: 0.36.3 + prism-media: 1.3.4 + tslib: 2.4.0 + ws: 8.9.0 + transitivePeerDependencies: + - '@discordjs/opus' + - bufferutil + - ffmpeg-static + - node-opus + - opusscript + - utf-8-validate + dev: false + optional: true + + /@types/node/18.8.3: + resolution: {integrity: sha512-0os9vz6BpGwxGe9LOhgP/ncvYN5Tx1fNcd2TM3rD/aCGBkysb+ZWpXEocG24h6ZzOi13+VB8HndAQFezsSOw1w==} + dev: false + optional: true + + /@types/ws/8.5.3: + resolution: {integrity: sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==} + dependencies: + '@types/node': 18.8.3 + dev: false + optional: true + /ansi-styles/4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -26,6 +58,13 @@ packages: dev: false optional: true + /busboy/1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + dependencies: + streamsearch: 1.1.0 + dev: false + /chalk/4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -45,6 +84,11 @@ packages: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} dev: false + /discord-api-types/0.36.3: + resolution: {integrity: sha512-bz/NDyG0KBo/tY14vSkrwQ/n3HKPf87a0WFW/1M9+tXYK+vp5Z5EksawfCWo2zkAc6o7CClc0eff1Pjrqznlwg==} + dev: false + optional: true + /discord-rpc/4.0.1: resolution: {integrity: sha512-HOvHpbq5STRZJjQIBzwoKnQ0jHplbEWFWlPDwXXKm/bILh4nzjcg7mNqll0UY7RsjFoaXA7e/oYb/4lvpda2zA==} dependencies: @@ -85,12 +129,47 @@ packages: whatwg-url: 5.0.0 dev: false - /opusscript/0.0.8: - resolution: {integrity: sha512-VSTi1aWFuCkRCVq+tx/BQ5q9fMnQ9pVZ3JU4UHKqTkf0ED3fKEPdr+gKAAl3IA2hj9rrP6iyq3hlcJq3HELtNQ==} - requiresBuild: true + /oceanic.js/1.1.2: + resolution: {integrity: sha512-aABMK2UERHvyiwjWFx5m5ZZY7oPUHmVSOhICadZh/vqyxvGf48p+aqGlRu9bEtN6XZYPZJecQi/9IPN+phXn1Q==} + engines: {node: '>=16.16.0'} + dependencies: + undici: 5.11.0 + ws: 8.9.0 + optionalDependencies: + '@discordjs/voice': 0.11.0 + transitivePeerDependencies: + - '@discordjs/opus' + - bufferutil + - ffmpeg-static + - node-opus + - opusscript + - utf-8-validate + dev: false + + /prism-media/1.3.4: + resolution: {integrity: sha512-eW7LXORkTCQznZs+eqe9VjGOrLBxcBPXgNyHXMTSRVhphvd/RrxgIR7WaWt4fkLuhshcdT5KHL88LAfcvS3f5g==} + peerDependencies: + '@discordjs/opus': ^0.8.0 + ffmpeg-static: ^5.0.2 || ^4.2.7 || ^3.0.0 || ^2.4.0 + node-opus: ^0.3.3 + opusscript: ^0.0.8 + peerDependenciesMeta: + '@discordjs/opus': + optional: true + ffmpeg-static: + optional: true + node-opus: + optional: true + opusscript: + optional: true dev: false optional: true + /streamsearch/1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + dev: false + /supports-color/7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -102,12 +181,18 @@ packages: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} dev: false - /tweetnacl/1.0.3: - resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==} - requiresBuild: true + /tslib/2.4.0: + resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} dev: false optional: true + /undici/5.11.0: + resolution: {integrity: sha512-oWjWJHzFet0Ow4YZBkyiJwiK5vWqEYoH7BINzJAJOLedZ++JpAlCbUktW2GQ2DS2FpKmxD/JMtWUUWl1BtghGw==} + engines: {node: '>=12.18'} + dependencies: + busboy: 1.6.0 + dev: false + /webidl-conversions/3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} dev: false @@ -132,8 +217,8 @@ packages: optional: true dev: false - /ws/8.8.1: - resolution: {integrity: sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==} + /ws/8.9.0: + resolution: {integrity: sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -145,21 +230,6 @@ packages: optional: true dev: false - github.com/abalabahaha/eris/eb403730855714eafa36c541dbe2cb84c9979158: - resolution: {tarball: https://codeload.github.com/abalabahaha/eris/tar.gz/eb403730855714eafa36c541dbe2cb84c9979158} - name: eris - version: 0.17.2-dev - engines: {node: '>=10.4.0'} - dependencies: - ws: 8.8.1 - optionalDependencies: - opusscript: 0.0.8 - tweetnacl: 1.0.3 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - dev: false - github.com/devsnek/node-register-scheme/e7cc9a63a1f512565da44cb57316d9fb10750e17: resolution: {tarball: https://codeload.github.com/devsnek/node-register-scheme/tar.gz/e7cc9a63a1f512565da44cb57316d9fb10750e17} name: register-scheme diff --git a/src/commands/afk.js b/src/commands/afk.js index 88931d0..7a3e396 100644 --- a/src/commands/afk.js +++ b/src/commands/afk.js @@ -3,14 +3,13 @@ const {updatePresence} = require("../lib/presence"); addCommand("A", "toggles AFK mode", function () { if (comcord.state.afk == true) { - comcord.state.afk = false; + comcord.client.shards.forEach((shard) => (shard.presence.afk = false)); comcord.client.editStatus("online"); - comcord.client.editAFK(false); console.log(""); } else { comcord.state.afk = true; + comcord.client.shards.forEach((shard) => (shard.presence.afk = true)); comcord.client.editStatus("idle"); - comcord.client.editAFK(true); console.log(""); } diff --git a/src/commands/emote.js b/src/commands/emote.js index aef4b7d..d909c6d 100644 --- a/src/commands/emote.js +++ b/src/commands/emote.js @@ -13,10 +13,10 @@ addCommand("e", "emote", function () { } else { try { process.stdout.write("\n"); - await comcord.client.createMessage( - comcord.state.currentChannel, - `*${input}*` - ); + await comcord.client.guilds + .get(comcord.state.currentGuild) + .channels.get(comcord.state.currentChannel) + .createMessage({content: `*${input}*`}); console.log(`<${comcord.client.user.username} ${input}>`); } catch (err) { console.log(""); diff --git a/src/commands/history.js b/src/commands/history.js index 343b46a..14d123a 100644 --- a/src/commands/history.js +++ b/src/commands/history.js @@ -8,10 +8,10 @@ async function getHistory(limit = 20) { return; } - const messages = await comcord.client.getMessages( - comcord.state.currentChannel, - {limit} - ); + const messages = await comcord.client.guilds + .get(comcord.state.currentGuild) + .channels.get(comcord.state.currentChannel) + .getMessages({limit}); messages.reverse(); console.log("--Beginning-Review".padEnd(72, "-")); diff --git a/src/commands/listChannels.js b/src/commands/listChannels.js index 7d12c3e..7c18345 100644 --- a/src/commands/listChannels.js +++ b/src/commands/listChannels.js @@ -1,5 +1,3 @@ -const Eris = require("eris"); - const {addCommand} = require("../lib/command"); function listChannels() { @@ -9,34 +7,26 @@ function listChannels() { } let longest = 0; - let longestTopic = 0; const guild = comcord.client.guilds.get(comcord.state.currentGuild); const channels = [...guild.channels.values()].filter((c) => c.type == 0); channels.sort((a, b) => a.position - b.position); for (const channel of channels) { const perms = channel.permissionsOf(comcord.client.user.id); - const private = !perms.has(Eris.Constants.Permissions.readMessages); + const private = !perms.has("VIEW_CHANNEL"); if (channel.name.length + (private ? 1 : 0) > longest) longest = Math.min(25, channel.name.length + (private ? 1 : 0)); - if (channel.topic != null && channel.topic.length > longestTopic) - longestTopic = channel.topic.length; } console.log(""); - console.log( - " " + - "channel-name".padStart(longest, " ") + - " " + - "topic".padStart(Math.min(80 - (longest + 5), longestTopic), " ") - ); + console.log(" " + "channel-name".padStart(longest, " ") + " " + "topic"); console.log("-".repeat(80)); for (const channel of channels) { const topic = channel.topic != null ? channel.topic.replace(/\n/g, " ") : ""; const perms = channel.permissionsOf(comcord.client.user.id); - const private = !perms.has(Eris.Constants.Permissions.readMessages); + const private = !perms.has("VIEW_CHANNEL"); const name = (private ? "*" : "") + channel.name; @@ -47,11 +37,12 @@ function listChannels() { " " ) + " " + - (topic.length > 80 - longest + 9 + (topic.length > 80 - (longest + 5) ? topic.substring(0, 79 - (longest + 5)) + "\u2026" - : topic.padStart(Math.min(80 - (longest + 5), longestTopic), " ")) + : topic) ); } + console.log("-".repeat(80)); console.log(""); } diff --git a/src/commands/listGuilds.js b/src/commands/listGuilds.js index fc9fd3a..9feffff 100644 --- a/src/commands/listGuilds.js +++ b/src/commands/listGuilds.js @@ -7,7 +7,7 @@ function listGuilds() { for (const guild of comcord.client.guilds.values()) { if (guild.name.length > longest) longest = guild.name.length; - const online = [...guild.members.values()].filter((m) => m.status).length; + const online = [...guild.members.values()].filter((m) => m.presence).length; guilds.push({name: guild.name, members: guild.memberCount, online}); } @@ -24,6 +24,7 @@ function listGuilds() { guild.members.toString().padStart(5, " ") ); } + console.log("-".repeat(80)); console.log(""); } diff --git a/src/commands/listUsers.js b/src/commands/listUsers.js index acaa2f5..394117b 100644 --- a/src/commands/listUsers.js +++ b/src/commands/listUsers.js @@ -39,12 +39,12 @@ function listUsers() { `\n[you are in '${guild.name}' in '${channel.name}' among ${guild.memberCount}]\n` ); - const online = [...guild.members.values()].filter((m) => m.status); + const online = [...guild.members.values()].filter((m) => m.presence); online.sort((a, b) => a.name - b.name); let longest = 0; for (const member of online) { - const name = member.user.username + "#" + member.user.discriminator; + const name = member.user.tag; if (name.length + 3 > longest) longest = name.length + 3; } @@ -52,8 +52,8 @@ function listUsers() { let index = 0; for (const member of online) { - const name = member.user.username + "#" + member.user.discriminator; - const status = getStatus(member.status); + const name = member.user.tag; + const status = getStatus(member.presence.status); const nameAndStatus = chalk.reset(name) + status; index++; diff --git a/src/commands/send.js b/src/commands/send.js index 2402a5e..2d04907 100644 --- a/src/commands/send.js +++ b/src/commands/send.js @@ -21,10 +21,10 @@ function sendMode() { } else { try { process.stdout.write("\n"); - await comcord.client.createMessage( - comcord.state.currentChannel, - input - ); + await comcord.client.guilds + .get(comcord.state.currentGuild) + .channels.get(comcord.state.currentChannel) + .createMessage({content: input}); if (comcord.state.afk == true) { comcord.state.afk = false; diff --git a/src/index.js b/src/index.js index 1d3f4ed..367043f 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,4 @@ -const Eris = require("eris"); +const {Client} = require("oceanic.js"); const chalk = require("chalk"); const DiscordRPC = require("discord-rpc"); @@ -21,10 +21,23 @@ global.comcord = { }, commands: {}, }; -const client = new Eris("Bot " + token, { +const client = new Client({ + auth: "Bot " + token, defaultImageFormat: "png", defaultImageSize: 1024, - intents: Eris.Constants.Intents.all, + gateway: { + intents: ["ALL"], + activities: [ + { + name: "comcord", + type: "GAME", + application_id: CLIENT_ID, + timestamps: { + start: comcord.state.startTime, + }, + }, + ], + }, }); comcord.client = client; const rpc = new DiscordRPC.Client({transport: "ipc"}); @@ -51,25 +64,12 @@ process.stdin.setEncoding("utf8"); client.once("ready", function () { console.log( - "Logged in as: " + - chalk.yellow( - `${client.user.username}#${client.user.discriminator} (${client.user.id})` - ) + "Logged in as: " + chalk.yellow(`${client.user.tag} (${client.user.id})`) ); comcord.state.nameLength = client.user.username.length + 2; listGuilds(); - client.editStatus("online", [ - { - application_id: CLIENT_ID, - name: "comcord", - timestamps: { - start: comcord.state.startTime, - }, - }, - ]); - rpc .login({ clientId: CLIENT_ID, diff --git a/src/lib/messages.js b/src/lib/messages.js index 55c035d..17d781d 100644 --- a/src/lib/messages.js +++ b/src/lib/messages.js @@ -181,7 +181,7 @@ function formatMessage({ .replace(REGEX_COMMAND, replaceCommands) .replace(REGEX_TIMESTAMP, replaceTimestamps); if (reply.attachments.length > 0) { - replyContent += `<${reply.attachments.length} attachment${ + replyContent += ` <${reply.attachments.length} attachment${ reply.attachments.length > 1 ? "s" : "" }>`; replyContent = replyContent.trim(); @@ -284,7 +284,7 @@ function formatMessage({ } if (attachments) { - for (const attachment of attachments) { + for (const attachment of attachments.values()) { if (noColor) { console.log(``); } else { From 9199002e1282a02b93211517c36d2d3bd739324e Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sun, 9 Oct 2022 21:15:53 -0600 Subject: [PATCH 34/92] config support, user account support, private message support --- README.md | 27 ++-- src/commands/afk.js | 1 + src/commands/listUsers.js | 11 +- src/commands/privateMessages.js | 51 ++++++++ src/commands/switchGuild.js | 2 + src/index.js | 219 ++++++++++++++++++++++++++++---- src/lib/clientSpoof.js | 107 ++++++++++++++++ src/lib/messages.js | 21 ++- src/lib/presence.js | 65 ++++++++-- src/lib/rcfile.js | 30 +++++ 10 files changed, 484 insertions(+), 50 deletions(-) create mode 100644 src/commands/privateMessages.js create mode 100644 src/lib/clientSpoof.js create mode 100644 src/lib/rcfile.js diff --git a/README.md b/README.md index 8330e0d..9479673 100644 --- a/README.md +++ b/README.md @@ -8,17 +8,24 @@ A CLI-based client for Discord inspired by [SDF](https://sdf.org)'s [commode](ht ## Usage 1. `pnpm i` 2. `node src/index.js ` +Your token will be then stored in `.comcordrc` after the first launch. -Currently only bot accounts are supported, and that is unlikely to change anytime soon. -~~Eris has a lot of user-only endpoints implemented, but it would require hacking apart Eris to do the things nessicary to spoof being the actual client.~~ -(I have no idea currently what the state of user support in Oceanic is) -I also don't want to give skids an easy point of reference of how to spoof the client. :^) +### User Accounts +User accounts are *partially* supported via `allowUserAccounts=true` in your `.comcordrc`. +This is use at your own risk, despite spoofing the official client. I am not responsible for any banned accounts. +#### Guild members not populating +This is due to Oceanic not implementing Lazy Guilds as they are user account specific. **DO NOT bother Oceanic to implement it!** They are purely a bot-focused library. + +If you are willing to implement Lazy Guilds based off of [unofficial documentation](https://luna.gitlab.io/discord-unofficial-docs/lazy_guilds.html) +and my already existing horrible hacks to make user accounts work in the first place, feel free to send a PR (on GitLab, GitHub repo is a read only mirror). + +### Bot Accounts (prefered) You **MUST** grant your bot all Privileged Gateway Intents. ## Design Decisions * Node.js was chosen currently due to familiarity. -* ~~Eris~~ Oceanic was chosen due to familiarity and the nature of everything not being abstracted out to 200 different classes unlike discord.js. +* Oceanic was chosen due to familiarity and the nature of everything not being abstracted out to 200 different classes unlike discord.js. * "Jank" by design. While I don't expect anyone to actually use comcord on serial terminals or teletypes other than for meme factor, the option is still there. ## TODO @@ -39,6 +46,9 @@ You **MUST** grant your bot all Privileged Gateway Intents. - [x] Clear (c) - [ ] Surf channels forwards (>) - [ ] Surf channels backwards (<) + - [x] AFK toggle (A) + - [x] Send DM (s) + - [x] Answer DM (a) - [x] Message Receiving - [x] Markdown styling - [ ] Common markdown (bold, italic, etc) @@ -54,8 +64,9 @@ You **MUST** grant your bot all Privileged Gateway Intents. - [x] Puts incoming messages into queue whilst in send mode - [ ] Mentions - [ ] Replies -- [ ] Configuration - - [ ] Default guild/channel +- [x] Configuration + - [x] Default guild/channel + - No way to set in client (yet?), `defaultChannel=` and `defaultGuild=` in your `.comcordrc`. - [ ] Threads -- [ ] Not have the token just be in argv +- [x] Not have the token just be in argv - [x] Not have everything in one file diff --git a/src/commands/afk.js b/src/commands/afk.js index 7a3e396..70fc347 100644 --- a/src/commands/afk.js +++ b/src/commands/afk.js @@ -3,6 +3,7 @@ const {updatePresence} = require("../lib/presence"); addCommand("A", "toggles AFK mode", function () { if (comcord.state.afk == true) { + comcord.state.afk = false; comcord.client.shards.forEach((shard) => (shard.presence.afk = false)); comcord.client.editStatus("online"); console.log(""); diff --git a/src/commands/listUsers.js b/src/commands/listUsers.js index 394117b..27f1bd8 100644 --- a/src/commands/listUsers.js +++ b/src/commands/listUsers.js @@ -40,21 +40,22 @@ function listUsers() { ); const online = [...guild.members.values()].filter((m) => m.presence); - online.sort((a, b) => a.name - b.name); + online.sort((a, b) => a.tag.localeCompare(b.tag)); let longest = 0; for (const member of online) { - const name = member.user.tag; + const name = member.tag; if (name.length + 3 > longest) longest = name.length + 3; } - const columns = Math.ceil(process.stdout.columns / longest); + const columns = Math.floor(process.stdout.columns / longest); let index = 0; for (const member of online) { - const name = member.user.tag; + const name = member.tag; const status = getStatus(member.presence.status); - const nameAndStatus = chalk.reset(name) + status; + const nameAndStatus = + (member.user.bot ? chalk.yellow(name) : chalk.reset(name)) + status; index++; process.stdout.write( diff --git a/src/commands/privateMessages.js b/src/commands/privateMessages.js new file mode 100644 index 0000000..b7d0d6a --- /dev/null +++ b/src/commands/privateMessages.js @@ -0,0 +1,51 @@ +const chalk = require("chalk"); + +const {addCommand} = require("../lib/command"); +const {startPrompt} = require("../lib/prompt"); +const {listUsers} = require("./listUsers"); + +function startDM(user) { + startPrompt(":msg> ", async function (input) { + if (input == "") { + console.log(`\n`); + } else { + try { + const channel = await user.createDM(); + await channel.createMessage({content: input}); + console.log(chalk.bold.green(`\n`)); + } catch (err) { + console.log("\n"); + } + } + }); +} + +addCommand("s", "send private", function () { + console.log("Provide a RECIPIENT"); + startPrompt(":to> ", function (who) { + let target; + for (const user of comcord.client.users.values()) { + if (user.tag == who) { + target = user; + break; + } + } + + if (target) { + console.log(""); + startDM(target); + } else { + listUsers(); + } + }); +}); + +addCommand("a", "answer a send", function () { + if (comcord.state.lastDM) { + console.log(chalk.bold.green(``)); + startDM(comcord.state.lastDM); + } else { + // FIXME: figure out the actual message in com + console.log(""); + } +}); diff --git a/src/commands/switchGuild.js b/src/commands/switchGuild.js index 542b954..c072a63 100644 --- a/src/commands/switchGuild.js +++ b/src/commands/switchGuild.js @@ -56,3 +56,5 @@ function switchGuild(input) { addCommand("G", "goto guild", function () { startPrompt(":guild> ", switchGuild); }); + +module.exports = {switchGuild}; diff --git a/src/index.js b/src/index.js index 367043f..8399763 100644 --- a/src/index.js +++ b/src/index.js @@ -1,15 +1,36 @@ -const {Client} = require("oceanic.js"); -const chalk = require("chalk"); +const {Client, Constants} = require("oceanic.js"); const DiscordRPC = require("discord-rpc"); +const chalk = require("chalk"); +const fs = require("fs"); + +const rcfile = require("./lib/rcfile"); +const config = {}; + +if (fs.existsSync(rcfile.path)) { + console.log("% Reading " + rcfile.path + " ..."); + rcfile.readFile(config); +} const CLIENT_ID = "1026163285877325874"; const token = process.argv[2]; +if (!config.token && token) { + console.log("% Writing token to .comcordrc"); + config.token = token; + rcfile.writeFile(config); +} + +if (!config.token && !token) { + console.log("No token provided."); + process.exit(1); +} process.title = "comcord"; global.comcord = { + config, state: { + rpcConnected: false, startTime: Date.now(), currentGuild: null, currentChannel: null, @@ -22,21 +43,28 @@ global.comcord = { commands: {}, }; const client = new Client({ - auth: "Bot " + token, + auth: + (config.allowUserAccounts == "true" ? "" : "Bot ") + + (token ?? config.token), defaultImageFormat: "png", defaultImageSize: 1024, gateway: { intents: ["ALL"], - activities: [ - { - name: "comcord", - type: "GAME", - application_id: CLIENT_ID, - timestamps: { - start: comcord.state.startTime, + maxShards: 1, + concurrency: 1, + presence: { + status: "online", + activities: [ + { + name: "comcord", + type: 0, + application_id: CLIENT_ID, + timestamps: { + start: comcord.state.startTime, + }, }, - }, - ], + ], + }, }, }); comcord.client = client; @@ -53,10 +81,11 @@ require("./commands/help"); const {sendMode} = require("./commands/send"); require("./commands/emote"); const {listGuilds} = require("./commands/listGuilds"); -require("./commands/switchGuild"); // loads listChannels and listUsers +const {switchGuild} = require("./commands/switchGuild"); // loads listChannels and listUsers require("./commands/switchChannel"); //loads listUsers require("./commands/history"); // includes extended history require("./commands/afk"); +require("./commands/privateMessages"); process.stdin.setRawMode(true); process.stdin.resume(); @@ -70,20 +99,44 @@ client.once("ready", function () { listGuilds(); - rpc - .login({ - clientId: CLIENT_ID, - }) - .catch(function () {}); + if (config.defaultGuild) { + const guild = client.guilds.get(config.defaultGuild); + if (guild != null) { + if (config.defaultChannel) { + comcord.state.currentChannel = config.defaultChannel; + comcord.state.lastChannel.set( + config.defaultGuild, + config.defaultChannel + ); + } + switchGuild(guild.name); + } else { + console.log("% This account is not in the defined default guild."); + } + } else { + if (config.defaultChannel) { + console.log("% Default channel defined without defining default guild."); + } + } + + if (client.user.bot) { + rpc + .login({ + clientId: CLIENT_ID, + }) + .catch(function () {}); + } }); client.on("error", function () {}); rpc.on("connected", function () { + comcord.state.rpcConnected = true; updatePresence(); }); let retryingRPC = false; rpc.once("ready", function () { rpc.transport.on("close", function () { + comcord.state.rpcConnected = false; if (!retryingRPC) { retryingRPC = true; setTimeout(function () { @@ -102,21 +155,55 @@ rpc.once("ready", function () { }); rpc.on("error", function () {}); -client.on("messageCreate", function (msg) { +client.on("messageCreate", async function (msg) { if (msg.author.id === client.user.id) return; - if (msg.channel.id == comcord.state.currentChannel) { + if (msg.channelID && !msg.channel) { + try { + const dmChannel = await msg.author.createDM(); + if (dmChannel.id === msg.channelID) { + msg.channel = dmChannel; + } + } catch { + // + } + } + + if ( + (msg.channel ? msg.channel.id : msg.channelID) == + comcord.state.currentChannel || + msg.channel?.recipient != null + ) { if (comcord.state.inPrompt) { comcord.state.messageQueue.push(msg); } else { processMessage(msg); } } + + if (msg.channel?.recipient != null) { + comcord.state.lastDM = msg.author; + } }); -client.on("messageUpdate", function (msg, old) { +client.on("messageUpdate", async function (msg, old) { if (msg.author.id === client.user.id) return; - if (msg.channel.id == comcord.state.currentChannel) { + if (msg.channelID && !msg.channel) { + try { + const dmChannel = await msg.author.createDM(); + if (dmChannel.id === msg.channelID) { + msg.channel = dmChannel; + } + } catch { + // + } + } + + if ( + (msg.channel ? msg.channel.id : msg.channelID) == + comcord.state.currentChannel || + msg.channel?.recipient != null + ) { if (msg.content == old.content) return; if (comcord.state.inPrompt) { @@ -125,6 +212,10 @@ client.on("messageUpdate", function (msg, old) { processMessage(msg); } } + + if (msg.channel?.recipient != null) { + comcord.state.lastDM = msg.author; + } }); process.stdin.on("data", async function (key) { @@ -158,7 +249,89 @@ process.stdin.on("data", async function (key) { } }); -client.connect(); +if ( + config.allowUserAccounts == "true" && + !(token ?? config.token).startsWith("Bot ") +) { + if (fetch == null) { + console.log("Node v18+ needed for user account support."); + process.exit(1); + } + + (async function () { + comcord.clientSpoof = require("./lib/clientSpoof"); + const superProperties = await comcord.clientSpoof.getSuperProperties(); + + console.log("% Allowing non-bot tokens to connect"); + const connectLines = client.connect.toString().split("\n"); + connectLines.splice(0, 4); + connectLines.splice(-1, 1); + + const newConnect = new client.connect.constructor(connectLines.join("\n")); + client.connect = newConnect.bind(client); + + // gross hack + global.Constants_1 = Constants; + try { + global.Erlpack = require("erlpack"); + } catch { + global.Erlpack = false; + } + + console.log("% Injecting headers into request handler"); + client.rest.handler.options.userAgent = `Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) discord/${superProperties.client_version} Chrome/91.0.4472.164 Electron/13.6.6 Safari/537.36`; + client.rest.handler._request = client.rest.handler.request.bind( + client.rest.handler + ); + client.rest.handler.request = async function (options) { + options.headers = options.headers ?? {}; + options.headers["X-Super-Properties"] = + await comcord.clientSpoof.getSuperPropertiesBase64(); + + return await this._request.apply(this, [options]); + }.bind(client.rest.handler); + + console.log("% Setting gateway connection properties"); + client.shards.options.connectionProperties = superProperties; + + console.log("% Injecting application into READY payload"); + client.shards._spawn = client.shards.spawn.bind(client.shards); + client.shards.spawn = function (id) { + const res = this._spawn.apply(this, [id]); + const shard = this.get(id); + if (shard) { + shard._onDispatch = shard.onDispatch.bind(shard); + shard.onDispatch = async function (packet) { + if (packet.t == "READY") { + packet.d.application = {id: CLIENT_ID, flags: 565248}; + } + + const ret = await this._onDispatch.apply(this, [packet]); + + if (packet.t == "READY") { + for (const guild of packet.d.guilds) { + await this._onDispatch.apply(this, [ + { + t: "GUILD_CREATE", + d: guild, + }, + ]); + } + } + + return ret; + }.bind(shard); + } + + return res; + }.bind(client.shards); + + console.log("% Connecting to gateway now"); + await client.connect(); + })(); +} else { + client.connect(); +} console.log("COMcord (c)left 2022"); console.log("Type 'h' for Commands"); diff --git a/src/lib/clientSpoof.js b/src/lib/clientSpoof.js new file mode 100644 index 0000000..d67cf17 --- /dev/null +++ b/src/lib/clientSpoof.js @@ -0,0 +1,107 @@ +/* + * This single file is **EXCLUDED** from the project license. + * + * (c) 2022 Cynthia Foxwell, all rights reserved. + * Permission is hereby granted to redistribute this file ONLY with copies of comcord. + * You may not reverse engineer, modify, copy, or redistribute this file for any other uses outside of comcord. + */ + +const os = require("os"); + +async function fetchMainPage() { + const res = await fetch("https://discord.com/channels/@me"); + return await res.text(); +} + +async function fetchAsset(assetPath) { + return await fetch("https://discord.com/" + assetPath).then((res) => + res.text() + ); +} + +const MATCH_SCRIPT = '