awacke1 commited on
Commit
0331b3d
ยท
verified ยท
1 Parent(s): 25c5d30

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +188 -139
app.py CHANGED
@@ -14,39 +14,64 @@ from urllib.parse import quote
14
  import base64
15
  import io
16
  import streamlit.components.v1 as components
 
 
 
 
 
 
17
 
18
  # Initial App Configuration (static)
19
  icons = '๐Ÿค–๐Ÿง ๐Ÿ”ฌ๐Ÿ“'
20
  START_ROOM = "Sector ๐ŸŒŒ"
21
 
22
  # Set page config once at the top
23
- st.set_page_config(
24
  page_title="๐Ÿค–๐Ÿง MMO Chat Brain๐Ÿ“๐Ÿ”ฌ", # Initial static title
25
  page_icon=icons,
26
  layout="wide",
27
  initial_sidebar_state="auto"
28
  )
29
 
30
- # Fun usernames with emojis
31
- FUN_USERNAMES = [
32
- "CosmicJester ๐ŸŒŒ", "PixelPanda ๐Ÿผ", "QuantumQuack ๐Ÿฆ†", "StellarSquirrel ๐Ÿฟ๏ธ",
33
- "GizmoGuru โš™๏ธ", "NebulaNinja ๐ŸŒ ", "ByteBuster ๐Ÿ’พ", "GalacticGopher ๐ŸŒ",
34
- "RocketRaccoon ๐Ÿš€", "EchoElf ๐Ÿง", "PhantomFox ๐ŸฆŠ", "WittyWizard ๐Ÿง™",
35
- "LunarLlama ๐ŸŒ™", "SolarSloth โ˜€๏ธ", "AstroAlpaca ๐Ÿฆ™", "CyberCoyote ๐Ÿบ",
36
- "MysticMoose ๐ŸฆŒ", "GlitchGnome ๐Ÿงš", "VortexViper ๐Ÿ", "ChronoChimp ๐Ÿ’"
37
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
  # Directories and files
40
  CHAT_DIR = "chat_logs"
41
  VOTE_DIR = "vote_logs"
42
  STATE_FILE = "user_state.txt"
 
 
43
  os.makedirs(CHAT_DIR, exist_ok=True)
44
  os.makedirs(VOTE_DIR, exist_ok=True)
 
 
45
 
46
  CHAT_FILE = os.path.join(CHAT_DIR, "global_chat.md")
47
  QUOTE_VOTES_FILE = os.path.join(VOTE_DIR, "quote_votes.md")
48
  MEDIA_VOTES_FILE = os.path.join(VOTE_DIR, "media_votes.md")
49
- HISTORY_FILE = os.path.join(VOTE_DIR, "vote_history.md")
50
 
51
  # Unicode digits and fonts
52
  UNICODE_DIGITS = {i: f"{i}\uFE0Fโƒฃ" for i in range(10)}
@@ -58,6 +83,11 @@ UNICODE_FONTS = [
58
  server_running = False
59
  server_task = None
60
 
 
 
 
 
 
61
  def get_node_name():
62
  """๐ŸŒ - Naming Node with Code - Spins a name, oh so bold!"""
63
  action = "๐ŸŒ - Naming Node with Code - Spins a name, oh so bold!"
@@ -70,7 +100,7 @@ def get_node_name():
70
  return args.node_name or f"node-{uuid.uuid4().hex[:8]}", args.port
71
 
72
  def log_action(username, action):
73
- """Helper to log actions with deduplication"""
74
  if 'action_log' not in st.session_state:
75
  st.session_state.action_log = {}
76
  user_log = st.session_state.action_log.setdefault(username, {})
@@ -78,7 +108,7 @@ def log_action(username, action):
78
  user_log = {k: v for k, v in user_log.items() if current_time - v < 10}
79
  st.session_state.action_log[username] = user_log
80
  if action not in user_log:
81
- with open(CHAT_FILE, 'a') as f:
82
  f.write(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {username}: {action}\n")
83
  user_log[action] = current_time
84
 
@@ -89,6 +119,12 @@ async def save_chat_entry(username, message):
89
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
90
  entry = f"[{timestamp}] {username}: {message}"
91
  await asyncio.to_thread(lambda: open(CHAT_FILE, 'a').write(f"{entry}\n"))
 
 
 
 
 
 
92
 
93
  async def load_chat():
94
  """๐Ÿ“– - Chat Fetch Quest - Grabs the log, no jest!"""
@@ -101,11 +137,11 @@ async def load_chat():
101
  content = await asyncio.to_thread(f.read)
102
  return content
103
 
104
- def get_user_list(chat_content):
105
  """๐Ÿ‘ฅ - Crew Clue Brew - Spots whoโ€™s who in the crew!"""
106
  action = "๐Ÿ‘ฅ - Crew Clue Brew - Spots whoโ€™s who in the crew!"
107
  username = st.session_state.get('username', 'System ๐ŸŒŸ')
108
- log_action(username, action)
109
  users = set()
110
  for line in chat_content.split('\n'):
111
  if line.strip() and ': ' in line:
@@ -129,19 +165,6 @@ async def get_message_suggestions(chat_content, prefix):
129
  messages = [line.split(': ', 1)[1] for line in lines if ': ' in line and line.strip()]
130
  return [msg for msg in messages if msg.lower().startswith(prefix.lower())][:5]
131
 
132
- async def load_quotes(source="famous"):
133
- """๐Ÿ“œ - Quote Tote Note - Wise words float, we gloat!"""
134
- action = "๐Ÿ“œ - Quote Tote Note - Wise words float, we gloat!"
135
- username = st.session_state.get('username', 'System ๐ŸŒŸ')
136
- await asyncio.to_thread(log_action, username, action)
137
- famous_quotes = [
138
- "The true sign of intelligence is not knowledge but imagination. โ€“ Albert Einstein",
139
- ]
140
- custom_quotes = [
141
- "Every age unfolds a new lesson. Life's chapters evolve, each teaching us anew.",
142
- ]
143
- return famous_quotes if source == "famous" else custom_quotes
144
-
145
  async def save_vote(file, item, user_hash, username, comment=""):
146
  """๐Ÿ‘ - Vote Note Float - Cheers rise, we gloat!"""
147
  action = "๐Ÿ‘ - Vote Note Float - Cheers rise, we gloat!"
@@ -185,12 +208,32 @@ async def generate_user_hash():
185
  st.session_state.user_hash = hashlib.md5(str(random.getrandbits(128)).encode()).hexdigest()[:8]
186
  return st.session_state.user_hash
187
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  async def save_pasted_image(image_data):
189
  """๐Ÿ“ธ - Snap Cap Trap - Saves your pic, no flap!"""
190
  action = "๐Ÿ“ธ - Snap Cap Trap - Saves your pic, no flap!"
191
  username = st.session_state.get('username', 'System ๐ŸŒŸ')
192
  await asyncio.to_thread(log_action, username, action)
193
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
194
  filename = f"paste_{timestamp}.png"
195
  filepath = os.path.join('./', filename)
196
  if ',' in image_data:
@@ -205,7 +248,7 @@ async def get_video_html(video_path, width="100%"):
205
  action = "๐ŸŽฅ - Reel Deal Steal - Plays your flick, so real!"
206
  username = st.session_state.get('username', 'System ๐ŸŒŸ')
207
  await asyncio.to_thread(log_action, username, action)
208
- video_url = f"data:video/mp4;base64,{base64.b64encode(await asyncio.to_thread(open(video_path, 'rb').read)).decode()}"
209
  return f'<video width="{width}" controls autoplay muted loop><source src="{video_url}" type="video/mp4">Your browser does not support the video tag.</video>'
210
 
211
  async def get_audio_html(audio_path, width="100%"):
@@ -213,7 +256,7 @@ async def get_audio_html(audio_path, width="100%"):
213
  action = "๐ŸŽถ - Tune Moon Boom - Drops a beat, so groom!"
214
  username = st.session_state.get('username', 'System ๐ŸŒŸ')
215
  await asyncio.to_thread(log_action, username, action)
216
- audio_url = f"data:audio/mpeg;base64,{base64.b64encode(await asyncio.to_thread(open(audio_path, 'rb').read)).decode()}"
217
  return f'<audio controls style="width: {width};"><source src="{audio_url}" type="audio/mpeg">Your browser does not support the audio element.</audio>'
218
 
219
  active_connections = {}
@@ -228,7 +271,7 @@ async def websocket_handler(websocket, path):
228
  room_id = "chat"
229
  active_connections.setdefault(room_id, {})[client_id] = websocket
230
  chat_content = await load_chat()
231
- username = st.session_state.get('username', random.choice(FUN_USERNAMES))
232
  if not await has_joined_before(client_id, chat_content):
233
  await save_chat_entry(f"Client-{client_id}", f"{username} has joined {START_ROOM}!")
234
  async for message in websocket:
@@ -269,111 +312,90 @@ async def run_websocket_server():
269
  server_running = True
270
  await server.wait_closed()
271
 
272
- def create_streamlit_interface():
 
 
 
 
 
 
 
 
 
 
 
273
  """๐ŸŽจ - UI Brew Crew - Builds the view, so new!"""
274
  action = "๐ŸŽจ - UI Brew Crew - Builds the view, so new!"
275
  username = st.session_state.get('username', 'System ๐ŸŒŸ')
276
- log_action(username, action) # Sync call here since we're in Streamlit context
277
-
278
- # Dynamic title based on username
279
- if 'username' in st.session_state:
280
- dynamic_title = f"๐Ÿค–๐Ÿง MMO {st.session_state.username}๐Ÿ“๐Ÿ”ฌ"
281
- else:
282
- dynamic_title = "๐Ÿค–๐Ÿง MMO Chat Brain๐Ÿ“๐Ÿ”ฌ"
283
-
284
- # Base CSS
285
- st.markdown("""
286
- <style>
287
- .chat-box {font-family: monospace; background: #1e1e1e; color: #d4d4d4; padding: 10px; border-radius: 5px; height: 300px; overflow-y: auto;}
288
- .timer {font-size: 24px; color: #ffcc00; text-align: center; animation: pulse 1s infinite;}
289
- @keyframes pulse {0% {transform: scale(1);} 50% {transform: scale(1.1);} 100% {transform: scale(1);}}
290
- #paste-target {border: 2px dashed #ccc; padding: 20px; text-align: center; cursor: pointer;}
291
- </style>
292
- """, unsafe_allow_html=True)
293
-
294
- st.title(dynamic_title)
295
- st.markdown(f"Welcome to {START_ROOM} - chat, vote, upload, paste images, and enjoy quoting! ๐ŸŽ‰")
296
 
297
- # Initialize client ID and username
298
- if 'client_id' not in st.session_state:
299
- st.session_state.client_id = str(uuid.uuid4())
300
  if 'username' not in st.session_state:
301
- chat_content = asyncio.run(load_chat()) # Run async function synchronously
302
  available_names = [name for name in FUN_USERNAMES if not any(f"{name} has joined" in line for line in chat_content.split('\n'))]
303
- st.session_state.username = random.choice(available_names) if available_names else random.choice(FUN_USERNAMES)
304
 
305
- # Session state initialization
306
  if 'refresh_rate' not in st.session_state:
307
  st.session_state.refresh_rate = 5
308
  if 'timer_start' not in st.session_state:
309
  st.session_state.timer_start = time.time()
310
- if 'quote_index' not in st.session_state:
311
- quotes = asyncio.run(load_quotes("famous"))
312
- st.session_state.quote_index = random.randint(0, max(0, len(quotes) - 1)) if quotes else 0
313
- if 'quote_source' not in st.session_state:
314
- st.session_state.quote_source = "famous"
315
  if 'pasted_image_data' not in st.session_state:
316
  st.session_state.pasted_image_data = None
317
  if 'message_text' not in st.session_state:
318
  st.session_state.message_text = ""
 
 
 
 
319
 
320
- # JavaScript component for image paste
321
- paste_component = components.html(
322
- """
323
- <div id="paste-target">Paste an image here (Ctrl+V)</div>
324
- <script>
325
- const pasteTarget = document.getElementById('paste-target');
326
- pasteTarget.addEventListener('paste', (event) => {
327
- const items = (event.clipboardData || window.clipboardData).items;
328
- for (let i = 0; i < items.length; i++) {
329
- if (items[i].type.indexOf('image') !== -1) {
330
- const blob = items[i].getAsFile();
331
- const reader = new FileReader();
332
- reader.onload = (e) => {
333
- window.parent.postMessage({
334
- type: 'streamlit:setComponentValue',
335
- value: e.target.result
336
- }, '*');
337
- pasteTarget.innerHTML = '<p>Image pasted! Processing...</p>';
338
- };
339
- reader.readAsDataURL(blob);
340
- }
341
- }
342
- event.preventDefault();
343
- });
344
- </script>
345
- """,
346
- height=100
347
- )
348
 
349
- # Handle pasted image data
350
- pasted_image = st.session_state.get('pasted_image_data')
351
- if pasted_image:
352
- filename = asyncio.run(save_pasted_image(pasted_image))
353
- if filename:
354
- asyncio.run(save_chat_entry(st.session_state.username, f"Pasted image: {filename}"))
355
- st.session_state.pasted_image_data = None
356
- st.rerun()
357
 
358
- # Chat section with quoting
 
 
 
 
 
 
359
  st.subheader(f"{START_ROOM} Chat ๐Ÿ’ฌ")
360
- chat_content = asyncio.run(load_chat())
361
  chat_lines = chat_content.split('\n')
362
- chat_votes = asyncio.run(load_votes(QUOTE_VOTES_FILE))
363
  for i, line in enumerate(chat_lines):
364
  if line.strip() and ': ' in line:
365
  col1, col2, col3 = st.columns([4, 1, 1])
366
  with col1:
367
  st.markdown(line)
 
 
 
 
 
 
 
 
 
 
368
  with col2:
369
  vote_count = chat_votes.get(line.split('. ')[1] if '. ' in line else line, 0)
370
  if st.button(f"๐Ÿ‘ {vote_count}", key=f"chat_vote_{i}"):
371
  comment = st.session_state.message_text
372
- asyncio.run(save_vote(QUOTE_VOTES_FILE, line.split('. ')[1] if '. ' in line else line, asyncio.run(generate_user_hash()), st.session_state.username, comment))
373
  if st.session_state.pasted_image_data:
374
- filename = asyncio.run(save_pasted_image(st.session_state.pasted_image_data))
375
  if filename:
376
- asyncio.run(save_chat_entry(st.session_state.username, f"Pasted image: {filename}"))
377
  st.session_state.pasted_image_data = None
378
  st.session_state.message_text = ''
379
  st.rerun()
@@ -382,58 +404,87 @@ def create_streamlit_interface():
382
  st.session_state.quote_line = line
383
  st.rerun()
384
 
385
- # Quoting section
386
  if 'quote_line' in st.session_state:
387
  st.markdown(f"### Quoting: {st.session_state.quote_line}")
388
  quote_response = st.text_area("Add your response", key="quote_response")
389
  if st.button("Send Quote ๐Ÿš€", key="send_quote"):
390
- def process_quote():
 
391
  action = "๐Ÿ“ข - Quote Float Boat - Echoes chat, we gloat!"
392
- log_action(st.session_state.username, action)
393
  markdown_response = f"### Quote Response\n- **Original**: {st.session_state.quote_line}\n- **{st.session_state.username} Replies**: {quote_response}"
394
  if st.session_state.pasted_image_data:
395
- filename = asyncio.run(save_pasted_image(st.session_state.pasted_image_data))
396
  if filename:
397
  markdown_response += f"\n- **Image**: ![Pasted Image]({filename})"
398
  st.session_state.pasted_image_data = None
399
- asyncio.run(save_chat_entry(st.session_state.username, markdown_response))
400
- process_quote()
401
  del st.session_state.quote_line
402
  st.session_state.message_text = ''
403
  st.rerun()
404
 
405
- # Username change dropdown
406
- new_username = st.selectbox("Change Name", [""] + FUN_USERNAMES, index=0)
407
  if new_username and new_username != st.session_state.username:
408
- asyncio.run(save_chat_entry("System ๐ŸŒŸ", f"{st.session_state.username} changed name to {new_username}"))
409
  st.session_state.username = new_username
410
  st.rerun()
411
 
412
- # Message input
413
  message = st.text_input(f"Message as {st.session_state.username}", key="message_input", value=st.session_state.message_text, on_change=lambda: st.session_state.update(message_text=st.session_state.message_input))
414
  if st.button("Send ๐Ÿš€", key="send_button") and message.strip():
415
- asyncio.run(save_chat_entry(st.session_state.username, message))
416
  if st.session_state.pasted_image_data:
417
- filename = asyncio.run(save_pasted_image(st.session_state.pasted_image_data))
418
  if filename:
419
- asyncio.run(save_chat_entry(st.session_state.username, f"Pasted image: {filename}"))
420
  st.session_state.pasted_image_data = None
421
  st.session_state.message_text = ''
422
  st.rerun()
423
 
424
- # Media section with upload and delete
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
425
  st.subheader("Media Gallery ๐ŸŽจ๐ŸŽถ๐ŸŽฅ")
426
  uploaded_file = st.file_uploader("Upload Media", type=['png', 'jpg', 'mp3', 'mp4'])
427
  if uploaded_file:
428
  file_path = os.path.join('./', uploaded_file.name)
429
- with open(file_path, 'wb') as f:
430
- f.write(uploaded_file.getbuffer())
431
  st.success(f"Uploaded {uploaded_file.name}")
432
 
433
  media_files = glob.glob("./*.png") + glob.glob("./*.jpg") + glob.glob("./*.mp3") + glob.glob("./*.mp4")
434
  if media_files:
435
  cols = st.columns(3)
436
- media_votes = asyncio.run(load_votes(MEDIA_VOTES_FILE))
437
  for idx, media_file in enumerate(media_files):
438
  vote_count = media_votes.get(media_file, 0)
439
  if vote_count > 0:
@@ -441,27 +492,27 @@ def create_streamlit_interface():
441
  if media_file.endswith(('.png', '.jpg')):
442
  st.image(media_file, use_container_width=True)
443
  elif media_file.endswith('.mp3'):
444
- st.markdown(asyncio.run(get_audio_html(media_file)), unsafe_allow_html=True)
445
  elif media_file.endswith('.mp4'):
446
- st.markdown(asyncio.run(get_video_html(media_file)), unsafe_allow_html=True)
447
  col1, col2 = st.columns(2)
448
  with col1:
449
  if st.button(f"๐Ÿ‘ {vote_count}", key=f"media_vote_{idx}"):
450
  comment = st.session_state.message_text
451
- asyncio.run(save_vote(MEDIA_VOTES_FILE, media_file, asyncio.run(generate_user_hash()), st.session_state.username, comment))
452
  if st.session_state.pasted_image_data:
453
- filename = asyncio.run(save_pasted_image(st.session_state.pasted_image_data))
454
  if filename:
455
- asyncio.run(save_chat_entry(st.session_state.username, f"Pasted image: {filename}"))
456
  st.session_state.pasted_image_data = None
457
  st.session_state.message_text = ''
458
  st.rerun()
459
  with col2:
460
  if st.button("๐Ÿ—‘๏ธ", key=f"media_delete_{idx}"):
461
- os.remove(media_file)
462
  st.rerun()
463
 
464
- # Refresh timer
465
  st.subheader("Refresh โณ")
466
  refresh_rate = st.slider("Refresh Rate", 1, 300, st.session_state.refresh_rate)
467
  st.session_state.refresh_rate = refresh_rate
@@ -470,16 +521,14 @@ def create_streamlit_interface():
470
  font_name, font_func = random.choice(UNICODE_FONTS)
471
  countdown_str = "".join(UNICODE_DIGITS[int(d)] for d in str(i)) if i < 10 else font_func(str(i))
472
  timer_placeholder.markdown(f"<p class='timer'>โณ {font_func('Refresh in:')} {countdown_str}</p>", unsafe_allow_html=True)
473
- time.sleep(1) # Sync sleep here since it's UI-driven
474
  st.rerun()
475
 
476
- # Sidebar vote stats
477
- st.sidebar.subheader("Vote Counts")
478
- chat_votes = asyncio.run(load_votes(QUOTE_VOTES_FILE))
479
- media_votes = asyncio.run(load_votes(MEDIA_VOTES_FILE))
480
- for item, count in {**chat_votes, **media_votes}.items():
481
- if count > 0:
482
- st.sidebar.write(f"{item}: {count} votes")
483
 
484
  async def main():
485
  """๐ŸŽฎ - Game Fame Claim - Starts the fun, no shame!"""
@@ -487,10 +536,10 @@ async def main():
487
  username = st.session_state.get('username', 'System ๐ŸŒŸ')
488
  await asyncio.to_thread(log_action, username, action)
489
  global NODE_NAME, server_task
490
- NODE_NAME, port = get_node_name() # Sync call since it's CLI parsing
491
  if server_task is None:
492
  server_task = asyncio.create_task(run_websocket_server())
493
- create_streamlit_interface() # Call synchronously
494
 
495
  if __name__ == "__main__":
496
  asyncio.run(main())
 
14
  import base64
15
  import io
16
  import streamlit.components.v1 as components
17
+ import edge_tts
18
+ from audio_recorder_streamlit import audio_recorder
19
+ import nest_asyncio
20
+
21
+ # Apply patch to allow nested asyncio.run() calls
22
+ nest_asyncio.apply()
23
 
24
  # Initial App Configuration (static)
25
  icons = '๐Ÿค–๐Ÿง ๐Ÿ”ฌ๐Ÿ“'
26
  START_ROOM = "Sector ๐ŸŒŒ"
27
 
28
  # Set page config once at the top
29
+ st.set_page_config conjectural_set_page_config(
30
  page_title="๐Ÿค–๐Ÿง MMO Chat Brain๐Ÿ“๐Ÿ”ฌ", # Initial static title
31
  page_icon=icons,
32
  layout="wide",
33
  initial_sidebar_state="auto"
34
  )
35
 
36
+ # Fun usernames with emojis and their paired voices
37
+ FUN_USERNAMES = {
38
+ "CosmicJester ๐ŸŒŒ": "en-US-AriaNeural",
39
+ "PixelPanda ๐Ÿผ": "en-US-JennyNeural",
40
+ "QuantumQuack ๐Ÿฆ†": "en-GB-SoniaNeural",
41
+ "StellarSquirrel ๐Ÿฟ๏ธ": "en-AU-NatashaNeural",
42
+ "GizmoGuru โš™๏ธ": "en-CA-ClaraNeural",
43
+ "NebulaNinja ๐ŸŒ ": "en-US-GuyNeural",
44
+ "ByteBuster ๐Ÿ’พ": "en-GB-RyanNeural",
45
+ "GalacticGopher ๐ŸŒ": "en-AU-WilliamNeural",
46
+ "RocketRaccoon ๐Ÿš€": "en-CA-LiamNeural",
47
+ "EchoElf ๐Ÿง": "en-US-AriaNeural",
48
+ "PhantomFox ๐ŸฆŠ": "en-US-JennyNeural",
49
+ "WittyWizard ๐Ÿง™": "en-GB-SoniaNeural",
50
+ "LunarLlama ๐ŸŒ™": "en-AU-NatashaNeural",
51
+ "SolarSloth โ˜€๏ธ": "en-CA-ClaraNeural",
52
+ "AstroAlpaca ๐Ÿฆ™": "en-US-GuyNeural",
53
+ "CyberCoyote ๐Ÿบ": "en-GB-RyanNeural",
54
+ "MysticMoose ๐ŸฆŒ": "en-AU-WilliamNeural",
55
+ "GlitchGnome ๐Ÿงš": "en-CA-LiamNeural",
56
+ "VortexViper ๐Ÿ": "en-US-AriaNeural",
57
+ "ChronoChimp ๐Ÿ’": "en-US-JennyNeural"
58
+ }
59
 
60
  # Directories and files
61
  CHAT_DIR = "chat_logs"
62
  VOTE_DIR = "vote_logs"
63
  STATE_FILE = "user_state.txt"
64
+ AUDIO_DIR = "audio_logs"
65
+ HISTORY_DIR = "history_logs"
66
  os.makedirs(CHAT_DIR, exist_ok=True)
67
  os.makedirs(VOTE_DIR, exist_ok=True)
68
+ os.makedirs(AUDIO_DIR, exist_ok=True)
69
+ os.makedirs(HISTORY_DIR, exist_ok=True)
70
 
71
  CHAT_FILE = os.path.join(CHAT_DIR, "global_chat.md")
72
  QUOTE_VOTES_FILE = os.path.join(VOTE_DIR, "quote_votes.md")
73
  MEDIA_VOTES_FILE = os.path.join(VOTE_DIR, "media_votes.md")
74
+ HISTORY_FILE = os.path.join(HISTORY_DIR, "chat_history.md")
75
 
76
  # Unicode digits and fonts
77
  UNICODE_DIGITS = {i: f"{i}\uFE0Fโƒฃ" for i in range(10)}
 
83
  server_running = False
84
  server_task = None
85
 
86
+ # Helper Functions
87
+ def format_timestamp_prefix():
88
+ """๐Ÿ“… - Time Stamp Champ - Marks the clock, no flop!"""
89
+ return datetime.now().strftime("%Y%m%d_%H%M%S")
90
+
91
  def get_node_name():
92
  """๐ŸŒ - Naming Node with Code - Spins a name, oh so bold!"""
93
  action = "๐ŸŒ - Naming Node with Code - Spins a name, oh so bold!"
 
100
  return args.node_name or f"node-{uuid.uuid4().hex[:8]}", args.port
101
 
102
  def log_action(username, action):
103
+ """๐Ÿ“œ - Log Jog Blog - Tracks the deed, no greed!"""
104
  if 'action_log' not in st.session_state:
105
  st.session_state.action_log = {}
106
  user_log = st.session_state.action_log.setdefault(username, {})
 
108
  user_log = {k: v for k, v in user_log.items() if current_time - v < 10}
109
  st.session_state.action_log[username] = user_log
110
  if action not in user_log:
111
+ with open(HISTORY_FILE, 'a') as f:
112
  f.write(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {username}: {action}\n")
113
  user_log[action] = current_time
114
 
 
119
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
120
  entry = f"[{timestamp}] {username}: {message}"
121
  await asyncio.to_thread(lambda: open(CHAT_FILE, 'a').write(f"{entry}\n"))
122
+ # Generate and save audio for the message
123
+ voice = FUN_USERNAMES.get(username, "en-US-AriaNeural")
124
+ audio_file = await async_edge_tts_generate(message, voice)
125
+ if audio_file:
126
+ with open(HISTORY_FILE, 'a') as f:
127
+ f.write(f"[{timestamp}] {username}: Audio generated - {audio_file}\n")
128
 
129
  async def load_chat():
130
  """๐Ÿ“– - Chat Fetch Quest - Grabs the log, no jest!"""
 
137
  content = await asyncio.to_thread(f.read)
138
  return content
139
 
140
+ async def get_user_list(chat_content):
141
  """๐Ÿ‘ฅ - Crew Clue Brew - Spots whoโ€™s who in the crew!"""
142
  action = "๐Ÿ‘ฅ - Crew Clue Brew - Spots whoโ€™s who in the crew!"
143
  username = st.session_state.get('username', 'System ๐ŸŒŸ')
144
+ await asyncio.to_thread(log_action, username, action)
145
  users = set()
146
  for line in chat_content.split('\n'):
147
  if line.strip() and ': ' in line:
 
165
  messages = [line.split(': ', 1)[1] for line in lines if ': ' in line and line.strip()]
166
  return [msg for msg in messages if msg.lower().startswith(prefix.lower())][:5]
167
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  async def save_vote(file, item, user_hash, username, comment=""):
169
  """๐Ÿ‘ - Vote Note Float - Cheers rise, we gloat!"""
170
  action = "๐Ÿ‘ - Vote Note Float - Cheers rise, we gloat!"
 
208
  st.session_state.user_hash = hashlib.md5(str(random.getrandbits(128)).encode()).hexdigest()[:8]
209
  return st.session_state.user_hash
210
 
211
+ async def async_edge_tts_generate(text, voice, rate=0, pitch=0, file_format="mp3"):
212
+ """๐ŸŽถ - Tune Moon Boom - Drops a beat, so groom!"""
213
+ action = "๐ŸŽถ - Tune Moon Boom - Drops a beat, so groom!"
214
+ username = st.session_state.get('username', 'System ๐ŸŒŸ')
215
+ await asyncio.to_thread(log_action, username, action)
216
+ timestamp = format_timestamp_prefix()
217
+ filename = os.path.join(AUDIO_DIR, f"audio_{timestamp}_{random.randint(1000, 9999)}.mp3")
218
+ communicate = edge_tts.Communicate(text, voice, rate=f"{rate:+d}%", pitch=f"{pitch:+d}Hz")
219
+ await communicate.save(filename)
220
+ return filename if os.path.exists(filename) else None
221
+
222
+ def play_and_download_audio(file_path):
223
+ """๐Ÿ”Š - Sound Pound Ground - Plays it loud, all around!"""
224
+ if file_path and os.path.exists(file_path):
225
+ st.audio(file_path)
226
+ with open(file_path, "rb") as f:
227
+ b64 = base64.b64encode(f.read()).decode()
228
+ dl_link = f'<a href="data:audio/mpeg;base64,{b64}" download="{os.path.basename(file_path)}">๐ŸŽต Download {os.path.basename(file_path)}</a>'
229
+ st.markdown(dl_link, unsafe_allow_html=True)
230
+
231
  async def save_pasted_image(image_data):
232
  """๐Ÿ“ธ - Snap Cap Trap - Saves your pic, no flap!"""
233
  action = "๐Ÿ“ธ - Snap Cap Trap - Saves your pic, no flap!"
234
  username = st.session_state.get('username', 'System ๐ŸŒŸ')
235
  await asyncio.to_thread(log_action, username, action)
236
+ timestamp = format_timestamp_prefix()
237
  filename = f"paste_{timestamp}.png"
238
  filepath = os.path.join('./', filename)
239
  if ',' in image_data:
 
248
  action = "๐ŸŽฅ - Reel Deal Steal - Plays your flick, so real!"
249
  username = st.session_state.get('username', 'System ๐ŸŒŸ')
250
  await asyncio.to_thread(log_action, username, action)
251
+ video_url = f"data:video/mp4;base64,{base64.b64encode(await asyncio.to_thread(open, video_path, 'rb').read()).decode()}"
252
  return f'<video width="{width}" controls autoplay muted loop><source src="{video_url}" type="video/mp4">Your browser does not support the video tag.</video>'
253
 
254
  async def get_audio_html(audio_path, width="100%"):
 
256
  action = "๐ŸŽถ - Tune Moon Boom - Drops a beat, so groom!"
257
  username = st.session_state.get('username', 'System ๐ŸŒŸ')
258
  await asyncio.to_thread(log_action, username, action)
259
+ audio_url = f"data:audio/mpeg;base64,{base64.b64encode(await asyncio.to_thread(open, audio_path, 'rb').read()).decode()}"
260
  return f'<audio controls style="width: {width};"><source src="{audio_url}" type="audio/mpeg">Your browser does not support the audio element.</audio>'
261
 
262
  active_connections = {}
 
271
  room_id = "chat"
272
  active_connections.setdefault(room_id, {})[client_id] = websocket
273
  chat_content = await load_chat()
274
+ username = st.session_state.get('username', random.choice(list(FUN_USERNAMES.keys())))
275
  if not await has_joined_before(client_id, chat_content):
276
  await save_chat_entry(f"Client-{client_id}", f"{username} has joined {START_ROOM}!")
277
  async for message in websocket:
 
312
  server_running = True
313
  await server.wait_closed()
314
 
315
+ async def process_voice_input(audio_bytes):
316
+ """๐ŸŽค - Voice Choice Rejoice - Speaks your mind, oh boy!"""
317
+ action = "๐ŸŽค - Voice Choice Rejoice - Speaks your mind, oh boy!"
318
+ username = st.session_state.get('username', 'System ๐ŸŒŸ')
319
+ await asyncio.to_thread(log_action, username, action)
320
+ if audio_bytes:
321
+ # Simple placeholder for speech-to-text (requires additional library like `speech_recognition`)
322
+ # For now, we'll simulate text input from audio
323
+ text = "Voice input simulation" # Replace with actual speech-to-text logic
324
+ await save_chat_entry(username, text)
325
+
326
+ async def create_streamlit_interface():
327
  """๐ŸŽจ - UI Brew Crew - Builds the view, so new!"""
328
  action = "๐ŸŽจ - UI Brew Crew - Builds the view, so new!"
329
  username = st.session_state.get('username', 'System ๐ŸŒŸ')
330
+ await asyncio.to_thread(log_action, username, action)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
 
 
 
 
332
  if 'username' not in st.session_state:
333
+ chat_content = await load_chat()
334
  available_names = [name for name in FUN_USERNAMES if not any(f"{name} has joined" in line for line in chat_content.split('\n'))]
335
+ st.session_state.username = random.choice(available_names) if available_names else random.choice(list(FUN_USERNAMES.keys()))
336
 
 
337
  if 'refresh_rate' not in st.session_state:
338
  st.session_state.refresh_rate = 5
339
  if 'timer_start' not in st.session_state:
340
  st.session_state.timer_start = time.time()
341
+ if 'quote_line' not in st.session_state:
342
+ st.session_state.quote_line = None
 
 
 
343
  if 'pasted_image_data' not in st.session_state:
344
  st.session_state.pasted_image_data = None
345
  if 'message_text' not in st.session_state:
346
  st.session_state.message_text = ""
347
+ if 'audio_cache' not in st.session_state:
348
+ st.session_state.audio_cache = {}
349
+ if 'chat_history' not in st.session_state:
350
+ st.session_state.chat_history = []
351
 
352
+ st.markdown("""
353
+ <style>
354
+ .chat-box {font-family: monospace; background: #1e1e1e; color: #d4d4d4; padding: 10px; border-radius: 5px; height: 300px; overflow-y: auto;}
355
+ .timer {font-size: 24px; color: #ffcc00; text-align: center; animation: pulse 1s infinite;}
356
+ @keyframes pulse {0% {transform: scale(1);} 50% {transform: scale(1.1);} 100% {transform: scale(1);}}
357
+ #paste-target {border: 2px dashed #ccc; padding: 20px; text-align: center; cursor: pointer;}
358
+ </style>
359
+ """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
360
 
361
+ st.title(f"๐Ÿค–๐Ÿง MMO {st.session_state.username}๐Ÿ“๐Ÿ”ฌ")
362
+ st.markdown(f"Welcome to {START_ROOM} - chat, vote, upload, paste images, and enjoy quoting! ๐ŸŽ‰")
 
 
 
 
 
 
363
 
364
+ # Voice Input
365
+ audio_bytes = audio_recorder()
366
+ if audio_bytes:
367
+ await process_voice_input(audio_bytes)
368
+ st.rerun()
369
+
370
+ # Chat Section
371
  st.subheader(f"{START_ROOM} Chat ๐Ÿ’ฌ")
372
+ chat_content = await load_chat()
373
  chat_lines = chat_content.split('\n')
374
+ chat_votes = await load_votes(QUOTE_VOTES_FILE)
375
  for i, line in enumerate(chat_lines):
376
  if line.strip() and ': ' in line:
377
  col1, col2, col3 = st.columns([4, 1, 1])
378
  with col1:
379
  st.markdown(line)
380
+ username = line.split(': ')[1].split(' ')[0]
381
+ audio_file = None
382
+ cache_key = f"{line}_{FUN_USERNAMES.get(username, 'en-US-AriaNeural')}"
383
+ if cache_key in st.session_state.audio_cache:
384
+ audio_file = st.session_state.audio_cache[cache_key]
385
+ else:
386
+ audio_file = await async_edge_tts_generate(line.split(': ', 1)[1], FUN_USERNAMES.get(username, "en-US-AriaNeural"))
387
+ st.session_state.audio_cache[cache_key] = audio_file
388
+ if audio_file:
389
+ play_and_download_audio(audio_file)
390
  with col2:
391
  vote_count = chat_votes.get(line.split('. ')[1] if '. ' in line else line, 0)
392
  if st.button(f"๐Ÿ‘ {vote_count}", key=f"chat_vote_{i}"):
393
  comment = st.session_state.message_text
394
+ await save_vote(QUOTE_VOTES_FILE, line.split('. ')[1] if '. ' in line else line, await generate_user_hash(), st.session_state.username, comment)
395
  if st.session_state.pasted_image_data:
396
+ filename = await save_pasted_image(st.session_state.pasted_image_data)
397
  if filename:
398
+ await save_chat_entry(st.session_state.username, f"Pasted image: {filename}")
399
  st.session_state.pasted_image_data = None
400
  st.session_state.message_text = ''
401
  st.rerun()
 
404
  st.session_state.quote_line = line
405
  st.rerun()
406
 
407
+ # Quoting Section
408
  if 'quote_line' in st.session_state:
409
  st.markdown(f"### Quoting: {st.session_state.quote_line}")
410
  quote_response = st.text_area("Add your response", key="quote_response")
411
  if st.button("Send Quote ๐Ÿš€", key="send_quote"):
412
+ async def process_quote():
413
+ """๐Ÿ“ข - Quote Float Boat - Echoes chat, we gloat!"""
414
  action = "๐Ÿ“ข - Quote Float Boat - Echoes chat, we gloat!"
415
+ await asyncio.to_thread(log_action, st.session_state.username, action)
416
  markdown_response = f"### Quote Response\n- **Original**: {st.session_state.quote_line}\n- **{st.session_state.username} Replies**: {quote_response}"
417
  if st.session_state.pasted_image_data:
418
+ filename = await save_pasted_image(st.session_state.pasted_image_data)
419
  if filename:
420
  markdown_response += f"\n- **Image**: ![Pasted Image]({filename})"
421
  st.session_state.pasted_image_data = None
422
+ await save_chat_entry(st.session_state.username, markdown_response)
423
+ await process_quote()
424
  del st.session_state.quote_line
425
  st.session_state.message_text = ''
426
  st.rerun()
427
 
428
+ # Username Change Dropdown
429
+ new_username = st.selectbox("Change Name", [""] + list(FUN_USERNAMES.keys()), index=0)
430
  if new_username and new_username != st.session_state.username:
431
+ await save_chat_entry("System ๐ŸŒŸ", f"{st.session_state.username} changed name to {new_username}")
432
  st.session_state.username = new_username
433
  st.rerun()
434
 
435
+ # Message Input
436
  message = st.text_input(f"Message as {st.session_state.username}", key="message_input", value=st.session_state.message_text, on_change=lambda: st.session_state.update(message_text=st.session_state.message_input))
437
  if st.button("Send ๐Ÿš€", key="send_button") and message.strip():
438
+ await save_chat_entry(st.session_state.username, message)
439
  if st.session_state.pasted_image_data:
440
+ filename = await save_pasted_image(st.session_state.pasted_image_data)
441
  if filename:
442
+ await save_chat_entry(st.session_state.username, f"Pasted image: {filename}")
443
  st.session_state.pasted_image_data = None
444
  st.session_state.message_text = ''
445
  st.rerun()
446
 
447
+ # Paste Target
448
+ paste_component = components.html(
449
+ """
450
+ <div id="paste-target">Paste an image here (Ctrl+V)</div>
451
+ <script>
452
+ const pasteTarget = document.getElementById('paste-target');
453
+ pasteTarget.addEventListener('paste', (event) => {
454
+ const items = (event.clipboardData || window.clipboardData).items;
455
+ for (let i = 0; i < items.length; i++) {
456
+ if (items[i].type.indexOf('image') !== -1) {
457
+ const blob = items[i].getAsFile();
458
+ const reader = new FileReader();
459
+ reader.onload = (e) => {
460
+ window.parent.postMessage({
461
+ type: 'streamlit:setComponentValue',
462
+ value: e.target.result
463
+ }, '*');
464
+ pasteTarget.innerHTML = '<p>Image pasted! Processing...</p>';
465
+ };
466
+ reader.readAsDataURL(blob);
467
+ }
468
+ }
469
+ event.preventDefault();
470
+ });
471
+ </script>
472
+ """,
473
+ height=100
474
+ )
475
+
476
+ # Media Section
477
  st.subheader("Media Gallery ๐ŸŽจ๐ŸŽถ๐ŸŽฅ")
478
  uploaded_file = st.file_uploader("Upload Media", type=['png', 'jpg', 'mp3', 'mp4'])
479
  if uploaded_file:
480
  file_path = os.path.join('./', uploaded_file.name)
481
+ await asyncio.to_thread(lambda: open(file_path, 'wb').write(uploaded_file.getbuffer()))
 
482
  st.success(f"Uploaded {uploaded_file.name}")
483
 
484
  media_files = glob.glob("./*.png") + glob.glob("./*.jpg") + glob.glob("./*.mp3") + glob.glob("./*.mp4")
485
  if media_files:
486
  cols = st.columns(3)
487
+ media_votes = await load_votes(MEDIA_VOTES_FILE)
488
  for idx, media_file in enumerate(media_files):
489
  vote_count = media_votes.get(media_file, 0)
490
  if vote_count > 0:
 
492
  if media_file.endswith(('.png', '.jpg')):
493
  st.image(media_file, use_container_width=True)
494
  elif media_file.endswith('.mp3'):
495
+ st.markdown(await get_audio_html(media_file), unsafe_allow_html=True)
496
  elif media_file.endswith('.mp4'):
497
+ st.markdown(await get_video_html(media_file), unsafe_allow_html=True)
498
  col1, col2 = st.columns(2)
499
  with col1:
500
  if st.button(f"๐Ÿ‘ {vote_count}", key=f"media_vote_{idx}"):
501
  comment = st.session_state.message_text
502
+ await save_vote(MEDIA_VOTES_FILE, media_file, await generate_user_hash(), st.session_state.username, comment)
503
  if st.session_state.pasted_image_data:
504
+ filename = await save_pasted_image(st.session_state.pasted_image_data)
505
  if filename:
506
+ await save_chat_entry(st.session_state.username, f"Pasted image: {filename}")
507
  st.session_state.pasted_image_data = None
508
  st.session_state.message_text = ''
509
  st.rerun()
510
  with col2:
511
  if st.button("๐Ÿ—‘๏ธ", key=f"media_delete_{idx}"):
512
+ await asyncio.to_thread(os.remove, media_file)
513
  st.rerun()
514
 
515
+ # Refresh Timer
516
  st.subheader("Refresh โณ")
517
  refresh_rate = st.slider("Refresh Rate", 1, 300, st.session_state.refresh_rate)
518
  st.session_state.refresh_rate = refresh_rate
 
521
  font_name, font_func = random.choice(UNICODE_FONTS)
522
  countdown_str = "".join(UNICODE_DIGITS[int(d)] for d in str(i)) if i < 10 else font_func(str(i))
523
  timer_placeholder.markdown(f"<p class='timer'>โณ {font_func('Refresh in:')} {countdown_str}</p>", unsafe_allow_html=True)
524
+ await asyncio.sleep(1)
525
  st.rerun()
526
 
527
+ # Sidebar History
528
+ st.sidebar.subheader("Chat History ๐Ÿ“œ")
529
+ with open(HISTORY_FILE, 'r') as f:
530
+ history_content = f.read()
531
+ st.sidebar.markdown(history_content)
 
 
532
 
533
  async def main():
534
  """๐ŸŽฎ - Game Fame Claim - Starts the fun, no shame!"""
 
536
  username = st.session_state.get('username', 'System ๐ŸŒŸ')
537
  await asyncio.to_thread(log_action, username, action)
538
  global NODE_NAME, server_task
539
+ NODE_NAME, port = await get_node_name()
540
  if server_task is None:
541
  server_task = asyncio.create_task(run_websocket_server())
542
+ await create_streamlit_interface()
543
 
544
  if __name__ == "__main__":
545
  asyncio.run(main())