jschat 0.3.3 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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