From c58671dc7dde66bca3472035a61e343baa0de1fa Mon Sep 17 00:00:00 2001 From: Raven Scott Date: Mon, 3 Jun 2024 19:23:14 -0400 Subject: [PATCH] first commit --- .gitignore | 3 + app.js | 226 ++++++++++++++++++++++++++++++++++++ chatBot/bot.js | 32 +++++ chatBot/includes/chatBot.js | 44 +++++++ index.html | 60 ++++++++++ package.json | 32 +++++ style.css | 197 +++++++++++++++++++++++++++++++ test/index.test.js | 1 + 8 files changed, 595 insertions(+) create mode 100644 .gitignore create mode 100644 app.js create mode 100644 chatBot/bot.js create mode 100644 chatBot/includes/chatBot.js create mode 100644 index.html create mode 100644 package.json create mode 100644 style.css create mode 100644 test/index.test.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5479247 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules +package-lock.json +storage diff --git a/app.js b/app.js new file mode 100644 index 0000000..ccb4289 --- /dev/null +++ b/app.js @@ -0,0 +1,226 @@ +import Hyperswarm from 'hyperswarm'; +import crypto from 'hypercore-crypto'; +import b4a from 'b4a'; +import ServeDrive from 'serve-drive'; +import Localdrive from 'localdrive'; + +const drive = new Localdrive('./storage'); +let swarm; +let userName = 'Anonymous'; +let userAvatar = ''; +let registeredUsers = JSON.parse(localStorage.getItem('registeredUsers')) || {}; +let peerCount = 0; + +async function initialize() { + swarm = new Hyperswarm(); + + await drive.ready(); + const servePort = 1337; + const serve = new ServeDrive({ port: servePort, get: ({ key, filename, version }) => drive }); + await serve.ready(); + console.log('Listening on http://localhost:' + serve.address().port); + + const registerForm = document.querySelector('#register-form'); + const selectAvatarButton = document.querySelector('#select-avatar'); + const createChatRoomButton = document.querySelector('#create-chat-room'); + const joinForm = document.querySelector('#join-form'); + const messageForm = document.querySelector('#message-form'); + + if (registerForm) { + registerForm.addEventListener('submit', registerUser); + } + if (selectAvatarButton) { + selectAvatarButton.addEventListener('click', () => { + document.querySelector('#avatar-file').click(); + }); + } + if (createChatRoomButton) { + createChatRoomButton.addEventListener('click', createChatRoom); + } + if (joinForm) { + joinForm.addEventListener('submit', joinChatRoom); + } + if (messageForm) { + messageForm.addEventListener('submit', sendMessage); + } + + const savedUser = localStorage.getItem('currentUser'); + if (savedUser) { + const user = JSON.parse(savedUser); + userName = user.username; + userAvatar = user.avatar || ''; + const setupDiv = document.querySelector('#setup'); + if (setupDiv) { + setupDiv.classList.remove('hidden'); + } + } else { + const registerDiv = document.querySelector('#register'); + if (registerDiv) { + registerDiv.classList.remove('hidden'); + } + } + + swarm.on('connection', (connection, info) => { + peerCount++; + updatePeerCount(); + connection.on('data', (data) => { + const messageObj = JSON.parse(data.toString()); + onMessageAdded(messageObj.name, messageObj.message, messageObj.avatar); + }); + connection.on('close', () => { + peerCount--; + updatePeerCount(); + }); + }); + + swarm.on('error', (err) => { + console.error('Swarm error:', err); + }); + + swarm.on('close', () => { + console.log('Swarm closed.'); + }); +} + +function updatePeerCount() { + const peersCountElement = document.querySelector('#peers-count'); + if (peersCountElement) { + peersCountElement.textContent = peerCount; + } +} + +async function registerUser(e) { + e.preventDefault(); + + const regUsernameInput = document.querySelector('#reg-username'); + if (!regUsernameInput) { + alert('Username input element not found.'); + return; + } + + const regUsername = regUsernameInput.value.trim(); + + if (!regUsername) { + alert('Please enter a username.'); + return; + } + + const loadingDiv = document.querySelector('#loading'); + loadingDiv.classList.remove('hidden'); + + const newUser = { username: regUsername, avatar: '' }; + localStorage.setItem('currentUser', JSON.stringify(newUser)); + + const fileInput = document.querySelector('#avatar-file'); + if (fileInput && fileInput.files.length > 0) { + const file = fileInput.files[0]; + const reader = new FileReader(); + reader.onload = async () => { + const fileBuffer = Buffer.from(reader.result); + await drive.put(`/icons/${regUsername}-${file.name}`, fileBuffer); + userAvatar = `http://localhost:1337/icons/${regUsername}-${file.name}`; + // Save avatar URL to localStorage + localStorage.setItem('avatarURL', userAvatar); + }; + reader.readAsArrayBuffer(file); + } + + continueRegistration(regUsername); +} + +async function continueRegistration(regUsername) { + const loadingDiv = document.querySelector('#loading'); + const setupDiv = document.querySelector('#setup'); + + if (!regUsername) { + alert('Please enter a username.'); + return; + } + + userName = regUsername; + setupDiv.classList.remove('hidden'); + document.querySelector('#register').classList.add('hidden'); + loadingDiv.classList.add('hidden'); + + document.querySelector('#join-chat-room').addEventListener('click', joinChatRoom); + + const randomTopic = crypto.randomBytes(32); + document.querySelector('#chat-room-topic').innerText = b4a.toString(randomTopic, 'hex'); +} + +async function createChatRoom() { + // Generate a new random topic (32 byte string) + const topicBuffer = crypto.randomBytes(32); + joinSwarm(topicBuffer); +} + +async function joinChatRoom(e) { + e.preventDefault(); + const topicStr = document.querySelector('#join-chat-room-topic').value; + const topicBuffer = b4a.from(topicStr, 'hex'); + joinSwarm(topicBuffer); +} + +async function joinSwarm(topicBuffer) { + document.querySelector('#setup').classList.add('hidden'); + document.querySelector('#loading').classList.remove('hidden'); + + // Join the swarm with the topic. Setting both client/server to true means that this app can act as both. + const discovery = swarm.join(topicBuffer, { client: true, server: true }); + await discovery.flushed(); + + const topic = b4a.toString(topicBuffer, 'hex'); + document.querySelector('#chat-room-topic').innerText = topic; + document.querySelector('#loading').classList.add('hidden'); + document.querySelector('#chat').classList.remove('hidden'); +} + +function sendMessage(e) { + e.preventDefault(); + const message = document.querySelector('#message').value; + document.querySelector('#message').value = ''; + + onMessageAdded(userName, message, userAvatar); + + // Send the message to all peers (that you are connected to) + const messageObj = JSON.stringify({ + type: 'message', + name: userName, + message, + avatar: userAvatar, + timestamp: Date.now(), + }); + + const peers = [...swarm.connections]; + for (const peer of peers) { + peer.write(messageObj); + } +} + +// appends element to #messages element with content set to sender and message +function onMessageAdded(from, message, avatar) { + const $div = document.createElement('div'); + $div.classList.add('message'); + + const $img = document.createElement('img'); + $img.src = avatar || 'https://via.placeholder.com/40'; + $div.appendChild($img); + + const $content = document.createElement('div'); + $content.classList.add('message-content'); + + const $header = document.createElement('div'); + $header.classList.add('message-header'); + $header.textContent = from; + + const $text = document.createElement('div'); + $text.textContent = message; + + $content.appendChild($header); + $content.appendChild($text); + $div.appendChild($content); + + document.querySelector('#messages').appendChild($div); +} + +initialize(); diff --git a/chatBot/bot.js b/chatBot/bot.js new file mode 100644 index 0000000..2465ad8 --- /dev/null +++ b/chatBot/bot.js @@ -0,0 +1,32 @@ +import chatBot from './includes/chatBot.js'; // Adjust the import path as necessary + +// Generate a Buffer from the hexadecimal topic value +const topicHex = '86b91c2848f96ed9a982005709338a95a6bbee55057ab0f861f2c0ae979c3177'; +const topicBuffer = Buffer.from(topicHex, 'hex'); + +// Create a new instance of the chatBot class with a valid botName +const botName = 'MyBot'; // Replace 'MyBot' with the desired bot name +const bot = new chatBot(topicBuffer, botName, onMessageReceived); + +// Join the chat room +bot.joinChatRoom(); + +// Wait for 2 seconds for the bot to connect +setTimeout(() => { + // Send a message + bot.sendMessage('Hello, world!'); +}, 2000); // Adjust the delay as necessary + +// Define the function to handle received messages +function onMessageReceived(peer, message) { + console.log(`Message received from ${message.name} at ${new Date(message.timestamp).toLocaleTimeString()}: ${message.message}`); + + // Check if the message is a ping command + if (message.message.toLowerCase() === '!ping') { + bot.sendMessage('Pong!'); + } else if (message.message.toLowerCase().startsWith('!8ball')) { + const responses = ['It is certain.', 'It is decidedly so.', 'Without a doubt.', 'Yes - definitely.', 'You may rely on it.', 'As I see it, yes.', 'Most likely.', 'Outlook good.', 'Yes.', 'Signs point to yes.', 'Reply hazy, try again.', 'Ask again later.', 'Better not tell you now.', 'Cannot predict now.', 'Concentrate and ask again.', 'Don\'t count on it.', 'My reply is no.', 'My sources say no.', 'Outlook not so good.', 'Very doubtful.']; + const randomIndex = Math.floor(Math.random() * responses.length); + bot.sendMessage(responses[randomIndex]); + } +} diff --git a/chatBot/includes/chatBot.js b/chatBot/includes/chatBot.js new file mode 100644 index 0000000..82ab798 --- /dev/null +++ b/chatBot/includes/chatBot.js @@ -0,0 +1,44 @@ +import Hyperswarm from 'hyperswarm'; + +class chatBot { + constructor(topicBuffer, botName, onMessageReceived) { + this.topicBuffer = topicBuffer; + this.botName = botName; + this.swarm = new Hyperswarm(); + this.onMessageReceived = onMessageReceived; + this.setupSwarm(); + } + + setupSwarm() { + this.swarm.on('connection', (peer) => { + peer.on('data', message => this.onMessageReceived(peer, JSON.parse(message.toString()))); + peer.on('error', e => console.log(`Connection error: ${e}`)); + }); + + this.swarm.on('update', () => { + console.log(`Peers count: ${this.swarm.connections.size}`); + }); + } + + joinChatRoom() { + this.discovery = this.swarm.join(this.topicBuffer, { client: true, server: true }); + this.discovery.flushed().then(() => { + console.log(`Bot ${this.botName} joined the chat room.`); + }); + } + + sendMessage(message) { + console.log('Bot name:', this.botName); + const timestamp = Date.now(); // Generate timestamp + const peers = [...this.swarm.connections]; + const data = JSON.stringify({ name: this.botName, message, timestamp }); // Include timestamp + for (const peer of peers) peer.write(data); + } + + destroy() { + this.swarm.destroy(); + console.log(`Bot ${this.botName} disconnected.`); + } +} + +export default chatBot; diff --git a/index.html b/index.html new file mode 100644 index 0000000..032aa3e --- /dev/null +++ b/index.html @@ -0,0 +1,60 @@ + + + + + + LinkUp + + + + +
+
LinkUp
+
+ + + +
+
+
+ + + + +
+ + diff --git a/package.json b/package.json new file mode 100644 index 0000000..6a89c1f --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "chat", + "main": "index.html", + "type": "module", + "pear": { + "name": "chat", + "type": "desktop", + "gui": { + "backgroundColor": "#1F2430", + "height": 540, + "width": 720 + } + }, + "license": "Apache-2.0", + "devDependencies": { + "brittle": "^3.0.0" + }, + "scripts": { + "dev": "pear dev", + "test": "brittle test/*.test.js" + }, + "dependencies": { + "b4a": "^1.6.6", + "corestore": "^6.18.2", + "electron": "^30.0.8", + "hypercore-crypto": "^3.4.1", + "hyperdrive": "^11.8.1", + "hyperswarm": "^4.7.14", + "localdrive": "^1.11.4", + "serve-drive": "^5.0.8" + } +} diff --git a/style.css b/style.css new file mode 100644 index 0000000..3feefcf --- /dev/null +++ b/style.css @@ -0,0 +1,197 @@ +/* Base styles */ +body { + background-color: #121212; + color: #E0E0E0; + font-family: 'Roboto', sans-serif; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + height: 100vh; + overflow: hidden; + transition: background-color 0.3s ease, color 0.3s ease; +} + +/* Header styles */ +header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem; + background-color: #1F1F1F; + color: #FFFFFF; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5); + cursor: move; + -webkit-app-region: drag; +} + +.window-controls { + display: flex; + align-items: center; + -webkit-app-region: no-drag; +} + +.window-controls button { + background-color: #2E2E2E; + border: none; + color: #FFFFFF; + font-size: 1.25rem; + margin-left: 0.5rem; + padding: 0.5rem; + border-radius: 4px; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.window-controls button:hover { + background-color: #3E3E3E; +} + +/* Button and input styles */ +button, input { + border: 1px solid #B0D944; + background-color: #333; + color: #B0D944; + padding: 0.75rem; + font-family: 'Roboto Mono', monospace; + font-size: 1rem; + line-height: 1.5rem; + border-radius: 4px; + transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease; +} + +button:hover, input:hover { + background-color: #444; +} + +input::placeholder { + color: #A0A0A0; +} + +/* Main container styles */ +main { + display: flex; + height: 100%; + justify-content: center; + align-items: center; + flex: 1; +} + +.hidden { + display: none !important; +} + +/* Form container styles */ +#register, #setup { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 2rem; + background-color: #1F1F1F; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); +} + +#or { + margin: 1.5rem 0; + color: #B0D944; +} + +#loading { + align-self: center; + font-size: 1.5rem; + font-weight: bold; +} + +/* Chat container styles */ +#chat { + display: flex; + flex-direction: column; + width: 100%; + padding: 1rem; + background-color: #1E1E1E; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); +} + +#header { + margin-bottom: 1rem; +} + +#details { + display: flex; + justify-content: space-between; + padding: 1rem; + background-color: #2B2B2B; + border-radius: 4px; +} + +#messages { + flex: 1; + overflow-y: auto; + padding: 1rem; + background-color: #262626; + border-radius: 4px; +} + +#message-form { + display: flex; + margin-top: 1rem; +} + +#message { + flex: 1; + margin-right: 0.5rem; +} + +#user-info { + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 1rem; +} + +#user-info input { + margin: 0 0.5rem; +} + +#user-list { + margin-top: 1rem; + display: flex; + flex-direction: column; +} + +/* Message styles */ +.message { + display: flex; + align-items: center; /* Center vertically */ + margin-bottom: 1rem; +} + +.message img { + width: 40px; + height: 40px; + border-radius: 50%; + margin-right: 0.75rem; + border: 2px solid #B0D944; +} + +.message-content { + max-width: 70%; /* Adjusted width */ + background-color: #2E2E2E; + padding: 0.5rem; /* Adjusted padding */ + border-radius: 10px; /* Increased border radius */ + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); +} + +.message-header { + font-weight: bold; + color: #B0D944; /* Added color */ +} + +.message-time { + color: #A0A0A0; /* Adjusted color */ + font-size: 0.75rem; + margin-left: 0.5rem; +} diff --git a/test/index.test.js b/test/index.test.js new file mode 100644 index 0000000..d9b912d --- /dev/null +++ b/test/index.test.js @@ -0,0 +1 @@ +import test from 'brittle' // https://github.com/holepunchto/brittle