Compare commits
102 Commits
9a14c10bac
...
main
Author | SHA1 | Date | |
---|---|---|---|
4ae2f4cbe8 | |||
7c874fab21 | |||
f125cad87c | |||
cb88b16606 | |||
94ea9b6840 | |||
7eddd45f79 | |||
70bc69ddea | |||
aa70e42fdc | |||
c530a8d69c | |||
084cb02a11 | |||
a0c7eb1561 | |||
43e9890235 | |||
ed4625275d | |||
55875f468c | |||
e0ad9c7067 | |||
d423031cfb | |||
181d011b8d | |||
6cfbb36d35 | |||
da1bf28e3d | |||
a1b11f7ae6 | |||
de9e01e932 | |||
8c0f7ebd0f | |||
0e6d074c11 | |||
6ea37b8082 | |||
fd02cff23f | |||
cd92618351 | |||
9d759e5af3 | |||
882e35f14d | |||
241cf62220 | |||
5080c37bcf | |||
7350d134a1 | |||
a6229b4b9a | |||
c477a988fd | |||
04c677e8e7 | |||
983d88002e | |||
150b69a2a2 | |||
487bc13970 | |||
2cf819553a | |||
49f4cc88ed | |||
66ae839318 | |||
db88b09ba5 | |||
1c6cc9a82e | |||
f706e7d621 | |||
42dccf9547 | |||
d4dc1b299d | |||
3e9a2101d5 | |||
2d0f97bc8f | |||
6cbfe34966 | |||
9b4357dd00 | |||
14aa129f6d | |||
d449249f41 | |||
bb26b5c2a8 | |||
52a319a1c0 | |||
70655ce901 | |||
75be3a5b33 | |||
d5650a9d7b | |||
2ff9c73585 | |||
d50e0df6a1 | |||
8262bd4eb7 | |||
457bf01bc3 | |||
feff30fecc | |||
b3ccda32ea | |||
bd885bb591 | |||
66dc7cdac2 | |||
624222f726 | |||
01ca4aae5a | |||
37ce0ff5bf | |||
65576e82b3 | |||
9eeb38ca3f | |||
d3792b4bd0 | |||
50a02e8dfd | |||
ff0acb1a85 | |||
4d9d22060b | |||
f84a6cff1b | |||
6a74c23f9c | |||
f50370d2e5 | |||
59e72998c6 | |||
458954c1e1 | |||
b56d603b1e | |||
7dbcf17429 | |||
98aa79f075 | |||
7a31202ac3 | |||
7061aa1e5e | |||
af37909b3f | |||
2faf2b6f78 | |||
b1ea06018b | |||
116b23d70f | |||
0dbd441cd5 | |||
b50bd0bbab | |||
6b1b13f75e | |||
0a3732fb07 | |||
5edcff29e1 | |||
8c4a38ca8e | |||
3c803c811e | |||
6a7eb78d34 | |||
76b00ce5b2 | |||
145b5b728b | |||
6b3f043332 | |||
060308ec92 | |||
7461dadf88 | |||
052fbecc20 | |||
b9db313eff |
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,3 +7,4 @@ chatBot/.env
|
|||||||
chatBot/commands/ai.js
|
chatBot/commands/ai.js
|
||||||
config.json
|
config.json
|
||||||
.idea
|
.idea
|
||||||
|
AIBot
|
134
README.md
Normal file
134
README.md
Normal 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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 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).
|
BIN
assets/agent.png
Normal file
BIN
assets/agent.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
220
chatBot/README.md
Normal file
220
chatBot/README.md
Normal 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
BIN
chatBot/assets/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
@ -39,10 +39,9 @@ loadCommands().then(commands => {
|
|||||||
|
|
||||||
// We use Event Emitter here to handle new messages
|
// We use Event Emitter here to handle new messages
|
||||||
bot.on('onMessage', (peer, message) => {
|
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);
|
||||||
|
|
||||||
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
|
// Check if the message starts with a command prefix
|
||||||
if (message.message.startsWith('!')) {
|
if (message.message.startsWith('!')) {
|
||||||
// Extract the command and arguments
|
// Extract the command and arguments
|
||||||
@ -53,7 +52,10 @@ loadCommands().then(commands => {
|
|||||||
|
|
||||||
// If the command exists, execute its handler
|
// If the command exists, execute its handler
|
||||||
if (commandHandler && typeof commandHandler.handler === 'function') {
|
if (commandHandler && typeof commandHandler.handler === 'function') {
|
||||||
|
console.log(`Executing command: ${command} with arguments: ${args}`);
|
||||||
commandHandler.handler(bot, args, message);
|
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_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 => {
|
}).catch(error => {
|
||||||
console.error('Error loading commands:', error);
|
console.error('Error loading commands:', error);
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
// ping.js
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
handler: function(bot, args, message) {
|
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);
|
||||||
}
|
}
|
||||||
};
|
};
|
10
chatBot/commands/test-audio.js
Normal file
10
chatBot/commands/test-audio.js
Normal 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);
|
||||||
|
}
|
||||||
|
};
|
@ -2,4 +2,4 @@ LINKUP_ROOM_ID=
|
|||||||
|
|
||||||
BOT_NAME=myBot
|
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."
|
@ -1,44 +1,127 @@
|
|||||||
|
import path from 'path';
|
||||||
import Hyperswarm from 'hyperswarm';
|
import Hyperswarm from 'hyperswarm';
|
||||||
import EventEmitter from 'node:events';
|
import EventEmitter from 'node:events';
|
||||||
import b4a from "b4a";
|
import b4a from "b4a";
|
||||||
import TextMessage from "./TextMessage.js";
|
import TextMessage from "./message/TextMessage.js";
|
||||||
import FileMessage from "./FileMessage.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 {
|
class Client extends EventEmitter {
|
||||||
constructor(botName) {
|
constructor(botName) {
|
||||||
super();
|
super();
|
||||||
if(!botName) return console.error("Bot Name is not defined!");
|
if (!botName) return console.error("Bot Name is not defined!");
|
||||||
this.botName = botName;
|
this.botName = botName;
|
||||||
this.swarm = new Hyperswarm();
|
this.swarm = new Hyperswarm();
|
||||||
this.joinedRooms = new Set(); // Track the rooms the bot has joined
|
this.joinedRooms = new Set(); // Track the rooms the bot has joined
|
||||||
this.currentTopic = null; // Track the current topic
|
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();
|
this.setupSwarm();
|
||||||
|
|
||||||
process.on('exit', () => {
|
process.on('exit', () => {
|
||||||
|
console.log('EXIT signal received. Shutting down HyperSwarm...');
|
||||||
this.destroy();
|
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() {
|
setupSwarm() {
|
||||||
this.swarm.on('connection', (peer) => {
|
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());
|
const messageObj = JSON.parse(message.toString());
|
||||||
if (this.joinedRooms.has(messageObj.topic)) { // Process message only if it is from a joined room
|
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
|
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")
|
const msgType = messageObj.type;
|
||||||
this.emit('onFile', peer, new FileMessage(messageObj.name, messageObj.fileName, messageObj.fileUrl, messageObj.fileType, messageObj.avatar, messageObj.topic, messageObj.timestamp));
|
const peerName = messageObj.name; // Changed from name to userName
|
||||||
|
const peerAvatar = messageObj.avatar;
|
||||||
|
const timestamp = messageObj.timestamp;
|
||||||
|
|
||||||
if (messageObj.type === "icon")
|
if (msgType === "message")
|
||||||
this.emit('onIcon', peer, messageObj);
|
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 => {
|
peer.on('error', e => {
|
||||||
this.emit('onError', 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) {
|
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.joinedRooms.add(chatRoomID); // Add the room to the list of joined rooms
|
||||||
this.currentTopic = chatRoomID; // Store the current topic
|
this.currentTopic = chatRoomID; // Store the current topic
|
||||||
this.discovery = this.swarm.join(Buffer.from(chatRoomID, 'hex'), { client: true, server: true });
|
this.discovery = this.swarm.join(Buffer.from(chatRoomID, 'hex'), { client: true, server: true });
|
||||||
@ -58,18 +145,65 @@ class Client extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sendTextMessage(message) {
|
sendTextMessage(message) {
|
||||||
|
console.log(`Preparing to send text message: ${message}`);
|
||||||
this.sendMessage(TextMessage.new(this, message));
|
this.sendMessage(TextMessage.new(this, message));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMessage(message) {
|
async sendFileMessage(filePath, fileType) {
|
||||||
console.log('Bot name:', this.botName);
|
try {
|
||||||
const data = message.toJsonString();
|
await this.drive.ready();
|
||||||
const peers = [...this.swarm.connections];
|
const fileBuffer = fs.readFileSync(filePath);
|
||||||
for (const peer of peers) peer.write(data);
|
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() {
|
async sendAudioMessage(filePath, audioType) {
|
||||||
this.swarm.destroy();
|
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.`);
|
console.log(`Bot ${this.botName} disconnected.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
@ -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;
|
|
27
chatBot/includes/message/AudioMessage.js
Normal file
27
chatBot/includes/message/AudioMessage.js
Normal 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;
|
29
chatBot/includes/message/FileMessage.js
Normal file
29
chatBot/includes/message/FileMessage.js
Normal 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;
|
20
chatBot/includes/message/IconMessage.js
Normal file
20
chatBot/includes/message/IconMessage.js
Normal 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;
|
21
chatBot/includes/message/Message.js
Normal file
21
chatBot/includes/message/Message.js
Normal 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;
|
21
chatBot/includes/message/TextMessage.js
Normal file
21
chatBot/includes/message/TextMessage.js
Normal 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
104
commands.js
Normal 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
BIN
images/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 55 KiB |
188
index.html
188
index.html
@ -17,10 +17,10 @@
|
|||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<div id="sidebar">
|
<div id="sidebar">
|
||||||
<ul id="room-list">
|
<ul id="guild-list" class="list-group">
|
||||||
<!-- Room list will be populated dynamically -->
|
<!-- Guild list will be populated dynamically -->
|
||||||
</ul>
|
</ul>
|
||||||
<button id="toggle-setup-btn">Create/Join Room</button>
|
<button id="toggle-setup-btn">Create/Join Guild</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<div id="register" class="hidden">
|
<div id="register" class="hidden">
|
||||||
@ -35,12 +35,12 @@
|
|||||||
<div id="user-info">
|
<div id="user-info">
|
||||||
<!-- User info will be populated dynamically -->
|
<!-- User info will be populated dynamically -->
|
||||||
</div>
|
</div>
|
||||||
<button id="create-chat-room">Create Room</button>
|
<button id="create-guild-btn">Create Guild</button>
|
||||||
<div id="or">- or -</div>
|
<div id="or">- or -</div>
|
||||||
<div id="join-chat-room-container">
|
<div id="join-guild-container">
|
||||||
<label for="connection-topic">Topic:</label>
|
<label for="join-guild-topic">Guild Topic:</label>
|
||||||
<input type="text" id="join-chat-room-topic" placeholder="connection topic">
|
<input type="text" id="join-guild-topic" placeholder="guild topic">
|
||||||
<button id="join-chat-room">Join Room</button>
|
<button id="join-guild">Join Guild</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="chat" class="hidden">
|
<div id="chat" class="hidden">
|
||||||
@ -49,11 +49,10 @@
|
|||||||
<div style="display: inline;">
|
<div style="display: inline;">
|
||||||
<strong><span id="chat-room-name"></span></strong> | <a href="#" id="copy-topic-link" class="mini-button">Copy Topic</a>
|
<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-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>
|
</div>
|
||||||
<div id="user-list">
|
|
||||||
<!-- User list will be populated here -->
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="messages-container">
|
<div id="messages-container">
|
||||||
<div id="messages"></div>
|
<div id="messages"></div>
|
||||||
@ -62,6 +61,7 @@
|
|||||||
<textarea id="message" rows="4"></textarea>
|
<textarea id="message" rows="4"></textarea>
|
||||||
<input type="file" id="file-input" style="display:none;">
|
<input type="file" id="file-input" style="display:none;">
|
||||||
<button type="button" id="attach-file">Attach File</button>
|
<button type="button" id="attach-file">Attach File</button>
|
||||||
|
<button type="button" id="talk-btn">Talk</button>
|
||||||
<input type="submit" value="Send" />
|
<input type="submit" value="Send" />
|
||||||
</form>
|
</form>
|
||||||
<button id="remove-room-btn">Leave Room</button>
|
<button id="remove-room-btn">Leave Room</button>
|
||||||
@ -69,11 +69,46 @@
|
|||||||
<div id="loading" class="hidden">Loading ...</div>
|
<div id="loading" class="hidden">Loading ...</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</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">×</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">×</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>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const messageInput = document.getElementById('message');
|
const messageInput = document.getElementById('message');
|
||||||
const copyTopicLink = document.getElementById('copy-topic-link');
|
const copyTopicLink = document.getElementById('copy-topic-link');
|
||||||
const chatRoomTopic = document.getElementById('chat-room-topic');
|
const chatRoomTopic = document.getElementById('chat-room-topic');
|
||||||
|
const copyGuildIdButton = document.getElementById('copy-guild-id');
|
||||||
|
|
||||||
if (messageInput) {
|
if (messageInput) {
|
||||||
messageInput.addEventListener('keydown', function(event) {
|
messageInput.addEventListener('keydown', function(event) {
|
||||||
@ -90,7 +125,7 @@
|
|||||||
if (chatRoomTopic) {
|
if (chatRoomTopic) {
|
||||||
const topic = chatRoomTopic.innerText;
|
const topic = chatRoomTopic.innerText;
|
||||||
navigator.clipboard.writeText(topic).then(() => {
|
navigator.clipboard.writeText(topic).then(() => {
|
||||||
alert('Topic copied to clipboard!');
|
console.log('Topic copied to clipboard:', topic);
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
console.error('Failed to copy topic:', err);
|
console.error('Failed to copy topic:', err);
|
||||||
});
|
});
|
||||||
@ -100,32 +135,123 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const switchRoom = (topic) => {
|
if (copyGuildIdButton) {
|
||||||
console.log('Switching to room:', topic); // Debugging log
|
copyGuildIdButton.addEventListener('click', function(event) {
|
||||||
const chatRoomTopic = document.querySelector('#chat-room-topic');
|
event.preventDefault();
|
||||||
const chatRoomName = document.querySelector('#chat-room-name');
|
const guildTopic = document.getElementById('manage-guild-modal').dataset.guildTopic;
|
||||||
|
navigator.clipboard.writeText(guildTopic).then(() => {
|
||||||
if (chatRoomTopic) {
|
console.log('Guild ID copied to clipboard:', guildTopic);
|
||||||
chatRoomTopic.innerText = topic; // Set full topic here
|
}).catch(err => {
|
||||||
} else {
|
console.error('Failed to copy guild ID:', err);
|
||||||
console.error('Element #chat-room-topic not found');
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show chat UI elements
|
const guildList = document.querySelector('#guild-list');
|
||||||
document.querySelector('#chat').classList.remove('hidden');
|
if (guildList) {
|
||||||
document.querySelector('#setup').classList.add('hidden');
|
guildList.addEventListener('click', function(event) {
|
||||||
};
|
const roomItem = event.target.closest('.room-item');
|
||||||
|
|
||||||
const roomList = document.querySelector('#room-list');
|
|
||||||
if (roomList) {
|
|
||||||
roomList.addEventListener('click', function(event) {
|
|
||||||
const roomItem = event.target.closest('li');
|
|
||||||
if (roomItem) {
|
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>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
"minWidth": 955,
|
"minWidth": 955,
|
||||||
"minHeight": 547,
|
"minHeight": 547,
|
||||||
"width": 955
|
"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",
|
"license": "Apache-2.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -24,12 +25,15 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"b4a": "^1.6.6",
|
"b4a": "^1.6.6",
|
||||||
"corestore": "^6.18.2",
|
"corestore": "^6.18.2",
|
||||||
|
"dompurify": "^3.1.6",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"electron": "^30.0.8",
|
"electron": "^30.0.8",
|
||||||
|
"highlight.js": "^11.10.0",
|
||||||
"hypercore-crypto": "^3.4.1",
|
"hypercore-crypto": "^3.4.1",
|
||||||
"hyperdrive": "^11.8.1",
|
"hyperdrive": "^11.8.1",
|
||||||
"hyperswarm": "^4.7.14",
|
"hyperswarm": "^4.7.14",
|
||||||
"localdrive": "^1.11.4",
|
"localdrive": "^1.11.4",
|
||||||
|
"markdown-it": "^14.1.0",
|
||||||
"marked": "^12.0.2",
|
"marked": "^12.0.2",
|
||||||
"serve-drive": "^5.0.8"
|
"serve-drive": "^5.0.8"
|
||||||
}
|
}
|
||||||
|
257
style.css
257
style.css
@ -17,23 +17,28 @@ body {
|
|||||||
padding: 3px 7px;
|
padding: 3px 7px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #fff;
|
color: #ffffff;
|
||||||
background-color: #7289da;
|
background-color: #464343;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
transition: background-color 0.3s ease, transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
.mini-button:hover {
|
.mini-button:hover {
|
||||||
background-color: #0056b3;
|
background-color: #181717;
|
||||||
}
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
.bold {
|
.bold {
|
||||||
font-weight: 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 {
|
pear-ctrl {
|
||||||
margin-top: 9px;
|
margin-top: 9px;
|
||||||
@ -50,7 +55,7 @@ pear-ctrl:after {
|
|||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: #B0D94413;
|
background-color: #1f1f1f;
|
||||||
filter: drop-shadow(2px 10px 6px #888);
|
filter: drop-shadow(2px 10px 6px #888);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +63,6 @@ main {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
/* Ensure no overflow in main */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#sidebar {
|
#sidebar {
|
||||||
@ -107,27 +111,21 @@ main {
|
|||||||
#messages-container {
|
#messages-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
/* Allow vertical scrolling */
|
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
/* Hide horizontal scrolling */
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#messages-container::-webkit-scrollbar {
|
#messages-container::-webkit-scrollbar {
|
||||||
width: 8px;
|
width: 8px;
|
||||||
/* Set the width of the scrollbar */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#messages-container::-webkit-scrollbar-thumb {
|
#messages-container::-webkit-scrollbar-thumb {
|
||||||
background-color: #b0d944;
|
background-color: #464343;
|
||||||
/* Set the color of the scrollbar thumb */
|
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
/* Round the corners of the scrollbar thumb */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#messages-container::-webkit-scrollbar-track {
|
#messages-container::-webkit-scrollbar-track {
|
||||||
background: #2e2e2e;
|
background: #2e2e2e;
|
||||||
/* Set the color of the scrollbar track */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#message-form {
|
#message-form {
|
||||||
@ -142,19 +140,15 @@ main {
|
|||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
padding-right: 0.5rem;
|
padding-right: 0.5rem;
|
||||||
height: 1.5rem;
|
height: 1.5rem;
|
||||||
/* Initial single line height */
|
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
/* Hide scrollbar */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#message:focus {
|
#message:focus {
|
||||||
height: auto;
|
height: auto;
|
||||||
/* Allow the textarea to grow dynamically when focused */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#message:empty {
|
#message:empty {
|
||||||
height: 1.5rem;
|
height: 1.5rem;
|
||||||
/* Ensure single line height when empty */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#sidebar button {
|
#sidebar button {
|
||||||
@ -162,15 +156,17 @@ main {
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: #7289da;
|
background-color: #3e3c3c;
|
||||||
border: none;
|
border: none;
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
transition: background-color 0.3s ease, transform 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sidebar button:hover {
|
#sidebar button:hover {
|
||||||
background-color: #5b6eae;
|
background-color: #191919;
|
||||||
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
#remove-room-btn {
|
#remove-room-btn {
|
||||||
@ -181,10 +177,12 @@ main {
|
|||||||
color: white;
|
color: white;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
transition: background-color 0.3s ease, transform 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
#remove-room-btn:hover {
|
#remove-room-btn:hover {
|
||||||
background-color: #c03535;
|
background-color: #c03535;
|
||||||
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hidden {
|
.hidden {
|
||||||
@ -221,51 +219,66 @@ header {
|
|||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.3s ease;
|
transition: background-color 0.3s ease, transform 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.window-controls button:hover {
|
.window-controls button:hover {
|
||||||
background-color: #3e3e3e;
|
background-color: #3e3e3e;
|
||||||
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Button and input styles */
|
/* Button and input styles */
|
||||||
button,
|
button,
|
||||||
input,
|
input,
|
||||||
textarea {
|
textarea {
|
||||||
border: 1px solid #b0d944;
|
border: 1px solid #464343;
|
||||||
background-color: #333;
|
background-color: #333;
|
||||||
color: #b0d944;
|
color: #e0e0e0;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
/* Reduced padding */
|
|
||||||
font-family: 'Roboto Mono', monospace;
|
font-family: 'Roboto Mono', monospace;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
line-height: 1.25rem;
|
line-height: 1.25rem;
|
||||||
/* Adjusted line height */
|
|
||||||
border-radius: 4px;
|
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;
|
resize: none;
|
||||||
/* Prevent resizing */
|
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
/* Hide scrollbar */
|
}
|
||||||
|
|
||||||
|
button:hover,
|
||||||
|
input[type="submit"]:hover {
|
||||||
|
background-color: #191919;
|
||||||
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
height: auto;
|
height: auto;
|
||||||
/* Allow the textarea to grow dynamically */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea:focus {
|
textarea:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
/* Remove focus outline */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea::placeholder {
|
textarea::placeholder {
|
||||||
color: #a0a0a0;
|
color: #a0a0a0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#attach-file, #message-form input[type="submit"] {
|
#attach-file,
|
||||||
padding: 0.5rem 1rem; /* Add padding to buttons */
|
#message-form input[type="submit"] {
|
||||||
margin-left: 0.5rem; /* Add margin between buttons */
|
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 */
|
/* Main container styles */
|
||||||
@ -297,7 +310,7 @@ main {
|
|||||||
|
|
||||||
#or {
|
#or {
|
||||||
margin: 1.5rem 0;
|
margin: 1.5rem 0;
|
||||||
color: #b0d944;
|
color: #e0e0e0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#loading {
|
#loading {
|
||||||
@ -341,6 +354,12 @@ main {
|
|||||||
|
|
||||||
#join-chat-room-container button {
|
#join-chat-room-container button {
|
||||||
margin-left: 0.5rem;
|
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 {
|
#details {
|
||||||
@ -352,17 +371,21 @@ main {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
/* Added to ensure box model includes padding */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#details>div {
|
#details>div {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
/* Allow peers count to stack */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#submit-button {
|
#submit-button {
|
||||||
margin-left: 1rem;
|
margin-left: 1rem;
|
||||||
|
transition: background-color 0.3s ease, transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#submit-button:hover {
|
||||||
|
background-color: #191919;
|
||||||
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
#messages {
|
#messages {
|
||||||
@ -370,7 +393,6 @@ main {
|
|||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
/* Reduced padding */
|
|
||||||
background-color: #262626;
|
background-color: #262626;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -413,7 +435,6 @@ main {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
/* Reduced margin */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.message img.avatar {
|
.message img.avatar {
|
||||||
@ -421,24 +442,22 @@ main {
|
|||||||
height: 32px;
|
height: 32px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
border: 2px solid #b0d944;
|
border: 2px solid #464343;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-content {
|
.message-content {
|
||||||
max-width: 70%;
|
max-width: 70%;
|
||||||
background-color: #2e2e2e;
|
background-color: #2e2e2e;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
/* Reduced padding */
|
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
/* Reduced border radius */
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-header {
|
.message-header {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #b0d944;
|
color: #e0e0e0;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
/* Reduced font size */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-text {
|
.message-text {
|
||||||
@ -450,6 +469,150 @@ main {
|
|||||||
height: auto;
|
height: auto;
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
/* Removed circular border */
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user