shellify 1.0.0 → 1.1.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
  SHA256:
3
- metadata.gz: 6dc23ed10c36ddf92d2881d109271119d606b8ea380183a1d277353f13693f16
4
- data.tar.gz: d66f3374654de5851cf5565a4c2c1ede72971c80a223a2748cc8af6d2f25daf7
3
+ metadata.gz: 314e6a4441fcb3d6f77285ac44ecd3a5d2d77f04fe015aaefea06ac489f75813
4
+ data.tar.gz: 7cf9f6ac9154daab30f54797ada008e80cf5c29914fef6fdf91abf0bd392cc2b
5
5
  SHA512:
6
- metadata.gz: 32d3d1b078e5fe7fa1877cefed381104ce05df8ac2d72b3a23ef984ebba6efac5a3e37e2f6de70c5ece2495dd16cec8228a6db385235d43a67309d78da0a0f6e
7
- data.tar.gz: 8d80c7a94c9ebc89408236324e3b2f6f2f10f3ce15bb26b88a012ac9ac5d91eb6e9f198553f34646e1f43d92341a35e81f3103b04c77ce64abd4d8e513e803cd
6
+ metadata.gz: 8e2a4ad6c4bd8fb83a6e4c8f3af3feec80c05e4ac8e7075e6fb0b9dd09c4b12e97012b7deb8cc672553a2b581fc0d72adf40cb99beee21334f111dc9777c594f
7
+ data.tar.gz: 4e44e84dc5dd2fdc9b305636f04a170bf99faf0d88a8a40c158656817ff8b9c28a5ec872eb7d7bed5711a4f8a0c9fab4efcb8bc5e94ba7a4b16000e2ee103eb7
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2022 Derek Povah <http://derekpovah.com>
3
+ Copyright (c) 2022 Derek Povah <https://derekpovah.com>
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -14,25 +14,10 @@ $ gem install shellify
14
14
 
15
15
  #### Setup
16
16
 
17
- 1. Create a Spotify application in the [Spotify Developer Dashboard](https://developer.spotify.com/dashboard).
18
- 1. Get your Spotify OAUTH `access_token` and `refresh_token`. (Shelilfy currently doesn't currently implement a two way OAUTH flow, but Spotify has [sample apps](https://github.com/spotify/web-api-auth-examples) that you can use to get your initial keys. Shellify will handle exchanging refresh tokens for access tokens after initial setup.)
19
- 1. Create two json files in `~/.config/shellify`
20
- `config.json`
21
- ```json
22
- {
23
- "client_id": "xxxxxxxxxxxxxxxx",
24
- "client_secret": "xxxxxxxxxxxxxxx"
25
- }
26
- ```
27
-
28
- `spotify_user.json`
29
- ```json
30
- {
31
- "id": "spotify_user_id",
32
- "token": "xxxxxxxxxxxxxxx",
33
- "refresh_token": "xxxxxxxxxxxxxxx"
34
- }
35
- ```
17
+ 1. Create an application on the [Spotify Developer Dashboard](https://developer.spotify.com/dashboard).
18
+ 1. Add `http://localhost:8888/callback` to the Spotify Application's Redirect URIs
19
+ 1. Run `shellify configure`
20
+ 1. Run `shellify authenticate`
36
21
 
37
22
  Commands
38
23
  --------
data/lib/shellify/cli.rb CHANGED
@@ -19,6 +19,32 @@ module Shellify
19
19
  program :version, Shellify::VERSION
20
20
  program :description, 'Use Spotify from the command line'
21
21
 
22
+ command :configure do |c|
23
+ c.description = 'Set the Spotify client_id and client_secret'
24
+ c.action do
25
+ client_id = ask("Spotify Client ID: ")
26
+ client_secret = ask("Spotify Client Secret: ") { |q| q.echo = '*' }
27
+ @config.client_id = client_id
28
+ @config.client_secret = client_secret
29
+ @config.save!
30
+ end
31
+ end
32
+
33
+ command :authenticate do |c|
34
+ c.description = 'Authenticate with the Spotify API'
35
+ c.action do
36
+ spotify_username = ask('Your Spotify Username: ')
37
+ puts
38
+ puts 'Go to the link below to authorize Shellify.'
39
+ puts generate_oauth_url
40
+ oauth_credentials = Shellify::OauthCallbackHandler.run(@config)
41
+ @user.id = spotify_username
42
+ @user.token = oauth_credentials['access_token']
43
+ @user.refresh_token = oauth_credentials['refresh_token']
44
+ @user.save!
45
+ end
46
+ end
47
+
22
48
  command :devices do |c|
23
49
  c.description = 'List available playback devices'
24
50
  c.action do
@@ -2,37 +2,45 @@
2
2
 
3
3
  module Shellify
4
4
  class Config
5
- attr_reader :client_id, :client_secret, :config_dir
5
+ attr_accessor :client_id, :client_secret, :config_dir
6
6
 
7
7
  CONFIG_DIR = ENV['HOME'] + '/.config/shellify'
8
8
  CONFIG_FILE = CONFIG_DIR + '/config.json'
9
+ SPOTIFY_AUTHORIZATION_SCOPES = %w[
10
+ user-read-playback-state
11
+ user-modify-playback-state
12
+ user-read-currently-playing
13
+ user-library-modify
14
+ user-library-read
15
+ playlist-modify-private
16
+ playlist-read-collaborative
17
+ playlist-read-private
18
+ playlist-modify-public
19
+ ].join(' ')
9
20
 
10
21
  def initialize
11
22
  @config_dir = CONFIG_DIR
12
23
  @config_file = CONFIG_FILE
13
- create_config_file
14
24
  load_config
15
- RSpotify.authenticate(@client_id, @client_secret)
25
+ RSpotify.authenticate(@client_id, @client_secret) if configured?
16
26
  end
17
27
 
18
- private
28
+ def configured?
29
+ !@client_id.nil? && !@client_secret.nil?
30
+ end
19
31
 
20
- def load_config
21
- JSON.parse(File.read(CONFIG_FILE)).each_pair { |k,v| instance_variable_set("@#{k}", v) }
32
+ def save!
33
+ File.open(CONFIG_FILE, 'w') do |file|
34
+ file.write(JSON.pretty_generate({client_id: @client_id, client_secret: @client_secret}))
35
+ end
22
36
  end
23
37
 
24
- def create_config_file
25
- return if File.exists?(CONFIG_FILE)
38
+ private
26
39
 
27
- FileUtils.mkdir_p(CONFIG_DIR)
28
- FileUtils.touch(CONFIG_FILE)
29
- write_default_config
30
- end
40
+ def load_config
41
+ return unless File.exists?(CONFIG_FILE)
31
42
 
32
- def write_default_config
33
- File.open(CONFIG_FILE, 'w') do |file|
34
- file.write(JSON.pretty_generate({client_id: '', client_secret: ''}))
35
- end
43
+ JSON.parse(File.read(CONFIG_FILE)).each_pair { |k,v| instance_variable_set("@#{k}", v) }
36
44
  end
37
45
  end
38
46
  end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+ require 'cgi'
5
+ require 'base64'
6
+
7
+ module Shellify
8
+ class OauthCallbackHandler
9
+ def self.run(...)
10
+ new(...).run
11
+ end
12
+
13
+ def initialize(config)
14
+ @config = config
15
+ end
16
+
17
+ def run
18
+ @server = TCPServer.open(8888)
19
+ @client = @server.accept
20
+
21
+ path = @client.gets.split[1]
22
+ params = CGI.parse(path.split('?').last).transform_values(&:first)
23
+ body = 'Success! (You can close this now)'
24
+
25
+ begin
26
+ tokens = fetch_tokens(params['code'])
27
+ rescue RestClient::Exception => e
28
+ body = "Spotify didn't like that\n" + e.response
29
+ end
30
+
31
+ @client.puts headers(body.length)
32
+ @client.puts body
33
+ tokens
34
+ ensure
35
+ @client.close if @client
36
+ @server.close
37
+ end
38
+
39
+ private
40
+
41
+ def headers(content_length)
42
+ [
43
+ 'HTTP/1.1 200 Ok',
44
+ "date: #{Time.now.utc.strftime("%a, %d %b %Y %H:%M:%S GMT")}",
45
+ 'server: ruby',
46
+ "Content-Length: #{content_length}",
47
+ '',
48
+ '',
49
+ ].join("\r\n")
50
+ end
51
+
52
+ def fetch_tokens(code)
53
+ headers = {
54
+ 'Authorization': "Basic " + Base64.strict_encode64("#{@config.client_id}:#{@config.client_secret}"),
55
+ }
56
+
57
+ params = {
58
+ client_id: @config.client_id,
59
+ scope: Shellify::Config::SPOTIFY_AUTHORIZATION_SCOPES,
60
+ redirect_uri: 'http://localhost:8888/callback',
61
+ grant_type: 'authorization_code',
62
+ code: code,
63
+ }
64
+
65
+ JSON.parse(RestClient.post("https://accounts.spotify.com/api/token", params, headers))
66
+ end
67
+ end
68
+ end
data/lib/shellify/user.rb CHANGED
@@ -2,48 +2,62 @@
2
2
 
3
3
  module Shellify
4
4
  class User < RSpotify::User
5
+ attr_accessor :token, :refresh_token, :id
5
6
 
6
7
  USER_FILE = '/spotify_user.json'
7
8
 
8
9
  def initialize(config_dir)
9
10
  @config_dir = config_dir
11
+ @user_file_path = config_dir + USER_FILE
10
12
  create_user_file
11
- @spotify_user = load_persisted_user
13
+ write_default_user
14
+ load_persisted_user
12
15
  super({
13
16
  'credentials' => {
14
- 'token' => @spotify_user.token,
15
- 'refresh_token' => @spotify_user.refresh_token,
17
+ 'token' => @token,
18
+ 'refresh_token' => @refresh_token,
16
19
  'access_refresh_callback' => access_refresh_callback,
17
20
  },
18
- 'id' => @spotify_user.id,
21
+ 'id' => @id,
19
22
  })
20
23
  end
21
24
 
22
- private
25
+ def configured?
26
+ !@token.empty? && !@refresh_token.empty? && !@id.empty?
27
+ end
23
28
 
24
- def load_persisted_user
25
- OpenStruct.new(JSON.parse(File.read(@config_dir + USER_FILE)))
29
+ def save!
30
+ File.open(@user_file_path, 'w') do |file|
31
+ file.write(JSON.pretty_generate({id: @id, token: @token, refresh_token: @refresh_token}))
32
+ end
26
33
  end
27
34
 
28
- def persist_user(access_token)
29
- @spotify_user.token = access_token
35
+ private
30
36
 
31
- File.open(@config_dir + USER_FILE, 'w') do |file|
32
- file.write(JSON.pretty_generate(@spotify_user.to_h))
33
- end
37
+ def load_persisted_user
38
+ JSON.parse(File.read(@user_file_path)).each_pair { |k,v| instance_variable_set("@#{k}", v) }
34
39
  end
35
40
 
36
41
  def access_refresh_callback
37
42
  Proc.new do |new_access_token, _token_lifetime|
38
- persist_user(new_access_token)
43
+ @token = new_access_token
44
+ save!
39
45
  end
40
46
  end
41
47
 
42
48
  def create_user_file
43
- return if File.exists?(@config_dir + USER_FILE)
49
+ return if File.exists?(@user_file_path)
50
+
51
+ FileUtils.mkdir_p(@config_dir)
52
+ FileUtils.touch(@user_file_path)
53
+ end
44
54
 
45
- FileUtils.mkdir_p(CONFIG_DIR)
46
- FileUtils.touch(@config_dir + USER_FILE)
55
+ def write_default_user
56
+ return unless File.zero?(@user_file_path)
57
+
58
+ File.open(@user_file_path, 'w') do |file|
59
+ file.write(JSON.pretty_generate({id: '', token: '', refresh_token: '',}))
60
+ end
47
61
  end
48
62
  end
49
63
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'time'
4
+ require 'uri'
4
5
 
5
6
  module Shellify
6
7
  module Utils
@@ -15,5 +16,16 @@ module Shellify
15
16
  def time_to_ms(time)
16
17
  time.split(':').map { |a| a.to_i }.inject(0) { |a, b| a * 60 + b} * 1000
17
18
  end
19
+
20
+ def generate_oauth_url
21
+ url_params = {
22
+ response_type: 'code',
23
+ client_id: @config.client_id,
24
+ scope: Shellify::Config::SPOTIFY_AUTHORIZATION_SCOPES,
25
+ redirect_uri: 'http://localhost:8888/callback',
26
+ }
27
+
28
+ "https://accounts.spotify.com/authorize?#{URI.encode_www_form(url_params)}"
29
+ end
18
30
  end
19
31
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Shellify
4
- VERSION = '1.0.0'
4
+ VERSION = '1.1.0'
5
5
  end
data/lib/shellify.rb CHANGED
@@ -6,7 +6,8 @@ require 'rspotify'
6
6
  require 'shellify/version'
7
7
 
8
8
  module Shellify
9
- autoload :Cli, 'shellify/cli'
10
- autoload :Config, 'shellify/config'
11
- autoload :User, 'shellify/user'
9
+ autoload :Cli, 'shellify/cli'
10
+ autoload :Config, 'shellify/config'
11
+ autoload :User, 'shellify/user'
12
+ autoload :OauthCallbackHandler, 'shellify/oauth_callback_handler'
12
13
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shellify
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Derek Povah
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-01-28 00:00:00.000000000 Z
11
+ date: 2022-01-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: commander
@@ -80,6 +80,7 @@ files:
80
80
  - lib/shellify.rb
81
81
  - lib/shellify/cli.rb
82
82
  - lib/shellify/config.rb
83
+ - lib/shellify/oauth_callback_handler.rb
83
84
  - lib/shellify/user.rb
84
85
  - lib/shellify/utils.rb
85
86
  - lib/shellify/version.rb