pingfm 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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