Compare commits

...

102 Commits

Author SHA1 Message Date
4ae2f4cbe8 Update package.json 2024-11-23 01:53:15 -05:00
7c874fab21 Auto Join Guild on Create 2024-07-08 00:57:58 -04:00
f125cad87c change screenshot 2024-07-08 00:41:46 -04:00
cb88b16606 Readd Markdown Support + Syntax Highlights and DOMPurify is used to sanitize HTML content to prevent XSS attacks. 2024-07-07 23:42:17 -04:00
94ea9b6840 Merge pull request 'Feat: Adding GUILDS!' (#12) from guilds into main
Reviewed-on: #12
2024-07-07 23:01:33 -04:00
7eddd45f79 Only auto play on peers 2024-07-07 22:49:15 -04:00
70bc69ddea auto play audio messages 2024-07-07 22:45:55 -04:00
aa70e42fdc change internal agent prefix 2024-07-07 22:40:22 -04:00
c530a8d69c Add back file menu 2024-07-07 22:39:31 -04:00
084cb02a11 fix icon message 2024-07-07 22:00:13 -04:00
a0c7eb1561 fix 2024-07-07 21:52:57 -04:00
43e9890235 readd updateIcon 2024-07-07 21:49:13 -04:00
ed4625275d init all guild topics on start 2024-07-07 21:40:52 -04:00
55875f468c more test 2024-07-07 21:30:09 -04:00
e0ad9c7067 more test 2024-07-07 21:21:55 -04:00
d423031cfb more test 2024-07-07 21:14:17 -04:00
181d011b8d more test 2024-07-07 21:08:30 -04:00
6cfbb36d35 Merge branch 'guilds' of git.ssh.surf:snxraven/LinkUp-P1P-Chat into guilds 2024-07-07 20:51:41 -04:00
da1bf28e3d test 2024-07-07 20:51:12 -04:00
a1b11f7ae6 test 2024-07-07 20:46:59 -04:00
de9e01e932 revert 8c0f7ebd0f
revert test
2024-06-22 21:19:28 +00:00
8c0f7ebd0f test 2024-06-22 16:56:04 -04:00
0e6d074c11 test 2024-06-22 16:53:07 -04:00
6ea37b8082 test 2024-06-22 16:52:05 -04:00
fd02cff23f test 2024-06-22 16:49:42 -04:00
cd92618351 Add the beginnings of guilds 2024-06-20 00:46:24 -04:00
9d759e5af3 Merge pull request 'fix: make avatars not draggable and selectable' (#11) from Cyber/LinkUp-P2P-Chat:main into main
Reviewed-on: #11

Good Job!
2024-06-15 17:50:47 +00:00
882e35f14d fix: make avatars not draggable and selectable 2024-06-15 15:42:35 +02:00
241cf62220 update readme, add screenshot 2024-06-15 03:57:42 -04:00
5080c37bcf update 2024-06-15 03:28:36 -04:00
7350d134a1 update 2024-06-15 03:20:52 -04:00
a6229b4b9a update readme 2024-06-15 03:19:54 -04:00
c477a988fd update readme 2024-06-15 03:18:52 -04:00
04c677e8e7 update readme 2024-06-15 03:16:15 -04:00
983d88002e Add current bot readme 2024-06-15 03:12:38 -04:00
150b69a2a2 update example paths 2024-06-15 03:04:41 -04:00
487bc13970 Adding proper support for audio messages via bot 2024-06-15 03:03:43 -04:00
2cf819553a correct support for sending files via the bot 2024-06-15 02:52:20 -04:00
49f4cc88ed fix usernames being sent 2024-06-14 15:38:45 -04:00
66ae839318 Fix usernames for bots 2024-06-14 15:21:41 -04:00
db88b09ba5 handling user icons via HyperDrive 2024-06-14 15:09:54 -04:00
1c6cc9a82e Merge pull request 'Reworked message format, added support for audio messages, made Message class' (#10) from MiTask/LinkUp-P2P-Chat:main into main
Reviewed-on: #10
2024-06-14 18:15:43 +00:00
f706e7d621 Made Client.js->setupSwarm() parsing messages more readable 2024-06-14 20:06:05 +03:00
42dccf9547 Actually just returning the url if its host is not 'localhost' 2024-06-14 19:58:40 +03:00
d4dc1b299d Ignoring 'updatePortInUrl' if port is 443 (SSL connection) 2024-06-14 19:53:51 +03:00
3e9a2101d5 I did this.botName and not bot.botName lol 2024-06-14 19:51:34 +03:00
2d0f97bc8f Oops i made async constructor 2024-06-14 19:50:10 +03:00
6cbfe34966 Testing bot avatars thingy 2024-06-14 19:49:26 +03:00
9b4357dd00 i hate js. it ignores all of my code and just sends 'messagetype' instead of 'type' 2024-06-14 19:25:17 +03:00
14aa129f6d Trying some changes to make bot messages and client mmessages have same format 2024-06-14 17:44:05 +03:00
d449249f41 Revert reverting 2024-06-14 17:39:16 +03:00
bb26b5c2a8 Revert all changes i did to app.js 2024-06-14 17:37:58 +03:00
52a319a1c0 Fixed my bots messages 2024-06-14 17:36:16 +03:00
70655ce901 So messages actually broke somehow lol 2024-06-14 17:34:06 +03:00
75be3a5b33 Reworked commands, added some funny stuff 2024-06-14 17:20:37 +03:00
d5650a9d7b I think i did it yay 2024-06-14 17:09:30 +03:00
2ff9c73585 It doesnt want to work 😭 2024-06-14 17:07:21 +03:00
d50e0df6a1 Time to do lots of debugging 😭 2024-06-14 17:05:00 +03:00
8262bd4eb7 Hm. Its pretty strange. Thats why i dont really like JavaScript 2024-06-14 17:04:08 +03:00
457bf01bc3 Trying out isPrototypeOf() function 2024-06-14 17:02:26 +03:00
feff30fecc Testing if sendMessage argument extends Message class 2024-06-14 17:00:35 +03:00
b3ccda32ea did lots of work on bot package 2024-06-14 16:43:17 +03:00
bd885bb591 Fix peerName in Message received 2024-06-14 04:46:19 -04:00
66dc7cdac2 Merge pull request 'Fix shutdown' (#9) from MiTask/LinkUp-P2P-Chat:main into main
Reviewed-on: #9
2024-06-14 08:44:09 +00:00
624222f726 Merge branch 'main' into main 2024-06-14 08:43:47 +00:00
01ca4aae5a Update chatBot/includes/Client.js 2024-06-14 08:43:27 +00:00
37ce0ff5bf Merge pull request 'Added graceful shutdown' (#8) from MiTask/LinkUp-P2P-Chat:main into main
Reviewed-on: #8

Perfecto
2024-06-14 08:37:47 +00:00
65576e82b3 Added proper shutting down 2024-06-14 08:35:54 +00:00
9eeb38ca3f Validate topicBuffer: Ensure topicBuffer is always defined and valid before converting it to a string. 2024-06-14 04:33:50 -04:00
d3792b4bd0 Merge pull request 'Up to date fork' (#1) from snxraven/LinkUp-P2P-Chat:main into main
Reviewed-on: MiTask/LinkUp-P2P-Chat#1
2024-06-14 08:32:25 +00:00
50a02e8dfd topic validation for the join box 2024-06-14 04:04:00 -04:00
ff0acb1a85 add readme 2024-06-14 03:17:31 -04:00
4d9d22060b Adding hyperdrive files management via ~list-files 2024-06-14 01:56:46 -04:00
f84a6cff1b better formatting for help 2024-06-14 01:28:08 -04:00
6a74c23f9c fixed 2024-06-14 01:19:58 -04:00
f50370d2e5 Missed something 2024-06-14 01:18:13 -04:00
59e72998c6 Fix message history by adding new clear function for internal command 2024-06-14 01:17:18 -04:00
458954c1e1 adding in new commands 2024-06-14 01:10:23 -04:00
b56d603b1e Add internal command handler with ping and clear commands to start 2024-06-14 00:57:05 -04:00
7dbcf17429 Duplicate message fix 2024-06-14 00:33:19 -04:00
98aa79f075 Prevent Message Duplicates in Message History 2024-06-14 00:08:32 -04:00
7a31202ac3 Fixing user avatars/icons concerning all of the major changes today 2024-06-13 23:12:31 -04:00
7061aa1e5e fixes 2024-06-13 22:41:43 -04:00
af37909b3f update 2024-06-13 22:29:05 -04:00
2faf2b6f78 Moving to MultiSwarm with proper connection tracking per topic 2024-06-13 21:44:30 -04:00
b1ea06018b Using swarm.connections.size 2024-06-13 21:05:10 -04:00
116b23d70f Move back to global peer count 2024-06-13 20:47:45 -04:00
0dbd441cd5 Merge branch 'main' of git.ssh.surf:snxraven/LinkUp-P2P-Chat 2024-06-13 20:05:21 -04:00
b50bd0bbab Room Counts done better 2024-06-13 20:02:01 -04:00
6b1b13f75e revert 0a3732fb07
revert Re adding per room topic counts
2024-06-13 23:41:30 +00:00
0a3732fb07 Re adding per room topic counts 2024-06-13 19:23:42 -04:00
5edcff29e1 revert 8c4a38ca8e
revert peer count per room
2024-06-13 20:06:49 +00:00
8c4a38ca8e peer count per room 2024-06-13 16:00:02 -04:00
3c803c811e Debug 2024-06-13 14:50:23 -04:00
6a7eb78d34 fixing talk button not turning red 2024-06-13 06:10:26 -04:00
76b00ce5b2 New dark mode theme, removing green, making UI uniform 2024-06-13 05:55:14 -04:00
145b5b728b Better style for the side bar + removing alert on copy 2024-06-13 05:24:44 -04:00
6b3f043332 first attempt at audio messages 2024-06-13 03:53:23 -04:00
060308ec92 Remove localStorage uses 2024-06-13 01:44:44 -04:00
7461dadf88 remove unused function 2024-06-13 01:38:10 -04:00
052fbecc20 Remove not needed join line 2024-06-12 00:08:34 -04:00
b9db313eff Fix chatBot 2024-06-11 17:40:02 -04:00
23 changed files with 2162 additions and 542 deletions

1
.gitignore vendored
View File

@ -7,3 +7,4 @@ chatBot/.env
chatBot/commands/ai.js
config.json
.idea
AIBot

134
README.md Normal file
View File

@ -0,0 +1,134 @@
`LinkUp` is currently in` active development` and `not fully launched`.
To launch the App in Dev Mode:
`git clone https://git.ssh.surf/snxraven/LinkUp-P2P-Chat.git`
`cd LinkUp-P2P-Chat`
`npm i; npm i pear -g;`
Lastly - run the app:
`pear dev -s /tmp/tmp_pear`
--------
# LinkUp Documentation
## Overview
LinkUp is a peer-to-peer chat application that allows users to create and join chat rooms, share files, and communicate with each other in real-time. The application uses the Hyperswarm, Hyperdrive, and Corestore libraries for decentralized networking and storage. This documentation provides a comprehensive guide to understanding, setting up, and using LinkUp.
![Screenshot](images/screenshot.png)
## Table of Contents
- [Installation](#installation)
- [Configuration](#configuration)
- [Usage](#usage)
- [Registering a User](#registering-a-user)
- [Creating a Chat Room](#creating-a-chat-room)
- [Joining a Chat Room](#joining-a-chat-room)
- [Sending Messages](#sending-messages)
- [Attaching Files](#attaching-files)
- [Recording and Sending Audio Messages](#recording-and-sending-audio-messages)
- [Commands](#commands)
- [Development](#development)
- [Project Structure](#project-structure)
- [Event Handling](#event-handling)
- [FAQs](#faqs)
## Configuration
LinkUp uses a configuration file (`config.json`) to store user information, chat room details, and registered users. This file is automatically created and managed by the application.
### Configuration File Structure
```json
{
"userName": "",
"userAvatar": "",
"rooms": [],
"registeredUsers": {}
}
```
- `userName`: The username of the registered user.
- `userAvatar`: The URL of the user's avatar image.
- `rooms`: An array of chat rooms the user has joined or created.
- `registeredUsers`: An object containing registered usernames and their avatar URLs.
## Usage
### Registering a User
1. Launch the application.
2. If the configuration file does not exist, you will be prompted to register.
3. Enter a username and optionally select an avatar image.
4. Submit the registration form to complete the process.
### Creating a Chat Room
1. Click on the "Create/Join Room" button in the sidebar.
2. Click on "Create Room".
3. A new room will be created with a random topic.
### Joining a Chat Room
1. Click on the "Create/Join Room" button in the sidebar.
2. Enter the topic of the room you wish to join in the "Topic" field.
3. Click on "Join Room".
### Sending Messages
1. Select a chat room from the sidebar.
2. Enter your message in the message input box at the bottom of the chat window.
3. Press `Enter` or click on the "Send" button to send the message.
### Attaching Files
1. Click on the "Attach File" button in the message form.
2. Select a file from your device.
3. The file will be uploaded and a download link will be shared in the chat.
### Recording and Sending Audio Messages
1. Click and hold the "Talk" button to start recording an audio message.
2. Release the button to stop recording and send the audio message.
## Commands
LinkUp supports several commands that can be entered in the chat input box:
- `~clear`: Clears the chat messages.
- `~ping`: Responds with "pong".
- `~help`: Lists available commands.
- `~join [topic]`: Joins a chat room with the specified topic.
- `~leave`: Leaves the current chat room.
- `~create [alias]`: Creates a new chat room with the specified alias.
- `~list-files`: Lists all files available in the current chat room.
## Development
### Project Structure
- `app.js`: Main application logic.
- `commands.js`: Command handling logic.
- `index.html`: HTML structure of the application.
- `style.css`: CSS for styling the application.
- `config.json`: Configuration file for storing user and room data.
### Event Handling
LinkUp uses an `EventEmitter` to handle various events such as receiving messages, joining rooms, and updating the peer count. Event listeners are set up during the initialization process.
### Key Functions
- `initialize()`: Initializes the application, sets up event listeners, and loads configuration.
- `registerUser()`: Handles user registration.
- `createChatRoom()`: Creates a new chat room.
- `joinChatRoom()`: Joins an existing chat room.
- `sendMessage()`: Sends a text message.
- `handleFileInput()`: Handles file attachments.
- `setupEventListeners()`: Sets up event listeners for UI interactions.
## FAQs
**Q: How do I change my username or avatar?**
A: Currently, changing username or avatar after registration is not supported. You would need to delete the `config.json` file and restart the application to re-register.
**Q: How can I see the list of files shared in a room?**
A: Use the `~list-files` command to see all files shared in the current chat room.
**Q: How do I leave a chat room?**
A: Click on the "Leave Room" button in the chat window.
For more information, please refer to the [Pears Documentation](https://docs.pears.com/).
Feel free to contribute to this project by submitting issues or pull requests on [Git](https://git.ssh.surf/snxraven/LinkUp-P2P-Chat).

1310
app.js

File diff suppressed because it is too large Load Diff

BIN
assets/agent.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

220
chatBot/README.md Normal file
View File

@ -0,0 +1,220 @@
# Bot System Documentation
## Overview
This document provides a comprehensive guide to the bot system, detailing its structure, functionalities, and how to set it up. This bot system leverages the Hyperswarm network to enable decentralized communication and supports various types of messages, including text, files, and audio.
## Table of Contents
1. [Introduction](#introduction)
2. [Getting Started](#getting-started)
- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [Configuration](#configuration)
3. [Bot Architecture](#bot-architecture)
- [Client Class](#client-class)
- [Message Classes](#message-classes)
4. [Command Handling](#command-handling)
5. [Event Handling](#event-handling)
6. [Running the Bot](#running-the-bot)
7. [API Reference](#api-reference)
## Introduction
This bot system is designed for decentralized chat rooms using the Hyperswarm network. It supports multiple message types and dynamic command handling, making it highly customizable and extensible.
## Getting Started
### Prerequisites
- Node.js (version 14.x or later)
- npm (version 6.x or later)
### Installation
1. Clone the repository:
```bash
git clone https://git.ssh.surf/snxraven/LinkUp-P2P-Chat.git
cd LinkUp-P2P-Chat
```
2. Install the dependencies:
```bash
npm install
```
3. Change to bot directory
```bash
cd chatBot
```
4. Run the bot:
```
node bot.js
```
### Configuration
Create a `.env` file in the root directory and add the following environment variables:
```
BOT_NAME=MyBot
ON_READY_MESSAGE=Bot is ready and operational!
LINKUP_ROOM_ID=<room_id>
```
## Bot Architecture
### Client Class
The `Client` class is the core component of the bot system. It handles connections to the Hyperswarm network, manages message sending and receiving, and emits events for various actions.
#### Constructor
```javascript
constructor(botName)
```
- `botName`: The name of the bot.
#### Methods
- `initializeServeDrive()`: Initializes the ServeDrive for serving files and audio.
- `getRandomPort()`: Returns a random port number.
- `fetchAvatar(filePath)`: Fetches and sets the bot's avatar from a local file.
- `setupSwarm()`: Sets up the Hyperswarm network and connection handlers.
- `joinChatRoom(chatRoomID)`: Joins a specified chat room.
- `sendTextMessage(message)`: Sends a text message.
- `sendFileMessage(filePath, fileType)`: Sends a file message.
- `sendAudioMessage(filePath, audioType)`: Sends an audio message.
- `sendMessage(message)`: Sends a generic message.
- `destroy()`: Disconnects the bot and shuts down the Hyperswarm network.
### Message Classes
The bot system supports various message types through dedicated classes, all extending the base `Message` class.
#### Message Class
```javascript
class Message {
constructor(messageType, peerName, peerAvatar, topic, timestamp)
}
```
#### TextMessage Class
```javascript
class TextMessage extends Message {
constructor(peerName, peerAvatar, topic, timestamp, message)
static new(bot, message)
}
```
#### FileMessage Class
```javascript
class FileMessage extends Message {
constructor(peerName, peerAvatar, topic, timestamp, fileName, fileUrl, fileType, fileData)
static new(bot, fileName, fileUrl, fileType, fileBuffer)
}
```
#### AudioMessage Class
```javascript
class AudioMessage extends Message {
constructor(peerName, peerAvatar, topic, timestamp, audioUrl, audioType, audioData)
static new(bot, audioUrl, audioType, audioBuffer)
}
```
#### IconMessage Class
```javascript
class IconMessage extends Message {
constructor(peerName, peerAvatar, timestamp)
static new(bot, avatarBuffer)
}
```
## Command Handling
Commands are dynamically loaded from the `commands` directory. Each command is a JavaScript module with a default export containing a `handler` function.
### Example Command Module
```javascript
// commands/example.js
export default {
handler: (bot, args, message) => {
bot.sendTextMessage(`Example command executed with args: ${args.join(' ')}`);
}
};
```
## Event Handling
The bot uses Node.js `EventEmitter` to handle events. Key events include:
- `onMessage`: Triggered when a new message is received.
- `onFile`: Triggered when a file message is received.
- `onAudio`: Triggered when an audio message is received.
- `onIcon`: Triggered when an icon message is received.
- `onError`: Triggered when there is a connection error.
- `onBotJoinRoom`: Triggered when the bot joins a room.
### Example Event Listener
```javascript
bot.on('onMessage', (peer, message) => {
console.log(`Message from ${message.peerName}: ${message.message}`);
});
```
## Running the Bot
To start the bot, run the following command:
```bash
node bot.js
```
Ensure that the `.env` file is correctly configured and that all necessary dependencies are installed.
## API Reference
### Client
- `constructor(botName)`: Creates a new instance of the Client.
- `initializeServeDrive()`: Initializes ServeDrive.
- `getRandomPort()`: Generates a random port number.
- `fetchAvatar(filePath)`: Fetches the bot's avatar.
- `setupSwarm()`: Sets up the Hyperswarm network.
- `joinChatRoom(chatRoomID)`: Joins a specified chat room.
- `sendTextMessage(message)`: Sends a text message.
- `sendFileMessage(filePath, fileType)`: Sends a file message.
- `sendAudioMessage(filePath, audioType)`: Sends an audio message.
- `sendMessage(message)`: Sends a generic message.
- `destroy()`: Disconnects the bot.
### TextMessage
- `constructor(peerName, peerAvatar, topic, timestamp, message)`: Creates a new text message.
- `static new(bot, message)`: Creates a new text message instance.
### FileMessage
- `constructor(peerName, peerAvatar, topic, timestamp, fileName, fileUrl, fileType, fileData)`: Creates a new file message.
- `static new(bot, fileName, fileUrl, fileType, fileBuffer)`: Creates a new file message instance.
### AudioMessage
- `constructor(peerName, peerAvatar, topic, timestamp, audioUrl, audioType, audioData)`: Creates a new audio message.
- `static new(bot, audioUrl, audioType, audioBuffer)`: Creates a new audio message instance.
### IconMessage
- `constructor(peerName, peerAvatar, timestamp)`: Creates a new icon message.
- `static new(bot, avatarBuffer)`: Creates a new icon message instance.

BIN
chatBot/assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -39,10 +39,9 @@ loadCommands().then(commands => {
// We use Event Emitter here to handle new messages
bot.on('onMessage', (peer, message) => {
console.log(`Message received from ${message.peerName} at ${new Date(message.timestamp).toLocaleTimeString()}: ${message.message}`);
console.log(message);
console.log(`Message received from ${message.name}@${message.topic} at ${new Date(message.timestamp).toLocaleTimeString()}: ${message.message}`);
// Check if the message starts with a command prefix
if (message.message.startsWith('!')) {
// Extract the command and arguments
@ -53,7 +52,10 @@ loadCommands().then(commands => {
// If the command exists, execute its handler
if (commandHandler && typeof commandHandler.handler === 'function') {
console.log(`Executing command: ${command} with arguments: ${args}`);
commandHandler.handler(bot, args, message);
} else {
console.warn(`Command not found: ${command}`);
}
}
});
@ -64,7 +66,10 @@ loadCommands().then(commands => {
});
bot.joinChatRoom(process.env.LINKUP_ROOM_ID);
bot.joinChatRoom(process.env.LINKUP_ROOM_ID2);
const iconPath = path.join(new URL('./assets/icon.png', import.meta.url).pathname);
bot.fetchAvatar(iconPath); // Fetch the avatar from local file
}).catch(error => {
console.error('Error loading commands:', error);
});

View File

@ -1,7 +1,10 @@
// ping.js
export default {
handler: function(bot, args, message) {
bot.sendTextMessage('Pong!');
// Specify the path to the file you want to send
const filePath = '/Users/raven/chat/chatBot/bot.js'; // Replace with the actual file path
const fileType = 'text/html'; // Specify the correct file type
// Send the file message using the bot instance
bot.sendFileMessage(filePath, fileType);
}
};

View File

@ -0,0 +1,10 @@
export default {
handler: function(bot, args, message) {
// Specify the path to the audio file you want to send
const audioFilePath = '/to/file/path.mp3'; // Replace with the actual audio file path
const audioType = 'audio';
// Send the audio message using the bot instance
bot.sendAudioMessage(audioFilePath, audioType);
}
};

View File

@ -2,4 +2,4 @@ LINKUP_ROOM_ID=
BOT_NAME=myBot
ON_READY_MESSAGE="Hello, I am a bot. I am here to help you. Please type 'help' to see the list of commands."
ON_READY_MESSAGE="Hello, I am a bot. I am here to help you."

View File

@ -1,8 +1,16 @@
import path from 'path';
import Hyperswarm from 'hyperswarm';
import EventEmitter from 'node:events';
import b4a from "b4a";
import TextMessage from "./TextMessage.js";
import FileMessage from "./FileMessage.js";
import TextMessage from "./message/TextMessage.js";
import FileMessage from "./message/FileMessage.js";
import AudioMessage from "./message/AudioMessage.js";
import Message from "./message/Message.js";
import IconMessage from "./message/IconMessage.js";
import Corestore from 'corestore';
import Hyperdrive from 'hyperdrive';
import fs from 'fs';
import ServeDrive from 'serve-drive';
class Client extends EventEmitter {
constructor(botName) {
@ -12,33 +20,108 @@ class Client extends EventEmitter {
this.swarm = new Hyperswarm();
this.joinedRooms = new Set(); // Track the rooms the bot has joined
this.currentTopic = null; // Track the current topic
// Initialize Corestore and Hyperdrive
this.storagePath = './storage/';
this.store = new Corestore(this.storagePath);
this.drive = new Hyperdrive(this.store);
// Initialize ServeDrive
this.servePort = null;
this.initializeServeDrive();
this.setupSwarm();
process.on('exit', () => {
console.log('EXIT signal received. Shutting down HyperSwarm...');
this.destroy();
});
process.on('SIGTERM', async () => {
console.log('SIGTERM signal received. Shutting down HyperSwarm...');
await this.destroy();
console.log('HyperSwarm was shut down. Exiting the process with exit code 0.');
process.exit(0);
});
process.on('SIGINT', async () => {
console.log('SIGINT signal received. Shutting down HyperSwarm...');
await this.destroy();
console.log('HyperSwarm was shut down. Exiting the process with exit code 0.');
process.exit(0);
});
}
async initializeServeDrive() {
try {
this.servePort = this.getRandomPort();
const serve = new ServeDrive({
port: this.servePort,
get: ({ key, filename, version }) => this.drive
});
await serve.ready();
console.log('ServeDrive listening on port:', this.servePort);
} catch (error) {
console.error('Error initializing ServeDrive:', error);
}
}
getRandomPort() {
return Math.floor(Math.random() * (65535 - 49152 + 1)) + 49152;
}
async fetchAvatar(filePath) {
try {
await this.drive.ready();
const iconBuffer = fs.readFileSync(filePath);
await this.drive.put(`/icons/${this.botName}.png`, iconBuffer);
this.botAvatar = `http://localhost:${this.servePort}/icons/${this.botName}.png`;
// Cache the icon message
this.iconMessage = IconMessage.new(this, iconBuffer);
} catch (error) {
console.error('Error fetching avatar:', error);
}
}
setupSwarm() {
this.swarm.on('connection', (peer) => {
peer.on('data', message => {
// Send the cached icon message to the new peer
if (this.iconMessage) {
peer.write(this.iconMessage.toJsonString());
}
peer.on('data', async message => {
const messageObj = JSON.parse(message.toString());
if (this.joinedRooms.has(messageObj.topic)) { // Process message only if it is from a joined room
this.currentTopic = messageObj.topic; // Set the current topic from the incoming message
if (messageObj.type === "message")
this.emit('onMessage', peer, new TextMessage(messageObj.name, messageObj.avatar, messageObj.topic, messageObj.message, messageObj.timestamp));
if (messageObj.type === "file")
this.emit('onFile', peer, new FileMessage(messageObj.name, messageObj.fileName, messageObj.fileUrl, messageObj.fileType, messageObj.avatar, messageObj.topic, messageObj.timestamp));
const msgType = messageObj.type;
const peerName = messageObj.name; // Changed from name to userName
const peerAvatar = messageObj.avatar;
const timestamp = messageObj.timestamp;
if (messageObj.type === "icon")
this.emit('onIcon', peer, messageObj);
if (msgType === "message")
this.emit('onMessage', peer, new TextMessage(peerName, peerAvatar, this.currentTopic, timestamp, messageObj.message));
if (msgType === "file") {
const fileBuffer = await this.drive.get(`/files/${messageObj.fileName}`);
this.emit('onFile', peer, new FileMessage(peerName, peerAvatar, this.currentTopic, timestamp, messageObj.fileName, `http://localhost:${this.servePort}/files/${messageObj.fileName}`, messageObj.fileType, messageObj.fileData));
}
if (msgType === "icon")
this.emit('onIcon', peer, new IconMessage(peerName, peerAvatar, timestamp));
if (msgType === "audio") {
const audioBuffer = await this.drive.get(`/audio/${messageObj.audioName}`);
this.emit('onAudio', peer, new AudioMessage(peerName, peerAvatar, this.currentTopic, timestamp, `http://localhost:${this.servePort}/audio/${messageObj.audioName}`, messageObj.audioType, messageObj.audioData));
}
}
});
peer.on('error', e => {
this.emit('onError', e);
console.error(`Connection error: ${e}`)
console.error(`Connection error: ${e}`);
});
});
@ -48,6 +131,10 @@ class Client extends EventEmitter {
}
joinChatRoom(chatRoomID) {
if (!chatRoomID || typeof chatRoomID !== 'string') {
return console.error("Invalid chat room ID!");
}
this.joinedRooms.add(chatRoomID); // Add the room to the list of joined rooms
this.currentTopic = chatRoomID; // Store the current topic
this.discovery = this.swarm.join(Buffer.from(chatRoomID, 'hex'), { client: true, server: true });
@ -58,18 +145,65 @@ class Client extends EventEmitter {
}
sendTextMessage(message) {
console.log(`Preparing to send text message: ${message}`);
this.sendMessage(TextMessage.new(this, message));
}
sendMessage(message) {
console.log('Bot name:', this.botName);
const data = message.toJsonString();
const peers = [...this.swarm.connections];
for (const peer of peers) peer.write(data);
async sendFileMessage(filePath, fileType) {
try {
await this.drive.ready();
const fileBuffer = fs.readFileSync(filePath);
const fileName = path.basename(filePath);
await this.drive.put(`/files/${fileName}`, fileBuffer);
const fileUrl = `http://localhost:${this.servePort}/files/${fileName}`;
const fileMessage = FileMessage.new(this, fileName, fileUrl, fileType, fileBuffer); // Pass fileBuffer to the new method
this.sendMessage(fileMessage);
} catch (error) {
console.error('Error sending file message:', error);
}
}
destroy() {
this.swarm.destroy();
async sendAudioMessage(filePath, audioType) {
try {
await this.drive.ready();
const audioBuffer = fs.readFileSync(filePath);
const audioName = path.basename(filePath);
await this.drive.put(`/audio/${audioName}`, audioBuffer);
const audioUrl = `http://localhost:${this.servePort}/audio/${audioName}`;
const audioMessage = AudioMessage.new(this, audioUrl, audioType, audioBuffer); // Pass audioBuffer to the new method
this.sendMessage(audioMessage);
} catch (error) {
console.error('Error sending audio message:', error);
}
}
sendMessage(message) {
if (!(message instanceof Message)) {
console.error(`message does not extend Message class (TextMessage, FileMessage, AudioMessage).`, message);
return;
}
console.log("Sending message:", message);
const data = message.toJsonString();
const peers = [...this.swarm.connections];
if (peers.length === 0) {
console.warn("No active peer connections found.");
return;
}
console.log(`Sending message to ${peers.length} peers.`);
for (const peer of peers) {
try {
peer.write(data);
console.log(`Message sent to peer: ${peer.remoteAddress}`);
} catch (error) {
console.error(`Failed to send message to peer: ${peer.remoteAddress}`, error);
}
}
}
async destroy() {
await this.swarm.destroy();
console.log(`Bot ${this.botName} disconnected.`);
}
}

View File

@ -1,26 +0,0 @@
class FileMessage {
public FileMessage(peerName, fileName, fileUrl, fileType, peerAvatar, topic, timestamp) {
this.peerName = peerName;
this.fileName = fileName;
this.fileUrl = fileUrl;
this.fileType = fileType;
this.peerAvatar = peerAvatar;
this.topic = topic;
this.timestamp = timestamp;
}
public toJsonString() {
return JSON.stringify({
type: 'file',
name: this.peerName,
fileName: this.fileName,
fileUrl: this.fileUrl,
fileType: this.fileType,
avatar: this.peerAvatar,
topic: this.topic,
timestamp: this.timestamp,
});
}
}
export default FileMessage;

View File

@ -1,26 +0,0 @@
class TextMessage {
public TextMessage(peerName, peerAvatar, topic, message, timestamp) {
this.peerName = peerName;
this.peerAvatar = peerAvatar;
this.topic = topic;
this.message = message;
this.timestamp = timestamp;
}
public toJsonString() {
return JSON.stringify({
type: 'message',
name: this.peerName,
message: this.message,
avatar: this.peerAvatar,
topic: this.topic,
timestamp: this.timestamp
});
}
public static new(bot, message) {
return new TextMessage(bot.botName, "", bot.currentTopic, message, Date.now());
}
}
export default TextMessage;

View File

@ -0,0 +1,27 @@
import Message from "./Message.js";
import b4a from "b4a";
class AudioMessage extends Message {
constructor(peerName, peerAvatar, topic, timestamp, audioUrl, audioType, audioData) {
super("audio", peerName, peerAvatar, topic, timestamp);
this.audioUrl = audioUrl;
this.audioType = audioType;
this.audioData = audioData; // Add audio data property
}
toJsonString() {
return JSON.stringify({
...this.toJson(),
audioUrl: this.audioUrl,
audioType: this.audioType,
audio: this.audioData // Include audio data in JSON
});
}
static new(bot, audioUrl, audioType, audioBuffer) {
const audioData = b4a.toString(audioBuffer, 'base64'); // Convert audio buffer to base64
return new AudioMessage(bot.botName, bot.botAvatar, bot.currentTopic, Date.now(), audioUrl, audioType, audioData);
}
}
export default AudioMessage;

View File

@ -0,0 +1,29 @@
import Message from "./Message.js";
import b4a from "b4a";
class FileMessage extends Message {
constructor(peerName, peerAvatar, topic, timestamp, fileName, fileUrl, fileType, fileData) {
super("file", peerName, peerAvatar, topic, timestamp);
this.fileName = fileName;
this.fileUrl = fileUrl;
this.fileType = fileType;
this.fileData = fileData; // Add file data property
}
toJsonString() {
return JSON.stringify({
...this.toJson(),
fileName: this.fileName,
fileUrl: this.fileUrl,
fileType: this.fileType,
file: this.fileData // Include file data in JSON
});
}
static new(bot, fileName, fileUrl, fileType, fileBuffer) {
const fileData = b4a.toString(fileBuffer, 'base64'); // Convert file buffer to base64
return new FileMessage(bot.botName, bot.botAvatar, bot.currentTopic, Date.now(), fileName, fileUrl, fileType, fileData);
}
}
export default FileMessage;

View File

@ -0,0 +1,20 @@
import Message from "./Message.js";
import b4a from "b4a";
class IconMessage extends Message {
constructor(peerName, peerAvatar, timestamp) {
super("icon", peerName, peerAvatar, null, timestamp);
}
toJsonString() {
return JSON.stringify({
...this.toJson()
});
}
static new(bot, avatarBuffer) {
return new IconMessage(bot.botName, b4a.toString(avatarBuffer, 'base64'), Date.now());
}
}
export default IconMessage;

View File

@ -0,0 +1,21 @@
class Message {
constructor(messageType, peerName, peerAvatar, topic, timestamp) {
this.type = messageType;
this.peerName = peerName;
this.peerAvatar = peerAvatar;
this.topic = topic;
this.timestamp = timestamp;
}
toJson() {
return {
type: this.type,
name: this.peerName,
avatar: this.peerAvatar,
topic: this.topic,
timestamp: this.timestamp
};
}
}
export default Message;

View File

@ -0,0 +1,21 @@
import Message from "./Message.js";
class TextMessage extends Message {
constructor(peerName, peerAvatar, topic, timestamp, message) {
super("message", peerName, peerAvatar, topic, timestamp);
this.message = message;
}
toJsonString() {
return JSON.stringify({
...this.toJson(),
message: this.message,
});
}
static new(bot, message) {
return new TextMessage(bot.botName, bot.botAvatar, bot.currentTopic, Date.now(), message);
}
}
export default TextMessage;

104
commands.js Normal file
View File

@ -0,0 +1,104 @@
import b4a from 'b4a';
import fs from 'fs';
const agentAvatarPath = './assets/agent.png';
let agentAvatar = '';
// Load the agent avatar once when the module is imported
if (fs.existsSync(agentAvatarPath)) {
const avatarBuffer = fs.readFileSync(agentAvatarPath);
agentAvatar = `data:image/png;base64,${b4a.toString(avatarBuffer, 'base64')}`;
}
export default async function handleCommand(command, context) {
const { eventEmitter, currentTopic, clearMessages, addMessage, joinRoom, leaveRoom, createRoom, listFiles, deleteFile, servePort } = context;
console.log("Context received in handleCommand:", context); // Add logging
const args = command.trim().split(' ');
const cmd = args[0].toLowerCase();
const restArgs = args.slice(1).join(' ');
console.log("Command received:", cmd); // Add logging
console.log("Current topic:", currentTopic); // Add logging to check the current topic
switch (cmd) {
case '>clear':
clearMessages(currentTopic);
break;
case '>ping':
addMessage('LinkUp', 'pong', agentAvatar, currentTopic);
break;
case '>help':
addMessage('LinkUp', 'Available commands:\n- >clear\n- >ping\n- >help\n- >join [topic]\n- >leave\n- >create [alias]\n- >list-files', agentAvatar, currentTopic);
break;
case '>join':
if (restArgs) {
await joinRoom(currentTopic, restArgs);
} else {
addMessage('LinkUp', 'Usage: >join [topic]', agentAvatar, currentTopic);
}
break;
case '>leave':
leaveRoom(currentTopic);
break;
case '>create':
if (restArgs) {
await createRoom(currentTopic, restArgs);
} else {
addMessage('LinkUp', 'Usage: >create [alias]', agentAvatar, currentTopic);
}
break;
case '>list-files':
const files = await listFiles();
const fileList = files.length > 0 ? files.map(file => `- ${file}`).join('\n') : 'No files available';
addMessage('LinkUp', `Available files:\n${fileList}`, agentAvatar, currentTopic);
// Render the file list with delete buttons
renderFileList(files, deleteFile, servePort);
break;
default:
addMessage('LinkUp', `Unknown command: ${cmd}`, agentAvatar, currentTopic);
console.log('Unknown command:', command);
}
}
function renderFileList(files, deleteFile, servePort) {
const container = document.querySelector('#messages');
if (!container) {
console.error('Element #messages not found');
return;
}
const fileList = document.createElement('ul');
files.forEach(file => {
const listItem = document.createElement('li');
const fileButton = document.createElement('button');
fileButton.textContent = file.trim();
fileButton.onclick = () => downloadFile(file.trim(), servePort);
const deleteButton = document.createElement('button');
deleteButton.textContent = 'Delete';
deleteButton.onclick = async () => {
await deleteFile(file);
listItem.remove();
};
listItem.appendChild(fileButton);
listItem.appendChild(deleteButton);
fileList.appendChild(listItem);
});
container.appendChild(fileList);
}
function downloadFile(filename, servePort) {
const url = `http://localhost:${servePort}/files/${filename}`;
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}

BIN
images/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@ -17,10 +17,10 @@
</header>
<main>
<div id="sidebar">
<ul id="room-list">
<!-- Room list will be populated dynamically -->
<ul id="guild-list" class="list-group">
<!-- Guild list will be populated dynamically -->
</ul>
<button id="toggle-setup-btn">Create/Join Room</button>
<button id="toggle-setup-btn">Create/Join Guild</button>
</div>
<div id="content">
<div id="register" class="hidden">
@ -35,12 +35,12 @@
<div id="user-info">
<!-- User info will be populated dynamically -->
</div>
<button id="create-chat-room">Create Room</button>
<button id="create-guild-btn">Create Guild</button>
<div id="or">- or -</div>
<div id="join-chat-room-container">
<label for="connection-topic">Topic:</label>
<input type="text" id="join-chat-room-topic" placeholder="connection topic">
<button id="join-chat-room">Join Room</button>
<div id="join-guild-container">
<label for="join-guild-topic">Guild Topic:</label>
<input type="text" id="join-guild-topic" placeholder="guild topic">
<button id="join-guild">Join Guild</button>
</div>
</div>
<div id="chat" class="hidden">
@ -49,11 +49,10 @@
<div style="display: inline;">
<strong><span id="chat-room-name"></span></strong> | <a href="#" id="copy-topic-link" class="mini-button">Copy Topic</a>
<span id="chat-room-topic" style="display: none;"></span>
<span id="chat-guild-topic" style="display: none;"></span>
<span id="chat-guild-name" style="display: none;"></span>
</div>
</div>
<div id="user-list">
<!-- User list will be populated here -->
</div>
</div>
<div id="messages-container">
<div id="messages"></div>
@ -62,6 +61,7 @@
<textarea id="message" rows="4"></textarea>
<input type="file" id="file-input" style="display:none;">
<button type="button" id="attach-file">Attach File</button>
<button type="button" id="talk-btn">Talk</button>
<input type="submit" value="Send" />
</form>
<button id="remove-room-btn">Leave Room</button>
@ -69,11 +69,46 @@
<div id="loading" class="hidden">Loading ...</div>
</div>
</main>
<!-- Create Guild Modal -->
<div id="create-guild-modal" class="modal hidden">
<div class="modal-content">
<span class="close-btn" id="close-create-guild-modal">&times;</span>
<h2>Create Guild</h2>
<form id="create-guild-form">
<label for="guild-name">Guild Name:</label>
<input type="text" id="guild-name" required>
<button type="submit">Create</button>
</form>
</div>
</div>
<!-- Manage Guild Modal -->
<div id="manage-guild-modal" class="modal hidden">
<div class="modal-content">
<span class="close-btn" id="close-manage-guild-modal">&times;</span>
<h2>Manage Guild</h2>
<div id="guild-info">
<!-- Guild info will be populated dynamically -->
</div>
<button id="copy-guild-id" class="mini-button">Copy Guild ID</button>
<form id="add-room-form">
<label for="room-name">Room Name:</label>
<input type="text" id="room-name" required>
<button type="submit">Add Room</button>
</form>
<ul id="room-list">
<!-- Room list will be populated dynamically -->
</ul>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const messageInput = document.getElementById('message');
const copyTopicLink = document.getElementById('copy-topic-link');
const chatRoomTopic = document.getElementById('chat-room-topic');
const copyGuildIdButton = document.getElementById('copy-guild-id');
if (messageInput) {
messageInput.addEventListener('keydown', function(event) {
@ -90,7 +125,7 @@
if (chatRoomTopic) {
const topic = chatRoomTopic.innerText;
navigator.clipboard.writeText(topic).then(() => {
alert('Topic copied to clipboard!');
console.log('Topic copied to clipboard:', topic);
}).catch(err => {
console.error('Failed to copy topic:', err);
});
@ -100,32 +135,123 @@
});
}
const switchRoom = (topic) => {
console.log('Switching to room:', topic); // Debugging log
const chatRoomTopic = document.querySelector('#chat-room-topic');
const chatRoomName = document.querySelector('#chat-room-name');
if (chatRoomTopic) {
chatRoomTopic.innerText = topic; // Set full topic here
} else {
console.error('Element #chat-room-topic not found');
if (copyGuildIdButton) {
copyGuildIdButton.addEventListener('click', function(event) {
event.preventDefault();
const guildTopic = document.getElementById('manage-guild-modal').dataset.guildTopic;
navigator.clipboard.writeText(guildTopic).then(() => {
console.log('Guild ID copied to clipboard:', guildTopic);
}).catch(err => {
console.error('Failed to copy guild ID:', err);
});
});
}
// Show chat UI elements
document.querySelector('#chat').classList.remove('hidden');
document.querySelector('#setup').classList.add('hidden');
};
const roomList = document.querySelector('#room-list');
if (roomList) {
roomList.addEventListener('click', function(event) {
const roomItem = event.target.closest('li');
const guildList = document.querySelector('#guild-list');
if (guildList) {
guildList.addEventListener('click', function(event) {
const roomItem = event.target.closest('.room-item');
if (roomItem) {
switchRoom(roomItem.dataset.topic);
const guildTopic = roomItem.dataset.guildTopic;
const roomTopic = roomItem.dataset.topic;
switchRoom(guildTopic, roomTopic);
}
});
}
const createGuildBtn = document.getElementById('create-guild-btn');
const createGuildModal = document.getElementById('create-guild-modal');
const closeCreateGuildModal = document.getElementById('close-create-guild-modal');
const createGuildForm = document.getElementById('create-guild-form');
createGuildBtn.addEventListener('click', () => {
createGuildModal.classList.remove('hidden');
});
closeCreateGuildModal.addEventListener('click', () => {
createGuildModal.classList.add('hidden');
});
createGuildForm.addEventListener('submit', (event) => {
event.preventDefault();
const guildName = document.getElementById('guild-name').value.trim();
if (guildName) {
createGuild(guildName);
createGuildModal.classList.add('hidden');
}
});
const joinGuildBtn = document.getElementById('join-guild');
joinGuildBtn.addEventListener('click', async (event) => {
const guildTopic = document.getElementById('join-guild-topic').value.trim();
if (guildTopic) {
await joinGuildRequest(guildTopic);
}
});
const manageGuildModal = document.getElementById('manage-guild-modal');
const closeManageGuildModal = document.getElementById('close-manage-guild-modal');
const addRoomForm = document.getElementById('add-room-form');
const roomNameInput = document.getElementById('room-name');
closeManageGuildModal.addEventListener('click', () => {
manageGuildModal.classList.add('hidden');
});
addRoomForm.addEventListener('submit', (event) => {
event.preventDefault();
const roomName = roomNameInput.value.trim();
const guildTopic = manageGuildModal.dataset.guildTopic;
if (roomName && guildTopic) {
addRoomToGuild(guildTopic, roomName);
roomNameInput.value = '';
}
});
});
function createGuild(guildName) {
const event = new CustomEvent('createGuild', { detail: { guildName } });
document.dispatchEvent(event);
}
function addRoomToGuild(guildTopic, roomName) {
const event = new CustomEvent('addRoomToGuild', { detail: { guildTopic, roomName } });
document.dispatchEvent(event);
}
function manageGuild(guildTopic) {
const event = new CustomEvent('manageGuild', { detail: { guildTopic } });
document.dispatchEvent(event);
}
function switchRoom(guildTopic, roomTopic) {
const event = new CustomEvent('switchRoom', { detail: { guildTopic, roomTopic } });
document.dispatchEvent(event);
}
function openManageGuildModal(guildTopic) {
const manageGuildModal = document.getElementById('manage-guild-modal');
const guildInfo = document.getElementById('guild-info');
const roomList = document.getElementById('room-list');
manageGuildModal.dataset.guildTopic = guildTopic;
guildInfo.innerHTML = `<h3>${config.guilds[guildTopic].alias}</h3>`;
roomList.innerHTML = '';
config.guilds[guildTopic].rooms.forEach(room => {
const roomItem = document.createElement('li');
roomItem.textContent = room.alias;
roomItem.dataset.guildTopic = guildTopic;
roomItem.dataset.topic = room.topic;
roomItem.addEventListener('dblclick', () => enterEditMode(roomItem, guildTopic));
roomItem.addEventListener('click', () => switchRoom(guildTopic, room.topic));
roomList.appendChild(roomItem);
});
manageGuildModal.classList.remove('hidden');
}
</script>
</body>
</html>

View File

@ -11,7 +11,8 @@
"minWidth": 955,
"minHeight": 547,
"width": 955
}
},
"links": ["http://127.0.0.1", "http://localhost", "https://ka-f.fontawesome.com", "https://cdn.jsdelivr.net", "https://cdnjs.cloudflare.com"]
},
"license": "Apache-2.0",
"devDependencies": {
@ -24,12 +25,15 @@
"dependencies": {
"b4a": "^1.6.6",
"corestore": "^6.18.2",
"dompurify": "^3.1.6",
"dotenv": "^16.4.5",
"electron": "^30.0.8",
"highlight.js": "^11.10.0",
"hypercore-crypto": "^3.4.1",
"hyperdrive": "^11.8.1",
"hyperswarm": "^4.7.14",
"localdrive": "^1.11.4",
"markdown-it": "^14.1.0",
"marked": "^12.0.2",
"serve-drive": "^5.0.8"
}

249
style.css
View File

@ -17,23 +17,28 @@ body {
padding: 3px 7px;
font-size: 14px;
font-weight: bold;
color: #fff;
background-color: #7289da;
color: #ffffff;
background-color: #464343;
border: none;
border-radius: 3px;
text-decoration: none;
cursor: pointer;
transition: background-color 0.3s ease, transform 0.3s ease;
}
.mini-button:hover {
background-color: #0056b3;
background-color: #181717;
transform: scale(1.05);
}
.bold {
font-weight: bold;
}
pear-ctrl[data-platform="darwin"] { float: right; margin-top: 4px; }
pear-ctrl[data-platform="darwin"] {
float: right;
margin-top: 4px;
}
pear-ctrl {
margin-top: 9px;
@ -50,7 +55,7 @@ pear-ctrl:after {
left: 0;
top: 0;
width: 100%;
background-color: #B0D94413;
background-color: #1f1f1f;
filter: drop-shadow(2px 10px 6px #888);
}
@ -58,7 +63,6 @@ main {
display: flex;
flex: 1;
overflow: hidden;
/* Ensure no overflow in main */
}
#sidebar {
@ -107,27 +111,21 @@ main {
#messages-container {
flex: 1;
overflow-y: auto;
/* Allow vertical scrolling */
overflow-x: hidden;
/* Hide horizontal scrolling */
width: 100%;
}
#messages-container::-webkit-scrollbar {
width: 8px;
/* Set the width of the scrollbar */
}
#messages-container::-webkit-scrollbar-thumb {
background-color: #b0d944;
/* Set the color of the scrollbar thumb */
background-color: #464343;
border-radius: 10px;
/* Round the corners of the scrollbar thumb */
}
#messages-container::-webkit-scrollbar-track {
background: #2e2e2e;
/* Set the color of the scrollbar track */
}
#message-form {
@ -142,19 +140,15 @@ main {
margin-right: 0.5rem;
padding-right: 0.5rem;
height: 1.5rem;
/* Initial single line height */
overflow-y: hidden;
/* Hide scrollbar */
}
#message:focus {
height: auto;
/* Allow the textarea to grow dynamically when focused */
}
#message:empty {
height: 1.5rem;
/* Ensure single line height when empty */
}
#sidebar button {
@ -162,15 +156,17 @@ main {
padding: 10px;
margin-bottom: 10px;
cursor: pointer;
background-color: #7289da;
background-color: #3e3c3c;
border: none;
color: white;
font-size: 14px;
border-radius: 5px;
transition: background-color 0.3s ease, transform 0.3s ease;
}
#sidebar button:hover {
background-color: #5b6eae;
background-color: #191919;
transform: scale(1.05);
}
#remove-room-btn {
@ -181,10 +177,12 @@ main {
color: white;
cursor: pointer;
border-radius: 5px;
transition: background-color 0.3s ease, transform 0.3s ease;
}
#remove-room-btn:hover {
background-color: #c03535;
transform: scale(1.05);
}
.hidden {
@ -221,51 +219,66 @@ header {
padding: 0.5rem;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s ease;
transition: background-color 0.3s ease, transform 0.3s ease;
}
.window-controls button:hover {
background-color: #3e3e3e;
transform: scale(1.05);
}
/* Button and input styles */
button,
input,
textarea {
border: 1px solid #b0d944;
border: 1px solid #464343;
background-color: #333;
color: #b0d944;
color: #e0e0e0;
padding: 0.5rem;
/* Reduced padding */
font-family: 'Roboto Mono', monospace;
font-size: 1rem;
line-height: 1.25rem;
/* Adjusted line height */
border-radius: 4px;
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease, transform 0.3s ease;
resize: none;
/* Prevent resizing */
overflow-y: hidden;
/* Hide scrollbar */
}
button:hover,
input[type="submit"]:hover {
background-color: #191919;
transform: scale(1.05);
}
textarea {
height: auto;
/* Allow the textarea to grow dynamically */
}
textarea:focus {
outline: none;
/* Remove focus outline */
}
textarea::placeholder {
color: #a0a0a0;
}
#attach-file, #message-form input[type="submit"] {
padding: 0.5rem 1rem; /* Add padding to buttons */
margin-left: 0.5rem; /* Add margin between buttons */
#attach-file,
#message-form input[type="submit"] {
padding: 0.5rem 1rem;
margin-left: 0.5rem;
}
#talk-btn {
padding: 0.5rem 1rem;
margin-left: 0.5rem;
font-size: 14px;
border-radius: 5px;
cursor: pointer;
}
#talk-btn:active {
color: #fff;
background-color: #f04747;
}
/* Main container styles */
@ -297,7 +310,7 @@ main {
#or {
margin: 1.5rem 0;
color: #b0d944;
color: #e0e0e0;
}
#loading {
@ -341,6 +354,12 @@ main {
#join-chat-room-container button {
margin-left: 0.5rem;
transition: background-color 0.3s ease, transform 0.3s ease;
}
#join-chat-room-container button:hover {
background-color: #191919;
transform: scale(1.05);
}
#details {
@ -352,17 +371,21 @@ main {
border-radius: 4px;
width: 100%;
box-sizing: border-box;
/* Added to ensure box model includes padding */
}
#details>div {
display: flex;
flex-direction: column;
/* Allow peers count to stack */
}
#submit-button {
margin-left: 1rem;
transition: background-color 0.3s ease, transform 0.3s ease;
}
#submit-button:hover {
background-color: #191919;
transform: scale(1.05);
}
#messages {
@ -370,7 +393,6 @@ main {
min-height: 200px;
overflow-y: auto;
padding: 0.5rem;
/* Reduced padding */
background-color: #262626;
border-radius: 4px;
display: flex;
@ -413,7 +435,6 @@ main {
display: flex;
align-items: flex-start;
margin-bottom: 0.5rem;
/* Reduced margin */
}
.message img.avatar {
@ -421,24 +442,22 @@ main {
height: 32px;
border-radius: 50%;
margin-right: 0.5rem;
border: 2px solid #b0d944;
border: 2px solid #464343;
user-select: none;
}
.message-content {
max-width: 70%;
background-color: #2e2e2e;
padding: 0.5rem;
/* Reduced padding */
border-radius: 8px;
/* Reduced border radius */
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.message-header {
font-weight: bold;
color: #b0d944;
color: #e0e0e0;
font-size: 0.9rem;
/* Reduced font size */
}
.message-text {
@ -450,6 +469,150 @@ main {
height: auto;
margin-top: 0.5rem;
border-radius: 4px;
/* Removed circular border */
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
/* Updated Room List Styles */
#guild-list {
list-style: none;
padding: 0;
margin: 0;
}
.guild-item {
padding: 10px;
margin-bottom: 10px;
background-color: #3a3f44;
border-radius: 5px;
cursor: pointer;
display: flex;
flex-direction: column;
transition: background-color 0.3s ease;
}
.guild-item:hover {
background-color: #4a5258;
}
.guild-item h3 {
margin: 0;
font-size: 16px;
color: #ffffff;
}
.guild-item .manage-guild-btn {
align-self: flex-end;
margin-top: 10px;
font-size: 12px;
padding: 5px 10px;
}
.guild-item .room-list {
list-style: none;
padding: 0;
margin: 10px 0 0 0;
display: flex;
flex-direction: column;
}
.guild-item .room-list li {
padding: 5px;
margin-bottom: 5px;
background-color: #464343;
border-radius: 3px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
transition: background-color 0.3s ease;
}
.guild-item .room-list li:hover {
background-color: #5a5f64;
}
.guild-item .room-list li span {
flex: 1;
}
.guild-item .room-list li .edit-icon,
.guild-item .room-list li .delete-icon {
margin-left: 10px;
color: #e0e0e0;
cursor: pointer;
transition: color 0.3s ease;
}
.guild-item .room-list li .edit-icon:hover,
.guild-item .room-list li .delete-icon:hover {
color: #a0a0a0;
}
/* Modal styles */
.modal {
display: flex;
justify-content: center;
align-items: center;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
z-index: 1000;
}
.modal-content {
background-color: #2e2e2e;
padding: 1.5rem;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5);
max-width: 500px;
width: 100%;
text-align: center;
}
.close-btn {
color: #e0e0e0;
float: right;
font-size: 1.5rem;
cursor: pointer;
}
.close-btn:hover {
color: #f04747;
}
.modal h2 {
margin-bottom: 1rem;
color: #fff;
}
.modal form {
display: flex;
flex-direction: column;
align-items: center;
}
.modal form input,
.modal form button {
margin-bottom: 1rem;
width: 100%;
}
.modal form button {
width: auto;
background-color: #3e3e3e;
color: #fff;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s ease, transform 0.3s ease;
}
.modal form button:hover {
background-color: #191919;
transform: scale(1.05);
}