dacpclient 0.2.11 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cf81a474e862f6f8c2bde6e929bc29a693b701df
4
- data.tar.gz: dfaa5045aded28a3ab95474c938759c1e371af78
3
+ metadata.gz: 0168ee84709980d66f477982404c0d5cddbb2f92
4
+ data.tar.gz: e105474bc17e7127c0d0f9427219152df7a83d0f
5
5
  SHA512:
6
- metadata.gz: af0b53555a31b9ad99cc4e3b334607520fa3a9225bd0da7c155abd9b919ba0fcea4cf43bc2949bf056b5969c77595eb14d8ca79ca9df557e3ffe2c998cce0cf8
7
- data.tar.gz: a16eb079d959501146eeb3aadb8607c1233ff6f9fa1e94df2455995a09796da1d1fd1a146372deed65047e158a076e44936fa47489b7244477e1043f4c786f62
6
+ metadata.gz: 508242daaf50dacc3c003d4e0d033ea0ed06df405dae77ce27d2ab34e3d0199e10a77143a1ab7b0fb1214b210873f5b1c29708616dcba7562109c984142064cc
7
+ data.tar.gz: f2f2dfd1c2238ae09d217ce4128032bbb174ef2e027042d687c5b8a6c8869ac1493002ee42f6df3dde07274f747fc450064f83da8b43155295d5c725c36e77ae
data/Gemfile CHANGED
@@ -3,4 +3,4 @@ gemspec
3
3
  source 'https://rubygems.org'
4
4
 
5
5
  gem 'minitest', '~> 5.3.1'
6
- gem 'rubocop', '~> 0.20.0'
6
+ gem 'rubocop', '~> 0.21.0'
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  A DACP (iTunes Remote protocol) client written in the wonderful Ruby language.
6
6
  You can use this for controlling iTunes. It uses the same protocol as the iTunes remote iOS app.
7
7
 
8
- You can control iTunes by connecting and entering a pin, or with Home Sharing. DACPClient supports both methods.
8
+ You can control iTunes by connecting and entering a pin.
9
9
 
10
10
  Look at the [bin/dacpclient](https://github.com/jurriaan/ruby-dacpclient/blob/master/bin/dacpclient) file for an example client.
11
11
 
@@ -29,33 +29,25 @@ Or install it yourself using:
29
29
 
30
30
  See [bin/dacpclient](https://github.com/jurriaan/ruby-dacpclient/blob/master/bin/dacpclient)
31
31
 
32
- Usage: dacpclient [command]
32
+ DACPClient v0.3.0
33
33
  (c) 2014 Jurriaan Pruis <email@jurriaanpruis.nl>
34
34
 
35
- Where command is one of the following:
36
- status
37
- status_ticker
38
- home_sharing
39
- play
40
- pause
41
- playpause
42
- next
43
- prev
44
- databases
45
- playqueue
46
- upnext
47
- stop
48
- debug
49
- usage
50
- previous
51
- help
52
-
53
- ## Todo
54
-
55
- - Use bonjour
56
- - Add tests
57
- - Add more tagdefinitions
58
- - Documentation
35
+ DACPClient commands:
36
+ dacpclient help # Display all possible commands
37
+ dacpclient help [COMMAND] # Describe available commands or one specific command
38
+ dacpclient hostname # Set the hostname
39
+ dacpclient next # Go to next item
40
+ dacpclient pause # Pause Playing
41
+ dacpclient play # Start playing
42
+ dacpclient play_playlist # Plays a playlist
43
+ dacpclient playlists # Show the playlists
44
+ dacpclient playpause # Toggle Playing
45
+ dacpclient prev # Go to previous item
46
+ dacpclient status # Shows the status of the DACP server
47
+ dacpclient stop # Stop playing
48
+ dacpclient upnext # Show what's up next
49
+ dacpclient version # Show DACPClient Version
50
+ dacpclient volume # Get or set volume
59
51
 
60
52
  ## Contributing
61
53
 
data/bin/dacpclient CHANGED
@@ -6,11 +6,13 @@ require 'socket'
6
6
  require 'yaml'
7
7
  require 'fileutils'
8
8
  require 'io/console'
9
+ require 'fuzzy_match'
9
10
  require 'thor'
10
11
 
11
12
  # This is the CLI DACP Client. Normally installed as `dacpclient`
12
13
  class CLIClient < Thor
13
- package_name :dacpclient
14
+ package_name 'DACPClient'
15
+
14
16
  include Thor::Actions
15
17
  def initialize(*)
16
18
  @config = {}
@@ -59,21 +61,6 @@ class CLIClient < Thor
59
61
  show_status
60
62
  end
61
63
 
62
- desc :home_sharing, 'Setup Home Sharing (Not fully functional)'
63
- def home_sharing
64
- puts 'Setting up Home Sharing. Saving Home Sharing GUID to ' + config_file
65
- puts "\nPlease enter your Apple ID credentials:"
66
- email = ask('Apple ID (e-mail address):').strip
67
- password = ask('Password:')
68
- guid = @client.setup_home_sharing(email, password)
69
- @config['appleid'] = email
70
- @config['hsgid'] = guid
71
- save_config
72
- puts "\n\n"
73
- puts "Got your Home Sharing GUID (#{guid}). Logging in.."
74
- login
75
- end
76
-
77
64
  desc :hostname, 'Set the hostname'
78
65
  def hostname
79
66
  @config['host'] = ask('Please enter a new hostname to connect to:').strip
@@ -125,13 +112,29 @@ class CLIClient < Thor
125
112
  playlist_items = @client.playlists
126
113
  puts 'Playlists:'
127
114
  puts '----------'
115
+ count = 0
116
+ num = Math.log10(playlist_items.length).floor + 1
128
117
  playlist_items.each do |playlist|
129
- print ' ' unless playlist.base_playlist?
118
+ printf("%#{num}d. ", count += 1) unless playlist.base_playlist?
119
+
130
120
  puts "#{playlist.name} (#{playlist.count})"
131
121
  end
132
122
  puts
133
123
  end
134
124
 
125
+ desc :play_playlist, 'Plays a playlist'
126
+ def play_playlist(playlist)
127
+ login
128
+ playlists = @client.playlists
129
+ if playlist =~ /^\d+$/
130
+ playlist = playlists[playlist.to_i]
131
+ else
132
+ fz = FuzzyMatch.new(playlists, read: :name)
133
+ playlist = fz.find(playlist)
134
+ end
135
+ @client.playspec(playlist)
136
+ end
137
+
135
138
  desc :upnext, 'Show what\'s up next'
136
139
  def upnext
137
140
  login
@@ -183,7 +186,17 @@ class CLIClient < Thor
183
186
  puts "DACPClient v#{DACPClient::VERSION}"
184
187
  puts "using DMAPParser v#{DMAPParser::VERSION}"
185
188
  print 'DACPClient and DMAPParser are Copyright (c) '
186
- puts "#{Time.now.year} Jurriaan Pruis"
189
+ puts "#{Time.now.year} Jurriaan Pruis <email@jurriaanpruis.nl>"
190
+ end
191
+
192
+ default_task :banner
193
+
194
+ desc :help, 'Display all possible commands'
195
+ def banner
196
+ puts "DACPClient v#{DACPClient::VERSION}"
197
+ puts "(c) #{Time.now.year} Jurriaan Pruis <email@jurriaanpruis.nl>"
198
+ puts
199
+ help
187
200
  end
188
201
 
189
202
  private
data/dacpclient.gemspec CHANGED
@@ -24,6 +24,7 @@ Gem::Specification.new do |spec|
24
24
  spec.add_runtime_dependency 'plist', '~> 3.1.0'
25
25
  spec.add_runtime_dependency 'dmapparser', '~> 0.1.0'
26
26
  spec.add_runtime_dependency 'thor', '~> 0.19.1'
27
+ spec.add_runtime_dependency 'fuzzy_match', '~> 2.0.4'
27
28
 
28
29
  spec.add_development_dependency 'yard'
29
30
  spec.add_development_dependency 'redcarpet'
@@ -39,7 +39,7 @@ module DACPClient
39
39
 
40
40
  private
41
41
 
42
- def node_resolver(node, resolved)
42
+ def node_resolver(_node, resolved)
43
43
  devices << Device.new(get_device_host(resolved), resolved.port,
44
44
  resolved.text_record)
45
45
 
@@ -11,6 +11,8 @@ require 'dacpclient/version'
11
11
  require 'dacpclient/model'
12
12
  require 'dacpclient/models/status'
13
13
  require 'dacpclient/models/pair_info'
14
+ require 'dacpclient/models/database'
15
+ require 'dacpclient/models/databases'
14
16
  require 'dacpclient/models/playlist'
15
17
  require 'dacpclient/models/playlists'
16
18
  require 'dacpclient/models/play_queue_item'
@@ -23,9 +25,6 @@ module DACPClient
23
25
  attr_writer :guid
24
26
  attr_reader :name, :host, :port, :session_id
25
27
 
26
- HOME_SHARING_HOST = 'https://homesharing.itunes.apple.com'.freeze
27
- HOME_SHARING_PATH = '/WebObjects/MZHomeSharing.woa/wa/getShareIdentifiers'.freeze
28
-
29
28
  DEFAULT_HEADERS = {
30
29
  'Viewer-Only-Client' => '1',
31
30
  'Accept-Encoding' => 'gzip',
@@ -56,19 +55,6 @@ module DACPClient
56
55
  alias_method :next, :nextitem
57
56
  alias_method :speakers, :getspeakers
58
57
 
59
- def setup_home_sharing(user, password)
60
- hs_client = Faraday.new(url: HOME_SHARING_HOST)
61
- result = hs_client.post do |request|
62
- request.url HOME_SHARING_PATH
63
- request.headers['Content-Type'] = 'text/xml'
64
- request.headers.merge!(DEFAULT_HEADERS)
65
- request.body = { 'appleId' => user, 'guid' => 'empty',
66
- 'password' => password }.to_plist
67
- end
68
- response = Plist.parse_xml(result.body)
69
- @hsgid = response['sgid']
70
- end
71
-
72
58
  def guid
73
59
  return @guid unless @guid.nil?
74
60
  d = Digest::SHA2.hexdigest(@name)
@@ -156,6 +142,19 @@ module DACPClient
156
142
  set_property('dmcp.volume', volume)
157
143
  end
158
144
 
145
+ def cue(command, query, index = 0)
146
+ do_action(:cue, command: command, query: query, index: index)
147
+ end
148
+
149
+ def playspec(playlist, db = default_db)
150
+ dbspec = "'dmap.persistentid:0x#{db.persistent_id.to_s(16)}'"
151
+ cspec = "'dmap.persistentid:0x#{playlist.persistent_id.to_s(16)}'"
152
+ # don't worry about playing item's from playlists yet..
153
+ # container-item-spec='dmap.containeritemid:%s'
154
+ do_action(:playspec, database_spec: dbspec,
155
+ container_spec: cspec)
156
+ end
157
+
159
158
  def repeat
160
159
  response = do_action(:getproperty, properties: 'dacp.repeatstate')
161
160
  response[:carp]
@@ -198,16 +197,25 @@ module DACPClient
198
197
  end
199
198
 
200
199
  def databases
201
- do_action('databases', clean_url: true)
200
+ meta = %w(dmap.itemname dmap.itemcount dmap.itemid dmap.persistentid
201
+ daap.baseplaylist com.apple.itunes.special-playlist
202
+ com.apple.itunes.smart-playlist com.apple.itunes.saved-genius
203
+ dmap.parentcontainerid dmap.editcommandssupported).join(',')
204
+ do_action('databases', clean_url: true, meta: meta, model: Databases)
202
205
  end
203
206
 
204
207
  def playlists(db = default_db)
205
- do_action("databases/#{db.miid}/containers", clean_url: true,
206
- model: Playlists).items
208
+ meta = %w(dmap.itemname dmap.itemcount dmap.itemid dmap.persistentid
209
+ daap.baseplaylist com.apple.itunes.special-playlist
210
+ com.apple.itunes.smart-playlist com.apple.itunes.saved-genius
211
+ dmap.parentcontainerid dmap.editcommandssupported).join(',')
212
+ do_action("databases/#{db.item_id}/containers", clean_url: true,
213
+ meta: meta,
214
+ model: Playlists).items
207
215
  end
208
216
 
209
217
  def default_db
210
- databases.mlcl.to_a.find { |item| item.mdbk == 1 }
218
+ databases.items.find { |item| item.default_db == 1 }
211
219
  end
212
220
 
213
221
  def default_playlist(db = default_db)
@@ -269,14 +277,17 @@ module DACPClient
269
277
  do_action(:setproperty, property => value)
270
278
  end
271
279
 
272
- def do_action(action, clean_url: false, model: nil, **params)
280
+ def do_action(action, params = {})
281
+ clean_url = params.delete(:clean_url) || false
282
+ model = params.delete(:model)
283
+
273
284
  action = '/' + action.to_s
274
285
  unless @session_id.nil?
275
286
  params['session-id'] = @session_id.to_s
276
287
  action = '/ctrl-int/1' + action unless clean_url
277
288
  end
278
289
  params['hsgid'] = @hsgid unless @hsgid.nil?
279
-
290
+ params = filter_param_keys(params)
280
291
  result = @client.get do |request|
281
292
  request.options.params_encoder = Faraday::FlatterParamsEncoder
282
293
  request.url action
@@ -287,6 +298,10 @@ module DACPClient
287
298
  parse_result result, model
288
299
  end
289
300
 
301
+ def filter_param_keys(params)
302
+ Hash[ params.map { |k, v| [k.to_s.tr('_', '-'), v] }]
303
+ end
304
+
290
305
  def parse_result(result, model)
291
306
  if !result.success?
292
307
  fail DACPForbiddenError, result
@@ -9,7 +9,7 @@ module FaradayMiddleware
9
9
  ACCEPT_ENCODING = 'Accept-Encoding'.freeze
10
10
  ENCODINGS = 'gzip,deflate'.freeze
11
11
 
12
- def initialize(app, options = nil)
12
+ def initialize(app)
13
13
  @app = app
14
14
  end
15
15
 
@@ -38,6 +38,10 @@ module DACPClient
38
38
  end.to_dmap
39
39
  end
40
40
 
41
+ def respond_to?(method)
42
+ dmap_attributes.key?(method)
43
+ end
44
+
41
45
  def method_missing(method, *args, &block)
42
46
  if method.to_s =~ /(.*)\=$/ &&
43
47
  dmap_attributes.key?(Regexp.last_match[1].to_sym)
@@ -0,0 +1,11 @@
1
+ module DACPClient
2
+ class Database < Model
3
+ dmap_tag :mlit
4
+ dmap_attribute :item_id, :miid
5
+ dmap_attribute :name, :minm
6
+ dmap_attribute :count, :mimc
7
+ dmap_attribute :container_count, :mctc
8
+ dmap_attribute :default_db, :mdbk
9
+ dmap_attribute :persistent_id, :mper
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ module DACPClient
2
+ class Databases < Model
3
+ dmap_tag :avdb
4
+ dmap_attribute :status, :mstt
5
+ dmap_attribute :update_type, :myty
6
+ dmap_attribute :container_count, :mtco
7
+ dmap_attribute :returned_count, :mrco
8
+ dmap_container :items, :mlcl, DACPClient::Database
9
+ end
10
+ end
@@ -5,5 +5,6 @@ module DACPClient
5
5
  dmap_attribute :name, :minm
6
6
  dmap_attribute :base_playlist, :abpl
7
7
  dmap_attribute :count, :mimc
8
+ dmap_attribute :persistent_id, :mper
8
9
  end
9
10
  end
@@ -1,4 +1,4 @@
1
1
  # The DACPClient module
2
2
  module DACPClient
3
- VERSION = '0.2.11'
3
+ VERSION = '0.3.0'
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dacpclient
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.11
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jurriaan Pruis
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-04-03 00:00:00.000000000 Z
11
+ date: 2014-04-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: 0.19.1
83
+ - !ruby/object:Gem::Dependency
84
+ name: fuzzy_match
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 2.0.4
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 2.0.4
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: yard
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -160,6 +174,8 @@ files:
160
174
  - lib/dacpclient/faraday/flatter_params_encoder.rb
161
175
  - lib/dacpclient/faraday/gzip.rb
162
176
  - lib/dacpclient/model.rb
177
+ - lib/dacpclient/models/database.rb
178
+ - lib/dacpclient/models/databases.rb
163
179
  - lib/dacpclient/models/pair_info.rb
164
180
  - lib/dacpclient/models/play_queue.rb
165
181
  - lib/dacpclient/models/play_queue_item.rb