qbt_client 0.1.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.
@@ -0,0 +1,608 @@
1
+ ##############################################################################
2
+ # File:: web_ui.rb
3
+ # Purpose:: Web UI client for qBittorrent.
4
+ #
5
+ # Author:: Jeff McAffee 2015-02-07
6
+ # Copyright:: Copyright (c) 2015, kTech Systems LLC. All rights reserved.
7
+ # Website:: http://ktechsystems.com
8
+ ##############################################################################
9
+
10
+ require 'json/pure'
11
+ require 'httparty'
12
+ require 'digest'
13
+
14
+
15
+ module QbtClient
16
+
17
+ class WebUI
18
+ include HTTParty
19
+
20
+ #debug_output $stdout
21
+
22
+ ###
23
+ # constructor
24
+ #
25
+ def initialize(ip, port, user, pass)
26
+ @ip = ip
27
+ @port = port
28
+ @user = user
29
+ @pass = pass
30
+
31
+ self.class.digest_auth(user, pass)
32
+ self.class.base_uri "#{ip}:#{port}"
33
+ end
34
+
35
+ ###
36
+ # Get array of all torrents
37
+ #
38
+ # Example response:
39
+ # [
40
+ # {
41
+ # "dlspeed"=>"3.1 MiB/s",
42
+ # "eta"=>"9m",
43
+ # "hash"=>"156b69b8643bd11849a5d8f2122e13fbb61bd041",
44
+ # "name"=>"slackware64-14.1-iso",
45
+ # "num_leechs"=>"1 (14)",
46
+ # "num_seeds"=>"97 (270)",
47
+ # "priority"=>"*",
48
+ # "progress"=>0.172291,
49
+ # "ratio"=>"0.0",
50
+ # "size"=>"2.2 GiB",
51
+ # "state"=>"downloading",
52
+ # "upspeed"=>"0 B/s"
53
+ # },
54
+ # {
55
+ # "dlspeed"=>"1.8 KiB/s",
56
+ # "eta"=>"28d 1h",
57
+ # "hash"=>"1fe5775d32d3e58e48b3a96dd2883c5250882cda",
58
+ # "name"=>"Grimm.S04E12.720p.HDTV.X264-DIMENSION.mkv",
59
+ # "num_leechs"=>"7 (471)",
60
+ # "num_seeds"=>"15 (1866)",
61
+ # "priority"=>"*",
62
+ # "progress"=>1.53669e-07,
63
+ # "ratio"=>"0.0",
64
+ # "size"=>"825.4 MiB",
65
+ # "state"=>"downloading",
66
+ # "upspeed"=>"0 B/s"
67
+ # }
68
+ # ]
69
+ #
70
+ def torrent_list
71
+ self.class.format :json
72
+ self.class.get('/json/torrents').parsed_response
73
+ end
74
+
75
+ def torrent_data torrent_hash
76
+ torrents = torrent_list
77
+
78
+ torrents.each do |t|
79
+ if t["hash"] == torrent_hash
80
+ return t
81
+ end
82
+ end
83
+ end
84
+
85
+ ###
86
+ # Get properties of a torrent (different data than what's returned
87
+ # in #torrent_list).
88
+ #
89
+ # Example response:
90
+ # {
91
+ # "comment"=>"Visit us: https://eztv.ch/ - Bitcoin: 1EZTVaGQ6UsjYJ9fwqGnd45oZ6HGT7WKZd",
92
+ # "creation_date"=>"Friday, February 6, 2015 8:01:22 PM MST",
93
+ # "dl_limit"=>"∞",
94
+ # "nb_connections"=>"0 (100 max)",
95
+ # "piece_size"=>"512.0 KiB",
96
+ # "save_path"=>"/home/jeff/Downloads/",
97
+ # "share_ratio"=>"0.0",
98
+ # "time_elapsed"=>"< 1m",
99
+ # "total_downloaded"=>"646.8 KiB (657.8 KiB this session)",
100
+ # "total_uploaded"=>"0 B (0 B this session)",
101
+ # "total_wasted"=>"428 B",
102
+ # "up_limit"=>"∞"
103
+ # }
104
+ #
105
+ def properties torrent_hash
106
+ self.class.format :json
107
+ self.class.get('/json/propertiesGeneral/' + torrent_hash).parsed_response
108
+ end
109
+
110
+ ###
111
+ # Get tracker data for a torrent
112
+ #
113
+ # Example response:
114
+ # [
115
+ # {
116
+ # "msg"=>"",
117
+ # "num_peers"=>"0",
118
+ # "status"=>"Working",
119
+ # "url"=>"udp://open.demonii.com:1337"},
120
+ # {
121
+ # "msg"=>"",
122
+ # "num_peers"=>"0",
123
+ # "status"=>"Not contacted yet",
124
+ # "url"=>"udp://tracker.coppersurfer.tk:6969"},
125
+ # {
126
+ # "msg"=>"",
127
+ # "num_peers"=>"0",
128
+ # "status"=>"Not contacted yet",
129
+ # "url"=>"udp://tracker.leechers-paradise.org:6969"},
130
+ # {
131
+ # "msg"=>"",
132
+ # "num_peers"=>"0",
133
+ # "status"=>"Not contacted yet",
134
+ # "url"=>"udp://exodus.desync.com:6969"}
135
+ # ]
136
+ #
137
+ def trackers torrent_hash
138
+ self.class.format :json
139
+ self.class.get('/json/propertiesTrackers/' + torrent_hash).parsed_response
140
+ end
141
+
142
+ ###
143
+ # Add one or more trackers to a torrent
144
+ #
145
+ # If passing mulitple urls, pass them as an array.
146
+ #
147
+ def add_trackers torrent_hash, urls
148
+ urls = Array(urls)
149
+ # Ampersands in urls must be escaped.
150
+ urls = urls.map { |url| url.gsub('&', '%26') }
151
+ urls = urls.join('%0A')
152
+
153
+ options = {
154
+ body: "hash=#{torrent_hash}&urls=#{urls}"
155
+ }
156
+
157
+ self.class.post('/command/addTrackers', options)
158
+ end
159
+
160
+ ###
161
+ # Get torrent contents (files data)
162
+ #
163
+ # Example response:
164
+ # [
165
+ # {
166
+ # "is_seed"=>false,
167
+ # "name"=>"Grimm.S04E12.720p.HDTV.X264-DIMENSION.mkv",
168
+ # "priority"=>1,
169
+ # "progress"=>0.0,
170
+ # "size"=>"825.4 MiB"
171
+ # }
172
+ # ]
173
+ #
174
+ def contents torrent_hash
175
+ self.class.format :json
176
+ self.class.get('/json/propertiesFiles/' + torrent_hash).parsed_response
177
+ end
178
+
179
+ ###
180
+ # Get application transfer info
181
+ #
182
+ # Example response:
183
+ # {
184
+ # "dl_info"=>"D: 0 B/s/s - T: 657.8 KiB",
185
+ # "up_info"=>"U: 0 B/s/s - T: 0 B"
186
+ # }
187
+ #
188
+ def transfer_info
189
+ self.class.format :json
190
+ self.class.get('/json/transferInfo').parsed_response
191
+ end
192
+
193
+ ###
194
+ # Get application preferences (options)
195
+ #
196
+ # Example response:
197
+ # {
198
+ # "alt_dl_limit"=>10,
199
+ # "alt_up_limit"=>10,
200
+ # "anonymous_mode"=>false,
201
+ # "autorun_enabled"=>false,
202
+ # "autorun_program"=>"",
203
+ # "bypass_local_auth"=>false,
204
+ # "dht"=>true,
205
+ # "dhtSameAsBT"=>true,
206
+ # "dht_port"=>6881,
207
+ # "dl_limit"=>-1,
208
+ # "dont_count_slow_torrents"=>false,
209
+ # "download_in_scan_dirs"=>[],
210
+ # "dyndns_domain"=>"changeme.dyndns.org",
211
+ # "dyndns_enabled"=>false,
212
+ # "dyndns_password"=>"",
213
+ # "dyndns_service"=>0,
214
+ # "dyndns_username"=>"",
215
+ # "enable_utp"=>true,
216
+ # "encryption"=>0,
217
+ # "export_dir"=>"",
218
+ # "export_dir_enabled"=>false,
219
+ # "incomplete_files_ext"=>false,
220
+ # "ip_filter_enabled"=>false,
221
+ # "ip_filter_path"=>"",
222
+ # "limit_tcp_overhead"=>false,
223
+ # "limit_utp_rate"=>true,
224
+ # "listen_port"=>6881,
225
+ # "locale"=>"en_US",
226
+ # "lsd"=>true,
227
+ # "mail_notification_auth_enabled"=>false,
228
+ # "mail_notification_email"=>"",
229
+ # "mail_notification_enabled"=>false,
230
+ # "mail_notification_password"=>"",
231
+ # "mail_notification_smtp"=>"smtp.changeme.com",
232
+ # "mail_notification_ssl_enabled"=>false,
233
+ # "mail_notification_username"=>"",
234
+ # "max_active_downloads"=>3,
235
+ # "max_active_torrents"=>5,
236
+ # "max_active_uploads"=>3,
237
+ # "max_connec"=>500,
238
+ # "max_connec_per_torrent"=>100,
239
+ # "max_uploads_per_torrent"=>4,
240
+ # "pex"=>true,
241
+ # "preallocate_all"=>false,
242
+ # "proxy_auth_enabled"=>false,
243
+ # "proxy_ip"=>"0.0.0.0",
244
+ # "proxy_password"=>"",
245
+ # "proxy_peer_connections"=>false,
246
+ # "proxy_port"=>8080,
247
+ # "proxy_type"=>-1,
248
+ # "proxy_username"=>"",
249
+ # "queueing_enabled"=>false,
250
+ # "save_path"=>"/home/jeff/Downloads",
251
+ # "scan_dirs"=>[],
252
+ # "schedule_from_hour"=>8,
253
+ # "schedule_from_min"=>0,
254
+ # "schedule_to_hour"=>20,
255
+ # "schedule_to_min"=>0,
256
+ # "scheduler_days"=>0,
257
+ # "scheduler_enabled"=>false,
258
+ # "ssl_cert"=>"",
259
+ # "ssl_key"=>"",
260
+ # "temp_path"=>"/home/jeff/Downloads/temp",
261
+ # "temp_path_enabled"=>false,
262
+ # "up_limit"=>50,
263
+ # "upnp"=>true,
264
+ # "use_https"=>false,
265
+ # "web_ui_password"=>"ae150cdc82b40c4373d2e15e0ffe8f67",
266
+ # "web_ui_port"=>8083,
267
+ # "web_ui_username"=>"admin"
268
+ # }
269
+ #
270
+ def preferences
271
+ self.class.format :json
272
+ self.class.get('/json/preferences').parsed_response
273
+ end
274
+
275
+ ###
276
+ # Set application preferences
277
+ #
278
+ # Note: When setting password, pass it as plain text.
279
+ # You can send only the key/value pairs you want to change (in a hash),
280
+ # rather than the entire set of data.
281
+ #
282
+ def set_preferences pref_hash
283
+ pref_hash = Hash(pref_hash)
284
+ options = {
285
+ body: "json=#{pref_hash.to_json}"
286
+ }
287
+
288
+ self.class.post('/command/setPreferences', options)
289
+ end
290
+
291
+ ###
292
+ # Pause a torrent
293
+ #
294
+ def pause torrent_hash
295
+ options = {
296
+ body: "hash=#{torrent_hash}"
297
+ }
298
+
299
+ self.class.post('/command/pause', options)
300
+ end
301
+
302
+ ###
303
+ # Pause all torrents
304
+ #
305
+ def pause_all
306
+ self.class.post('/command/pauseall')
307
+ end
308
+
309
+ ###
310
+ # Resume downloading/seeding of a torrent
311
+ #
312
+ def resume torrent_hash
313
+ options = {
314
+ body: "hash=#{torrent_hash}"
315
+ }
316
+
317
+ self.class.post('/command/resume', options)
318
+ end
319
+
320
+ ###
321
+ # Resume downloading/seeding of all torrents
322
+ #
323
+ def resume_all
324
+ self.class.post('/command/resumeall')
325
+ end
326
+
327
+ ###
328
+ # Begin downloading one or more torrents.
329
+ #
330
+ # If passing mulitple urls, pass them as an array.
331
+ #
332
+ def download urls
333
+ urls = Array(urls)
334
+ urls = urls.join('%0A')
335
+
336
+ options = {
337
+ body: "urls=#{urls}"
338
+ }
339
+
340
+ self.class.post('/command/download', options)
341
+ end
342
+
343
+ ###
344
+ # Delete one or more torrents AND THEIR DATA
345
+ #
346
+ # If passing multiple torrent hashes, pass them as an array.
347
+ #
348
+ def delete_torrent_and_data torrent_hashes
349
+ torrent_hashes = Array(torrent_hashes)
350
+ torrent_hashes = torrent_hashes.join('|')
351
+
352
+ options = {
353
+ body: "hashes=#{torrent_hashes}"
354
+ }
355
+
356
+ self.class.post('/command/deletePerm', options)
357
+ end
358
+
359
+ ###
360
+ # Delete one or more torrents (doesn't delete their data)
361
+ #
362
+ # If passing multiple torrent hashes, pass them as an array.
363
+ #
364
+ def delete torrent_hashes
365
+ torrent_hashes = Array(torrent_hashes)
366
+ torrent_hashes = torrent_hashes.join('|')
367
+
368
+ options = {
369
+ body: "hashes=#{torrent_hashes}"
370
+ }
371
+
372
+ self.class.post('/command/delete', options)
373
+ end
374
+
375
+ ###
376
+ # Recheck a torrent
377
+ #
378
+ def recheck torrent_hash
379
+ options = {
380
+ body: "hash=#{torrent_hash}"
381
+ }
382
+
383
+ self.class.post('/command/recheck', options)
384
+ end
385
+
386
+ ###
387
+ # Increase the priority of one or more torrents
388
+ #
389
+ # If passing multiple torrent hashes, pass them as an array.
390
+ # Note: This does nothing unless queueing has been enabled
391
+ # via preferences.
392
+ #
393
+ def increase_priority torrent_hashes
394
+ torrent_hashes = Array(torrent_hashes)
395
+ torrent_hashes = torrent_hashes.join('|')
396
+
397
+ options = {
398
+ body: "hashes=#{torrent_hashes}"
399
+ }
400
+
401
+ self.class.post('/command/increasePrio', options)
402
+ end
403
+
404
+ ###
405
+ # Decrease the priority of one or more torrents
406
+ #
407
+ # If passing multiple torrent hashes, pass them as an array.
408
+ # Note: This does nothing unless queueing has been enabled
409
+ # via preferences.
410
+ #
411
+ def decrease_priority torrent_hashes
412
+ torrent_hashes = Array(torrent_hashes)
413
+ torrent_hashes = torrent_hashes.join('|')
414
+
415
+ options = {
416
+ body: "hashes=#{torrent_hashes}"
417
+ }
418
+
419
+ self.class.post('/command/decreasePrio', options)
420
+ end
421
+
422
+ ###
423
+ # Increase the priority of one or more torrents to the maximum value
424
+ #
425
+ # If passing multiple torrent hashes, pass them as an array.
426
+ # Note: This does nothing unless queueing has been enabled
427
+ # via preferences.
428
+ #
429
+ def maximize_priority torrent_hashes
430
+ torrent_hashes = Array(torrent_hashes)
431
+ torrent_hashes = torrent_hashes.join('|')
432
+
433
+ options = {
434
+ body: "hashes=#{torrent_hashes}"
435
+ }
436
+
437
+ self.class.post('/command/topPrio', options)
438
+ end
439
+
440
+ ###
441
+ # Decrease the priority of one or more torrents to the minimum value
442
+ #
443
+ # If passing multiple torrent hashes, pass them as an array.
444
+ # Note: This does nothing unless queueing has been enabled
445
+ # via preferences.
446
+ #
447
+ def minimize_priority torrent_hashes
448
+ torrent_hashes = Array(torrent_hashes)
449
+ torrent_hashes = torrent_hashes.join('|')
450
+
451
+ options = {
452
+ body: "hashes=#{torrent_hashes}"
453
+ }
454
+
455
+ self.class.post('/command/bottomPrio', options)
456
+ end
457
+
458
+ ###
459
+ # Set the download priority of a file within a torrent
460
+ #
461
+ # file_id is a 0 based position of the file within the torrent
462
+ #
463
+ def set_file_priority torrent_hash, file_id, priority
464
+ query = ["hash=#{torrent_hash}", "id=#{file_id}", "priority=#{priority}"]
465
+
466
+ options = {
467
+ body: query.join('&')
468
+ }
469
+
470
+ self.class.post('/command/setFilePrio', options)
471
+ end
472
+
473
+ ###
474
+ # Get the application's global download limit
475
+ #
476
+ # A limit of 0 means unlimited.
477
+ #
478
+ # Returns an integer (bytes)
479
+ #
480
+ def global_download_limit
481
+ self.class.format :json
482
+ self.class.post('/command/getGlobalDlLimit').parsed_response
483
+ end
484
+
485
+ ###
486
+ # Set the application's global download limit
487
+ #
488
+ # A limit of 0 means unlimited.
489
+ #
490
+ # limit: integer (bytes)
491
+ #
492
+ def set_global_download_limit limit
493
+ query = "limit=#{limit}"
494
+
495
+ options = {
496
+ body: query
497
+ }
498
+
499
+ self.class.post('/command/setGlobalDlLimit', options)
500
+ end
501
+
502
+ ###
503
+ # Get the application's global upload limit
504
+ #
505
+ # A limit of 0 means unlimited.
506
+ #
507
+ # Returns an integer (bytes)
508
+ #
509
+ def global_upload_limit
510
+ self.class.format :json
511
+ self.class.post('/command/getGlobalUpLimit').parsed_response
512
+ end
513
+
514
+ ###
515
+ # Set the application's global upload limit
516
+ #
517
+ # A limit of 0 means unlimited.
518
+ #
519
+ # limit: integer (bytes)
520
+ #
521
+ def set_global_upload_limit limit
522
+ query = "limit=#{limit}"
523
+
524
+ options = {
525
+ body: query
526
+ }
527
+
528
+ self.class.post('/command/setGlobalUpLimit', options)
529
+ end
530
+
531
+ ###
532
+ # Get a torrent's download limit
533
+ #
534
+ # A limit of 0 means unlimited.
535
+ #
536
+ # Returns an integer (bytes)
537
+ #
538
+ def download_limit torrent_hash
539
+ self.class.format :json
540
+
541
+ options = {
542
+ body: "hash=#{torrent_hash}"
543
+ }
544
+
545
+ self.class.post('/command/getTorrentDlLimit', options).parsed_response
546
+ end
547
+
548
+ ###
549
+ # Set a torrent's download limit
550
+ #
551
+ # A limit of 0 means unlimited.
552
+ #
553
+ # torrent_hash: string
554
+ # limit: integer (bytes)
555
+ #
556
+ def set_download_limit torrent_hash, limit
557
+ query = ["hash=#{torrent_hash}", "limit=#{limit}"]
558
+
559
+ options = {
560
+ body: query.join('&')
561
+ }
562
+
563
+ self.class.post('/command/setTorrentDlLimit', options)
564
+ end
565
+
566
+ ###
567
+ # Get a torrent's upload limit
568
+ #
569
+ # A limit of 0 means unlimited.
570
+ #
571
+ # Returns an integer (bytes)
572
+ #
573
+ def upload_limit torrent_hash
574
+ self.class.format :json
575
+
576
+ options = {
577
+ body: "hash=#{torrent_hash}"
578
+ }
579
+
580
+ self.class.post('/command/getTorrentUpLimit', options).parsed_response
581
+ end
582
+
583
+ ###
584
+ # Set a torrent's upload limit
585
+ #
586
+ # A limit of 0 means unlimited.
587
+ #
588
+ # torrent_hash: string
589
+ # limit: integer (bytes)
590
+ #
591
+ def set_upload_limit torrent_hash, limit
592
+ query = ["hash=#{torrent_hash}", "limit=#{limit}"]
593
+
594
+ options = {
595
+ body: query.join('&')
596
+ }
597
+
598
+ self.class.post('/command/setTorrentUpLimit', options)
599
+ end
600
+
601
+ private
602
+
603
+ def md5 str
604
+ Digest::MD5.hexdigest str
605
+ end
606
+ end
607
+ end
608
+
data/lib/qbt_client.rb ADDED
@@ -0,0 +1,6 @@
1
+ require "qbt_client/version"
2
+
3
+ module QbtClient
4
+ end
5
+
6
+ require 'qbt_client/web_ui'
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'qbt_client/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "qbt_client"
8
+ spec.version = QbtClient::VERSION
9
+ spec.authors = ["Jeff McAffee"]
10
+ spec.email = ["jeff@ktechsystems.com"]
11
+ spec.summary = %q{qBittorent client}
12
+ spec.description = %q{qBittorrent client}
13
+ spec.homepage = "https://github.com/jmcaffee/qbt_client"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "httparty", "~> 0.7"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.7"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "rspec"
26
+ spec.add_development_dependency "pry-byebug"
27
+ spec.add_development_dependency "pry"
28
+ end