Compare commits

..

248 Commits

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

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

Perfecto
2024-06-14 08:37:47 +00:00
65576e82b3 Added proper shutting down 2024-06-14 08:35:54 +00:00
9eeb38ca3f Validate topicBuffer: Ensure topicBuffer is always defined and valid before converting it to a string. 2024-06-14 04:33:50 -04:00
d3792b4bd0 Merge pull request 'Up to date fork' (#1) from snxraven/LinkUp-P2P-Chat:main into main
Reviewed-on: #1
2024-06-14 08:32:25 +00:00
50a02e8dfd topic validation for the join box 2024-06-14 04:04:00 -04:00
ff0acb1a85 add readme 2024-06-14 03:17:31 -04:00
4d9d22060b Adding hyperdrive files management via ~list-files 2024-06-14 01:56:46 -04:00
f84a6cff1b better formatting for help 2024-06-14 01:28:08 -04:00
6a74c23f9c fixed 2024-06-14 01:19:58 -04:00
f50370d2e5 Missed something 2024-06-14 01:18:13 -04:00
59e72998c6 Fix message history by adding new clear function for internal command 2024-06-14 01:17:18 -04:00
458954c1e1 adding in new commands 2024-06-14 01:10:23 -04:00
b56d603b1e Add internal command handler with ping and clear commands to start 2024-06-14 00:57:05 -04:00
7dbcf17429 Duplicate message fix 2024-06-14 00:33:19 -04:00
98aa79f075 Prevent Message Duplicates in Message History 2024-06-14 00:08:32 -04:00
7a31202ac3 Fixing user avatars/icons concerning all of the major changes today 2024-06-13 23:12:31 -04:00
7061aa1e5e fixes 2024-06-13 22:41:43 -04:00
af37909b3f update 2024-06-13 22:29:05 -04:00
2faf2b6f78 Moving to MultiSwarm with proper connection tracking per topic 2024-06-13 21:44:30 -04:00
b1ea06018b Using swarm.connections.size 2024-06-13 21:05:10 -04:00
116b23d70f Move back to global peer count 2024-06-13 20:47:45 -04:00
0dbd441cd5 Merge branch 'main' of git.ssh.surf:snxraven/LinkUp-P2P-Chat 2024-06-13 20:05:21 -04:00
b50bd0bbab Room Counts done better 2024-06-13 20:02:01 -04:00
6b1b13f75e revert 0a3732fb07
revert Re adding per room topic counts
2024-06-13 23:41:30 +00:00
0a3732fb07 Re adding per room topic counts 2024-06-13 19:23:42 -04:00
5edcff29e1 revert 8c4a38ca8e
revert peer count per room
2024-06-13 20:06:49 +00:00
8c4a38ca8e peer count per room 2024-06-13 16:00:02 -04:00
3c803c811e Debug 2024-06-13 14:50:23 -04:00
6a7eb78d34 fixing talk button not turning red 2024-06-13 06:10:26 -04:00
76b00ce5b2 New dark mode theme, removing green, making UI uniform 2024-06-13 05:55:14 -04:00
145b5b728b Better style for the side bar + removing alert on copy 2024-06-13 05:24:44 -04:00
6b3f043332 first attempt at audio messages 2024-06-13 03:53:23 -04:00
060308ec92 Remove localStorage uses 2024-06-13 01:44:44 -04:00
7461dadf88 remove unused function 2024-06-13 01:38:10 -04:00
052fbecc20 Remove not needed join line 2024-06-12 00:08:34 -04:00
b9db313eff Fix chatBot 2024-06-11 17:40:02 -04:00
9a14c10bac Merge pull request 'Reworking bot code. Again...' (#7) from MiTask/LinkUp-P2P-Chat:main into main
Reviewed-on: snxraven/LinkUp-P2P-Chat#7
2024-06-11 20:38:54 +00:00
f69b22c810 Removed .idea folder 2024-06-11 21:49:34 +03:00
00b26d6afe Changed lots of stuff inside of bot code. Got rid of peers in message object. 2024-06-11 21:47:42 +03:00
4aa205cd73 Got rid of 2 useless classes 2024-06-11 20:56:02 +03:00
5bb5e63105 Trying to merge is so hard 2024-06-11 20:54:43 +03:00
4415d196de small change in text 2024-06-11 20:53:30 +03:00
cd4b333954 Remove not needed code 2024-06-11 03:31:20 -04:00
a92b22ef17 Remove not needed code 2024-06-11 03:28:26 -04:00
7a74d3fc4d Add room names to room view header, add a copy topic button and remove the display of the topics 2024-06-11 02:52:53 -04:00
6eab069cc5 Move Peer Count to Title Bar | Make the count global to all rooms 2024-06-11 01:12:01 -04:00
7ec2901165 revert 9aedd9fda4
revert update peer count per topic
2024-06-11 04:40:01 +00:00
9aedd9fda4 update peer count per topic 2024-06-11 00:18:27 -04:00
e57db27a13 Fixing bot 2024-06-10 22:20:36 -04:00
157c8af4f4 Update bot Client to work with the new connection logic 2024-06-10 22:04:57 -04:00
6a0b05df86 REFRACTOR: New connection logic, all saved rooms are connected on init. New Room tracking and message tracking with message history after switching. Better connection management 2024-06-10 21:51:34 -04:00
d0d408230a Adding buttons instead of links for downloads 2024-06-10 21:08:44 -04:00
3dcabac0b4 Fix channel edit input size 2024-06-10 20:28:16 -04:00
8d1ecc2c19 Adding in Room Aliases 2024-06-10 20:01:00 -04:00
01fee56692 Fixing bot messages 2024-06-10 19:49:46 -04:00
e1153cb5df Fixed Message attachments! 2024-06-10 17:18:06 -04:00
25e421e982 more work on file attachments 2024-06-10 17:13:20 -04:00
3a0af4ace2 Adding syntax highlights 2024-06-10 16:32:52 -04:00
4fbbc73527 Merge pull request 'Fixed bot crash on file attachment, added 2 more new events and topic ID to file message' (#6) from MiTask/LinkUp-P2P-Chat:main into main
Reviewed-on: snxraven/LinkUp-P2P-Chat#6
2024-06-10 20:09:25 +00:00
edf61f0462 Started working on proper classes for messages (files and text), user peer class, chatroom class 2024-06-10 21:34:50 +03:00
92ba56b9fc Added topic to file attachment json 2024-06-10 21:25:41 +03:00
7275b69948 Added onIcon and onFile events. onMessage is now being called only on messageType == message. Removed debug code from swarm.on('update'). 2024-06-10 21:23:59 +03:00
56d8a8091e Yeah it actually works :yay: 2024-06-10 21:18:33 +03:00
4ec0be2774 I think i got it to work lol 2024-06-10 21:17:16 +03:00
27b3528304 It can have multiple topics :( time to figure a way to do it then ig 2024-06-10 21:13:52 +03:00
2ca07e46c3 so topics in .map() are array. Is it array that always has only one buffer or can have lots of them??? 2024-06-10 21:11:46 +03:00
e396bcbb0b b4a doesnt work its time to debug again 2024-06-10 21:10:25 +03:00
3b7f0fbd1d replaced topic.toString with b4a.toString(topic) 2024-06-10 21:09:33 +03:00
79bdd04810 Lmfao i did peer.topics 2024-06-10 21:06:09 +03:00
1dca21fe75 Did an oopsie with array.filter ([array.filter] instead of [array].filter) 2024-06-10 21:03:01 +03:00
edd3f632cf Now its time to debug peer topics since its empty right now 2024-06-10 21:02:01 +03:00
340362872f Lol it actually was inverted for some reason 2024-06-10 21:00:22 +03:00
230cdd1321 Huh? Is it actually inverted?? 2024-06-10 20:58:59 +03:00
acb5609d03 Its not working 😭 time to debug 2024-06-10 20:57:32 +03:00
66d684e473 Filter all the peerInfo topics that are undefined and then map all of them to hex string 2024-06-10 20:56:02 +03:00
03f053723d Merge branch 'main' of https://git.ssh.surf/snxraven/LinkUp-P2P-Chat 2024-06-10 20:49:17 +03:00
033330fd04 started working on parsing peers & topics they're currently in on swarm update event using 'swarm.peers' 2024-06-10 20:46:59 +03:00
b88337c398 Merge pull request 'Fixed app.js not sending user ID itself and only peers it knows' (#5) from MiTask/LinkUp-P2P-Chat:main into main
Reviewed-on: snxraven/LinkUp-P2P-Chat#5
2024-06-10 15:26:59 +00:00
bfe0bafebc I removed all of the debugging code for now to push the fix for app.js 2024-06-10 18:23:47 +03:00
d233f9e5aa I sometimes really hate nodej 2024-06-10 18:07:59 +03:00
4134e20403 Array of arrays got fixed but i still cant figure out the issue with topics ehh 2024-06-10 18:06:00 +03:00
d993090f8b I actually didnt fix the issue with array of arrays. Now it should be fine 2024-06-10 18:03:15 +03:00
ecfc62ff2c Forgot to check if my temp array is > 0 😭 2024-06-10 18:00:51 +03:00
7ed5681639 Huh publicKey is undefined lol 2024-06-10 17:59:54 +03:00
5e52298196 Ehh its not remotePubKey but just pubKey 2024-06-10 17:58:37 +03:00
5d6728f39f Filtering all the peers except myself now 2024-06-10 17:57:52 +03:00
051aa09c2d Opps i had array of arrays of peers in message json 2024-06-10 17:55:23 +03:00
2ed13e50f7 Ok i will stop exiting to test it properly 2024-06-10 17:53:36 +03:00
aa53cb487b I tried to iterate over not iterable object again 😭 2024-06-10 17:50:20 +03:00
d1ee697c84 There's something even more interesting called 'peerInfo.topics' lol. So bad it has no good docs and i have to do this stuff to learn more 2024-06-10 17:48:24 +03:00
7751d03b57 Found something interesting called 'swarm.peers' 2024-06-10 17:44:32 +03:00
6980520a85 Lol i tried to iterate over not-iterable object 2024-06-10 17:40:57 +03:00
7399585d9e Trying to find a way to check which topic each pear is from 2024-06-10 17:38:50 +03:00
fda25abe8c Oops debugging didnt go as planned lol 2024-06-10 17:34:57 +03:00
4cfae430b2 Started working on ChatRoom class 2024-06-10 17:34:21 +03:00
8788adbc2b Fixed sending ON_READY_MESSAGE 2024-06-10 17:28:18 +03:00
d9dbdf4401 Oh i actually broke it because push returns length and not array itself 💀 2024-06-10 17:22:06 +03:00
961599f366 I might've fix sending all 'peers' lol 2024-06-10 17:19:36 +03:00
704f6ca0dc Hmm how can i get publickey of the user sending info and not just peers who connected? 2024-06-10 17:17:34 +03:00
805983e3f5 Oops forgot about bot startup message. It doesnt work because there's no known peers yet 2024-06-10 17:14:26 +03:00
165d2f0faa Trying to fix a bug with message peers 2024-06-10 17:07:54 +03:00
24101e5611 Trying to make multiple rooms support. This is gonna be a bit of a hell lol. 2024-06-09 22:55:32 +03:00
9f5d5b7dba Merge branch 'main' of https://git.ssh.surf/snxraven/LinkUp-P2P-Chat 2024-06-09 21:01:00 +03:00
f269781226 revert too tired 2024-06-09 07:00:42 -04:00
5ff2460d23 add debug, last try for now 2024-06-09 06:44:26 -04:00
d5ea2abb7a add debug 2024-06-09 06:39:15 -04:00
3835149c92 add debug 2024-06-09 06:35:45 -04:00
206d095bcf attempts for file attachments again 2024-06-09 06:25:30 -04:00
0010feed24 attempts for file attachments again 2024-06-09 06:13:58 -04:00
b07579cdc9 another shot 2024-06-09 06:10:42 -04:00
f6d9991c71 attempts for file attachments again 2024-06-09 06:06:08 -04:00
3042974e55 repair file sharing hopefully 2024-06-09 05:40:37 -04:00
b46ca2b2df repair icons for report peers 2024-06-09 05:35:37 -04:00
55d00260fa fix user icons between peers 2024-06-09 05:09:39 -04:00
1c07819d91 Merge pull request 'Fixed rooms not being deleted from the json file' (#4) from MiTask/LinkUp-P2P-Chat:main into main
Reviewed-on: snxraven/LinkUp-P2P-Chat#4

#killinit. - Coding and vibin like it should be!
2024-06-09 08:19:59 +00:00
9587ae4818 I guess its time to work on multiple rooms now 2024-06-09 11:19:57 +03:00
8bac7def09 I fr fixed it this time 2024-06-09 11:17:20 +03:00
d44ef48d89 I guess i know why it doesnt delete the room now 2024-06-09 11:16:22 +03:00
2a2fbed732 Well i didnt... 2024-06-09 11:15:05 +03:00
c59795a407 Well i hope i fixed it now 2024-06-09 11:12:40 +03:00
33e0a13f93 Merge branch 'main' of https://git.ssh.surf/snxraven/LinkUp-P2P-Chat 2024-06-09 11:10:02 +03:00
840b6e2069 Fixed removing rooms 2024-06-09 11:09:58 +03:00
babde932e7 adding readableTimestamp to messages 2024-06-09 04:09:07 -04:00
72bf632cd4 I dont know what to say here because i just need up-to-date repo on my laptop 2024-06-09 11:07:52 +03:00
8458430989 adding peers to messages 2024-06-09 04:07:24 -04:00
966873774c Merge branch 'main' of https://git.ssh.surf/snxraven/LinkUp-P2P-Chat 2024-06-09 11:05:42 +03:00
1c85c78e4c add topic id to message 2024-06-09 04:02:45 -04:00
3df73b97c5 Guess gonna try this and then really take a break 2024-06-09 10:55:54 +03:00
824e2dab42 Started working on making the bot be in multiple chatrooms 2024-06-09 10:51:15 +03:00
f83befa2e5 Really fix it 2024-06-09 03:38:53 -04:00
7a1eef31e4 Bug Fix: Update HTTP Port for icon on launch, remove timebased stoage directory for single user usage 2024-06-09 03:34:41 -04:00
a79195d140 Revert "Bug Fix: Update HTTP Port for icon on launch, remove timebased stoage directory for single user usage"
This reverts commit 3d0c10db2b.
2024-06-09 03:34:27 -04:00
3d0c10db2b Bug Fix: Update HTTP Port for icon on launch, remove timebased stoage directory for single user usage 2024-06-09 03:33:28 -04:00
9bcf6b8b43 Merge pull request 'Working on Config system (Saving & Loading user data)' (#3) from MiTask/LinkUp-P2P-Chat:main into main
Reviewed-on: snxraven/LinkUp-P2P-Chat#3

Well done!
2024-06-09 07:11:48 +00:00
0077989253 It acutally works just fine now yay 2024-06-09 10:08:29 +03:00
f3b4755821 Very strange stuff tbh 2024-06-09 10:03:23 +03:00
f6f804ab39 It actually works now but i want to get it to work properly 2024-06-09 10:01:01 +03:00
f5f8d0ce19 And I honestly have no idea why it doesnt want to work 2024-06-09 10:00:01 +03:00
13e4020920 It just doesnt want to put proper data to the config for some strange reason 2024-06-09 09:58:35 +03:00
4a881ed721 Its actually not the reason... 2024-06-09 09:57:11 +03:00
2befd34039 Ohh reading is async, thats why it didnt work 2024-06-09 09:56:12 +03:00
b577ff3207 It doesnt want to use config for some reason. Time to debug stuff 2024-06-09 09:54:30 +03:00
8ca1cc2b8f I just forgot to read from the file LOL 2024-06-09 09:53:11 +03:00
a3fbe7434a Hmm will it work this way? 2024-06-09 09:50:05 +03:00
a9f50e892b Oopsie checking if config exists didnt work (It is done by checking if username is not empty lol) 2024-06-09 09:46:42 +03:00
1271c48571 Mostly finished loading. User avatar stuff is left to do 😭 2024-06-09 09:44:52 +03:00
d33d31debf Got rid of userName and userAvatar in exchange of config.userName and config.userAvatar 2024-06-09 09:32:33 +03:00
89ebe02743 Merge branch 'main' of https://git.ssh.surf/snxraven/LinkUp-P2P-Chat 2024-06-09 09:29:39 +03:00
b227683778 Adding padding to the attach file and send buttons 2024-06-09 02:27:20 -04:00
c2e1242d8b Merge branch 'main' of https://git.ssh.surf/snxraven/LinkUp-P2P-Chat
This is automatic merge of FileUploading and Config commits.
2024-06-09 09:24:04 +03:00
3947e78aef Started working on saving the user data on actions like registration, joining and leaving rooms. Lacks of loading on start up. 2024-06-09 09:23:45 +03:00
ee52e49030 Adding file and image upload and sharing 2024-06-09 02:22:46 -04:00
2fa431886c make window controls look better, move app name to the right side 2024-06-09 01:55:39 -04:00
27f555d267 Setting force window default size, min size and adding real window buttons 2024-06-09 01:27:42 -04:00
148f79ed13 Actually fixing the scrollbar issue and styling the chat scrollbar 2024-06-09 00:58:14 -04:00
d6f14c3062 removing overflow-y from css to remove scrollbar 2024-06-09 00:50:03 -04:00
34b771bea9 Removing imports that are no longer needed 2024-06-09 00:00:11 -04:00
6d469dd0fd move storage into subdir 2024-06-08 23:49:34 -04:00
4e961aebb7 Update CSS layout and Syles 2024-06-08 20:38:18 -04:00
4cc087fae2 delete ai command from repo 2024-06-08 23:08:39 +00:00
d26c025073 update gitignore 2024-06-08 19:07:31 -04:00
a4f89be654 Merge branch 'main' of git.ssh.surf:snxraven/LinkUp-P2P-Chat 2024-06-08 19:07:04 -04:00
e1adede7c3 update gitignore 2024-06-08 19:06:25 -04:00
cc654112b2 Merge pull request 'Some more changed to the bot structure' (#2) from MiTask/LinkUp-P2P-Chat:main into main
Reviewed-on: snxraven/LinkUp-P2P-Chat#2
2024-06-08 21:11:17 +00:00
9461368c02 Update port range 2024-06-08 17:08:30 -04:00
302589e97e Bug Fix: Allowing clients to have different ServePorts per client and storages per client 2024-06-08 16:48:10 -04:00
736a3a5e70 Merge branch 'main' of git.ssh.surf:snxraven/LinkUp-P2P-Chat 2024-06-08 16:47:46 -04:00
d65b76cbc5 Merge branch 'main' into main 2024-06-08 20:47:37 +00:00
7855689289 Bug Fix: Allowing clients to have different ServePorts per client and storages per client 2024-06-08 16:46:53 -04:00
8dbaa629d2 Revert "Bug Fix: Allowing clients to have different ServePorts per client and storages per client"
This reverts commit b1271ceb82.
2024-06-08 16:46:36 -04:00
b1271ceb82 Bug Fix: Allowing clients to have different ServePorts per client and storages per client 2024-06-08 16:46:29 -04:00
5931d68a29 Revert "Bug Fix: Allowing clients to have different ServePorts per client and storages per client"
This reverts commit 5f8817a2f2.
2024-06-08 16:45:46 -04:00
5f8817a2f2 Bug Fix: Allowing clients to have different ServePorts per client and storages per client 2024-06-08 16:45:15 -04:00
9f2353c902 Added check if botName or chatRoomID is not defined 2024-06-08 23:38:44 +03:00
cd5cfb20a4 Replaced 'setTimeOut' with bot.on('onBotJoinRoom', () => {}); for sending ON_READY_MESSAGE to the chat 2024-06-08 23:36:00 +03:00
164efc5b72 Removed Enum class because it does not work 😭 2024-06-08 23:31:24 +03:00
b244aa9bdf Added Event enum and 2 new events 2024-06-08 23:29:51 +03:00
f045f5804d Merge pull request 'Rework ChatBot' (#1) from MiTask/LinkUp-P2P-Chat:main into main
Reviewed-on: snxraven/LinkUp-P2P-Chat#1
2024-06-08 20:19:09 +00:00
114875cddb Merge branch 'main' of https://git.ssh.surf/snxraven/LinkUp-P2P-Chat 2024-06-08 23:17:29 +03:00
dd8099649f Tests for user icons with eventEmitter 2024-06-08 16:15:55 -04:00
c15b298268 Tests 2024-06-08 16:13:20 -04:00
d0310247c5 Trying to fix peer counts 2024-06-08 16:08:53 -04:00
24 changed files with 2564 additions and 373 deletions

7
.gitignore vendored
View File

@ -1,5 +1,10 @@
node_modules
package-lock.json
shared-storage
storage_*
storage
chatBot/.env
chatBot/.env
chatBot/commands/ai.js
config.json
.idea
AIBot

134
README.md Normal file
View File

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

1318
app.js

File diff suppressed because it is too large Load Diff

BIN
assets/agent.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

220
chatBot/README.md Normal file
View File

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

BIN
chatBot/assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -1,6 +1,6 @@
import fs from 'fs';
import path from 'path';
import Client from './includes/client.js'; // Adjust the import path as necessary
import Client from './includes/Client.js'; // Adjust the import path as necessary
import 'dotenv/config';
// Create a new instance of the chatBot class with a valid botName
@ -35,15 +35,13 @@ async function loadCommands() {
loadCommands().then(commands => {
// Create the chatBot instance with the defined onMessageReceived function
const bot = new Client(process.env.LINKUP_ROOM_ID, botName);
const bot = new Client(botName);
// We use Event Emitter here to handle new messages
bot.on('onMessage', (peer, message) => {
if (message.type == "icon") return;
console.log(`Message received from ${message.peerName} at ${new Date(message.timestamp).toLocaleTimeString()}: ${message.message}`);
console.log(message);
console.log(`Message received from ${message.name} at ${new Date(message.timestamp).toLocaleTimeString()}: ${message.message}`);
// Check if the message starts with a command prefix
if (message.message.startsWith('!')) {
// Extract the command and arguments
@ -54,17 +52,24 @@ loadCommands().then(commands => {
// If the command exists, execute its handler
if (commandHandler && typeof commandHandler.handler === 'function') {
console.log(`Executing command: ${command} with arguments: ${args}`);
commandHandler.handler(bot, args, message);
} else {
console.warn(`Command not found: ${command}`);
}
}
})
bot.joinChatRoom();
});
bot.on('onBotJoinRoom', () => {
console.log("Bot is ready!");
bot.sendTextMessage(process.env.ON_READY_MESSAGE);
});
bot.joinChatRoom(process.env.LINKUP_ROOM_ID);
const iconPath = path.join(new URL('./assets/icon.png', import.meta.url).pathname);
bot.fetchAvatar(iconPath); // Fetch the avatar from local file
// Wait for 2 seconds for the bot to connect
setTimeout(() => {
// Send a message
bot.sendMessage(process.env.ON_READY_MESSAGE);
}, 2000); // Adjust the delay as necessary
}).catch(error => {
console.error('Error loading commands:', error);
});

View File

@ -4,6 +4,6 @@ export default {
handler: function(bot, args, message) {
const responses = ['It is certain.', 'It is decidedly so.', 'Without a doubt.', 'Yes - definitely.', 'You may rely on it.', 'As I see it, yes.', 'Most likely.', 'Outlook good.', 'Yes.', 'Signs point to yes.', 'Reply hazy, try again.', 'Ask again later.', 'Better not tell you now.', 'Cannot predict now.', 'Concentrate and ask again.', 'Don\'t count on it.', 'My reply is no.', 'My sources say no.', 'Outlook not so good.', 'Very doubtful.'];
const randomIndex = Math.floor(Math.random() * responses.length);
bot.sendMessage(responses[randomIndex]);
bot.sendTextMessage(responses[randomIndex]);
}
};

View File

@ -3,6 +3,6 @@ export default {
description: 'Greet the user',
handler: function(bot, args, message) {
const userName = message.name;
bot.sendMessage(`Hello, ${userName}! How can I assist you today?`);
bot.sendTextMessage(`Hello, ${userName}! How can I assist you today?`);
}
};

View File

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

View File

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

View File

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

211
chatBot/includes/Client.js Normal file
View File

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

View File

@ -1,45 +0,0 @@
import Hyperswarm from 'hyperswarm';
import EventEmitter from 'node:events'
class Client extends EventEmitter {
constructor(chatRoomID, botName) {
super();
this.topicBuffer = Buffer.from(chatRoomID, 'hex');
this.botName = botName;
this.swarm = new Hyperswarm();
this.setupSwarm();
}
setupSwarm() {
this.swarm.on('connection', (peer) => {
peer.on('data', message => this.emit("onMessage", peer, JSON.parse(message.toString())));
peer.on('error', e => console.log(`Connection error: ${e}`));
});
this.swarm.on('update', () => {
console.log(`Peers count: ${this.swarm.connections.size}`);
});
}
joinChatRoom() {
this.discovery = this.swarm.join(this.topicBuffer, { client: true, server: true });
this.discovery.flushed().then(() => {
console.log(`Bot ${this.botName} joined the chat room.`);
});
}
sendMessage(message) {
console.log('Bot name:', this.botName);
const timestamp = Date.now(); // Generate timestamp
const peers = [...this.swarm.connections];
const data = JSON.stringify({ name: this.botName, message, timestamp }); // Include timestamp
for (const peer of peers) peer.write(data);
}
destroy() {
this.swarm.destroy();
console.log(`Bot ${this.botName} disconnected.`);
}
}
export default Client;

View File

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

View File

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

View File

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

View File

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

View File

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

104
commands.js Normal file
View File

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

BIN
images/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@ -7,22 +7,20 @@
<link rel="stylesheet" type="text/css" href="style.css">
<script type="module" src="./app.js"></script>
<script src="https://cdn.jsdelivr.net/npm/markdown-it/dist/markdown-it.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/styles/atom-one-dark.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/highlight.min.js"></script>
</head>
<body>
<header>
<div>LinkUp</div>
<div class="window-controls">
<button id="minimize-button">-</button>
<button id="maximize-button">+</button>
<button id="close-button">x</button>
</div>
<pear-ctrl></pear-ctrl>
<div id="linkup-text">LinkUp | Peers: <span id="peers-count">0</span></div>
</header>
<main>
<div id="sidebar">
<ul id="room-list">
<!-- Room list will be populated dynamically -->
<ul id="guild-list" class="list-group">
<!-- Guild list will be populated dynamically -->
</ul>
<button id="toggle-setup-btn">Create/Join Room</button>
<button id="toggle-setup-btn">Create/Join Guild</button>
</div>
<div id="content">
<div id="register" class="hidden">
@ -37,26 +35,23 @@
<div id="user-info">
<!-- User info will be populated dynamically -->
</div>
<button id="create-chat-room">Create Room</button>
<button id="create-guild-btn">Create Guild</button>
<div id="or">- or -</div>
<div id="join-chat-room-container">
<label for="connection-topic">Topic:</label>
<input type="text" id="join-chat-room-topic" placeholder="connection topic">
<button id="join-chat-room">Join Room</button>
<div id="join-guild-container">
<label for="join-guild-topic">Guild Topic:</label>
<input type="text" id="join-guild-topic" placeholder="guild topic">
<button id="join-guild">Join Guild</button>
</div>
</div>
<div id="chat" class="hidden">
<div id="header">
<div id="details">
<div>
Topic: <span id="chat-room-topic"></span>
<div style="display: inline;">
<strong><span id="chat-room-name"></span></strong> | <a href="#" id="copy-topic-link" class="mini-button">Copy Topic</a>
<span id="chat-room-topic" style="display: none;"></span>
<span id="chat-guild-topic" style="display: none;"></span>
<span id="chat-guild-name" style="display: none;"></span>
</div>
<div>
Peers: <span id="peers-count">0</span>
</div>
</div>
<div id="user-list">
<!-- User list will be populated here -->
</div>
</div>
<div id="messages-container">
@ -64,6 +59,9 @@
</div>
<form id="message-form">
<textarea id="message" rows="4"></textarea>
<input type="file" id="file-input" style="display:none;">
<button type="button" id="attach-file">Attach File</button>
<button type="button" id="talk-btn">Talk</button>
<input type="submit" value="Send" />
</form>
<button id="remove-room-btn">Leave Room</button>
@ -71,16 +69,189 @@
<div id="loading" class="hidden">Loading ...</div>
</div>
</main>
<!-- Create Guild Modal -->
<div id="create-guild-modal" class="modal hidden">
<div class="modal-content">
<span class="close-btn" id="close-create-guild-modal">&times;</span>
<h2>Create Guild</h2>
<form id="create-guild-form">
<label for="guild-name">Guild Name:</label>
<input type="text" id="guild-name" required>
<button type="submit">Create</button>
</form>
</div>
</div>
<!-- Manage Guild Modal -->
<div id="manage-guild-modal" class="modal hidden">
<div class="modal-content">
<span class="close-btn" id="close-manage-guild-modal">&times;</span>
<h2>Manage Guild</h2>
<div id="guild-info">
<!-- Guild info will be populated dynamically -->
</div>
<button id="copy-guild-id" class="mini-button">Copy Guild ID</button>
<form id="add-room-form">
<label for="room-name">Room Name:</label>
<input type="text" id="room-name" required>
<button type="submit">Add Room</button>
</form>
<ul id="room-list">
<!-- Room list will be populated dynamically -->
</ul>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const messageInput = document.getElementById('message');
messageInput.addEventListener('keydown', function(event) {
if (event.key === 'Enter' && !event.shiftKey) {
const copyTopicLink = document.getElementById('copy-topic-link');
const chatRoomTopic = document.getElementById('chat-room-topic');
const copyGuildIdButton = document.getElementById('copy-guild-id');
if (messageInput) {
messageInput.addEventListener('keydown', function(event) {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
document.getElementById('message-form').dispatchEvent(new Event('submit'));
}
});
}
if (copyTopicLink) {
copyTopicLink.addEventListener('click', function(event) {
event.preventDefault();
document.getElementById('message-form').dispatchEvent(new Event('submit'));
if (chatRoomTopic) {
const topic = chatRoomTopic.innerText;
navigator.clipboard.writeText(topic).then(() => {
console.log('Topic copied to clipboard:', topic);
}).catch(err => {
console.error('Failed to copy topic:', err);
});
} else {
console.error('Element #chat-room-topic not found');
}
});
}
if (copyGuildIdButton) {
copyGuildIdButton.addEventListener('click', function(event) {
event.preventDefault();
const guildTopic = document.getElementById('manage-guild-modal').dataset.guildTopic;
navigator.clipboard.writeText(guildTopic).then(() => {
console.log('Guild ID copied to clipboard:', guildTopic);
}).catch(err => {
console.error('Failed to copy guild ID:', err);
});
});
}
const guildList = document.querySelector('#guild-list');
if (guildList) {
guildList.addEventListener('click', function(event) {
const roomItem = event.target.closest('.room-item');
if (roomItem) {
const guildTopic = roomItem.dataset.guildTopic;
const roomTopic = roomItem.dataset.topic;
switchRoom(guildTopic, roomTopic);
}
});
}
const createGuildBtn = document.getElementById('create-guild-btn');
const createGuildModal = document.getElementById('create-guild-modal');
const closeCreateGuildModal = document.getElementById('close-create-guild-modal');
const createGuildForm = document.getElementById('create-guild-form');
createGuildBtn.addEventListener('click', () => {
createGuildModal.classList.remove('hidden');
});
closeCreateGuildModal.addEventListener('click', () => {
createGuildModal.classList.add('hidden');
});
createGuildForm.addEventListener('submit', (event) => {
event.preventDefault();
const guildName = document.getElementById('guild-name').value.trim();
if (guildName) {
createGuild(guildName);
createGuildModal.classList.add('hidden');
}
});
const joinGuildBtn = document.getElementById('join-guild');
joinGuildBtn.addEventListener('click', async (event) => {
const guildTopic = document.getElementById('join-guild-topic').value.trim();
if (guildTopic) {
await joinGuildRequest(guildTopic);
}
});
const manageGuildModal = document.getElementById('manage-guild-modal');
const closeManageGuildModal = document.getElementById('close-manage-guild-modal');
const addRoomForm = document.getElementById('add-room-form');
const roomNameInput = document.getElementById('room-name');
closeManageGuildModal.addEventListener('click', () => {
manageGuildModal.classList.add('hidden');
});
addRoomForm.addEventListener('submit', (event) => {
event.preventDefault();
const roomName = roomNameInput.value.trim();
const guildTopic = manageGuildModal.dataset.guildTopic;
if (roomName && guildTopic) {
addRoomToGuild(guildTopic, roomName);
roomNameInput.value = '';
}
});
});
function createGuild(guildName) {
const event = new CustomEvent('createGuild', { detail: { guildName } });
document.dispatchEvent(event);
}
function addRoomToGuild(guildTopic, roomName) {
const event = new CustomEvent('addRoomToGuild', { detail: { guildTopic, roomName } });
document.dispatchEvent(event);
}
function manageGuild(guildTopic) {
const event = new CustomEvent('manageGuild', { detail: { guildTopic } });
document.dispatchEvent(event);
}
function switchRoom(guildTopic, roomTopic) {
const event = new CustomEvent('switchRoom', { detail: { guildTopic, roomTopic } });
document.dispatchEvent(event);
}
function openManageGuildModal(guildTopic) {
const manageGuildModal = document.getElementById('manage-guild-modal');
const guildInfo = document.getElementById('guild-info');
const roomList = document.getElementById('room-list');
manageGuildModal.dataset.guildTopic = guildTopic;
guildInfo.innerHTML = `<h3>${config.guilds[guildTopic].alias}</h3>`;
roomList.innerHTML = '';
config.guilds[guildTopic].rooms.forEach(room => {
const roomItem = document.createElement('li');
roomItem.textContent = room.alias;
roomItem.dataset.guildTopic = guildTopic;
roomItem.dataset.topic = room.topic;
roomItem.addEventListener('dblclick', () => enterEditMode(roomItem, guildTopic));
roomItem.addEventListener('click', () => switchRoom(guildTopic, room.topic));
roomList.appendChild(roomItem);
});
manageGuildModal.classList.remove('hidden');
}
</script>
</body>
</html>

View File

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

487
style.css
View File

@ -12,65 +12,164 @@ body {
transition: background-color 0.3s ease, color 0.3s ease;
}
#messages-container {
height: 45vh; /* Adjust as needed */
overflow-y: auto;
}
.mini-button {
display: inline-block;
padding: 3px 7px;
font-size: 14px;
font-weight: bold;
color: #ffffff;
background-color: #464343;
border: none;
border-radius: 3px;
text-decoration: none;
cursor: pointer;
transition: background-color 0.3s ease, transform 0.3s ease;
}
/* body {
.mini-button:hover {
background-color: #181717;
transform: scale(1.05);
}
.bold {
font-weight: bold;
}
pear-ctrl[data-platform="darwin"] {
float: right;
margin-top: 4px;
}
pear-ctrl {
margin-top: 9px;
margin-left: 9px;
position: absolute;
}
pear-ctrl:after {
content: '';
display: block;
height: 1.8rem;
position: fixed;
z-index: -1;
left: 0;
top: 0;
width: 100%;
background-color: #1f1f1f;
filter: drop-shadow(2px 10px 6px #888);
}
main {
display: flex;
} */
#sidebar {
flex: 1;
overflow: hidden;
}
#sidebar {
width: 200px;
background-color: #2f3136;
color: white;
height: 100vh;
height: 100%;
padding: 10px;
box-sizing: border-box;
}
#room-list {
list-style: none;
padding: 0;
margin-bottom: 20px;
}
#room-list li {
padding: 10px;
cursor: pointer;
margin-bottom: 5px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
#room-list li:hover {
background-color: #40444b;
}
#content {
}
#content {
flex: 1;
display: flex;
flex-direction: column;
height: 100%;
padding: 10px;
}
#sidebar button {
}
#register,
#setup,
#chat {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 2rem;
background-color: #1f1f1f;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}
#chat {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
padding: 1rem;
background-color: #1e1e1e;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
width: 100%;
}
#messages-container {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
width: 100%;
}
#messages-container::-webkit-scrollbar {
width: 8px;
}
#messages-container::-webkit-scrollbar-thumb {
background-color: #464343;
border-radius: 10px;
}
#messages-container::-webkit-scrollbar-track {
background: #2e2e2e;
}
#message-form {
display: flex;
align-items: center;
margin-top: 1rem;
width: 100%;
}
#message {
flex: 1;
margin-right: 0.5rem;
padding-right: 0.5rem;
height: 1.5rem;
overflow-y: hidden;
}
#message:focus {
height: auto;
}
#message:empty {
height: 1.5rem;
}
#sidebar button {
width: 100%;
padding: 10px;
margin-bottom: 10px;
cursor: pointer;
background-color: #7289da;
background-color: #3e3c3c;
border: none;
color: white;
font-size: 14px;
border-radius: 5px;
}
#sidebar button:hover {
background-color: #5b6eae;
}
#remove-room-btn {
transition: background-color 0.3s ease, transform 0.3s ease;
}
#sidebar button:hover {
background-color: #191919;
transform: scale(1.05);
}
#remove-room-btn {
margin-top: 10px;
padding: 10px;
background-color: #f04747;
@ -78,21 +177,22 @@ body {
color: white;
cursor: pointer;
border-radius: 5px;
}
#remove-room-btn:hover {
transition: background-color 0.3s ease, transform 0.3s ease;
}
#remove-room-btn:hover {
background-color: #c03535;
}
.hidden {
transform: scale(1.05);
}
.hidden {
display: none;
}
}
/* Header styles */
header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background-color: #1f1f1f;
color: #fff;
@ -101,9 +201,12 @@ header {
-webkit-app-region: drag;
}
#linkup-text {
margin-left: auto;
}
.window-controls {
display: flex;
align-items: center;
-webkit-app-region: no-drag;
}
@ -116,42 +219,68 @@ header {
padding: 0.5rem;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s ease;
transition: background-color 0.3s ease, transform 0.3s ease;
}
.window-controls button:hover {
background-color: #3e3e3e;
transform: scale(1.05);
}
/* Button and input styles */
button,
input,
textarea {
border: 1px solid #b0d944;
border: 1px solid #464343;
background-color: #333;
color: #b0d944;
padding: 0.75rem;
color: #e0e0e0;
padding: 0.5rem;
font-family: 'Roboto Mono', monospace;
font-size: 1rem;
line-height: 1.5rem;
line-height: 1.25rem;
border-radius: 4px;
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
resize: none; /* Prevent resizing */
overflow-y: hidden; /* Hide scrollbar */
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease, transform 0.3s ease;
resize: none;
overflow-y: hidden;
}
button:hover,
input[type="submit"]:hover {
background-color: #191919;
transform: scale(1.05);
}
textarea {
height: auto; /* Allow the textarea to grow dynamically */
height: auto;
}
textarea:focus {
outline: none; /* Remove focus outline */
outline: none;
}
textarea::placeholder {
color: #a0a0a0;
}
#attach-file,
#message-form input[type="submit"] {
padding: 0.5rem 1rem;
margin-left: 0.5rem;
}
#talk-btn {
padding: 0.5rem 1rem;
margin-left: 0.5rem;
font-size: 14px;
border-radius: 5px;
cursor: pointer;
}
#talk-btn:active {
color: #fff;
background-color: #f04747;
}
/* Main container styles */
main {
display: flex;
@ -181,7 +310,7 @@ main {
#or {
margin: 1.5rem 0;
color: #b0d944;
color: #e0e0e0;
}
#loading {
@ -204,6 +333,7 @@ main {
}
#header {
width: 100%;
margin-bottom: 1rem;
}
@ -224,35 +354,57 @@ main {
#join-chat-room-container button {
margin-left: 0.5rem;
transition: background-color 0.3s ease, transform 0.3s ease;
}
#join-chat-room-container button:hover {
background-color: #191919;
transform: scale(1.05);
}
#details {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background-color: #2b2b2b;
border-radius: 4px;
width: 100%;
box-sizing: border-box;
}
#details>div {
display: flex;
flex-direction: column;
}
#submit-button {
margin-left: 1rem;
transition: background-color 0.3s ease, transform 0.3s ease;
}
#submit-button:hover {
background-color: #191919;
transform: scale(1.05);
}
#messages {
flex: 1;
min-height: 200px;
overflow-y: auto;
padding: 1rem;
padding: 0.5rem;
background-color: #262626;
border-radius: 4px;
display: flex;
flex-direction: column;
width: 100%;
}
#message-form {
display: flex;
align-items: center;
margin-top: 1rem;
margin-right: 1rem;
width: 100%;
}
#message {
@ -281,97 +433,186 @@ main {
/* Message styles */
.message {
display: flex;
align-items: center;
margin-bottom: 1rem;
align-items: flex-start;
margin-bottom: 0.5rem;
}
.message img {
width: 40px;
height: 40px;
.message img.avatar {
width: 32px;
height: 32px;
border-radius: 50%;
margin-right: 0.75rem;
border: 2px solid #b0d944;
margin-right: 0.5rem;
border: 2px solid #464343;
user-select: none;
}
.message-content {
max-width: 70%;
background-color: #2e2e2e;
padding: 0.5rem;
border-radius: 10px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.message-header {
font-weight: bold;
color: #b0d944;
color: #e0e0e0;
font-size: 0.9rem;
}
.message-time {
color: #a0a0a0;
font-size: 0.75rem;
margin-left: 0.5rem;
.message-text {
color: #e0e0e0;
}
/* Button and input styles */
button,
input,
textarea {
border: 1px solid #b0d944;
background-color: #333;
color: #b0d944;
padding: 0.75rem;
font-family: 'Roboto Mono', monospace;
font-size: 1rem;
line-height: 1.5rem;
.attached-image {
max-width: 100%;
height: auto;
margin-top: 0.5rem;
border-radius: 4px;
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
resize: none; /* Prevent resizing */
overflow-y: hidden; /* Hide scrollbar */
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
textarea {
height: auto; /* Allow the textarea to grow dynamically */
/* Updated Room List Styles */
#guild-list {
list-style: none;
padding: 0;
margin: 0;
}
textarea:focus {
outline: none; /* Remove focus outline */
}
textarea::placeholder {
color: #a0a0a0;
}
/* Chat container styles */
#chat {
.guild-item {
padding: 10px;
margin-bottom: 10px;
background-color: #3a3f44;
border-radius: 5px;
cursor: pointer;
display: flex;
flex-direction: column;
flex: 1;
width: 100%;
padding: 1rem;
background-color: #1e1e1e;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
overflow: hidden;
transition: background-color 0.3s ease;
}
#message-form {
.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;
margin-top: 1rem;
margin-right: 1rem;
flex-direction: column;
}
#message {
.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;
margin-right: 0.5rem;
padding-right: 0.5rem;
height: 1.5rem; /* Initial single line height */
overflow-y: hidden; /* Hide scrollbar */
}
#message:focus {
height: auto; /* Allow the textarea to grow dynamically when focused */
.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);
}
#message:empty {
height: 1.5rem; /* Ensure single line height when empty */
}