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 +1 -0
- data/History.txt +4 -0
- data/Manifest.txt +25 -0
- data/README +101 -0
- data/Rakefile +31 -0
- data/bin/pingfm +42 -0
- data/lib/pingfm.rb +56 -0
- data/lib/pingfm/client.rb +219 -0
- data/lib/pingfm/keyloader.rb +55 -0
- data/spec/keyloader_spec.rb +43 -0
- data/spec/pingfm_spec.rb +159 -0
- data/spec/spec_helper.rb +130 -0
- data/tasks/ann.rake +81 -0
- data/tasks/bones.rake +21 -0
- data/tasks/gem.rake +126 -0
- data/tasks/git.rake +41 -0
- data/tasks/manifest.rake +49 -0
- data/tasks/notes.rake +28 -0
- data/tasks/post_load.rake +39 -0
- data/tasks/rdoc.rake +51 -0
- data/tasks/rubyforge.rake +57 -0
- data/tasks/setup.rb +268 -0
- data/tasks/spec.rake +55 -0
- data/tasks/svn.rake +48 -0
- data/tasks/test.rake +38 -0
- metadata +83 -0
@@ -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
|
data/spec/pingfm_spec.rb
ADDED
@@ -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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|