awacke1 commited on
Commit
00dbb5f
Β·
verified Β·
1 Parent(s): 02fa697

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +394 -0
app.py ADDED
@@ -0,0 +1,394 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import asyncio
3
+ import websockets
4
+ import uuid
5
+ import argparse
6
+ from datetime import datetime
7
+ import os
8
+ import random
9
+ import time
10
+ import hashlib
11
+ from PIL import Image
12
+ import glob
13
+ 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)}
53
+ UNICODE_FONTS = [
54
+ ("Normal", lambda x: x),
55
+ ("Bold", lambda x: "".join(chr(ord(c) + 0x1D400 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D41A - 0x61) if 'a' <= c <= 'z' else c for c in x)),
56
+ # ... (other font styles remain the same)
57
+ ]
58
+
59
+ server_running = False
60
+ server_task = None
61
+
62
+ def get_node_name():
63
+ parser = argparse.ArgumentParser(description='Start a chat node with a specific name')
64
+ parser.add_argument('--node-name', type=str, default=None)
65
+ parser.add_argument('--port', type=int, default=8501)
66
+ args = parser.parse_args()
67
+ return args.node_name or f"node-{uuid.uuid4().hex[:8]}", args.port
68
+
69
+ def save_chat_entry(username, message):
70
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
71
+ entry = f"[{timestamp}] {username}: {message}"
72
+ with open(CHAT_FILE, 'a') as f:
73
+ f.write(f"{entry}\n")
74
+
75
+ def load_chat():
76
+ if not os.path.exists(CHAT_FILE):
77
+ with open(CHAT_FILE, 'w') as f:
78
+ f.write(f"# {START_ROOM} Chat\n\nWelcome to the cosmic hub - start chatting! 🎀\n")
79
+ with open(CHAT_FILE, 'r') as f:
80
+ content = f.read()
81
+ lines = content.strip().split('\n')
82
+ return "\n".join(f"{i+1}. {line}" for i, line in enumerate(lines) if line.strip())
83
+
84
+ def get_user_list(chat_content):
85
+ users = set()
86
+ for line in chat_content.split('\n'):
87
+ if line.strip() and ': ' in line:
88
+ user = line.split(': ')[1].split(' ')[0]
89
+ users.add(user)
90
+ return sorted(list(users))
91
+
92
+ def has_joined_before(client_id, chat_content):
93
+ return any(f"Client-{client_id} has joined" in line for line in chat_content.split('\n'))
94
+
95
+ def get_message_suggestions(chat_content, prefix):
96
+ lines = chat_content.split('\n')
97
+ messages = [line.split(': ', 1)[1] for line in lines if ': ' in line and line.strip()]
98
+ return [msg for msg in messages if msg.lower().startswith(prefix.lower())][:5]
99
+
100
+ def load_quotes(source="famous"):
101
+ famous_quotes = [
102
+ "The true sign of intelligence is not knowledge but imagination. – Albert Einstein",
103
+ # ... (other quotes remain the same)
104
+ ]
105
+ custom_quotes = [
106
+ "Every age unfolds a new lesson. Life's chapters evolve, each teaching us anew.",
107
+ # ... (other custom quotes remain the same)
108
+ ]
109
+ return famous_quotes if source == "famous" else custom_quotes
110
+
111
+ def save_vote(file, item, user_hash, username, comment=""):
112
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
113
+ entry = f"[{timestamp}] {user_hash} voted for {item}"
114
+ with open(file, 'a') as f:
115
+ f.write(f"{entry}\n")
116
+ with open(HISTORY_FILE, "a") as f:
117
+ f.write(f"- {timestamp} - User {user_hash} voted for {item}\n")
118
+ # Add a new chat entry for the upvote
119
+ chat_message = f"{username} upvoted: \"{item}\""
120
+ if comment:
121
+ chat_message += f" - {comment}"
122
+ save_chat_entry(username, chat_message)
123
+
124
+ def load_votes(file):
125
+ if not os.path.exists(file):
126
+ with open(file, 'w') as f:
127
+ f.write("# Vote Tally\n\nNo votes yet - get clicking! πŸ–±οΈ\n")
128
+ with open(file, 'r') as f:
129
+ lines = f.read().strip().split('\n')[2:] # Skip header
130
+ votes = {}
131
+ user_votes = set()
132
+ for line in lines:
133
+ if line.strip() and 'voted for' in line:
134
+ user_hash = line.split('] ')[1].split(' voted for ')[0]
135
+ item = line.split('voted for ')[1]
136
+ vote_key = f"{user_hash}-{item}"
137
+ if vote_key not in user_votes:
138
+ votes[item] = votes.get(item, 0) + 1
139
+ user_votes.add(vote_key)
140
+ return votes
141
+
142
+ def generate_user_hash():
143
+ if 'user_hash' not in st.session_state:
144
+ st.session_state.user_hash = hashlib.md5(str(random.getrandbits(128)).encode()).hexdigest()[:8]
145
+ return st.session_state.user_hash
146
+
147
+ def save_pasted_image(image_data):
148
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
149
+ filename = f"paste_{timestamp}.png"
150
+ filepath = os.path.join('./', filename)
151
+ if ',' in image_data:
152
+ image_data = image_data.split(',')[1]
153
+ img_bytes = base64.b64decode(image_data)
154
+ img = Image.open(io.BytesIO(img_bytes))
155
+ img.save(filepath, "PNG")
156
+ return filename
157
+
158
+ def get_video_html(video_path, width="100%"):
159
+ video_url = f"data:video/mp4;base64,{base64.b64encode(open(video_path, 'rb').read()).decode()}"
160
+ 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>'
161
+
162
+ def get_audio_html(audio_path, width="100%"):
163
+ audio_url = f"data:audio/mpeg;base64,{base64.b64encode(open(audio_path, 'rb').read()).decode()}"
164
+ return f'<audio controls style="width: {width};"><source src="{audio_url}" type="audio/mpeg">Your browser does not support the audio element.</audio>'
165
+
166
+ active_connections = {}
167
+
168
+ async def websocket_handler(websocket, path):
169
+ try:
170
+ client_id = str(uuid.uuid4())
171
+ room_id = "chat"
172
+ active_connections.setdefault(room_id, {})[client_id] = websocket
173
+ chat_content = load_chat()
174
+ username = st.session_state.get('username', random.choice(FUN_USERNAMES))
175
+ if not has_joined_before(client_id, chat_content):
176
+ save_chat_entry(f"Client-{client_id}", f"{username} has joined {START_ROOM}!")
177
+ async for message in websocket:
178
+ parts = message.split('|', 1)
179
+ if len(parts) == 2:
180
+ username, content = parts
181
+ save_chat_entry(username, content)
182
+ await broadcast_message(f"{username}|{content}", room_id)
183
+ except websockets.ConnectionClosed:
184
+ pass
185
+ finally:
186
+ if room_id in active_connections and client_id in active_connections[room_id]:
187
+ del active_connections[room_id][client_id]
188
+
189
+ async def broadcast_message(message, room_id):
190
+ if room_id in active_connections:
191
+ disconnected = []
192
+ for client_id, ws in active_connections[room_id].items():
193
+ try:
194
+ await ws.send(message)
195
+ except websockets.ConnectionClosed:
196
+ disconnected.append(client_id)
197
+ for client_id in disconnected:
198
+ del active_connections[room_id][client_id]
199
+
200
+ async def run_websocket_server():
201
+ global server_running, server_task
202
+ if not server_running:
203
+ server = await websockets.serve(websocket_handler, '0.0.0.0', 8765)
204
+ server_running = True
205
+ await server.wait_closed()
206
+
207
+ def create_streamlit_interface():
208
+ # Dynamic title based on username
209
+ if 'username' in st.session_state:
210
+ dynamic_title = f"πŸ€–πŸ§ MMO {st.session_state.username}πŸ“πŸ”¬"
211
+ else:
212
+ dynamic_title = "πŸ€–πŸ§ MMO Chat BrainπŸ“πŸ”¬"
213
+
214
+ # Base CSS
215
+ st.markdown("""
216
+ <style>
217
+ .chat-box {font-family: monospace; background: #1e1e1e; color: #d4d4d4; padding: 10px; border-radius: 5px; height: 300px; overflow-y: auto;}
218
+ .timer {font-size: 24px; color: #ffcc00; text-align: center; animation: pulse 1s infinite;}
219
+ @keyframes pulse {0% {transform: scale(1);} 50% {transform: scale(1.1);} 100% {transform: scale(1);}}
220
+ #paste-target {border: 2px dashed #ccc; padding: 20px; text-align: center; cursor: pointer;}
221
+ </style>
222
+ """, unsafe_allow_html=True)
223
+
224
+ st.title(dynamic_title)
225
+ st.markdown(f"Welcome to {START_ROOM} - chat, vote, upload, paste images, and enjoy! πŸŽ‰")
226
+
227
+ # Initialize client ID and username
228
+ if 'client_id' not in st.session_state:
229
+ st.session_state.client_id = str(uuid.uuid4())
230
+ if 'username' not in st.session_state:
231
+ available_names = [name for name in FUN_USERNAMES if not any(f"{name} has joined" in line for line in load_chat().split('\n'))]
232
+ st.session_state.username = random.choice(available_names) if available_names else random.choice(FUN_USERNAMES)
233
+
234
+ # Session state initialization
235
+ if 'refresh_rate' not in st.session_state:
236
+ st.session_state.refresh_rate = 5
237
+ if 'timer_start' not in st.session_state:
238
+ st.session_state.timer_start = time.time()
239
+ if 'quote_index' not in st.session_state:
240
+ quotes = load_quotes("famous")
241
+ st.session_state.quote_index = random.randint(0, max(0, len(quotes) - 1)) if quotes else 0
242
+ if 'quote_source' not in st.session_state:
243
+ st.session_state.quote_source = "famous"
244
+ if 'pasted_image_data' not in st.session_state:
245
+ st.session_state.pasted_image_data = None
246
+
247
+ # JavaScript component for image paste
248
+ paste_component = components.html(
249
+ """
250
+ <div id="paste-target">Paste an image here (Ctrl+V)</div>
251
+ <script>
252
+ const pasteTarget = document.getElementById('paste-target');
253
+ pasteTarget.addEventListener('paste', (event) => {
254
+ const items = (event.clipboardData || window.clipboardData).items;
255
+ for (let i = 0; i < items.length; i++) {
256
+ if (items[i].type.indexOf('image') !== -1) {
257
+ const blob = items[i].getAsFile();
258
+ const reader = new FileReader();
259
+ reader.onload = (e) => {
260
+ window.parent.postMessage({
261
+ type: 'streamlit:setComponentValue',
262
+ value: e.target.result
263
+ }, '*');
264
+ pasteTarget.innerHTML = '<p>Image pasted! Processing...</p>';
265
+ };
266
+ reader.readAsDataURL(blob);
267
+ }
268
+ }
269
+ event.preventDefault();
270
+ });
271
+ </script>
272
+ """,
273
+ height=100
274
+ )
275
+
276
+ # Handle pasted image data
277
+ pasted_image = st.session_state.get('pasted_image_data')
278
+ if pasted_image:
279
+ filename = save_pasted_image(pasted_image)
280
+ if filename:
281
+ save_chat_entry(st.session_state.username, f"Pasted image: {filename}")
282
+ st.session_state.pasted_image_data = None # Clear after processing
283
+ st.rerun()
284
+
285
+ # Chat section
286
+ st.subheader(f"{START_ROOM} Chat πŸ’¬")
287
+ chat_content = load_chat()
288
+ chat_votes = load_votes(QUOTE_VOTES_FILE)
289
+ for i, line in enumerate(chat_content.split('\n')):
290
+ if line.strip() and ': ' in line:
291
+ col1, col2 = st.columns([5, 1])
292
+ with col1:
293
+ st.markdown(line)
294
+ with col2:
295
+ vote_count = chat_votes.get(line.split('. ')[1] if '. ' in line else line, 0)
296
+ if st.button(f"πŸ‘ {vote_count}", key=f"chat_vote_{i}"):
297
+ comment = st.session_state.get('message_input', '')
298
+ save_vote(QUOTE_VOTES_FILE, line.split('. ')[1] if '. ' in line else line, generate_user_hash(), st.session_state.username, comment)
299
+ if pasted_image:
300
+ filename = save_pasted_image(pasted_image)
301
+ if filename:
302
+ save_chat_entry(st.session_state.username, f"Pasted image: {filename}")
303
+ st.session_state.pasted_image_data = None
304
+ st.session_state.message_input = '' # Clear input after vote
305
+ st.rerun()
306
+
307
+ # Username change dropdown
308
+ new_username = st.selectbox("Change Name", [""] + FUN_USERNAMES, index=0)
309
+ if new_username and new_username != st.session_state.username:
310
+ save_chat_entry("System 🌟", f"{st.session_state.username} changed name to {new_username}")
311
+ st.session_state.username = new_username
312
+ st.rerun()
313
+
314
+ # Message input
315
+ message = st.text_input(f"Message as {st.session_state.username}", key="message_input")
316
+ if st.button("Send πŸš€", key="send_button") and message.strip():
317
+ save_chat_entry(st.session_state.username, message)
318
+ st.session_state.message_input = '' # Clear input after send
319
+ if st.session_state.get('pasted_image_data'):
320
+ filename = save_pasted_image(st.session_state.pasted_image_data)
321
+ if filename:
322
+ save_chat_entry(st.session_state.username, f"Pasted image: {filename}")
323
+ st.session_state.pasted_image_data = None
324
+ st.rerun()
325
+
326
+ # Media section with upload and delete
327
+ st.subheader("Media Gallery 🎨🎢πŸŽ₯")
328
+ uploaded_file = st.file_uploader("Upload Media", type=['png', 'jpg', 'mp3', 'mp4'])
329
+ if uploaded_file:
330
+ file_path = os.path.join('./', uploaded_file.name)
331
+ with open(file_path, 'wb') as f:
332
+ f.write(uploaded_file.getbuffer())
333
+ st.success(f"Uploaded {uploaded_file.name}")
334
+
335
+ media_files = glob.glob("./*.png") + glob.glob("./*.jpg") + glob.glob("./*.mp3") + glob.glob("./*.mp4")
336
+ if media_files:
337
+ cols = st.columns(3)
338
+ media_votes = load_votes(MEDIA_VOTES_FILE)
339
+ for idx, media_file in enumerate(media_files):
340
+ vote_count = media_votes.get(media_file, 0)
341
+ if vote_count > 0: # Only show if upvoted
342
+ with cols[idx % 3]:
343
+ if media_file.endswith(('.png', '.jpg')):
344
+ st.image(media_file, use_container_width=True)
345
+ elif media_file.endswith('.mp3'):
346
+ st.markdown(get_audio_html(media_file), unsafe_allow_html=True)
347
+ elif media_file.endswith('.mp4'):
348
+ st.markdown(get_video_html(media_file), unsafe_allow_html=True)
349
+ col1, col2 = st.columns(2)
350
+ with col1:
351
+ if st.button(f"πŸ‘ {vote_count}", key=f"media_vote_{idx}"):
352
+ comment = st.session_state.get('message_input', '')
353
+ save_vote(MEDIA_VOTES_FILE, media_file, generate_user_hash(), st.session_state.username, comment)
354
+ if st.session_state.get('pasted_image_data'):
355
+ filename = save_pasted_image(st.session_state.pasted_image_data)
356
+ if filename:
357
+ save_chat_entry(st.session_state.username, f"Pasted image: {filename}")
358
+ st.session_state.pasted_image_data = None
359
+ st.session_state.message_input = '' # Clear input after vote
360
+ st.rerun()
361
+ with col2:
362
+ if st.button("πŸ—‘οΈ", key=f"media_delete_{idx}"):
363
+ os.remove(media_file)
364
+ st.rerun()
365
+
366
+ # Refresh timer
367
+ st.subheader("Refresh ⏳")
368
+ refresh_rate = st.slider("Refresh Rate", 1, 300, st.session_state.refresh_rate)
369
+ st.session_state.refresh_rate = refresh_rate
370
+ timer_placeholder = st.empty()
371
+ for i in range(st.session_state.refresh_rate, -1, -1):
372
+ font_name, font_func = random.choice(UNICODE_FONTS)
373
+ countdown_str = "".join(UNICODE_DIGITS[int(d)] for d in str(i)) if i < 10 else font_func(str(i))
374
+ timer_placeholder.markdown(f"<p class='timer'>⏳ {font_func('Refresh in:')} {countdown_str}</p>", unsafe_allow_html=True)
375
+ time.sleep(1)
376
+ st.rerun()
377
+
378
+ # Sidebar vote stats
379
+ st.sidebar.subheader("Vote Counts")
380
+ chat_votes = load_votes(QUOTE_VOTES_FILE)
381
+ media_votes = load_votes(MEDIA_VOTES_FILE)
382
+ for item, count in {**chat_votes, **media_votes}.items():
383
+ if count > 0: # Only show items with upvotes
384
+ st.sidebar.write(f"{item}: {count} votes")
385
+
386
+ async def main():
387
+ global NODE_NAME, server_task
388
+ NODE_NAME, port = get_node_name()
389
+ if server_task is None:
390
+ server_task = asyncio.create_task(run_websocket_server())
391
+ create_streamlit_interface()
392
+
393
+ if __name__ == "__main__":
394
+ asyncio.run(main())