Initial commit
This commit is contained in:
118
.gitignore
vendored
Normal file
118
.gitignore
vendored
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
## Generated by ChatGPT
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
pipenv.lock
|
||||||
|
|
||||||
|
# poetry
|
||||||
|
poetry.lock
|
||||||
|
|
||||||
|
# virtualenv
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Echo-MVP
|
||||||
|
|
||||||
|
This is a "minimal" chatroom made in Python. A full version will come later.
|
||||||
69
main.py
Normal file
69
main.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
from flask_socketio import SocketIO, emit, join_room
|
||||||
|
from flask import Flask, render_template, redirect, request, url_for, jsonify
|
||||||
|
|
||||||
|
from util.room.registry import RoomRegistry
|
||||||
|
from util.room.logic import generate_room_code
|
||||||
|
|
||||||
|
ROOMS = set()
|
||||||
|
|
||||||
|
app = Flask(
|
||||||
|
__name__,
|
||||||
|
template_folder="resource/template",
|
||||||
|
static_folder="resource/static"
|
||||||
|
)
|
||||||
|
socketio = SocketIO(app)
|
||||||
|
|
||||||
|
registry = RoomRegistry()
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return render_template("home.html")
|
||||||
|
|
||||||
|
@app.route('/room/<room_code>')
|
||||||
|
def room(room_code):
|
||||||
|
return render_template('room.html', room_code=room_code)
|
||||||
|
|
||||||
|
@socketio.on('message')
|
||||||
|
def handle_message(data):
|
||||||
|
room_code = data['room']
|
||||||
|
sender = data['sender']
|
||||||
|
text = data['text']
|
||||||
|
|
||||||
|
room = registry.get_room(room_code)
|
||||||
|
if room:
|
||||||
|
room.add_message(sender, text)
|
||||||
|
emit('message', {'sender': sender, 'text': text}, to=room_code)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/room', methods=['POST'])
|
||||||
|
def create_room():
|
||||||
|
data = request.json
|
||||||
|
code = generate_room_code()
|
||||||
|
password = data.get("password")
|
||||||
|
room = registry.create_room(code, password)
|
||||||
|
return jsonify({"code": room.code})
|
||||||
|
|
||||||
|
@app.route('/api/room/<code>/messages')
|
||||||
|
def get_messages(code):
|
||||||
|
room = registry.get_room(code)
|
||||||
|
if not room:
|
||||||
|
return jsonify({"error": "Room not found"}), 404
|
||||||
|
return jsonify(room.messages)
|
||||||
|
|
||||||
|
|
||||||
|
@socketio.on('join')
|
||||||
|
def handle_join(data):
|
||||||
|
room = data['room']
|
||||||
|
sender = data.get('sender', 'Anonymous')
|
||||||
|
join_room(room)
|
||||||
|
|
||||||
|
emit('message', {
|
||||||
|
'sender': 'System',
|
||||||
|
'text': f'{sender} has joined the room.'
|
||||||
|
}, to=room)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
socketio.run(app, debug=False, host='0.0.0.0', port=8080)
|
||||||
|
|
||||||
20
requirements.txt
Normal file
20
requirements.txt
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
bidict==0.23.1
|
||||||
|
blinker==1.9.0
|
||||||
|
click==8.1.7
|
||||||
|
dnspython==2.7.0
|
||||||
|
eventlet==0.40.0
|
||||||
|
Flask==3.1.0
|
||||||
|
Flask-SocketIO==5.5.1
|
||||||
|
greenlet==3.2.2
|
||||||
|
gunicorn==23.0.0
|
||||||
|
h11==0.16.0
|
||||||
|
itsdangerous==2.2.0
|
||||||
|
Jinja2==3.1.4
|
||||||
|
jsonify==0.5
|
||||||
|
MarkupSafe==3.0.2
|
||||||
|
packaging==25.0
|
||||||
|
python-engineio==4.12.2
|
||||||
|
python-socketio==5.13.0
|
||||||
|
simple-websocket==1.1.0
|
||||||
|
Werkzeug==3.1.3
|
||||||
|
wsproto==1.2.0
|
||||||
BIN
resource/.DS_Store
vendored
Normal file
BIN
resource/.DS_Store
vendored
Normal file
Binary file not shown.
156
resource/static/css/styles.css
Normal file
156
resource/static/css/styles.css
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
:root {
|
||||||
|
--primary-color: rgb(243, 243, 243);
|
||||||
|
--secondary-color: rgb(7, 190, 187);
|
||||||
|
--tertiary-color: rgb(250, 250, 250);
|
||||||
|
--text-color: rgb(62, 62, 62);
|
||||||
|
--text-highlight-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Navigation */
|
||||||
|
.navbar {
|
||||||
|
position: fixed;
|
||||||
|
top:0px;
|
||||||
|
left:0px;
|
||||||
|
right:0px;
|
||||||
|
background-color: var(--tertiary-color);
|
||||||
|
border-bottom: 1px solid var(--secondary-color); /* Changed color to lightsteelblue */
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar a {
|
||||||
|
float: left;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
padding: 8px 16px;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background-color 0.15s ease, color 0.15s ease, padding 0.15s ease; /* Only transition the background-color */
|
||||||
|
color: var(--text-color); /* Set default text color */
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar a:hover {
|
||||||
|
background-color: var(--secondary-color);
|
||||||
|
color: var(--text-highlight-color); /* Change text color on hover for better contrast */
|
||||||
|
padding: 8px 20px; /* Increase padding on hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
.navname{
|
||||||
|
font-weight: bolder;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navname a{
|
||||||
|
background-color: var(--primary-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Body */
|
||||||
|
body {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
margin: 0px;
|
||||||
|
color: var(--text-color);
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Container */
|
||||||
|
.container {
|
||||||
|
padding: 35px 10px;
|
||||||
|
margin: auto;
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-join-box {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-input {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid var(--secondary-color);
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: var(--tertiary-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
font-size: 16px;
|
||||||
|
outline: none;
|
||||||
|
transition: padding 0.15s ease, border-color 0.15s ease;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-input:focus {
|
||||||
|
border-color: var(--secondary-color);
|
||||||
|
padding-right: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-button {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background-color: var(--tertiary-color);
|
||||||
|
border: 1px solid var(--secondary-color);
|
||||||
|
border-radius: 5px;
|
||||||
|
color: var(--text-color);
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.15s ease, color 0.15s ease, padding 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-button:hover {
|
||||||
|
background-color: var(--secondary-color);
|
||||||
|
color: var(--text-highlight-color);
|
||||||
|
padding-left: 18px;
|
||||||
|
padding-right: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chat-box {
|
||||||
|
flex: 1; /* This is the key: allows chat-box to grow and fill vertical space */
|
||||||
|
overflow-y: auto; /* Adds a scrollbar when content overflows vertically */
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid var(--secondary-color); /* Visual border for chat box */
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 10px; /* Space between chat box and input */
|
||||||
|
background-color: var(--tertiary-color); /* Example background for messages */
|
||||||
|
color: var(--text-color);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chat-box p {
|
||||||
|
margin: 0 0 5px 0; /* Adjust message spacing */
|
||||||
|
line-height: 1.4;
|
||||||
|
word-wrap: break-word; /* Ensures long words break and wrap */
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-input-container {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chat-input {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid var(--secondary-color);
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: var(--tertiary-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
font-size: 16px;
|
||||||
|
outline: none;
|
||||||
|
transition: padding 0.15s ease, border-color 0.15s ease;
|
||||||
|
flex: 1; /* Makes the input take up available horizontal space */
|
||||||
|
}
|
||||||
|
|
||||||
|
#send-button {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background-color: var(--tertiary-color);
|
||||||
|
border: 1px solid var(--secondary-color);
|
||||||
|
border-radius: 5px;
|
||||||
|
color: var(--text-color);
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.15s ease, color 0.15s ease, padding 0.15s ease;
|
||||||
|
flex-shrink: 0; /* Prevents the button from shrinking */
|
||||||
|
}
|
||||||
|
|
||||||
|
#send-button:hover {
|
||||||
|
background-color: var(--secondary-color);
|
||||||
|
color: var(--text-highlight-color);
|
||||||
|
padding-left: 18px;
|
||||||
|
padding-right: 18px;
|
||||||
|
}
|
||||||
18
resource/template/base.html
Normal file
18
resource/template/base.html
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
|
||||||
|
<link rel="icon" href="data:,">
|
||||||
|
<title>{% block title %}{% endblock %}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% include 'navbar.html' %}
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
{% block content %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
63
resource/template/home.html
Normal file
63
resource/template/home.html
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% block title %}Home{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>Hello, hello, hello...</h1>
|
||||||
|
<p>Yeah, it's early.</p>
|
||||||
|
<div class="room-join-box">
|
||||||
|
<input type="text" id="username-input" class="room-input" placeholder="Your name">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="room-join-box">
|
||||||
|
<input type="text" id="room-code-input" class="room-input" placeholder="Enter Room Code" maxlength="10">
|
||||||
|
<button class="room-button" id="join-button">Join</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="room-join-box">
|
||||||
|
<button class="room-button" id="create-button">Create New Room</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function getUsername() {
|
||||||
|
const username = document.getElementById('username-input').value.trim();
|
||||||
|
if (!username) {
|
||||||
|
alert("Please enter your name.");
|
||||||
|
throw new Error("Username required");
|
||||||
|
}
|
||||||
|
localStorage.setItem('username', username);
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('join-button').addEventListener('click', () => {
|
||||||
|
try {
|
||||||
|
getUsername();
|
||||||
|
const code = document.getElementById('room-code-input').value.trim();
|
||||||
|
if (code) {
|
||||||
|
window.location.href = `/room/${code}`;
|
||||||
|
} else {
|
||||||
|
alert("Enter a room code.");
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('create-button').addEventListener('click', async () => {
|
||||||
|
try {
|
||||||
|
getUsername();
|
||||||
|
const res = await fetch('/api/room', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({})
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.code) {
|
||||||
|
window.location.href = `/room/${data.code}`;
|
||||||
|
} else {
|
||||||
|
alert("Failed to create room.");
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
5
resource/template/navbar.html
Normal file
5
resource/template/navbar.html
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<div class="navbar">
|
||||||
|
<a class="navname">Echo</a>
|
||||||
|
<a href="/">Home</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
0
resource/template/options.html
Normal file
0
resource/template/options.html
Normal file
66
resource/template/room.html
Normal file
66
resource/template/room.html
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
{% extends 'room_base.html' %}
|
||||||
|
{% block title %}Room {{ room_code }}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>Room {{ room_code }}</h1>
|
||||||
|
<div id="chat-box"></div>
|
||||||
|
|
||||||
|
<div class="chat-input-container">
|
||||||
|
<input type="text" id="chat-input" placeholder="Say something..." />
|
||||||
|
<button id="send-button">Send</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const socket = io();
|
||||||
|
const room = "{{ room_code }}";
|
||||||
|
|
||||||
|
const name = localStorage.getItem('username') || 'Anonymous';
|
||||||
|
socket.emit('join', { room, sender: name });
|
||||||
|
|
||||||
|
|
||||||
|
// Fetch message history
|
||||||
|
fetch(`/api/room/${room}/messages`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
const chatBox = document.getElementById('chat-box');
|
||||||
|
if (data && Array.isArray(data)) {
|
||||||
|
data.forEach(msg => {
|
||||||
|
const msgElem = document.createElement('p');
|
||||||
|
msgElem.textContent = `${msg.sender}: ${msg.text}`;
|
||||||
|
chatBox.appendChild(msgElem);
|
||||||
|
});
|
||||||
|
chatBox.scrollTop = chatBox.scrollHeight;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Message listener
|
||||||
|
socket.on('message', (data) => {
|
||||||
|
const chatBox = document.getElementById('chat-box');
|
||||||
|
const msgElem = document.createElement('p');
|
||||||
|
|
||||||
|
msgElem.textContent = `${data.sender}: ${data.text}`;
|
||||||
|
if (data.sender === 'System') {
|
||||||
|
msgElem.style.fontStyle = 'italic';
|
||||||
|
msgElem.style.color = 'gray';
|
||||||
|
}
|
||||||
|
|
||||||
|
chatBox.appendChild(msgElem);
|
||||||
|
chatBox.scrollTop = chatBox.scrollHeight;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Send message
|
||||||
|
document.getElementById('send-button').onclick = () => {
|
||||||
|
const text = document.getElementById('chat-input').value;
|
||||||
|
const sender = localStorage.getItem('username') || 'Anonymous';
|
||||||
|
|
||||||
|
if (text.trim()) {
|
||||||
|
socket.emit('message', {
|
||||||
|
room,
|
||||||
|
text,
|
||||||
|
sender
|
||||||
|
});
|
||||||
|
document.getElementById('chat-input').value = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
20
resource/template/room_base.html
Normal file
20
resource/template/room_base.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
|
||||||
|
<link rel="icon" href="data:,">
|
||||||
|
<script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
|
||||||
|
|
||||||
|
<title>{% block title %}{% endblock %}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% include 'navbar.html' %}
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
{% block content %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
20
util/room/data.py
Normal file
20
util/room/data.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import List, Dict
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Room:
|
||||||
|
code: str
|
||||||
|
created_at: datetime = field(default_factory=datetime.utcnow)
|
||||||
|
messages: List[Dict] = field(default_factory=list) # Could include sender, timestamp, etc.
|
||||||
|
password: str = None # Optional
|
||||||
|
|
||||||
|
def add_message(self, sender: str, text: str):
|
||||||
|
self.messages.append({
|
||||||
|
"sender": sender,
|
||||||
|
"text": text,
|
||||||
|
"timestamp": datetime.now().isoformat()
|
||||||
|
})
|
||||||
|
|
||||||
|
def check_password(self, attempt: str) -> bool:
|
||||||
|
return self.password is None or self.password == attempt
|
||||||
6
util/room/logic.py
Normal file
6
util/room/logic.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import secrets
|
||||||
|
import string
|
||||||
|
|
||||||
|
def generate_room_code(length=10):
|
||||||
|
chars = string.ascii_letters + string.digits
|
||||||
|
return ''.join(secrets.choice(chars) for _ in range(length))
|
||||||
17
util/room/registry.py
Normal file
17
util/room/registry.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from util.room.data import Room
|
||||||
|
|
||||||
|
class RoomRegistry:
|
||||||
|
def __init__(self):
|
||||||
|
self.rooms: dict[str, Room] = {}
|
||||||
|
|
||||||
|
def create_room(self, code: str, password: str = None) -> Room:
|
||||||
|
room = Room(code=code, password=password)
|
||||||
|
self.rooms[code] = room
|
||||||
|
return room
|
||||||
|
|
||||||
|
def get_room(self, code: str) -> Room:
|
||||||
|
return self.rooms.get(code)
|
||||||
|
|
||||||
|
def delete_room(self, code: str):
|
||||||
|
if code in self.rooms:
|
||||||
|
del self.rooms[code]
|
||||||
Reference in New Issue
Block a user