User:Jonas Lund/tri1.2: Difference between revisions

From XPUB & Lens-Based wiki
(Created page with "=Let's Talk About Pictures (LTAP)= The LTAP is a collaboration with Louis Doulas and Harry Burke, to create a website that facilitates and encourages a one to one discussion abou...")
 
 
(3 intermediate revisions by the same user not shown)
Line 1: Line 1:
=Let's Talk About Pictures (LTAP)=
=Let's Talk About Pictures (LTAP)=
The LTAP is a collaboration with Louis Doulas and Harry Burke, to create a website that facilitates and encourages a one to one discussion about pictures. In LTAP, each user is initially presented with an upload form which accepts images. The user is required to upload a picture of their choosing and pick a nickname to enter the discussion, the chat room. The chat room displays your discussion partner's uploaded picture to the left and the chat box to the right, and the conversation can begin.
LTAP is a collaboration with Louis Doulas and Harry Burke, to create a website that facilitates and encourages a one to one discussion about pictures. In LTAP, each user is initially presented with an upload form which accepts images. The user is required to upload a picture of their choosing and pick a nickname to enter the discussion, the chat room. The chat room displays a random picture from the database of images and the conversation can begin.


=LTAP Development=
=LTAP Development=
Line 48: Line 48:
server.js
server.js
<source lang="javascript">
<source lang="javascript">
  //initiate database
//db
  var mysql = require('mysql');
var mysql = require('mysql');
  var db = 'ltap';
var db = "ltappiting";
  var chats = 'chats';
var chats = 'chats';
  var chatLogs = 'chat_logs'
var chatLogs = 'chat_logs';
  var client = mysql.createClient({
var client = mysql.createClient({
    host: '127.0.0.1',
  host: "localhost",
    user: 'root',
  user: "root",
    password: 'root',
  password: "root"
    port: 8889
});
  });
 
  //includes
  var http = require("http");
  var url = require("url");
  var fs = require("fs");
  var util = require('util');
  var formidable = require('formidable');
  var exec = require('child_process').exec;
  var fu = exports;


  function onRequest(req, res) {
//server
    var pathname = url.parse(req.url).pathname;
var http = require("http");
    console.log("Request for " + pathname + " received.");
var url = require("url");
var fs = require("fs");
var util = require('util');
var formidable = require('formidable');
var exec = require('child_process').exec;
var fu = exports;


    //routing
function onRequest(req, res) {
    switch (pathname) {
  var pathname = url.parse(req.url).pathname;
        case '/':
  console.log("Request for " + pathname + " received.");
        req.setEncoding("utf8");
            var html = fs.readFileSync(__dirname+'/index.html');
            res.end(html);
        break;
        //handle file upload
        case '/upload':
            uploadFile(req, res);
        break;
          default:
            //check if file exits, otherwise return 404
            req.setEncoding("utf8");
            var content_type = fu.mime.lookupExtension(extname(pathname));


              fs.readFile(__dirname+"/"+pathname, function(err, data) {  
  //routing
                console.log(pathname);
  switch (pathname) {
                if(err) {
      case '/':
                  res.writeHead(404, {"Content-Type": "text/plain"});
      req.setEncoding("utf8");
                  res.write("404 Not found");
          var html = fs.readFileSync(__dirname+'/index.html');
                  res.end();
          res.end(html);
                } else {
      break;
                  headers = { "Content-Type": content_type
      case '/upload':
                            , "Content-Length": data.length
          uploadFile(req, res);
                            };
      break;
        default:
          req.setEncoding("utf8");
          var content_type = fu.mime.lookupExtension(extname(pathname));
                                 
            fs.readFile(__dirname+"/"+pathname, function(err, data) {  
              if(err) {
                res.writeHead(404, {"Content-Type": "text/plain"});
                res.write("404 Not found");
                res.end();
              } else {
                headers = { "Content-Type": content_type
                          , "Content-Length": data.length
                          };
               
                res.writeHead(200, headers);
                res.write(data);
                res.end();               
              }
            });
      break;
    }
}
var server = http.createServer(onRequest).listen(1337);
console.log("server running on 1337");


                  res.writeHead(200, headers);
var nowjs = require("now");
                  res.write(data);
var everyone = nowjs.initialize(server);
                  res.end();               
                }
              });
        break;
      }
  }
  var server = http.createServer(onRequest).listen(1337);
  console.log("server running on 1337");


  var nowjs = require("now");
//Session Variables
  var everyone = nowjs.initialize(server);
var sessions = {};
var rooms = [];
rooms[0] = 0;


  //Session Variables
// I'm working on a 1-1 chat client with nodejs and nowjs, and I'm wondering about the best practice for queuing/assigning rooms (groups) to the users, given that it should only be a 1 to 1 conversation, and the one who's been waiting the longest should be assigned first'. Any ideas? :)
  var sessions = {};
  var rooms = [];
  rooms[0] = 0;


  //Send message to everyone on the users group
//Send message to everyone on the users group
  everyone.now.distributeMessage = function(message){
everyone.now.distributeMessage = function(message){
      console.log('Received '+message+' from '+this.now.nick +' in serverroom '+this.now.room);
    console.log('Received '+message+' from '+this.now.nick +' in serverroom '+this.now.room);
      var group = nowjs.getGroup(this.now.room);     
    var group = nowjs.getGroup(this.now.room);     
      group.now.receiveMessage(this.now.id, this.now.nick, message);
    group.now.receiveMessage(this.now.id, this.now.nick, message);
     
   
      var date = new Date();
    var date = new Date();
    //insert into DB
    client.query('USE '+db);
    client.query(
      'INSERT INTO '+chatLogs+' '+
      'SET user = ?, text = ?, date = ?',
      [this.user.clientId, message, date]
    ); 


      //insert into DB
};
      client.query('USE '+db);
      client.query(
        'INSERT INTO '+chatLogs+' '+
        'SET user = ?, text = ?, date = ?',
        [this.user.clientId, message, date]
      ); 


  };
//function to be called from client
 
everyone.now.join = function() {         
  //Join action, called from client
    var userID = this.user.clientId;
  everyone.now.join = function() {         
    var nick = this.now.nick;   
      var userID = this.user.clientId;
    var image = this.now.image;
      var nick = this.now.nick;   
    var id = this.now.id;
      var image = this.now.image;
   
      var id = this.now.id;
    //get available room
 
    var room = getRoom();
      //get available room
    this.now.room = room;
      var room = getRoom();
   
      this.now.room = room;
       
     
      //create user object
       var user = {
       var user = {
         id: userID,
         id: userID,
         nick: nick,
         nick: nick,
         room: room,
         room: room,
         image: image
         image: image      
         //timestamp: new Date()       
         //timestamp: new Date()       
       };
       };
 
               
       //add user to room
       //add user to room
       nowjs.getGroup(room).addUser(userID);
       nowjs.getGroup(room).addUser(userID);
 
 
       //add user to session
       //add user to session
       sessions[user.id] = user;
       sessions[user.id] = user;
       var group = nowjs.getGroup(this.now.room);
       var group = nowjs.getGroup(this.now.room);
 
         
       if(rooms[room] == 2) {
       if(rooms[room] == 2) {
         var status = "Partner Joined";   
         var status = "Buddy Joined, say Hi!";
 
    
         //get-users, add to db
         //get-users, add to db
         nowjs.getGroup(room).getUsers(function (users) {  
         nowjs.getGroup(room).getUsers(function (users) {  
Line 177: Line 174:
           var date = new Date();
           var date = new Date();
            
            
           //send image to the client
           //var randNumber = Math.floor(Math.random()*11) + 11;
          group.now.updateImage(image1,image2);
 
          //insert into DB
           client.query('USE '+db);
           client.query('USE '+db);
           client.query(
           client.query(
             'INSERT INTO '+chats+' '+
             //'SELECT image1, id FROM '+chats + ' WHERE id=' + randNumber,
            'SET user1 = ?, user2 = ?, nick1 = ?, nick2 = ?, image1 = ?, image2 = ?, date = ?',
            'SELECT image1, id FROM '+chats + ' ORDER BY rand() LIMIT 1',
            [user1, user2, nick1, nick2, image1, image2, date]
            function selectCb(err, results, fields) {
           );
              if (err) {
                throw err;
              }
         
            var chatImage = results[0].image1;
            client.end();               
           
            console.log(chatImage);
           
            group.now.updateImage(chatImage);
                   
            //insert into DB
            client.query('USE '+db);
            client.query(
              'INSERT INTO '+chats+' '+
              'SET user1 = ?, user2 = ?, nick1 = ?, nick2 = ?, image1 = ?, image2 = ?, chatimage = ?, date = ?',
              [user1, user2, nick1, nick2, image1, image2, chatImage, date]
            );
           });  
         });
         });
 
 
       } else {
       } else {
         var status = "Finding chat buddy..";
         var status = "Finding chat buddy..";
       }   
       }   
       group.now.chatStatus(status);
       group.now.chatStatus(status);
  }
    
    
  //User Disconnect, remove from sessions
}
  nowjs.on('disconnect', function () {
    var userroom = this.now.room;
    var nick = this.now.nick;
    var id = this.now.id;
    var userID = this.user.clientId;
   
    //check if user sticks around for a new chat buddy
    if(rooms[userroom] == 2) {
      rooms[userroom] = 1;
      var status = "Partner Left.. ;( – Looking for a new one...";
    } else {
      rooms[userroom] = 0;
    }


    //remove user
everyone.now.isTyping = function(type){
    if (userID && sessions[id]) {
     console.log('Received '+type+' from '+this.now.id);
      delete sessions[userID];
    }
     var group = nowjs.getGroup(this.now.room);
    group.now.chatStatus(status);


     console.log("user:", this.now.id, this.now.nick, " left");
     var group = nowjs.getGroup(this.now.room);   
  });
    group.now.updateType(type, this.now.nick, this.now.id);
};


  //check room array for available room
nowjs.on('disconnect', function () {  
  function getRoom() {
  var userroom = this.now.room;
    for (var i=0; i < rooms.length; i++) {
  var nick = this.now.nick;
      if(rooms[i] === 1) {
  var id = this.now.id;
        rooms[i] = 2;    
  var userID = this.user.clientId;
        return i;
   
 
  if(rooms[userroom] == 2) {
      } else if (rooms[i] === 0) {
    rooms[userroom] = 1;
          rooms[i] = 1;
    var status = "Buddy Left.. – Looking for a new one...";
          return i;
  } else {
      }
     rooms[userroom] = 0;
     };
  }


    rooms.push(1);
  if (userID && sessions[id])
    return rooms.length-1;  
    delete sessions[userID];
   }
   }


   //handle image upload
   var group = nowjs.getGroup(this.now.room);
   function uploadFile(req, res) {
   group.now.chatStatus(status);
    var form = new formidable.IncomingForm(),
     
        files = [],
  console.log("user:", this.now.id, this.now.nick, " left");
        fields = [];
});
        fileob = {};


    form.uploadDir = __dirname + '/tmp';
function getRoom() { 
     form.encoding = 'binary';
  for (var i=0; i < rooms.length; i++) {
     if(rooms[i] === 1) {
      rooms[i] = 2;     
      return i;


     form
     } else if (rooms[i] === 0) {
      .on('field', function(field, value) {
         rooms[i] = 1;
         console.log(field, value);
         return i;
         fields.push([field, value]);
    }
      })
  };
      .on('file', function(field, file) {
 
        //console.log("on file", field, file);
  rooms.push(1);
        files.push([field, file]);
  return rooms.length-1;  
}


        //check size type, cancel if wrong                 
//upload
        var newfile = {
function uploadFile(req, res) {
            file: file.size,  
  var form = new formidable.IncomingForm(),
            name: file.name,
      files = [],
            path: file.path,
      fields = [];
            type: file.type
      fileob = {};
        };
     
  form.uploadDir = __dirname + '/tmp';
  form.encoding = 'binary';


        fileob = newfile;
  form
    .on('field', function(field, value) {
      console.log(field, value);
      fields.push([field, value]);
    })
    .on('file', function(field, file) {
      files.push([field, file]);


       })
       //check size type, cancel if wrong                 
      .on('end', function() {
      var newfile = {
        console.log('-> upload done');
          file: file.size,
        res.writeHead(200, {'content-type': 'text/plain'});
          name: file.name,
 
          path: file.path,
        //filename cleanup
          type: file.type
        var ext = extname(fileob.name);       
      };
        var uni = Math.floor(Math.random()*99999).toString();
                         
        var cleanFilename = fileob.name.replace(/[^a-z0-9]/gi, '_').toLowerCase();
      fileob = newfile;
        var filename = uni + cleanFilename + ext;
     
       
    })
        //return file name to client
    .on('end', function() {
        res.write(filename);
      console.log('-> upload done');
        res.end();       
      res.writeHead(200, {'content-type': 'text/plain'});
       
         
        //move file in place
      //move
        var cmd = "mv " + fileob.path + " /path/to/upload/folder/" + filename;
      var ext = extname(fileob.name);       
        console.log(cmd);
      var uni = Math.floor(Math.random()*99999).toString();
        exec(cmd, puts);
      var cleanFilename = fileob.name.replace(/[^a-z0-9]/gi, '_').toLowerCase();
 
      var filename = uni + cleanFilename + ext;
      });
           
  //  form.parse(req);
      res.write(filename);
 
      res.end();       
      form.parse(req, function(err, fields, files) {
           
        if (err) {
      var cmd = "mv " + fileob.path + " /home/jonasx/t2sp.net/upload/" + filename;
          console.log(err);
      console.log(cmd);
        }
      exec(cmd, puts);
      });
                 
  }
    });
  function puts(error, stdout, stderr) { util.puts(stdout) }
    form.parse(req, function(err, fields, files) {
      if (err) {
        console.log(err);
      }
    });
}
function puts(error, stdout, stderr) { util.puts(stdout) }
     
function include(arr,obj) {
    return (arr.indexOf(obj) != -1);
}


  function include(arr,obj) {
Array.prototype.remove = function(from, to) {
      return (arr.indexOf(obj) != -1);
  var rest = this.slice((to || from) + 1 || this.length);
   }
   this.length = from < 0 ? this.length + from : from;
  return this.push.apply(this, rest);
};


  Array.prototype.remove = function(from, to) {
function extname (path) {
    var rest = this.slice((to || from) + 1 || this.length);
  var index = path.lastIndexOf(".");
    this.length = from < 0 ? this.length + from : from;
  return index < 0 ? "" : path.substring(index);
    return this.push.apply(this, rest);
}
  };


   //get ext name
// stolen from node_chat, jack- thanks
   function extname (path) {
fu.mime = {
     var index = path.lastIndexOf(".");
   // returns MIME type for extension, or fallback, or octet-steam
    return index < 0 ? "" : path.substring(index);
   lookupExtension : function(ext, fallback) {
   }
     return fu.mime.TYPES[ext.toLowerCase()] || fallback || 'application/octet-stream';
   },


   //stolen from node_chat, jack- thanks
   // List of most common mime-types, stolen from Rack.
   fu.mime = {
  TYPES : { ".3gp"  : "video/3gpp"
     // returns MIME type for extension, or fallback, or octet-steam
          , ".a"    : "application/octet-stream"
     lookupExtension : function(ext, fallback) {
          , ".ai"    : "application/postscript"
      return fu.mime.TYPES[ext.toLowerCase()] || fallback || 'application/octet-stream';
          , ".aif"  : "audio/x-aiff"
     },
          , ".aiff"  : "audio/x-aiff"
          , ".asc"  : "application/pgp-signature"
          , ".asf"  : "video/x-ms-asf"
          , ".asm"  : "text/x-asm"
          , ".asx"  : "video/x-ms-asf"
          , ".atom"  : "application/atom+xml"
          , ".au"    : "audio/basic"
          , ".avi"  : "video/x-msvideo"
          , ".bat"   : "application/x-msdownload"
          , ".bin"  : "application/octet-stream"
          , ".bmp"  : "image/bmp"
          , ".bz2"  : "application/x-bzip2"
          , ".c"    : "text/x-c"
          , ".cab"  : "application/vnd.ms-cab-compressed"
          , ".cc"    : "text/x-c"
          , ".chm"  : "application/vnd.ms-htmlhelp"
          , ".class"  : "application/octet-stream"
          , ".com"  : "application/x-msdownload"
          , ".conf"  : "text/plain"
          , ".cpp"  : "text/x-c"
          , ".crt"  : "application/x-x509-ca-cert"
          , ".css"  : "text/css"
          , ".csv"  : "text/csv"
          , ".cxx"  : "text/x-c"
          , ".deb"  : "application/x-debian-package"
          , ".der"  : "application/x-x509-ca-cert"
          , ".diff"  : "text/x-diff"
          , ".djv"  : "image/vnd.djvu"
          , ".djvu"  : "image/vnd.djvu"
          , ".dll"  : "application/x-msdownload"
          , ".dmg"  : "application/octet-stream"
          , ".doc"  : "application/msword"
          , ".dot"  : "application/msword"
          , ".dtd"  : "application/xml-dtd"
          , ".dvi"  : "application/x-dvi"
          , ".ear"  : "application/java-archive"
          , ".eml"  : "message/rfc822"
          , ".eps"  : "application/postscript"
          , ".exe"  : "application/x-msdownload"
          , ".f"    : "text/x-fortran"
          , ".f77"  : "text/x-fortran"
          , ".f90"  : "text/x-fortran"
          , ".flv"  : "video/x-flv"
          , ".for"  : "text/x-fortran"
          , ".gem"  : "application/octet-stream"
          , ".gemspec" : "text/x-script.ruby"
          , ".gif"  : "image/gif"
          , ".gz"    : "application/x-gzip"
          , ".h"    : "text/x-c"
          , ".hh"    : "text/x-c"
          , ".htm"  : "text/html"
          , ".html"  : "text/html"
          , ".ico"  : "image/vnd.microsoft.icon"
          , ".ics"  : "text/calendar"
          , ".ifb"  : "text/calendar"
          , ".iso"  : "application/octet-stream"
          , ".jar"  : "application/java-archive"
          , ".java"  : "text/x-java-source"
          , ".jnlp"  : "application/x-java-jnlp-file"
          , ".jpeg"  : "image/jpeg"
          , ".jpg"  : "image/jpeg"
          , ".js"    : "application/javascript"
          , ".json"  : "application/json"
          , ".log"  : "text/plain"
          , ".m3u"  : "audio/x-mpegurl"
          , ".m4v"  : "video/mp4"
          , ".man"  : "text/troff"
          , ".mathml"  : "application/mathml+xml"
          , ".mbox"  : "application/mbox"
          , ".mdoc"  : "text/troff"
          , ".me"    : "text/troff"
          , ".mid"  : "audio/midi"
          , ".midi"  : "audio/midi"
          , ".mime"  : "message/rfc822"
          , ".mml"  : "application/mathml+xml"
          , ".mng"  : "video/x-mng"
          , ".mov"  : "video/quicktime"
          , ".mp3"  : "audio/mpeg"
          , ".mp4"  : "video/mp4"
          , ".mp4v"  : "video/mp4"
          , ".mpeg"  : "video/mpeg"
          , ".mpg"  : "video/mpeg"
          , ".ms"    : "text/troff"
          , ".msi"  : "application/x-msdownload"
          , ".odp"  : "application/vnd.oasis.opendocument.presentation"
          , ".ods"  : "application/vnd.oasis.opendocument.spreadsheet"
          , ".odt"  : "application/vnd.oasis.opendocument.text"
          , ".ogg"  : "application/ogg"
          , ".p"     : "text/x-pascal"
          , ".pas"  : "text/x-pascal"
          , ".pbm"  : "image/x-portable-bitmap"
          , ".pdf"  : "application/pdf"
          , ".pem"  : "application/x-x509-ca-cert"
          , ".pgm"  : "image/x-portable-graymap"
          , ".pgp"  : "application/pgp-encrypted"
          , ".pkg"  : "application/octet-stream"
          , ".pl"    : "text/x-script.perl"
          , ".pm"    : "text/x-script.perl-module"
          , ".png"  : "image/png"
          , ".pnm"  : "image/x-portable-anymap"
          , ".ppm"  : "image/x-portable-pixmap"
          , ".pps"  : "application/vnd.ms-powerpoint"
          , ".ppt"  : "application/vnd.ms-powerpoint"
          , ".ps"    : "application/postscript"
          , ".psd"  : "image/vnd.adobe.photoshop"
          , ".py"    : "text/x-script.python"
          , ".qt"    : "video/quicktime"
          , ".ra"    : "audio/x-pn-realaudio"
          , ".rake"  : "text/x-script.ruby"
          , ".ram"  : "audio/x-pn-realaudio"
          , ".rar"  : "application/x-rar-compressed"
          , ".rb"    : "text/x-script.ruby"
          , ".rdf"  : "application/rdf+xml"
          , ".roff"  : "text/troff"
          , ".rpm"  : "application/x-redhat-package-manager"
          , ".rss"  : "application/rss+xml"
          , ".rtf"  : "application/rtf"
          , ".ru"    : "text/x-script.ruby"
          , ".s"     : "text/x-asm"
          , ".sgm"  : "text/sgml"
          , ".sgml"  : "text/sgml"
          , ".sh"    : "application/x-sh"
          , ".sig"  : "application/pgp-signature"
          , ".snd"  : "audio/basic"
          , ".so"    : "application/octet-stream"
          , ".svg"  : "image/svg+xml"
          , ".svgz"  : "image/svg+xml"
          , ".swf"  : "application/x-shockwave-flash"
          , ".t"     : "text/troff"
          , ".tar"  : "application/x-tar"
          , ".tbz"  : "application/x-bzip-compressed-tar"
          , ".tcl"  : "application/x-tcl"
          , ".tex"  : "application/x-tex"
          , ".texi"  : "application/x-texinfo"
          , ".texinfo" : "application/x-texinfo"
          , ".text"  : "text/plain"
          , ".tif"  : "image/tiff"
          , ".tiff"  : "image/tiff"
          , ".torrent" : "application/x-bittorrent"
          , ".tr"    : "text/troff"
          , ".txt"  : "text/plain"
          , ".vcf"  : "text/x-vcard"
          , ".vcs"  : "text/x-vcalendar"
          , ".vrml"  : "model/vrml"
          , ".war"  : "application/java-archive"
          , ".wav"  : "audio/x-wav"
          , ".wma"  : "audio/x-ms-wma"
          , ".wmv"  : "video/x-ms-wmv"
          , ".wmx"  : "video/x-ms-wmx"
          , ".wrl"  : "model/vrml"
          , ".wsdl"  : "application/wsdl+xml"
          , ".xbm"  : "image/x-xbitmap"
          , ".xhtml"  : "application/xhtml+xml"
          , ".xls"  : "application/vnd.ms-excel"
          , ".xml"  : "application/xml"
          , ".xpm"  : "image/x-xpixmap"
          , ".xsl"  : "application/xml"
          , ".xslt"  : "application/xslt+xml"
          , ".yaml"  : "text/yaml"
          , ".yml"  : "text/yaml"
          , ".zip"  : "application/zip"
          }
};


    // List of most common mime-types, stolen from Rack.
    TYPES : { ".3gp"  : "video/3gpp"
            , ".a"    : "application/octet-stream"
            , ".ai"    : "application/postscript"
            , ".aif"  : "audio/x-aiff"
            , ".aiff"  : "audio/x-aiff"
            , ".asc"  : "application/pgp-signature"
            , ".asf"  : "video/x-ms-asf"
            , ".asm"  : "text/x-asm"
            , ".asx"  : "video/x-ms-asf"
            , ".atom"  : "application/atom+xml"
            , ".au"    : "audio/basic"
            , ".avi"  : "video/x-msvideo"
            , ".bat"  : "application/x-msdownload"
            , ".bin"  : "application/octet-stream"
            , ".bmp"  : "image/bmp"
            , ".bz2"  : "application/x-bzip2"
            , ".c"    : "text/x-c"
            , ".cab"  : "application/vnd.ms-cab-compressed"
            , ".cc"    : "text/x-c"
            , ".chm"  : "application/vnd.ms-htmlhelp"
            , ".class"  : "application/octet-stream"
            , ".com"  : "application/x-msdownload"
            , ".conf"  : "text/plain"
            , ".cpp"  : "text/x-c"
            , ".crt"  : "application/x-x509-ca-cert"
            , ".css"  : "text/css"
            , ".csv"  : "text/csv"
            , ".cxx"  : "text/x-c"
            , ".deb"  : "application/x-debian-package"
            , ".der"  : "application/x-x509-ca-cert"
            , ".diff"  : "text/x-diff"
            , ".djv"  : "image/vnd.djvu"
            , ".djvu"  : "image/vnd.djvu"
            , ".dll"  : "application/x-msdownload"
            , ".dmg"  : "application/octet-stream"
            , ".doc"  : "application/msword"
            , ".dot"  : "application/msword"
            , ".dtd"  : "application/xml-dtd"
            , ".dvi"  : "application/x-dvi"
            , ".ear"  : "application/java-archive"
            , ".eml"  : "message/rfc822"
            , ".eps"  : "application/postscript"
            , ".exe"  : "application/x-msdownload"
            , ".f"    : "text/x-fortran"
            , ".f77"  : "text/x-fortran"
            , ".f90"  : "text/x-fortran"
            , ".flv"  : "video/x-flv"
            , ".for"  : "text/x-fortran"
            , ".gem"  : "application/octet-stream"
            , ".gemspec" : "text/x-script.ruby"
            , ".gif"  : "image/gif"
            , ".gz"    : "application/x-gzip"
            , ".h"    : "text/x-c"
            , ".hh"    : "text/x-c"
            , ".htm"  : "text/html"
            , ".html"  : "text/html"
            , ".ico"  : "image/vnd.microsoft.icon"
            , ".ics"  : "text/calendar"
            , ".ifb"  : "text/calendar"
            , ".iso"  : "application/octet-stream"
            , ".jar"  : "application/java-archive"
            , ".java"  : "text/x-java-source"
            , ".jnlp"  : "application/x-java-jnlp-file"
            , ".jpeg"  : "image/jpeg"
            , ".jpg"  : "image/jpeg"
            , ".js"    : "application/javascript"
            , ".json"  : "application/json"
            , ".log"  : "text/plain"
            , ".m3u"  : "audio/x-mpegurl"
            , ".m4v"  : "video/mp4"
            , ".man"  : "text/troff"
            , ".mathml"  : "application/mathml+xml"
            , ".mbox"  : "application/mbox"
            , ".mdoc"  : "text/troff"
            , ".me"    : "text/troff"
            , ".mid"  : "audio/midi"
            , ".midi"  : "audio/midi"
            , ".mime"  : "message/rfc822"
            , ".mml"  : "application/mathml+xml"
            , ".mng"  : "video/x-mng"
            , ".mov"  : "video/quicktime"
            , ".mp3"  : "audio/mpeg"
            , ".mp4"  : "video/mp4"
            , ".mp4v"  : "video/mp4"
            , ".mpeg"  : "video/mpeg"
            , ".mpg"  : "video/mpeg"
            , ".ms"    : "text/troff"
            , ".msi"  : "application/x-msdownload"
            , ".odp"  : "application/vnd.oasis.opendocument.presentation"
            , ".ods"  : "application/vnd.oasis.opendocument.spreadsheet"
            , ".odt"  : "application/vnd.oasis.opendocument.text"
            , ".ogg"  : "application/ogg"
            , ".p"    : "text/x-pascal"
            , ".pas"  : "text/x-pascal"
            , ".pbm"  : "image/x-portable-bitmap"
            , ".pdf"  : "application/pdf"
            , ".pem"  : "application/x-x509-ca-cert"
            , ".pgm"  : "image/x-portable-graymap"
            , ".pgp"  : "application/pgp-encrypted"
            , ".pkg"  : "application/octet-stream"
            , ".pl"    : "text/x-script.perl"
            , ".pm"    : "text/x-script.perl-module"
            , ".png"  : "image/png"
            , ".pnm"  : "image/x-portable-anymap"
            , ".ppm"  : "image/x-portable-pixmap"
            , ".pps"  : "application/vnd.ms-powerpoint"
            , ".ppt"  : "application/vnd.ms-powerpoint"
            , ".ps"    : "application/postscript"
            , ".psd"  : "image/vnd.adobe.photoshop"
            , ".py"    : "text/x-script.python"
            , ".qt"    : "video/quicktime"
            , ".ra"    : "audio/x-pn-realaudio"
            , ".rake"  : "text/x-script.ruby"
            , ".ram"  : "audio/x-pn-realaudio"
            , ".rar"  : "application/x-rar-compressed"
            , ".rb"    : "text/x-script.ruby"
            , ".rdf"  : "application/rdf+xml"
            , ".roff"  : "text/troff"
            , ".rpm"  : "application/x-redhat-package-manager"
            , ".rss"  : "application/rss+xml"
            , ".rtf"  : "application/rtf"
            , ".ru"    : "text/x-script.ruby"
            , ".s"    : "text/x-asm"
            , ".sgm"  : "text/sgml"
            , ".sgml"  : "text/sgml"
            , ".sh"    : "application/x-sh"
            , ".sig"  : "application/pgp-signature"
            , ".snd"  : "audio/basic"
            , ".so"    : "application/octet-stream"
            , ".svg"  : "image/svg+xml"
            , ".svgz"  : "image/svg+xml"
            , ".swf"  : "application/x-shockwave-flash"
            , ".t"    : "text/troff"
            , ".tar"  : "application/x-tar"
            , ".tbz"  : "application/x-bzip-compressed-tar"
            , ".tcl"  : "application/x-tcl"
            , ".tex"  : "application/x-tex"
            , ".texi"  : "application/x-texinfo"
            , ".texinfo" : "application/x-texinfo"
            , ".text"  : "text/plain"
            , ".tif"  : "image/tiff"
            , ".tiff"  : "image/tiff"
            , ".torrent" : "application/x-bittorrent"
            , ".tr"    : "text/troff"
            , ".txt"  : "text/plain"
            , ".vcf"  : "text/x-vcard"
            , ".vcs"  : "text/x-vcalendar"
            , ".vrml"  : "model/vrml"
            , ".war"  : "application/java-archive"
            , ".wav"  : "audio/x-wav"
            , ".wma"  : "audio/x-ms-wma"
            , ".wmv"  : "video/x-ms-wmv"
            , ".wmx"  : "video/x-ms-wmx"
            , ".wrl"  : "model/vrml"
            , ".wsdl"  : "application/wsdl+xml"
            , ".xbm"  : "image/x-xbitmap"
            , ".xhtml"  : "application/xhtml+xml"
            , ".xls"  : "application/vnd.ms-excel"
            , ".xml"  : "application/xml"
            , ".xpm"  : "image/x-xpixmap"
            , ".xsl"  : "application/xml"
            , ".xslt"  : "application/xslt+xml"
            , ".yaml"  : "text/yaml"
            , ".yml"  : "text/yaml"
            , ".zip"  : "application/zip"
            }
  }; 
</source>
</source>


index.html
index.html
<source lang="html4strict">
<source lang="html4strict">
  <!doctype html>
<!doctype html>
  <html lang="en">
<html lang="en">
  <head>
<head>
    <meta charset="utf-8">
  <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title>LTAP</title>
  <title>LTAP</title>
    <meta name="description" content="">
  <meta name="description" content="">
    <meta name="author" content="">
  <meta name="author" content="">
    <meta name="viewport" content="width=device-width,initial-scale=1">
  <meta name="viewport" content="width=device-width,initial-scale=1">
    <link rel="stylesheet" href="style.css">
  <link rel="stylesheet" href="style.css">
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
    <script src="/nowjs/now.js"></script>
  <script src="/nowjs/now.js"></script>
     <script>
  <script type="text/javascript">
     $(document).ready(function(){
 
    now.receiveMessage = function(id, nick, message){
  util = {
     urlRE: /https?:\/\/([-\w\.]+)+(:\d+)?(\/([^\s]*(\?\S+)?)?)?/g,
 
    toStaticHTML: function(inputHtml) {
      inputHtml = inputHtml.toString();
      return inputHtml.replace(/&/g, "&amp;")
                      .replace(/</g, "&lt;")
                      .replace(/>/g, "&gt;");
    },
   
     zeroPad: function (digits, n) {
      n = n.toString();
      while (n.length < digits)
        n = '0' + n;
      return n;
    },
 
    timeString: function (date) {
      var minutes = date.getMinutes().toString();
      var hours = date.getHours().toString();
      return this.zeroPad(2, hours) + ":" + this.zeroPad(2, minutes);
    },
 
    isBlank: function(text) {
      var blank = /^\s*$/;
      return (text.match(blank) !== null);
    }
  };
 
  $(document).ready(function(){
    var CONFIG = { focus: true
                  ,unread: 0
                };
 
  now.receiveMessage = function(id, nick, messageRaw){


     if(id === now.id) {
     if(id === now.id) {
       var nick = "you";
       var nick = "you";
     }
     }
     
      if(!CONFIG.focus){
        CONFIG.unread++;
        updateTitle();
      }
     
     
      var message = util.toStaticHTML(messageRaw);
    var time = new Date();
    var relTime = util.timeString(time);
      var lastMessage = $("#message div:last");
      var lastMessageTime = $(lastMessage).find(".time").text();
 
      if($(lastMessage).attr("data-id") === id) {
      if(relTime === lastMessageTime) {
     
        var content = '<span class="clear">'
                  //  + ' <span class="time">' + relTime + '</span>'
                      + ' <span class="msg-text">' + message  + '</span>'
                      + '</span>';
     
      } else {
        var content = '<span class="clear">'
                    + ' <span class="time">' + relTime + '</span>'
                      + ' <span class="msg-text">' + message  + '</span>'
                      + '</span>';
 
      }
        $(lastMessage).append(content);
        $("#message").scrollTop(55000);
 
      } else {
               
      var content = '<div data-id="'+id+'">'
                    + '  <span class="time">' + util.timeString(time) + '</span>'
                    + '  <span class="nick '+nick+'">' + nick + '</span>'
                    + '  <span class="msg-text">' + message  + '</span'
                    + '</div>'
                    ;
    $("#message").append(content).scrollTop(55000);
      }
    if($(".isTyping").is("visible")) {
        $(".isTyping").appendTo($("#messages"));
      }   
  }


    var content = '<div>'
  $(".send").click(function() {
                  + '  <span class="nick '+nick+'">' + nick + ':</span>'
      var input = "";
                  + '  <span class="msg-text">' + message  + '</span'
      now.messageRoom(currRoom, input.text());
                  + '</div>'
  });
                  ;
 
  now.chatStatus = function(status) {
  var time = new Date();
   
  var content = '<div>'
                + '  <span class="time">' + util.timeString(time) + '</span>'
                + '  <span class="status">' + status + '</span>'
                + '</div>'
                ;


  $("#message").append(content).scrollTop(55000);
    if(status === "Buddy Joined, say Hi!") {
  }
      $("#text-input").prop("disabled", false).focus();
    }
   
    if(status === "Buddy Left.. – Looking for a new one...") {
      $("#text-input").prop("disabled", true);
    }      


     $(".send").click(function() {
     $("#message").append(content).scrollTop(55000);
        var input = "";
  }
        now.messageRoom(currRoom, input.text());
   
    });
  now.updateImage = function(image) {
    $("#image").attr("src", image).show();      


    now.chatStatus = function(status) {
  }
    var content = '<div>'
 
                  + '  <span class="status">' + status + '</span>'
  now.updateType = function(val, nick, id) {
                  + '</div>'
  console.log(val, nick, id, now.id);
                  ;
  if(id != now.id) {
       if(status === "Partner Joined") {
      if(val === true) {
        $("#text-input").prop("disabled", false).focus();
        $(".isTyping").text(nick + " is typing…").appendTo($("#message")).show();
      }
        $("#message").scrollTop(55000);
      } else {
        $(".isTyping").hide();
       }     
  }
  }
 
  $("#chatBox").bind("keypress", function(e) {
    var code = (e.keyCode ? e.keyCode : e.which);
    if(code == 13) { //Enter keycode
      var message = $("#text-input").val();
      if(message) {
      now.distributeMessage($("#text-input").val());
      $("#text-input").val("");
      }
      now.isTyping(false);
      return false;
    }
         
    now.isTyping(true);
  });
 
  $("#text-input").bind("blur", function() {
    console.log("blurred");
    now.isTyping(false);
  });
 
  $("#loginform").submit(function(){
    var nick = $("#nick").val();


       $("#message").append(content);
    if(!nick) {
       var nick = "Stranger";
     }
     }
       
    var image = $("#imageurl").val();   
    if(!image) {
      $(".error").html("Image Can't Be Empty").show();     
    } else {                 
      //ugly regex hack for validating image
      var match = image.match(/http:\/\/.+?\.jpg|jpeg|png|gif/gi);
      if(!match) {
        $(".error").html("Not a valid Image Url").show();
      } else {
        now.id = Math.floor(Math.random()*99999999999).toString();
        now.room = 1;
        now.nick = nick;
        now.image = image;
        now.join(nick, image);


     now.updateImage = function(image1,image2) {
        $("#chat").show();
       if(image1 === now.image) {
        $("#login").hide();
        var image = image2;
        $("#text-input").focus();       
       } else {
      }     
        var image = image1;
     }   
       }
    return false;
       $("#image").attr("src", image);      
  });
 
 
  $("#file").submit(function() {  
    $("#transFrame").load(function() {        
    response = $(this).contents().find('body').text();
   
    if(response) {
      $("#file").hide();
      var filename = "http://t2sp.net/upload/" + response;
       $("#imageurl").val(filename);
      $(".toggle-handle").hide();
        
       $("#imageurl").prop("disabled", true);
     }
     }
       
    //return false;
    });
  });
 
  $(".toggle-handle").click(function() {
    $(this).next(".toggle-box").show();
    return false;
  });
 
  // on establishing 'now' connection, set server room and allow message sending
now.ready(function(){
});
  //listen for browser events so we know to update the document title
  $(window).bind("blur", function() {
    CONFIG.focus = false;
    updateTitle();
  });


  // Send message to people in the same group
  $(window).bind("focus", function() {
  $("#chatBox").submit(function(){
    CONFIG.focus = true;
    var message = $("#text-input").val();
    CONFIG.unread = 0;
    if(message) {
    updateTitle();
    now.distributeMessage($("#text-input").val());
  });
    $("#text-input").val("");
    }
function updateTitle(){
    return false;
    if (CONFIG.unread) {
  });
      document.title = "(" + CONFIG.unread.toString() + ") LTAP";
    } else {
      document.title = "LTAP";
    }
  }
});
</script>
</head>
<body> 
  <div id="login">
    <hgroup>
      <h1>LTAP</h1>
    </hgroup>
      
      
     //handle login
     <form id="loginform" action="" method="get">
    $("#loginform").submit(function(){
       <h3 class="error hidden"></h3>
       var nick = $("#nick").val();


       if(!nick) {
       <input type="text" id="nick" placeholder="nickname (optional)"/>
        var nick = "Stranger";
      <input type="text" id="imageurl" placeholder="Image (URL)"/>
      }
        
        
       var image = $("#imageurl").val();   
       <input type="submit" id="submit" value="go" class="button"/>
       if(!image) {
    </form>       
         $(".error").html("Image Can't Be Empty").show();     
 
       } else {
    <h3 class='toggle-handle'>Or Upload A File...</h3>
                         
    <div class='toggle-box hidden'>
        //ugly regex hack for validating image
       <form target="transFrame" id="file" action="/upload" method="post" enctype="multipart/form-data">
        var match = image.match(/http:\/\/.+?\.jpg|jpeg|png|gif/gi);
        <input type="file" name="file-upload" id="file-upload" multiple="multiple">
         if(!match) {
         <input type="submit" value="Upload" class="button">
          $(".error").html("Not a valid Image Url").show();
       </form>   
        } else {
    </div>
          now.id = Math.floor(Math.random()*99999999999).toString();
  </div>
          now.room = 1;
 
          now.nick = nick;
  <iframe style="" class="hidden" name="transFrame" id="transFrame"></iframe>
          now.image = image;
  <div id="chat" class="hidden">
          now.join(nick, image);
    <div class="image">
      <img id="image" class="" src="http://www.pandl.org/img/1x1" alt="placeholder img"/>
    </div>
   
    <div class="right">
      <div id="message">     
        <span class="isTyping hidden"></span>
      </div>
 
      <form id="chatBox" action="" method="get">
         <textarea type="text" id="text-input" name="chat" value="" disabled></textarea>
      </form>
    </div>
  </div>
 
  <div class="disclaimer">
    <p>* all conversations are being stored in a database</p>
  </div>
</body>
</html>
</source>
 
style.css
<source lang="css">
article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { display: block; }
audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; }
audio:not([controls]) { display: none; }
[hidden] { display: none; }
html { font-size: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
html, button, input, select, textarea { font-family: sans-serif; color: #222; }
body { margin: 0; font-size: 1em; line-height: 1.4;}
::-moz-selection { background: #333; color: #fff; text-shadow: none; }
::selection { background: #333; color: #fff; text-shadow: none; }
 
/* =============================================================================
  Links
  ========================================================================== */
a { color: #00e; }
a:visited { color: #551a8b; }
a:hover { color: #06e; }
a:focus { outline: thin dotted; }
a:hover, a:active { outline: 0; }
 
ul, li, ol { margin: 0; padding: 0; list-style: none;}
img { border: 0; -ms-interpolation-mode: bicubic; vertical-align: middle; }
form { margin: 0; }
fieldset { border: 0; margin: 0; padding: 0; }
label { cursor: pointer; }
legend { border: 0; *margin-left: -7px; padding: 0; white-space: normal; }
button, input, select, textarea { font-size: 100%; margin: 0; vertical-align: baseline; *vertical-align: middle; }
button, input { line-height: normal; }
button, input[type="button"], input[type="reset"], input[type="submit"] { cursor: pointer; -webkit-appearance: button; *overflow: visible; }
button[disabled], input[disabled] { cursor: default; }
input[type="checkbox"], input[type="radio"] { box-sizing: border-box; padding: 0; *width: 13px; *height: 13px; }
input[type="search"] { -webkit-appearance: textfield; -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box; }
input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; }
button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; }
textarea { overflow: auto; vertical-align: top; resize: vertical; }


          $("#chat").show();
html, body {width: 100%; height: 100%; font: 16px/1.3 sans-serif; overflow: hidden; margin: 0; padding: 0;}
          $("#login").hide();
#login input {margin-bottom: 10px; width: 320px; border: 0; border-bottom: 1px solid; font-size: 24px; color: #000;}
          $("#text-input").focus();      
#login input[type='submit'] {width: 160px; margin-left: 80px; margin-bottom: 20px;}
        }     
      }   
      return false;
    });


    //ugly file uploading hack
#login {width: 320px; margin: 100px auto;}
    $("#file").submit(function() {  
.error {color: #f00;}
      $("#transFrame").load(function() {      
.image {width: 50%; float: left; text-align: center; padding-top: 10px; }
      response = $(this).contents().find('body').text();
.image img {max-width: 80%; max-height: 80%;}
.right {bottom: 60px;left: 50%;position: absolute;right: 20px;top: 10px;}#chat {height: 100%; width: 100%; margin: 0; padding: 0;}
#message {height: 85%; width: 100%; border: 1px solid #ccc; padding: 10px 0 10px 10px; overflow-y: scroll; margin-bottom: 10px;}
#message div {padding-bottom: 5px;}
#chatBox {height: 15%;}
#text-input {width: 100%;height: 100%; margin: 0; padding: 10px 0 10px 10px; border: 1px solid #ccc; font-size: 16px;}
h3 {color: #333;}
.nick {color: red;}
.you {color: blue;}
.disclaimer {font-size: 12px; color: #666; position: fixed; bottom: 5px; left: 5px;}
.isTyping {font-size: 14px; font-weight: bold; padding-top: 10.4px;}


      if(response) {
input:valid, textarea:valid { }
        $("#file").hide();
input:invalid, textarea:invalid { background-color: #f0dddd; }
        var filename = "http://localhost:1337/upload/" + response;
        $("#imageurl").val(filename);     
        $(".toggle-handle").hide();


        $("#imageurl").prop("disabled", true);
.hidden { display: none;}
      }
.invisible { visibility: hidden; }
.cf:before, .cf:after, #message div:before, #message div:after { content: ""; display: table; }
.cf:after, #message div:after { clear: both; }
.cf, #message div { *zoom: 1; }


      //return false;
.button {
      });
  -moz-border-radius:      3px;
    });
  -webkit-border-radius:  3px;
  background:              white url('button.png') 0 0 repeat-x;  
  background:            -moz-linear-gradient(0% 170% 90deg, #c4c4c4, white);
  background:            -webkit-gradient(linear, 0% 0%, 0% 170%, from(white), to(#c4c4c4));
  border:                  1px solid;
  border-color:            #e6e6e6 #cccccc #cccccc #e6e6e6;
  border-radius:          3px;
  color:                  #404040;
  display:                inline-block;
  font-family:            "helvetica neue", helvetica, arial, freesans, "liberation sans", "numbus sans l", sans-serif;
  font-size:              13px;
  outline                  0;
  padding:                5px 8px 5px;
  text-align:              center;
  text-decoration:        none;
  text-shadow:            1px 1px 0 white;
  white-space:            nowrap; }


     $(".toggle-handle").click(function() {
  .button:hover {
      $(this).next(".toggle-box").show();
     background:          -moz-linear-gradient(0% 170% 90deg, #b8b8b8, white);
      return false;
    background:          -webkit-gradient(linear, 0% 0%, 0% 170%, from(white), to(#b8b8b8));
     });
    border-color:          #99ccff;
     color:                #333333; }


    // on establishing 'now' connection, set server room and allow message sending
  .button:active {
  now.ready(function(){
    position:              relative;
     
    top:                  1px; }
  });
  });
  </script>
  </head>
  <body> 
    <div id="login">
      <form id="loginform" action="" method="get">
        <h3 class="error hidden"></h3>


        <input type="text" id="nick" placeholder="nickname"/>
  .button:active, .button:focus {
        <input type="text" id="imageurl" placeholder="Image (URL)"/>
    background-position:  0 -25px;
    background:          -moz-linear-gradient(0% 170% 90deg, white, #dedede);
    background:          -webkit-gradient(linear, 0% 0%, 0% 170%, from(#dedede), to(white));
    border-color:          #8fc7ff #94c9ff #94c9ff #8fc7ff;
    color:                #1a1a1a;
    text-shadow:          1px -1px 0 rgba(255, 255, 255, 0.5); }


        <input type="submit" id="submit" value="go" />
.time {color: #666; font-size: 14px; float: right; margin-right: 10px;}
      </form>       
#message div {border-bottom: 1px dotted rgb(230,230,230); padding: 6px 0px 5px 0; clear: both;}
.nick {float: left; margin-right: 5px;}
.msg-text {float: left; width: 90%; clear: both;}


      <h3 class='toggle-handle'>Or Upload A File...</h3>
</source>
      <div class='toggle-box hidden'>
        <form target="transFrame" id="file" action="/upload" method="post" enctype="multipart/form-data">
          <input type="file" name="file-upload" id="file-upload" multiple="multiple">
          <input type="submit" value="Upload" >
        </form>   
      </div>
    </div>


    <iframe style="" class="hidden" name="transFrame" id="transFrame"></iframe>
sql
<source lang="mysql">


    <div id="chat" class="hidden">
CREATE TABLE `chat_logs` (
      <div class="image">
  `id` int(11) NOT NULL AUTO_INCREMENT,
        <img id="image" src="http://i.imgur.com/aED4b.jpg" />
  `user` tinytext,
      </div>
  `text` text,
  `date` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;


      <div class="right">
CREATE TABLE `chats` (
        <div id="message">     
  `id` int(11) NOT NULL AUTO_INCREMENT,
        </div>   
  `user1` tinytext,
  `user2` tinytext,
  `nick1` tinytext,
  `nick2` tinytext,
  `image1` tinytext,
  `image2` tinytext,
  `chatimage` tinytext,
  `date` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;


        <form id="chatBox" action="" method="get">
          <input type="text" id="text-input" name="chat" value="" disabled/>
        </form>
      </div>
    </div>


  </body>
  </html>
</source>
</source>
http://t2sp.net/docs/doc1.png
http://t2sp.net/docs/doc2.png
http://t2sp.net/docs/doc3.png
http://t2sp.net/docs/doc4.png


__NOINDEX__
__NOINDEX__

Latest revision as of 12:17, 3 April 2012

Let's Talk About Pictures (LTAP)

LTAP is a collaboration with Louis Doulas and Harry Burke, to create a website that facilitates and encourages a one to one discussion about pictures. In LTAP, each user is initially presented with an upload form which accepts images. The user is required to upload a picture of their choosing and pick a nickname to enter the discussion, the chat room. The chat room displays a random picture from the database of images and the conversation can begin.

LTAP Development

Plan

Use nodejs, with the modules nowjs, mysql, forever, formidable, to create a realtime chat with image uploads and a mysql database for storage. (Consider switching mysql for couchdb or mongodb).

Resources

Application Logic

File Uploads

  • Node Formidable Module for handling multipart uploadshttps://github.com/felixge/node-formidable/

Mysql, Mysql Module

General articles to read


Deployment

  • Look into Nginx


Source

server.js

//db
var mysql = require('mysql');
var db = "ltappiting";
var chats = 'chats';
var chatLogs = 'chat_logs';
var client = mysql.createClient({
   host: "localhost",
   user: "root",
   password: "root"
});

//server
var http = require("http");
var url = require("url");
var fs = require("fs");
var util = require('util');
var formidable = require('formidable');
var exec = require('child_process').exec;
var fu = exports;

function onRequest(req, res) {
   var pathname = url.parse(req.url).pathname;
   console.log("Request for " + pathname + " received.");  

   //routing
   switch (pathname) {
      case '/':
      req.setEncoding("utf8");
          var html = fs.readFileSync(__dirname+'/index.html');
          res.end(html);
      break;
      case '/upload':
          uploadFile(req, res);
      break;
        default:
          req.setEncoding("utf8");
           var content_type = fu.mime.lookupExtension(extname(pathname));
                                  
            fs.readFile(__dirname+"/"+pathname, function(err, data) { 
              if(err) {
                res.writeHead(404, {"Content-Type": "text/plain"});
                res.write("404 Not found");
                res.end();
              } else {
                headers = { "Content-Type": content_type
                          , "Content-Length": data.length
                          };
                
                res.writeHead(200, headers);
                res.write(data);
                res.end();                
              }
            });
      break;
    }
}
var server = http.createServer(onRequest).listen(1337);
console.log("server running on 1337");

var nowjs = require("now");
var everyone = nowjs.initialize(server);

//Session Variables
var sessions = {};
var rooms = [];
rooms[0] = 0;

// I'm working on a 1-1 chat client with nodejs and nowjs, and I'm wondering about the best practice for queuing/assigning rooms (groups) to the users, given that it should only be a 1 to 1 conversation, and the one who's been waiting the longest should be assigned first'. Any ideas? :)

//Send message to everyone on the users group
everyone.now.distributeMessage = function(message){
    console.log('Received '+message+' from '+this.now.nick +' in serverroom '+this.now.room);
    var group = nowjs.getGroup(this.now.room);    
    group.now.receiveMessage(this.now.id, this.now.nick, message);
    
    var date = new Date();
    //insert into DB
    client.query('USE '+db);
    client.query(
      'INSERT INTO '+chatLogs+' '+
      'SET user = ?, text = ?, date = ?',
      [this.user.clientId, message, date]
    );  

};

//function to be called from client
everyone.now.join = function() {        
    var userID = this.user.clientId;
    var nick = this.now.nick;  
    var image = this.now.image;
    var id = this.now.id;
    
    //get available room
    var room = getRoom();
    this.now.room = room;
    
        
      var user = {
        id: userID,
        nick: nick,
        room: room,
        image: image        
        //timestamp: new Date()      
       };
                 
      //add user to room
      nowjs.getGroup(room).addUser(userID);
  
      //add user to session
      sessions[user.id] = user;
      var group = nowjs.getGroup(this.now.room);
          
      if(rooms[room] == 2) {
        var status = "Buddy Joined, say Hi!";
  
        //get-users, add to db
        nowjs.getGroup(room).getUsers(function (users) { 
          var user1 = users[0];
          var user2 = users[1];
          var nick1 =  sessions[user1].nick;
          var image1 = sessions[user1].image;       
          var nick2 =  sessions[user2].nick;
          var image2 = sessions[user2].image;
          var date = new Date();
          
          //var randNumber = Math.floor(Math.random()*11) + 11;
          client.query('USE '+db);
          client.query(
            //'SELECT image1, id FROM '+chats + ' WHERE id=' + randNumber,
            'SELECT image1, id FROM '+chats + ' ORDER BY rand() LIMIT 1',
            function selectCb(err, results, fields) {
              if (err) {
                throw err;
              }
          
            var chatImage = results[0].image1;
            client.end();                
            
            console.log(chatImage);
            
            group.now.updateImage(chatImage);
                    
            //insert into DB
            client.query('USE '+db);
            client.query(
              'INSERT INTO '+chats+' '+
              'SET user1 = ?, user2 = ?, nick1 = ?, nick2 = ?, image1 = ?, image2 = ?, chatimage = ?, date = ?',
              [user1, user2, nick1, nick2, image1, image2, chatImage, date]
            );
          }); 
        });
  
      } else {
        var status = "Finding chat buddy..";
      }  
      group.now.chatStatus(status);
  
}

everyone.now.isTyping = function(type){
    console.log('Received '+type+' from '+this.now.id);

    var group = nowjs.getGroup(this.now.room);    
    group.now.updateType(type, this.now.nick, this.now.id);
};

nowjs.on('disconnect', function () { 
  var userroom = this.now.room;
  var nick = this.now.nick;
  var id = this.now.id;
  var userID = this.user.clientId;  
    
  if(rooms[userroom] == 2) {
    rooms[userroom] = 1;
    var status = "Buddy Left.. – Looking for a new one...";
  } else {
    rooms[userroom] = 0;
  }

  if (userID && sessions[id]) {  
    delete sessions[userID];
  }

  var group = nowjs.getGroup(this.now.room);
  group.now.chatStatus(status);
      
  console.log("user:", this.now.id, this.now.nick, " left");
});

function getRoom() {  
  for (var i=0; i < rooms.length; i++) {
    if(rooms[i] === 1) {
      rooms[i] = 2;      
      return i;

    } else if (rooms[i] === 0) {
        rooms[i] = 1;
        return i;
    }
  }; 
  
   rooms.push(1);
   return rooms.length-1; 
}

//upload
function uploadFile(req, res) {
  var form = new formidable.IncomingForm(),
      files = [],
      fields = [];
      fileob = {};
      
  form.uploadDir = __dirname + '/tmp';
  form.encoding = 'binary';

  form
    .on('field', function(field, value) {
      console.log(field, value);
      fields.push([field, value]);
    })
    .on('file', function(field, file) {
      files.push([field, file]);

      //check size type, cancel if wrong                  
      var newfile = {
          file: file.size, 
          name: file.name,
          path: file.path,
          type: file.type
      };
                          
      fileob = newfile;
      
    })
    .on('end', function() {
      console.log('-> upload done');
      res.writeHead(200, {'content-type': 'text/plain'});
           
      //move
      var ext = extname(fileob.name);      
      var uni = Math.floor(Math.random()*99999).toString();
      var cleanFilename = fileob.name.replace(/[^a-z0-9]/gi, '_').toLowerCase();
      var filename = uni + cleanFilename + ext;
            
      res.write(filename);
      res.end();      
            
      var cmd = "mv " + fileob.path + " /home/jonasx/t2sp.net/upload/" + filename;
      console.log(cmd);
      exec(cmd, puts);
                  
    });
    form.parse(req, function(err, fields, files) {
      if (err) {
        console.log(err);
      }
    });
}
function puts(error, stdout, stderr) { util.puts(stdout) }
      
function include(arr,obj) {
    return (arr.indexOf(obj) != -1);
}

Array.prototype.remove = function(from, to) {
  var rest = this.slice((to || from) + 1 || this.length);
  this.length = from < 0 ? this.length + from : from;
  return this.push.apply(this, rest);
};

function extname (path) {
  var index = path.lastIndexOf(".");
  return index < 0 ? "" : path.substring(index);
}

// stolen from node_chat, jack- thanks
fu.mime = {
  // returns MIME type for extension, or fallback, or octet-steam
  lookupExtension : function(ext, fallback) {
    return fu.mime.TYPES[ext.toLowerCase()] || fallback || 'application/octet-stream';
  },

  // List of most common mime-types, stolen from Rack.
  TYPES : { ".3gp"   : "video/3gpp"
          , ".a"     : "application/octet-stream"
          , ".ai"    : "application/postscript"
          , ".aif"   : "audio/x-aiff"
          , ".aiff"  : "audio/x-aiff"
          , ".asc"   : "application/pgp-signature"
          , ".asf"   : "video/x-ms-asf"
          , ".asm"   : "text/x-asm"
          , ".asx"   : "video/x-ms-asf"
          , ".atom"  : "application/atom+xml"
          , ".au"    : "audio/basic"
          , ".avi"   : "video/x-msvideo"
          , ".bat"   : "application/x-msdownload"
          , ".bin"   : "application/octet-stream"
          , ".bmp"   : "image/bmp"
          , ".bz2"   : "application/x-bzip2"
          , ".c"     : "text/x-c"
          , ".cab"   : "application/vnd.ms-cab-compressed"
          , ".cc"    : "text/x-c"
          , ".chm"   : "application/vnd.ms-htmlhelp"
          , ".class"   : "application/octet-stream"
          , ".com"   : "application/x-msdownload"
          , ".conf"  : "text/plain"
          , ".cpp"   : "text/x-c"
          , ".crt"   : "application/x-x509-ca-cert"
          , ".css"   : "text/css"
          , ".csv"   : "text/csv"
          , ".cxx"   : "text/x-c"
          , ".deb"   : "application/x-debian-package"
          , ".der"   : "application/x-x509-ca-cert"
          , ".diff"  : "text/x-diff"
          , ".djv"   : "image/vnd.djvu"
          , ".djvu"  : "image/vnd.djvu"
          , ".dll"   : "application/x-msdownload"
          , ".dmg"   : "application/octet-stream"
          , ".doc"   : "application/msword"
          , ".dot"   : "application/msword"
          , ".dtd"   : "application/xml-dtd"
          , ".dvi"   : "application/x-dvi"
          , ".ear"   : "application/java-archive"
          , ".eml"   : "message/rfc822"
          , ".eps"   : "application/postscript"
          , ".exe"   : "application/x-msdownload"
          , ".f"     : "text/x-fortran"
          , ".f77"   : "text/x-fortran"
          , ".f90"   : "text/x-fortran"
          , ".flv"   : "video/x-flv"
          , ".for"   : "text/x-fortran"
          , ".gem"   : "application/octet-stream"
          , ".gemspec" : "text/x-script.ruby"
          , ".gif"   : "image/gif"
          , ".gz"    : "application/x-gzip"
          , ".h"     : "text/x-c"
          , ".hh"    : "text/x-c"
          , ".htm"   : "text/html"
          , ".html"  : "text/html"
          , ".ico"   : "image/vnd.microsoft.icon"
          , ".ics"   : "text/calendar"
          , ".ifb"   : "text/calendar"
          , ".iso"   : "application/octet-stream"
          , ".jar"   : "application/java-archive"
          , ".java"  : "text/x-java-source"
          , ".jnlp"  : "application/x-java-jnlp-file"
          , ".jpeg"  : "image/jpeg"
          , ".jpg"   : "image/jpeg"
          , ".js"    : "application/javascript"
          , ".json"  : "application/json"
          , ".log"   : "text/plain"
          , ".m3u"   : "audio/x-mpegurl"
          , ".m4v"   : "video/mp4"
          , ".man"   : "text/troff"
          , ".mathml"  : "application/mathml+xml"
          , ".mbox"  : "application/mbox"
          , ".mdoc"  : "text/troff"
          , ".me"    : "text/troff"
          , ".mid"   : "audio/midi"
          , ".midi"  : "audio/midi"
          , ".mime"  : "message/rfc822"
          , ".mml"   : "application/mathml+xml"
          , ".mng"   : "video/x-mng"
          , ".mov"   : "video/quicktime"
          , ".mp3"   : "audio/mpeg"
          , ".mp4"   : "video/mp4"
          , ".mp4v"  : "video/mp4"
          , ".mpeg"  : "video/mpeg"
          , ".mpg"   : "video/mpeg"
          , ".ms"    : "text/troff"
          , ".msi"   : "application/x-msdownload"
          , ".odp"   : "application/vnd.oasis.opendocument.presentation"
          , ".ods"   : "application/vnd.oasis.opendocument.spreadsheet"
          , ".odt"   : "application/vnd.oasis.opendocument.text"
          , ".ogg"   : "application/ogg"
          , ".p"     : "text/x-pascal"
          , ".pas"   : "text/x-pascal"
          , ".pbm"   : "image/x-portable-bitmap"
          , ".pdf"   : "application/pdf"
          , ".pem"   : "application/x-x509-ca-cert"
          , ".pgm"   : "image/x-portable-graymap"
          , ".pgp"   : "application/pgp-encrypted"
          , ".pkg"   : "application/octet-stream"
          , ".pl"    : "text/x-script.perl"
          , ".pm"    : "text/x-script.perl-module"
          , ".png"   : "image/png"
          , ".pnm"   : "image/x-portable-anymap"
          , ".ppm"   : "image/x-portable-pixmap"
          , ".pps"   : "application/vnd.ms-powerpoint"
          , ".ppt"   : "application/vnd.ms-powerpoint"
          , ".ps"    : "application/postscript"
          , ".psd"   : "image/vnd.adobe.photoshop"
          , ".py"    : "text/x-script.python"
          , ".qt"    : "video/quicktime"
          , ".ra"    : "audio/x-pn-realaudio"
          , ".rake"  : "text/x-script.ruby"
          , ".ram"   : "audio/x-pn-realaudio"
          , ".rar"   : "application/x-rar-compressed"
          , ".rb"    : "text/x-script.ruby"
          , ".rdf"   : "application/rdf+xml"
          , ".roff"  : "text/troff"
          , ".rpm"   : "application/x-redhat-package-manager"
          , ".rss"   : "application/rss+xml"
          , ".rtf"   : "application/rtf"
          , ".ru"    : "text/x-script.ruby"
          , ".s"     : "text/x-asm"
          , ".sgm"   : "text/sgml"
          , ".sgml"  : "text/sgml"
          , ".sh"    : "application/x-sh"
          , ".sig"   : "application/pgp-signature"
          , ".snd"   : "audio/basic"
          , ".so"    : "application/octet-stream"
          , ".svg"   : "image/svg+xml"
          , ".svgz"  : "image/svg+xml"
          , ".swf"   : "application/x-shockwave-flash"
          , ".t"     : "text/troff"
          , ".tar"   : "application/x-tar"
          , ".tbz"   : "application/x-bzip-compressed-tar"
          , ".tcl"   : "application/x-tcl"
          , ".tex"   : "application/x-tex"
          , ".texi"  : "application/x-texinfo"
          , ".texinfo" : "application/x-texinfo"
          , ".text"  : "text/plain"
          , ".tif"   : "image/tiff"
          , ".tiff"  : "image/tiff"
          , ".torrent" : "application/x-bittorrent"
          , ".tr"    : "text/troff"
          , ".txt"   : "text/plain"
          , ".vcf"   : "text/x-vcard"
          , ".vcs"   : "text/x-vcalendar"
          , ".vrml"  : "model/vrml"
          , ".war"   : "application/java-archive"
          , ".wav"   : "audio/x-wav"
          , ".wma"   : "audio/x-ms-wma"
          , ".wmv"   : "video/x-ms-wmv"
          , ".wmx"   : "video/x-ms-wmx"
          , ".wrl"   : "model/vrml"
          , ".wsdl"  : "application/wsdl+xml"
          , ".xbm"   : "image/x-xbitmap"
          , ".xhtml"   : "application/xhtml+xml"
          , ".xls"   : "application/vnd.ms-excel"
          , ".xml"   : "application/xml"
          , ".xpm"   : "image/x-xpixmap"
          , ".xsl"   : "application/xml"
          , ".xslt"  : "application/xslt+xml"
          , ".yaml"  : "text/yaml"
          , ".yml"   : "text/yaml"
          , ".zip"   : "application/zip"
          }
};

index.html

 <!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <title>LTAP</title>
  <meta name="description" content="">
  <meta name="author" content="">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <link rel="stylesheet" href="style.css">
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
  <script src="/nowjs/now.js"></script>
  <script type="text/javascript">
   
  util = {
    urlRE: /https?:\/\/([-\w\.]+)+(:\d+)?(\/([^\s]*(\?\S+)?)?)?/g,
  
    toStaticHTML: function(inputHtml) {
      inputHtml = inputHtml.toString();
      return inputHtml.replace(/&/g, "&amp;")
                      .replace(/</g, "&lt;")
                      .replace(/>/g, "&gt;");
    },
    
    zeroPad: function (digits, n) {
      n = n.toString();
      while (n.length < digits)
        n = '0' + n;
      return n;
    },

    timeString: function (date) {
      var minutes = date.getMinutes().toString();
      var hours = date.getHours().toString();
      return this.zeroPad(2, hours) + ":" + this.zeroPad(2, minutes);
    },
  
    isBlank: function(text) {
      var blank = /^\s*$/;
      return (text.match(blank) !== null);
    }
  };
  
  $(document).ready(function(){
    var CONFIG = { focus: true
                  ,unread: 0
                 };
	  
	  now.receiveMessage = function(id, nick, messageRaw){

  	  if(id === now.id) {
  	    var nick = "you";
  	  }
      
      if(!CONFIG.focus){
        CONFIG.unread++;
        updateTitle();
      }
      
      
      var message = util.toStaticHTML(messageRaw);
  	  var time = new Date();
  	  var relTime = util.timeString(time);
      var lastMessage = $("#message div:last");
      var lastMessageTime = $(lastMessage).find(".time").text();
  
      if($(lastMessage).attr("data-id") === id) {
    	  if(relTime === lastMessageTime) {
    	  
      	  var content = '<span class="clear">'
      	            //  + ' <span class="time">' + relTime + '</span>'
                      + ' <span class="msg-text">' + message  + '</span>'
                      + '</span>';
    	  
    	  } else {
      	  var content = '<span class="clear">'
      	              + ' <span class="time">' + relTime + '</span>'
                      + ' <span class="msg-text">' + message  + '</span>'
                      + '</span>';
  
    	  }
        $(lastMessage).append(content);
        $("#message").scrollTop(55000);
  
      }	else {
                
    	  var content = '<div data-id="'+id+'">'
                    + '  <span class="time">' + util.timeString(time) + '</span>'
                    + '  <span class="nick '+nick+'">' + nick + '</span>'
                    + '  <span class="msg-text">' + message  + '</span'
                    + '</div>'
                    ;
  		  $("#message").append(content).scrollTop(55000);

      }

     	if($(".isTyping").is("visible")) {
        $(".isTyping").appendTo($("#messages"));
      }     
  }

  $(".send").click(function() {
      var input = "";
      now.messageRoom(currRoom, input.text());
  });
  
  now.chatStatus = function(status) {
	  var time = new Date();
	  	  
	  var content = '<div>'
                + '  <span class="time">' + util.timeString(time) + '</span>'
                + '  <span class="status">' + status + '</span>'
                + '</div>'
                ;

    if(status === "Buddy Joined, say Hi!") {
      $("#text-input").prop("disabled", false).focus();
    }
    
    if(status === "Buddy Left.. – Looking for a new one...") {
      $("#text-input").prop("disabled", true);
    }        

    $("#message").append(content).scrollTop(55000);
  }
    
  now.updateImage = function(image) {
    $("#image").attr("src", image).show();        

  }
  
  now.updateType = function(val, nick, id) {
	  console.log(val, nick, id, now.id);
	  if(id != now.id) {
      if(val === true) {
        $(".isTyping").text(nick + " is typing…").appendTo($("#message")).show();
        $("#message").scrollTop(55000);
      } else {
        $(".isTyping").hide();
      }      
	  }
  }
  
  $("#chatBox").bind("keypress", function(e) { 
     var code = (e.keyCode ? e.keyCode : e.which);
     if(code == 13) { //Enter keycode
    	  var message = $("#text-input").val();
    	  if(message) {
    		  now.distributeMessage($("#text-input").val());
    		  $("#text-input").val("");
    	  }
      now.isTyping(false);
      return false;
     }
          
     now.isTyping(true);
  }); 
  
  $("#text-input").bind("blur", function() { 
    console.log("blurred");
     now.isTyping(false);
  });
  
  $("#loginform").submit(function(){ 
    var nick = $("#nick").val();

    if(!nick) {
      var nick = "Stranger";
    }
        
    var image = $("#imageurl").val();    
    if(!image) {
      $(".error").html("Image Can't Be Empty").show();      
    } else {                  
      //ugly regex hack for validating image
      var match = image.match(/http:\/\/.+?\.jpg|jpeg|png|gif/gi);
      if(!match) {
        $(".error").html("Not a valid Image Url").show();
      } else {
        now.id = Math.floor(Math.random()*99999999999).toString();
        now.room = 1;
        now.nick = nick;
        now.image = image;
        now.join(nick, image);

        $("#chat").show();
        $("#login").hide();
        $("#text-input").focus();        
      }      
    }    
    return false;
  });

   
  $("#file").submit(function() { 
    $("#transFrame").load(function() {       
    response = $(this).contents().find('body').text();
    
    if(response) {
      $("#file").hide();
      var filename = "http://t2sp.net/upload/" + response;
      $("#imageurl").val(filename);
      $(".toggle-handle").hide();
      
      $("#imageurl").prop("disabled", true);
    }
        
    //return false;
    });
  });
  
  $(".toggle-handle").click(function() { 
    $(this).next(".toggle-box").show();
    return false;
  });
  
  // on establishing 'now' connection, set server room and allow message sending
	now.ready(function(){
				
	});
	
  //listen for browser events so we know to update the document title
  $(window).bind("blur", function() {
    CONFIG.focus = false;
    updateTitle();
  });

  $(window).bind("focus", function() {
    CONFIG.focus = true;
    CONFIG.unread = 0;
    updateTitle();
  });
	
	function updateTitle(){
    if (CONFIG.unread) {
      document.title = "(" + CONFIG.unread.toString() + ") LTAP";
    } else {
      document.title = "LTAP";
    }
  }
		
});
	
</script>
</head>
<body>  
  <div id="login">
    <hgroup>
      <h1>LTAP</h1>
    </hgroup>
    
    <form id="loginform" action="" method="get">
      <h3 class="error hidden"></h3>

      <input type="text" id="nick" placeholder="nickname (optional)"/>
      <input type="text" id="imageurl" placeholder="Image (URL)"/>
      
      <input type="submit" id="submit" value="go" class="button"/>
    </form>        

    <h3 class='toggle-handle'>Or Upload A File...</h3>
    <div class='toggle-box hidden'>
      <form target="transFrame" id="file" action="/upload" method="post" enctype="multipart/form-data">
        <input type="file" name="file-upload" id="file-upload" multiple="multiple">
        <input type="submit" value="Upload" class="button">
      </form>    
    </div>
  </div>

  <iframe style="" class="hidden" name="transFrame" id="transFrame"></iframe>
  <div id="chat" class="hidden">
    <div class="image">
      <img id="image" class="" src="http://www.pandl.org/img/1x1" alt="placeholder img"/>
    </div>
    
    <div class="right">
      <div id="message">      
        <span class="isTyping hidden"></span>
      </div>

      <form id="chatBox" action="" method="get">
        <textarea type="text" id="text-input" name="chat" value="" disabled></textarea>
      </form>
    </div>
  </div>

  <div class="disclaimer">
    <p>* all conversations are being stored in a database</p>
  </div>
 
</body>
</html>

style.css

article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { display: block; }
audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; }
audio:not([controls]) { display: none; }
[hidden] { display: none; }
html { font-size: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
html, button, input, select, textarea { font-family: sans-serif; color: #222; }
body { margin: 0; font-size: 1em; line-height: 1.4;}
::-moz-selection { background: #333; color: #fff; text-shadow: none; }
::selection { background: #333; color: #fff; text-shadow: none; }

/* =============================================================================
   Links
   ========================================================================== */
a { color: #00e; }
a:visited { color: #551a8b; }
a:hover { color: #06e; }
a:focus { outline: thin dotted; }
a:hover, a:active { outline: 0; }

ul, li, ol { margin: 0; padding: 0; list-style: none;}
img { border: 0; -ms-interpolation-mode: bicubic; vertical-align: middle; }
form { margin: 0; }
fieldset { border: 0; margin: 0; padding: 0; }
label { cursor: pointer; }
legend { border: 0; *margin-left: -7px; padding: 0; white-space: normal; }
button, input, select, textarea { font-size: 100%; margin: 0; vertical-align: baseline; *vertical-align: middle; }
button, input { line-height: normal; }
button, input[type="button"], input[type="reset"], input[type="submit"] { cursor: pointer; -webkit-appearance: button; *overflow: visible; }
button[disabled], input[disabled] { cursor: default; }
input[type="checkbox"], input[type="radio"] { box-sizing: border-box; padding: 0; *width: 13px; *height: 13px; }
input[type="search"] { -webkit-appearance: textfield; -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box; }
input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; }
button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; }
textarea { overflow: auto; vertical-align: top; resize: vertical; }

html, body {width: 100%; height: 100%; font: 16px/1.3 sans-serif; overflow: hidden; margin: 0; padding: 0;}
#login input {margin-bottom: 10px; width: 320px; border: 0; border-bottom: 1px solid; font-size: 24px; color: #000;}
#login input[type='submit'] {width: 160px; margin-left: 80px; margin-bottom: 20px;}

#login {width: 320px; margin: 100px auto;}
.error {color: #f00;}
.image {width: 50%; float: left; text-align: center; padding-top: 10px; }
.image img {max-width: 80%; max-height: 80%;}
.right {bottom: 60px;left: 50%;position: absolute;right: 20px;top: 10px;}#chat {height: 100%; width: 100%; margin: 0; padding: 0;}
#message {height: 85%; width: 100%; border: 1px solid #ccc; padding: 10px 0 10px 10px; overflow-y: scroll; margin-bottom: 10px;}
#message div {padding-bottom: 5px;}
#chatBox {height: 15%;}
#text-input {width: 100%;height: 100%; margin: 0; padding: 10px 0 10px 10px; border: 1px solid #ccc; font-size: 16px;}
h3 {color: #333;}
.nick {color: red;}
.you {color: blue;}
.disclaimer {font-size: 12px; color: #666; position: fixed; bottom: 5px; left: 5px;}
.isTyping {font-size: 14px; font-weight: bold; padding-top: 10.4px;}

input:valid, textarea:valid {  }
input:invalid, textarea:invalid { background-color: #f0dddd; }

.hidden { display: none;}
.invisible { visibility: hidden; }
.cf:before, .cf:after, #message div:before, #message div:after { content: ""; display: table; }
.cf:after, #message div:after { clear: both; }
.cf, #message div { *zoom: 1; }

.button {
  -moz-border-radius:      3px;
  -webkit-border-radius:   3px;
  background:              white url('button.png') 0 0 repeat-x; 
  background:             -moz-linear-gradient(0% 170% 90deg, #c4c4c4, white);
  background:             -webkit-gradient(linear, 0% 0%, 0% 170%, from(white), to(#c4c4c4));
  border:                  1px solid;
  border-color:            #e6e6e6 #cccccc #cccccc #e6e6e6;
  border-radius:           3px;
  color:                   #404040;
  display:                 inline-block;
  font-family:            "helvetica neue", helvetica, arial, freesans, "liberation sans", "numbus sans l", sans-serif;
  font-size:               13px;
  outline                  0;
  padding:                 5px 8px 5px;
  text-align:              center;
  text-decoration:         none;
  text-shadow:             1px 1px 0 white; 
  white-space:             nowrap; }

  .button:hover {
    background:           -moz-linear-gradient(0% 170% 90deg, #b8b8b8, white);
    background:           -webkit-gradient(linear, 0% 0%, 0% 170%, from(white), to(#b8b8b8));
    border-color:          #99ccff;
    color:                 #333333; }

  .button:active {
    position:              relative;
    top:                   1px; }

  .button:active, .button:focus {
    background-position:   0 -25px;
    background:           -moz-linear-gradient(0% 170% 90deg, white, #dedede);
    background:           -webkit-gradient(linear, 0% 0%, 0% 170%, from(#dedede), to(white));
    border-color:          #8fc7ff #94c9ff #94c9ff #8fc7ff;
    color:                 #1a1a1a;
    text-shadow:           1px -1px 0 rgba(255, 255, 255, 0.5); }

.time {color: #666; font-size: 14px; float: right; margin-right: 10px;}
#message div {border-bottom: 1px dotted rgb(230,230,230); padding: 6px 0px 5px 0; clear: both;}
.nick {float: left; margin-right: 5px;}
.msg-text {float: left; width: 90%; clear: both;}

sql

CREATE TABLE `chat_logs` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user` tinytext,
  `text` text,
  `date` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

CREATE TABLE `chats` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user1` tinytext,
  `user2` tinytext,
  `nick1` tinytext,
  `nick2` tinytext,
  `image1` tinytext,
  `image2` tinytext,
  `chatimage` tinytext,
  `date` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

doc1.png doc2.png doc3.png doc4.png