cloudapp-power-cli 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/Gemfile +3 -0
- data/README.md +77 -0
- data/bin/cloudapp +506 -0
- data/cloudapp-power-cli.gemspec +17 -0
- metadata +62 -0
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# cloudapp-power-cli
|
2
|
+
|
3
|
+
A powerful CLI for [CloudApp](http://getcloudapp.com/) written in Ruby, formerly named *cloudapp-cli* that makes the complete CloudApp API available on command line.
|
4
|
+
|
5
|
+
**[Works with regenwolken](https://github.com/posativ/regenwolken)**
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Install it via:
|
10
|
+
|
11
|
+
gem install cloudapp-power-cli
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
Register:
|
16
|
+
|
17
|
+
cloudapp register us3r passw0rd
|
18
|
+
|
19
|
+
Configuration of persistent login data:
|
20
|
+
|
21
|
+
cloudapp login us3r passw0rd
|
22
|
+
|
23
|
+
(Note: If you tell it so e.g. via the above command or during `cloudapp register`, the cli will save your login data to '~/.cloudapp-cli' to prevent you from specifing it on every call.)
|
24
|
+
|
25
|
+
Use per-call login data (**no** login data will be written to disk at all):
|
26
|
+
|
27
|
+
cloudapp -u user:pwd command...
|
28
|
+
|
29
|
+
Other Operations:
|
30
|
+
|
31
|
+
cloudapp account # show your account details (-v shows even more)
|
32
|
+
|
33
|
+
cloudapp list # list first page of drops (5 per page)
|
34
|
+
|
35
|
+
cloudapp list -p 2 -n 10 # list second page of drops with 10 drops per page
|
36
|
+
|
37
|
+
cloudapp list -a # list all your drops (first 100 ^^)
|
38
|
+
|
39
|
+
cloudapp upload drumandbass.ogg # upload a file
|
40
|
+
|
41
|
+
cloudapp upload --private topsecret.pdf # upload a file as private
|
42
|
+
|
43
|
+
cloudapp bookmark heise http://heise.de/ # creates an alias
|
44
|
+
|
45
|
+
cloudapp bookmark http://google.com/ # the url will be the alias' name
|
46
|
+
|
47
|
+
cloudapp download dZ69 # download a drop to current directory
|
48
|
+
|
49
|
+
cloudapp view dZ69 # view details of a drop (more details with -v)
|
50
|
+
|
51
|
+
cloudapp change username thenewname # changes e-mail and updates the local credentials, too
|
52
|
+
|
53
|
+
cloudapp change password thenewpwd # changes password and updates the local credentials, too
|
54
|
+
|
55
|
+
cloudapp change privacy private # change default security of _new_ drops to private (public is the other option)
|
56
|
+
|
57
|
+
cloudapp private dZ69 # change a drop to private
|
58
|
+
|
59
|
+
cloudapp rename dZ69 UserGuide.txt # rename a drop
|
60
|
+
|
61
|
+
cloudapp delete dZ69 # delete a drop
|
62
|
+
|
63
|
+
cloudapp recover dZ69 # recover a drop
|
64
|
+
|
65
|
+
cloudapp search Screenshot # list all items with "Screenshot" in name or redirect url (for bookmarks)
|
66
|
+
|
67
|
+
cloudapp search "^.*rb$" # since the term is effectively a regex you could do many magic for filtering
|
68
|
+
|
69
|
+
cloudapp gc-view GIFT1234 # view gift card details
|
70
|
+
|
71
|
+
cloudapp gc-redeem GIFT1234 # redeem a gift card
|
72
|
+
|
73
|
+
Note: If you want to specify arguments (e.g. item IDs or file names) with a leading dash, the Option Parser will claim about an unknown option - than you should insert an double-dash followed by a space in front of the argument with the leading dash.
|
74
|
+
|
75
|
+
## Credits
|
76
|
+
|
77
|
+
To [posativ](https://github.com/posativ) for testing and bug reporting!
|
data/bin/cloudapp
ADDED
@@ -0,0 +1,506 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
require 'date'
|
5
|
+
require 'optparse'
|
6
|
+
require 'uri'
|
7
|
+
require 'yaml'
|
8
|
+
|
9
|
+
require 'rubygems'
|
10
|
+
require 'cloudapp_api'
|
11
|
+
|
12
|
+
LIST_TIME_FMT = '%d.%m.%y %H:%M'
|
13
|
+
VIEW_TIME_FMT = '%d. %b %Y %H:%M:%S'
|
14
|
+
ACC_TIME_FMT = VIEW_TIME_FMT
|
15
|
+
|
16
|
+
ITEM_TYPES = [:image, :bookmark, :text, :archive, :audio, :video, :unknown]
|
17
|
+
|
18
|
+
# just a guess to make output format prettier
|
19
|
+
HASH_LENGTH = 10
|
20
|
+
|
21
|
+
# low level errors
|
22
|
+
LOW_ERRORS = [
|
23
|
+
#TimeoutError, EOFError, Errno::ETIMEDOUT, Errno::ECONNREFUSED,
|
24
|
+
Errno::EPIPE, Errno::EINVAL
|
25
|
+
]
|
26
|
+
|
27
|
+
module CloudApp
|
28
|
+
class Drop
|
29
|
+
def slug
|
30
|
+
url.split(/\//).last
|
31
|
+
end
|
32
|
+
|
33
|
+
# patch for cloudapp_api supporting custom domains
|
34
|
+
def self.find(id)
|
35
|
+
res = get "http://#{$domain}/#{id}"
|
36
|
+
res.ok? ? Drop.new(res) : bad_response(res)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Module
|
42
|
+
def subclasses
|
43
|
+
classes = []
|
44
|
+
ObjectSpace.each_object do |klass|
|
45
|
+
next unless Module === klass
|
46
|
+
classes << klass if self > klass
|
47
|
+
end
|
48
|
+
classes
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class String
|
53
|
+
def json2time
|
54
|
+
# offset should be zero since this is assumed to be UTC
|
55
|
+
dt = DateTime.strptime(self, "%Y-%m-%dT%H:%M:%SZ")
|
56
|
+
Time.utc(dt.year, dt.month, dt.day, dt.hour, dt.min, dt.sec)
|
57
|
+
# "2011-09-24T08:49:14Z"
|
58
|
+
end
|
59
|
+
|
60
|
+
def localtime
|
61
|
+
json2time.localtime
|
62
|
+
end
|
63
|
+
|
64
|
+
def json2fmtlocaltime(fmt)
|
65
|
+
json2time.localtime.strftime(fmt)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class Time
|
70
|
+
# HACK: this is a hack to ensure older httparty then 0.8.0 will work too
|
71
|
+
def json2fmtlocaltime(fmt)
|
72
|
+
localtime.strftime(fmt)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def args?(n=2)
|
77
|
+
if ARGV.length < n
|
78
|
+
puts @opts
|
79
|
+
abort "Wrong number of arguments given!"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# only use bold colors (1;) for those on black on white screens
|
84
|
+
def cyan; if @options[:colors]; "\e[1;36m" else "" end end
|
85
|
+
def green; if @options[:colors]; "\e[1;32m" else "" end end
|
86
|
+
def red; if @options[:colors]; "\e[1;31m" else "" end end
|
87
|
+
def rst; if @options[:colors]; "\e[0m" else "" end end
|
88
|
+
|
89
|
+
def print_long(drop)
|
90
|
+
list_flags = ''
|
91
|
+
if drop.private; list_flags += [green, 'p', rst].join else list_flags += '-' end
|
92
|
+
if !drop.deleted_at.nil?; list_flags += [red, 'd', rst].join else list_flags += '-' end
|
93
|
+
if not drop.redirect_url.nil?
|
94
|
+
list_name = ["-> ", cyan, drop.redirect_url, rst].join
|
95
|
+
# prepend the drop's name, if it's different from the url
|
96
|
+
# (which should only happen if you specify it by hand)
|
97
|
+
list_name = [cyan, drop.name, rst, " ", list_name].join if drop.name != drop.redirect_url
|
98
|
+
elsif drop.item_type == 'image'
|
99
|
+
list_name = [green, drop.name, rst].join
|
100
|
+
else
|
101
|
+
list_name = drop.name
|
102
|
+
end
|
103
|
+
format("%-#{HASH_LENGTH}s %s %-9s %-10s %-#{8 + $domain.length + HASH_LENGTH}s %s",
|
104
|
+
drop.slug, list_flags, drop.item_type,
|
105
|
+
drop.updated_at.json2fmtlocaltime(LIST_TIME_FMT),
|
106
|
+
drop.url, list_name)
|
107
|
+
end
|
108
|
+
|
109
|
+
def fetch(url, limit = 3)
|
110
|
+
raise ArgumentError, 'HTTP redirection too deep' if limit == 0
|
111
|
+
|
112
|
+
response = Net::HTTP.get_response(URI.parse(url))
|
113
|
+
case response
|
114
|
+
when Net::HTTPSuccess then response
|
115
|
+
when Net::HTTPRedirection then fetch(response['location'], limit-1)
|
116
|
+
else response.error! end
|
117
|
+
end
|
118
|
+
|
119
|
+
def load_config
|
120
|
+
if @options.key?(:creds)
|
121
|
+
u,p = @options[:creds].split(':')
|
122
|
+
return {'username' => u, 'password' => p}
|
123
|
+
end
|
124
|
+
if !File.exists?($config_file)
|
125
|
+
abort "Login required! Please save your login via ´login´ or use ´-u´ option."
|
126
|
+
end
|
127
|
+
YAML.load_file($config_file)
|
128
|
+
end
|
129
|
+
|
130
|
+
def save_config(config)
|
131
|
+
if File.exists?($config_file)
|
132
|
+
config = YAML.load_file($config_file).merge(config)
|
133
|
+
end
|
134
|
+
File.open($config_file, 'w+') do |f| YAML.dump(config, f) end
|
135
|
+
end
|
136
|
+
|
137
|
+
def load_service_url
|
138
|
+
return @options[:service_url] if @options.key?(:service_url)
|
139
|
+
if File.exists?($config_file)
|
140
|
+
config = YAML.load_file($config_file)
|
141
|
+
return config['service_url'] if config['service_url']
|
142
|
+
end
|
143
|
+
"http://my.cl.ly"
|
144
|
+
end
|
145
|
+
|
146
|
+
# main starts here
|
147
|
+
|
148
|
+
@options = {:colors => true}
|
149
|
+
|
150
|
+
@opts = OptionParser.new do |opts|
|
151
|
+
opts.banner = "Usage: cloudapp [options] command [arguments]\n\n"
|
152
|
+
opts.banner += "Commands:\n"
|
153
|
+
opts.banner += " register USERNAME PASSWORD\n"
|
154
|
+
opts.banner += " login [USERNAME] [PASSWORD]\n"
|
155
|
+
opts.banner += " service [URL]\n"
|
156
|
+
opts.banner += " change (username|password|privacy) NEWVALUE\n"
|
157
|
+
opts.banner += " account\n"
|
158
|
+
opts.banner += " list\n"
|
159
|
+
opts.banner += " upload FILE\n"
|
160
|
+
opts.banner += " bookmark [NAME] LINK\n"
|
161
|
+
opts.banner += " download SLUG\n"
|
162
|
+
opts.banner += " view SLUG\n"
|
163
|
+
opts.banner += " delete SLUG\n"
|
164
|
+
opts.banner += " recover SLUG\n"
|
165
|
+
opts.banner += " rename SLUG NAME\n"
|
166
|
+
opts.banner += " private SLUG\n"
|
167
|
+
opts.banner += " public SLUG\n"
|
168
|
+
opts.banner += " search NAME\n"
|
169
|
+
opts.banner += " gc-view CODE\n"
|
170
|
+
opts.banner += " gc-redeem CODE\n"
|
171
|
+
opts.banner += " auto-purge HOURS\n"
|
172
|
+
opts.banner += "\nOptions:\n"
|
173
|
+
|
174
|
+
opts.on('-u CREDS', 'Specify credentials as USER:PASS') do |u| @options[:creds] = u end
|
175
|
+
opts.on('-p PAGE', Integer, 'Show page with nr. PAGE in `list` (default: 1)') do |p| @options[:page] = p if p > 0 end
|
176
|
+
opts.on('-n ITEMSPP', Integer, 'Show ITEMSPP items per page in `list` (default: 5)') do |n| @options[:per_page] = n if n > 0 end
|
177
|
+
opts.on('-t TYPE', '--type TYPE', ITEM_TYPES, 'Show only items of TYPE in `list`') do |t| @options[:type] = t end
|
178
|
+
opts.on('-s SOURCE', '--source SOURCE', 'Show only items from SOURCE in ´list´') do |s| @options[:source] = s end
|
179
|
+
opts.on('-d', '--deleted', 'Show deleted items in ´list´ too') do |d| @options[:deleted] = true end
|
180
|
+
opts.on('-a', '--all', 'Show all items of an account at once in `list` (overrides -p, -n)') do |a| @options[:all] = true end
|
181
|
+
opts.on('--private', 'Do private upload') do |priv| @options[:private] = true end
|
182
|
+
opts.on('--public', 'Do public upload') do |pub| @options[:public] = true end
|
183
|
+
opts.on('--disable-colors', 'Disables colors') do |dis| @options[:colors] = false end
|
184
|
+
opts.on('--service URL', 'Specify service URL') do |url| @options[:service_url] = url end
|
185
|
+
opts.on('-y', '--yes', 'Answer all questions with yes (useful e.g. in scripts)') do |y| @options[:yes] = true end
|
186
|
+
opts.on('-v', '--verbose', 'Enables verbose mode on some commands (currently view, account)') do |v| @options[:verbose] = true end
|
187
|
+
opts.on('-h', '-?', '--help', 'Display this screen') do
|
188
|
+
puts opts
|
189
|
+
exit
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
@opts.parse!
|
194
|
+
|
195
|
+
$config_file = File.join(ENV['HOME'], '.cloudapp-cli')
|
196
|
+
|
197
|
+
# HACK: inject our service url as HTTParty base_uri into
|
198
|
+
# all subclasses of CloudApp::Base
|
199
|
+
service_url = load_service_url
|
200
|
+
CloudApp::Base.subclasses.each do |c|
|
201
|
+
c.base_uri service_url
|
202
|
+
end
|
203
|
+
|
204
|
+
case ARGV.first
|
205
|
+
when nil
|
206
|
+
puts @opts
|
207
|
+
exit
|
208
|
+
when 'login'
|
209
|
+
if ARGV[1] and ARGV[2]
|
210
|
+
client = CloudApp::Client.new
|
211
|
+
client.authenticate(ARGV[1], ARGV[2])
|
212
|
+
save = true
|
213
|
+
begin
|
214
|
+
CloudApp::Account.find
|
215
|
+
rescue => ex
|
216
|
+
save = false
|
217
|
+
end
|
218
|
+
if !save
|
219
|
+
if @options[:yes]
|
220
|
+
puts "Saving login though it seems incorrect."
|
221
|
+
save = true
|
222
|
+
elsif $stdin.tty?
|
223
|
+
print "Your new login seems to be incorrect, save anyways? (y/n): "
|
224
|
+
answer = $stdin.gets
|
225
|
+
case answer
|
226
|
+
when "y\n"
|
227
|
+
save = true
|
228
|
+
else
|
229
|
+
puts "You didn't answer with 'y', done nothing."
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
if save
|
234
|
+
# set
|
235
|
+
save_config({'username' => ARGV[1], 'password' => ARGV[2]})
|
236
|
+
puts 'Account login saved.'
|
237
|
+
end
|
238
|
+
else
|
239
|
+
# get
|
240
|
+
config = load_config
|
241
|
+
puts "Current login:"
|
242
|
+
puts " Username: #{config['username']}"
|
243
|
+
puts " Password: #{config['password']}"
|
244
|
+
end
|
245
|
+
exit 0
|
246
|
+
when 'register'
|
247
|
+
args? 3
|
248
|
+
begin
|
249
|
+
acc = CloudApp::Account.create :email => ARGV[1], :password => ARGV[2], :accept_tos => true
|
250
|
+
rescue CloudApp::ResponseError => ex
|
251
|
+
abort "Username already registered!" if ex.code == 406
|
252
|
+
abort "Server rejected request because of invalid username/email or password!" if ex.code = 422
|
253
|
+
abort "Registration failed (#{ex.to_s}) - may be a bug"
|
254
|
+
rescue => ex
|
255
|
+
abort "Registration failed (#{ex.to_s})"
|
256
|
+
end
|
257
|
+
puts "Successfully registered!\nYour account has been activated instantly, happy clouding!" if not acc.activated_at.nil?
|
258
|
+
puts "Successfully registered but your account isn't currently activated." if acc.activated_at.nil?
|
259
|
+
if @options[:yes]
|
260
|
+
puts "Saving login to local login storage #{$config_file}."
|
261
|
+
save_config({'username' => ARGV[1], 'password' => ARGV[2]})
|
262
|
+
elsif $stdin.tty?
|
263
|
+
if File.exists?($config_file)
|
264
|
+
print "Do you want to overwrite your local login storage #{$config_file}? (y/n): "
|
265
|
+
else
|
266
|
+
print "Do you want to save your login to #{$config_file} for automated use? (y/n): "
|
267
|
+
end
|
268
|
+
answer = $stdin.gets
|
269
|
+
case answer
|
270
|
+
when "y\n"
|
271
|
+
save_config({'username' => ARGV[1], 'password' => ARGV[2]})
|
272
|
+
puts "Account registered and login saved."
|
273
|
+
else
|
274
|
+
puts "You didn't answer with 'y', done nothing."
|
275
|
+
end
|
276
|
+
end
|
277
|
+
exit 0
|
278
|
+
when 'service'
|
279
|
+
if ARGV[1]
|
280
|
+
uri = ARGV[1]
|
281
|
+
uri = 'http://' + uri if URI.parse(ARGV[1]).scheme.nil?
|
282
|
+
if (uri =~ URI::regexp).nil?
|
283
|
+
puts "Your new service is not a valid URL, done nothing."
|
284
|
+
else
|
285
|
+
# set
|
286
|
+
save_config({'service_url' => uri})
|
287
|
+
puts "New service saved."
|
288
|
+
end
|
289
|
+
else
|
290
|
+
# get
|
291
|
+
puts "Current service: #{service_url}"
|
292
|
+
end
|
293
|
+
exit 0
|
294
|
+
end
|
295
|
+
|
296
|
+
config = load_config
|
297
|
+
|
298
|
+
@client = CloudApp::Client.new
|
299
|
+
|
300
|
+
# all following commands need authentication
|
301
|
+
@client.authenticate(config['username'], config['password'])
|
302
|
+
begin
|
303
|
+
@acc = CloudApp::Account.find
|
304
|
+
$domain = @acc.domain.nil? ? 'cl.ly' : @acc.domain
|
305
|
+
rescue CloudApp::ResponseError => ex
|
306
|
+
abort "Incorrect login!" if ex.code == 403
|
307
|
+
abort "Your account hasn't been activated!" if ex.code == 409
|
308
|
+
abort "Authentication unexpectedly failed: #{ex.to_s}\n #{ex.backtrace.join("\n ")}"
|
309
|
+
end
|
310
|
+
|
311
|
+
begin
|
312
|
+
case ARGV.first
|
313
|
+
when 'list'
|
314
|
+
topt = {:page => 1, :per_page => 5}
|
315
|
+
|
316
|
+
topt[:page] = @options[:page] if @options.key?(:page)
|
317
|
+
topt[:per_page] = @options[:per_page] if @options.key?(:per_page)
|
318
|
+
# HACK: 100 items should be enough for everyone!
|
319
|
+
topt[:page], topt[:per_page] = 1, 100 if @options[:all]
|
320
|
+
topt[:type] = @options[:type] if @options.key?(:type)
|
321
|
+
topt[:deleted] = @options[:deleted] if @options.key?(:deleted)
|
322
|
+
topt[:source] = @options[:source] if @options.key?(:source)
|
323
|
+
|
324
|
+
# caption
|
325
|
+
puts format("%-#{HASH_LENGTH}s %s", "SLUG", "p=private, d=deleted")
|
326
|
+
|
327
|
+
@client.drops(topt).each do |drop|
|
328
|
+
puts print_long(drop)
|
329
|
+
end
|
330
|
+
|
331
|
+
when 'view'
|
332
|
+
args?
|
333
|
+
drop = @client.drop ARGV[1]
|
334
|
+
puts "Details for #{drop.slug}:"
|
335
|
+
puts " Name: #{drop.name}"
|
336
|
+
puts " Type: #{drop.item_type}"
|
337
|
+
puts " URL: #{drop.url}"
|
338
|
+
puts " Privacy: #{drop.private ? 'private' : 'public'}"
|
339
|
+
puts " Views: #{drop.view_counter}"
|
340
|
+
puts " Redirect: #{drop.redirect_url}" if !drop.redirect_url.nil?
|
341
|
+
puts " Created: #{drop.created_at.json2fmtlocaltime(VIEW_TIME_FMT)}"
|
342
|
+
puts " Updated: #{drop.updated_at.json2fmtlocaltime(VIEW_TIME_FMT)}"
|
343
|
+
puts " Deleted: #{drop.deleted_at.json2fmtlocaltime(VIEW_TIME_FMT)}" if !drop.deleted_at.nil?
|
344
|
+
puts " Href: #{drop.href}" if @options[:verbose]
|
345
|
+
puts " Source: #{drop.source}" if @options[:verbose]
|
346
|
+
|
347
|
+
when 'change'
|
348
|
+
args? 3
|
349
|
+
case ARGV[1]
|
350
|
+
when 'username'
|
351
|
+
CloudApp::Account.update :email => ARGV[2], :current_password => config['password']
|
352
|
+
# only update local credential storage when current creds *not* given via command line
|
353
|
+
if @options[:creds].nil?
|
354
|
+
save_config({'username' => ARGV[2]})
|
355
|
+
puts "Login changed, updated #{$config_file}."
|
356
|
+
end
|
357
|
+
when 'password'
|
358
|
+
CloudApp::Account.update :password => ARGV[2], :current_password => config['password']
|
359
|
+
# only update local credential storage when current creds *not* given via command line
|
360
|
+
if @options[:creds].nil?
|
361
|
+
save_config({'password' => ARGV[2]})
|
362
|
+
puts "Login changed, updated #{$config_file}."
|
363
|
+
end
|
364
|
+
when 'privacy'
|
365
|
+
CloudApp::Account.update :private_items => (ARGV[2] == 'private')
|
366
|
+
end
|
367
|
+
|
368
|
+
when 'account'
|
369
|
+
# use @acc and stats
|
370
|
+
stats = CloudApp::Account.stats
|
371
|
+
puts "Account '#{config[:username]}':"
|
372
|
+
puts " ID: #{@acc.id}"
|
373
|
+
puts " Username: #{@acc.email}"
|
374
|
+
puts " Default privacy: #{@acc.private_items ? 'private' : 'public'}"
|
375
|
+
puts " Subscription: #{@acc.subscribed ? 'yes' : 'no'}" +
|
376
|
+
(@acc.subscribed ? " (expires: #{@acc.subscription_expires_at})" : '')
|
377
|
+
puts " Domain: #{@acc.domain}"
|
378
|
+
puts " Domain homepage: #{@acc.domain_home_page}"
|
379
|
+
puts " Created: #{@acc.created_at.json2fmtlocaltime(ACC_TIME_FMT)}"
|
380
|
+
puts " Updated: #{@acc.updated_at.json2fmtlocaltime(ACC_TIME_FMT)}" if @options[:verbose]
|
381
|
+
puts " Activated: #{@acc.activated_at.json2fmtlocaltime(ACC_TIME_FMT)}" if @options[:verbose]
|
382
|
+
puts
|
383
|
+
puts "Stats:"
|
384
|
+
puts " Nr. of items: #{stats[:items]}"
|
385
|
+
puts " Nr. of total views: #{stats[:views]}"
|
386
|
+
|
387
|
+
when 'upload'
|
388
|
+
args?
|
389
|
+
# TODO: Notify the user when exceeding upload limits
|
390
|
+
begin
|
391
|
+
if @options[:private]
|
392
|
+
drop = @client.upload ARGV[1], :private => true
|
393
|
+
elsif @options[:public]
|
394
|
+
drop = @client.upload ARGV[1], :private => false
|
395
|
+
else
|
396
|
+
drop = @client.upload ARGV[1]
|
397
|
+
end
|
398
|
+
puts "#{drop.url}"
|
399
|
+
rescue *LOW_ERRORS => ex
|
400
|
+
abort "Upload failed maybe due to too large file or server limits!"
|
401
|
+
end
|
402
|
+
|
403
|
+
when 'bookmark'
|
404
|
+
if ARGV.length == 2
|
405
|
+
drop = @client.bookmark ARGV[1], ARGV[1]
|
406
|
+
puts "Created bookmark #{drop.slug}, URL:\n#{drop.url}"
|
407
|
+
else
|
408
|
+
args? 3
|
409
|
+
drop = @client.bookmark ARGV[1], ARGV[2]
|
410
|
+
puts "Created bookmark #{drop.slug}, URL:\n#{drop.url}"
|
411
|
+
end
|
412
|
+
|
413
|
+
when 'private'
|
414
|
+
args?
|
415
|
+
drop = @client.privacy ARGV[1], true
|
416
|
+
puts "#{drop.slug} is now private"
|
417
|
+
|
418
|
+
when 'public'
|
419
|
+
args?
|
420
|
+
drop = @client.privacy ARGV[1], false
|
421
|
+
puts "#{drop.slug} is now public"
|
422
|
+
|
423
|
+
when 'delete','remove'
|
424
|
+
args?
|
425
|
+
drop = @client.delete ARGV[1]
|
426
|
+
puts "Moved #{drop.slug} into trash"
|
427
|
+
|
428
|
+
when 'recover'
|
429
|
+
args?
|
430
|
+
drop = @client.recover ARGV[1]
|
431
|
+
puts "Recovered #{drop.slug} from trash"
|
432
|
+
|
433
|
+
when 'rename'
|
434
|
+
args? 3
|
435
|
+
drop = @client.rename ARGV[1], ARGV[2]
|
436
|
+
puts "#{ARGV[1]} renamed to #{ARGV[2]}"
|
437
|
+
|
438
|
+
when 'download'
|
439
|
+
args?
|
440
|
+
drop = @client.drop ARGV[1]
|
441
|
+
if drop.item_type == 'bookmark'
|
442
|
+
abort "Cannot download bookmarks!"
|
443
|
+
end
|
444
|
+
# File name filtering - this build up from a union of NTFS, FAT, HFS, ext3
|
445
|
+
file_name = drop.name.gsub(/[\x00\/\\:\*\?\"<>\|\^]/, '_')
|
446
|
+
if File.exists?(file_name)
|
447
|
+
abort "Target file '#{file_name}' already exists! Canceling..."
|
448
|
+
end
|
449
|
+
res = fetch(drop.content_url)
|
450
|
+
File.open(file_name, 'w') do |file|
|
451
|
+
file.write(res.body)
|
452
|
+
end
|
453
|
+
puts "Downloaded #{drop.slug} as '#{file_name}'."
|
454
|
+
|
455
|
+
when 'search'
|
456
|
+
args?
|
457
|
+
@client.drops({:page => 1, :per_page => 100}).each do |drop|
|
458
|
+
# check drop.name and drop.redirect_url
|
459
|
+
if drop.name.match(ARGV[1]) or (!drop.redirect_url.nil? and drop.redirect_url.match(ARGV[1]))
|
460
|
+
puts print_long(drop)
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
when 'gc-view'
|
465
|
+
args?
|
466
|
+
gift = CloudApp::GiftCard.find ARGV[1]
|
467
|
+
puts "Details for gift card #{gift.code}:"
|
468
|
+
puts " ID: #{gift.id}"
|
469
|
+
puts " Plan: #{gift.plan}"
|
470
|
+
puts " Months: #{gift.months}"
|
471
|
+
puts " Href: #{gift.href}"
|
472
|
+
puts " Created: #{gift.created_at.json2fmtlocaltime(VIEW_TIME_FMT)}"
|
473
|
+
puts " Updated: #{gift.updated_at.json2fmtlocaltime(VIEW_TIME_FMT)}" if @options[:verbose]
|
474
|
+
puts " Redeemed: #{gift.redeemed_at.json2fmtlocaltime(VIEW_TIME_FMT)}" if !gift.redeemed_at.nil?
|
475
|
+
puts " Effective: #{gift.effective_at.json2fmtlocaltime(VIEW_TIME_FMT)}" if !gift.effective_at.nil?
|
476
|
+
puts " Expires: #{gift.expires_at.json2fmtlocaltime(VIEW_TIME_FMT)}" if !gift.expires_at.nil?
|
477
|
+
|
478
|
+
when 'gc-redeem'
|
479
|
+
args?
|
480
|
+
gift = CloudApp::GiftCard.redeem ARGV[1]
|
481
|
+
puts "Redeemed gift card #{gift.code}"
|
482
|
+
|
483
|
+
when 'auto-purge'
|
484
|
+
args?
|
485
|
+
hours = ARGV[1].to_i
|
486
|
+
abort "Hours given are negative" if hours < 0
|
487
|
+
puts "Request to delete all items created more than #{hours} hours ago from now"
|
488
|
+
latest = Time.now - hours*60*60
|
489
|
+
@client.drops({:page => 1, :per_page => 1000}).each do |drop|
|
490
|
+
if drop.created_at.localtime < latest
|
491
|
+
puts "Deleting #{drop.slug}"
|
492
|
+
@client.delete drop.slug
|
493
|
+
end
|
494
|
+
end
|
495
|
+
puts "Done"
|
496
|
+
|
497
|
+
else
|
498
|
+
puts @opts
|
499
|
+
abort "Unknown command!"
|
500
|
+
end
|
501
|
+
rescue CloudApp::ResponseError => ex
|
502
|
+
abort "Item not found!" if ex.code == 404
|
503
|
+
abort "File too large! (#{ex.to_s})" if ex.code == 413
|
504
|
+
abort "Your account hasn't been activated!" if ex.code == 409
|
505
|
+
abort "#{ARGV.first} unexpectedly failed: #{ex.to_s}\n #{ex.backtrace.join("\n ")}"
|
506
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
|
2
|
+
Gem::Specification.new do |s|
|
3
|
+
s.name = "cloudapp-power-cli"
|
4
|
+
s.version = "1.0.0"
|
5
|
+
s.summary = "A powerful CLI for CloudApp written in Ruby."
|
6
|
+
s.description = "A powerful CLI for CloudApp that makes the complete CloudApp API available on command line and works with Regenwolken."
|
7
|
+
s.author = "Christian Nicolai"
|
8
|
+
s.email = "cn@mycrobase.de"
|
9
|
+
s.license = "Apache License Version 2.0"
|
10
|
+
s.homepage = "https://github.com/cmur2/cloudapp-power-cli"
|
11
|
+
|
12
|
+
s.files = `git ls-files`.split($/)
|
13
|
+
s.executables = ["cloudapp"]
|
14
|
+
s.require_paths = ["lib"]
|
15
|
+
|
16
|
+
s.add_runtime_dependency "cloudapp_api"
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cloudapp-power-cli
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Christian Nicolai
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-03-29 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: cloudapp_api
|
16
|
+
requirement: &79873160 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *79873160
|
25
|
+
description: A powerful CLI for CloudApp that makes the complete CloudApp API available
|
26
|
+
on command line and works with Regenwolken.
|
27
|
+
email: cn@mycrobase.de
|
28
|
+
executables:
|
29
|
+
- cloudapp
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- Gemfile
|
34
|
+
- README.md
|
35
|
+
- bin/cloudapp
|
36
|
+
- cloudapp-power-cli.gemspec
|
37
|
+
homepage: https://github.com/cmur2/cloudapp-power-cli
|
38
|
+
licenses:
|
39
|
+
- Apache License Version 2.0
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options: []
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ! '>='
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
requirements: []
|
57
|
+
rubyforge_project:
|
58
|
+
rubygems_version: 1.8.6
|
59
|
+
signing_key:
|
60
|
+
specification_version: 3
|
61
|
+
summary: A powerful CLI for CloudApp written in Ruby.
|
62
|
+
test_files: []
|