|
import streamlit as st |
|
import asyncio |
|
import websockets |
|
import uuid |
|
import argparse |
|
from datetime import datetime |
|
import os |
|
import random |
|
import time |
|
import hashlib |
|
|
|
|
|
FUN_USERNAMES = [ |
|
"CosmicJester π", "PixelPanda πΌ", "QuantumQuack π¦", "StellarSquirrel πΏοΈ", |
|
"GizmoGuru βοΈ", "NebulaNinja π ", "ByteBuster πΎ", "GalacticGopher π", |
|
"RocketRaccoon π", "EchoElf π§", "PhantomFox π¦", "WittyWizard π§", |
|
"LunarLlama π", "SolarSloth βοΈ", "AstroAlpaca π¦", "CyberCoyote πΊ", |
|
"MysticMoose π¦", "GlitchGnome π§", "VortexViper π", "ChronoChimp π" |
|
] |
|
|
|
|
|
CHAT_DIR = "chat_logs" |
|
VOTE_DIR = "vote_logs" |
|
os.makedirs(CHAT_DIR, exist_ok=True) |
|
os.makedirs(VOTE_DIR, exist_ok=True) |
|
|
|
|
|
CHAT_FILE = os.path.join(CHAT_DIR, "global_chat.md") |
|
QUOTE_VOTES_FILE = os.path.join(VOTE_DIR, "quote_votes.md") |
|
IMAGE_VOTES_FILE = os.path.join(VOTE_DIR, "image_votes.md") |
|
HISTORY_FILE = os.path.join(VOTE_DIR, "vote_history.md") |
|
|
|
|
|
def get_node_name(): |
|
"""π² Spins the wheel of fate to name our node - a random alias or user pick! π·οΈ""" |
|
parser = argparse.ArgumentParser(description='Start a chat node with a specific name') |
|
parser.add_argument('--node-name', type=str, default=None, help='Name for this chat node') |
|
parser.add_argument('--port', type=int, default=8501, help='Port to run the Streamlit interface on') |
|
args = parser.parse_args() |
|
return args.node_name or f"node-{uuid.uuid4().hex[:8]}", args.port |
|
|
|
|
|
def save_chat_entry(username, message): |
|
"""ποΈ Carves a chat line into the grand Markdown tome - history in the making! ποΈ""" |
|
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") |
|
entry = f"[{timestamp}] {username}: {message}" |
|
try: |
|
with open(CHAT_FILE, 'a') as f: |
|
f.write(f"{entry}\n") |
|
return True |
|
except Exception as e: |
|
print(f"Oops! Failed to save chat: {e}") |
|
return False |
|
|
|
|
|
def load_chat(): |
|
"""π Digs up the chat treasure from the filesystem - tales of old and new! π°""" |
|
if not os.path.exists(CHAT_FILE): |
|
with open(CHAT_FILE, 'w') as f: |
|
f.write("# Global Chat\n\nNo messages yet - start chatting! π€\n") |
|
try: |
|
with open(CHAT_FILE, 'r') as f: |
|
content = f.read() |
|
lines = content.strip().split('\n') |
|
numbered_content = "\n".join(f"{i+1}. {line}" for i, line in enumerate(lines) if line.strip()) |
|
return numbered_content |
|
except Exception as e: |
|
print(f"Chat load hiccup: {e}") |
|
return "# Error loading chat\nSomething went wonky! π΅" |
|
|
|
|
|
def get_user_list(chat_content): |
|
"""π Peeks at the chat to spot all the cool cats talking - whoβs in the club? πΈ""" |
|
users = set() |
|
for line in chat_content.split('\n'): |
|
if line.strip() and ': ' in line: |
|
user = line.split(': ')[1].split(' ')[0] |
|
users.add(user) |
|
return sorted(list(users)) |
|
|
|
|
|
def load_quotes(source="famous"): |
|
"""π Grabs a stack of wise words from famous folks or custom quips! π£οΈ""" |
|
famous_quotes = [ |
|
"The true sign of intelligence is not knowledge but imagination. β Albert Einstein", |
|
"I have not failed. I've just found 10,000 ways that won't work. β Thomas Edison", |
|
"Innovation distinguishes between a leader and a follower. β Steve Jobs", |
|
"Research is what I'm doing when I don't know what I'm doing. β Wernher von Braun", |
|
"The only way to discover the limits of the possible is to go beyond them into the impossible. β Arthur C. Clarke", |
|
"Success is a science; if you have the conditions, you get the result. β Oscar Wilde", |
|
"An expert is a person who has made all the mistakes that can be made in a very narrow field. β Niels Bohr", |
|
"The important thing is to not stop questioning. Curiosity has its own reason for existing. β Albert Einstein", |
|
"The best way to predict the future is to invent it. β Alan Kay", |
|
"If I have seen further it is by standing on the shoulders of Giants. β Isaac Newton", |
|
"Logic will get you from A to B. Imagination will take you everywhere. β Albert Einstein", |
|
"Imagination is more important than knowledge. Knowledge is limited. Imagination encircles the world. β Albert Einstein", |
|
"Science is a way of thinking much more than it is a body of knowledge. β Carl Sagan", |
|
"We cannot solve our problems with the same thinking we used when we created them. β Albert Einstein", |
|
"The true method of knowledge is experiment. β William Blake", |
|
"The scientist is not a person who gives the right answers, he's one who asks the right questions. β Claude Levi-Strauss", |
|
"It's kind of fun to do the impossible. β Walt Disney", |
|
"Any sufficiently advanced technology is indistinguishable from magic. β Arthur C. Clarke", |
|
"Creativity is intelligence having fun. β Albert Einstein", |
|
"To invent, you need a good imagination and a pile of junk. β Thomas Edison" |
|
] |
|
custom_quotes = [ |
|
"Every age unfolds a new lesson. Life's chapters evolve, each teaching us anew.", |
|
"From infancy to twilight, our journey is painted in growth. Every stage shines with its own wisdom.", |
|
"Love is the universal language, transcending boundaries and touching souls.", |
|
"Through love, we find connection, unity, and the essence of existence." |
|
] |
|
return famous_quotes if source == "famous" else custom_quotes |
|
|
|
|
|
def save_vote(file, item, user_hash): |
|
"""βοΈ Tallies a vote in the grand ledger - your opinion matters! π³οΈ""" |
|
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") |
|
entry = f"[{timestamp}] {user_hash} voted for {item}" |
|
try: |
|
with open(file, 'a') as f: |
|
f.write(f"{entry}\n") |
|
with open(HISTORY_FILE, 'a') as f: |
|
f.write(f"- {timestamp} - User {user_hash} voted for {item}\n") |
|
return True |
|
except Exception as e: |
|
print(f"Vote save flop: {e}") |
|
return False |
|
|
|
|
|
def load_votes(file): |
|
"""π Counts the votes from the ledger - whoβs winning the popularity contest? π""" |
|
if not os.path.exists(file): |
|
with open(file, 'w') as f: |
|
f.write("# Vote Tally\n\nNo votes yet - get clicking! π±οΈ\n") |
|
try: |
|
with open(file, 'r') as f: |
|
lines = f.read().strip().split('\n') |
|
votes = {} |
|
for line in lines[2:]: |
|
if line.strip() and 'voted for' in line: |
|
item = line.split('voted for ')[1] |
|
votes[item] = votes.get(item, 0) + 1 |
|
return votes |
|
except Exception as e: |
|
print(f"Vote load oopsie: {e}") |
|
return {} |
|
|
|
|
|
def generate_user_hash(): |
|
"""π΅οΈ Crafts a snazzy 8-digit ID badge - youβre in the club now! ποΈ""" |
|
if 'user_hash' not in st.session_state: |
|
session_id = str(random.getrandbits(128)) |
|
hash_object = hashlib.md5(session_id.encode()) |
|
st.session_state['user_hash'] = hash_object.hexdigest()[:8] |
|
return st.session_state['user_hash'] |
|
|
|
active_connections = {} |
|
|
|
|
|
async def websocket_handler(websocket, path): |
|
"""π§ Guards the chat gate, letting messages fly and booting crashers! π¨""" |
|
try: |
|
client_id = str(uuid.uuid4()) |
|
room_id = "chat" |
|
active_connections.setdefault(room_id, {})[client_id] = websocket |
|
print(f"Client {client_id} joined the chat party!") |
|
|
|
async for message in websocket: |
|
try: |
|
parts = message.split('|', 1) |
|
if len(parts) == 2: |
|
username, content = parts |
|
save_chat_entry(username, content) |
|
await broadcast_message(f"{username}|{content}", room_id) |
|
except Exception as e: |
|
print(f"Message mishap: {e}") |
|
await websocket.send(f"ERROR|Oops, bad message format! π¬") |
|
except websockets.ConnectionClosed: |
|
print(f"Client {client_id} bailed from the chat!") |
|
finally: |
|
if room_id in active_connections and client_id in active_connections[room_id]: |
|
del active_connections[room_id][client_id] |
|
if not active_connections[room_id]: |
|
del active_connections[room_id] |
|
|
|
|
|
async def broadcast_message(message, room_id): |
|
"""π’ Shouts the latest chat beat to every dancer in the room - hear it loud! π΅""" |
|
if room_id in active_connections: |
|
disconnected = [] |
|
for client_id, ws in active_connections[room_id].items(): |
|
try: |
|
await ws.send(message) |
|
except websockets.ConnectionClosed: |
|
disconnected.append(client_id) |
|
for client_id in disconnected: |
|
del active_connections[room_id][client_id] |
|
|
|
|
|
async def start_websocket_server(host='0.0.0.0', port=8765): |
|
"""π Cranks up the WebSocket jukebox, ready to rock the chat scene! πΈ""" |
|
server = await websockets.serve(websocket_handler, host, port) |
|
print(f"WebSocket server jamming on ws://{host}:{port}") |
|
return server |
|
|
|
|
|
def create_streamlit_interface(initial_username): |
|
"""ποΈ Sets up the chat stage with live updates, timers, voting, and refresh flair! π""" |
|
|
|
st.markdown(""" |
|
<style> |
|
.chat-box { |
|
font-family: monospace; |
|
background: #1e1e1e; |
|
color: #d4d4d4; |
|
padding: 10px; |
|
border-radius: 5px; |
|
height: 300px; |
|
overflow-y: auto; |
|
} |
|
.timer { |
|
font-size: 18px; |
|
color: #ffcc00; |
|
text-align: center; |
|
} |
|
</style> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
st.title(f"Chat & Quote Node: {NODE_NAME}") |
|
st.markdown("Chat, vote on messages, quotes, and images - keep the party rocking! π") |
|
|
|
|
|
if 'username' not in st.session_state: |
|
st.session_state.username = initial_username |
|
if 'refresh_rate' not in st.session_state: |
|
st.session_state.refresh_rate = 5 |
|
if 'last_refresh' not in st.session_state: |
|
st.session_state.last_refresh = time.time() |
|
if 'quote_index' not in st.session_state: |
|
st.session_state.quote_index = random.randint(0, len(load_quotes("famous")) - 1) |
|
if 'quote_source' not in st.session_state: |
|
st.session_state.quote_source = "famous" |
|
|
|
|
|
st.subheader("Chat Room π¬") |
|
chat_content = load_chat() |
|
chat_lines = chat_content.split('\n') |
|
for i, line in enumerate(chat_lines): |
|
if line.strip() and ': ' in line: |
|
col1, col2 = st.columns([5, 1]) |
|
with col1: |
|
st.markdown(line) |
|
with col2: |
|
if st.button(f"π", key=f"chat_vote_{i}"): |
|
user_hash = generate_user_hash() |
|
save_vote(QUOTE_VOTES_FILE, line, user_hash) |
|
st.session_state.last_refresh = time.time() |
|
st.rerun() |
|
|
|
user_list = get_user_list(chat_content) |
|
new_username = st.selectbox("Switch User", user_list + [st.session_state.username], index=len(user_list)) |
|
if new_username != st.session_state.username: |
|
st.session_state.username = new_username |
|
|
|
message = st.text_input("Message", placeholder="Type your epic line here! βοΈ") |
|
if st.button("Send π") and message.strip(): |
|
save_chat_entry(st.session_state.username, message) |
|
st.session_state.last_refresh = time.time() |
|
st.rerun() |
|
|
|
|
|
st.subheader("Quote of the Moment π") |
|
quotes = load_quotes(st.session_state.quote_source) |
|
quote = quotes[st.session_state.quote_index] |
|
col1, col2 = st.columns([5, 1]) |
|
with col1: |
|
st.markdown(quote) |
|
with col2: |
|
if st.button("π Upvote", key="quote_vote"): |
|
user_hash = generate_user_hash() |
|
save_vote(QUOTE_VOTES_FILE, quote, user_hash) |
|
st.session_state.last_refresh = time.time() |
|
st.rerun() |
|
if time.time() - st.session_state.last_refresh > 10: |
|
st.session_state.quote_index = (st.session_state.quote_index + 1) % len(quotes) |
|
st.session_state.quote_source = "custom" if st.session_state.quote_source == "famous" else "famous" |
|
st.session_state.last_refresh = time.time() |
|
st.rerun() |
|
|
|
|
|
st.subheader("Image Voting πΌοΈ") |
|
image_dir = '.' |
|
valid_extensions = ('.png', '.jpg', '.jpeg', '.gif', '.bmp', '.tiff', '.webp') |
|
images = [f for f in os.listdir(image_dir) if f.lower().endswith(valid_extensions)] |
|
if len(images) >= 2: |
|
image1, image2 = random.sample(images, 2) |
|
col1, col2 = st.columns(2) |
|
with col1: |
|
st.image(os.path.join(image_dir, image1)) |
|
if st.button(f"π Upvote {image1}", key=f"img_vote_{image1}"): |
|
user_hash = generate_user_hash() |
|
save_vote(IMAGE_VOTES_FILE, image1, user_hash) |
|
st.session_state.last_refresh = time.time() |
|
st.rerun() |
|
with col2: |
|
st.image(os.path.join(image_dir, image2)) |
|
if st.button(f"π Upvote {image2}", key=f"img_vote_{image2}"): |
|
user_hash = generate_user_hash() |
|
save_vote(IMAGE_VOTES_FILE, image2, user_hash) |
|
st.session_state.last_refresh = time.time() |
|
st.rerun() |
|
else: |
|
st.error("Need at least 2 images in the directory for voting!") |
|
|
|
|
|
st.subheader("Set Refresh Rate β³") |
|
refresh_rate = st.slider("Refresh Rate (seconds)", min_value=1, max_value=300, value=st.session_state.refresh_rate, step=1) |
|
st.session_state.refresh_rate = refresh_rate |
|
|
|
col1, col2, col3 = st.columns(3) |
|
with col1: |
|
if st.button("π Small (1s)"): |
|
st.session_state.refresh_rate = 1 |
|
st.session_state.last_refresh = time.time() |
|
st.rerun() |
|
with col2: |
|
if st.button("π’ Medium (5s)"): |
|
st.session_state.refresh_rate = 5 |
|
st.session_state.last_refresh = time.time() |
|
st.rerun() |
|
with col3: |
|
if st.button("π Large (5m)"): |
|
st.session_state.refresh_rate = 300 |
|
st.session_state.last_refresh = time.time() |
|
st.rerun() |
|
|
|
|
|
elapsed = time.time() - st.session_state.last_refresh |
|
remaining = max(0, st.session_state.refresh_rate - int(elapsed)) |
|
st.markdown(f"<p class='timer'>Next refresh in: {remaining} seconds</p>", unsafe_allow_html=True) |
|
|
|
|
|
if elapsed >= st.session_state.refresh_rate: |
|
st.session_state.last_refresh = time.time() |
|
st.rerun() |
|
else: |
|
st.markdown(f"<script>window.setTimeout(() => window.location.reload(), {int((st.session_state.refresh_rate - elapsed) * 1000)});</script>", unsafe_allow_html=True) |
|
|
|
|
|
st.sidebar.subheader("Vote Totals") |
|
chat_votes = load_votes(QUOTE_VOTES_FILE) |
|
image_votes = load_votes(IMAGE_VOTES_FILE) |
|
for item, count in chat_votes.items(): |
|
st.sidebar.write(f"{item}: {count} votes") |
|
for image, count in image_votes.items(): |
|
st.sidebar.write(f"{image}: {count} votes") |
|
|
|
|
|
async def main(): |
|
"""π€ Drops the mic and starts the chat party - itβs showtime, folks! π""" |
|
global NODE_NAME |
|
NODE_NAME, port = get_node_name() |
|
await start_websocket_server() |
|
|
|
query_params = st.query_params if hasattr(st, 'query_params') else {} |
|
initial_username = query_params.get("username", random.choice(FUN_USERNAMES)) if query_params else random.choice(FUN_USERNAMES) |
|
print(f"Welcoming {initial_username} to the chat bash!") |
|
|
|
create_streamlit_interface(initial_username) |
|
|
|
if __name__ == "__main__": |
|
asyncio.run(main()) |