cloudapp-power-cli 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|