jschat 0.3.3 → 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
data/bin/jschat-server CHANGED
@@ -4,4 +4,14 @@ require 'logger'
4
4
  require 'jschat/server'
5
5
  require 'jschat/server_options'
6
6
 
7
+ def reload!
8
+ puts 'Reloading Files'
9
+ Gem.clear_paths
10
+ spec = Gem.searcher.find('jschat/server')
11
+ load File.join(spec.full_gem_path, 'lib', 'jschat', 'server.rb')
12
+ load File.join(spec.full_gem_path, 'lib', 'jschat', 'server_options.rb')
13
+ end
14
+
15
+ trap 'SIGHUP', lambda { reload! }
16
+
7
17
  JsChat::Server.run!
data/lib/jschat/client.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'rubygems'
2
2
  require 'eventmachine'
3
+ gem 'json', '>= 1.1.9'
3
4
  require 'json'
4
5
  require 'ncurses'
5
6
  require 'optparse'
@@ -1,6 +1,7 @@
1
1
  require 'rubygems'
2
2
  require 'sinatra'
3
3
  require 'sha1'
4
+ gem 'json', '>= 1.1.9'
4
5
  require 'json'
5
6
  require 'sprockets'
6
7
  require 'jschat/init'
@@ -59,6 +60,10 @@ before do
59
60
 
60
61
  if twitter_user?
61
62
  load_twitter_user_and_set_bridge_id
63
+
64
+ unless valid_twitter_client_id?
65
+ clear_cookies
66
+ end
62
67
  end
63
68
  end
64
69
  end
@@ -177,11 +182,25 @@ helpers do
177
182
  end
178
183
 
179
184
  def detected_layout
180
- iphone_user_agent? ? :iphone : :layout
185
+ if iphone_user_agent?
186
+ :iphone
187
+ elsif ipad_user_agent?
188
+ :ipad
189
+ else
190
+ :layout
191
+ end
192
+ end
193
+
194
+ def detected_message_form
195
+ iphone_user_agent? ? :iphone_message_form : :message_form
181
196
  end
182
197
 
183
198
  def iphone_user_agent?
184
- request.env["HTTP_USER_AGENT"] && request.env["HTTP_USER_AGENT"][/(Mobile\/.+Safari)/]
199
+ request.env["HTTP_USER_AGENT"] && request.env["HTTP_USER_AGENT"][/(\(iPhone)/]
200
+ end
201
+
202
+ def ipad_user_agent?
203
+ request.env["HTTP_USER_AGENT"] && request.env["HTTP_USER_AGENT"][/(\(iPad)/]
185
204
  end
186
205
 
187
206
  def load_bridge
@@ -238,7 +257,8 @@ helpers do
238
257
  options = load_twitter_user.merge(options).merge({
239
258
  'twitter_name' => session[:twitter_name],
240
259
  'access_token' => session[:access_token],
241
- 'secret_token' => session[:secret_token]
260
+ 'secret_token' => session[:secret_token],
261
+ 'client_id' => session[:client_id]
242
262
  })
243
263
  JsChat::Storage.driver.save_user(options)
244
264
  end
@@ -258,6 +278,10 @@ helpers do
258
278
  JsChat::Storage.driver.find_user({ 'twitter_name' => session[:twitter_name] }) || {}
259
279
  end
260
280
 
281
+ def valid_twitter_client_id?
282
+ session[:client_id] == load_twitter_user['client_id']
283
+ end
284
+
261
285
  def load_twitter_user_and_set_bridge_id
262
286
  user = load_twitter_user
263
287
  if user['jschat_id'] and user['jschat_id'].size > 0
@@ -268,6 +292,11 @@ helpers do
268
292
  def nickname
269
293
  request.cookies['jschat-name']
270
294
  end
295
+
296
+ def unique_token
297
+ chars = ("a".."z").to_a + ("1".."9").to_a
298
+ Array.new(8, '').collect { chars[rand(chars.size)] }.join
299
+ end
271
300
  end
272
301
 
273
302
  # Identify
@@ -311,8 +340,10 @@ get '/messages' do
311
340
  else
312
341
  if @bridge.last_error and @bridge.last_error['error']['code'] == 107
313
342
  error 500, [@bridge.last_error].to_json
314
- else
343
+ elsif @bridge.last_error
315
344
  [@bridge.last_error].to_json
345
+ else
346
+ error 500, 'Unknown error'
316
347
  end
317
348
  end
318
349
  end
@@ -367,7 +398,7 @@ end
367
398
  get '/chat/' do
368
399
  load_bridge
369
400
  if @bridge and @bridge.active?
370
- erb :message_form, :layout => detected_layout
401
+ erb detected_message_form, :layout => detected_layout
371
402
  else
372
403
  erb :index, :layout => detected_layout
373
404
  end
@@ -375,7 +406,7 @@ end
375
406
 
376
407
  post '/message' do
377
408
  load_bridge
378
- save_last_room params['room']
409
+ save_last_room params['to']
379
410
  @bridge.send_message params['message'], params['to']
380
411
  'OK'
381
412
  end
@@ -424,6 +455,7 @@ get '/twitter_auth' do
424
455
  )
425
456
  rescue OAuth::Unauthorized => exception
426
457
  puts exception
458
+ halt "Unable to login with Twitter: #{exception.class}"
427
459
  end
428
460
 
429
461
  if @twitter.authorized?
@@ -441,6 +473,7 @@ get '/twitter_auth' do
441
473
  end
442
474
 
443
475
  session[:jschat_id] = user['jschat_id'] if user['jschat_id'] and !user['jschat_id'].empty?
476
+ session[:client_id] = unique_token
444
477
  save_nickname name
445
478
  save_twitter_user('twitter_name' => @twitter.info['screen_name'],
446
479
  'jschat_id' => session[:jschat_id],
@@ -475,6 +508,15 @@ get '/twitter_auth' do
475
508
  end
476
509
  end
477
510
 
511
+ # TODO: This doesn't seem to work with twitter oauth right now
512
+ post '/tweet' do
513
+ if twitter_user? and @twitter.authorized?
514
+ @twitter.update(params['tweet'])
515
+ else
516
+ error 500, 'You are not signed in with Twitter'
517
+ end
518
+ end
519
+
478
520
  # This serves the JavaScript concat'd by Sprockets
479
521
  # run script/sprocket.rb to cache this
480
522
  get '/javascripts/all.js' do
@@ -99,6 +99,8 @@ JsChat.ChatController = Class.create({
99
99
  var message = $('message').value;
100
100
  $('message').value = '';
101
101
 
102
+ this.tabCompletion.history.add(message);
103
+
102
104
  if (message.length > 0) {
103
105
  var command_posted = $H(UserCommands).find(function(command) {
104
106
  var name = command[0];
@@ -110,14 +112,12 @@ JsChat.ChatController = Class.create({
110
112
  }.bind(this));
111
113
 
112
114
  if (!command_posted) {
113
- if (message.match(/^\//)) {
115
+ if (message.match(/^\/\s?\//)) {
116
+ this.postMessage(message.replace(/\//, '').strip());
117
+ } else if (message.match(/^\//)) {
114
118
  Display.add_message('Error: Command not found. Use /help display commands.', 'error');
115
119
  } else {
116
- Display.message({ 'message': message.escapeHTML(), 'user': JsChat.user.name }, new Date());
117
- new Ajax.Request('/message', {
118
- method: 'post',
119
- parameters: { 'message': message, 'to': PageHelper.currentRoom() }
120
- });
120
+ this.postMessage(message);
121
121
  }
122
122
  }
123
123
  }
@@ -129,6 +129,21 @@ JsChat.ChatController = Class.create({
129
129
  return false;
130
130
  },
131
131
 
132
+ postMessage: function(message) {
133
+ Display.message({ 'message': message.escapeHTML(), 'user': JsChat.user.name }, new Date());
134
+ new Ajax.Request('/message', {
135
+ method: 'post',
136
+ parameters: { 'message': message, 'to': PageHelper.currentRoom() }
137
+ });
138
+ },
139
+
140
+ sendTweet: function(message) {
141
+ new Ajax.Request('/tweet', {
142
+ method: 'post',
143
+ parameters: { 'tweet': message }
144
+ });
145
+ },
146
+
132
147
  initDisplay: function() {
133
148
  Display.unread = 0;
134
149
  Display.show_unread = false;
@@ -293,7 +308,7 @@ JsChat.ChatController = Class.create({
293
308
  },
294
309
 
295
310
  updateNames: function() {
296
- UserCommands['/names'].apply(this);
311
+ JsChat.Request.get('/names', function(t) { this.displayMessages(t.responseText); }.bind(this));
297
312
  },
298
313
 
299
314
  showMessagesResponse: function(transport) {
@@ -15,6 +15,15 @@ var LinkHelper = {
15
15
  return '<a href="\#{url}" target="_blank"><img class="inline-image" src="\#{image}" /></a>'.interpolate({ url: url, image: url })
16
16
  },
17
17
 
18
+ twitpic_url: function(url) {
19
+ return url.match(/\bhttp:\/\/twitpic.com\/(show|[^\s]*)\b/i);
20
+ },
21
+
22
+ twitpic: function(url) {
23
+ var twitpic_id = url.split('/').last();
24
+ return '<a href="\#{url}" target="_blank"><img class="inline-image" src="http://twitpic.com/show/mini/\#{twitpic_id}" /></a>'.interpolate({ twitpic_id: twitpic_id, url: url })
25
+ },
26
+
18
27
  youtube_url: function(url) {
19
28
  return url.match(/youtube\.com/) && url.match(/watch\?v/);
20
29
  },
@@ -78,6 +78,8 @@ var TextHelper = {
78
78
  result += link.replace(link, LinkHelper.vimeo(link));
79
79
  } else if (LinkHelper.image_url(link) && !JsChat.user.hideImages) {
80
80
  result += link.replace(link, LinkHelper.image(link));
81
+ } else if (LinkHelper.twitpic_url(link) && !JsChat.user.hideImages) {
82
+ result += link.replace(link, LinkHelper.twitpic(link));
81
83
  } else if (LinkHelper.url(link)) {
82
84
  result += link.replace(link, LinkHelper.link(link));
83
85
  } else {
@@ -6,7 +6,6 @@ User = function() {
6
6
  User.prototype.setName = function(name) {
7
7
  Cookie.create('jschat-name', name, 28, '/');
8
8
  this.name = name;
9
- $('name').innerHTML = name;
10
9
  };
11
10
 
12
11
  User.prototype.setHideImages = function(hideImages) {
@@ -58,6 +58,7 @@ var UserCommands = {
58
58
  onSuccess: function(response) {
59
59
  this.displayMessages(response.responseText);
60
60
  JsChat.user.setName(name);
61
+ this.updateNames();
61
62
  }.bind(this),
62
63
  onFailure: function() {
63
64
  Display.add_message("Server error: couldn't access: #{url}".interpolate({ url: url }), 'server');
@@ -66,7 +67,7 @@ var UserCommands = {
66
67
  },
67
68
 
68
69
  '/names': function() {
69
- JsChat.Request.get('/names', function(t) { this.displayMessages(t.responseText); }.bind(this));
70
+ this.updateNames();
70
71
  },
71
72
 
72
73
  '/toggle images': function() {
@@ -88,6 +89,11 @@ var UserCommands = {
88
89
  this.partRoom(room);
89
90
  },
90
91
 
92
+ '/tweet\\s+(.*)': function() {
93
+ var message = arguments[0][1];
94
+ this.sendTweet(message);
95
+ },
96
+
91
97
  '/quit': function() {
92
98
  window.location = '/quit';
93
99
  }
@@ -1,3 +1,42 @@
1
+ var History = Class.create({
2
+ initialize: function() {
3
+ this.messages = [];
4
+ this.index = 0;
5
+ this.limit = 100;
6
+ },
7
+
8
+ prev: function() {
9
+ this.index = this.index <= 0 ? this.messages.length - 1 : this.index - 1;
10
+ },
11
+
12
+ next: function() {
13
+ this.index = this.index >= this.messages.length - 1 ? 0 : this.index + 1;
14
+ },
15
+
16
+ reset: function() {
17
+ this.index = this.messages.length;
18
+ },
19
+
20
+ value: function() {
21
+ if (this.messages.length == 0) return '';
22
+ return this.messages[this.index];
23
+ },
24
+
25
+ add: function(value) {
26
+ if (!value || value.length == 0) return;
27
+
28
+ this.messages.push(value);
29
+ if (this.messages.length > this.limit) {
30
+ this.messages = this.messages.slice(-this.limit);
31
+ }
32
+ this.index = this.messages.length;
33
+ },
34
+
35
+ atTop: function() {
36
+ return this.index === this.messages.length;
37
+ }
38
+ });
39
+
1
40
  var TabCompletion = Class.create({
2
41
  initialize: function(element) {
3
42
  this.element = $(element);
@@ -5,6 +44,7 @@ var TabCompletion = Class.create({
5
44
  this.match_offset = 0;
6
45
  this.cycling = false;
7
46
  this.has_focus = true;
47
+ this.history = new History();
8
48
 
9
49
  document.observe('keydown', this.keyboardEvents.bindAsEventListener(this));
10
50
  this.element.observe('focus', this.onFocus.bindAsEventListener(this));
@@ -23,7 +63,7 @@ var TabCompletion = Class.create({
23
63
  },
24
64
 
25
65
  tabSearch: function(input) {
26
- var names = $$('#names li').collect(function(element) { return element.innerHTML });
66
+ var names = $$('#names li').collect(function(element) { return element.innerHTML }).sort();
27
67
  return names.findAll(function(name) { return name.toLowerCase().match(input.toLowerCase()) });
28
68
  },
29
69
 
@@ -106,6 +146,26 @@ var TabCompletion = Class.create({
106
146
  return false;
107
147
  break;
108
148
 
149
+ case Event.KEY_UP:
150
+ if (this.history.atTop()) {
151
+ this.history.add(this.element.value);
152
+ }
153
+
154
+ this.history.prev();
155
+ this.element.value = this.history.value();
156
+ FormHelpers.setCaretPosition(this.element, this.element.value.length + 1);
157
+ Event.stop(e);
158
+ return false;
159
+ break;
160
+
161
+ case Event.KEY_DOWN:
162
+ this.history.next();
163
+ this.element.value = this.history.value();
164
+ FormHelpers.setCaretPosition(this.element, this.element.value.length + 1);
165
+ Event.stop(e);
166
+ return false;
167
+ break;
168
+
109
169
  default:
110
170
  this.reset();
111
171
  break;
@@ -0,0 +1,32 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4
+ <head>
5
+ <title>JsChat iPad</title>
6
+ <link rel="icon" href="/favicon.ico" />
7
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
8
+ <meta name="apple-mobile-web-app-capable" content="yes" />
9
+ <script src="http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.3/prototype.js" type="text/javascript"></script>
10
+ <script src="/javascripts/all.js?<%= Time.now.to_i %>" type="text/javascript"></script>
11
+ <link href="/stylesheets/screen.css?<%= Time.now.to_i %>" media="screen" rel="stylesheet" type="text/css" />
12
+ </head>
13
+ <body class="ipad">
14
+ <div id="loading" style="display: none">Loading...</div>
15
+ <div class="header">
16
+ <h1><a href="/"><img src="/images/jschat.gif" alt="JsChat" /></a></h1>
17
+ <ul id="rooms" class="rooms" style="display: none">
18
+ <li class="join"><a href="#">+</a></li>
19
+ </ul>
20
+ <ul class="navigation">
21
+ <li><a href="/">Home</a></li>
22
+ <li><a href="http://github.com/alexyoung/jschat">Download</a>
23
+ <li id="help-nav" style="display: none"><a href="#" id="help-link">Help</a>
24
+ <li id="quit-nav" style="display: none"><a href="/quit">Quit</a>
25
+ </ul>
26
+ </div>
27
+ <div class="header-shadow"></div>
28
+ <div class="page">
29
+ <%= yield %>
30
+ </div>
31
+ </body>
32
+ </html>
@@ -5,12 +5,13 @@
5
5
  <title>JsChat iPhone</title>
6
6
  <link rel="icon" href="/favicon.ico" />
7
7
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
8
+ <meta name="apple-mobile-web-app-capable" content="yes" />
8
9
  <script src="http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.3/prototype.js" type="text/javascript"></script>
9
10
  <script src="/javascripts/all.js?<%= Time.now.to_i %>" type="text/javascript"></script>
10
11
  <link href="/stylesheets/screen.css?<%= Time.now.to_i %>" media="screen" rel="stylesheet" type="text/css" />
11
12
  <link href="/stylesheets/iphone.css?<%= Time.now.to_i %>" media="screen" rel="stylesheet" type="text/css" />
12
13
  </head>
13
- <body class="iphone">
14
+ <body class="iphone" onload="setTimeout(function() { window.scrollTo(0, 1); }, 100);">
14
15
  <div id="loading" style="display: none">Loading...</div>
15
16
  <div class="header">
16
17
  <h1><a href="/"><img src="/images/jschat.gif" alt="JsChat" /></a></h1>
@@ -0,0 +1,15 @@
1
+ <ul id="messages">
2
+ </ul>
3
+ <div id="info">
4
+ <h2 id="room-name">Loading...</h2>
5
+ <div id="name" style="display: none"><%= nickname %></div>
6
+ <ul id="names">
7
+ </ul>
8
+ </div>
9
+ <div id="input">
10
+ <form method="post" action="/message" id="post_message">
11
+ <input name="message" id="message" value="" type="text" autocomplete="off" autocapitalize="off" />
12
+ <input name="submit" type="submit" id="send_button" value="Send" />
13
+ </form>
14
+ </div>
15
+
@@ -2,7 +2,6 @@
2
2
  </ul>
3
3
  <div id="info">
4
4
  <h2 id="room-name">Loading...</h2>
5
- <div id="name" style="display: none"><%= nickname %></div>
6
5
  <ul id="names">
7
6
  </ul>
8
7
  </div>
data/lib/jschat/server.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'rubygems'
2
2
  require 'eventmachine'
3
+ gem 'json', '>= 1.1.9'
3
4
  require 'json'
4
5
  require 'time'
5
6
  require 'socket'
data/test/test_helper.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'test/unit'
2
2
  require 'rubygems'
3
3
  require 'eventmachine'
4
+ gem 'json', '>= 1.1.9'
4
5
  require 'json'
5
6
  $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
6
7
  require File.join(File.dirname(__FILE__), '..', 'lib', 'jschat', 'server.rb')
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 3
8
- - 3
9
- version: 0.3.3
8
+ - 5
9
+ version: 0.3.5
10
10
  platform: ruby
11
11
  authors:
12
12
  - Alex R. Young
@@ -183,7 +183,9 @@ files:
183
183
  - lib/jschat/http/tmp/restart.txt
184
184
  - lib/jschat/http/views/form.erb
185
185
  - lib/jschat/http/views/index.erb
186
+ - lib/jschat/http/views/ipad.erb
186
187
  - lib/jschat/http/views/iphone.erb
188
+ - lib/jschat/http/views/iphone_message_form.erb
187
189
  - lib/jschat/http/views/layout.erb
188
190
  - lib/jschat/http/views/message_form.erb
189
191
  - lib/jschat/http/views/twitter.erb
@@ -198,7 +200,7 @@ files:
198
200
  - test/test_helper.rb
199
201
  - README.textile
200
202
  - MIT-LICENSE
201
- has_rdoc: true
203
+ has_rdoc: false
202
204
  homepage: http://github.com/alexyoung/jschat
203
205
  licenses: []
204
206