pingfm 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/.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