Skip to content

Commit

Permalink
Initial snooping
Browse files Browse the repository at this point in the history
  • Loading branch information
lifo committed Sep 2, 2011
0 parents commit daea635
Show file tree
Hide file tree
Showing 11 changed files with 2,074 additions and 0 deletions.
11 changes: 11 additions & 0 deletions Gemfile
@@ -0,0 +1,11 @@
source :rubygems

gem 'cramp'
gem 'rainbows'

# Rack based routing
gem 'http_router'

# Collection of async-proof rack middlewares - https://github.com/rkh/async-rack.git
gem 'async-rack'
gem 'yajl-ruby'
40 changes: 40 additions & 0 deletions Gemfile.lock
@@ -0,0 +1,40 @@
GEM
remote: http://rubygems.org/
specs:
activesupport (3.0.10)
async-rack (0.5.1)
rack (~> 1.1)
cramp (0.15.1)
activesupport (~> 3.0.9)
eventmachine (~> 1.0.0.beta.3)
rack (~> 1.3.2)
thor (~> 0.14.6)
eventmachine (1.0.0.beta.3)
http_router (0.10.2)
rack (>= 1.0.0)
url_mount (~> 0.2.1)
kgio (2.6.0)
rack (1.3.2)
rainbows (4.2.0)
kgio (~> 2.5)
rack (~> 1.1)
unicorn (~> 4.0)
raindrops (0.7.0)
thor (0.14.6)
unicorn (4.0.1)
kgio (~> 2.4)
rack
raindrops (~> 0.6)
url_mount (0.2.1)
rack
yajl-ruby (0.8.2)

PLATFORMS
ruby

DEPENDENCIES
async-rack
cramp
http_router
rainbows
yajl-ruby
57 changes: 57 additions & 0 deletions app/actions/chat_action.rb
@@ -0,0 +1,57 @@
class ChatAction < Cramp::Websocket
on_finish :handle_leave
on_data :received_data

def received_data(data)
message = parse_json(data)
case message[:action]
when 'join'
handle_join(message)
when 'message'
handle_message(message)
end
end

def handle_join(message)
@user = message[:user]

config = {
:server => "irc.freenode.net",
:port => 6667,
:nickname => @user,
:realname => @user,
:username => @user,
:channels => ["#cramp"]
}

message_handler = Proc.new {|event| render encode_json(:message => event.message, :user => event.from, :action => 'message') }
config[:handlers] = {'privmsg' => message_handler}

@irc = EM.connect(config[:server], config[:port], IRC::Connection, :config => config)
end

def handle_leave
return unless @irc

@irc.quit('Goodbye!')
@irc.close_connection
end

def handle_message(message)
return unless @irc

@irc.send_message(@irc.channels[0], message[:message])
render encode_json(:message => message[:message], :user => @user, :action => 'message')
end

protected

def encode_json(payload)
Yajl::Encoder.encode(payload)
end

def parse_json(payload)
Yajl::Parser.parse(payload, :symbolize_keys => true)
end

end
8 changes: 8 additions & 0 deletions app/actions/home_action.rb
@@ -0,0 +1,8 @@
class HomeAction < Cramp::Action
@@template = ERB.new(File.read(Snoop::Application.root('app/views/index.erb')))

def start
render @@template.result(binding)
finish
end
end
211 changes: 211 additions & 0 deletions app/views/index.erb
@@ -0,0 +1,211 @@
<html>
<head>
<script src='http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js'></script>
<script src='http://jquery-json.googlecode.com/files/jquery.json-2.2.min.js'></script>
<script src='http://datejs.googlecode.com/svn/trunk/build/date.js'></script>
<script>
$(document).ready(function(){
if (typeof(WebSocket) != 'undefined' || typeof(MozWebSocket)) {
$('#ask').show();
} else {
$('#error').show();
}

// join on enter
$('#ask input').keydown(function(event) {
if (event.keyCode == 13) {
$('#ask a').click();
}
})

// join on click
$('#ask a').click(function() {
join($('#ask input').val());
$('#ask').hide();
$('#channel').show();
$('input#message').focus();
});

function join(name) {
var host = window.location.host.split(':')[0];

var SocketKlass = "MozWebSocket" in window ? MozWebSocket : WebSocket;
var ws = new SocketKlass('ws://<%= request.host_with_port %>/websocket');

var container = $('div#msgs');
ws.onmessage = function(evt) {
var obj = $.evalJSON(evt.data);
if (typeof obj != 'object') return;

var action = obj['action'];
var struct = container.find('li.' + action + ':first');
if (struct.length < 1) {
console.log("Could not handle: " + evt.data);
return;
}

var msg = struct.clone();
msg.find('.time').text((new Date()).toString("HH:mm:ss"));

if (action == 'message') {
var matches;
if (matches = obj['message'].match(/^\s*[\/\\]me\s(.*)/)) {
msg.find('.user').text(obj['user'] + ' ' + matches[1]);
msg.find('.user').css('font-weight', 'bold');
} else {
msg.find('.user').text(obj['user']);
msg.find('.message').text(': ' + obj['message']);
}
} else if (action == 'control') {
msg.find('.user').text(obj['user']);
msg.find('.message').text(obj['message']);
msg.addClass('control');
}

if (obj['user'] == name) msg.find('.user').addClass('self');
container.find('ul').append(msg.show());
container.scrollTop(container.find('ul').innerHeight());
}

$('#channel form').submit(function(event) {
event.preventDefault();
var input = $(this).find(':input');
var msg = input.val();
ws.send($.toJSON({ action: 'message', message: msg }));
input.val('');
});

// send name when joining
ws.onopen = function() {
ws.send($.toJSON({ action: 'join', user: name }));
}
}
});
</script>
<style type="text/css" media="screen">
* {
font-family: Georgia;
}
a {
color: #000;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
div.bordered {
margin: 0 auto;
margin-top: 100px;
width: 600px;
padding: 20px;
text-align: center;
border: 10px solid #ddd;
-webkit-border-radius: 20px;
}
#error {
background-color: #BA0000;
color: #fff;
font-weight: bold;
}
#ask {
font-size: 20pt;
}
#ask input {
font-size: 20pt;
padding: 10px;
margin: 0 10px;
}
#ask span.join {
padding: 10px;
background-color: #ddd;
-webkit-border-radius: 10px;
}
#channel {
margin-top: 100px;
height: 480px;
position: relative;
}
#channel div#descr {
position: absolute;
left: -10px;
top: -190px;
font-size: 13px;
text-align: left;
line-height: 20px;
padding: 5px;
width: 630px;
}
div#msgs {
overflow-y: scroll;
height: 400px;
}
div#msgs ul {
list-style: none;
padding: 0;
margin: 0;
text-align: left;
}
div#msgs li {
line-height: 20px;
}
div#msgs li span.user {
color: #ff9900;
}
div#msgs li span.user.self {
color: #aa2211;
}
div#msgs li span.time {
float: right;
margin-right: 5px;
color: #aaa;
font-family: "Courier New";
font-size: 12px;
}
div#msgs li.control {
text-align: center;
}
div#msgs li.control span.message {
color: #aaa;
}
div#input {
text-align: left;
margin-top: 20px;
}
div#input #message {
width: 600px;
border: 5px solid #bbb;
-webkit-border-radius: 3px;
font-size: 30pt;
}
</style>
</head>
<body>
<div id="error" class="bordered" style="display: none;">
This browser has no native WebSocket support.<br/>
Use a WebKit nightly or Google Chrome.
</div>
<div id="ask" class="bordered" style="display: none;">
Name: <input type="text" id="name" /> <a href="#"><span class="join">Join!</span></a>
</div>
<div id="channel" class="bordered" style="display: none;">
<div id="descr" class="bordered">
#fauna @ irc.freenode.net
</div>
<div id="msgs">
<ul>
<li class="message" style="display: none">
<span class="user"></span><span class="message"></span>
<span class="time"></span>
</li>
<li class="control" style="display: none">
<span class="user"></span>&nbsp;<span class="message"></span>
<span class="time"></span>
</li>
</ul>
</div>
<div id="input">
<form><input type="text" id="message" /></form>
</div>
</div>
</body>
</html>
40 changes: 40 additions & 0 deletions application.rb
@@ -0,0 +1,40 @@
require "rubygems"
require "bundler"

module Snoop
class Application

def self.root(path = nil)
@_root ||= File.expand_path(File.dirname(__FILE__))
path ? File.join(@_root, path.to_s) : @_root
end

def self.env
@_env ||= ENV['RACK_ENV'] || 'development'
end

def self.routes
@_routes ||= eval(File.read('./config/routes.rb'))
end

# Initialize the application
def self.initialize!
Cramp::Websocket.backend = :rainbows
end

end
end

Bundler.require(:default, Snoop::Application.env)

require 'erb'
require 'yaml'
require 'stringio'
require 'yajl'

$: << Snoop::Application.root('lib/ruby-em-irc2/lib')
require 'em-ruby-irc'

# Preload application classes
Dir['./app/**/*.rb'].each {|f| require f}

0 comments on commit daea635

Please sign in to comment.