Repository navigation
Expand file tree
/
Copy pathapp.py
More file actions
159 lines (113 loc) · 4.01 KB
/
Copy pathapp.py
File metadata and controls
159 lines (113 loc) · 4.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
from flask import Flask, render_template, request
from flask_socketio import SocketIO, emit
from datetime import datetime, timedelta
from flask_sqlalchemy import SQLAlchemy
import hashlib
import secrets
import time
MAX_LEN = 500
MAX_NAME_LEN = 30
RATE_LIMIT = 3
RATE_WINDOW = 10
app = Flask(__name__)
app.config['SECRET_KEY'] = 'Shayan...'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///chat.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
socketio = SocketIO(app)
class Message(db.Model):
id = db.Column(db.Integer, primary_key=True)
text = db.Column(db.String(MAX_LEN), nullable=False)
user_id = db.Column(db.String(MAX_NAME_LEN), nullable=True)
time = db.Column(db.DateTime, default=lambda: datetime.utcnow() + timedelta(hours=3, minutes=30))
class Name(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(MAX_NAME_LEN), unique=True, nullable=False)
token_hash = db.Column(db.String(64), nullable=False)
with app.app_context():
db.create_all()
@app.route('/')
def index():
return render_template('index.html')
connected_users = {}
user_times = {}
def hash_token(token):
return hashlib.sha256(token.encode('utf-8')).hexdigest()
def valid_name(name):
name = name.strip()
return 1 <= len(name) <= MAX_NAME_LEN
def rate_limited(sid):
now = time.monotonic()
recent = [t for t in user_times.get(sid, []) if now - t < RATE_WINDOW]
recent.append(now)
user_times[sid] = recent
return len(recent) > RATE_LIMIT
def send_history(sid):
messages = Message.query.order_by(Message.time).all()
history = [
{
'text': m.text,
'time': m.time.strftime('%H:%M'),
'user_id': m.user_id
}
for m in messages
]
emit('history', history, to=sid)
@socketio.on('claim_name')
def handle_claim_name(data):
if not isinstance(data, dict):
return
name = data.get('name', '')
token = data.get('token')
if not isinstance(name, str) or (token is not None and not isinstance(token, str)):
return
if not valid_name(name):
emit('claim_error', 'نام باید بین ۱ تا ۳۰ کاراکتر باشد.')
return
if token:
known = Name.query.filter_by(token_hash=hash_token(token)).first()
if known:
connected_users[request.sid] = known.name
emit('name_ok', {'name': known.name, 'token': None})
send_history(request.sid)
socketio.emit('online_count', len(connected_users))
return
if Name.query.filter_by(name=name).first():
emit('claim_error', 'این نام قبلاً استفاده شده. یک نام دیگر انتخاب کنید.')
return
new_token = secrets.token_urlsafe(32)
db.session.add(Name(name=name, token_hash=hash_token(new_token)))
db.session.commit()
connected_users[request.sid] = name
emit('name_ok', {'name': name, 'token': new_token})
send_history(request.sid)
socketio.emit('online_count', len(connected_users))
@socketio.on('connect')
def handle_connect():
emit('online_count', len(connected_users))
@socketio.on('disconnect')
def handle_disconnect():
connected_users.pop(request.sid, None)
user_times.pop(request.sid, None)
socketio.emit('online_count', len(connected_users))
@socketio.on('msg')
def handle_msg(data):
name = connected_users.get(request.sid, 'ناشناس')
if not isinstance(data, str) or len(data) > MAX_LEN:
return
data = data.strip()
if not data:
return
if rate_limited(request.sid):
emit('rate_limit', 'شما بیش از حد سریع پیام میفرستید. کمی صبر کنید.')
return
new_msg = Message(text=data, user_id=name)
db.session.add(new_msg)
db.session.commit()
emit('msg', {
'text': data,
'time': new_msg.time.strftime('%H:%M'),
'user_id': name
}, broadcast=True)
if __name__ == '__main__':
socketio.run(app, debug=True)