pandoru 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +29 -0
- data/README.md +7 -2
- data/exe/pandoru-login +44 -0
- data/lib/pandoru/credentials.rb +109 -0
- data/lib/pandoru/models/station.rb +2 -1
- data/lib/pandoru/secret_store.rb +173 -0
- data/lib/pandoru/transport.rb +6 -1
- data/lib/pandoru/version.rb +1 -1
- data/lib/pandoru.rb +2 -0
- metadata +36 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fd2045e11084de310e45882480a3e8472ebceac1e81ca927682f978f10458ffa
|
|
4
|
+
data.tar.gz: f279f57d936e055be8c819f154a9b863c45ffdf01ceb4970cd130035c480b033
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 654f2fcb4948e93307ce68650c7b53fbd29e20252dac628311e5509ec3b25bfb2b0f9116d51ed5da583cc72fecd91333b2518ea206117ef1418fd7a9eec05453
|
|
7
|
+
data.tar.gz: aed635c1e7eec1bc31b7790386f99c700d4964061dcc672535d9233e1ee81381050687d8829e37709ba491cfac190557f842a5cb42e6022eacf04728378a1b4f
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,31 @@ All notable changes to this project are documented here. The format is based
|
|
|
4
4
|
on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
|
|
5
5
|
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.2.0] - 2026-05-25
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- `Pandoru::Credentials.resolve` — tiered credential resolution: explicit args →
|
|
11
|
+
`PANDORA_USERNAME`/`PANDORA_PASSWORD` env → OS secret store → JSON file at
|
|
12
|
+
`$PANDORU_CREDENTIALS` / `~/.config/pandoru/credentials.json` (first complete
|
|
13
|
+
pair wins; blank values fall through).
|
|
14
|
+
- `Pandoru::SecretStore` — portable OS secret storage (macOS Keychain, Linux
|
|
15
|
+
libsecret; Windows falls back to the file tier) so the password need not live
|
|
16
|
+
in plaintext.
|
|
17
|
+
- `pandoru-login` executable — prompt for credentials and store them in the OS
|
|
18
|
+
secret store.
|
|
19
|
+
|
|
20
|
+
## [0.1.1] - 2026-05-25
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- Transport sent plaintext HTTP to the TLS port (`http://host:443`) for any
|
|
24
|
+
method not in `REQUIRE_TLS` (e.g. `user.getStationList`), which Pandora drops
|
|
25
|
+
with an empty reply — breaking every authenticated call except login and
|
|
26
|
+
`getPlaylist`. The request scheme now follows the transport's configured TLS
|
|
27
|
+
setting, so TLS hosts use https for all methods.
|
|
28
|
+
- `Station#add_seed` passed its arguments to `add_music` in the wrong order
|
|
29
|
+
(station and music tokens swapped), producing a malformed `station.addMusic`
|
|
30
|
+
request. It now calls `add_music(music_token, token)`.
|
|
31
|
+
|
|
7
32
|
## [0.1.0] - 2026-05-25
|
|
8
33
|
|
|
9
34
|
Initial public release. A Ruby port of pydora (tracking upstream `pydora 2.3.1`)
|
|
@@ -27,3 +52,7 @@ targeting Pandora's partner/device JSON API (`tuner.pandora.com/services/json/`)
|
|
|
27
52
|
INVALID_PARTNER_LOGIN.
|
|
28
53
|
- Corrected the encryption/decryption key orientation in the bundled default
|
|
29
54
|
partner settings.
|
|
55
|
+
|
|
56
|
+
[0.2.0]: https://github.com/TwilightCoders/pandoru/releases/tag/v0.2.0
|
|
57
|
+
[0.1.1]: https://github.com/TwilightCoders/pandoru/releases/tag/v0.1.1
|
|
58
|
+
[0.1.0]: https://github.com/TwilightCoders/pandoru/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
[]()
|
|
2
|
+
[](https://rubygems.org/gems/pandoru)
|
|
3
|
+
[](https://travis-ci.org/TwilightCoders/pandoru)
|
|
4
|
+
[](https://qlty.sh/gh/TwilightCoders/projects/pandoru)
|
|
5
|
+
[](https://qlty.sh/gh/TwilightCoders/projects/pandoru)
|
|
1
6
|
# Pandoru
|
|
2
7
|
|
|
3
8
|
**Pandoru** is a Ruby port of the Python `pydora` library, providing a comprehensive client for the unofficial Pandora music streaming API. This gem allows you to interact with Pandora programmatically to manage stations, get playlists, search for music, and control playback.
|
|
4
9
|
|
|
5
|
-
> **Note**: This is an unofficial API client. Use at your own risk and respect Pandora's terms of service.
|
|
10
|
+
> ⚠️ **Note**: This is an unofficial API client. Use at your own risk and respect Pandora's terms of service.
|
|
6
11
|
|
|
7
12
|
---
|
|
8
13
|
|
|
@@ -260,4 +265,4 @@ This Ruby gem is a port of the excellent [pydora](https://github.com/mcrute/pydo
|
|
|
260
265
|
|
|
261
266
|
## Disclaimer
|
|
262
267
|
|
|
263
|
-
This project is not affiliated with or endorsed by Pandora Media, Inc. Use of this library may violate Pandora's Terms of Service. Use at your own risk.
|
|
268
|
+
> ⚠️ This project is not affiliated with or endorsed by Pandora Media, Inc. Use of this library may violate Pandora's Terms of Service. Use at your own risk.
|
data/exe/pandoru-login
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Store Pandora credentials in the OS secret store (macOS Keychain, Linux
|
|
5
|
+
# libsecret) so no password lives in a plaintext file. The client reads them at
|
|
6
|
+
# runtime via Pandoru::Credentials.
|
|
7
|
+
#
|
|
8
|
+
# pandoru-login # prompt + store
|
|
9
|
+
# pandoru-login --delete # remove the stored item
|
|
10
|
+
|
|
11
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
|
|
12
|
+
|
|
13
|
+
require 'pandoru'
|
|
14
|
+
require 'io/console'
|
|
15
|
+
|
|
16
|
+
store = Pandoru::SecretStore
|
|
17
|
+
|
|
18
|
+
unless store.available?
|
|
19
|
+
warn "No OS secret store available on this platform (#{store.backend_name})."
|
|
20
|
+
warn 'Fall back to a credentials file — see README "Credentials".'
|
|
21
|
+
exit 1
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
if ARGV[0] == '--delete'
|
|
25
|
+
removed = store.delete
|
|
26
|
+
puts removed ? 'Removed Pandora credentials from the secret store.' : 'Nothing stored.'
|
|
27
|
+
exit removed ? 0 : 1
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
print 'Pandora email/username: '
|
|
31
|
+
username = $stdin.gets&.strip
|
|
32
|
+
abort 'Aborted — no username.' if username.nil? || username.empty?
|
|
33
|
+
|
|
34
|
+
print 'Password (hidden): '
|
|
35
|
+
password = $stdin.noecho(&:gets)&.chomp
|
|
36
|
+
puts
|
|
37
|
+
abort 'Aborted — no password.' if password.nil? || password.empty?
|
|
38
|
+
|
|
39
|
+
if store.store(username, password)
|
|
40
|
+
puts "✓ Stored in #{store.backend_name} (account: #{username})."
|
|
41
|
+
puts 'No password on disk — the client reads it at runtime.'
|
|
42
|
+
else
|
|
43
|
+
abort 'Failed to store credentials in the secret store.'
|
|
44
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
module Pandoru
|
|
4
|
+
# Resolves Pandora account credentials from the first source that supplies a
|
|
5
|
+
# complete pair, so the client/CLI works whether you prefer env vars, the OS
|
|
6
|
+
# secret store, or a config file. Empty values count as absent, so an
|
|
7
|
+
# unset/blank env var gracefully falls through to the next source rather than
|
|
8
|
+
# half-authenticating.
|
|
9
|
+
#
|
|
10
|
+
# Precedence (highest first), mirroring the common config-resolution standard
|
|
11
|
+
# (explicit wins; env for 12-factor/CI; XDG Base Directory for the file):
|
|
12
|
+
#
|
|
13
|
+
# 1. Explicit values passed in (tests, embedding).
|
|
14
|
+
# 2. ENV PANDORA_USERNAME / PANDORA_PASSWORD.
|
|
15
|
+
# 3. The OS secret store (Keychain / libsecret), if a credential is stored.
|
|
16
|
+
# 4. A JSON file { "username": ..., "password": ... } at the first that
|
|
17
|
+
# exists, in order:
|
|
18
|
+
# $PANDORU_CREDENTIALS (explicit path override)
|
|
19
|
+
# $XDG_CONFIG_HOME/pandoru/credentials.json (XDG; default ~/.config)
|
|
20
|
+
#
|
|
21
|
+
# A future tier should delegate to Pandoru::ClientBuilders for the existing
|
|
22
|
+
# pydora `.cfg` / pianobar config files rather than reimplementing that
|
|
23
|
+
# parsing — tracked as a follow-up.
|
|
24
|
+
class Credentials
|
|
25
|
+
class NotFound < StandardError; end
|
|
26
|
+
|
|
27
|
+
Resolved = Struct.new(:username, :password, :source, keyword_init: true)
|
|
28
|
+
|
|
29
|
+
USERNAME_KEYS = %w[username user email].freeze
|
|
30
|
+
PASSWORD_KEYS = %w[password pass].freeze
|
|
31
|
+
|
|
32
|
+
def self.resolve(username: nil, password: nil, env: ENV, home: Dir.home, secret_store: SecretStore)
|
|
33
|
+
new(env: env, home: home, secret_store: secret_store)
|
|
34
|
+
.resolve(username: username, password: password)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def initialize(env: ENV, home: Dir.home, secret_store: SecretStore)
|
|
38
|
+
@env = env
|
|
39
|
+
@home = home
|
|
40
|
+
@secret_store = secret_store
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def resolve(username: nil, password: nil)
|
|
44
|
+
if present?(username) && present?(password)
|
|
45
|
+
return Resolved.new(username: username, password: password, source: :explicit)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
if present?(@env['PANDORA_USERNAME']) && present?(@env['PANDORA_PASSWORD'])
|
|
49
|
+
return Resolved.new(username: @env['PANDORA_USERNAME'],
|
|
50
|
+
password: @env['PANDORA_PASSWORD'], source: :env)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
if @secret_store && (creds = @secret_store.fetch)
|
|
54
|
+
return Resolved.new(username: creds[0], password: creds[1], source: :secret_store)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
candidate_files.each do |path|
|
|
58
|
+
next unless File.file?(path)
|
|
59
|
+
creds = parse_file(path)
|
|
60
|
+
next unless creds
|
|
61
|
+
return Resolved.new(username: creds[0], password: creds[1], source: path)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
raise NotFound, not_found_message
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Ordered list of JSON credential paths to try.
|
|
68
|
+
def candidate_files
|
|
69
|
+
paths = []
|
|
70
|
+
paths << @env['PANDORU_CREDENTIALS'] if present?(@env['PANDORU_CREDENTIALS'])
|
|
71
|
+
paths << File.join(xdg_config_home, 'pandoru', 'credentials.json')
|
|
72
|
+
paths.uniq
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def xdg_config_home
|
|
78
|
+
present?(@env['XDG_CONFIG_HOME']) ? @env['XDG_CONFIG_HOME'] : File.join(@home, '.config')
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Returns [username, password] from a JSON file, or nil if unreadable or
|
|
82
|
+
# incomplete (so resolution continues to the next candidate).
|
|
83
|
+
def parse_file(path)
|
|
84
|
+
data = JSON.parse(File.read(path))
|
|
85
|
+
return nil unless data.is_a?(Hash)
|
|
86
|
+
|
|
87
|
+
username = USERNAME_KEYS.map { |k| data[k] }.find { |v| present?(v) }
|
|
88
|
+
password = PASSWORD_KEYS.map { |k| data[k] }.find { |v| present?(v) }
|
|
89
|
+
present?(username) && present?(password) ? [username, password] : nil
|
|
90
|
+
rescue JSON::ParserError
|
|
91
|
+
nil
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def present?(value)
|
|
95
|
+
!value.nil? && !value.to_s.strip.empty?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def not_found_message
|
|
99
|
+
checked = ['PANDORA_USERNAME / PANDORA_PASSWORD (env)', 'OS secret store (run pandoru-login)'] + candidate_files
|
|
100
|
+
<<~MSG.strip
|
|
101
|
+
No Pandora credentials found. Checked, in order:
|
|
102
|
+
#{checked.map { |c| " - #{c}" }.join("\n")}
|
|
103
|
+
Store them with `pandoru-login`, provide a JSON file like
|
|
104
|
+
{ "username": "you@example.com", "password": "…" }, or set the
|
|
105
|
+
PANDORA_USERNAME / PANDORA_PASSWORD environment variables.
|
|
106
|
+
MSG
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -169,7 +169,8 @@ module Pandoru
|
|
|
169
169
|
|
|
170
170
|
def add_seed(music_token)
|
|
171
171
|
return false unless allow_add_music && @api_client
|
|
172
|
-
|
|
172
|
+
# add_music expects (music_token, station_token) — music token first.
|
|
173
|
+
@api_client.add_music(music_token, token)
|
|
173
174
|
true
|
|
174
175
|
end
|
|
175
176
|
end
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
require 'open3'
|
|
2
|
+
require 'json'
|
|
3
|
+
|
|
4
|
+
module Pandoru
|
|
5
|
+
# Portable secret storage: keeps the Pandora credential out of any plaintext
|
|
6
|
+
# file by delegating to the host OS's native secret service. There's no solid
|
|
7
|
+
# cross-platform Ruby gem for this, so we shell out to each platform's tool,
|
|
8
|
+
# the way fastlane et al. do:
|
|
9
|
+
#
|
|
10
|
+
# macOS → security (Keychain)
|
|
11
|
+
# Linux → secret-tool (libsecret / Secret Service: GNOME Keyring, KWallet)
|
|
12
|
+
# Windows → Credential Manager (not yet wired — see Adapters::Windows)
|
|
13
|
+
#
|
|
14
|
+
# The credential is stored as a single JSON blob { "username", "password" }
|
|
15
|
+
# under one fixed key, so retrieval is uniform across backends and we never
|
|
16
|
+
# need to enumerate the store to discover the username. Everything degrades
|
|
17
|
+
# gracefully: with no working backend, fetch returns nil and the resolver
|
|
18
|
+
# falls through to the config file.
|
|
19
|
+
module SecretStore
|
|
20
|
+
SERVICE = 'pandoru'
|
|
21
|
+
|
|
22
|
+
# Shells out, returning [stdout, success?]. Injectable so adapters can be
|
|
23
|
+
# unit-tested without touching a real keychain.
|
|
24
|
+
DEFAULT_RUNNER = lambda do |cmd, stdin_data|
|
|
25
|
+
opts = { err: File::NULL }
|
|
26
|
+
opts[:stdin_data] = stdin_data if stdin_data
|
|
27
|
+
out, status = Open3.capture2(*cmd, **opts)
|
|
28
|
+
[out, status.success?]
|
|
29
|
+
rescue Errno::ENOENT
|
|
30
|
+
['', false]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
module_function
|
|
34
|
+
|
|
35
|
+
# The first available adapter for this host, or a Null adapter.
|
|
36
|
+
def adapter(runner: DEFAULT_RUNNER)
|
|
37
|
+
[Adapters::MacOS, Adapters::SecretTool, Adapters::Windows]
|
|
38
|
+
.map { |klass| klass.new(runner: runner) }
|
|
39
|
+
.find(&:available?) || Adapters::Null.new(runner: runner)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def available?(adapter: adapter())
|
|
43
|
+
!adapter.is_a?(Adapters::Null)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def backend_name(adapter: adapter())
|
|
47
|
+
adapter.name
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# [username, password] from the store, or nil if absent/unreadable.
|
|
51
|
+
def fetch(service: SERVICE, adapter: adapter())
|
|
52
|
+
raw = adapter.read(service)
|
|
53
|
+
return nil if raw.nil? || raw.strip.empty?
|
|
54
|
+
|
|
55
|
+
data = JSON.parse(raw)
|
|
56
|
+
username = data['username']
|
|
57
|
+
password = data['password']
|
|
58
|
+
return nil unless present?(username) && present?(password)
|
|
59
|
+
|
|
60
|
+
[username, password]
|
|
61
|
+
rescue JSON::ParserError
|
|
62
|
+
nil
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def store(username, password, service: SERVICE, adapter: adapter())
|
|
66
|
+
adapter.write(service, JSON.generate('username' => username, 'password' => password))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def delete(service: SERVICE, adapter: adapter())
|
|
70
|
+
adapter.delete(service)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def present?(value)
|
|
74
|
+
!value.nil? && !value.to_s.strip.empty?
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Per-OS adapters. Each maps the generic read/write/delete of an opaque
|
|
78
|
+
# secret string to the native CLI; all I/O goes through the injected runner.
|
|
79
|
+
module Adapters
|
|
80
|
+
class Base
|
|
81
|
+
def initialize(runner: DEFAULT_RUNNER)
|
|
82
|
+
@runner = runner
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def name = self.class.name.split('::').last
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def run(cmd, stdin_data: nil)
|
|
90
|
+
@runner.call(cmd, stdin_data)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def which?(tool)
|
|
94
|
+
_out, ok = run(['which', tool])
|
|
95
|
+
ok
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# macOS Keychain via `security`. Account is fixed to the service name; the
|
|
100
|
+
# secret blob is the password slot.
|
|
101
|
+
class MacOS < Base
|
|
102
|
+
def name = 'macOS Keychain'
|
|
103
|
+
|
|
104
|
+
def available?
|
|
105
|
+
RUBY_PLATFORM.include?('darwin') && which?('security')
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def read(service)
|
|
109
|
+
out, ok = run(['security', 'find-generic-password', '-s', service, '-w'])
|
|
110
|
+
ok ? out.chomp : nil
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def write(service, secret)
|
|
114
|
+
_out, ok = run(['security', 'add-generic-password', '-U',
|
|
115
|
+
'-s', service, '-a', service, '-w', secret,
|
|
116
|
+
'-D', 'application password', '-l', service])
|
|
117
|
+
ok
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def delete(service)
|
|
121
|
+
_out, ok = run(['security', 'delete-generic-password', '-s', service])
|
|
122
|
+
ok
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Linux libsecret via `secret-tool` (Secret Service: GNOME Keyring/KWallet).
|
|
127
|
+
class SecretTool < Base
|
|
128
|
+
def name = 'libsecret (secret-tool)'
|
|
129
|
+
|
|
130
|
+
def available?
|
|
131
|
+
RUBY_PLATFORM.include?('linux') && which?('secret-tool')
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def read(service)
|
|
135
|
+
out, ok = run(['secret-tool', 'lookup', 'service', service])
|
|
136
|
+
ok ? out.chomp : nil
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def write(service, secret)
|
|
140
|
+
_out, ok = run(['secret-tool', 'store', '--label', service, 'service', service],
|
|
141
|
+
stdin_data: secret)
|
|
142
|
+
ok
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def delete(service)
|
|
146
|
+
_out, ok = run(['secret-tool', 'clear', 'service', service])
|
|
147
|
+
ok
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Windows Credential Manager. Left unwired: doing it without a gem means a
|
|
152
|
+
# PowerShell + Win32 CredRead/CredWrite P/Invoke shim, which can't be
|
|
153
|
+
# verified here. Reports unavailable so Windows falls back to the config
|
|
154
|
+
# file. Implement read/write/delete against `cmdkey`/PowerShell to enable.
|
|
155
|
+
class Windows < Base
|
|
156
|
+
def name = 'Windows Credential Manager (unsupported)'
|
|
157
|
+
def available? = false
|
|
158
|
+
def read(_service) = nil
|
|
159
|
+
def write(_service, _secret) = false
|
|
160
|
+
def delete(_service) = false
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# No native store available — everything no-ops, resolver uses the file.
|
|
164
|
+
class Null < Base
|
|
165
|
+
def name = 'none'
|
|
166
|
+
def available? = false
|
|
167
|
+
def read(_service) = nil
|
|
168
|
+
def write(_service, _secret) = false
|
|
169
|
+
def delete(_service) = false
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
data/lib/pandoru/transport.rb
CHANGED
|
@@ -308,7 +308,12 @@ module Pandoru
|
|
|
308
308
|
end
|
|
309
309
|
|
|
310
310
|
def build_url(method, params = {})
|
|
311
|
-
|
|
311
|
+
# Pandora now requires TLS on every endpoint, so follow the transport's
|
|
312
|
+
# configured scheme uniformly. The old per-method REQUIRE_TLS downgrade
|
|
313
|
+
# built http://host:443 for non-listed methods (e.g. user.getStationList)
|
|
314
|
+
# — plaintext to the TLS port, which Pandora drops (EOF). Protocol and
|
|
315
|
+
# port must agree.
|
|
316
|
+
protocol = @api_tls ? "https" : "http"
|
|
312
317
|
port = @api_tls ? 443 : 80
|
|
313
318
|
# Only include port if it's non-standard
|
|
314
319
|
port_string = (protocol == "https" && port == 443) || (protocol == "http" && port == 80) ? "" : ":#{port}"
|
data/lib/pandoru/version.rb
CHANGED
data/lib/pandoru.rb
CHANGED
|
@@ -4,6 +4,8 @@ require_relative 'pandoru/transport'
|
|
|
4
4
|
require_relative 'pandoru/client'
|
|
5
5
|
require_relative 'pandoru/client_builder'
|
|
6
6
|
require_relative 'pandoru/models'
|
|
7
|
+
require_relative 'pandoru/secret_store'
|
|
8
|
+
require_relative 'pandoru/credentials'
|
|
7
9
|
require 'pathname'
|
|
8
10
|
require 'logger'
|
|
9
11
|
|
metadata
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pandoru
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dale Stevens
|
|
8
|
-
bindir:
|
|
8
|
+
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
@@ -163,20 +163,51 @@ dependencies:
|
|
|
163
163
|
- - "~>"
|
|
164
164
|
- !ruby/object:Gem::Version
|
|
165
165
|
version: '6.0'
|
|
166
|
+
- !ruby/object:Gem::Dependency
|
|
167
|
+
name: simplecov
|
|
168
|
+
requirement: !ruby/object:Gem::Requirement
|
|
169
|
+
requirements:
|
|
170
|
+
- - "~>"
|
|
171
|
+
- !ruby/object:Gem::Version
|
|
172
|
+
version: '0.22'
|
|
173
|
+
type: :development
|
|
174
|
+
prerelease: false
|
|
175
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
176
|
+
requirements:
|
|
177
|
+
- - "~>"
|
|
178
|
+
- !ruby/object:Gem::Version
|
|
179
|
+
version: '0.22'
|
|
180
|
+
- !ruby/object:Gem::Dependency
|
|
181
|
+
name: simplecov-lcov
|
|
182
|
+
requirement: !ruby/object:Gem::Requirement
|
|
183
|
+
requirements:
|
|
184
|
+
- - "~>"
|
|
185
|
+
- !ruby/object:Gem::Version
|
|
186
|
+
version: '0.8'
|
|
187
|
+
type: :development
|
|
188
|
+
prerelease: false
|
|
189
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
190
|
+
requirements:
|
|
191
|
+
- - "~>"
|
|
192
|
+
- !ruby/object:Gem::Version
|
|
193
|
+
version: '0.8'
|
|
166
194
|
description: A comprehensive Ruby client for the Pandora music streaming API, providing
|
|
167
195
|
access to stations, playlists, search, and user management features.
|
|
168
196
|
email:
|
|
169
197
|
- dale@twilightcoders.net
|
|
170
|
-
executables:
|
|
198
|
+
executables:
|
|
199
|
+
- pandoru-login
|
|
171
200
|
extensions: []
|
|
172
201
|
extra_rdoc_files: []
|
|
173
202
|
files:
|
|
174
203
|
- CHANGELOG.md
|
|
175
204
|
- LICENSE
|
|
176
205
|
- README.md
|
|
206
|
+
- exe/pandoru-login
|
|
177
207
|
- lib/pandoru.rb
|
|
178
208
|
- lib/pandoru/client.rb
|
|
179
209
|
- lib/pandoru/client_builder.rb
|
|
210
|
+
- lib/pandoru/credentials.rb
|
|
180
211
|
- lib/pandoru/errors.rb
|
|
181
212
|
- lib/pandoru/models.rb
|
|
182
213
|
- lib/pandoru/models/_base.rb
|
|
@@ -185,6 +216,7 @@ files:
|
|
|
185
216
|
- lib/pandoru/models/search.rb
|
|
186
217
|
- lib/pandoru/models/station.rb
|
|
187
218
|
- lib/pandoru/models/track_explanation.rb
|
|
219
|
+
- lib/pandoru/secret_store.rb
|
|
188
220
|
- lib/pandoru/transport.rb
|
|
189
221
|
- lib/pandoru/version.rb
|
|
190
222
|
homepage: https://github.com/TwilightCoders/pandoru
|
|
@@ -199,7 +231,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
199
231
|
requirements:
|
|
200
232
|
- - ">="
|
|
201
233
|
- !ruby/object:Gem::Version
|
|
202
|
-
version: '
|
|
234
|
+
version: '3.0'
|
|
203
235
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
204
236
|
requirements:
|
|
205
237
|
- - ">="
|