campfire_export 0.1.0 → 0.1.1
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/Rakefile +4 -0
- data/bin/campfire_export +11 -2
- data/campfire_export.gemspec +2 -0
- data/lib/campfire_export/timezone.rb +1 -1
- data/lib/campfire_export/version.rb +1 -1
- data/lib/campfire_export.rb +44 -59
- data/spec/campfire_export/account_spec.rb +76 -0
- data/spec/campfire_export/room_spec.rb +29 -0
- metadata +46 -26
data/Rakefile
CHANGED
data/bin/campfire_export
CHANGED
@@ -53,5 +53,14 @@ ensure_config_for(config, 'subdomain',
|
|
53
53
|
ensure_config_for(config, 'api_token',
|
54
54
|
"Your Campfire API token (see 'My Info' on your Campfire site)")
|
55
55
|
|
56
|
-
|
57
|
-
account.
|
56
|
+
begin
|
57
|
+
account = CampfireExport::Account.new(config['subdomain'],
|
58
|
+
config['api_token'])
|
59
|
+
account.find_timezone
|
60
|
+
account.rooms.each do |room|
|
61
|
+
room.export(config_date(config, 'start_date'),
|
62
|
+
config_date(config, 'end_date'))
|
63
|
+
end
|
64
|
+
rescue => e
|
65
|
+
puts "Unable to export account: #{e}"
|
66
|
+
end
|
data/campfire_export.gemspec
CHANGED
@@ -17,6 +17,8 @@ Gem::Specification.new do |s|
|
|
17
17
|
s.required_ruby_version = '>= 1.8.7'
|
18
18
|
|
19
19
|
s.add_development_dependency "bundler", "~> 1.0.15"
|
20
|
+
s.add_development_dependency "fuubar", "~> 0.0.5"
|
21
|
+
s.add_development_dependency "rspec", "~> 2.6.0"
|
20
22
|
s.add_development_dependency "tzinfo", "~> 0.3.29"
|
21
23
|
s.add_development_dependency "httparty", "~> 0.7.8"
|
22
24
|
s.add_development_dependency "nokogiri", "~> 1.4.5"
|
data/lib/campfire_export.rb
CHANGED
@@ -51,14 +51,10 @@ module CampfireExport
|
|
51
51
|
def zero_pad(number)
|
52
52
|
"%02d" % number
|
53
53
|
end
|
54
|
-
|
55
|
-
def account_dir
|
56
|
-
"campfire/#{CampfireExport::Account.subdomain}"
|
57
|
-
end
|
58
|
-
|
54
|
+
|
59
55
|
# Requires that room and date be defined in the calling object.
|
60
56
|
def export_dir
|
61
|
-
"
|
57
|
+
"campfire/#{Account.subdomain}/#{room.name}/" +
|
62
58
|
"#{date.year}/#{zero_pad(date.mon)}/#{zero_pad(date.day)}"
|
63
59
|
end
|
64
60
|
|
@@ -66,6 +62,7 @@ module CampfireExport
|
|
66
62
|
def export_file(content, filename, mode='w')
|
67
63
|
# Check to make sure we're writing into the target directory tree.
|
68
64
|
true_path = File.expand_path(File.join(export_dir, filename))
|
65
|
+
|
69
66
|
unless true_path.start_with?(File.expand_path(export_dir))
|
70
67
|
raise CampfireExport::Exception.new("#{export_dir}/#{filename}",
|
71
68
|
"can't export file to a directory higher than target directory; " +
|
@@ -133,45 +130,28 @@ module CampfireExport
|
|
133
130
|
@subdomain = ""
|
134
131
|
@api_token = ""
|
135
132
|
@base_url = ""
|
136
|
-
@timezone =
|
133
|
+
@timezone = nil
|
137
134
|
|
138
135
|
class << self
|
139
136
|
attr_accessor :subdomain, :api_token, :base_url, :timezone
|
140
137
|
end
|
141
138
|
|
142
139
|
def initialize(subdomain, api_token)
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
# Make the base directory immediately so we can start logging errors.
|
148
|
-
FileUtils.mkdir_p account_dir
|
149
|
-
|
150
|
-
CampfireExport::Account.timezone = parse_timezone
|
140
|
+
Account.subdomain = subdomain
|
141
|
+
Account.api_token = api_token
|
142
|
+
Account.base_url = "https://#{subdomain}.campfirenow.com"
|
151
143
|
end
|
152
|
-
|
153
|
-
def
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
find_tzinfo(selected_zone.attribute("value").text)
|
159
|
-
rescue => e
|
160
|
-
log(:error, "couldn't find timezone setting (using GMT instead)", e)
|
161
|
-
find_tzinfo("Etc/GMT")
|
162
|
-
end
|
144
|
+
|
145
|
+
def find_timezone
|
146
|
+
settings = Nokogiri::HTML get('/account/settings').body
|
147
|
+
selected_zone = settings.css('select[id="account_time_zone_id"] ' +
|
148
|
+
'> option[selected="selected"]')
|
149
|
+
Account.timezone = find_tzinfo(selected_zone.attribute("value").text)
|
163
150
|
end
|
164
|
-
|
165
|
-
def
|
166
|
-
|
167
|
-
|
168
|
-
doc.css('room').each do |room_xml|
|
169
|
-
room = CampfireExport::Room.new(room_xml)
|
170
|
-
room.export(start_date, end_date)
|
171
|
-
end
|
172
|
-
rescue => e
|
173
|
-
log(:error, "room list download failed", e)
|
174
|
-
end
|
151
|
+
|
152
|
+
def rooms
|
153
|
+
doc = Nokogiri::XML get('/rooms.xml').body
|
154
|
+
doc.css('room').map {|room_xml| Room.new(room_xml) }
|
175
155
|
end
|
176
156
|
end
|
177
157
|
|
@@ -180,30 +160,21 @@ module CampfireExport
|
|
180
160
|
attr_accessor :id, :name, :created_at, :last_update
|
181
161
|
|
182
162
|
def initialize(room_xml)
|
183
|
-
@id
|
184
|
-
@name
|
185
|
-
|
186
|
-
|
187
|
-
@created_at = CampfireExport::Account.timezone.utc_to_local(created_utc)
|
188
|
-
|
189
|
-
begin
|
190
|
-
last_message = Nokogiri::XML get("/room/#{id}/recent.xml?limit=1").body
|
191
|
-
update_utc = DateTime.parse(last_message.css('created-at').text)
|
192
|
-
@last_update = CampfireExport::Account.timezone.utc_to_local(update_utc)
|
193
|
-
rescue => e
|
194
|
-
log(:error, "couldn't get last update in #{room} (defaulting to today)", e)
|
195
|
-
@last_update = Time.now
|
196
|
-
end
|
163
|
+
@id = room_xml.css('id').text
|
164
|
+
@name = room_xml.css('name').text
|
165
|
+
created_utc = DateTime.parse(room_xml.css('created-at').text)
|
166
|
+
@created_at = Account.timezone.utc_to_local(created_utc)
|
197
167
|
end
|
198
|
-
|
168
|
+
|
199
169
|
def export(start_date=nil, end_date=nil)
|
200
170
|
# Figure out how to do the least amount of work while still conforming
|
201
171
|
# to the requester's boundary dates.
|
172
|
+
find_last_update
|
202
173
|
start_date.nil? ? date = created_at : date = [start_date, created_at].max
|
203
174
|
end_date.nil? ? end_date = last_update : end_date = [end_date, last_update].min
|
204
175
|
|
205
176
|
while date <= end_date
|
206
|
-
transcript =
|
177
|
+
transcript = Transcript.new(self, date)
|
207
178
|
transcript.export
|
208
179
|
|
209
180
|
# Ensure that we stay well below the 37signals API limits.
|
@@ -211,6 +182,20 @@ module CampfireExport
|
|
211
182
|
date = date.next
|
212
183
|
end
|
213
184
|
end
|
185
|
+
|
186
|
+
private
|
187
|
+
def find_last_update
|
188
|
+
begin
|
189
|
+
last_message = Nokogiri::XML get("/room/#{id}/recent.xml?limit=1").body
|
190
|
+
update_utc = DateTime.parse(last_message.css('created-at').text)
|
191
|
+
@last_update = Account.timezone.utc_to_local(update_utc)
|
192
|
+
rescue Exception => e
|
193
|
+
log(:error,
|
194
|
+
"couldn't get last update in #{room} (defaulting to today)",
|
195
|
+
e)
|
196
|
+
@last_update = Time.now
|
197
|
+
end
|
198
|
+
end
|
214
199
|
end
|
215
200
|
|
216
201
|
class Transcript
|
@@ -242,7 +227,7 @@ module CampfireExport
|
|
242
227
|
log(:info, "exporting transcripts\n")
|
243
228
|
begin
|
244
229
|
FileUtils.mkdir_p export_dir
|
245
|
-
rescue => e
|
230
|
+
rescue Exception => e
|
246
231
|
log(:error, "Unable to create #{export_dir}", e)
|
247
232
|
else
|
248
233
|
export_xml
|
@@ -260,7 +245,7 @@ module CampfireExport
|
|
260
245
|
begin
|
261
246
|
export_file(xml, 'transcript.xml')
|
262
247
|
verify_export('transcript.xml', xml.to_s.length)
|
263
|
-
rescue => e
|
248
|
+
rescue Exception => e
|
264
249
|
log(:error, "XML transcript export for #{export_dir} failed", e)
|
265
250
|
end
|
266
251
|
end
|
@@ -273,7 +258,7 @@ module CampfireExport
|
|
273
258
|
messages.each {|message| plaintext << message.to_s }
|
274
259
|
export_file(plaintext, 'transcript.txt')
|
275
260
|
verify_export('transcript.txt', plaintext.length)
|
276
|
-
rescue => e
|
261
|
+
rescue Exception => e
|
277
262
|
log(:error, "Plaintext transcript export for #{export_dir} failed", e)
|
278
263
|
end
|
279
264
|
end
|
@@ -293,7 +278,7 @@ module CampfireExport
|
|
293
278
|
|
294
279
|
export_file(transcript_html, 'transcript.html')
|
295
280
|
verify_export('transcript.html', transcript_html.length)
|
296
|
-
rescue => e
|
281
|
+
rescue Exception => e
|
297
282
|
log(:error, "HTML transcript export for #{export_dir} failed", e)
|
298
283
|
end
|
299
284
|
end
|
@@ -303,7 +288,7 @@ module CampfireExport
|
|
303
288
|
if message.is_upload?
|
304
289
|
begin
|
305
290
|
message.upload.export
|
306
|
-
rescue => e
|
291
|
+
rescue Exception => e
|
307
292
|
path = "#{message.upload.export_dir}/#{message.upload.filename}"
|
308
293
|
log(:error, "Upload export for #{path} failed", e)
|
309
294
|
end
|
@@ -339,7 +324,7 @@ module CampfireExport
|
|
339
324
|
@@usernames ||= {}
|
340
325
|
@@usernames[user_id] ||= begin
|
341
326
|
doc = Nokogiri::XML get("/users/#{user_id}.xml").body
|
342
|
-
rescue
|
327
|
+
rescue Exception => e
|
343
328
|
"[unknown user]"
|
344
329
|
else
|
345
330
|
# Take the first name and last initial, if there is more than one name.
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'campfire_export'
|
2
|
+
require 'tzinfo'
|
3
|
+
|
4
|
+
module CampfireExport
|
5
|
+
describe Account do
|
6
|
+
before(:each) do
|
7
|
+
@subdomain = "test-subdomain"
|
8
|
+
@api_token = "test-apikey"
|
9
|
+
@account = Account.new(@subdomain, @api_token)
|
10
|
+
|
11
|
+
@good_timezone = '<select id="account_time_zone_id">' +
|
12
|
+
'<option selected="selected" value="America/Los_Angeles">' +
|
13
|
+
'</option></select>'
|
14
|
+
@bad_timezone = @good_timezone.gsub('America/Los_Angeles',
|
15
|
+
'No Such Timezone')
|
16
|
+
@timezone_html = stub("timezone HTML block")
|
17
|
+
@timezone_html.stub(:body).and_return(@good_timezone)
|
18
|
+
end
|
19
|
+
|
20
|
+
context "when it is created" do
|
21
|
+
it "sets up the account config variables" do
|
22
|
+
Account.subdomain.should equal(@subdomain)
|
23
|
+
Account.api_token.should equal(@api_token)
|
24
|
+
Account.base_url.should == "https://#{@subdomain}.campfirenow.com"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "when timezone is loaded" do
|
29
|
+
it "determines the user's timezone" do
|
30
|
+
@account.should_receive(:get).with("/account/settings"
|
31
|
+
).and_return(@timezone_html)
|
32
|
+
@account.find_timezone
|
33
|
+
Account.timezone.to_s.should == "America - Los Angeles"
|
34
|
+
end
|
35
|
+
|
36
|
+
it "raises an error if it gets a bad time zone identifier" do
|
37
|
+
@timezone_html.stub(:body).and_return(@bad_timezone)
|
38
|
+
@account.stub(:get).with("/account/settings"
|
39
|
+
).and_return(@timezone_html)
|
40
|
+
expect {
|
41
|
+
@account.find_timezone
|
42
|
+
}.to raise_error(TZInfo::InvalidTimezoneIdentifier)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "raises an error if it can't get the account settings at all" do
|
46
|
+
@account.stub(:get).with("/account/settings"
|
47
|
+
).and_raise(CampfireExport::Exception.new("/account/settings",
|
48
|
+
"Not Found", 404))
|
49
|
+
expect {
|
50
|
+
@account.find_timezone
|
51
|
+
}.to raise_error(CampfireExport::Exception)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "when rooms are requested" do
|
56
|
+
it "returns an array of rooms" do
|
57
|
+
room_xml = "<rooms><room>1</room><room>2</room><room>3</room></rooms>"
|
58
|
+
room_doc = mock("room doc")
|
59
|
+
room_doc.should_receive(:body).and_return(room_xml)
|
60
|
+
@account.should_receive(:get).with('/rooms.xml').and_return(room_doc)
|
61
|
+
room = mock("room")
|
62
|
+
Room.should_receive(:new).exactly(3).times.and_return(room)
|
63
|
+
@account.rooms.should have(3).items
|
64
|
+
end
|
65
|
+
|
66
|
+
it "raises an error if it can't get the room list" do
|
67
|
+
@account.stub(:get).with('/rooms.xml'
|
68
|
+
).and_raise(CampfireExport::Exception.new('/rooms.xml',
|
69
|
+
"Not Found", 404))
|
70
|
+
expect {
|
71
|
+
@account.rooms
|
72
|
+
}.to raise_error(CampfireExport::Exception)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'campfire_export'
|
2
|
+
require 'campfire_export/timezone'
|
3
|
+
|
4
|
+
require 'nokogiri'
|
5
|
+
|
6
|
+
module CampfireExport
|
7
|
+
describe Room do
|
8
|
+
include TimeZone
|
9
|
+
|
10
|
+
before :each do
|
11
|
+
@room_xml = Nokogiri::XML "<room><name>Test Room</name><id>666</id>" +
|
12
|
+
"<created-at>2009-11-17T19:41:38Z</created-at></room>"
|
13
|
+
Account.timezone = find_tzinfo("America/Los_Angeles")
|
14
|
+
end
|
15
|
+
|
16
|
+
context "when it is created" do
|
17
|
+
it "sets up basic properties" do
|
18
|
+
room = Room.new(@room_xml)
|
19
|
+
room.name.should == "Test Room"
|
20
|
+
room.id.should == "666"
|
21
|
+
room.created_at.should == DateTime.parse("2009-11-17T11:41:38Z")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "when it finds the last update" do
|
26
|
+
it "loads the last update from the most recent message"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: campfire_export
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease:
|
4
|
+
prerelease: false
|
6
5
|
segments:
|
7
6
|
- 0
|
8
7
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
8
|
+
- 1
|
9
|
+
version: 0.1.1
|
11
10
|
platform: ruby
|
12
11
|
authors:
|
13
12
|
- Marc Hedlund
|
@@ -15,17 +14,16 @@ autorequire:
|
|
15
14
|
bindir: bin
|
16
15
|
cert_chain: []
|
17
16
|
|
18
|
-
date: 2011-
|
17
|
+
date: 2011-08-04 00:00:00 -04:00
|
18
|
+
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: bundler
|
22
22
|
prerelease: false
|
23
23
|
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
-
none: false
|
25
24
|
requirements:
|
26
25
|
- - ~>
|
27
26
|
- !ruby/object:Gem::Version
|
28
|
-
hash: 9
|
29
27
|
segments:
|
30
28
|
- 1
|
31
29
|
- 0
|
@@ -34,53 +32,75 @@ dependencies:
|
|
34
32
|
type: :development
|
35
33
|
version_requirements: *id001
|
36
34
|
- !ruby/object:Gem::Dependency
|
37
|
-
name:
|
35
|
+
name: fuubar
|
38
36
|
prerelease: false
|
39
37
|
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
-
none: false
|
41
38
|
requirements:
|
42
39
|
- - ~>
|
43
40
|
- !ruby/object:Gem::Version
|
44
|
-
|
41
|
+
segments:
|
42
|
+
- 0
|
43
|
+
- 0
|
44
|
+
- 5
|
45
|
+
version: 0.0.5
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: rspec
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ~>
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
segments:
|
56
|
+
- 2
|
57
|
+
- 6
|
58
|
+
- 0
|
59
|
+
version: 2.6.0
|
60
|
+
type: :development
|
61
|
+
version_requirements: *id003
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: tzinfo
|
64
|
+
prerelease: false
|
65
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
45
69
|
segments:
|
46
70
|
- 0
|
47
71
|
- 3
|
48
72
|
- 29
|
49
73
|
version: 0.3.29
|
50
74
|
type: :development
|
51
|
-
version_requirements: *
|
75
|
+
version_requirements: *id004
|
52
76
|
- !ruby/object:Gem::Dependency
|
53
77
|
name: httparty
|
54
78
|
prerelease: false
|
55
|
-
requirement: &
|
56
|
-
none: false
|
79
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
57
80
|
requirements:
|
58
81
|
- - ~>
|
59
82
|
- !ruby/object:Gem::Version
|
60
|
-
hash: 19
|
61
83
|
segments:
|
62
84
|
- 0
|
63
85
|
- 7
|
64
86
|
- 8
|
65
87
|
version: 0.7.8
|
66
88
|
type: :development
|
67
|
-
version_requirements: *
|
89
|
+
version_requirements: *id005
|
68
90
|
- !ruby/object:Gem::Dependency
|
69
91
|
name: nokogiri
|
70
92
|
prerelease: false
|
71
|
-
requirement: &
|
72
|
-
none: false
|
93
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
73
94
|
requirements:
|
74
95
|
- - ~>
|
75
96
|
- !ruby/object:Gem::Version
|
76
|
-
hash: 13
|
77
97
|
segments:
|
78
98
|
- 1
|
79
99
|
- 4
|
80
100
|
- 5
|
81
101
|
version: 1.4.5
|
82
102
|
type: :development
|
83
|
-
version_requirements: *
|
103
|
+
version_requirements: *id006
|
84
104
|
description: Export transcripts and uploaded files from your 37signals' Campfire account.
|
85
105
|
email:
|
86
106
|
- marc@precipice.org
|
@@ -101,6 +121,9 @@ files:
|
|
101
121
|
- lib/campfire_export.rb
|
102
122
|
- lib/campfire_export/timezone.rb
|
103
123
|
- lib/campfire_export/version.rb
|
124
|
+
- spec/campfire_export/account_spec.rb
|
125
|
+
- spec/campfire_export/room_spec.rb
|
126
|
+
has_rdoc: true
|
104
127
|
homepage: https://github.com/precipice/campfire_export
|
105
128
|
licenses:
|
106
129
|
- Apache 2.0
|
@@ -110,31 +133,28 @@ rdoc_options: []
|
|
110
133
|
require_paths:
|
111
134
|
- lib
|
112
135
|
required_ruby_version: !ruby/object:Gem::Requirement
|
113
|
-
none: false
|
114
136
|
requirements:
|
115
137
|
- - ">="
|
116
138
|
- !ruby/object:Gem::Version
|
117
|
-
hash: 57
|
118
139
|
segments:
|
119
140
|
- 1
|
120
141
|
- 8
|
121
142
|
- 7
|
122
143
|
version: 1.8.7
|
123
144
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
124
|
-
none: false
|
125
145
|
requirements:
|
126
146
|
- - ">="
|
127
147
|
- !ruby/object:Gem::Version
|
128
|
-
hash: 3
|
129
148
|
segments:
|
130
149
|
- 0
|
131
150
|
version: "0"
|
132
151
|
requirements: []
|
133
152
|
|
134
153
|
rubyforge_project: campfire_export
|
135
|
-
rubygems_version: 1.
|
154
|
+
rubygems_version: 1.3.6
|
136
155
|
signing_key:
|
137
156
|
specification_version: 3
|
138
157
|
summary: Export transcripts and uploaded files from your 37signals' Campfire account.
|
139
|
-
test_files:
|
140
|
-
|
158
|
+
test_files:
|
159
|
+
- spec/campfire_export/account_spec.rb
|
160
|
+
- spec/campfire_export/room_spec.rb
|