Skip to content

Commit

Permalink
Basic flood control
Browse files Browse the repository at this point in the history
  • Loading branch information
root committed Nov 25, 2016
1 parent 2d8b71b commit 43877bd
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 14 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ A: Use `<ENTER>` to send messages. Use `<TAB>` to autocomplete usernames, exampl
Q: How to find the date and time of the messages?
A: Hover over the messages, and a tooltip will show the date and time.

Q: Is there a flood control feature?
A: The chat has a very basic flood control: a user cannot send more than 10 messages in 5 seconds.

Q: Is there a way to prevent a particular username from being used by anyone except me?
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`.*

Expand Down
33 changes: 19 additions & 14 deletions talktalktalk.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
var users;
var firstId;
var lastId;
var loop;

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

function disconnected_message () {
$('#popup').text('Click anywhere or press any key to reload the page.').removeClass('hidden');
function popup_message (message, clicktoclosepopup, clicktoreload) {
$('#popup').text(message).removeClass('hidden');
$(':not(.popup)').addClass('opaque');
$(document).on('click keydown', function() { $(document).off('click keydown'); $(document).off('keydown'); ws.close(); ready(); })
if (clicktoclosepopup)
$(document).on('click keydown', function() { $(document).off('click keydown'); $('#popup').addClass('hidden'); $(':not(#popup)').removeClass('opaque'); });
else if (clicktoreload)
$(document).on('click keydown', function() { $(document).off('click keydown'); $(document).off('keydown'); ws.close(); ready(); });
}

function usernamechanged_message () {
$('#popup').text('Your username has been changed because the one you entered is reserved.').removeClass('hidden');
$(':not(.popup)').addClass('opaque');
$(document).on('click keydown', function() { $(document).off('click keydown'); $('#popup').addClass('hidden'); $(':not(#popup)').removeClass('opaque'); })
}

if (!window.WebSocket) {
$('#popup').text('Your browser is not supported, please use another browser.').removeClass('hidden');
$('#writing').prop('disabled', true);
Expand Down Expand Up @@ -164,17 +162,17 @@
ws.onclose = function() {
setTimeout(function() {
if (ws.readyState != 0 && ws.readyState != 1)
disconnected_message();
popup_message('Click anywhere or press any key to reload the page.', false, true);
}, 2000);
};

setInterval(function() {
loop = setInterval(function() {
ws.send('ping');
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
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
setTimeout( function() {
if (Date.now() - lastPong > 10000) // you missed your last chance !
disconnected_message(); // disconnected from internet
popup_message('Click anywhere or press any key to reload the page.', false, true); // disconnected from internet
else // ok the phone was just idle, let's resume
ws.send(JSON.stringify({ "type" : "username", "username" : $('#username').text() })); // let's send username again, so that other users see me
}, 1000);
Expand Down Expand Up @@ -245,7 +243,13 @@
else if (data['type'] === 'usernameunavailable') {
$('#username').text(data['username']);
localStorage.setItem("username", $('#username').text());
usernamechanged_message();
popup_message('Your username has been changed because the one you entered is reserved.', true, false);
}
else if (data['type'] === 'flood') {
popup_message('Please do not flood this chat.', false, false);
ws.onclose = undefined;
clearInterval(loop);
ws.close();
}
else if (data['type'] === 'displayeduser') {
displayeduser = data['username'];
Expand All @@ -266,7 +270,8 @@

$('#writing').keydown(function(e) {
if (e.keyCode == 13 && !e.shiftKey) {
ws.send(JSON.stringify({ type: 'message', username: $('#username').text(), message: $('#writing').val().trim() }));
if ($('#writing').val().trim() !== '')
ws.send(JSON.stringify({ type: 'message', username: $('#username').text(), message: $('#writing').val().trim() }));
$('#writing').val('');
e.preventDefault();
}
Expand Down
14 changes: 14 additions & 0 deletions talktalktalk.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from gevent import pywsgi
from geventwebsocket.handler import WebSocketHandler
from geventwebsocket.exceptions import WebSocketError
from collections import deque
from config import PORT, HOST, ADMINNAME, ADMINHIDDENNAME, ALLOWEDTAGS

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

users = {}
pings = {}
usermessagetimes = {}

def send_userlist():
for u in users.keys():
Expand Down Expand Up @@ -76,20 +78,32 @@ def dbworker(): # when a user disappears during more than 30 seconds (+/-
@get('/ws', apply=[websocket])
def chat(ws):
global idx
usermessagetimes[ws] = deque(maxlen=10)
while True:
try:
receivedmsg = ws.receive()
if receivedmsg is not None:

receivedmsg = receivedmsg.decode('utf8')
if len(receivedmsg) > 4096: # this user is probably a spammer
ws.send(json.dumps({'type' : 'flood'}))
break

pings[ws] = time.time()

if receivedmsg == 'ping': # ping/pong packet to make sure connection is still alive
ws.send('id' + str(idx-1)) # send the latest message id in return
if ws not in users: # was deleted by dbworker
ws.send(json.dumps({'type' : 'username'}))
else:
usermessagetimes[ws].append(time.time()) # flood control
if len(usermessagetimes[ws]) == usermessagetimes[ws].maxlen:
if usermessagetimes[ws][-1] - usermessagetimes[ws][0] < 5: # if more than 10 messages in 5 seconds (including ping messages)
ws.send(json.dumps({'type' : 'flood'})) # disconnect the spammer
break

msg = json.loads(receivedmsg)

if msg['type'] == 'message':
message = (bleach.clean(msg['message'], tags=ALLOWEDTAGS, strip=True)).strip()

Expand Down

0 comments on commit 43877bd

Please sign in to comment.