Oshuma-pingfm 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ pkg/
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 1.0.0 / 2008-08-30
2
+
3
+ * first gem release
4
+ * woohoo!
data/Manifest.txt ADDED
@@ -0,0 +1,25 @@
1
+ .gitignore
2
+ History.txt
3
+ Manifest.txt
4
+ README
5
+ Rakefile
6
+ bin/pingfm
7
+ lib/pingfm.rb
8
+ lib/pingfm/client.rb
9
+ lib/pingfm/keyloader.rb
10
+ spec/keyloader_spec.rb
11
+ spec/pingfm_spec.rb
12
+ spec/spec_helper.rb
13
+ tasks/ann.rake
14
+ tasks/bones.rake
15
+ tasks/gem.rake
16
+ tasks/git.rake
17
+ tasks/manifest.rake
18
+ tasks/notes.rake
19
+ tasks/post_load.rake
20
+ tasks/rdoc.rake
21
+ tasks/rubyforge.rake
22
+ tasks/setup.rb
23
+ tasks/spec.rake
24
+ tasks/svn.rake
25
+ tasks/test.rake
data/README ADDED
@@ -0,0 +1,101 @@
1
+ Ping.fm Ruby Client/Library
2
+
3
+ == Authors
4
+
5
+ * Krunoslav Husak (http://h00s.net)
6
+ * Dale Campbell (http://corrupt.save-state.net)
7
+ * Kevin Williams (http://almostserio.us)
8
+
9
+ == CODE:
10
+
11
+ http://pingfm.rubyforge.org/
12
+
13
+ == DESCRIPTION:
14
+
15
+ Ping.fm (http://ping.fm) is a simple service that makes updating your social networks a snap, and this it's Ruby library.
16
+
17
+ == FEATURES/PROBLEMS:
18
+
19
+ * Installing the gem creates a 'pingfm' shell script to post from the shell.
20
+ * Keys are stored in a YAML file in your home directory (under Linux/OSX).
21
+
22
+ == SYNOPSIS:
23
+
24
+ Shell usage:
25
+
26
+ $ pingfm This message will post to my default services.
27
+
28
+ Everything after the 'pingfm' command is what will be posted to the service. You
29
+ may also include the message within quotes (ex. using the client within a shell script).
30
+
31
+ If your keys have not been stored, it will ask for them. These keys will be saved
32
+ in a YAML file in your home directory and you won't be asked for them again.
33
+
34
+ You can obtain your keys here:
35
+
36
+ * User API Key - http://ping.fm/key/
37
+ * Application API Key - http://ping.fm/developers/
38
+
39
+ Library usage:
40
+
41
+ # Require the library and initialize it.
42
+ require 'pingfm'
43
+ pingfm = Pingfm::Client.new('api_key', 'user_app_key')
44
+
45
+ # Ensure proper API and User App keys.
46
+ pingfm.validate['status']
47
+ # => 'OK' if success, otherwise 'FAIL'
48
+
49
+ # Grab latest 5 posts.
50
+ pingfm.latest(5) # => {'messages' => [...]}
51
+
52
+ # Posting to all services.
53
+ pingfm.post('The Dark Knight was amazing.')
54
+ # => {'status' => 'OK'} if success, otherwise 'FAIL'
55
+
56
+ # Post using custom user trigger; must be defined on the Ping.fm site.
57
+ pingfm.tpost('The message here.', '#something_custom', 'Optional Title')
58
+ # => {'status' => 'OK'} if success, otherwise 'FAIL'
59
+
60
+
61
+ Check the source comments for more details.
62
+
63
+ == REQUIREMENTS:
64
+
65
+ Ruby!
66
+
67
+ == INSTALL:
68
+
69
+ # From Rubyforge...
70
+ $ sudo gem install pingfm
71
+
72
+ *OR*
73
+
74
+ # From Github...
75
+ $ sudo gem sources -a http://gems.github.com (only need to do this once)
76
+ $ sudo gem install oshuma-pingfm
77
+
78
+ == LICENSE:
79
+
80
+ (The MIT License)
81
+
82
+ Copyright (c) 2008 Krunoslav Husak, Dale Campbell, Kevin Williams
83
+
84
+ Permission is hereby granted, free of charge, to any person obtaining
85
+ a copy of this software and associated documentation files (the
86
+ 'Software'), to deal in the Software without restriction, including
87
+ without limitation the rights to use, copy, modify, merge, publish,
88
+ distribute, sublicense, and/or sell copies of the Software, and to
89
+ permit persons to whom the Software is furnished to do so, subject to
90
+ the following conditions:
91
+
92
+ The above copyright notice and this permission notice shall be
93
+ included in all copies or substantial portions of the Software.
94
+
95
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
96
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
97
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
98
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
99
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
100
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
101
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,31 @@
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
+ load 'tasks/setup.rb'
6
+
7
+ ensure_in_path 'lib'
8
+ require 'pingfm'
9
+
10
+ task :default => 'spec:run'
11
+
12
+ PROJ.name = 'pingfm'
13
+ PROJ.authors = ['Krunoslav Husak', 'Dale Campbell', 'Kevin Williams']
14
+ PROJ.email = ['dale@save-state.net', 'kevwil@gmail.com']
15
+ PROJ.url = 'http://pingfm.rubyforge.org/'
16
+ PROJ.version = ENV['VERSION'] || Pingfm.version
17
+ PROJ.rubyforge.name = 'pingfm'
18
+ PROJ.readme_file = 'README'
19
+
20
+ PROJ.spec.opts << '--color'
21
+
22
+ namespace :gem do
23
+ desc 'create a gemspec file to support github gems'
24
+ task :gemspec => 'gem:prereqs' do
25
+ File.open("#{PROJ.name}.gemspec", 'w+') do |f|
26
+ f.write PROJ.gem._spec.to_ruby
27
+ end
28
+ end
29
+ end
30
+
31
+ # EOF
data/bin/pingfm ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # TODO: Move this into a YAML config?
4
+ API_KEY = '5fcb8b7041d5c32c7e1e60dc076989ba'
5
+
6
+ require File.expand_path(
7
+ File.join(File.dirname(__FILE__), '..', 'lib', 'pingfm'))
8
+
9
+ keyloader = ::Pingfm::Keyloader.new
10
+ unless keyloader.has_keys?
11
+ keyloader.api_key = API_KEY
12
+ puts 'Enter your Ping.fm User API key (http://ping.fm/key/): '
13
+ keyloader.app_key = STDIN.gets.chomp
14
+ keyloader.save
15
+ end
16
+
17
+ # post message from ARGV
18
+
19
+ pingfm = ::Pingfm::Client.new(keyloader.api_key, keyloader.app_key)
20
+
21
+ s = pingfm.validate
22
+ if s['status'] == 'OK'
23
+ status = ARGV.join(' ')
24
+
25
+ # Might be a good idea to throw an exception here, instead of just bailing.
26
+ if status.nil? || status.empty?
27
+ puts 'Must provide a message to send.'
28
+ exit
29
+ end
30
+
31
+ post_result = pingfm.post(status)
32
+
33
+ if s['status'] == 'FAIL'
34
+ puts s['message']
35
+ else
36
+ puts 'Message sent.'
37
+ end
38
+ else
39
+ puts s['message']
40
+ end
41
+
42
+ # EOF
@@ -0,0 +1,186 @@
1
+ # Ping.fm Ruby Client
2
+ require 'net/http'
3
+ require 'rexml/document'
4
+
5
+ module Pingfm
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://api.ping.fm/v1'
10
+
11
+ class Client
12
+
13
+ def initialize(api_key, user_app_key)
14
+ @api_key = api_key
15
+ @user_app_key = user_app_key
16
+ end
17
+
18
+ # Validates API key and user APP 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 Ping.fm
33
+ # if successful returns:
34
+ # {'status' => 'OK', services = [{'id' => 'serviceid', 'name' => 'servicename', 'methods' => 'status,blog'}, ...]}
35
+ # if unsuccessful returns:
36
+ # {'status' => 'FAIL', 'message' => 'message what went wrong'}
37
+ def 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'], 'name' => service.attributes['name'], 'methods' => service.elements['methods'].text})
44
+ end
45
+ return services
46
+ else
47
+ return status_fail(response)
48
+ end
49
+ end
50
+
51
+ # Returns a user's custom triggers
52
+ # if successful returns:
53
+ # {'status' => 'OK', triggers = [{'id' => 'triggerid', 'method' => 'triggermethod', 'services' => [{'id' => 'serviceid', 'name' => 'servicename'}, ...]}, ...]}
54
+ # if unsuccessful returns:
55
+ # {'status' => 'FAIL', 'message' => 'message what went wrong'}
56
+ def triggers
57
+ response = get_response('user.triggers')
58
+ if response.elements['rsp'].attributes['status'] == 'OK'
59
+ triggers = status_ok
60
+ triggers['triggers'] = []
61
+ response.elements.each('rsp/triggers/trigger') do |trigger|
62
+ triggers['triggers'].push({'id' => trigger.attributes['id'], 'method' => trigger.attributes['method'], 'services' => []})
63
+
64
+ trigger.elements.each('services/service') do |service|
65
+ triggers['triggers'].last['services'].push({'id' => service.attributes['id'], 'name' => service.attributes['name']})
66
+ end
67
+ end
68
+ return triggers
69
+ else
70
+ return status_fail(response)
71
+ end
72
+ end
73
+
74
+ # Returns the last <tt>limit</tt> messages a user has posted through Ping.fm
75
+ # Optional arguments:
76
+ # limit = limit the results returned, default is 25
77
+ # order = which direction to order the returned results by date, default is DESC (descending)
78
+ # if successful returns:
79
+ # {'status' => 'OK', 'messages' => [{'id' => 'messageid', 'method' => 'messsagemethod', 'rfc' => 'date', 'unix' => 'date', 'title' => 'messagetitle', 'body' => 'messagebody', 'services' => [{'id' => 'serviceid', 'name' => 'servicename'}, ...]}, ...]}
80
+ # if unsuccessful returns:
81
+ # {'status' => 'FAIL', 'message' => 'message what went wrong'}
82
+ def latest(limit = 25, order = 'DESC')
83
+ response = get_response('user.latest', 'limit' => limit, 'order' => order)
84
+ if response.elements['rsp'].attributes['status'] == 'OK'
85
+ latest = status_ok
86
+ latest['messages'] = []
87
+ response.elements.each('rsp/messages/message') do |message|
88
+ latest['messages'].push({})
89
+ latest['messages'].last['id'] = message.attributes['id']
90
+ latest['messages'].last['method'] = message.attributes['method']
91
+ latest['messages'].last['rfc'] = message.elements['date'].attributes['rfc']
92
+ latest['messages'].last['unix'] = message.elements['date'].attributes['unix']
93
+
94
+ if message.elements['*/title'] != nil
95
+ latest['messages'].last['title'] = message.elements['*/title'].text
96
+ else
97
+ latest['messages'].last['title'] = ''
98
+ end
99
+ latest['messages'].last['body'] = message.elements['*/body'].text
100
+ latest['messages'].last['services'] = []
101
+ message.elements.each('services/service') do |service|
102
+ latest['messages'].last['services'].push({'id' => service.attributes['id'], 'name' => service.attributes['name']})
103
+ end
104
+ end
105
+ return latest
106
+ else
107
+ return status_fail(response)
108
+ end
109
+ end
110
+
111
+ # Posts a message to the user's Ping.fm services
112
+ # Arguments:
113
+ # body = message body
114
+ # Optional arguments:
115
+ # title = title of the posted message, title is required for 'blog' post method
116
+ # post_method = posting method; either 'default', 'blog', 'microblog' or 'status.'
117
+ # service = a single service to post to
118
+ # debug = set debug to 1 to avoid posting test data
119
+ # if successful returns:
120
+ # {'status' => 'OK'}
121
+ # if unsuccessful returns:
122
+ # {'status' => 'FAIL', 'message' => 'message what went wrong'}
123
+ def post(body, title = '', post_method = 'default', service = '', debug = 0)
124
+ response = get_response('user.post',
125
+ 'body' => body, 'title' => title,
126
+ 'post_method' => post_method, 'service' => service,
127
+ 'debug' => debug)
128
+ if response.elements['rsp'].attributes['status'] == 'OK'
129
+ return status_ok
130
+ else
131
+ return status_fail(response)
132
+ end
133
+ end
134
+
135
+ # Posts a message to the user's Ping.fm services using one of their custom triggers
136
+ # Arguments:
137
+ # body = message body
138
+ # trigger = custom trigger the user has defined from the Ping.fm website
139
+ # Optional arguments:
140
+ # title = title of the posted message, title is required for 'blog' post method
141
+ # debug = set debug to 1 to avoid posting test data
142
+ # if successful returns:
143
+ # {'status' => 'OK'}
144
+ # if unsuccessful returns:
145
+ # {'status' => 'FAIL', 'message' => 'message what went wrong'}
146
+ def tpost(body, trigger, title = '', debug = 0)
147
+ response = get_response('user.tpost',
148
+ 'body' => body, 'title' => title,
149
+ 'trigger' => trigger, 'debug' => debug)
150
+ if response.elements['rsp'].attributes['status'] == 'OK'
151
+ return status_ok
152
+ else
153
+ return status_fail(response)
154
+ end
155
+ end
156
+
157
+ private
158
+
159
+ # Gets a particular ping.fm response.
160
+ # <tt>type</tt>: The service type (ex. 'user.services'). Gets appended to <tt>API_URL</tt>.
161
+ # <tt>parameters</tt>: Optional (depending on the <tt>type</tt>) parameters to be passed along
162
+ # with the request. The API key and user app key are merged with this on every call.
163
+ def get_response(type, parameters = {})
164
+ parameters.merge!('api_key' => @api_key, 'user_app_key' => @user_app_key)
165
+ REXML::Document.new(http_request("#{API_URL}/#{type}", parameters))
166
+ end
167
+
168
+ # This makes the actual HTTP request.
169
+ def http_request(url, parameters)
170
+ response = Net::HTTP.post_form(URI.parse(url), parameters)
171
+ return response.body
172
+ end
173
+
174
+ # Successful response.
175
+ def status_ok
176
+ return {'status' => 'OK'}
177
+ end
178
+
179
+ # Failed response.
180
+ def status_fail(response)
181
+ return {'status' => 'FAIL', 'message' => response.elements['rsp/message'].text}
182
+ end
183
+
184
+ end
185
+
186
+ end
@@ -0,0 +1,55 @@
1
+ require 'yaml'
2
+
3
+ module Pingfm
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
+ # ping.fm uses this as the key for the registered application
14
+ attr_accessor :api_key
15
+
16
+ # ping.fm uses this as the key for the user
17
+ attr_accessor :app_key
18
+
19
+ def initialize(keyfile = File.expand_path('~/.pingfm_keys.yml'))
20
+ @api_key = nil
21
+ @keyfile = keyfile
22
+
23
+ # load keys on init
24
+ load_keys!
25
+ end
26
+
27
+ # load a new set of keys
28
+ def load_keys(keyfile)
29
+ if File.exist?(keyfile) and File.readable?(keyfile)
30
+ data = YAML::load_file(keyfile)
31
+ @keyfile = keyfile if @keyfile.nil?
32
+ @api_key = data['api_key']
33
+ @app_key = data['app_key']
34
+ end
35
+ end
36
+
37
+ # load keys using the known keyfile
38
+ def load_keys!
39
+ load_keys(@keyfile)
40
+ end
41
+
42
+ # if keys have been loaded successfully
43
+ def has_keys?
44
+ return true unless @api_key.nil? or @app_key.nil?
45
+ return false
46
+ end
47
+
48
+ # save key data to keyfile
49
+ def save
50
+ File.open( @keyfile, 'w+' ) do |out|
51
+ YAML::dump( {'api_key' => @api_key, 'app_key' => @app_key}, out )
52
+ end
53
+ end
54
+ end
55
+ end
data/lib/pingfm.rb ADDED
@@ -0,0 +1,56 @@
1
+ # $Id$
2
+
3
+ # Equivalent to a header guard in C/C++
4
+ # Used to prevent the class/module from being loaded more than once
5
+ unless defined? Pingfm
6
+
7
+ module Pingfm
8
+
9
+ # :stopdoc:
10
+ VERSION = '1.0.0'
11
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
12
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
13
+ # :startdoc:
14
+
15
+ # Returns the version string for the library.
16
+ #
17
+ def self.version
18
+ VERSION
19
+ end
20
+
21
+ # Returns the library path for the module. If any arguments are given,
22
+ # they will be joined to the end of the libray path using
23
+ # <tt>File.join</tt>.
24
+ #
25
+ def self.libpath( *args )
26
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, *args)
27
+ end
28
+
29
+ # Returns the lpath for the module. If any arguments are given,
30
+ # they will be joined to the end of the path using
31
+ # <tt>File.join</tt>.
32
+ #
33
+ def self.path( *args )
34
+ args.empty? ? PATH : ::File.join(PATH, *args)
35
+ end
36
+
37
+ # Utility method used to rquire all files ending in .rb that lie in the
38
+ # directory below this file that has the same name as the filename passed
39
+ # in. Optionally, a specific _directory_ name can be passed in such that
40
+ # the _filename_ does not have to be equivalent to the directory.
41
+ #
42
+ def self.require_all_libs_relative_to( fname, dir = nil )
43
+ dir ||= ::File.basename(fname, '.*')
44
+ search_me = ::File.expand_path(
45
+ ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
46
+
47
+ Dir.glob(search_me).sort.each {|rb| require rb}
48
+ end
49
+
50
+ end # module Pingfm
51
+
52
+ Pingfm.require_all_libs_relative_to __FILE__
53
+
54
+ end # unless defined?
55
+
56
+ # EOF
@@ -0,0 +1,43 @@
1
+ require File.join(File.dirname(__FILE__), %w[spec_helper])
2
+ require 'tempfile'
3
+
4
+ describe Pingfm::Keyloader do
5
+
6
+ before(:each) do
7
+ @keydata = {'api_key' => 'foo', 'app_key' => 'bar'}
8
+ @tf = Tempfile.new('keys.yml')
9
+ YAML::dump(@keydata, @tf)
10
+ @tmp_path = @tf.path
11
+ @tf.close
12
+ end
13
+
14
+ after(:each) do
15
+ @tf.unlink
16
+ end
17
+
18
+ it "should use keys from yaml file if found" do
19
+ loader = Pingfm::Keyloader.new(@tmp_path)
20
+ loader.has_keys?.should be_true
21
+ loader.keyfile.should eql(@tmp_path)
22
+ loader.api_key.should eql(@keydata['api_key'])
23
+ loader.app_key.should eql(@keydata['app_key'])
24
+ end
25
+
26
+ it "should save keys to keyfile" do
27
+ loader = Pingfm::Keyloader.new(@tmp_path)
28
+ loader.app_key = 'baz'
29
+ loader.save
30
+
31
+ loader.has_keys?.should be_true
32
+ File.exist?(loader.keyfile).should be_true
33
+ File.readable?(loader.keyfile).should be_true
34
+ YAML::load_file(loader.keyfile)['app_key'].should eql('baz')
35
+ end
36
+
37
+ it "should behave if keys cannot be loaded" do
38
+ loader = Pingfm::Keyloader.new('/tmp/nofile')
39
+ loader.has_keys?.should be_false
40
+ loader.api_key.should be_nil
41
+ loader.app_key.should be_nil
42
+ end
43
+ end