first commit
This commit is contained in:
commit
c58671dc7d
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
package-lock.json
|
||||
storage
|
226
app.js
Normal file
226
app.js
Normal 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
32
chatBot/bot.js
Normal 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]);
|
||||
}
|
||||
}
|
44
chatBot/includes/chatBot.js
Normal file
44
chatBot/includes/chatBot.js
Normal 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
60
index.html
Normal 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
32
package.json
Normal 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
197
style.css
Normal 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
1
test/index.test.js
Normal file
@ -0,0 +1 @@
|
||||
import test from 'brittle' // https://github.com/holepunchto/brittle
|
Loading…
Reference in New Issue
Block a user