summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/site.clj43
-rwxr-xr-xstatic/pichat.js71
2 files changed, 80 insertions, 34 deletions
diff --git a/src/site.clj b/src/site.clj
index f2e5626..cce5da1 100644
--- a/src/site.clj
+++ b/src/site.clj
@@ -8,9 +8,30 @@
(defstruct user-struct :nick :last-seen)
(defstruct message-struct :nick :content :timestamp)
+
(def users (ref {}))
(def messages (ref []))
+(def run-flusher true)
+(def flusher-sleep-ms 4000)
+(def user-timeout-ms 30000)
+
+(defn swap [f]
+ (fn [& more] (apply f (reverse more))))
+
+(def flusher (agent nil))
+
+(defn flush! [x]
+ (when run-flusher
+ (send-off *agent* #'flush!))
+ (dosync
+ (let [now (System/currentTimeMillis)
+ alive? (fn [[n u]] (> (u :last-seen) (- now user-timeout-ms)))]
+ (ref-set users
+ (into {} (filter alive? @users)))))
+ (. Thread (sleep flusher-sleep-ms))
+ x)
+
(defn resp-error [message]
{:status 400 :headers {} :body message})
@@ -19,14 +40,14 @@
(defn join-success [nick]
(alter users assoc nick (struct user-struct nick (System/currentTimeMillis)))
- (let [users (keys @users)
- messages (take 20 @messages)
+ (let [users (sort (keys @users))
+ messages (reverse (take 40 @messages))
data {"users" users "messages" messages}]
[(session-assoc :nick nick)
(resp-success data)]))
(defn try-join [params]
- (let [nick (params :nick)]
+ (let [nick (escape-html (params :nick))]
(dosync
(if (contains? @users nick)
(resp-error "NICK_TAKEN")
@@ -37,17 +58,18 @@
(defn refresh [nick]
(dosync
- (let [last-seen (get-in @users [nick :last-seen])]
- (alter users assoc-in [nick :last-seen] (System/currentTimeMillis))
- (resp-success {"messages" (new-messages last-seen)}))))
-
-(defn swap [f]
- (fn [& more] (apply f (reverse more))))
+ (if (contains? @users nick)
+ (let [last-seen (get-in @users [nick :last-seen])
+ user-list (sort (keys @users))]
+ (alter users assoc-in [nick :last-seen] (System/currentTimeMillis))
+ (resp-success {"messages" (new-messages last-seen)
+ "users" user-list}))
+ (resp-error "UNKNOWN_USER"))))
(defn msg [session params]
(dosync
(let [nick (session :nick)
- content (params :content)
+ content (escape-html (params :content))
msg (struct message-struct nick content (System/currentTimeMillis))]
(if (contains? @users nick)
(do (alter messages (swap cons) msg)
@@ -70,3 +92,4 @@
(run-server {:port 8080}
"/*" (servlet pichat))
+(send-off flusher flush!) \ No newline at end of file
diff --git a/static/pichat.js b/static/pichat.js
index 99bf1b5..aeef1bd 100755
--- a/static/pichat.js
+++ b/static/pichat.js
@@ -51,28 +51,52 @@ function join() {
});
}
+function escapeHtml(txt) {
+ return $("<span>").text(txt).html();
+}
+
function buildUserDiv(user) {
- return '<div>' + user + '</div>';
+ return '<div>' + escapeHtml(user) + '</div>';
+}
+
+// http://rickyrosario.com/blog/converting-a-url-into-a-link-in-javascript-linkify-function
+function linkify(text){
+ if (text) {
+ text = text.replace(
+ /((https?\:\/\/)|(www\.))(\S+)(\w{2,4})(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/gi,
+ function(url){
+ var full_url = url;
+ if (!full_url.match('^https?:\/\/')) {
+ full_url = 'http://' + full_url;
+ }
+ return '<a href="' + full_url + '" target="_blank">' + url + '</a>';
+ }
+ );
+ }
+ return text;
}
// http://snippets.dzone.com/posts/show/6995
var URLRegex = /((http\:\/\/|https\:\/\/|ftp\:\/\/)|(www\.))+(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/i;
+var PicRegex = /\.(jpg|jpeg|png|gif|bmp)$/i;
function buildMessageDiv(msg) {
- var match = URLRegex.exec(msg.content)
- if (match) {
- return '<div class="msgDev"><b>' + msg.nick + ': </b>'
- + '<a href="' + match[0] + '" target="_blank">'
- + '<img height="150" width="150" src="'
- + match[0] + '" /></a></div>';
- } else {
- return '<div class="msgDiv"><b>' + msg.nick + ": </b>"
- + msg.content + "</div>";
- }
+ function buildContent(content) {
+ var match = URLRegex.exec(content)
+ if (match && PicRegex.test(match[0])) {
+ return '<a href="' + match[0] + '" target="_blank">'
+ + '<img height="150" width="150" src="'
+ + match[0] + '" /></a>';
+ } else {
+ return linkify(escapeHtml(msg.content));
+ }
+ }
+ return '<div class="msgDev"><b>' + escapeHtml(msg.nick) + ': </b>'
+ + buildContent(msg.content) + '</div>';
}
function buildChatInterface(users, messages) {
- var userList = '<div id="userlist">'
+ var userList = '<div id="userList">'
+ $.map(users, buildUserDiv).join('') + '</div>';
var messageList = '<div id="messagePane">'
+ '<div id="messageList">'
@@ -133,21 +157,20 @@ function scrollToBottom(div) {
function refresh() {
var onSuccess = function(json) {
- if (json.messages.length == 0) {
- return;
- }
-
- var shouldScroll = isScrolledToBottom($('#messageList')[0]);
+ if (json.messages.length > 0) {
+ var shouldScroll = isScrolledToBottom($('#messageList')[0]);
- // Ignore our own messages
- var filterFunc = function(m) { return m.nick != Nick };
- var msgStr = $.map($.grep(json.messages, filterFunc),
- buildMessageDiv).join('');
- $('#messageList').append(msgStr);
+ // Ignore our own messages
+ var filterFunc = function(m) { return m.nick != Nick };
+ var msgStr = $.map($.grep(json.messages, filterFunc),
+ buildMessageDiv).join('');
+ $('#messageList').append(msgStr);
- if (shouldScroll) {
- scrollToBottom($('#messageList')[0]);
+ if (shouldScroll) {
+ scrollToBottom($('#messageList')[0]);
+ }
}
+ $("#userList").html($.map(json.users, buildUserDiv).join(''));
};
var onError = function(resp, textStatus, errorThrown) {};