firering 0.1.1 → 1.0.0

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/README.rdoc CHANGED
@@ -4,31 +4,29 @@ Campfire API interface powered by EventMachine and Yajl.
4
4
 
5
5
  require 'firering'
6
6
 
7
- Firering.subdomain = "subdomain"
8
- Firering.token = "token"
7
+ print "Enter subdomain: "; subdomain = gets.chomp
8
+ print "Enter user: " ; login = gets.chomp
9
+ print "Enter password: " ; password = gets.chomp
9
10
 
10
- EM.run do
11
- Firering.rooms do |rooms|
12
- rooms.each do |room|
13
- if room.name == "RoomName"
14
-
15
- puts "Joining #{room.name}"
11
+ conn = Firering::Connection.new("http://#{subdomain}.campfirenow.com") do |c|
12
+ c.login = login
13
+ c.password = password
14
+ end
16
15
 
17
- Firering.room_join(room.id) do
18
- Firering.stream(room.id) do |message|
16
+ EM.run do
17
+ conn.authenticate do |user|
18
+ conn.rooms do |rooms|
19
19
 
20
- if message.from_user?
21
- Firering.user(message.user_id) do |user|
22
- puts "( #{user.name} ) #{message.body}"
23
- end
24
- else
25
- puts message.body if message.body.to_s !~ /^\s*$/
26
- end
20
+ rooms.each do |room|
21
+ if room.name == "Room Name"
27
22
 
23
+ room.stream do |message|
24
+ message.user { |user| puts "#{user}: #{message}" }
28
25
  end
29
- end
30
26
 
27
+ end
31
28
  end
29
+
32
30
  end
33
31
  end
34
32
 
@@ -37,10 +35,10 @@ Campfire API interface powered by EventMachine and Yajl.
37
35
 
38
36
  = campf-notify
39
37
 
40
- The gem bundles an executable script for spawning libnotify powered notifications.
41
- To be able to use it, check your distro package repositories for the apropriate
42
- package containing the "notify-send" command line utility. In the case of archlinux,
43
- the package name is "libnotify".
38
+ The gem bundles an executable script for spawning libnotify powered
39
+ notifications. To be able to use it, check your distro package repositories
40
+ for the apropriate package containing the "notify-send" command line utility.
41
+ In the case of archlinux, the package name is "libnotify".
44
42
 
45
43
  The script needs the following environment variables in place:
46
44
 
@@ -55,10 +53,12 @@ And watch the lovely notifications each time something is posted to a room.
55
53
 
56
54
  == Running the specs
57
55
 
58
- For running the specs, I opted for running a sinatra application for serving
59
- the fixture data. The server runs automatically when "rake spec" is executed,
60
- it runs on the standard sinatra port (4567), so you should make sure that
61
- port is free when running the specs.
56
+ When the specs are run a process is forked where a Rack application is run.
57
+ This rack application serves all the fixtured responses that mimic the working
58
+ of the real campfire app. The only caveat here is you may get a conflict if you
59
+ are already running something on the port (8909). If this happens you'll need
60
+ to open the specs/spec_helper.rb file and change the port used to run the
61
+ fixtures server.
62
62
 
63
63
  For more details take a look at spec/fixtures/load_server.rb file.
64
64
 
data/Rakefile CHANGED
@@ -92,7 +92,7 @@ task :release => :build do
92
92
  sh "git commit --allow-empty -a -m 'Release #{version}'"
93
93
  sh "git tag v#{version}"
94
94
  sh "git push origin master"
95
- sh "git push v#{version}"
95
+ sh "git push --tags"
96
96
  sh "gem push pkg/#{name}-#{version}.gem"
97
97
  end
98
98
 
data/bin/campf-notify CHANGED
@@ -3,51 +3,45 @@
3
3
  require 'firering'
4
4
  require 'shellwords'
5
5
 
6
- Firering.subdomain = ENV["CAMPFIRE_SUBDOMAIN"].to_s
7
- Firering.token = ENV["CAMPFIRE_TOKEN"].to_s
6
+ subdomain, token = ENV["CAMPFIRE_SUBDOMAIN"], ENV["CAMPFIRE_TOKEN"]
7
+ room_name, icon_path = ARGV
8
8
 
9
- room_name = ARGV[0].to_s
10
- icon_path = ARGV[1].to_s
11
-
12
- if Firering.subdomain =~ /^\s*$/ || Firering.token =~ /^\s*$/ || room_name =~ /^\s*$/ || !File.exists?(icon_path)
13
- puts "USAGE: campf-notify room-name notify-send-icon-path"
9
+ if subdomain.to_s =~ /^\s*$/ || token.to_s =~ /^\s*$/ || room_name.to_s =~ /^\s*$/ || icon_path.nil? || !File.file?(icon_path)
10
+ puts "USAGE: campf-notify room-name icon-path"
11
+ puts
12
+ puts "Icon path #{icon_path} is incorrect" unless icon_path && File.exists?(icon_path)
13
+ puts "Room name #{room_name} is missing" unless room_name =~ /\S/
14
14
  puts
15
- puts "Icon path #{icon_path} is incorrect\n" unless File.exists?(icon_path)
16
- puts "Room name #{room_name} is missing" unless room_name !~ /^\s*$/
17
-
18
15
  puts "The following environment variables are needed: "
19
16
  puts
20
- puts "CAMPFIRE_SUBDOMAIN: #{Firering.subdomain}"
21
- puts "CAMPFIRE_TOKEN: #{Firering.token}"
17
+ puts "CAMPFIRE_SUBDOMAIN: #{subdomain || 'missing!'}"
18
+ puts "CAMPFIRE_TOKEN: #{token || 'missing!'}"
22
19
  puts
23
- exit
20
+ exit 1
24
21
  else
25
- puts "connecting to #{Firering.subdomain}.#{Firering::HTTP.host}"
22
+ host = "http://#{subdomain}.campfirenow.com"
23
+ puts "connecting to #{host}"
26
24
  end
27
25
 
28
- EM.run do
29
- Firering.rooms do |rooms|
30
- rooms.each do |room|
31
- if room.name == room_name
26
+ conn = Firering::Connection.new(host) {|c| c.token = token }
32
27
 
33
- puts "Joining #{room.name}"
34
-
35
- Firering.room_join(room.id) do
36
- Firering.stream(room.id) do |message|
28
+ EM.run do
29
+ conn.rooms do |rooms|
30
+ room = rooms.detect { |r| r.name == room_name }
37
31
 
38
- if message.from_user?
39
- Firering.user(message.user_id) do |user|
40
- m = "( #{user.name} ) #{message.body}"
41
- system("notify-send -i #{icon_path.shellescape} -t 10000 Campfire #{m.shellescape}")
42
- end
43
- else
44
- m = message.body.to_s
45
- system("notify-send -i #{icon_path.shellescape} -t 10000 Campfire #{m.shellescape}") if m !~ /^\s*$/
46
- end
32
+ unless room
33
+ puts "Could not find that room. Found rooms:"
34
+ rooms.each { |room| puts " * #{room}" }
35
+ exit 1
36
+ end
47
37
 
48
- end
49
- end
38
+ puts "Streaming #{room}"
50
39
 
40
+ room.stream do |message|
41
+ if message.from_user?
42
+ message.user { |user| system("notify-send -i #{icon_path.shellescape} -t 10000 Campfire #{ "( #{user} ) #{message}".shellescape }") }
43
+ else
44
+ system("notify-send -i #{icon_path.shellescape} -t 10000 Campfire #{message.to_s.shellescape}") if message.to_s =~ /\S/
51
45
  end
52
46
  end
53
47
  end
@@ -2,19 +2,21 @@ require 'firering'
2
2
 
3
3
  puts "If you prefer it, you can authenticate your user with u/p instead of the campfire token."
4
4
 
5
- print "Enter subdomain: "
6
- subdomain = gets.chomp
5
+ print "Enter subdomain: "; subdomain = gets.chomp
6
+ print "Enter user: " ; login = gets.chomp
7
+ print "Enter password: " ; password = gets.chomp
7
8
 
8
- print "Enter user: "
9
- user = gets.chomp
10
-
11
- print "Enter password: "
12
- password = gets.chomp
9
+ conn = Firering::Connection.new("http://#{subdomain}.campfirenow.com") do |c|
10
+ c.login = login
11
+ c.password = password
12
+ end
13
13
 
14
14
  EM.run do
15
- Firering.authenticate(subdomain, user, password) do |user|
15
+ conn.authenticate do |user|
16
16
 
17
17
  puts "Token for user #{user.name} is #{user.token}"
18
+ puts "You can set an environment variable for using it on scripts:"
19
+ puts "export CAMPFIRE_TOKEN=#{user.token}"
18
20
 
19
21
  EM.stop
20
22
 
data/examples/events.rb CHANGED
@@ -1,15 +1,21 @@
1
1
  require 'firering'
2
2
 
3
- Firering.subdomain = ENV["CAMPFIRE_SUBDOMAIN"]
4
- Firering.token = ENV["CAMPFIRE_TOKEN"]
3
+ print "Enter subdomain: "; subdomain = gets.chomp
4
+ print "Enter user: " ; login = gets.chomp
5
+ print "Enter password: " ; password = gets.chomp
6
+
7
+ conn = Firering::Connection.new("http://#{subdomain}.campfirenow.com") do |c|
8
+ c.login = login
9
+ c.password = password
10
+ end
5
11
 
6
12
  EM.run do
7
- Firering.rooms do |rooms|
8
- rooms.each do |room|
9
- if room.name == "test2"
10
- Firering.room_join(room.id) do
11
- Firering.stream(room.id) do |message|
13
+ conn.authenticate do |user|
14
+ conn.rooms do |rooms|
15
+ rooms.each do |room|
12
16
 
17
+ if room.name == "test2"
18
+ room.stream do |message|
13
19
  case
14
20
  when message.advertisement? then puts "Handle Ad"
15
21
  when message.allow_guests? then puts "Handle Allow Guests"
@@ -28,9 +34,12 @@ EM.run do
28
34
  when message.upload? then puts "Handle Upload"
29
35
  end
30
36
 
31
- puts message.to_s
37
+ message.user do |user|
38
+ puts "#{user}: #{message}"
39
+ end
32
40
  end
33
41
  end
42
+
34
43
  end
35
44
  end
36
45
  end
@@ -1,41 +1,53 @@
1
1
  require 'firering'
2
2
 
3
- Firering.subdomain = ENV["CAMPFIRE_SUBDOMAIN"]
4
- Firering.token = ENV["CAMPFIRE_TOKEN"]
3
+ print "Enter subdomain: "; subdomain = gets.chomp
4
+ print "Enter user: " ; login = gets.chomp
5
+ print "Enter password: " ; password = gets.chomp
5
6
 
6
- EM.run do
7
- Firering.rooms do |rooms|
8
-
9
- rooms.each do |room|
10
- puts "Users in room #{room.name} (#{room.topic})"
7
+ conn = Firering::Connection.new("http://#{subdomain}.campfirenow.com") do |c|
8
+ c.login = login
9
+ c.password = password
10
+ end
11
11
 
12
- if room.users.empty?
13
- puts " empty (locked: #{room.locked?})"
14
- else
15
- room.users.each do |u|
16
- puts " #{ u.name }. Admin: #{ u.admin? }"
12
+ EM.run do
13
+ conn.authenticate do
14
+ conn.rooms do |rooms|
15
+
16
+ rooms.each do |room|
17
+ puts "Users in room #{room.name} (#{room.topic})"
18
+
19
+ room.users do |users|
20
+ if users.empty?
21
+ puts " empty (locked: #{room.locked?})"
22
+ else
23
+ users.each do |u|
24
+ puts " #{ u.name }. Admin: #{ u.admin? }"
25
+ end
26
+ end
17
27
  end
18
- end
19
28
 
20
- if room.locked?
21
- puts " can't get recent messages in a locked room'"
22
- else
23
- Firering.room_recent_messages(room.id) do |messages|
29
+ if room.locked?
30
+ puts " can't get recent messages in a locked room'"
31
+ else
32
+ room.recent_messages do |messages|
24
33
 
25
- puts "-" * 80
26
- puts "recent message on #{room.name}"
27
- puts "-" * 80
34
+ puts "-" * 80
35
+ puts "recent message on #{room.name}"
36
+ puts "-" * 80
28
37
 
29
- messages.slice(0, 4).each do |m|
30
- puts "\n (#{m.user_id})"
38
+ messages.slice(0, 4).each do |m|
39
+ m.user do |u|
40
+ puts "\n (#{u})"
31
41
 
32
- m.body.to_s.split("\n").each do |chunk|
33
- puts " > #{chunk}"
42
+ m.body.to_s.split("\n").each do |chunk|
43
+ puts " > #{chunk}"
44
+ end
45
+ end
34
46
  end
35
47
  end
36
48
  end
37
- end
38
49
 
50
+ end
39
51
  end
40
52
  end
41
53
 
data/examples/rooms.rb CHANGED
@@ -1,20 +1,25 @@
1
1
  require 'firering'
2
2
 
3
- Firering.subdomain = ENV["CAMPFIRE_SUBDOMAIN"]
4
- Firering.token = ENV["CAMPFIRE_TOKEN"]
3
+ conn = Firering::Connection.new("http://#{ENV["CAMPFIRE_SUBDOMAIN"]}.campfirenow.com") do |c|
4
+ c.token = ENV["CAMPFIRE_TOKEN"]
5
+ end
5
6
 
6
7
  EM.run do
7
- Firering.rooms do |rooms|
8
-
8
+ conn.rooms do |rooms|
9
9
  rooms.each do |room|
10
- puts "Users in room #{room.name}"
11
10
 
12
- if room.users.empty?
13
- puts " empty (locked: #{room.locked?})"
14
- else
15
- room.users.each do |u|
16
- puts " #{ u.name }"
11
+ room.users do |users|
12
+ puts "Users in room #{room.name}"
13
+
14
+ if users.empty?
15
+ puts " empty (locked: #{room.locked?})"
16
+ else
17
+ users.each do |u|
18
+ puts " #{ u.name }"
19
+ end
17
20
  end
21
+
22
+ puts
18
23
  end
19
24
 
20
25
  end
@@ -1,31 +1,34 @@
1
1
  require 'firering'
2
2
 
3
- Firering.subdomain = ENV["CAMPFIRE_SUBDOMAIN"]
4
- Firering.token = ENV["CAMPFIRE_TOKEN"]
3
+ conn = Firering::Connection.new("http://#{ENV["CAMPFIRE_SUBDOMAIN"]}.campfirenow.com") do |c|
4
+ c.token = ENV["CAMPFIRE_TOKEN"]
5
+ end
5
6
 
6
7
  ROOM = "test2"
7
8
 
8
9
  EM.run do
9
- Firering.rooms do |rooms|
10
+ conn.rooms do |rooms|
10
11
  rooms.each do |room|
12
+
11
13
  if room.name == ROOM
12
- Firering.room_join(room.id) do
14
+ room.join do
13
15
 
14
- Firering.update_room(room.id, "topic" => "test test test") do |response|
15
- puts " * Updating topic: "
16
- puts response.response_header.status
16
+ room.update("topic" => "test test test") do |data, http|
17
+ print " * Updating topic. HTTP Status returned: "
18
+ puts http.response_header.status
17
19
  end
18
20
 
19
- Firering.text(room.id, "hola") do
20
- puts "texted"
21
+ room.text("this is a test from the refactored gem") do
22
+ puts "sent text"
21
23
  end
22
24
 
23
- Firering.paste(room.id, "hola\nmundo") do
24
- puts "pasted"
25
+ room.paste("this is a\npaste\nfrom the refactored gem") do
26
+ puts "sent paste"
25
27
  end
26
28
 
27
29
  end
28
30
  end
31
+
29
32
  end
30
33
  end
31
34
 
data/firering.gemspec CHANGED
@@ -4,8 +4,8 @@ Gem::Specification.new do |s|
4
4
  s.rubygems_version = '1.3.5'
5
5
 
6
6
  s.name = 'firering'
7
- s.version = '0.1.1'
8
- s.date = '2010-08-08'
7
+ s.version = '1.0.0'
8
+ s.date = '2010-08-16'
9
9
  s.rubyforge_project = 'firering'
10
10
 
11
11
  s.summary = "Campfire API interface powered by EventMachine and Yajl."
@@ -43,16 +43,19 @@ Gem::Specification.new do |s|
43
43
  examples/update_room.rb
44
44
  firering.gemspec
45
45
  lib/firering.rb
46
+ lib/firering/connection.rb
46
47
  lib/firering/data.rb
47
48
  lib/firering/data/message.rb
48
49
  lib/firering/data/room.rb
49
50
  lib/firering/data/upload.rb
50
51
  lib/firering/data/user.rb
51
- lib/firering/http.rb
52
52
  lib/firering/requests.rb
53
- lib/firering/streaming.rb
53
+ log/.gitignore
54
+ spec/firering/connection_spec.rb
55
+ spec/firering/data/message_spec.rb
56
+ spec/firering/data/room_spec.rb
57
+ spec/firering/data/user_spec.rb
54
58
  spec/firering/data_spec.rb
55
- spec/firering/requests_spec.rb
56
59
  spec/fixtures/headers/delete_messages_ID_star.json
57
60
  spec/fixtures/headers/get_room_ID.json
58
61
  spec/fixtures/headers/get_room_ID_live.json
@@ -90,8 +93,8 @@ Gem::Specification.new do |s|
90
93
  spec/fixtures/json/put_room_ID.json
91
94
  spec/fixtures/load_server.rb
92
95
  spec/fixtures/retrieve.rb
93
- spec/fixtures/server.rb
94
96
  spec/spec_helper.rb
97
+ spec/support/helpers.rb
95
98
  ]
96
99
  # = MANIFEST =
97
100
 
@@ -0,0 +1,125 @@
1
+ module Firering
2
+ class Connection
3
+ include Firering::Requests
4
+
5
+ attr_accessor :host
6
+ attr_accessor :streaming_host
7
+ attr_accessor :max_retries
8
+ attr_accessor :retry_delay
9
+ attr_accessor :redirects
10
+ attr_accessor :logger
11
+ attr_accessor :password
12
+ attr_accessor :token
13
+ attr_accessor :login
14
+
15
+ def initialize(host, streaming_host = "https://streaming.campfirenow.com")
16
+ @retry_delay, @redirects, @max_retries = 2, 1, 2
17
+ self.host, self.streaming_host = host, streaming_host
18
+ yield self if block_given?
19
+ end
20
+
21
+ def logger
22
+ @logger ||= Logger.new(STDOUT)
23
+ end
24
+
25
+ def host=(host)
26
+ @host = Addressable::URI.parse(host)
27
+ end
28
+
29
+ def streaming_host=(host)
30
+ @streaming_host = Addressable::URI.parse(host)
31
+ end
32
+
33
+ def auth_headers
34
+ token ? {'authorization' => [token, "X"] } : {'authorization' => [login, password] }
35
+ end
36
+
37
+ def parameters(data = nil)
38
+ parameters = {
39
+ :redirects => redirects,
40
+ :head => auth_headers.merge("Content-Type" => "application/json")
41
+ }
42
+ parameters.merge!(:body => data.is_a?(String) ? data : Yajl::Encoder.encode(data)) if data
43
+ parameters
44
+ end
45
+
46
+ def http(method, path, data = nil, &callback)
47
+ uri = host.join(path)
48
+ logger.info("performing request to #{uri}")
49
+
50
+ http = EventMachine::HttpRequest.new(uri).send(method, parameters(data))
51
+
52
+ http.errback do
53
+ perform_retry(http) do
54
+ http(method, path, data, &callback)
55
+ end
56
+ end
57
+
58
+ http.callback {
59
+ @performed_retries = 0
60
+ if callback
61
+ data = Yajl::Parser.parse(http.response, :symbolize_keys => true) rescue Hash.new
62
+ callback.call(data, http)
63
+ end
64
+ }
65
+
66
+ http
67
+ end
68
+
69
+ # Streaming
70
+ #
71
+ # The Streaming API allows you to monitor a room in real time. The
72
+ # authenticated user must already have joined the room in order to use this
73
+ # API.
74
+ def stream(room_id, &callback)
75
+ parser = Yajl::Parser.new(:symbolize_keys => true)
76
+
77
+ parser.on_parse_complete = proc do |data|
78
+ callback.call(Firering::Message.instantiate(self, data)) if callback
79
+ end
80
+
81
+ uri = streaming_host.join("/room/#{room_id}/live.json")
82
+ logger.info("performing streaming request to #{uri.to_s}")
83
+ http = EventMachine::HttpRequest.new(uri).get(parameters)
84
+
85
+ http.stream { |chunk| parser << chunk; @performed_retries= 0 }
86
+
87
+ # Campfire servers will try to hold the streaming connections open indefinitely.
88
+ # However, API clients must be able to handle occasional timeouts or
89
+ # disruptions. Upon unexpected disconnection, API clients should wait for a
90
+ # few seconds before trying to reconnect. Formats
91
+ http.errback do
92
+ perform_retry(http) do
93
+ stream(room_id, url, &callback)
94
+ end
95
+ end
96
+
97
+ http
98
+ end
99
+
100
+ def perform_retry(http, &callback)
101
+ if EventMachine.reactor_running? && (@performed_retries.nil? || @performed_retries < @max_retries)
102
+ logger.error("http error #{http.error}. Trying again in #{retry_delay} seconds...")
103
+ EventMachine::add_timer(retry_delay) do
104
+ @performed_retries = (@performed_retries || 0) + 1
105
+ logger.info("Reconnecting...")
106
+ callback.call
107
+ end
108
+ else
109
+ logger.error("The event machine loop is not running")
110
+ raise Firering::Connection::HTTPError.new(http)
111
+ end
112
+ end
113
+
114
+ class HTTPError < RuntimeError
115
+ attr_reader :http
116
+ def initialize(http)
117
+ @http = http
118
+ end
119
+ def to_s
120
+ "http error #{@http.error if @http.respond_to?(:error)}".strip
121
+ end
122
+ end
123
+
124
+ end
125
+ end
@@ -1,29 +1,49 @@
1
- module Firering
2
- class Message < Firering::Data
3
- key :id, :room_id, :user_id, :body, :created_at, :type
4
-
5
- MESSAGE_TYPES = %w[
6
- TextMessage PasteMessage SoundMessage AdvertisementMessage
7
- AllowGuestsMessage DisallowGuestsMessage IdleMessage KickMessage
8
- LeaveMessage SystemMessage TimestampMessage TopicChangeMessage
9
- UnidleMessage UnlockMessage UploadMessage
10
- ]
11
-
12
- # txs activesupport
13
- underscore = proc do |word|
14
- word.to_s.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').tr("-", "_").downcase
15
- end
1
+ class Firering::Message < Firering::Data
16
2
 
17
- MESSAGE_TYPES.each do |message_type|
18
- current_type = (message_type =~ /(.+)Message/) && $1
19
- define_method("#{underscore[current_type]}?") do
20
- type == message_type
21
- end
22
- end
3
+ data_attributes :id, :room_id, :user_id, :body, :created_at, :type
23
4
 
24
- def from_user?
25
- !user_id.nil? && (!user_id.instance_of?(String) || user_id !~ /~\s*$/)
5
+ MESSAGE_TYPES = %w[
6
+ TextMessage PasteMessage SoundMessage AdvertisementMessage
7
+ AllowGuestsMessage DisallowGuestsMessage IdleMessage KickMessage
8
+ LeaveMessage SystemMessage TimestampMessage TopicChangeMessage
9
+ UnidleMessage UnlockMessage UploadMessage
10
+ ]
11
+
12
+ # txs activesupport
13
+ underscore = proc do |word|
14
+ w = word.to_s.gsub(/::/, '/')
15
+ w = w.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
16
+ w = w.gsub(/([a-z\d])([A-Z])/,'\1_\2')
17
+ w.tr("-", "_").downcase
18
+ end
19
+
20
+ MESSAGE_TYPES.each do |message_type|
21
+ current_type = (message_type =~ /(.+)Message/) && $1
22
+ define_method("#{underscore[current_type]}?") do
23
+ type == message_type
26
24
  end
25
+ end
26
+
27
+ def from_user?
28
+ !user_id.nil? && (!user_id.instance_of?(String) || user_id !~ /^\s*$/)
29
+ end
30
+
31
+ # Highlights a message / Removes a message highlight.
32
+ def star(id, yes_or_no = true, &callback)
33
+ connection.star_message(id, yes_or_no, &callback)
34
+ end
27
35
 
36
+ def room(&callback)
37
+ connection.room(room_id, &callback)
28
38
  end
39
+
40
+ def user(&callback)
41
+ if from_user?
42
+ connection.user(user_id, &callback)
43
+ else
44
+ callback.call(nil, connection)
45
+ end
46
+ end
47
+
48
+ alias_method :to_s, :body
29
49
  end