Oshuma-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-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