first commit

This commit is contained in:
Raven Scott 2024-06-03 19:23:14 -04:00
commit c58671dc7d
8 changed files with 595 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules
package-lock.json
storage

226
app.js Normal file
View File

@ -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();

32
chatBot/bot.js Normal file
View File

@ -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]);
}
}

View File

@ -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;

60
index.html Normal file
View File

@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LinkUp</title>
<link rel="stylesheet" type="text/css" href="style.css">
<script type="module" src="./app.js"></script>
</head>
<body>
<header>
<div>LinkUp</div>
<div class="window-controls">
<button id="minimize-button">-</button>
<button id="maximize-button">+</button>
<button id="close-button">x</button>
</div>
</header>
<main>
<div id="register" class="hidden">
<form id="register-form">
<input required id="reg-username" type="text" placeholder="Username" />
<input type="file" id="avatar-file" accept="image/*" style="display: none;" />
<button type="button" id="select-avatar">Select Avatar</button>
<input type="submit" value="Register" />
</form>
</div>
<div id="setup" class="hidden">
<div id="user-info">
<!-- User info will be populated dynamically -->
</div>
<label for="connection-topic">Enter connection topic:</label>
<input type="text" id="join-chat-room-topic" placeholder="Enter connection topic">
<button id="create-chat-room">Create</button>
<button id="join-chat-room">Join</button> <!-- Changed id -->
</div>
<div id="chat" class="hidden">
<div id="header">
<div id="details">
<div>
Topic: <span id="chat-room-topic"></span>
</div>
<div>
Peers: <span id="peers-count">0</span>
</div>
</div>
<div id="user-list">
<!-- User list will be populated here -->
</div>
</div>
<div id="messages"></div>
<form id="message-form">
<input id="message" type="text" />
<input type="submit" value="Send" />
</form>
</div>
<div id="loading" class="hidden">Loading ...</div>
</main>
</body>
</html>

32
package.json Normal file
View File

@ -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"
}
}

197
style.css Normal file
View File

@ -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;
}

1
test/index.test.js Normal file
View File

@ -0,0 +1 @@
import test from 'brittle' // https://github.com/holepunchto/brittle