fr33n0w___thechatroom/index.mu
F 412f49de31
Update index.mu
v1.45b nightly backup
2025-08-12 22:25:42 +02:00

1055 lines
40 KiB
Text

#!/usr/bin/env python3
import os, sys, json, time, random, re, sqlite3
DB_PATH = os.path.join(os.path.dirname(__file__), "chatusers.db")
EMO_DB = os.path.join(os.path.dirname(__file__), "emoticons.txt")
# EDITABLE SETTINGS:
MAX_CHARS = 105 # Adjust as needed to split messages after N chars
DISPLAY_LIMIT = 28 # Adjust how many visible messages you want in the UI
SYSADMYN = "fr4dm1n@@@" # SET YOUR ADMIN NICKNAME FOR CHAT ADMIN COMMANDS
# UI Emojis:
user_icon = "\U0001F464" # "\U0001F464" # "\U0001F465" - "\U0001FAAA"
message_icon = "\U0001F4AC"
msg2_icon = "\u2709\ufe0f"
send_icon = "\U0001F4E4"
totmsg_icon = "\U0001F4E9"
reload_icon = "\u21BB"
setup_icon = "\u2699\ufe0f"
cmd_icon = "\U0001F4BB" # \U0001F579
nickset_icon = "\U0001F504"
info_icon = "\u1F6C8"
stats_icon = "\u1F4DD"
# Antispam filters:
spam_patterns = [
r"buy\s+now",
r"free\s+money",
r"fr[e3]{2}\s+m[o0]ney",
r"click\s+here",
r"cl[i1]ck\s+h[e3]re",
r"subscribe\s+(now|today)",
r"win\s+big",
r"w[i1]n\s+b[i1]g",
r"limited\s+offer",
r"act\s+now",
r"get\s+rich\s+quick",
r"make\s+money\s+fast",
r"easy\s+cash",
r"work\s+from\s+home",
r"double\s+your\s+income",
r"guaranteed\s+results",
r"risk[-\s]*free",
r"lowest\s+price",
r"no\s+credit\s+check",
r"instant\s+approval",
r"earn\s+\$\d+",
r"cheap\s+meds",
r"online\s+pharmacy",
r"lose\s+weight\s+fast",
r"miracle\s+cure",
r"bitcoin\s+offer",
r"b[i1]tcoin\s+deal",
r"earn\s+bitcoin",
r"make\s+money\s+with\s+bitcoin",
r"crypto\s+investment",
r"crypto\s+deal",
r"get\s+rich\s+with\s+crypto",
r"eth[e3]reum\s+promo",
r"buy\s+crypto\s+now",
r"invest\s+in\s+(crypto|bitcoin|ethereum)"
]
spam_patterns += [
r"\bfree\s+(bitcoin|crypto|ethereum)\b",
r"\bsell\s+(bitcoin|crypto|ethereum)\b",
r"\bi\s+sell\s+(bitcoin|bitcoins|crypto|ethereum)\b",
r"\bbuy\s+(bitcoin|crypto|ethereum)\b",
r"\bget\s+(bitcoin|crypto|ethereum)\b",
r"\bmake\s+money\s+(with|from)\s+(bitcoin|crypto|ethereum)\b",
r"\binvest\s+(in|into)\s+(bitcoin|crypto|ethereum)\b",
r"\bbitcoin\s+(promo|deal|offer|discount)\b",
r"\bcrypto\s+(promo|deal|offer|discount)\b"
]
spam_patterns += [
r"\bfree\s+(bitcoin|bitcoins|coin|coins|crypto|tokens|ethereum)\b",
r"\b(bitcoin|bitcoins|coin|coins|crypto|tokens|ethereum)\s+for\s+you\b",
r"\bsell\s+(bitcoin|bitcoins|coin|coins|crypto|tokens|ethereum)\b",
r"\bbuy\s+(bitcoin|bitcoins|coin|coins|crypto|tokens|ethereum)\b",
r"\bi\s+sell\s+(bitcoin|bitcoins|coin|coins|crypto|tokens|ethereum)\b",
r"\bget\s+(bitcoin|bitcoins|coin|coins|crypto|tokens|ethereum)\b",
r"\bmake\s+money\s+(with|from)\s+(bitcoin|bitcoins|coin|coins|crypto|tokens|ethereum)\b",
r"\binvest\s+(in|into)\s+(bitcoin|bitcoins|coin|coins|crypto|tokens|ethereum)\b"
]
spam_patterns += [
r"(?:\W|^)(bitcoin|bitcoins|crypto|ethereum|tokens|coins)(?:\W|$)", # matches with punctuation or boundaries
r"\b(bitcoin|bitcoins|crypto|ethereum|tokens|coins)\s+for\s+(free|you)\b",
r"\bfree\s+(bitcoin|bitcoins|crypto|ethereum|tokens|coins)\b",
r"\bget\s+(bitcoin|bitcoins|crypto|ethereum|tokens|coins)\b",
r"\bmake\s+money\s+(with|from)\s+(bitcoin|bitcoins|crypto|ethereum|tokens|coins)\b"
]
# Color system
colors = [
"B900", "B090", "B009", "B099", "B909", "B066", "B933", "B336", "B939",
"B660", "B030", "B630", "B363", "B393", "B606", "B060", "B003", "B960", "B999",
"B822", "B525", "B255", "B729", "B279", "B297", "B972", "B792", "B227", "B277",
"B377", "B773", "B737", "B003", "B111", "B555", "B222", "B088", "B808", "B180"
]
def get_color(name):
return colors[sum(ord(c) for c in name.lower()) % len(colors)]
# Recover input from environment variables
def recover_input(key_suffix):
for k, v in os.environ.items():
if k.lower().endswith(key_suffix):
return v.strip()
return ""
raw_username = recover_input("username")
message = recover_input("message")
remote_identity = recover_input("remote_identity")
nickname = recover_input("field_username") # This is prioritized
dest = recover_input("dest")
# Fallback to command-line arguments if needed
if not raw_username and len(sys.argv) > 1:
raw_username = sys.argv[1].strip()
if not message and len(sys.argv) > 2:
message = sys.argv[2].strip()
if not dest and len(sys.argv) > 3:
dest = sys.argv[3].strip()
# Extract hash code from remote identity and LXMF address
hash_code = remote_identity[-4:] if remote_identity else ""
dest_code = dest[-4:] if dest else ""
# Smart fallback for display name
if nickname:
display_name = nickname
elif dest:
display_name = f"Guest_{dest_code}"
else:
display_name = "Guest"
# os env print for debug test
#print("> Meshchat Environment Variables:\n")
#for key, value in os.environ.items():
# print(f"{key} = {value}")
# os env print test to check recovered inputs
#print(f"[DEBUG] Recovered Inputs:")
#print(f" Username : {raw_username}")
#print(f" Message : {message}")
#print(f" Nickname : {nickname}")
#print(f" Nickfieldname : {field_nickname}")
#print(f" Remote Identity : {remote_identity}")
#print(f" Hash Code : {hash_code}")
#print(f" LXMF Code : {dest_code}")
#print(f" LXMF Address : {dest}")
#print(f" Display Name : {display_name}")
#print("Using database at:", os.path.abspath(DB_PATH))
# sql db nick binding and recovering
def init_db():
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
remote_identity TEXT,
dest TEXT UNIQUE NOT NULL,
display_name TEXT
)
""")
conn.commit()
conn.close()
def get_display_name_from_db(dest):
if not dest:
return None
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute("SELECT display_name FROM users WHERE dest = ?", (dest,))
result = cursor.fetchone()
conn.close()
return result[0] if result else None
def save_user_to_db(remote_identity, dest, display_name):
if not remote_identity or not dest:
return # Don't save if required info is missing
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute("""
INSERT INTO users (remote_identity, dest, display_name)
VALUES (?, ?, ?)
ON CONFLICT(dest) DO UPDATE SET
remote_identity = excluded.remote_identity,
display_name = excluded.display_name
""", (remote_identity, dest, display_name))
conn.commit()
conn.close()
# Initialize DB
init_db()
# Get environment variables
nickname = os.getenv("field_username", "").strip()
dest = os.getenv("dest", "").strip()
remote_identity = os.getenv("remote_identity", "").strip()
# Try to load display_name from DB
db_display_name = get_display_name_from_db(dest)
# Determine final display_name
if nickname:
display_name = nickname
elif db_display_name:
display_name = db_display_name
elif dest:
display_name = f"Guest_{dest[-4:]}"
else:
display_name = "Guest"
# Save user to DB if valid
save_user_to_db(remote_identity, dest, display_name)
# -----------------------------------------------
safe_username = (
raw_username.replace("`", "").replace("<", "").replace(">", "")
.replace("\n", "").replace("\r", "").replace('"', "").replace("'", "")
.replace("/", "").replace("\\", "").replace(";", "").replace(":", "")
.replace("&", "").replace("=", "").replace("{", "").replace("}", "")
.replace("[", "").replace("]", "").replace("(", "").replace(")", "")
.replace("\t", "").replace("*", "").replace("+", "").replace("%", "")
.replace("#", "").replace("^", "").replace("~", "").replace("|", "")
.replace("$", "").replace(" ", "").strip() or "Guest"
)
topic_file = os.path.join(os.path.dirname(__file__), "topic.json")
try:
with open(topic_file, "r") as tf:
topic_data = json.load(tf)
topic_text = topic_data.get("text", "Welcome to the chatroom!")
topic_author = topic_data.get("user", "System")
except:
topic_text = "Welcome to the chatroom!"
topic_author = "System"
log_file = os.path.join(os.path.dirname(__file__), "chat_log.json")
debug = []
try:
with open(log_file, "r") as f:
log = json.load(f)
debug.append(f" Total {len(log)} messages")
except Exception as e:
log = []
debug.append(f"Failed to load log: {e}")
# USER COMMANDS LOGIC:
cmd = message.strip().lower()
if safe_username == SYSADMYN and cmd.startswith("/clear"):
parts = cmd.split()
if len(parts) == 1:
# /clear → remove last message
if log:
removed = log.pop()
debug.append(f"Removed last message: <{removed['user']}> {removed['text']}")
else:
debug.append("No messages to clear.")
elif len(parts) == 2 and parts[1].isdigit():
# /clear N → remove last N messages
count = int(parts[1])
removed_count = 0
while log and removed_count < count:
removed = log.pop()
debug.append(f"Removed: <{removed['user']}> {removed['text']}")
removed_count += 1
debug.append(f"Cleared last {removed_count} messages.")
elif len(parts) == 3 and parts[1] == "user":
# /clear user NICKNAME → remove all messages from that user
target_user = parts[2]
original_len = len(log)
log[:] = [msg for msg in log if msg.get("user") != target_user]
removed_count = original_len - len(log)
debug.append(f"Cleared {removed_count} messages from user '{target_user}'.")
else:
debug.append("Invalid /clear syntax. Use /clear, /clear N, or /clear user NICKNAME.")
# Save updated log
try:
with open(log_file, "w", encoding="utf-8") as f:
json.dump(log, f, indent=2, ensure_ascii=False)
debug.append("Log updated after clearing.")
except Exception as e:
debug.append(f"Clear command error: {e}")
elif safe_username == SYSADMYN and cmd == "/clearall":
if log:
log.clear()
debug.append("All messages cleared by admin.")
try:
with open(log_file, "w", encoding="utf-8") as f:
json.dump(log, f, indent=2, ensure_ascii=False)
debug.append("Log successfully emptied.")
except Exception as e:
debug.append(f"ClearAll error: {e}")
else:
debug.append("Log already empty. Nothing to clear.")
elif cmd == "/stats":
user_stats = {}
user_set = set()
for msg in log:
if msg["user"] != "System":
user_stats[msg["user"]] = user_stats.get(msg["user"], 0) + 1
user_set.add(msg["user"])
total_users = len(user_set)
total_messages = len(log)
top_users = sorted(user_stats.items(), key=lambda x: x[1], reverse=True)
# Prepare lines
log.append({"time": time.strftime("[%H:%M:%S]"), "user": "System", "text": "`!` Stats Report: `!` "})
log.append({"time": time.strftime("[%H:%M:%S]"), "user": "System", "text": f"`!` Total messages: {total_messages} `!` "})
log.append({"time": time.strftime("[%H:%M:%S]"), "user": "System", "text": f"`!` Total users: {total_users} `!` "})
# Combine top chatters in one line
top_line = "`!` Top chatters: `!` " + " , ".join([f"`!` {user} ({count} msg) `!`" for user, count in top_users[:5]])
log.append({"time": time.strftime("[%H:%M:%S]"), "user": "System", "text": top_line})
############ /users COMMAND ##############
elif cmd == "/users":
# Count messages per user
from collections import Counter
user_counts = Counter(msg["user"] for msg in log if msg["user"] != "System")
# Sort by most active
sorted_users = sorted(user_counts.items(), key=lambda x: -x[1])
total_users = len(sorted_users)
# Header line
log.append({
"time": time.strftime("[%H:%M:%S]"),
"user": "System",
"text": f"`!` Active Users List and Stats, Total Users: ({total_users}) `! "
})
# Show in chunks of N with message counts
for i in range(0, total_users, 7):
chunk = ", ".join(f"`!` {user} `!({count}msg)" for user, count in sorted_users[i:i+7])
log.append({
"time": time.strftime("[%H:%M:%S]"),
"user": "System",
"text": chunk
})
############# /cmd COMMAND INFO LINES ############
elif cmd == "/cmd":
help_lines = [
f"`! {message_icon} THE CHATROOM!{message_icon} \\ EXTENDED USER COMMANDS INFO:`!",
f"`! GENERAL USE AND INFORMATIONAL COMMANDS:`!",
f"`! /info`! : Show The Chat Room! Informations, Usage and Disclaimer",
f"`! /cmd`! : Show all the available user commands",
f"`! /stats`! : Show chatroom statistics, including Top 5 Chatters",
f"`! /users`! : List all chatroom users",
f"`! /version`! : Show THE CHAT ROOM! script version, news and infos",
"--------------------------------------",
f"`!` {cmd_icon} INTERACTIVE CHAT COMMANDS`!`",
"`!` /lastseen <username>`!`: Last seen user info and latest user message",
"`!` /topic`!` : Show or Change Room Topic, usage: '/topic' or '/topic Your New Topic Here' ",
"`!` /search <keyword(s)>`!` : Search for keywords in the full chatlog ",
"`!` /time`!` : Show current Chat Server Time (UTC) and your Local Time",
"`!` /ping`!` : Reply with PONG! if the chat system is up and working",
"--------------------------------------",
f"`!` {cmd_icon} SOCIAL INTERACTIONS COMMANDS`!`",
"`!` /e`!` : Sends randomized emojis from the internal emoji list",
"`!` /c <text message>`!` : Sends a colored chat message with randomized background and font colors",
"`!` @nickname`!` : Sends a colored mention to highlight the mentioned user in a reply message",
"`!` $e`!` : Sends a random emoticon using '$e', usable in every part of the message. ",
"`!` /welcome`! : Sends a welcome message. Usage: /welcome or /welcome <nickname>. ",
"--------------------------------------",
f"`!` {cmd_icon} USER STATUS INTERACTIONS COMMANDS`!`",
"`!` /hi, /bye, /brb, /lol, /exit, /quit, /away, /back `!`",
"`!` Commands Usage Example: /hi Hello World! `! (Syntax is valid for all the above commands!)",
"--------------------------------------",
"`!` END OF COMMAND LIST: `[RELOAD THE PAGE TO GO BACK TO THE CHATROOM`:/page/index.mu`username]` `! ",
]
for line in help_lines:
log.append({
"time": time.strftime("[%H:%M:%S]"),
"user": "System",
"text": line
})
elif cmd == "/info":
info_lines = [
"`!` The Chat Room Info - Overview - Usage - Commands - Disclaimer - README! :) `!`",
"Welcome! This space is designed to connect people through an old school irc-styled interface.",
"No registration required, set your nickname and you are ready to message with other users.",
"Nicknames are randomly colorized and there is persistent color for every nickname.",
"No privacy compromission: use any nick you want. Nothing is recorded or associated to your rns identity.",
"This runs on a Nomadnet, so it will be visible internationally. Respect all the user languages in chat.",
"This chat is based on micron and python components.",
"ChatRoom script is running on a VPS server so it will be stable and always online",
"You can send irc-style messages and use various commands to explore the chatroom.",
"`!` Command Reference `!`",
"Just Some Examples:",
"/users : show active users and message counts",
"/lastseen <username> : check a user's recent activity",
"/topic : show or change the room topic",
"/stats : show chat stats including top chatters",
"`!` Use /help to view the full list of available commands. `!`",
"`!` Technical Notes `!`",
"Due to micron limitations, the chatroom does not refresh automatically.",
"To see new messages or preserve your nickname, reload the page using the provided link buttons.",
"Refreshing the page using meshchat browser function will remove nickname persistance, so use our Reload button",
"The main chatroom shows the last 30 messages; use the button at the bottom to view the full chat log.",
"`!` DISCLAIMER `!`",
"This chatroom is a space for connection, collaboration, and respectful interaction.",
"Rude, offensive, or inappropriate behavior is not tolerated. Messages may be deleted.",
"Suspension or message deletion can occur without prior warning in serious or repeated cases.",
"`!` BEFORE FREE SPECH, COMES RESPECT! - WELCOME TO >>THE CHAT ROOM!<< `!`"
]
for line in info_lines:
log.append({
"time": time.strftime("[%H:%M:%S]"),
"user": "System",
"text": line
})
############ TIME COMMAND ###############
elif cmd == "/time":
server_time = time.strftime("%Y-%m-%d %H:%M:%S")
try:
import pytz, datetime
user_time = datetime.datetime.now(pytz.timezone("Europe/Rome")).strftime("%Y-%m-%d %H:%M:%S")
except:
user_time = "(Local time not available)"
time_text = f"Server time: {server_time} // User time (Naples): {user_time}"
log.append({"time": time.strftime("[%H:%M:%S]"), "user": "System", "text": time_text})
############ VERSION COMMAND ##########
elif cmd == "/version":
version_text = "The Chat Room v1.45b / Powered by Reticulum NomadNet / IRC Style / Optimized for Meshchat / Made by F"
version_text2 = "This chat is running on a VPS server, powered by RNS v1.0.0 and Nomadnet v.0.8.0."
version_text3 = "Latest Implementations in v1.3b: AntiSpam Filter and Nickname persistency (Thanks To: Thomas!!)"
version_text4 = "Latest Implementations in v1.4b: Improved UI with Message splitting on long messages"
version_text5 = "Latest Implementations in v1.44b: Improved UI, resolved few ui bugs, added Menu Bar on the bottom, added /search command, added 'Read Last 100 Messages', started implementing user settings (for future user preferences implementations: custom nickname colors, multiple chat themes and more...coming soon!)"
version_text6 = "Latest Implementations in v1.45b: Added Social Interactions Commands, for full command list: /cmd \n Improved UI and readability"
log.append({"time": time.strftime("[%H:%M:%S]"), "user": "System", "text": version_text})
log.append({"time": time.strftime("[%H:%M:%S]"), "user": "System", "text": version_text2})
log.append({"time": time.strftime("[%H:%M:%S]"), "user": "System", "text": version_text3})
log.append({"time": time.strftime("[%H:%M:%S]"), "user": "System", "text": version_text4})
log.append({"time": time.strftime("[%H:%M:%S]"), "user": "System", "text": version_text5})
log.append({"time": time.strftime("[%H:%M:%S]"), "user": "System", "text": version_text6})
elif cmd.startswith("/lastseen "):
target_user = cmd[10:].strip()
last = next((msg for msg in reversed(log) if msg["user"] == target_user), None)
seen_text = f"Last seen {target_user} at {last['time']}: {last['text']}" if last else f"No record of user '{target_user}'."
log.append({"time": time.strftime("[%H:%M:%S]"), "user": "System", "text": seen_text})
elif cmd.startswith("/topic "):
new_topic = message[7:].replace("`", "").strip()
if new_topic:
trimmed_topic = new_topic[:70] # limit to N characters
timestamp = time.strftime("%d %B %Y")
topic_data = {"text": trimmed_topic, "user": safe_username, "time": timestamp}
try:
with open(topic_file, "w") as tf:
json.dump(topic_data, tf)
log.append({
"time": time.strftime("[%H:%M:%S]"),
"user": "System",
"text": f"Topic set by {safe_username} on {timestamp}: {trimmed_topic} `!`[<Reload Page!>`:/page/index.mu`username]`!"
})
except Exception as e:
debug.append(f"Topic update error: {e}")
else:
debug.append("No topic text provided.")
elif cmd == "/topic":
log.append({
"time": time.strftime("[%H:%M:%S]"),
"user": "System",
"text": f"Current Topic: {topic_text} (set by {topic_author} on {topic_data.get('time')})"
})
elif cmd.startswith("/search"):
search_input = message[8:].strip().lower()
if not search_input:
log.append({
"time": time.strftime("[%H:%M:%S]"),
"user": "System",
"text": "`!` Error! Command Usage: /search <keywords> - Please provide one or more keywords! `!`"
})
else:
keywords = search_input.split()
matches = []
for msg in log:
if msg.get("user") == "System":
continue
text = msg.get("text", "").lower()
if all(kw in text for kw in keywords):
matches.append(msg)
log.append({
"time": time.strftime("[%H:%M:%S]"),
"user": "System",
"text": f"`!` Search Results for: '{search_input}' - {len(matches)} match(es) found. `!`"
})
for match in matches[:10]:
log.append({
"time": time.strftime("[%H:%M:%S]"),
"user": "System",
"text": f"[{match.get('time', '??')}] <{match.get('user', '??')}> {match.get('text', '')}"
})
if len(matches) > 10:
log.append({
"time": time.strftime("[%H:%M:%S]"),
"user": "System",
"text": "`!` Showing first 10 results. Refine your search for more specific matches. `!`"
})
# PING PONG COMMAND
elif cmd == "/ping":
log.append({
"time": time.strftime("[%H:%M:%S]"),
"user": "System",
"text": "PONG! (System is up and working!)"
})
# /e RANDOM EMOJIS COMMAND
elif cmd == "/e":
try:
with open(EMO_DB, "r", encoding="utf-8") as f:
emojis = [line.strip() for line in f if line.strip()]
if emojis and safe_username:
import random
chosen = random.choice(emojis)
# Treat emoji as a normal message
log.append({
"time": time.strftime("[%H:%M:%S]"),
"user": safe_username,
"text": chosen
})
try:
with open(log_file, "w") as f:
json.dump(log, f)
debug.append(f" Emoji by '{safe_username}' sent: {chosen}")
except Exception as e:
debug.append(f" Emoji send error: {e}")
else:
log.append({
"time": time.strftime("[%H:%M:%S]"),
"user": "System",
"text": "`!` Emoji list is empty or username missing. `!`"
})
debug.append(" Emoji command skipped: missing emoji or username.")
except Exception as e:
log.append({
"time": time.strftime("[%H:%M:%S]"),
"user": "System",
"text": f"`!` Error loading emojis: {e} `!`"
})
debug.append(f" Emoji command error: {e}")
##### COLOR /c COMMAND ######
elif cmd.startswith("/c "):
user_message = message[3:].strip().replace("`", "") # Remove backticks to avoid formatting issues
if user_message and safe_username:
import random, json
def hex_brightness(hex_code):
r = int(hex_code[0], 16)
g = int(hex_code[1], 16)
b = int(hex_code[2], 16)
return (r + g + b) / 3
# Generate random hex color for background
bg_raw = ''.join(random.choices("0123456789ABCDEF", k=3))
bg_color = f"B{bg_raw}"
# Calculate brightness
brightness = hex_brightness(bg_raw)
font_color = "F000" if brightness > 7.5 else "FFF"
# Split message into chunks of 80 characters
def split_and_colorize(text, chunk_size=80):
chunks = [text[i:i+chunk_size] for i in range(0, len(text), chunk_size)]
return '\n'.join([f"`{bg_color}`{font_color}` {chunk} `b`f" for chunk in chunks])
colorful_text = split_and_colorize(user_message)
# Create log entry
entry = {
"time": time.strftime("[%H:%M:%S]"),
"user": safe_username,
"text": colorful_text
}
log.append(entry)
# Write to JSON file
try:
with open(log_file, "w", encoding="utf-8") as f:
json.dump(log, f)
debug.append(f"Test: Colored Message succesfully sent! by '{safe_username}'")
except Exception as e:
debug.append(f"Error sending colored message: {e}")
else:
debug.append("Error: Color command skipped due to missing message or username.")
###### /HI COMMAND #######
elif cmd.startswith("/hi"):
try:
parts = cmd.split(" ", 1)
user_message = parts[1].strip() if len(parts) > 1 else ""
timestamp = time.strftime("[%H:%M:%S]")
# Get color code for nickname
nickname_color = get_color(safe_username)
# Format nickname using your markup style
colored_nickname = f"`{nickname_color}{safe_username}`b"
# Build message
base_text = f"{colored_nickname} has joined The Chat Room!"
if user_message:
full_text = f" `!{base_text} Message: {user_message} `!"
else:
full_text = f" `!{base_text} `!"
log.append({
"time": timestamp,
"user": "System",
"text": full_text
})
with open(log_file, "w") as f:
json.dump(log, f)
except Exception as e:
log.append({
"time": time.strftime("[%H:%M:%S]"),
"user": "System",
"text": f"`!` Error processing /hi command: {e} `!`"
})
###### /BYE COMMAND #######
elif cmd.startswith("/bye"):
try:
parts = cmd.split(" ", 1)
user_message = parts[1].strip() if len(parts) > 1 else ""
timestamp = time.strftime("[%H:%M:%S]")
# Get color code for nickname
nickname_color = get_color(safe_username)
# Format nickname using your markup style
colored_nickname = f"`{nickname_color}{safe_username}`b"
# Build message
base_text = f"{colored_nickname} is leaving The Chat Room!"
if user_message:
full_text = f" `!{base_text} Message: {user_message} `!"
else:
full_text = f" `!{base_text} `!"
log.append({
"time": timestamp,
"user": "System",
"text": full_text
})
with open(log_file, "w") as f:
json.dump(log, f)
except Exception as e:
log.append({
"time": time.strftime("[%H:%M:%S]"),
"user": "System",
"text": f"`!` Error processing /bye command: {e} `!`"
})
###### /quit COMMAND #######
elif cmd.startswith("/quit"):
try:
parts = cmd.split(" ", 1)
user_message = parts[1].strip() if len(parts) > 1 else ""
timestamp = time.strftime("[%H:%M:%S]")
# Get color code for nickname
nickname_color = get_color(safe_username)
# Format nickname using your markup style
colored_nickname = f"`{nickname_color}{safe_username}`b"
# Build message
base_text = f"{colored_nickname} has quit The Chat Room!"
if user_message:
full_text = f" `!{base_text} Message: {user_message} `!"
else:
full_text = f" `!{base_text} `!"
log.append({
"time": timestamp,
"user": "System",
"text": full_text
})
with open(log_file, "w") as f:
json.dump(log, f)
except Exception as e:
log.append({
"time": time.strftime("[%H:%M:%S]"),
"user": "System",
"text": f"`!` Error processing /quit command: {e} `!`"
})
###### /exit COMMAND #######
elif cmd.startswith("/exit"):
try:
parts = cmd.split(" ", 1)
user_message = parts[1].strip() if len(parts) > 1 else ""
timestamp = time.strftime("[%H:%M:%S]")
# Get color code for nickname
nickname_color = get_color(safe_username)
# Format nickname using your markup style
colored_nickname = f"`{nickname_color}{safe_username}`b"
# Build message
base_text = f"{colored_nickname} has left The Chat Room!"
if user_message:
full_text = f" `!{base_text} Message: {user_message} `!"
else:
full_text = f" `!{base_text} `!"
log.append({
"time": timestamp,
"user": "System",
"text": full_text
})
with open(log_file, "w") as f:
json.dump(log, f)
except Exception as e:
log.append({
"time": time.strftime("[%H:%M:%S]"),
"user": "System",
"text": f"`!` Error processing /exit command: {e} `!`"
})
###### /BRB COMMAND #######
elif cmd.startswith("/brb"):
try:
parts = cmd.split(" ", 1)
user_message = parts[1].strip() if len(parts) > 1 else ""
timestamp = time.strftime("[%H:%M:%S]")
# Get color code for nickname
nickname_color = get_color(safe_username)
# Format nickname using your markup style
colored_nickname = f"`{nickname_color}{safe_username}`b"
# Build message
base_text = f"{colored_nickname} has left The Chat Room! I'LL BE RIGHT BACK! BRB!"
if user_message:
full_text = f" `!{base_text} Message: {user_message} `!"
else:
full_text = f" `!{base_text} `!"
log.append({
"time": timestamp,
"user": "System",
"text": full_text
})
with open(log_file, "w") as f:
json.dump(log, f)
except Exception as e:
log.append({
"time": time.strftime("[%H:%M:%S]"),
"user": "System",
"text": f"`!` Error processing /brb command: {e} `!`"
})
###### /lol COMMAND #######
elif cmd.startswith("/lol"):
try:
parts = cmd.split(" ", 1)
user_message = parts[1].strip() if len(parts) > 1 else ""
timestamp = time.strftime("[%H:%M:%S]")
# Get color code for nickname
nickname_color = get_color(safe_username)
# Format nickname using your markup style
colored_nickname = f"`{nickname_color}{safe_username}`b"
# Build message
base_text = f"{colored_nickname} is Laughing Out Loud! LOL! :D "
if user_message:
full_text = f" `!{base_text} Message: {user_message} `!"
else:
full_text = f" `!{base_text} `!"
log.append({
"time": timestamp,
"user": "System",
"text": full_text
})
with open(log_file, "w") as f:
json.dump(log, f)
except Exception as e:
log.append({
"time": time.strftime("[%H:%M:%S]"),
"user": "System",
"text": f"`!` Error processing /lol command: {e} `!`"
})
###### /away COMMAND #######
elif cmd.startswith("/away"):
try:
parts = cmd.split(" ", 1)
user_message = parts[1].strip() if len(parts) > 1 else ""
timestamp = time.strftime("[%H:%M:%S]")
# Get color code for nickname
nickname_color = get_color(safe_username)
# Format nickname using your markup style
colored_nickname = f"`{nickname_color}{safe_username}`b"
# Build message
base_text = f"{colored_nickname} is away."
if user_message:
full_text = f" `!{base_text} (Status: {user_message}) `!"
else:
full_text = f" `!{base_text} `!"
log.append({
"time": timestamp,
"user": "System",
"text": full_text
})
with open(log_file, "w") as f:
json.dump(log, f)
except Exception as e:
log.append({
"time": time.strftime("[%H:%M:%S]"),
"user": "System",
"text": f"`!` Error processing /away command: {e} `!`"
})
###### /back COMMAND #######
elif cmd.startswith("/back"):
try:
parts = cmd.split(" ", 1)
user_message = parts[1].strip() if len(parts) > 1 else ""
timestamp = time.strftime("[%H:%M:%S]")
# Get color code for nickname
nickname_color = get_color(safe_username)
# Format nickname using your markup style
colored_nickname = f"`{nickname_color}{safe_username}`b"
# Build message
base_text = f"{colored_nickname} is back! "
if user_message:
full_text = f" `!{base_text} {user_message} `!"
else:
full_text = f" `!{base_text} everyone! `!"
log.append({
"time": timestamp,
"user": "System",
"text": full_text
})
with open(log_file, "w") as f:
json.dump(log, f)
except Exception as e:
log.append({
"time": time.strftime("[%H:%M:%S]"),
"user": "System",
"text": f"`!` Error processing /back command: {e} `!`"
})
###### /welcome COMMAND #######
elif cmd.startswith("/welcome"):
try:
parts = cmd.split(" ", 1)
user_message = parts[1].strip() if len(parts) > 1 else ""
timestamp = time.strftime("[%H:%M:%S]")
# Get color code for nickname
nickname_color = get_color(safe_username)
# Format nickname using your markup style
colored_nickname = f"`{nickname_color}{safe_username}`b"
# Build message
base_text = f"{colored_nickname} Welcomes "
if user_message:
full_text = f" `!{base_text} {user_message} `!"
else:
full_text = f" `!{base_text} everyone! `!"
log.append({
"time": timestamp,
"user": "System",
"text": full_text
})
with open(log_file, "w") as f:
json.dump(log, f)
except Exception as e:
log.append({
"time": time.strftime("[%H:%M:%S]"),
"user": "System",
"text": f"`!` Error processing /welcome command: {e} `!`"
})
##################### END OF COMMANDS, CONTINUE SCRIPT ##############################
elif raw_username and message and message.lower() != "null":
sanitized_message = message.replace("`", "") # remove backticks to prevent formatting issues
# Spam detection logic
banned_words = ["buy now", "free money", "click here", "subscribe", "win big", "limited offer", "act now"]
is_spam = any(re.search(pattern, sanitized_message.lower()) for pattern in spam_patterns)
if is_spam:
# ?? Don't write to JSON, just log the system message
log.append({
"time": time.strftime("[%H:%M:%S]"),
"user": "System",
"text": "Spam Detected! Message Blocked!"
})
debug.append(f" Spam blocked from '{safe_username}'")
else:
# ? Normal message flow
log.append({
"time": time.strftime("[%H:%M:%S]"),
"user": safe_username,
"text": sanitized_message
})
try:
with open(log_file, "w") as f:
json.dump(log, f)
debug.append(f" Message by '{safe_username}' sent!")
except Exception as e:
debug.append(f" Send error: {e}")
else:
debug.append(" Page Reloaded. Idle. Void Message. Waiting for user interactions. For extended commands info digit: /help")
# Define helper function to split long messages using MAX CHARS const
def split_message(text, max_chars):
words = text.split()
lines = []
current_line = ""
for word in words:
if len(current_line) + len(word) + 1 <= max_chars:
current_line += (" " if current_line else "") + word
else:
lines.append(current_line)
current_line = word
if current_line:
lines.append(current_line)
return lines
# dynamic ui displayed messages adaptation
def calculate_effective_limit(log, display_limit, max_chars):
limit = display_limit
for msg in log[-display_limit:]:
if len(split_message(msg["text"], max_chars)) > 1:
limit -= 1
return max(limit, 22) # Minimum of 20 messages shown
effective_limit = calculate_effective_limit(log, DISPLAY_LIMIT, MAX_CHARS)
# mention users def logic on @user message
def highlight_mentions_in_line(line, known_users):
def replacer(match):
nickname = match.group(1)
if nickname in known_users:
color = get_color(nickname)
return f"`!@`{color}{nickname}`b`!"
else:
return f"@{nickname}" # Leave uncolored
return re.sub(r"@(\w+)", replacer, line)
# Load all individual emoticons from the file
with open(EMO_DB, "r", encoding="utf-8") as f:
EMOTICONS = []
for line in f:
EMOTICONS.extend(line.strip().split())
# $e catching for emoticons in messages
def substitute_emoticons_in_line(line):
return re.sub(r"\$e", lambda _: random.choice(EMOTICONS), line)
############################## Output UI template: ######################################
#INTRO TITLE:
template = f"> `!{message_icon} THE CHAT ROOM! {message_icon} `F007` Powered by Reticulum NomadNet - IRC Style - Free Global Chat Room - Optimized for Meshchat v2.x+ - v1.45b `f`!\n"
template += "-\n"
# TOPIC READING AND RENDERING:
template += f"`c`B000`Ff2e`!` ########## Room Topic: {topic_text} `! (Set by: {topic_author}, {topic_data.get('time')}) `!` ########## `!`f`b`a\n"
template += "-\n"
# CHATLOG READING AND RENDERING (original):
#for msg in log[-effective_limit:]:
# color = get_color(msg["user"])
# message_lines = split_message(msg["text"], MAX_CHARS)
# total_parts = len(message_lines)
# for i, line in enumerate(message_lines, start=1):
# marker = f"({i}/{total_parts})" if total_parts > 1 else ""
# template += f"\\\[{msg['time']} `!` `*` `{color}{msg['user']}:`b `!`*` {line} \n"
#template += "-"
# Build set of known usernames
known_users = {msg["user"] for msg in log}
# CHATLOG READING AND RENDERING edit to catch @mentions and $e:
for msg in log[-effective_limit:]:
color = get_color(msg["user"])
message_lines = split_message(msg["text"], MAX_CHARS)
total_parts = len(message_lines)
for i, line in enumerate(message_lines, start=1):
marker = f"({i}/{total_parts})" if total_parts > 1 else ""
if msg["user"] != "System":
line = substitute_emoticons_in_line(line) # Replace $e with random emoticons
highlighted_line = highlight_mentions_in_line(line, known_users) # Highlight @mentions
else:
highlighted_line = line # Skip substitutions for System user
template += f"`Ffff` \\{msg['time']} `*` `{color}{msg['user']}:`b `*` {highlighted_line} \n"
template += "-"
# sanitize and read name from display_name os env
safe_display_name = display_name.replace("`", "'")
# User Interaction Bar (Nick & Messages )
# template += f"\n`Ffff`! {user_icon} Nickname: `Bddf`F000`<12|username`{safe_display_name}>`!`B000`Ffff`[{nickset_icon} `:/page/index.mu`username]`! | {message_icon} Message: `Bddf`F000`<52|message`>`b`!"
# template += f" `!`Ffff`[{send_icon} Send Message`:/page/index.mu`username|message]`! | `!`[{reload_icon} Reload`:/page/index.mu`username]`!\n"
template += f"\n>`!` {user_icon} Nickname: `Baac`F000`<12|username`{safe_display_name}>`!`b`[{nickset_icon} `:/page/index.mu`username]`! {message_icon} Message: `Baac`<52|message`>`b`!"
template += f" `!`[{send_icon} Send Message`:/page/index.mu`username|message]`! | `!`[{reload_icon} Reload`:/page/index.mu`username]`!\n"
template += "-\n"
# STATUS BAR (incomplete)
# if debug:
# latest_debug = debug[-1]
# template += f">{cmd_icon} `B115`Fccc`!SYSTEM STATUS:`! {latest_debug}`b`f\n"
# USER COMMANDS
# template += "\n-\n"
template += f"`B216`Fddd` {cmd_icon} User Commands: /info, /stats, /users, /topic, /search <keyword(s)>, /time, /ping, /version, -------> `!Full Command List: /cmd `!`b`f\n"
#template += "-\n"
# MENUBAR
template += f"`B317`Feee` `!` {message_icon} Total Messages: ({len(log)}) | {message_icon} On Screen Messages: ({effective_limit}) | {totmsg_icon} `[Read Last 100`:/page/last100.mu]` | {totmsg_icon} `[Read Full Chat Log (Slow)`:/page/fullchat.mu]`! | `!`[{setup_icon} User Settings `:/page/index.mu`username]` `!`b`f\n"
#template += "-\n"
# FOOTER NOTE
#template += f"`B315`F90f` ** Note: For Nickname persistency, press the 'FingerPrint' button on MeshChat (v2.2.0+). To recover your session, press it again.`b`f`\n"
# RENDER UI:
print(template)
############################## END OF UI Template: ######################################