Skip to content

Commit

Permalink
Many little improvements: admin user, no malicious unicode in usernam…
Browse files Browse the repository at this point in the history
…e, etc.
  • Loading branch information
josephernest committed Nov 8, 2016
1 parent c45ff53 commit 6a5caad
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 28 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ FAQ
Q: How to send messages? Is there a way to get username autocompletion like in IRC?
A: Use `<ENTER>` to send messages. Use `<TAB>` to autocomplete usernames, example: `us` + `<TAB>` will give `user1`, then `user2`, etc. It also works with `@<username>`.

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`.**

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.

Expand Down
43 changes: 32 additions & 11 deletions talktalktalk.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,16 @@
#popup { z-index: 10; position: fixed; left: 20%; width:60%; top:40%; border: solid black 1px; background-color: white; text-align: center; padding: 30px 10px 30px 10px; color: black !important; }
.hidden { display: none !important; }
.opaque { color: #AAA; }
@media (max-width: 500px) { #right { width: 120px; left: calc(100% - 120px); } #wrapper { width: calc(100% - 130px); }
}
@media (max-width: 500px) { #right { width: 120px; left: calc(100% - 120px); } #wrapper { width: calc(100% - 130px); } #banner { display: none; } }
</style>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
</head>
<body>
<a id="banner" href="https://github.com/josephernest/talktalktalk"><img style="position: fixed; top: 0; right: 0; border: 0; width: 115px;" src="https://camo.githubusercontent.com/a6677b08c955af8400f44c6298f40e7d19cc5b2d/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f677261795f3664366436642e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_gray_6d6d6d.png"></a>
<div id="right">
TALKTALKTALK<br>############<br><br>
TALKTALKTALK<br>############<br><br><br>
<div id="notifications">NOTIFICATIONS<br><span id="notificationswitch" class="dashed">on</span><br><br></div>
USERNAME<br><span id="username" class="dashed">user1</span><input value="user1" id="usernameinput" maxlength="16" /><br><br>
USERNAME<br><span id="username" class="dashed">user</span><input value="user1" id="usernameinput" maxlength="16" /><br><br>
ONLINE USERS<div id="connected"></div>
</div>
<div id="wrapper">
Expand All @@ -49,6 +49,7 @@
<script type="text/javascript">
var popsound = new Audio('popsound.mp3');
var randomname = 'user' + Math.floor((Math.random() * 1000) + 1);
var displayeduser;
var ws;
var lastPong;
var users;
Expand All @@ -70,7 +71,7 @@
}

function add_urls(s) {
return s.replace(/(https?:\/\/[^ ]+)/g, '<a href="$1">$1</a>');
return s.replace(/(https?:\/\/[^ ]+)/g, '<a href="$1" target="_blank">$1</a>');
}

function timeConverter(t) { // http://stackoverflow.com/a/40449006/1422096
Expand Down Expand Up @@ -114,11 +115,17 @@
}

function disconnected_message () {
$('#popup').text('Click anywhere to reload the page.').removeClass('hidden');
$('#popup').text('Click anywhere or press any key to reload the page.').removeClass('hidden');
$(':not(.popup)').addClass('opaque');
$(document).click(function() { $(document).off('click'); ws.close(); ready(); })
$(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 All @@ -129,8 +136,10 @@
$('#writing').val('');
}

if (localStorage.getItem("username") === null)
if (localStorage.getItem("username") === null || localStorage.getItem("username") === '') {
$('#username').text(randomname);
localStorage.setItem("username", $('#username').text());
}
else
$('#username').text(localStorage.getItem("username"));

Expand Down Expand Up @@ -194,7 +203,7 @@
$('#main').append('<p title="' + timeConverter(data['datetime']) + '"><b>' + data['username'] + '</b>: ' + add_urls(data['message']) + '</p>');
if (is_scrolled_end())
scroll_end();
if (data['username'] !== $('#username').text() && $('#notificationswitch').text() === 'on') {
if (data['username'] !== $('#username').text() && data['username'] !== displayeduser && $('#notificationswitch').text() === 'on') {
popsound.load(); popsound.play();
}
}
Expand Down Expand Up @@ -222,10 +231,21 @@
else
scroll_end();
}
else if (data['type'] === 'userlist') {
else if (data['type'] === 'userlist') { // server sends the userlist to clients
users = data['connected'];
$('#connected').html(users.join('<br>')); // or .map(x => `${x}<br>`).join('')
}
else if (data['type'] === 'username') { // server asks to (re)send username
ws.send(JSON.stringify({ "type" : "username", "username" : $('#username').text() }));
}
else if (data['type'] === 'usernameunavailable') {
$('#username').text(data['username']);
localStorage.setItem("username", $('#username').text());
usernamechanged_message();
}
else if (data['type'] === 'displayeduser') {
displayeduser = data['username'];
}
};

$('#username').on('click', function(e) {
Expand Down Expand Up @@ -299,5 +319,6 @@
$(document).ready(ready);

</script>

</body>
</html>
</html>
55 changes: 38 additions & 17 deletions talktalktalk.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@
# license: MIT license


import sys, time, json, bleach, time, threading, dumbdbm
PORT = 9000
HOST = "127.0.0.1"
ADMINNAME = 'admin' # this username will be available if *and only if* the following username is entered in the input field:
ADMINHIDDENNAME = 'adminxyz'

import sys, time, json, bleach, time, threading, dumbdbm, random, re
import daemon
from bottle import route, run, view, request, post, ServerAdapter, get, static_file
from gevent import pywsgi
Expand Down Expand Up @@ -40,6 +45,17 @@ def send_userlist():
for u in users.keys():
u.send(json.dumps({'type' : 'userlist', 'connected': users.values()}))

def clean_username(usr, ws):
username = bleach.clean(usr).encode('utf8')
username = re.sub('[‍ :]', '', username) # removes " ", ":", and this evil char http://unicode-table.com/fr/200D/
if username.lower() == ADMINNAME or username == '':
username = 'user' + str(random.randint(0,1000))
ws.send(json.dumps({'type' : 'usernameunavailable', 'username' : username}))
elif username.lower() == ADMINHIDDENNAME:
username = ADMINNAME
ws.send(json.dumps({'type' : 'displayeduser', 'username' : username}))
return username

def dbworker(): # when a user disappears during more than 30 seconds (+/- 10), remove him/her from the userlist
while True:
userlistchanged = False
Expand All @@ -51,7 +67,6 @@ def dbworker(): # when a user disappears during more than 30 seconds (+/-
userlistchanged = True
if userlistchanged:
send_userlist()

time.sleep(10)

dbworkerThread = threading.Thread(target=dbworker)
Expand All @@ -69,33 +84,39 @@ def chat(ws):
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:
users[ws] = username
send_userlist()
if ws not in users: # was deleted by dbworker
ws.send(json.dumps({'type' : 'username'}))
else:
msg = json.loads(receivedmsg)
if msg['type'] == 'message':
message = (bleach.clean(msg['message'])).strip().encode('utf8')
username = (bleach.clean(msg['username'])).strip().encode('utf8')
if message and username:
s = json.dumps({'type' : 'message', 'message': message, 'username': username, 'id': idx, 'datetime': int(time.time())})

if ws not in users: # is this really mandatory ?
username = clean_username(msg['username'], ws)
users[ws] = username
send_userlist()

if message:
s = json.dumps({'type' : 'message', 'message': message, 'username': users[ws], 'id': idx, 'datetime': int(time.time())})
db[str(idx)] = s # Neither dumbdbm nor shelve module allow integer as key... I'm still looking for a better solution!
idx += 1
for u in users.keys():
u.send(s)

elif msg['type'] == 'messagesbefore':
idbefore = msg['id']
ws.send(json.dumps({'type' : 'messages', 'before': 1, 'messages': [db[str(i)] for i in range(max(0,idbefore - 100),idbefore)]}))

elif msg['type'] == 'messagesafter':
idafter = msg['id']
ws.send(json.dumps({'type' : 'messages', 'before': 0, 'messages': [db[str(i)] for i in range(idafter,idx)]}))

elif msg['type'] == 'username':
username = (bleach.clean(msg['username'])).strip().encode('utf8')
if username:
if ws not in users: # welcome new user
ws.send(json.dumps({'type' : 'messages', 'before': 0, 'messages': [db[str(i)] for i in range(max(0,idx - 100),idx)]}))
users[ws] = username
send_userlist()
username = clean_username(msg['username'], ws)
if ws not in users: # welcome new user
ws.send(json.dumps({'type' : 'messages', 'before': 0, 'messages': [db[str(i)] for i in range(max(0,idx - 100),idx)]}))
users[ws] = username
send_userlist()
else:
break
if ws in users:
Expand All @@ -114,7 +135,7 @@ def index():
def popsound():
return static_file('popsound.mp3', root='.')

run(host="127.0.0.1", port=9000, debug=True, server=GeventWebSocketServer)
run(host=HOST, port=PORT, debug=True, server=GeventWebSocketServer)

class talktalktalk(daemon.Daemon):
def run(self):
Expand All @@ -124,11 +145,11 @@ def run(self):
main()

elif len(sys.argv) == 2: # daemon mode
daemon = talktalktalk(pidfile='_.pid')
daemon = talktalktalk(pidfile='_.pid', stdout='log.txt', stderr='log.txt')

if 'start' == sys.argv[1]:
daemon.start()
elif 'stop' == sys.argv[1]:
daemon.stop()
elif 'restart' == sys.argv[1]:
daemon.restart()
daemon.restart()

0 comments on commit 6a5caad

Please sign in to comment.