Skip to content

Commit 43877bd

Browse files
author
root
committed
Basic flood control
1 parent 2d8b71b commit 43877bd

File tree

3 files changed

+36
-14
lines changed

3 files changed

+36
-14
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ A: Use `<ENTER>` to send messages. Use `<TAB>` to autocomplete usernames, exampl
6262
Q: How to find the date and time of the messages?
6363
A: Hover over the messages, and a tooltip will show the date and time.
6464

65+
Q: Is there a flood control feature?
66+
A: The chat has a very basic flood control: a user cannot send more than 10 messages in 5 seconds.
67+
6568
Q: Is there a way to prevent a particular username from being used by anyone except me?
6669
A: The username `admin` is available *if and only if* the username `adminxyz` is entered in the input box. Change `adminxyz` to a private password in the beginning of `talktalktalk.py`, and as a result *noone else than you will be able to use the username `admin`.*
6770

talktalktalk.html

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
var users;
5959
var firstId;
6060
var lastId;
61+
var loop;
6162

6263
function is_scrolled_end() { // See http://stackoverflow.com/a/40370876/1422096
6364
return ((window.innerHeight + window.pageYOffset) >= document.body.offsetHeight);
@@ -117,18 +118,15 @@
117118
$('#notifications').hide();
118119
}
119120

120-
function disconnected_message () {
121-
$('#popup').text('Click anywhere or press any key to reload the page.').removeClass('hidden');
121+
function popup_message (message, clicktoclosepopup, clicktoreload) {
122+
$('#popup').text(message).removeClass('hidden');
122123
$(':not(.popup)').addClass('opaque');
123-
$(document).on('click keydown', function() { $(document).off('click keydown'); $(document).off('keydown'); ws.close(); ready(); })
124+
if (clicktoclosepopup)
125+
$(document).on('click keydown', function() { $(document).off('click keydown'); $('#popup').addClass('hidden'); $(':not(#popup)').removeClass('opaque'); });
126+
else if (clicktoreload)
127+
$(document).on('click keydown', function() { $(document).off('click keydown'); $(document).off('keydown'); ws.close(); ready(); });
124128
}
125129

126-
function usernamechanged_message () {
127-
$('#popup').text('Your username has been changed because the one you entered is reserved.').removeClass('hidden');
128-
$(':not(.popup)').addClass('opaque');
129-
$(document).on('click keydown', function() { $(document).off('click keydown'); $('#popup').addClass('hidden'); $(':not(#popup)').removeClass('opaque'); })
130-
}
131-
132130
if (!window.WebSocket) {
133131
$('#popup').text('Your browser is not supported, please use another browser.').removeClass('hidden');
134132
$('#writing').prop('disabled', true);
@@ -164,17 +162,17 @@
164162
ws.onclose = function() {
165163
setTimeout(function() {
166164
if (ws.readyState != 0 && ws.readyState != 1)
167-
disconnected_message();
165+
popup_message('Click anywhere or press any key to reload the page.', false, true);
168166
}, 2000);
169167
};
170168

171-
setInterval(function() {
169+
loop = setInterval(function() {
172170
ws.send('ping');
173171
if (Date.now() - lastPong > 10000) { // disconnected from internet or phone idle mode; in both cases, ws.readyState can be 1 (OPEN) so we can't use that
174172
ws.send('ping'); // this is your last chance ! we send a packet now, and if in 1 second, nothings happen, this means we're dead
175173
setTimeout( function() {
176174
if (Date.now() - lastPong > 10000) // you missed your last chance !
177-
disconnected_message(); // disconnected from internet
175+
popup_message('Click anywhere or press any key to reload the page.', false, true); // disconnected from internet
178176
else // ok the phone was just idle, let's resume
179177
ws.send(JSON.stringify({ "type" : "username", "username" : $('#username').text() })); // let's send username again, so that other users see me
180178
}, 1000);
@@ -245,7 +243,13 @@
245243
else if (data['type'] === 'usernameunavailable') {
246244
$('#username').text(data['username']);
247245
localStorage.setItem("username", $('#username').text());
248-
usernamechanged_message();
246+
popup_message('Your username has been changed because the one you entered is reserved.', true, false);
247+
}
248+
else if (data['type'] === 'flood') {
249+
popup_message('Please do not flood this chat.', false, false);
250+
ws.onclose = undefined;
251+
clearInterval(loop);
252+
ws.close();
249253
}
250254
else if (data['type'] === 'displayeduser') {
251255
displayeduser = data['username'];
@@ -266,7 +270,8 @@
266270

267271
$('#writing').keydown(function(e) {
268272
if (e.keyCode == 13 && !e.shiftKey) {
269-
ws.send(JSON.stringify({ type: 'message', username: $('#username').text(), message: $('#writing').val().trim() }));
273+
if ($('#writing').val().trim() !== '')
274+
ws.send(JSON.stringify({ type: 'message', username: $('#username').text(), message: $('#writing').val().trim() }));
270275
$('#writing').val('');
271276
e.preventDefault();
272277
}

talktalktalk.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from gevent import pywsgi
1717
from geventwebsocket.handler import WebSocketHandler
1818
from geventwebsocket.exceptions import WebSocketError
19+
from collections import deque
1920
from config import PORT, HOST, ADMINNAME, ADMINHIDDENNAME, ALLOWEDTAGS
2021

2122
idx = 0
@@ -37,6 +38,7 @@ def main():
3738

3839
users = {}
3940
pings = {}
41+
usermessagetimes = {}
4042

4143
def send_userlist():
4244
for u in users.keys():
@@ -76,20 +78,32 @@ def dbworker(): # when a user disappears during more than 30 seconds (+/-
7678
@get('/ws', apply=[websocket])
7779
def chat(ws):
7880
global idx
81+
usermessagetimes[ws] = deque(maxlen=10)
7982
while True:
8083
try:
8184
receivedmsg = ws.receive()
8285
if receivedmsg is not None:
86+
8387
receivedmsg = receivedmsg.decode('utf8')
8488
if len(receivedmsg) > 4096: # this user is probably a spammer
89+
ws.send(json.dumps({'type' : 'flood'}))
8590
break
91+
8692
pings[ws] = time.time()
93+
8794
if receivedmsg == 'ping': # ping/pong packet to make sure connection is still alive
8895
ws.send('id' + str(idx-1)) # send the latest message id in return
8996
if ws not in users: # was deleted by dbworker
9097
ws.send(json.dumps({'type' : 'username'}))
9198
else:
99+
usermessagetimes[ws].append(time.time()) # flood control
100+
if len(usermessagetimes[ws]) == usermessagetimes[ws].maxlen:
101+
if usermessagetimes[ws][-1] - usermessagetimes[ws][0] < 5: # if more than 10 messages in 5 seconds (including ping messages)
102+
ws.send(json.dumps({'type' : 'flood'})) # disconnect the spammer
103+
break
104+
92105
msg = json.loads(receivedmsg)
106+
93107
if msg['type'] == 'message':
94108
message = (bleach.clean(msg['message'], tags=ALLOWEDTAGS, strip=True)).strip()
95109

0 commit comments

Comments
 (0)