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.
@@ -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
@@ -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
@@ -0,0 +1,159 @@
1
+ # $Id$
2
+
3
+ require File.join(File.dirname(__FILE__), %w[spec_helper])
4
+
5
+ describe Pingfm::Client, " with expected results" do
6
+
7
+ before(:each) do
8
+ @client = Pingfm::Client.new('a','b')
9
+ @params = {'api_key' => 'a', 'user_app_key' => 'b'}
10
+ end
11
+
12
+ it "should validate keys successfully" do
13
+ init_ok_response 'user.validate'
14
+
15
+ uri = URI.parse "#{Pingfm::API_URL}/#{@service_type}"
16
+
17
+ # mock the http call
18
+ http_resp = mock('response')
19
+ http_resp.should_receive(:body).and_return(@response)
20
+ Net::HTTP.should_receive(:post_form).with(uri, @params).and_return(http_resp)
21
+
22
+ # call and verify
23
+ result = @client.validate
24
+ result.should_not be_empty
25
+ result['status'].should_not be_nil
26
+ result['status'].should eql('OK')
27
+ end
28
+
29
+ it "should list the user's services properly" do
30
+ init_service_response
31
+
32
+ uri = URI.parse "#{Pingfm::API_URL}/#{@service_type}"
33
+
34
+ # mock the http call
35
+ http_resp = mock('response')
36
+ http_resp.should_receive(:body).and_return(@response)
37
+ Net::HTTP.should_receive(:post_form).with(uri, @params).and_return(http_resp)
38
+
39
+ # call and verify
40
+ result = @client.services
41
+ result.should_not be_empty
42
+ result['status'].should_not be_nil
43
+ result['status'].should eql('OK')
44
+ result['services'].should_not be_nil
45
+ result['services'].should_not be_empty
46
+ result['services'].length.should eql(2)
47
+ result['services'].first['id'].should eql('twitter')
48
+ end
49
+
50
+ it "should list the user's custom triggers" do
51
+ init_trigger_response
52
+
53
+ uri = URI.parse "#{Pingfm::API_URL}/#{@service_type}"
54
+
55
+ # mock the http call
56
+ http_resp = mock('response')
57
+ http_resp.should_receive(:body).and_return(@response)
58
+ Net::HTTP.should_receive(:post_form).with(uri, @params).and_return(http_resp)
59
+
60
+ # call and verify
61
+ result = @client.triggers
62
+ result.should_not be_empty
63
+ result['status'].should_not be_nil
64
+ result['status'].should eql('OK')
65
+ result['triggers'].should_not be_nil
66
+ result['triggers'].should_not be_empty
67
+ result['triggers'].length.should eql(2)
68
+ result['triggers'].first['id'].should eql('twt')
69
+ end
70
+
71
+ it "should list the user's recent posts" do
72
+ init_latest_response
73
+
74
+ uri = URI.parse "#{Pingfm::API_URL}/#{@service_type}"
75
+ @params.merge!('limit'=>5,'order'=>'DESC')
76
+
77
+ # mock the http call
78
+ http_resp = mock('response')
79
+ http_resp.should_receive(:body).and_return(@response)
80
+ Net::HTTP.should_receive(:post_form).with(uri, @params).and_return(http_resp)
81
+
82
+ # call and verify
83
+ result = @client.latest(5)
84
+ result.should_not be_empty
85
+ result['status'].should_not be_nil
86
+ result['status'].should eql('OK')
87
+ result['messages'].should_not be_nil
88
+ result['messages'].should_not be_empty
89
+ result['messages'].length.should eql(3)
90
+ result['messages'].first['id'].should eql('12345')
91
+ end
92
+
93
+ it "should post a message to the service" do
94
+ init_ok_response 'user.post'
95
+
96
+ uri = URI.parse "#{Pingfm::API_URL}/#{@service_type}"
97
+ @params.merge!('body' => 'foo', 'title' => '',
98
+ 'post_method' => 'default', 'service' => '',
99
+ 'debug' => 0)
100
+
101
+ # mock the http call
102
+ http_resp = mock('response')
103
+ http_resp.should_receive(:body).and_return(@response)
104
+ Net::HTTP.should_receive(:post_form).with(uri, @params).and_return(http_resp)
105
+
106
+ # call and verify
107
+ result = @client.post('foo')
108
+ result.should_not be_empty
109
+ result['status'].should_not be_nil
110
+ result['status'].should eql('OK')
111
+ end
112
+
113
+ it "should post a message to the service using a trigger" do
114
+ init_ok_response 'user.tpost'
115
+
116
+ uri = URI.parse "#{Pingfm::API_URL}/#{@service_type}"
117
+ @params.merge!('body' => 'foo', 'title' => '',
118
+ 'trigger' => 'twt', 'debug' => 0)
119
+
120
+ # mock the http call
121
+ http_resp = mock('response')
122
+ http_resp.should_receive(:body).and_return(@response)
123
+ Net::HTTP.should_receive(:post_form).with(uri, @params).and_return(http_resp)
124
+
125
+ # call and verify
126
+ result = @client.tpost('foo','twt')
127
+ result.should_not be_empty
128
+ result['status'].should_not be_nil
129
+ result['status'].should eql('OK')
130
+ end
131
+
132
+ end
133
+
134
+ describe Pingfm::Client, " with error messages" do
135
+ before(:each) do
136
+ @client = Pingfm::Client.new('a','b')
137
+ @params = {'api_key' => 'a', 'user_app_key' => 'b'}
138
+ end
139
+
140
+ it "should handle a failed validate cleanly" do
141
+ init_fail_response 'user.validate'
142
+
143
+ uri = URI.parse "#{Pingfm::API_URL}/#{@service_type}"
144
+
145
+ # mock the http call
146
+ http_resp = mock('response')
147
+ http_resp.should_receive(:body).and_return(@response)
148
+ Net::HTTP.should_receive(:post_form).with(uri, @params).and_return(http_resp)
149
+
150
+ # call and verify
151
+ result = @client.validate
152
+ result.should_not be_empty
153
+ result['status'].should_not be_nil
154
+ result['status'].should eql('FAIL')
155
+ result['message'].should_not be_nil
156
+ end
157
+ end
158
+
159
+ # EOF
@@ -0,0 +1,130 @@
1
+ # $Id$
2
+
3
+ require File.expand_path(
4
+ File.join(File.dirname(__FILE__), %w[.. lib pingfm]))
5
+
6
+ # Spec::Runner.configure do |config|
7
+ # == Mock Framework
8
+ #
9
+ # RSpec uses it's own mocking framework by default. If you prefer to
10
+ # use mocha, flexmock or RR, uncomment the appropriate line:
11
+ #
12
+ # config.mock_with :mocha
13
+ # config.mock_with :flexmock
14
+ # config.mock_with :rr
15
+ # end
16
+
17
+ def init_ok_response(service_type)
18
+ @service_type = service_type
19
+ @response = <<EOXML
20
+ <?xml version="1.0"?>
21
+ <rsp status="OK">
22
+ <transaction>12345</transaction>
23
+ <method>#{@service_type}</method>
24
+ </rsp>
25
+ EOXML
26
+ end
27
+
28
+ def init_fail_response(service_type)
29
+ @service_type = service_type
30
+ @response = <<EOXML
31
+ <?xml version="1.0"?>
32
+ <rsp status="FAIL">
33
+ <transaction>12345</transaction>
34
+ <method>#{@service_type}</method>
35
+ <message>You suck</message>
36
+ </rsp>
37
+ EOXML
38
+ end
39
+
40
+ def init_service_response
41
+ @service_type = 'user.services'
42
+ @response = <<EOXML
43
+ <?xml version="1.0"?>
44
+ <rsp status="OK">
45
+ <transaction>12345</transaction>
46
+ <method>user.services</method>
47
+ <services>
48
+ <service id="twitter" name="Twitter">
49
+ <trigger>@tt</trigger>
50
+ <url>http://twitter.com/</url>
51
+ <icon>http://p.ping.fm/static/icons/twitter.png</icon>
52
+ <methods>microblog,status</methods>
53
+ </service>
54
+ <service id="facebook" name="Facebook">
55
+ <trigger>@fb</trigger>
56
+ <url>http://www.facebook.com/</url>
57
+ <icon>http://p.ping.fm/static/icons/facebook.png</icon>
58
+ <methods>status</methods>
59
+ </service>
60
+ </services>
61
+ </rsp>
62
+ EOXML
63
+ end
64
+
65
+ def init_trigger_response
66
+ @service_type = 'user.triggers'
67
+ @response = <<EOXML
68
+ <?xml version="1.0"?>
69
+ <rsp status="OK">
70
+ <transaction>12345</transaction>
71
+ <method>user.triggers</method>
72
+ <triggers>
73
+ <trigger id="twt" method="microblog">
74
+ <services>
75
+ <service id="twitter" name="Twitter"/>
76
+ </services>
77
+ </trigger>
78
+ <trigger id="fb" method="status">
79
+ <services>
80
+ <service id="facebook" name="Facebook"/>
81
+ </services>
82
+ </trigger>
83
+ </triggers>
84
+ </rsp>
85
+ EOXML
86
+ end
87
+
88
+ def init_latest_response
89
+ @service_type = 'user.latest'
90
+ @response = <<EOXML
91
+ <?xml version="1.0"?>
92
+ <rsp status="OK">
93
+ <transaction>12345</transaction>
94
+ <method>user.latest</method>
95
+ <messages>
96
+ <message id="12345" method="blog">
97
+ <date rfc="Tue, 15 Apr 2008 13:56:18 -0500" unix="1234567890" />
98
+ <services>
99
+ <service id="blogger" name="Blogger"/>
100
+ </services>
101
+ <content>
102
+ <title>SnVzdCBoYW5naW4nIG91dCE=</title>
103
+ <body>R29pbmcgdG8gdGhlIHN0b3JlLg==</body>
104
+ </content>
105
+ </message>
106
+ <message id="12345" method="microblog">
107
+ <date rfc="Tue, 15 Apr 2008 13:56:18 -0500" unix="1234567890" />
108
+ <services>
109
+ <service id="twitter" name="Twitter"/>
110
+ </services>
111
+ <content>
112
+ <body>R29pbmcgdG8gdGhlIHN0b3JlLg==</body>
113
+ </content>
114
+ </message>
115
+ <message id="12345" method="status">
116
+ <date rfc="Tue, 15 Apr 2008 13:56:18 -0500" unix="1234567890" />
117
+ <services>
118
+ <service id="twitter" name="Twitter"/>
119
+ <service id="facebook" name="Facebook"/>
120
+ </services>
121
+ <content>
122
+ <body>aXMgdGVzdGluZyBQaW5nLmZtIQ==</body>
123
+ </content>
124
+ </message>
125
+ </messages>
126
+ </rsp>
127
+ EOXML
128
+ end
129
+
130
+ # EOF
data/tasks/ann.rake ADDED
@@ -0,0 +1,81 @@
1
+ # $Id$
2
+
3
+ begin
4
+ require 'bones/smtp_tls'
5
+ rescue LoadError
6
+ require 'net/smtp'
7
+ end
8
+ require 'time'
9
+
10
+ namespace :ann do
11
+
12
+ # A prerequisites task that all other tasks depend upon
13
+ task :prereqs
14
+
15
+ file PROJ.ann.file do
16
+ ann = PROJ.ann
17
+ puts "Generating #{ann.file}"
18
+ File.open(ann.file,'w') do |fd|
19
+ fd.puts("#{PROJ.name} version #{PROJ.version}")
20
+ fd.puts(" by #{Array(PROJ.authors).first}") if PROJ.authors
21
+ fd.puts(" #{PROJ.url}") if PROJ.url.valid?
22
+ fd.puts(" (the \"#{PROJ.release_name}\" release)") if PROJ.release_name
23
+ fd.puts
24
+ fd.puts("== DESCRIPTION")
25
+ fd.puts
26
+ fd.puts(PROJ.description)
27
+ fd.puts
28
+ fd.puts(PROJ.changes.sub(%r/^.*$/, '== CHANGES'))
29
+ fd.puts
30
+ ann.paragraphs.each do |p|
31
+ fd.puts "== #{p.upcase}"
32
+ fd.puts
33
+ fd.puts paragraphs_of(PROJ.readme_file, p).join("\n\n")
34
+ fd.puts
35
+ end
36
+ fd.puts ann.text if ann.text
37
+ end
38
+ end
39
+
40
+ desc "Create an announcement file"
41
+ task :announcement => ['ann:prereqs', PROJ.ann.file]
42
+
43
+ desc "Send an email announcement"
44
+ task :email => ['ann:prereqs', PROJ.ann.file] do
45
+ ann = PROJ.ann
46
+ from = ann.email[:from] || PROJ.email
47
+ to = Array(ann.email[:to])
48
+
49
+ ### build a mail header for RFC 822
50
+ rfc822msg = "From: #{from}\n"
51
+ rfc822msg << "To: #{to.join(',')}\n"
52
+ rfc822msg << "Subject: [ANN] #{PROJ.name} #{PROJ.version}"
53
+ rfc822msg << " (#{PROJ.release_name})" if PROJ.release_name
54
+ rfc822msg << "\n"
55
+ rfc822msg << "Date: #{Time.new.rfc822}\n"
56
+ rfc822msg << "Message-Id: "
57
+ rfc822msg << "<#{"%.8f" % Time.now.to_f}@#{ann.email[:domain]}>\n\n"
58
+ rfc822msg << File.read(ann.file)
59
+
60
+ params = [:server, :port, :domain, :acct, :passwd, :authtype].map do |key|
61
+ ann.email[key]
62
+ end
63
+
64
+ params[3] = PROJ.email if params[3].nil?
65
+
66
+ if params[4].nil?
67
+ STDOUT.write "Please enter your e-mail password (#{params[3]}): "
68
+ params[4] = STDIN.gets.chomp
69
+ end
70
+
71
+ ### send email
72
+ Net::SMTP.start(*params) {|smtp| smtp.sendmail(rfc822msg, from, to)}
73
+ end
74
+ end # namespace :ann
75
+
76
+ desc 'Alias to ann:announcement'
77
+ task :ann => 'ann:announcement'
78
+
79
+ CLOBBER << PROJ.ann.file
80
+
81
+ # EOF