diff --git a/app.js b/app.js
index b1df6c3..df2a563 100644
--- a/app.js
+++ b/app.js
@@ -5,7 +5,7 @@ import ServeDrive from 'serve-drive';
import Hyperdrive from 'hyperdrive';
import Corestore from 'corestore';
import { EventEmitter } from 'events';
-import fs from "fs";
+import fs from 'fs';
import handleCommand from './commands.js';
const agentAvatarPath = './assets/agent.png';
@@ -32,7 +32,7 @@ let servePort;
let config = {
userName: '',
userAvatar: '',
- rooms: [],
+ guilds: {},
registeredUsers: {}
};
@@ -44,8 +44,8 @@ function getRandomPort() {
return Math.floor(Math.random() * (65535 - 49152 + 1)) + 49152;
}
-function currentTopic() {
- return document.querySelector('#chat-room-topic').innerText;
+function currentGuildTopic() {
+ return document.querySelector('#chat-guild-topic').innerText;
}
function getCurrentPeerCount() {
@@ -61,17 +61,57 @@ function updatePeerCount() {
}
}
-async function joinRoom(topicStr) {
- const topicBuffer = b4a.from(topicStr, 'hex');
- addRoomToList(topicStr);
+async function processGuild(guildData) {
+ const parsedData = JSON.parse(guildData);
+ config.guilds[parsedData.guildTopic] = {
+ alias: parsedData.guildAlias,
+ rooms: parsedData.rooms,
+ owner: parsedData.owner
+ };
+ writeConfigToFile("./config.json");
+ renderGuildList();
+ await joinGuild(parsedData.guildTopic);
+}
+
+async function joinGuild(guildTopic) {
+ const guild = config.guilds[guildTopic];
+ if (guild) {
+ for (const room of guild.rooms) {
+ await joinRoom(guildTopic, room.topic, room.alias);
+ }
+ }
+}
+
+async function joinRoom(guildTopic, roomTopic, alias) {
+ const topicBuffer = b4a.from(roomTopic, 'hex');
+ addRoomToList(guildTopic, roomTopic, alias);
await joinSwarm(topicBuffer);
}
-async function createRoom(alias) {
- const topicBuffer = crypto.randomBytes(32);
- const topic = b4a.toString(topicBuffer, 'hex');
- addRoomToList(topic, alias);
- await joinSwarm(topicBuffer);
+async function createRoom(guildTopic, alias) {
+ const roomTopicBuffer = crypto.randomBytes(32);
+ const roomTopic = b4a.toString(roomTopicBuffer, 'hex');
+
+ config.guilds[guildTopic].rooms.push({ topic: roomTopic, alias: alias || truncateHash(roomTopic) });
+ writeConfigToFile("./config.json");
+
+ addRoomToList(guildTopic, roomTopic, alias);
+ await joinSwarm(roomTopicBuffer);
+
+ // Synchronize the new room with other peers
+ const roomMessage = JSON.stringify({
+ type: 'room',
+ guildTopic,
+ room: { topic: roomTopic, alias: alias || truncateHash(roomTopic) }
+ });
+
+ const guildSwarm = activeRooms.find(room => room.topic === guildTopic);
+ if (guildSwarm) {
+ const peers = [...guildSwarm.swarm.connections];
+ for (const peer of peers) {
+ peer.write(roomMessage);
+ }
+ }
}
async function listFiles() {
@@ -81,6 +121,7 @@ async function listFiles() {
}
return files;
}
+
async function deleteFile(filename) {
await drive.del(`/files/${filename}`);
}
@@ -98,8 +139,9 @@ async function initialize() {
const configExists = fs.existsSync("./config.json");
if (configExists) {
loadConfigFromFile();
- renderRoomList();
+ renderGuildList();
await connectToAllRooms();
+ await joinAllGuilds(); // Ensure the app joins all guilds on startup
}
if (!configExists) {
@@ -113,11 +155,47 @@ async function initialize() {
document.addEventListener("DOMContentLoaded", (event) => {
hljs.highlightAll();
});
+
+ document.addEventListener('createGuild', (event) => {
+ const { guildName } = event.detail;
+ createGuild(guildName);
+ });
+
+ document.addEventListener('addRoomToGuild', (event) => {
+ const { guildTopic, roomName } = event.detail;
+ createRoom(guildTopic, roomName);
+ });
+
+ document.addEventListener('manageGuild', (event) => {
+ const { guildTopic } = event.detail;
+ openManageGuildModal(guildTopic);
+ });
+
+ document.addEventListener('switchRoom', (event) => {
+ const { guildTopic, roomTopic } = event.detail;
+ if (!roomTopic) {
+ console.error('Invalid room topic:', roomTopic);
+ return;
+ }
+ console.log('Event switchRoom with roomTopic:', roomTopic);
+ switchRoom(guildTopic, roomTopic);
+ });
+
+ document.addEventListener('joinGuildRequest', (event) => {
+ const { guildTopic } = event.detail;
+ joinGuildRequest(guildTopic);
+ });
} catch (error) {
console.error('Error during initialization:', error);
}
}
+async function joinAllGuilds() {
+ for (const guildTopic in config.guilds) {
+ await joinGuildRequest(guildTopic);
+ }
+}
+
function setupEventListeners() {
const registerForm = document.querySelector('#register-form');
const selectAvatarButton = document.querySelector('#select-avatar');
@@ -129,6 +207,7 @@ function setupEventListeners() {
const attachFileButton = document.getElementById('attach-file');
const fileInput = document.getElementById('file-input');
const talkButton = document.getElementById('talk-btn');
+ const joinGuildBtn = document.getElementById('join-guild');
if (registerForm) {
registerForm.addEventListener('submit', registerUser);
@@ -139,7 +218,10 @@ function setupEventListeners() {
});
}
if (createChatRoomButton) {
- createChatRoomButton.addEventListener('click', createChatRoom);
+ createChatRoomButton.addEventListener('click', () => {
+ const guildTopic = currentGuildTopic();
+ createRoom(guildTopic);
+ });
}
if (joinChatRoomButton) {
joinChatRoomButton.addEventListener('click', joinChatRoom);
@@ -165,8 +247,30 @@ function setupEventListeners() {
if (talkButton) {
setupTalkButton();
}
+ if (joinGuildBtn) {
+ joinGuildBtn.addEventListener('click', () => {
+ const guildTopic = document.getElementById('join-guild-topic').value.trim();
+ if (guildTopic) {
+ joinGuildRequest(guildTopic);
+ }
+ });
+ }
+
+ // Add event listeners only for room items
+ document.querySelectorAll('.room-item').forEach(item => {
+ item.addEventListener('click', () => {
+ const guildTopic = item.dataset.guildTopic;
+ const roomTopic = item.dataset.topic;
+ if (!roomTopic) {
+ console.error('Invalid room topic for item:', item);
+ return;
+ }
+ switchRoom(guildTopic, roomTopic);
+ });
+ });
}
-function handleIncomingMessage(messageObj) {
+
+function handleIncomingMessage(messageObj, connection) {
console.log('Received message:', messageObj); // Debugging log
if (messageObj.type === 'icon') {
@@ -209,10 +313,84 @@ function handleIncomingMessage(messageObj) {
}).catch(error => {
console.error(`Failed to store audio message:`, error);
});
+ } else if (messageObj.type === 'guild') {
+ const guildData = messageObj.guildData;
+ processGuild(guildData);
+ } else if (messageObj.type === 'room') {
+ const { guildTopic, room } = messageObj;
+ if (config.guilds[guildTopic]) {
+ config.guilds[guildTopic].rooms.push(room);
+ writeConfigToFile("./config.json");
+ renderGuildList();
+ joinRoom(guildTopic, room.topic, room.alias);
+ }
+ } else if (messageObj.type === 'remove-room') {
+ const { guildTopic, roomTopic } = messageObj;
+ if (config.guilds[guildTopic]) {
+ const roomIndex = config.guilds[guildTopic].rooms.findIndex(room => room.topic === roomTopic);
+ if (roomIndex !== -1) {
+ config.guilds[guildTopic].rooms.splice(roomIndex, 1);
+ writeConfigToFile("./config.json");
+ renderGuildList();
+ leaveRoom(roomTopic);
+ }
+ }
+ } else if (messageObj.type === 'rename-room') {
+ const { guildTopic, roomTopic, newAlias } = messageObj;
+ if (config.guilds[guildTopic]) {
+ const room = config.guilds[guildTopic].rooms.find(room => room.topic === roomTopic);
+ if (room) {
+ room.alias = newAlias;
+ writeConfigToFile("./config.json");
+
+ // Synchronize the room rename with other peers
+ const renameMessage = JSON.stringify({
+ type: 'rename-room',
+ guildTopic,
+ roomTopic: topic,
+ newAlias
+ });
+
+ const guildSwarm = activeRooms.find(room => room.topic === guildTopic);
+ if (guildSwarm) {
+ const peers = [...guildSwarm.swarm.connections];
+ for (const peer of peers) {
+ peer.write(renameMessage);
+ }
+ }
+ }
+ }
+ } else if (messageObj.type === 'guildJoin') {
+ const { guildTopic, guildData } = messageObj;
+ if (!config.guilds[guildTopic]) {
+ config.guilds[guildTopic] = guildData;
+ writeConfigToFile("./config.json");
+ renderGuildList();
+ joinGuild(guildTopic);
+ }
+ } else if (messageObj.type === 'guildRequest') {
+ const guildTopic = messageObj.guildTopic;
+ const guild = config.guilds[guildTopic];
+ if (guild) {
+ const guildResponseMessage = JSON.stringify({
+ type: 'guildResponse',
+ guildData: JSON.stringify({
+ guildTopic,
+ guildAlias: guild.alias,
+ rooms: guild.rooms,
+ owner: guild.owner
+ })
+ });
+ connection.write(guildResponseMessage);
+ }
+ } else if (messageObj.type === 'guildResponse') {
+ const guildData = messageObj.guildData;
+ processGuild(guildData);
} else {
console.error('Received unknown message type:', messageObj);
}
}
+
async function handleConnection(connection, info) {
console.log('New connection', info);
@@ -221,7 +399,7 @@ async function handleConnection(connection, info) {
if (iconBuffer) {
const iconMessage = JSON.stringify({
type: 'icon',
- username: config.userName,
+ name: config.userName,
avatar: b4a.toString(iconBuffer, 'base64'),
timestamp: Date.now()
});
@@ -231,9 +409,37 @@ async function handleConnection(connection, info) {
console.error('Icon not found for user:', config.userName);
}
+ // Sending the guilds and rooms information
+ for (const guildTopic in config.guilds) {
+ const guild = config.guilds[guildTopic];
+ const guildMessage = JSON.stringify({
+ type: 'guild',
+ guildData: JSON.stringify({
+ guildTopic,
+ guildAlias: guild.alias,
+ rooms: guild.rooms,
+ owner: guild.owner
+ })
+ });
+ console.log('Sending guild information to new peer:', guildMessage);
+ connection.write(guildMessage);
+ }
+
connection.on('data', (data) => {
const messageObj = JSON.parse(data.toString());
- eventEmitter.emit('onMessage', messageObj);
+ if (messageObj.type === 'guildJoin') {
+ const guildData = config.guilds[messageObj.guildTopic];
+ if (guildData) {
+ const guildJoinMessage = JSON.stringify({
+ type: 'guildJoin',
+ guildTopic: messageObj.guildTopic,
+ guildData: guildData
+ });
+ connection.write(guildJoinMessage);
+ }
+ } else {
+ eventEmitter.emit('onMessage', messageObj, connection);
+ }
});
connection.on('close', () => {
@@ -377,27 +583,51 @@ async function continueRegistration(regUsername) {
writeConfigToFile("./config.json");
}
-async function createChatRoom() {
+async function createGuild(guildName) {
const topicBuffer = crypto.randomBytes(32);
const topic = b4a.toString(topicBuffer, 'hex');
- addRoomToList(topic);
- await joinSwarm(topicBuffer);
+
+ config.guilds[topic] = {
+ alias: guildName,
+ rooms: [],
+ owner: config.userName
+ };
+
+ addGuildToList(topic, guildName);
+ writeConfigToFile("./config.json");
}
async function joinChatRoom(e) {
e.preventDefault();
- const topicStr = document.querySelector('#join-chat-room-topic').value.trim();
+ const guildTopic = document.querySelector('#join-guild-topic').value.trim();
+ const roomTopic = document.querySelector('#join-chat-room-topic').value.trim();
// Validate the topic string
- const isValidTopic = /^[0-9a-fA-F]{64}$/.test(topicStr);
- if (!isValidTopic) {
- alert('Invalid topic format. Please enter a 64-character hexadecimal string.');
+ const isValidGuildTopic = /^[0-9a-fA-F]{64}$/.test(guildTopic);
+ const isValidRoomTopic = /^[0-9a-fA-F]{64}$/.test(roomTopic);
+ if (!isValidGuildTopic || !isValidRoomTopic) {
+ alert('Invalid topic format. Please enter 64-character hexadecimal strings.');
return;
}
- const topicBuffer = b4a.from(topicStr, 'hex');
- addRoomToList(topicStr);
- await joinSwarm(topicBuffer);
+ const guildTopicBuffer = b4a.from(guildTopic, 'hex');
+ const roomTopicBuffer = b4a.from(roomTopic, 'hex');
+ addRoomToList(guildTopic, roomTopic);
+ await joinSwarm(roomTopicBuffer);
+
+ // Fetch guild data from a peer
+ const guildJoinMessage = JSON.stringify({
+ type: 'guildJoin',
+ guildTopic: guildTopic
+ });
+
+ const guildSwarm = activeRooms.find(room => room.topic === guildTopic);
+ if (guildSwarm) {
+ const peers = [...guildSwarm.swarm.connections];
+ for (const peer of peers) {
+ peer.write(guildJoinMessage);
+ }
+ }
}
async function joinSwarm(topicBuffer) {
@@ -424,18 +654,100 @@ async function joinSwarm(topicBuffer) {
}
}
-function addRoomToList(topic, alias) {
- const roomList = document.querySelector('#room-list');
- const roomItem = document.createElement('li');
- roomItem.textContent = alias || truncateHash(topic);
- roomItem.dataset.topic = topic;
+function addGuildToList(guildTopic, alias) {
+ const guildList = document.querySelector('#guild-list');
+ const guildItem = document.createElement('li');
+ guildItem.textContent = alias || truncateHash(guildTopic);
+ guildItem.dataset.guildTopic = guildTopic;
+ guildItem.classList.add('guild-item');
- roomItem.addEventListener('dblclick', () => enterEditMode(roomItem));
- roomItem.addEventListener('click', () => switchRoom(topic));
- roomList.appendChild(roomItem);
+ if (config.guilds[guildTopic].owner === config.userName) {
+ const manageButton = document.createElement('button');
+ manageButton.textContent = 'Manage';
+ manageButton.classList.add('mini-button', 'manage-guild-btn');
+ manageButton.addEventListener('click', (e) => {
+ e.stopPropagation();
+ openManageGuildModal(guildTopic);
+ });
+ guildItem.appendChild(manageButton);
+ }
- config.rooms.push({ topic, alias: alias || truncateHash(topic) });
- writeConfigToFile("./config.json");
+ const roomList = document.createElement('ul');
+ roomList.classList.add('room-list');
+ guildItem.appendChild(roomList);
+
+ guildList.appendChild(guildItem);
+
+ // Add the rooms for this guild
+ config.guilds[guildTopic].rooms.forEach(room => {
+ addRoomToList(guildTopic, room.topic, room.alias);
+ });
+}
+
+function addRoomToList(guildTopic, roomTopic, alias) {
+ const guildItem = document.querySelector(`li[data-guild-topic="${guildTopic}"]`);
+ if (guildItem) {
+ let roomList = guildItem.querySelector('.room-list');
+ if (!roomList) {
+ roomList = document.createElement('ul');
+ roomList.classList.add('room-list');
+ guildItem.appendChild(roomList);
+ }
+
+ if (!roomList.querySelector(`li[data-topic="${roomTopic}"]`)) {
+ const roomItem = document.createElement('li');
+ roomItem.textContent = alias || truncateHash(roomTopic);
+ roomItem.dataset.topic = roomTopic;
+ roomItem.dataset.guildTopic = guildTopic;
+ roomItem.classList.add('room-item');
+
+ roomItem.addEventListener('dblclick', () => enterEditMode(roomItem));
+ roomItem.addEventListener('click', () => switchRoom(guildTopic, roomTopic));
+ roomList.appendChild(roomItem);
+ }
+ }
+}
+
+function openManageGuildModal(guildTopic) {
+ const guild = config.guilds[guildTopic];
+ if (!guild) return;
+
+ const manageGuildModal = document.getElementById('manage-guild-modal');
+ manageGuildModal.dataset.guildTopic = guildTopic;
+
+ const guildInfo = manageGuildModal.querySelector('#guild-info');
+ guildInfo.innerHTML = `
${guild.alias}
`;
+
+ const roomList = manageGuildModal.querySelector('#room-list');
+ roomList.innerHTML = '';
+
+ guild.rooms.forEach(room => {
+ const roomItem = document.createElement('li');
+ roomItem.textContent = room.alias;
+ roomItem.dataset.topic = room.topic;
+
+ const editIcon = document.createElement('span');
+ editIcon.textContent = '✏️';
+ editIcon.classList.add('edit-icon');
+ editIcon.addEventListener('click', (e) => {
+ e.stopPropagation();
+ enterEditMode(roomItem);
+ });
+
+ const deleteIcon = document.createElement('span');
+ deleteIcon.textContent = '❌';
+ deleteIcon.classList.add('delete-icon');
+ deleteIcon.addEventListener('click', (e) => {
+ e.stopPropagation();
+ removeRoom(guildTopic, room.topic);
+ });
+
+ roomItem.appendChild(editIcon);
+ roomItem.appendChild(deleteIcon);
+ roomList.appendChild(roomItem);
+ });
+
+ manageGuildModal.classList.remove('hidden');
}
function enterEditMode(roomItem) {
@@ -472,10 +784,31 @@ function exitEditMode(roomItem, input, topic) {
roomItem.textContent = newAlias;
// Update the config with the new alias
- const roomConfig = config.rooms.find(room => room.topic === topic);
- if (roomConfig) {
- roomConfig.alias = newAlias;
- writeConfigToFile("./config.json");
+ for (const guildTopic in config.guilds) {
+ const guild = config.guilds[guildTopic];
+ const room = guild.rooms.find(room => room.topic === topic);
+ if (room) {
+ room.alias = newAlias;
+ writeConfigToFile("./config.json");
+
+ // Synchronize the room rename with other peers
+ const renameMessage = JSON.stringify({
+ type: 'rename-room',
+ guildTopic,
+ roomTopic: topic,
+ newAlias
+ });
+
+ const guildSwarm = activeRooms.find(room => room.topic === guildTopic);
+ if (guildSwarm) {
+ const peers = [...guildSwarm.swarm.connections];
+ for (const peer of peers) {
+ peer.write(renameMessage);
+ }
+ }
+
+ break;
+ }
}
// Check if the edited room is the current room in view
@@ -490,40 +823,83 @@ function exitEditMode(roomItem, input, topic) {
}
}
-function switchRoom(topic) {
- console.log('Switching to room:', topic); // Debugging log
+function removeRoom(guildTopic, roomTopic) {
+ const guild = config.guilds[guildTopic];
+ if (guild) {
+ const roomIndex = guild.rooms.findIndex(room => room.topic === roomTopic);
+ if (roomIndex !== -1) {
+ guild.rooms.splice(roomIndex, 1);
+ writeConfigToFile("./config.json");
+ renderGuildList();
+ leaveRoom(roomTopic);
+
+ // Synchronize the room removal with other peers
+ const removeMessage = JSON.stringify({
+ type: 'remove-room',
+ guildTopic,
+ roomTopic
+ });
+
+ const guildSwarm = activeRooms.find(room => room.topic === guildTopic);
+ if (guildSwarm) {
+ const peers = [...guildSwarm.swarm.connections];
+ for (const peer of peers) {
+ peer.write(removeMessage);
+ }
+ }
+ }
+ }
+}
+
+function switchRoom(guildTopic, roomTopic) {
+ if (!roomTopic) {
+ console.error('Invalid room topic:', roomTopic);
+ return;
+ }
+
+ console.log('Switching to room:', roomTopic);
const chatRoomTopic = document.querySelector('#chat-room-topic');
const chatRoomName = document.querySelector('#chat-room-name');
+ const guild = config.guilds[guildTopic];
+
+ if (!guild) {
+ console.error('Guild not found:', guildTopic);
+ return;
+ }
if (chatRoomTopic) {
- chatRoomTopic.innerText = topic; // Set full topic here
+ chatRoomTopic.innerText = roomTopic; // Set full topic here
} else {
console.error('Element #chat-room-topic not found');
}
if (chatRoomName) {
- // Update the room name in the header
- const room = config.rooms.find(r => r.topic === topic);
- const roomName = room ? room.alias : truncateHash(topic);
- chatRoomName.innerText = roomName;
+ // Find the room in the current guild
+ const room = guild.rooms.find(room => room.topic === roomTopic);
+ if (room) {
+ // Update the room name in the header
+ chatRoomName.innerText = room.alias;
+ } else {
+ console.error('Room not found in the current guild:', roomTopic);
+ }
} else {
console.error('Element #chat-room-name not found');
}
- clearMessages();
- renderMessagesForRoom(topic);
- updatePeerCount();
-
- // Show chat UI elements
+ // Show the chat view
document.querySelector('#chat').classList.remove('hidden');
document.querySelector('#setup').classList.add('hidden');
+
+ // Render the messages for the room
+ renderMessagesForRoom(roomTopic);
}
-function leaveRoom(topic) {
+async function leaveRoom(topic) {
const roomIndex = activeRooms.findIndex(room => room.topic === topic);
if (roomIndex !== -1) {
- const room = activeRooms[roomIndex];
- room.swarm.destroy();
+ const { swarm, discovery } = activeRooms[roomIndex];
+ await discovery.destroy();
+ swarm.destroy();
activeRooms.splice(roomIndex, 1);
}
@@ -532,286 +908,230 @@ function leaveRoom(topic) {
roomItem.remove();
}
- config.rooms = config.rooms.filter(e => e.topic !== topic);
- writeConfigToFile("./config.json");
+ const messagesContainer = document.querySelector('#messages');
+ if (messagesContainer) {
+ messagesContainer.innerHTML = '';
+ }
- if (activeRooms.length > 0) {
- switchRoom(activeRooms[0].topic);
- } else {
- document.querySelector('#chat').classList.add('hidden');
- document.querySelector('#setup').classList.remove('hidden');
+ const chatRoomName = document.querySelector('#chat-room-name');
+ if (chatRoomName) {
+ chatRoomName.innerText = '';
+ }
+
+ const chatRoomTopic = document.querySelector('#chat-room-topic');
+ if (chatRoomTopic) {
+ chatRoomTopic.innerText = '';
+ }
+
+ const chatDiv = document.querySelector('#chat');
+ const setupDiv = document.querySelector('#setup');
+ if (chatDiv && setupDiv) {
+ chatDiv.classList.add('hidden');
+ setupDiv.classList.remove('hidden');
}
}
+function writeConfigToFile(path) {
+ fs.writeFileSync(path, JSON.stringify(config, null, 2));
+}
-async function sendMessage(e) {
- e.preventDefault();
- const message = document.querySelector('#message').value;
- document.querySelector('#message').value = '';
+function loadConfigFromFile() {
+ const configFile = fs.readFileSync("./config.json", 'utf8');
+ config = JSON.parse(configFile);
+}
- const topic = currentTopic();
- const timestamp = Date.now();
+function renderGuildList() {
+ const guildList = document.querySelector('#guild-list');
+ guildList.innerHTML = '';
- if (message.startsWith('~')) {
- // Handle command
- await handleCommand(message, {
- eventEmitter,
- currentTopic,
- clearMessages,
- addMessage: (from, message, avatar, topic) => onMessageAdded(from, message, avatar, topic, timestamp),
- joinRoom,
- leaveRoom,
- createRoom,
- listFiles,
- deleteFile
- });
- return;
- }
-
- console.log('Sending message:', message); // Debugging log
-
- onMessageAdded(config.userName, message, config.userAvatar, topic, timestamp);
-
- const messageObj = JSON.stringify({
- type: 'message',
- name: config.userName,
- avatar: config.userAvatar,
- topic: topic,
- timestamp: timestamp,
- message
- });
-
- const peers = [...activeRooms.find(room => room.topic === topic).swarm.connections];
- for (const peer of peers) {
- peer.write(messageObj);
+ for (const guildTopic in config.guilds) {
+ const guild = config.guilds[guildTopic];
+ addGuildToList(guildTopic, guild.alias);
}
}
-async function handleFileInput(event) {
- const file = event.target.files[0];
- if (file) {
- const reader = new FileReader();
- reader.onload = async (event) => {
- const buffer = new Uint8Array(event.target.result);
- const filePath = `/files/${file.name}`;
- await drive.put(filePath, buffer);
- const fileUrl = `http://localhost:${servePort}${filePath}`;
-
- const topic = currentTopic();
-
- const fileMessage = {
- type: 'file',
- name: config.userName,
- avatar: updatePortInUrl(config.userAvatar),
- topic: topic,
- timestamp: Date.now(),
- fileName: file.name,
- file: b4a.toString(buffer, 'base64'),
- fileType: file.type
- };
-
- console.log('Sending file message:', fileMessage); // Debugging log
-
- const peers = [...activeRooms.find(room => room.topic === topic).swarm.connections];
- for (const peer of peers) {
- peer.write(JSON.stringify(fileMessage));
- }
-
- addFileMessage(config.userName, file.name, fileUrl, file.type, config.userAvatar, topic);
- };
- reader.readAsArrayBuffer(file);
+async function connectToAllRooms() {
+ for (const guildTopic in config.guilds) {
+ const guild = config.guilds[guildTopic];
+ for (const room of guild.rooms) {
+ await joinRoom(guildTopic, room.topic, room.alias);
+ }
}
}
-function addFileMessage(from, fileName, fileUrl, fileType, avatar, topic) {
- console.log('Adding file message:', { from, fileName, fileUrl, fileType, avatar, topic }); // Debugging log
- const $div = document.createElement('div');
- $div.classList.add('message');
-
- const $img = document.createElement('img');
- $img.src = updatePortInUrl(avatar) || 'https://via.placeholder.com/40';
- $img.classList.add('avatar');
- $img.draggable = false;
- $div.appendChild($img);
-
- const $content = document.createElement('div');
- $content.classList.add('message-content');
-
- const $header = document.createElement('div');
- $header.classList.add('message-header');
- $header.textContent = from;
- $content.appendChild($header);
-
- if (fileType.startsWith('image/')) {
- const $image = document.createElement('img');
- $image.src = updatePortInUrl(fileUrl);
- $image.alt = fileName;
- $image.classList.add('attached-image');
- $content.appendChild($image);
- } else {
- const $fileButton = document.createElement('button');
- $fileButton.textContent = `Download File: ${fileName}`;
- $fileButton.onclick = function() {
- const $fileLink = document.createElement('a');
- $fileLink.href = fileUrl;
- $fileLink.download = fileName;
- $fileLink.click();
- };
- $content.appendChild($fileButton);
- }
-
- $div.appendChild($content);
-
- // Only render the message if it's for the current room
- if (currentTopic() === topic) {
- document.querySelector('#messages').appendChild($div);
- scrollToBottom();
- } else {
- console.log(`Message for topic ${topic} not rendered because current topic is ${currentTopic()}`); // Debugging log
- }
-}
-
-function scrollToBottom() {
- var container = document.getElementById("messages-container");
- container.scrollTop = container.scrollHeight;
-}
-
-function onMessageAdded(from, message, avatar, topic, timestamp) {
- console.log('Adding message:', { from, message, avatar, topic }); // Debugging log
- const messageObj = {
- from,
- message,
- avatar,
- timestamp: timestamp || Date.now()
- };
-
- // Add the message to the store
- addMessageToStore(topic, messageObj);
-
- // Only render messages for the current room
- if (currentTopic() === topic) {
- const $div = document.createElement('div');
- $div.classList.add('message');
-
- const $img = document.createElement('img');
- $img.src = updatePortInUrl(avatar) || 'https://via.placeholder.com/40'; // Default to a placeholder image if avatar URL is not provided
- $img.classList.add('avatar');
- $img.draggable = false;
- $div.appendChild($img);
-
- const $content = document.createElement('div');
- $content.classList.add('message-content');
-
- const $header = document.createElement('div');
- $header.classList.add('message-header');
- $header.textContent = from;
-
- const $text = document.createElement('div');
- $text.classList.add('message-text');
-
- if (message.includes('Available files:')) {
- const files = message.split('\n').slice(1); // Skip the "Available files:" line
- const fileList = document.createElement('ul');
-
- files.forEach(file => {
- file = file.replace("- ", "")
- const listItem = document.createElement('li');
- const fileButton = document.createElement('button');
- fileButton.textContent = file.trim();
- fileButton.onclick = () => downloadFile(file.trim());
-
- const deleteButton = document.createElement('button');
- deleteButton.textContent = 'Delete';
- deleteButton.onclick = () => {
- console.log("file to delete: ", file);
- deleteFile(file);
- listItem.remove();
- };
-
-
- listItem.appendChild(fileButton);
- listItem.appendChild(deleteButton);
- fileList.appendChild(listItem);
- });
-
- $text.appendChild(fileList);
- } else {
-
- const md = window.markdownit({
- highlight: function (str, lang) {
- if (lang && hljs.getLanguage(lang)) {
- try {
- return hljs.highlight(str, { language: lang }).value;
- } catch (__) {}
- }
- return ''; // use external default escaping
- }
- });
- const markdownContent = md.render(message);
- $text.innerHTML = markdownContent;
- }
-
- $content.appendChild($header);
- $content.appendChild($text);
- $div.appendChild($content);
-
- document.querySelector('#messages').appendChild($div);
- scrollToBottom();
- } else {
- console.log(`Message for topic ${topic} not rendered because current topic is ${currentTopic()}`); // Debugging log
- }
-}
-
-function downloadFile(filename) {
- const fileUrl = `http://localhost:${servePort}/files/${filename}`;
- const a = document.createElement('a');
- a.href = fileUrl;
- a.download = filename;
- a.click();
-}
-
-
-function addAudioMessage(from, audioUrl, avatar, topic) {
- console.log('Adding audio message:', { from, audioUrl, avatar, topic }); // Debugging log
- const $div = document.createElement('div');
- $div.classList.add('message');
-
- const $img = document.createElement('img');
- $img.src = updatePortInUrl(avatar) || 'https://via.placeholder.com/40';
- $img.classList.add('avatar');
- $img.draggable = false;
- $div.appendChild($img);
-
- const $content = document.createElement('div');
- $content.classList.add('message-content');
-
- const $header = document.createElement('div');
- $header.classList.add('message-header');
- $header.textContent = from;
- $content.appendChild($header);
-
- const $audio = document.createElement('audio');
- $audio.controls = true;
- if (from !== config.userName) {
- $audio.autoplay = true; // Add autoplay attribute for peers only
- }
- $audio.src = updatePortInUrl(audioUrl);
- $audio.classList.add('attached-audio');
- $content.appendChild($audio);
-
- $div.appendChild($content);
-
- // Only render the message if it's for the current room
- if (currentTopic() === topic) {
- document.querySelector('#messages').appendChild($div);
- scrollToBottom();
- } else {
- console.log(`Message for topic ${topic} not rendered because current topic is ${currentTopic()}`); // Debugging log
- }
+function toggleSetupView() {
+ const setupDiv = document.querySelector('#setup');
+ const chatDiv = document.querySelector('#chat');
+ setupDiv.classList.toggle('hidden');
+ chatDiv.classList.toggle('hidden');
}
function truncateHash(hash) {
- return `${hash.slice(0, 6)}...${hash.slice(-6)}`;
+ return `${hash.substring(0, 4)}...${hash.substring(hash.length - 4)}`;
+}
+
+function updatePortInUrl(url) {
+ if (typeof url !== 'string') {
+ console.error('Invalid URL format:', url);
+ return '';
+ }
+ const urlObj = new URL(url);
+ urlObj.port = servePort;
+ return urlObj.toString();
+}
+
+function addFileMessage(name, fileName, fileUrl, fileType, avatar, topic) {
+ const container = document.querySelector('#messages');
+ if (!container) {
+ console.error('Element #messages not found');
+ return;
+ }
+
+ const messageDiv = document.createElement('div');
+ messageDiv.classList.add('message');
+ if (topic !== currentTopic()) {
+ messageDiv.classList.add('hidden'); // Hide messages not belonging to the current room
+ }
+
+ const avatarImg = document.createElement('img');
+ avatarImg.src = updatePortInUrl(avatar);
+ avatarImg.alt = `${name}'s avatar`;
+ avatarImg.classList.add('avatar');
+
+ const messageContent = document.createElement('div');
+ messageContent.classList.add('message-content');
+
+ const senderName = document.createElement('div');
+ senderName.classList.add('message-sender');
+ senderName.textContent = name;
+
+ const fileLink = document.createElement('a');
+ fileLink.href = fileUrl;
+ fileLink.textContent = `File: ${fileName}`;
+ fileLink.classList.add('message-file');
+
+ if (fileType.startsWith('image/')) {
+ const filePreview = document.createElement('img');
+ filePreview.src = fileUrl;
+ filePreview.alt = fileName;
+ filePreview.classList.add('file-preview');
+ messageContent.appendChild(filePreview);
+ } else if (fileType.startsWith('video/')) {
+ const filePreview = document.createElement('video');
+ filePreview.src = fileUrl;
+ filePreview.alt = fileName;
+ filePreview.classList.add('file-preview');
+ filePreview.controls = true;
+ messageContent.appendChild(filePreview);
+ } else if (fileType.startsWith('audio/')) {
+ const filePreview = document.createElement('audio');
+ filePreview.src = fileUrl;
+ filePreview.alt = fileName;
+ filePreview.classList.add('file-preview');
+ filePreview.controls = true;
+ messageContent.appendChild(filePreview);
+ } else {
+ const filePreview = document.createElement('div');
+ filePreview.textContent = 'No preview available';
+ filePreview.classList.add('file-preview');
+ messageContent.appendChild(filePreview);
+ }
+
+ messageContent.appendChild(senderName);
+ messageContent.appendChild(fileLink);
+ messageDiv.appendChild(avatarImg);
+ messageDiv.appendChild(messageContent);
+ container.appendChild(messageDiv);
+
+ if (topic === currentTopic()) {
+ container.scrollTop = container.scrollHeight;
+ }
+}
+function addAudioMessage(name, audioUrl, avatar, topic) {
+ const container = document.querySelector('#messages');
+ if (!container) {
+ console.error('Element #messages not found');
+ return;
+ }
+
+ const messageDiv = document.createElement('div');
+ messageDiv.classList.add('message');
+ if (topic !== currentTopic()) {
+ messageDiv.classList.add('hidden'); // Hide messages not belonging to the current room
+ }
+
+ const avatarImg = document.createElement('img');
+ avatarImg.src = updatePortInUrl(avatar);
+ avatarImg.alt = `${name}'s avatar`;
+ avatarImg.classList.add('avatar');
+
+ const messageContent = document.createElement('div');
+ messageContent.classList.add('message-content');
+
+ const senderName = document.createElement('div');
+ senderName.classList.add('message-sender');
+ senderName.textContent = name;
+
+ const audioElement = document.createElement('audio');
+ audioElement.src = audioUrl;
+ audioElement.controls = true;
+ // Autoplay only if the message is from a peer
+ if (name !== config.userName) {
+ audioElement.autoplay = true;
+ }
+ audioElement.classList.add('message-audio');
+
+ messageContent.appendChild(senderName);
+ messageContent.appendChild(audioElement);
+ messageDiv.appendChild(avatarImg);
+ messageDiv.appendChild(messageContent);
+ container.appendChild(messageDiv);
+
+ if (topic === currentTopic()) {
+ container.scrollTop = container.scrollHeight;
+ }
+}
+
+
+function addMessage(name, message, avatar, topic) {
+ const container = document.querySelector('#messages');
+ if (!container) {
+ console.error('Element #messages not found');
+ return;
+ }
+
+ const messageDiv = document.createElement('div');
+ messageDiv.classList.add('message');
+ if (topic !== currentTopic()) {
+ messageDiv.classList.add('hidden'); // Hide messages not belonging to the current room
+ }
+
+ const avatarImg = document.createElement('img');
+ avatarImg.src = updatePortInUrl(avatar);
+ avatarImg.alt = `${name}'s avatar`;
+ avatarImg.classList.add('avatar');
+
+ const messageContent = document.createElement('div');
+ messageContent.classList.add('message-content');
+
+ const senderName = document.createElement('div');
+ senderName.classList.add('message-sender');
+ senderName.textContent = name;
+
+ const messageText = document.createElement('div');
+ messageText.classList.add('message-text');
+
+ messageText.innerHTML = message;
+
+ messageContent.appendChild(senderName);
+ messageContent.appendChild(messageText);
+ messageDiv.appendChild(avatarImg);
+ messageDiv.appendChild(messageContent);
+ container.appendChild(messageDiv);
+
+ if (topic === currentTopic()) {
+ container.scrollTop = container.scrollHeight;
+ }
}
async function updateIcon(username, avatarBuffer) {
@@ -826,120 +1146,153 @@ async function updateIcon(username, avatarBuffer) {
}
}
-function clearMessagesCMD() {
- const messagesContainer = document.querySelector('#messages');
- while (messagesContainer.firstChild) {
- messagesContainer.removeChild(messagesContainer.firstChild);
- }
-
- // Clear the messages from the store for the current room
- const topic = currentTopic();
- messagesStore[topic] = [];
-}
function clearMessages() {
const messagesContainer = document.querySelector('#messages');
while (messagesContainer.firstChild) {
messagesContainer.removeChild(messagesContainer.firstChild);
}
-
- // Clear the messages from the store for the current room
- // const topic = currentTopic();
- // messagesStore[topic] = [];
}
-function toggleSetupView() {
- const setupDiv = document.querySelector('#setup');
- setupDiv.classList.toggle('hidden');
+function currentTopic() {
+ return document.querySelector('#chat-room-topic').innerText;
}
-function writeConfigToFile(filePath) {
- fs.writeFile(filePath, JSON.stringify(config), (err) => {
- if (err) return console.error(err);
- console.log("File has been created");
+async function sendMessage(e) {
+ e.preventDefault();
+ const message = document.querySelector('#message').value;
+ document.querySelector('#message').value = '';
+
+ const topic = currentTopic();
+ const timestamp = Date.now();
+
+ console.log("Sending message to current topic:", topic); // Add logging
+ if (message.startsWith('>')) {
+ await handleCommand(message, {
+ eventEmitter,
+ currentTopic: topic, // Pass the current topic as a string
+ clearMessages,
+ addMessage,
+ joinRoom,
+ leaveRoom,
+ createRoom,
+ listFiles,
+ deleteFile,
+ servePort
+ });
+ return;
+ }
+
+ console.log('Sending message:', message); // Debugging log
+
+ onMessageAdded(config.userName, message, config.userAvatar, topic, timestamp);
+
+ const messageObj = JSON.stringify({
+ type: 'message',
+ name: config.userName,
+ avatar: updatePortInUrl(config.userAvatar),
+ topic: topic,
+ timestamp: timestamp,
+ message
});
+
+ const peers = [...activeRooms.find(room => room.topic === topic).swarm.connections];
+ for (const peer of peers) {
+ peer.write(messageObj);
+ }
}
-function updatePortInUrl(url) {
- if (!url) return url;
- const urlObject = new URL(url);
- if(!urlObject.host.startsWith("localhost")) return urlObject.toString();
- urlObject.port = servePort;
- return urlObject.toString();
-}
-
-function renderRoomList() {
- const roomList = document.querySelector('#room-list');
- roomList.innerHTML = '';
-
- config.rooms.forEach(room => {
- const roomItem = document.createElement('li');
- roomItem.textContent = room.alias || truncateHash(room.topic);
- roomItem.dataset.topic = room.topic;
-
- roomItem.addEventListener('dblclick', () => enterEditMode(roomItem));
- roomItem.addEventListener('click', () => switchRoom(room.topic));
- roomList.appendChild(roomItem);
- });
-}
-
-function renderMessagesForRoom(topic) {
- console.log('Rendering messages for room:', topic); // Debugging log
- // Clear the message area
- clearMessages();
-
- // Fetch and render messages for the selected room
- const messages = getMessagesForRoom(topic);
- messages.forEach(message => {
- onMessageAdded(message.from, message.message, message.avatar, topic, message.timestamp);
- });
-}
-
-function getMessagesForRoom(topic) {
- return messagesStore[topic] || [];
-}
-
-function addMessageToStore(topic, messageObj) {
+function onMessageAdded(name, message, avatar, topic, timestamp) {
if (!messagesStore[topic]) {
messagesStore[topic] = [];
}
-
- // Check for duplicates using a combination of message content and timestamp
- const isDuplicate = messagesStore[topic].some(msg =>
- msg.from === messageObj.from &&
- msg.message === messageObj.message &&
- msg.timestamp === messageObj.timestamp
- );
+ messagesStore[topic].push({ name, message, avatar, timestamp });
- if (!isDuplicate) {
- messagesStore[topic].push(messageObj);
- } else {
- console.log('Duplicate message detected:', messageObj); // Debugging log
+ const chatRoomTopic = currentTopic();
+ if (topic === chatRoomTopic) {
+ addMessage(name, message, avatar, topic);
}
}
-function loadConfigFromFile() {
- config = JSON.parse(fs.readFileSync("./config.json", 'utf8'));
- console.log("Read config from file:", config);
- // Update port in URLs
- config.userAvatar = updatePortInUrl(config.userAvatar);
- config.rooms.forEach(room => {
- room.alias = room.alias || truncateHash(room.topic);
+function renderMessagesForRoom(topic) {
+ const container = document.querySelector('#messages');
+ if (!container) {
+ console.error('Element #messages not found');
+ return;
+ }
+
+ container.innerHTML = '';
+
+ if (!messagesStore[topic]) return;
+
+ messagesStore[topic].forEach(({ name, message, avatar }) => {
+ addMessage(name, message, avatar, topic);
});
- for (let user in config.registeredUsers) {
- config.registeredUsers[user] = updatePortInUrl(config.registeredUsers[user]);
+}
+
+function handleFileInput(event) {
+ const file = event.target.files[0];
+ if (file) {
+ const reader = new FileReader();
+ reader.onload = async function(event) {
+ const buffer = new Uint8Array(event.target.result);
+ const filePath = `/files/${file.name}`;
+ await drive.put(filePath, buffer);
+
+ const fileUrl = `http://localhost:${servePort}/files/${file.name}`;
+ const topic = currentTopic();
+
+ const fileMessage = {
+ type: 'file',
+ name: config.userName,
+ fileName: file.name,
+ file: b4a.toString(buffer, 'base64'),
+ fileType: file.type,
+ avatar: updatePortInUrl(config.userAvatar),
+ topic: topic,
+ timestamp: Date.now()
+ };
+
+ const peers = [...activeRooms.find(room => room.topic === topic).swarm.connections];
+ for (const peer of peers) {
+ peer.write(JSON.stringify(fileMessage));
+ }
+
+ addFileMessage(config.userName, file.name, fileUrl, file.type, config.userAvatar, topic);
+ };
+ reader.readAsArrayBuffer(file);
}
}
-async function connectToAllRooms() {
- // Connect to all rooms on startup
- for (const room of config.rooms) {
- const topicBuffer = b4a.from(room.topic, 'hex');
- await joinSwarm(topicBuffer);
+async function joinGuildRequest(guildTopic) {
+ const guildTopicBuffer = b4a.from(guildTopic, 'hex');
+ if (!activeRooms.some(room => room.topic === guildTopic)) {
+ try {
+ const swarm = new Hyperswarm();
+ const discovery = swarm.join(guildTopicBuffer, { client: true, server: true });
+ await discovery.flushed();
+
+ swarm.on('connection', (connection, info) => {
+ handleConnection(connection, info);
+ // Request guild information from peers
+ const guildRequestMessage = JSON.stringify({
+ type: 'guildRequest',
+ guildTopic
+ });
+ connection.write(guildRequestMessage);
+ });
+
+ activeRooms.push({ topic: guildTopic, swarm, discovery });
+
+ console.log('Joined guild topic:', guildTopic); // Debugging log
+
+ updatePeerCount();
+ } catch (error) {
+ console.error('Error joining swarm for guild topic:', guildTopic, error);
+ }
}
}
-// Call this function when loading the rooms initially
-renderRoomList();
+window.joinGuildRequest = joinGuildRequest;
initialize();
diff --git a/chatBot/commands/ping.js b/chatBot/commands/ping.js
index 8ba0a84..d7566cd 100644
--- a/chatBot/commands/ping.js
+++ b/chatBot/commands/ping.js
@@ -1,7 +1,7 @@
export default {
handler: function(bot, args, message) {
// Specify the path to the file you want to send
- const filePath = '/to/file/path.js'; // Replace with the actual file path
+ 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
diff --git a/commands.js b/commands.js
index 2d6b26f..2131078 100644
--- a/commands.js
+++ b/commands.js
@@ -1,4 +1,3 @@
-// commands.js
import b4a from 'b4a';
import fs from 'fs';
@@ -12,45 +11,94 @@ if (fs.existsSync(agentAvatarPath)) {
}
export default async function handleCommand(command, context) {
- const { eventEmitter, currentTopic, clearMessages, addMessage, joinRoom, leaveRoom, createRoom, listFiles } = 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();
+ case '>clear':
+ clearMessages(currentTopic);
break;
- case '~ping':
- addMessage('LinkUp', 'pong', agentAvatar, currentTopic());
+ 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());
+ 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':
+ case '>join':
if (restArgs) {
- await joinRoom(restArgs);
+ await joinRoom(currentTopic, restArgs);
} else {
- addMessage('LinkUp', 'Usage: ~join [topic]', agentAvatar, currentTopic());
+ addMessage('LinkUp', 'Usage: >join [topic]', agentAvatar, currentTopic);
}
break;
- case '~leave':
- leaveRoom(currentTopic());
+ case '>leave':
+ leaveRoom(currentTopic);
break;
- case '~create':
+ case '>create':
if (restArgs) {
- await createRoom(restArgs);
+ await createRoom(currentTopic, restArgs);
} else {
- addMessage('LinkUp', 'Usage: ~create [alias]', agentAvatar, currentTopic());
+ addMessage('LinkUp', 'Usage: >create [alias]', agentAvatar, currentTopic);
}
break;
- case '~list-files':
+ 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());
+ 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);
+}
diff --git a/index.html b/index.html
index d918404..6d89123 100644
--- a/index.html
+++ b/index.html
@@ -17,10 +17,10 @@
@@ -35,12 +35,12 @@
-
+
- or -
-
-
-
-
@@ -62,7 +61,7 @@
-
+
@@ -70,11 +69,46 @@
Loading ...
+
+
+
+
+ ×
+
Create Guild
+
+
+
+
+
+
+
+
×
+
Manage Guild
+
+
+
+
+
+
+
+
+