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-09-25
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
+ print '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
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,219 @@
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
+ # Return a complete list of supported services
33
+ # if successful returns:
34
+ # {'status' => 'OK', 'services' => [{'id' => 'serviceid', 'name' => 'servicename', 'trigger' => 'servicetrigger', 'url' => 'serviceurl', 'icon' => 'serviceicon'}, ...]}
35
+ # if unsuccessful returns:
36
+ # {'status' => 'FAIL', 'message' => 'message what went wrong'}
37
+ def system_services
38
+ response = get_response('system.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
+ 'trigger' => service.elements['trigger'].text,
46
+ 'url' => service.elements['url'].text,
47
+ 'icon' => service.elements['icon'].text})
48
+ end
49
+ return services
50
+ else
51
+ return status_fail(response)
52
+ end
53
+ end
54
+
55
+ # Returns a list of services the user has set up through Ping.fm
56
+ # if successful returns:
57
+ # {'status' => 'OK', 'services' => [{'id' => 'serviceid', 'name' => 'servicename', 'trigger' => 'servicetrigger', 'url' => 'serviceurl', 'icon' => 'serviceicon', 'methods' => 'status,blog'}, ...]}
58
+ # if unsuccessful returns:
59
+ # {'status' => 'FAIL', 'message' => 'message what went wrong'}
60
+ def services
61
+ response = get_response('user.services')
62
+ if response.elements['rsp'].attributes['status'] == 'OK'
63
+ services = status_ok()
64
+ services['services'] = []
65
+ response.elements.each('rsp/services/service') do |service|
66
+ services['services'].push({'id' => service.attributes['id'],
67
+ 'name' => service.attributes['name'],
68
+ 'trigger' => service.elements['trigger'].text,
69
+ 'url' => service.elements['url'].text,
70
+ 'icon' => service.elements['icon'].text,
71
+ 'methods' => service.elements['methods'].text})
72
+ end
73
+ return services
74
+ else
75
+ return status_fail(response)
76
+ end
77
+ end
78
+
79
+ # Returns a user's custom triggers
80
+ # if successful returns:
81
+ # {'status' => 'OK', 'triggers' => [{'id' => 'triggerid', 'method' => 'triggermethod', 'services' => [{'id' => 'serviceid', 'name' => 'servicename'}, ...]}, ...]}
82
+ # if unsuccessful returns:
83
+ # {'status' => 'FAIL', 'message' => 'message what went wrong'}
84
+ def triggers
85
+ response = get_response('user.triggers')
86
+ if response.elements['rsp'].attributes['status'] == 'OK'
87
+ triggers = status_ok
88
+ triggers['triggers'] = []
89
+ response.elements.each('rsp/triggers/trigger') do |trigger|
90
+ triggers['triggers'].push({'id' => trigger.attributes['id'], 'method' => trigger.attributes['method'], 'services' => []})
91
+
92
+ trigger.elements.each('services/service') do |service|
93
+ triggers['triggers'].last['services'].push({'id' => service.attributes['id'], 'name' => service.attributes['name']})
94
+ end
95
+ end
96
+ return triggers
97
+ else
98
+ return status_fail(response)
99
+ end
100
+ end
101
+
102
+ # Returns the last <tt>limit</tt> messages a user has posted through Ping.fm
103
+ # Optional arguments:
104
+ # limit = limit the results returned, default is 25
105
+ # order = which direction to order the returned results by date, default is DESC (descending)
106
+ # if successful returns:
107
+ # {'status' => 'OK', 'messages' => [{'id' => 'messageid', 'method' => 'messsagemethod', 'rfc' => 'date', 'unix' => 'date', 'title' => 'messagetitle', 'body' => 'messagebody', 'services' => [{'id' => 'serviceid', 'name' => 'servicename'}, ...]}, ...]}
108
+ # if unsuccessful returns:
109
+ # {'status' => 'FAIL', 'message' => 'message what went wrong'}
110
+ def latest(limit = 25, order = 'DESC')
111
+ response = get_response('user.latest', 'limit' => limit, 'order' => order)
112
+ if response.elements['rsp'].attributes['status'] == 'OK'
113
+ latest = status_ok
114
+ latest['messages'] = []
115
+ response.elements.each('rsp/messages/message') do |message|
116
+ latest['messages'].push({})
117
+ latest['messages'].last['id'] = message.attributes['id']
118
+ latest['messages'].last['method'] = message.attributes['method']
119
+ latest['messages'].last['rfc'] = message.elements['date'].attributes['rfc']
120
+ latest['messages'].last['unix'] = message.elements['date'].attributes['unix']
121
+
122
+ if message.elements['*/title'] != nil
123
+ latest['messages'].last['title'] = message.elements['*/title'].text
124
+ else
125
+ latest['messages'].last['title'] = ''
126
+ end
127
+ if message.elements['location'] != nil
128
+ latest['messages'].last['location'] = message.elements['location'].text
129
+ else
130
+ latest['messages'].last['location'] = ''
131
+ end
132
+ latest['messages'].last['body'] = message.elements['*/body'].text
133
+ latest['messages'].last['services'] = []
134
+ message.elements.each('services/service') do |service|
135
+ latest['messages'].last['services'].push({'id' => service.attributes['id'], 'name' => service.attributes['name']})
136
+ end
137
+ end
138
+ return latest
139
+ else
140
+ return status_fail(response)
141
+ end
142
+ end
143
+
144
+ # Posts a message to the user's Ping.fm services
145
+ # Arguments:
146
+ # body = message body
147
+ # Optional arguments:
148
+ # title = title of the posted message, title is required for 'blog' post method
149
+ # post_method = posting method; either 'default', 'blog', 'microblog' or 'status.'
150
+ # service = a single service to post to
151
+ # debug = set debug to 1 to avoid posting test data
152
+ # if successful returns:
153
+ # {'status' => 'OK'}
154
+ # if unsuccessful returns:
155
+ # {'status' => 'FAIL', 'message' => 'message what went wrong'}
156
+ def post(body, title = '', post_method = 'default', service = '', debug = 0)
157
+ response = get_response('user.post',
158
+ 'body' => body, 'title' => title,
159
+ 'post_method' => post_method, 'service' => service,
160
+ 'debug' => debug)
161
+ if response.elements['rsp'].attributes['status'] == 'OK'
162
+ return status_ok
163
+ else
164
+ return status_fail(response)
165
+ end
166
+ end
167
+
168
+ # Posts a message to the user's Ping.fm services using one of their custom triggers
169
+ # Arguments:
170
+ # body = message body
171
+ # trigger = custom trigger the user has defined from the Ping.fm website
172
+ # Optional arguments:
173
+ # title = title of the posted message, title is required for 'blog' post method
174
+ # debug = set debug to 1 to avoid posting test data
175
+ # if successful returns:
176
+ # {'status' => 'OK'}
177
+ # if unsuccessful returns:
178
+ # {'status' => 'FAIL', 'message' => 'message what went wrong'}
179
+ def tpost(body, trigger, title = '', debug = 0)
180
+ response = get_response('user.tpost',
181
+ 'body' => body, 'title' => title,
182
+ 'trigger' => trigger, 'debug' => debug)
183
+ if response.elements['rsp'].attributes['status'] == 'OK'
184
+ return status_ok
185
+ else
186
+ return status_fail(response)
187
+ end
188
+ end
189
+
190
+ private
191
+
192
+ # Gets a particular ping.fm response.
193
+ # <tt>type</tt>: The service type (ex. 'user.services'). Gets appended to <tt>API_URL</tt>.
194
+ # <tt>parameters</tt>: Optional (depending on the <tt>type</tt>) parameters to be passed along
195
+ # with the request. The API key and user app key are merged with this on every call.
196
+ def get_response(type, parameters = {})
197
+ parameters.merge!('api_key' => @api_key, 'user_app_key' => @user_app_key)
198
+ REXML::Document.new(http_request("#{API_URL}/#{type}", parameters))
199
+ end
200
+
201
+ # This makes the actual HTTP request.
202
+ def http_request(url, parameters)
203
+ response = Net::HTTP.post_form(URI.parse(url), parameters)
204
+ return response.body
205
+ end
206
+
207
+ # Successful response.
208
+ def status_ok
209
+ return {'status' => 'OK'}
210
+ end
211
+
212
+ # Failed response.
213
+ def status_fail(response)
214
+ return {'status' => 'FAIL', 'message' => response.elements['rsp/message'].text}
215
+ end
216
+
217
+ end
218
+
219
+ end