hellotxt 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ pkg/
2
+ coverage/
@@ -0,0 +1,4 @@
1
+ == 1.0.0 / 2008-12-26
2
+
3
+ * first gem release
4
+ * woohoo!
@@ -0,0 +1,14 @@
1
+ .gitignore
2
+ History.txt
3
+ Manifest.txt
4
+ README
5
+ Rakefile
6
+ bin/hellotxt
7
+ lib/hellotxt.rb
8
+ lib/hellotxt/client.rb
9
+ lib/hellotxt/keyloader.rb
10
+ spec/client_spec.rb
11
+ spec/hellotxt_spec.rb
12
+ spec/keyloader_spec.rb
13
+ spec/spec_helper.rb
14
+ spec/spec_responses.rb
data/README ADDED
@@ -0,0 +1,90 @@
1
+ HelloTxt Ruby Client/Library
2
+
3
+ == Authors
4
+
5
+ * Kevin Williams (http://kevwil.github.com/)
6
+
7
+ == CODE:
8
+
9
+ http://github.com/kevwil/hellotxt/
10
+
11
+ == DESCRIPTION:
12
+
13
+ HelloTxt (http://hellotxt.com) is a simple service that makes updating your social networks a snap, and this is it's Ruby library.
14
+
15
+ == FEATURES/PROBLEMS:
16
+
17
+ * Installing the gem creates a 'hellotxt' shell script to post from the shell.
18
+ * Keys are stored in a YAML file in your home directory (%USERPROFILE% in Windows).
19
+
20
+ == SYNOPSIS:
21
+
22
+ Shell usage:
23
+
24
+ $ hellotxt "This message will post to my default services."
25
+
26
+ Everything after the 'hellotxt' command is what will be posted to the service. You could do the same thing without the quotes, and it would still work:
27
+
28
+ $ hellotxt updating from the command line is very handy for developers.
29
+
30
+ If your keys have not been stored, it will ask for them. These keys will be saved
31
+ in a YAML file in your home directory and you won't be asked for them again.
32
+
33
+ You can obtain your API key here: http://hellotxt.com/api/doc
34
+
35
+ Library usage:
36
+
37
+ # Require the library and initialize it.
38
+ require 'hellotxt'
39
+ hellotxt = ::HelloTxt::Client.new('api_key', 'user_key')
40
+
41
+ # Ensure proper API and USER keys.
42
+ hellotxt.validate['status']
43
+ # => 'OK' if success, otherwise 'FAIL'
44
+
45
+ # Posting to all services.
46
+ hellotxt.post('The Dark Knight was amazing.')
47
+ # => {'status' => 'OK'} if success, otherwise 'FAIL'
48
+
49
+
50
+ Check the source comments for more details.
51
+
52
+ == REQUIREMENTS:
53
+
54
+ Ruby!
55
+
56
+ == INSTALL:
57
+
58
+ # From Rubyforge...
59
+ $ sudo gem install hellotxt
60
+
61
+ *OR*
62
+
63
+ # From Github...
64
+ $ sudo gem sources -a http://gems.github.com (only need to do this once)
65
+ $ sudo gem install kevwil-hellotxt
66
+
67
+ == LICENSE:
68
+
69
+ (The MIT License)
70
+
71
+ Copyright (c) 2008-2009 Kevin Williams
72
+
73
+ Permission is hereby granted, free of charge, to any person obtaining
74
+ a copy of this software and associated documentation files (the
75
+ 'Software'), to deal in the Software without restriction, including
76
+ without limitation the rights to use, copy, modify, merge, publish,
77
+ distribute, sublicense, and/or sell copies of the Software, and to
78
+ permit persons to whom the Software is furnished to do so, subject to
79
+ the following conditions:
80
+
81
+ The above copyright notice and this permission notice shall be
82
+ included in all copies or substantial portions of the Software.
83
+
84
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
85
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
86
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
87
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
88
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
89
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
90
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,38 @@
1
+ # Look in the tasks/setup.rb file for the various options that can be
2
+ # configured in this Rakefile. The .rake files in the tasks directory
3
+ # are where the options are used.
4
+
5
+ begin
6
+ require 'bones'
7
+ Bones.setup
8
+ rescue LoadError
9
+ load 'tasks/setup.rb'
10
+ end
11
+
12
+ ensure_in_path 'lib'
13
+ require 'hellotxt'
14
+
15
+ task :default => 'spec:run'
16
+
17
+ PROJ.name = 'hellotxt'
18
+ PROJ.authors = ['Kevin Williams']
19
+ PROJ.email = ['kevwil@gmail.com']
20
+ PROJ.url = 'http://kevwil.github.com/hellotxt'
21
+ PROJ.version = ENV['VERSION'] || HelloTxt::VERSION
22
+ PROJ.rubyforge.name = 'hellotxt'
23
+ PROJ.readme_file = 'README'
24
+
25
+ PROJ.gem.need_tar = false
26
+
27
+ PROJ.spec.opts << '--color'
28
+
29
+ namespace :gem do
30
+ desc 'create a gemspec file to support github gems'
31
+ task :gemspec => 'gem:prereqs' do
32
+ File.open("#{PROJ.name}.gemspec", 'w+') do |f|
33
+ f.write PROJ.gem._spec.to_ruby
34
+ end
35
+ end
36
+ end
37
+
38
+ # EOF
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ API_KEY = '1wno0aw653hyry87'
4
+
5
+ require File.expand_path(
6
+ File.join(File.dirname(__FILE__), %w[.. lib hellotxt]))
7
+
8
+ keyloader = ::HelloTxt::Keyloader.new
9
+ unless keyloader.has_keys?
10
+ keyloader.api_key = API_KEY
11
+ print 'Enter your HelloTxt User API key (http://hellotxt.com/api/doc): '
12
+ keyloader.user_key = STDIN.gets.chomp
13
+ keyloader.save
14
+ end
15
+
16
+ # post message from ARGV
17
+
18
+ hellotxt = ::HelloTxt::Client.new(keyloader.api_key, keyloader.user_key)
19
+
20
+ s = hellotxt.validate
21
+ if s['status'] == 'OK'
22
+ status = ARGV.join(' ')
23
+
24
+ # Might be a good idea to throw an exception here, instead of just bailing.
25
+ if status.nil? || status.empty?
26
+ puts 'Must provide a message to send.'
27
+ exit
28
+ end
29
+
30
+ post_result = hellotxt.post(status)
31
+
32
+ if post_result['status'] == 'FAIL'
33
+ puts post_result['message']
34
+ else
35
+ puts 'Message sent.'
36
+ end
37
+ else
38
+ puts s['message']
39
+ end
40
+
41
+ # EOF
@@ -0,0 +1,54 @@
1
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__))
2
+
3
+ module HelloTxt
4
+
5
+ # :stopdoc:
6
+ VERSION = '1.0.0'
7
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
8
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
9
+ # :startdoc:
10
+
11
+ # Returns the version string for the library.
12
+ #
13
+ def self.version
14
+ VERSION
15
+ end
16
+
17
+ # Returns the library path for the module. If any arguments are given,
18
+ # they will be joined to the end of the libray path using
19
+ # <tt>File.join</tt>.
20
+ #
21
+ def self.libpath( *args )
22
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
23
+ end
24
+
25
+ # Returns the lpath for the module. If any arguments are given,
26
+ # they will be joined to the end of the path using
27
+ # <tt>File.join</tt>.
28
+ #
29
+ def self.path( *args )
30
+ args.empty? ? PATH : ::File.join(PATH, args.flatten)
31
+ end
32
+
33
+ # Utility method used to rquire all files ending in .rb that lie in the
34
+ # directory below this file that has the same name as the filename passed
35
+ # in. Optionally, a specific _directory_ name can be passed in such that
36
+ # the _filename_ does not have to be equivalent to the directory.
37
+ #
38
+ def self.require_all_libs_relative_to( fname, dir = nil )
39
+ dir ||= ::File.basename(fname, '.*')
40
+ search_me = ::File.expand_path(
41
+ ::File.join(::File.dirname(fname), dir, '*', '*.rb'))
42
+
43
+ Dir.glob(search_me).sort.each {|rb| require rb}
44
+ end
45
+
46
+ end # module Hellotxt
47
+
48
+ #HelloTxt.require_all_libs_relative_to(__FILE__)
49
+ module HelloTxt
50
+ autoload :Keyloader, 'hellotxt/keyloader'
51
+ autoload :Client, 'hellotxt/client'
52
+ end
53
+
54
+ # EOF
@@ -0,0 +1,109 @@
1
+ # HelloTxt Ruby Client
2
+ require 'net/http'
3
+ require 'rexml/document'
4
+
5
+ module HelloTxt
6
+
7
+ # MUST NOT end with a trailing slash, as this string is interpolated like this:
8
+ # "#{API_URL}/user.services"
9
+ API_URL = 'http://hellotxt.com/api/v1'
10
+
11
+ class Client
12
+
13
+ def initialize(api_key, user_key)
14
+ @api_key = api_key
15
+ @user_key = user_key
16
+ end
17
+
18
+ # Validates API key and USER key
19
+ # if successful returns:
20
+ # {'status' => 'OK'}
21
+ # if unsuccessful returns:
22
+ # {'status' => 'FAIL', 'message' => 'message what went wrong'}
23
+ def validate
24
+ response = get_response('user.validate')
25
+ if response.elements['rsp'].attributes['status'] == 'OK'
26
+ return status_ok
27
+ else
28
+ return status_fail(response)
29
+ end
30
+ end
31
+
32
+ # Returns a list of services the user has set up through HelloTxt
33
+ # if successful returns:
34
+ # {'status' => 'OK', 'services' => [{'id' => 'serviceid', 'name' => 'servicename', 'code' => 'servicecode', 'inhome' => 'checked', 'friend' => 'checked', 'collegue' => 'checked'}, ...]}
35
+ # if unsuccessful returns:
36
+ # {'status' => 'FAIL', 'message' => 'message what went wrong'}
37
+ def user_services
38
+ response = get_response('user.services')
39
+ if response.elements['rsp'].attributes['status'] == 'OK'
40
+ services = status_ok()
41
+ services['services'] = []
42
+ response.elements.each('rsp/services/service') do |service|
43
+ services['services'].push({'id' => service.attributes['id'],
44
+ 'name' => service.attributes['name'],
45
+ 'code' => service.elements['code'].text,
46
+ 'inhome' => service.elements['inhome'].text,
47
+ 'friend' => service.elements['friend'].text,
48
+ 'collegue' => service.elements['collegue'].text})
49
+ end
50
+ return services
51
+ else
52
+ return status_fail(response)
53
+ end
54
+ end
55
+
56
+ # Posts a message to the user's social services
57
+ # Arguments:
58
+ # body = message body
59
+ # Optional arguments:
60
+ # title = title of the posted message, only used if the service supports it
61
+ # group = service group type; either 'inhome', 'friend', 'collegue'
62
+ # image = raw bytes of an image to post
63
+ # debug = set debug to 1 to avoid posting test data
64
+ # if successful returns:
65
+ # {'status' => 'OK'}
66
+ # if unsuccessful returns:
67
+ # {'status' => 'FAIL', 'message' => 'message what went wrong'}
68
+ def post(body, title = '', group = 'inhome', image = '', debug = 0)
69
+ response = get_response('user.post',
70
+ 'body' => body, 'title' => title,
71
+ 'group' => group, 'image' => image,
72
+ 'debug' => debug)
73
+ if response.elements['rsp'].attributes['status'] == 'OK'
74
+ return status_ok
75
+ else
76
+ return status_fail(response)
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ # Gets a particular hellotxt response.
83
+ # <tt>type</tt>: The service type (ex. 'user.services'). Gets appended to <tt>API_URL</tt>.
84
+ # <tt>parameters</tt>: Optional (depending on the <tt>type</tt>) parameters to be passed along
85
+ # with the request. The API key and USER key are merged with this on every call.
86
+ def get_response(type, parameters = {})
87
+ parameters.merge!('api_key' => @api_key, 'user_key' => @user_key)
88
+ REXML::Document.new(http_request("#{API_URL}/method/#{type}", parameters))
89
+ end
90
+
91
+ # This makes the actual HTTP request.
92
+ def http_request(url, parameters)
93
+ response = Net::HTTP.post_form(URI.parse(url), parameters)
94
+ return response.body
95
+ end
96
+
97
+ # Successful response.
98
+ def status_ok
99
+ return {'status' => 'OK'}
100
+ end
101
+
102
+ # Failed response.
103
+ def status_fail(response)
104
+ return {'status' => 'FAIL', 'message' => response.elements['rsp/message'].text}
105
+ end
106
+
107
+ end
108
+
109
+ end
@@ -0,0 +1,58 @@
1
+ require 'yaml'
2
+
3
+ module HelloTxt
4
+
5
+ class KeyloadingError < Exception; end
6
+
7
+ # manages the YAML file containing the keys - encryption might be nice, might be overkill
8
+ class Keyloader
9
+
10
+ # Path to YAML file containing keys
11
+ attr_accessor :keyfile
12
+
13
+ # hellotxt uses this as the key for the registered application
14
+ attr_accessor :api_key
15
+
16
+ # hellotxt uses this as the key for the user
17
+ attr_accessor :user_key
18
+
19
+ KEY_PATH = (RUBY_PLATFORM =~ /mswin32/ ? ENV['USERPROFILE'] : ENV['HOME'])
20
+ KEY_FILE = '.hellotxt_keys.yml'
21
+
22
+ def initialize(keyfile = File.expand_path(File.join(KEY_PATH, KEY_FILE)))
23
+ @api_key = nil
24
+ @keyfile = keyfile
25
+
26
+ # load keys on init
27
+ load_keys!
28
+ end
29
+
30
+ # load a new set of keys
31
+ def load_keys(keyfile)
32
+ if File.exist?(keyfile) and File.readable?(keyfile)
33
+ data = YAML::load_file(keyfile)
34
+ @keyfile = keyfile if @keyfile.nil?
35
+ @api_key = data['api_key']
36
+ @user_key = data['user_key']
37
+ end
38
+ end
39
+
40
+ # load keys using the known keyfile
41
+ def load_keys!
42
+ load_keys(@keyfile)
43
+ end
44
+
45
+ # if keys have been loaded successfully
46
+ def has_keys?
47
+ return true unless @api_key.nil? or @user_key.nil?
48
+ return false
49
+ end
50
+
51
+ # save key data to keyfile
52
+ def save
53
+ File.open( @keyfile, 'w+' ) do |out|
54
+ YAML::dump( {'api_key' => @api_key, 'user_key' => @user_key}, out )
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,133 @@
1
+
2
+ require File.join(File.dirname(__FILE__), %w[spec_helper])
3
+
4
+ describe HelloTxt::Client, "with expected results" do
5
+
6
+ before(:each) do
7
+ @client = HelloTxt::Client.new('a','b')
8
+ @params = {'api_key' => 'a', 'user_key' => 'b'}
9
+ end
10
+
11
+ it "should validate keys successfully" do
12
+ init_ok_response 'user.validate'
13
+
14
+ uri = URI.parse "#{HelloTxt::API_URL}/method/#{@service_type}"
15
+
16
+ # mock the http call
17
+ http_resp = mock('response')
18
+ http_resp.expects(:body).returns(@response)
19
+ Net::HTTP.expects(:post_form).with(uri, @params).returns(http_resp)
20
+
21
+ # call and verify
22
+ result = @client.validate
23
+ result.should_not be_empty
24
+ result['status'].should_not be_nil
25
+ result['status'].should eql('OK')
26
+ end
27
+
28
+ it "should list the user's services properly" do
29
+ init_service_response
30
+
31
+ uri = URI.parse "#{HelloTxt::API_URL}/method/#{@service_type}"
32
+
33
+ # mock the http call
34
+ http_resp = mock('response')
35
+ http_resp.expects(:body).returns(@response)
36
+ Net::HTTP.expects(:post_form).with(uri, @params).returns(http_resp)
37
+
38
+ # call and verify
39
+ result = @client.user_services
40
+ result.should_not be_empty
41
+ result['status'].should_not be_nil
42
+ result['status'].should eql('OK')
43
+ result['services'].should_not be_nil
44
+ result['services'].should_not be_empty
45
+ result['services'].length.should eql(5)
46
+ result['services'].first['name'].should eql('twitter')
47
+ end
48
+
49
+ it "should post a message to the service" do
50
+ init_ok_response 'user.post'
51
+
52
+ uri = URI.parse "#{HelloTxt::API_URL}/method/#{@service_type}"
53
+ @params.merge!('body' => 'foo', 'title' => '',
54
+ 'group' => 'inhome', 'image' => '',
55
+ 'debug' => 0)
56
+
57
+ # mock the http call
58
+ http_resp = mock('response')
59
+ http_resp.expects(:body).returns(@response)
60
+ Net::HTTP.expects(:post_form).with(uri, @params).returns(http_resp)
61
+
62
+ # call and verify
63
+ result = @client.post('foo')
64
+ result.should_not be_empty
65
+ result['status'].should_not be_nil
66
+ result['status'].should eql('OK')
67
+ end
68
+
69
+ end
70
+
71
+ describe HelloTxt::Client, "with error messages" do
72
+ before(:each) do
73
+ @client = HelloTxt::Client.new('a','b')
74
+ @params = {'api_key' => 'a', 'user_key' => 'b'}
75
+ end
76
+
77
+ it "should handle a failed validate cleanly" do
78
+ init_fail_response 'user.validate'
79
+
80
+ uri = URI.parse "#{HelloTxt::API_URL}/method/#{@service_type}"
81
+
82
+ # mock the http call
83
+ http_resp = mock('response')
84
+ http_resp.expects(:body).returns(@response)
85
+ Net::HTTP.expects(:post_form).with(uri, @params).returns(http_resp)
86
+
87
+ # call and verify
88
+ result = @client.validate
89
+ result.should_not be_empty
90
+ result['status'].should_not be_nil
91
+ result['status'].should eql('FAIL')
92
+ result['message'].should_not be_nil
93
+ end
94
+
95
+ it "should handle a failed user's services cleanly" do
96
+ init_fail_response 'user.services'
97
+
98
+ uri = URI.parse "#{HelloTxt::API_URL}/method/#{@service_type}"
99
+
100
+ # mock the http call
101
+ http_resp = mock('response')
102
+ http_resp.expects(:body).returns(@response)
103
+ Net::HTTP.expects(:post_form).with(uri, @params).returns(http_resp)
104
+
105
+ # call and verify
106
+ result = @client.user_services
107
+ result.should_not be_empty
108
+ result['status'].should_not be_nil
109
+ result['status'].should eql('FAIL')
110
+ result['message'].should_not be_nil
111
+ end
112
+
113
+ it "should handle a failed user post cleanly" do
114
+ init_fail_response 'user.post'
115
+
116
+ uri = URI.parse "#{HelloTxt::API_URL}/method/#{@service_type}"
117
+
118
+ # mock the http call
119
+ http_resp = mock('response')
120
+ http_resp.expects(:body).returns(@response)
121
+ @params.merge!({'group' => 'inhome', 'title' => '',
122
+ 'image' => '', 'body' => 'test message', 'debug' => 0})
123
+ Net::HTTP.expects(:post_form).with(uri, @params).returns(http_resp)
124
+
125
+ # call and verify
126
+ result = @client.post('test message')
127
+ result.should_not be_empty
128
+ result['status'].should_not be_nil
129
+ result['status'].should eql('FAIL')
130
+ result['message'].should_not be_nil
131
+ end
132
+
133
+ end