commit 47f3f8a6cf2f0d579ac58f68c98bb6fd0302b910 Author: raven Date: Tue Jan 10 17:13:31 2023 -0500 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..37d7e73 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +.env diff --git a/README.md b/README.md new file mode 100644 index 0000000..901b054 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# shChat Bridge + +A Peer to Peer bridge between Discord and sshChat using [Hyper-Protocol](https://docs.holepunch.to/) + + +A high-level API for finding and connecting to peers who are interested in a "topic." + +[@hyperswarm/dht](https://docs.holepunch.to/building-blocks/hyperswarm#dht): + +A DHT powering Hyperswarm. Through this DHT, each server is bound to a unique key pair, with the client connecting to the server using the server's public key. + + +[@hyperswarm/secretstream](https://docs.holepunch.to/building-blocks/hyperswarm#secretstream): + +Secretstream is used to securely create connections between two peers in Hyperswarm. + +## What is HyperSwarm? +Hyperswarm allows you to find and connect to peers who are announcing a common "topic". The swarm topic can be anything. This means that, with Hyperswarm, you can discover and connect peers with a shared interest over a distributed network. As an example, we often use Hypercore's discovery key as the swarm topic for discovering peers to replicate with. +Once Hyperswarm has discovered peers, nodes in the DHT help those peers connect via a UDP-based hole-punching algorithm that's designed to work well in home networks. + +https://docs.holepunch.to/building-blocks/hyperswarm + +## Usage + +To install: + +`git clone git@git.codingvm.codes:snxraven/sshChatBridge.git` + +`cd sshChatBridge` + +`npm i` + +To run a new topic (room): + +`node bot.js` + +This will log the bot into Discord. + + +To set a topic use the !set-topic command within the channel you wish to bridge. + +The bot will provide a connection topic hash to connect to. + +Once a peer connects all messages will be sent both ways. + +!leave [TOPIC HASH] will destroy a topic bridge. + + diff --git a/bot.js b/bot.js new file mode 100644 index 0000000..8282299 --- /dev/null +++ b/bot.js @@ -0,0 +1,165 @@ +const Discord = require('discord.js'); +const Hyperswarm = require('hyperswarm'); +const crypto = require('hypercore-crypto'); +const { Client, GatewayIntentBits, Partials, messageLink } = require("discord.js"); +const goodbye = require('graceful-goodbye') +require('dotenv').config() + +const client = new Client({ + 'intents': [ + GatewayIntentBits.DirectMessages, + GatewayIntentBits.MessageContent, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildBans, + GatewayIntentBits.GuildMessages + ], + 'partials': [Partials.Channel, Partials.Message, Partials.MessageContent] + }); + +const topicMap = new Map(); + goodbye(() => { + for(let [channel_id, topic_obj] of topicMap) + { + topic_obj.swarm.destroy(); + } + }) + +client.on('ready', () => { + console.log(`Logged in as ${client.user.tag}!`); +}); + +client.on('messageCreate', msg => { + + if (msg.content.startsWith("!") && !msg.author.bot){ + const args = msg.content.split(" "); + const cmd = args.shift().slice(1); + + if (cmd === "current-topic") { + if(topicMap.has(msg.channel.id)) + { + let topic_obj = topicMap.get(msg.channel.id); + msg.reply(`The current topic for this channel is: ${topic_obj.topic.toString('hex')}`); + } + else{ + msg.reply(`No topic is assigned for this channel`); + } + } + + if (cmd === "join-topic") { + if (!args[0]) { + msg.reply("Please provide a new topic in hexadecimal format"); + return; + } + let topic = crypto.randomBytes(32); + let channel_id = args[1]; + if(!channel_id) + { + msg.reply("Please provide a channel_id to send this topic's messages") + return; + } + //Check if the given channel_id exists or not. + if(!client.channels.cache.get(channel_id)) + { + msg.reply("The provided channel id does not exist."); + return; + } + let swarm; + let conns =[]; + if(topicMap.has(channel_id)) + { + topic_obj = topicMap.get(channel_id); + swarm = topic_obj.swarm; + conns = topic_obj.conns; + } + else + { + swarm = new Hyperswarm(); + topicMap.set(channel_id, {topic: topic, swarm: swarm, conns: conns}); + // Join the new topic + swarm.join(topic, { + lookup: true, + announce: true, + timeout: 30000 + }); + swarm.on('connection', (conn, info) => { + console.log(`Connected to a peer for topic ${topic.toString('hex')}`); + conns.push(conn); + conn.on('data', data => { + // Send the message from the topic to the Discord channel + client.channels.fetch(channel_id).then(channel=>channel.send(data.toString())) + }); + }); + client.channels.fetch(channel_id).then(channel => { + msg.reply(`Successfully joined topic and sending messages to ${channel}`); + + }); + + } + } + + if (cmd === "set-topic") { + let channel_id = msg.channel.id; + //Check if the given channel_id exists or not. + if(!client.channels.cache.get(channel_id)) + { + msg.reply("The provided channel id does not exist."); + return; + } + let topic = crypto.randomBytes(32); + let swarm; + let conns =[]; + if(topicMap.has(channel_id)) + { + topic_obj = topicMap.get(channel_id); + topic_obj.swarm.leave(topic_obj.topic); + topic_obj.topic = topic; + swarm = topic_obj.swarm; + conns = topic_obj.conns; + } + else{ + swarm = new Hyperswarm(); + conns = []; + topicMap.set(channel_id, {topic: topic, swarm: swarm, conns: conns}); + // Join the new topic + swarm.join(topic, { + lookup: true, + announce: true, + timeout: 30000 + }); + swarm.on('connection', (conn, info) => { + console.log(`Connected to a peer for topic ${topic.toString('hex')}`); + conns.push(conn); + conn.on('data', data => { + // Send the message from the topic to the Discord channel + client.channels.fetch(channel_id).then(channel=>channel.send(data.toString())) + }); + }); + } + msg.reply(`Successfully set topic ${topic.toString('hex')} for channel ${channel_id}`); + } + + if (cmd === "leave-topic") { + let channel_id = msg.channel.id; + let topic_obj = topicMap.get(channel_id); + if (topic_obj) { + topic_obj.swarm.leave(topic_obj.topic); + topicMap.delete(channel_id); + return msg.reply(`Successfully left topic ${topic_obj.topic.toString('hex')} for channel ${channel_id}`); + } else { + return msg.reply(`No topic is assigned for this channel`); + } + } + + } + // Check if message is sent in a channel with a topic assigned and send it to the topic + if(topicMap.has(msg.channel.id) && !msg.author.bot){ + let topic_obj = topicMap.get(msg.channel.id); + let conns = topic_obj.conns; + for (const conn of conns) { + conn.write(`${msg.author.username}(Discord): ${msg.content}`); + } + } + }); + + client.login(process.env.TOKEN); diff --git a/package.json b/package.json new file mode 100644 index 0000000..04a0748 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "ssh-chat-bridge", + "version": "1.0.0", + "description": "A bridge for Discord and sshchat", + "main": "bot.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git@git.codingvm.codes:snxraven/sshChatBridge.git" + }, + "author": "", + "license": "ISC", + "dependencies": { + "discord.js": "^14.7.1", + "dotenv": "^16.0.3", + "graceful-goodbye": "^1.2.0", + "hypercore-crypto": "^3.3.1", + "hyperswarm": "^4.3.5" + } +}