Compare commits

..

No commits in common. "95fca1c026e3c5dd95eeb69acacb5bbc5bc32c29" and "6b90fbd8d4c686e5784ca6e0cec109d6c1f934ed" have entirely different histories.

17 changed files with 126 additions and 634 deletions

View file

@ -8,24 +8,16 @@ A CLI-based client for Discord inspired by [SDF](https://sdf.org)'s [commode](ht
## Usage ## Usage
1. `pnpm i` 1. `pnpm i`
2. `node src/index.js <token>` 2. `node src/index.js <token>`
Your token will be then stored in `.comcordrc` after the first launch.
### User Accounts Currently only bot accounts are supported, and that is unlikely to change anytime soon.
User accounts are *partially* supported via `allowUserAccounts=true` in your `.comcordrc`. 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.
This is use at your own risk, despite spoofing the official client. I am not responsible for any banned accounts. I also don't want to give skids an easy point of reference of how to spoof the client. :^)
#### 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. You **MUST** grant your bot all Privileged Gateway Intents.
## Design Decisions ## Design Decisions
* Node.js was chosen currently due to familiarity. * Node.js was chosen currently due to familiarity.
* Oceanic was chosen due to familiarity and the nature of everything not being abstracted out to 200 different classes unlike discord.js. * 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. * "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 ## TODO
@ -46,16 +38,12 @@ You **MUST** grant your bot all Privileged Gateway Intents.
- [x] Clear (c) - [x] Clear (c)
- [ ] Surf channels forwards (>) - [ ] Surf channels forwards (>)
- [ ] Surf channels backwards (<) - [ ] Surf channels backwards (<)
- [x] AFK toggle (A)
- [x] Send DM (s)
- [x] Answer DM (a)
- [x] Message Receiving - [x] Message Receiving
- [x] Markdown styling - [ ] Markdown styling
- [ ] Common markdown (bold, italic, etc) - [ ] Common markdown (bold, italic, etc)
- [ ] Figure out how spoilers would work - [ ] Figure out how spoilers would work
- [x] Emotes????? - [ ] Emotes?????
- [x] Timestamp parsing - [ ] Timestamp parsing
- [x] Mentions parsing
- [ ] Embeds in the style of commode's posted links - [ ] Embeds in the style of commode's posted links
- [x] 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 - [ ] Inline DMs to replicate commode's private messages
@ -64,9 +52,8 @@ You **MUST** grant your bot all Privileged Gateway Intents.
- [x] Puts incoming messages into queue whilst in send mode - [x] Puts incoming messages into queue whilst in send mode
- [ ] Mentions - [ ] Mentions
- [ ] Replies - [ ] Replies
- [x] Configuration - [ ] Configuration
- [x] Default guild/channel - [ ] Default guild/channel
- No way to set in client (yet?), `defaultChannel=` and `defaultGuild=` in your `.comcordrc`.
- [ ] Threads - [ ] Threads
- [x] Not have the token just be in argv - [ ] Not have the token just be in argv
- [x] Not have everything in one file - [x] Not have everything in one file

View file

@ -9,6 +9,6 @@
"dependencies": { "dependencies": {
"chalk": "4.1.2", "chalk": "4.1.2",
"discord-rpc": "^4.0.1", "discord-rpc": "^4.0.1",
"oceanic.js": "^1.1.2" "eris": "github:abalabahaha/eris#dev"
} }
} }

View file

@ -3,47 +3,15 @@ lockfileVersion: 5.4
specifiers: specifiers:
chalk: 4.1.2 chalk: 4.1.2
discord-rpc: ^4.0.1 discord-rpc: ^4.0.1
oceanic.js: ^1.1.2 eris: github:abalabahaha/eris#dev
dependencies: dependencies:
chalk: 4.1.2 chalk: 4.1.2
discord-rpc: 4.0.1 discord-rpc: 4.0.1
oceanic.js: 1.1.2 eris: github.com/abalabahaha/eris/eb403730855714eafa36c541dbe2cb84c9979158
packages: 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: /ansi-styles/4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -58,13 +26,6 @@ packages:
dev: false dev: false
optional: true 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: /chalk/4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -84,11 +45,6 @@ packages:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
dev: false 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: /discord-rpc/4.0.1:
resolution: {integrity: sha512-HOvHpbq5STRZJjQIBzwoKnQ0jHplbEWFWlPDwXXKm/bILh4nzjcg7mNqll0UY7RsjFoaXA7e/oYb/4lvpda2zA==} resolution: {integrity: sha512-HOvHpbq5STRZJjQIBzwoKnQ0jHplbEWFWlPDwXXKm/bILh4nzjcg7mNqll0UY7RsjFoaXA7e/oYb/4lvpda2zA==}
dependencies: dependencies:
@ -129,47 +85,12 @@ packages:
whatwg-url: 5.0.0 whatwg-url: 5.0.0
dev: false dev: false
/oceanic.js/1.1.2: /opusscript/0.0.8:
resolution: {integrity: sha512-aABMK2UERHvyiwjWFx5m5ZZY7oPUHmVSOhICadZh/vqyxvGf48p+aqGlRu9bEtN6XZYPZJecQi/9IPN+phXn1Q==} resolution: {integrity: sha512-VSTi1aWFuCkRCVq+tx/BQ5q9fMnQ9pVZ3JU4UHKqTkf0ED3fKEPdr+gKAAl3IA2hj9rrP6iyq3hlcJq3HELtNQ==}
engines: {node: '>=16.16.0'} requiresBuild: true
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 dev: false
optional: true optional: true
/streamsearch/1.1.0:
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
engines: {node: '>=10.0.0'}
dev: false
/supports-color/7.2.0: /supports-color/7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -181,18 +102,12 @@ packages:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
dev: false dev: false
/tslib/2.4.0: /tweetnacl/1.0.3:
resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==}
requiresBuild: true
dev: false dev: false
optional: true 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: /webidl-conversions/3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
dev: false dev: false
@ -217,8 +132,8 @@ packages:
optional: true optional: true
dev: false dev: false
/ws/8.9.0: /ws/8.8.1:
resolution: {integrity: sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==} resolution: {integrity: sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
peerDependencies: peerDependencies:
bufferutil: ^4.0.1 bufferutil: ^4.0.1
@ -230,6 +145,21 @@ packages:
optional: true optional: true
dev: false 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: github.com/devsnek/node-register-scheme/e7cc9a63a1f512565da44cb57316d9fb10750e17:
resolution: {tarball: https://codeload.github.com/devsnek/node-register-scheme/tar.gz/e7cc9a63a1f512565da44cb57316d9fb10750e17} resolution: {tarball: https://codeload.github.com/devsnek/node-register-scheme/tar.gz/e7cc9a63a1f512565da44cb57316d9fb10750e17}
name: register-scheme name: register-scheme

View file

@ -4,13 +4,13 @@ const {updatePresence} = require("../lib/presence");
addCommand("A", "toggles AFK mode", function () { addCommand("A", "toggles AFK mode", function () {
if (comcord.state.afk == true) { if (comcord.state.afk == true) {
comcord.state.afk = false; comcord.state.afk = false;
comcord.client.shards.forEach((shard) => (shard.presence.afk = false));
comcord.client.editStatus("online"); comcord.client.editStatus("online");
comcord.client.editAFK(false);
console.log("<you have returned>"); console.log("<you have returned>");
} else { } else {
comcord.state.afk = true; comcord.state.afk = true;
comcord.client.shards.forEach((shard) => (shard.presence.afk = true));
comcord.client.editStatus("idle"); comcord.client.editStatus("idle");
comcord.client.editAFK(true);
console.log("<you go AFK>"); console.log("<you go AFK>");
} }

View file

@ -13,10 +13,10 @@ addCommand("e", "emote", function () {
} else { } else {
try { try {
process.stdout.write("\n"); process.stdout.write("\n");
await comcord.client.guilds await comcord.client.createMessage(
.get(comcord.state.currentGuild) comcord.state.currentChannel,
.channels.get(comcord.state.currentChannel) `*${input}*`
.createMessage({content: `*${input}*`}); );
console.log(`<${comcord.client.user.username} ${input}>`); console.log(`<${comcord.client.user.username} ${input}>`);
} catch (err) { } catch (err) {
console.log("<failed to send message: " + err.message + ">"); console.log("<failed to send message: " + err.message + ">");

View file

@ -8,10 +8,10 @@ async function getHistory(limit = 20) {
return; return;
} }
const messages = await comcord.client.guilds const messages = await comcord.client.getMessages(
.get(comcord.state.currentGuild) comcord.state.currentChannel,
.channels.get(comcord.state.currentChannel) {limit}
.getMessages({limit}); );
messages.reverse(); messages.reverse();
console.log("--Beginning-Review".padEnd(72, "-")); console.log("--Beginning-Review".padEnd(72, "-"));

View file

@ -1,3 +1,5 @@
const Eris = require("eris");
const {addCommand} = require("../lib/command"); const {addCommand} = require("../lib/command");
function listChannels() { function listChannels() {
@ -7,26 +9,34 @@ function listChannels() {
} }
let longest = 0; let longest = 0;
let longestTopic = 0;
const guild = comcord.client.guilds.get(comcord.state.currentGuild); const guild = comcord.client.guilds.get(comcord.state.currentGuild);
const channels = [...guild.channels.values()].filter((c) => c.type == 0); const channels = [...guild.channels.values()].filter((c) => c.type == 0);
channels.sort((a, b) => a.position - b.position); channels.sort((a, b) => a.position - b.position);
for (const channel of channels) { for (const channel of channels) {
const perms = channel.permissionsOf(comcord.client.user.id); const perms = channel.permissionsOf(comcord.client.user.id);
const private = !perms.has("VIEW_CHANNEL"); const private = !perms.has(Eris.Constants.Permissions.readMessages);
if (channel.name.length + (private ? 1 : 0) > longest) if (channel.name.length + (private ? 1 : 0) > longest)
longest = Math.min(25, channel.name.length + (private ? 1 : 0)); 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("");
console.log(" " + "channel-name".padStart(longest, " ") + " " + "topic"); console.log(
" " +
"channel-name".padStart(longest, " ") +
" " +
"topic".padStart(Math.min(80 - (longest + 5), longestTopic), " ")
);
console.log("-".repeat(80)); console.log("-".repeat(80));
for (const channel of channels) { for (const channel of channels) {
const topic = const topic =
channel.topic != null ? channel.topic.replace(/\n/g, " ") : ""; channel.topic != null ? channel.topic.replace(/\n/g, " ") : "";
const perms = channel.permissionsOf(comcord.client.user.id); const perms = channel.permissionsOf(comcord.client.user.id);
const private = !perms.has("VIEW_CHANNEL"); const private = !perms.has(Eris.Constants.Permissions.readMessages);
const name = (private ? "*" : "") + channel.name; const name = (private ? "*" : "") + channel.name;
@ -37,12 +47,11 @@ function listChannels() {
" " " "
) + ) +
" " + " " +
(topic.length > 80 - (longest + 5) (topic.length > 80 - longest + 9
? topic.substring(0, 79 - (longest + 5)) + "\u2026" ? topic.substring(0, 79 - (longest + 5)) + "\u2026"
: topic) : topic.padStart(Math.min(80 - (longest + 5), longestTopic), " "))
); );
} }
console.log("-".repeat(80));
console.log(""); console.log("");
} }

View file

@ -7,7 +7,7 @@ function listGuilds() {
for (const guild of comcord.client.guilds.values()) { for (const guild of comcord.client.guilds.values()) {
if (guild.name.length > longest) longest = guild.name.length; if (guild.name.length > longest) longest = guild.name.length;
const online = [...guild.members.values()].filter((m) => m.presence).length; const online = [...guild.members.values()].filter((m) => m.status).length;
guilds.push({name: guild.name, members: guild.memberCount, online}); guilds.push({name: guild.name, members: guild.memberCount, online});
} }
@ -24,7 +24,6 @@ function listGuilds() {
guild.members.toString().padStart(5, " ") guild.members.toString().padStart(5, " ")
); );
} }
console.log("-".repeat(80));
console.log(""); console.log("");
} }

View file

@ -39,23 +39,22 @@ function listUsers() {
`\n[you are in '${guild.name}' in '${channel.name}' among ${guild.memberCount}]\n` `\n[you are in '${guild.name}' in '${channel.name}' among ${guild.memberCount}]\n`
); );
const online = [...guild.members.values()].filter((m) => m.presence); const online = [...guild.members.values()].filter((m) => m.status);
online.sort((a, b) => a.tag.localeCompare(b.tag)); online.sort((a, b) => a.name - b.name);
let longest = 0; let longest = 0;
for (const member of online) { for (const member of online) {
const name = member.tag; const name = member.user.username + "#" + member.user.discriminator;
if (name.length + 3 > longest) longest = name.length + 3; if (name.length + 3 > longest) longest = name.length + 3;
} }
const columns = Math.floor(process.stdout.columns / longest); const columns = Math.ceil(process.stdout.columns / longest);
let index = 0; let index = 0;
for (const member of online) { for (const member of online) {
const name = member.tag; const name = member.user.username + "#" + member.user.discriminator;
const status = getStatus(member.presence.status); const status = getStatus(member.status);
const nameAndStatus = const nameAndStatus = chalk.reset(name) + status;
(member.user.bot ? chalk.yellow(name) : chalk.reset(name)) + status;
index++; index++;
process.stdout.write( process.stdout.write(

View file

@ -1,51 +0,0 @@
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<message not sent to ${user.tag}>`);
} else {
try {
const channel = await user.createDM();
await channel.createMessage({content: input});
console.log(chalk.bold.green(`\n<message sent to ${user.tag}>`));
} catch (err) {
console.log("\n<failed to send message: " + err.message + ">");
}
}
});
}
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(`<answering ${comcord.state.lastDM.tag}>`));
startDM(comcord.state.lastDM);
} else {
// FIXME: figure out the actual message in com
console.log("<no one to answer>");
}
});

View file

@ -21,10 +21,10 @@ function sendMode() {
} else { } else {
try { try {
process.stdout.write("\n"); process.stdout.write("\n");
await comcord.client.guilds await comcord.client.createMessage(
.get(comcord.state.currentGuild) comcord.state.currentChannel,
.channels.get(comcord.state.currentChannel) input
.createMessage({content: input}); );
if (comcord.state.afk == true) { if (comcord.state.afk == true) {
comcord.state.afk = false; comcord.state.afk = false;

View file

@ -56,5 +56,3 @@ function switchGuild(input) {
addCommand("G", "goto guild", function () { addCommand("G", "goto guild", function () {
startPrompt(":guild> ", switchGuild); startPrompt(":guild> ", switchGuild);
}); });
module.exports = {switchGuild};

View file

@ -1,36 +1,15 @@
const {Client, Constants} = require("oceanic.js"); const Eris = require("eris");
const DiscordRPC = require("discord-rpc");
const chalk = require("chalk"); const chalk = require("chalk");
const fs = require("fs"); const DiscordRPC = require("discord-rpc");
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 CLIENT_ID = "1026163285877325874";
const token = process.argv[2]; 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"; process.title = "comcord";
global.comcord = { global.comcord = {
config,
state: { state: {
rpcConnected: false,
startTime: Date.now(), startTime: Date.now(),
currentGuild: null, currentGuild: null,
currentChannel: null, currentChannel: null,
@ -42,30 +21,10 @@ global.comcord = {
}, },
commands: {}, commands: {},
}; };
const client = new Client({ const client = new Eris("Bot " + token, {
auth:
(config.allowUserAccounts == "true" ? "" : "Bot ") +
(token ?? config.token),
defaultImageFormat: "png", defaultImageFormat: "png",
defaultImageSize: 1024, defaultImageSize: 1024,
gateway: { intents: Eris.Constants.Intents.all,
intents: ["ALL"],
maxShards: 1,
concurrency: 1,
presence: {
status: "online",
activities: [
{
name: "comcord",
type: 0,
application_id: CLIENT_ID,
timestamps: {
start: comcord.state.startTime,
},
},
],
},
},
}); });
comcord.client = client; comcord.client = client;
const rpc = new DiscordRPC.Client({transport: "ipc"}); const rpc = new DiscordRPC.Client({transport: "ipc"});
@ -81,11 +40,10 @@ require("./commands/help");
const {sendMode} = require("./commands/send"); const {sendMode} = require("./commands/send");
require("./commands/emote"); require("./commands/emote");
const {listGuilds} = require("./commands/listGuilds"); const {listGuilds} = require("./commands/listGuilds");
const {switchGuild} = require("./commands/switchGuild"); // loads listChannels and listUsers require("./commands/switchGuild"); // loads listChannels and listUsers
require("./commands/switchChannel"); //loads listUsers require("./commands/switchChannel"); //loads listUsers
require("./commands/history"); // includes extended history require("./commands/history"); // includes extended history
require("./commands/afk"); require("./commands/afk");
require("./commands/privateMessages");
process.stdin.setRawMode(true); process.stdin.setRawMode(true);
process.stdin.resume(); process.stdin.resume();
@ -93,118 +51,63 @@ process.stdin.setEncoding("utf8");
client.once("ready", function () { client.once("ready", function () {
console.log( console.log(
"Logged in as: " + chalk.yellow(`${client.user.tag} (${client.user.id})`) "Logged in as: " +
chalk.yellow(
`${client.user.username}#${client.user.discriminator} (${client.user.id})`
)
); );
comcord.state.nameLength = client.user.username.length + 2; comcord.state.nameLength = client.user.username.length + 2;
listGuilds(); listGuilds();
if (config.defaultGuild) { client.editStatus("online", [
const guild = client.guilds.get(config.defaultGuild); {
if (guild != null) { application_id: CLIENT_ID,
if (config.defaultChannel) { name: "comcord",
comcord.state.currentChannel = config.defaultChannel; timestamps: {
comcord.state.lastChannel.set( start: comcord.state.startTime,
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
rpc .login({
.login({ clientId: CLIENT_ID,
clientId: CLIENT_ID, })
}) .catch(function () {});
.catch(function () {});
}
}); });
client.on("error", function () {}); client.on("error", function () {});
rpc.on("connected", function () { rpc.on("connected", function () {
comcord.state.rpcConnected = true;
updatePresence(); updatePresence();
}); });
let retryingRPC = false;
rpc.once("ready", function () { rpc.once("ready", function () {
rpc.transport.on("close", function () { rpc.transport.on("close", async function () {
comcord.state.rpcConnected = false; try {
if (!retryingRPC) { await rpc.transport.connect();
retryingRPC = true; } catch (err) {
setTimeout(function () { rpc.transport.emit("close");
rpc.transport
.connect()
.then(() => {
retryingRPC = false;
})
.catch((err) => {
retryingRPC = false;
rpc.transport.emit("close");
});
}, 5000);
} }
}); });
}); });
rpc.on("error", function () {}); rpc.on("error", function () {});
client.on("messageCreate", async function (msg) { client.on("messageCreate", function (msg) {
if (msg.author.id === client.user.id) return; if (msg.author.id === client.user.id) return;
if (msg.channelID && !msg.channel) { if (msg.channel.id == comcord.state.currentChannel) {
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) { if (comcord.state.inPrompt) {
comcord.state.messageQueue.push(msg); comcord.state.messageQueue.push(msg);
} else { } else {
processMessage(msg); processMessage(msg);
} }
} }
if (msg.channel?.recipient != null) {
comcord.state.lastDM = msg.author;
}
}); });
client.on("messageUpdate", async function (msg, old) { client.on("messageUpdate", function (msg, old) {
if (msg.author.id === client.user.id) return; if (msg.author.id === client.user.id) return;
if (msg.channelID && !msg.channel) { if (msg.channel.id == comcord.state.currentChannel) {
try { if (msg.content == old.content) return;
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 (old && msg.content == old.content) return;
if (comcord.state.inPrompt) { if (comcord.state.inPrompt) {
comcord.state.messageQueue.push(msg); comcord.state.messageQueue.push(msg);
@ -212,10 +115,6 @@ client.on("messageUpdate", async function (msg, old) {
processMessage(msg); processMessage(msg);
} }
} }
if (msg.channel?.recipient != null) {
comcord.state.lastDM = msg.author;
}
}); });
process.stdin.on("data", async function (key) { process.stdin.on("data", async function (key) {
@ -249,89 +148,7 @@ process.stdin.on("data", async function (key) {
} }
}); });
if ( client.connect();
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("COMcord (c)left 2022");
console.log("Type 'h' for Commands"); console.log("Type 'h' for Commands");
@ -380,7 +197,6 @@ setInterval(function () {
} else { } else {
console.log(timeString); console.log(timeString);
} }
comcord.state.nameLength = client.user.username.length + 2;
sentTime = true; sentTime = true;
} else if (seconds > 2 && sentTime) { } else if (seconds > 2 && sentTime) {
sentTime = false; sentTime = false;

View file

@ -1,107 +0,0 @@
/*
* 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 = '<script src="(.+?)" integrity=".+?">';
const REGEX_SCRIPT = new RegExp(MATCH_SCRIPT);
const REGEX_SCRIPT_GLOBAL = new RegExp(MATCH_SCRIPT, "g");
async function extractScripts() {
const mainPage = await fetchMainPage();
return mainPage
.match(REGEX_SCRIPT_GLOBAL)
.map((script) => script.match(REGEX_SCRIPT)[1]);
}
const REGEX_BUILD_NUMBER = /Build Number: (\d+), Version Hash:/;
const REGEX_BUILD_NUMBER_SWC = /Build Number: "\).concat\("(\d+)"/;
async function getBuildNumber() {
if (comcord.state.cachedBuildNumber) {
return comcord.state.cachedBuildNumber;
}
const scripts = await extractScripts();
const chunkWithBuildInfoAsset = scripts[3];
const chunkWithBuildInfo = await fetchAsset(chunkWithBuildInfoAsset);
const buildNumber =
chunkWithBuildInfo.match(REGEX_BUILD_NUMBER_SWC)?.[1] ??
chunkWithBuildInfo.match(REGEX_BUILD_NUMBER)?.[1];
comcord.state.cachedBuildNumber = buildNumber;
return buildNumber;
}
async function getClientVersion() {
if (comcord.state.cachedClientVersion) {
return comcord.state.cachedClientVersion;
}
const data = await fetch(
"https://updates.discord.com/distributions/app/manifests/latest?channel=stable&platform=win&arch=x86"
).then((res) => res.json());
const clientVersion = data.full.host_version.join(".");
comcord.state.cachedClientVersion = clientVersion;
return clientVersion;
}
async function getSuperProperties() {
const buildNumber = await getBuildNumber();
const clientVersion = await getClientVersion();
let _os;
switch (process.platform) {
case "win32":
_os = "Windows";
break;
case "darwin":
_os = "Mac OS X";
break;
case "linux":
_os = "Linux";
break;
default:
_os = process.platform;
}
const props = {
os: _os,
browser: "Discord Client",
releaseChannel: "stable",
client_version: clientVersion,
os_version: os.release(),
os_arch: os.arch(),
system_locale: "en-US",
client_build_number: buildNumber,
client_event_source: null,
};
return props;
}
async function getSuperPropertiesBase64() {
return Buffer.from(JSON.stringify(await getSuperProperties())).toString(
"base64"
);
}
module.exports = {getSuperProperties, getSuperPropertiesBase64};

View file

@ -155,7 +155,6 @@ function replaceTimestamps(_, time, format = "f") {
function formatMessage({ function formatMessage({
name, name,
tag,
content, content,
bot, bot,
attachments, attachments,
@ -164,7 +163,6 @@ function formatMessage({
noColor = false, noColor = false,
dump = false, dump = false,
history = false, history = false,
dm = false,
}) { }) {
if (name.length + 2 > comcord.state.nameLength) if (name.length + 2 > comcord.state.nameLength)
comcord.state.nameLength = name.length + 2; comcord.state.nameLength = name.length + 2;
@ -182,9 +180,9 @@ function formatMessage({
.replace(REGEX_EMOTE, replaceEmotes) .replace(REGEX_EMOTE, replaceEmotes)
.replace(REGEX_COMMAND, replaceCommands) .replace(REGEX_COMMAND, replaceCommands)
.replace(REGEX_TIMESTAMP, replaceTimestamps); .replace(REGEX_TIMESTAMP, replaceTimestamps);
if (reply.attachments.size > 0) { if (reply.attachments.length > 0) {
replyContent += ` <${reply.attachments.size} attachment${ replyContent += `<${reply.attachments.length} attachment${
reply.attachments.size > 1 ? "s" : "" reply.attachments.length > 1 ? "s" : ""
}>`; }>`;
replyContent = replyContent.trim(); replyContent = replyContent.trim();
} }
@ -250,15 +248,7 @@ function formatMessage({
.replace(REGEX_COMMAND, replaceCommands) .replace(REGEX_COMMAND, replaceCommands)
.replace(REGEX_TIMESTAMP, replaceTimestamps); .replace(REGEX_TIMESTAMP, replaceTimestamps);
if (dm) { if (
if (noColor) {
console.log(`*${tag}* ${content}\x07`);
} else {
console.log(
chalk.bold.red(`*${tag}*`) + chalk.reset(" " + content + "\x07")
);
}
} else if (
(content.length > 1 && (content.length > 1 &&
content.startsWith("*") && content.startsWith("*") &&
content.endsWith("*")) || content.endsWith("*")) ||
@ -294,7 +284,7 @@ function formatMessage({
} }
if (attachments) { if (attachments) {
for (const attachment of attachments.values()) { for (const attachment of attachments) {
if (noColor) { if (noColor) {
console.log(`<attachment: ${attachment.url} >`); console.log(`<attachment: ${attachment.url} >`);
} else { } else {
@ -320,18 +310,13 @@ function formatMessage({
} }
} }
function processMessage(msg, options = {}) { function processMessage(msg, options) {
if (msg.channel?.recipient) {
options.dm = true;
}
if (msg.time) { if (msg.time) {
console.log(msg.content); console.log(msg.content);
} else if (msg.content && msg.content.indexOf("\n") > -1) { } else if (msg.content.indexOf("\n") > -1) {
if (msg.content.match(REGEX_CODEBLOCK)) { if (msg.content.match(REGEX_CODEBLOCK)) {
formatMessage({ formatMessage({
name: msg.author.username, name: msg.author.username,
tag: msg.author.tag,
bot: msg.author.bot, bot: msg.author.bot,
content: msg.content.replace( content: msg.content.replace(
REGEX_CODEBLOCK_GLOBAL, REGEX_CODEBLOCK_GLOBAL,
@ -349,7 +334,6 @@ function processMessage(msg, options = {}) {
const line = lines[index]; const line = lines[index];
formatMessage({ formatMessage({
name: msg.author.username, name: msg.author.username,
tag: msg.author.tag,
bot: msg.author.bot, bot: msg.author.bot,
content: content:
line + line +
@ -366,7 +350,6 @@ function processMessage(msg, options = {}) {
} else { } else {
formatMessage({ formatMessage({
name: msg.author.username, name: msg.author.username,
tag: msg.author.tag,
bot: msg.author.bot, bot: msg.author.bot,
content: msg.content + (msg.editedTimestamp != null ? " (edited)" : ""), content: msg.content + (msg.editedTimestamp != null ? " (edited)" : ""),
attachments: msg.attachments, attachments: msg.attachments,

View file

@ -1,5 +1,3 @@
const CLIENT_ID = "1026163285877325874";
function updatePresence() { function updatePresence() {
let guild, channel; let guild, channel;
if (comcord.state.currentGuild != null) { if (comcord.state.currentGuild != null) {
@ -9,54 +7,19 @@ function updatePresence() {
channel = guild.channels.get(comcord.state.currentChannel); channel = guild.channels.get(comcord.state.currentChannel);
} }
if (comcord.client.user.bot) { try {
if (comcord.state.rpcConnected) {
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.tag,
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) {
//
}
}
} else {
const activity = { const activity = {
application_id: CLIENT_ID, startTimestamp: comcord.state.startTime,
name: "comcord", smallImageKey: `https://cdn.discordapp.com/avatars/${comcord.client.user.id}/${comcord.client.user.avatar}.png?size=1024`,
timestamps: { smallImageText: `${comcord.client.user.username}#${comcord.client.user.discriminator}`,
start: comcord.state.startTime, buttons: [
}, {label: "comcord Repo", url: "https://github.com/Cynosphere/comcord"},
assets: {}, ],
buttons: ["comcord Repo"],
metadata: {
button_urls: ["https://github.com/Cynosphere/comcord"],
},
type: 0,
}; };
if (guild != null) { if (guild != null) {
activity.assets.large_image = `mp:icons/${guild.id}/${guild.icon}.png?size=1024`; activity.largeImageKey = `https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}.png?size=1024`;
activity.assets.large_text = guild.name; activity.largeImageText = guild.name;
if (channel != null) { if (channel != null) {
activity.details = `#${channel.name} - ${guild.name}`; activity.details = `#${channel.name} - ${guild.name}`;
} }
@ -64,13 +27,9 @@ function updatePresence() {
if (comcord.state.afk == true) { if (comcord.state.afk == true) {
activity.state = "AFK"; activity.state = "AFK";
} }
comcord.rpc.setActivity(activity);
comcord.client.shards.forEach((shard) => { } catch (err) {
if (shard.ready) { //
shard.presence.activities = [activity];
shard.sendPresenceUpdate();
}
});
} }
} }

View file

@ -1,30 +0,0 @@
const fs = require("fs");
const path = require("path");
const os = require("os");
const RCPATH = path.resolve(os.homedir(), ".comcordrc");
function readFile(config) {
const rc = fs.readFileSync(RCPATH, "utf8");
const lines = rc.split("\n");
for (const line of lines) {
const [key, value] = line.split("=");
config[key] = value;
}
}
function writeFile(config) {
if (fs.existsSync(RCPATH)) {
readFile(config);
}
const newrc = [];
for (const key in config) {
const value = config[key];
newrc.push(`${key}=${value}`);
}
fs.writeFileSync(RCPATH, newrc.join("\n"));
}
module.exports = {readFile, writeFile, path: RCPATH};