Skip to content

Commit

Permalink
Hello gem
Browse files Browse the repository at this point in the history
  • Loading branch information
derekpovah committed Jan 28, 2022
0 parents commit 35cc5c4
Show file tree
Hide file tree
Showing 12 changed files with 436 additions and 0 deletions.
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/.bundle/
/.yardoc
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
Gemfile.lock
.ruby-version
*.gem

# rspec failure tracking
.rspec_status
8 changes: 8 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# frozen_string_literal: true

source 'https://rubygems.org'

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

# Specify your gem's dependencies in aeries-api.gemspec
gemspec
21 changes: 21 additions & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2022 Derek Povah <https://derekpovah.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
Shellify
========

Use Spotify from the command line

Installation
------------

Add this line to your application's Gemfile:

```bash
$ gem install shellify
```

#### Setup

1. Create a Spotify application in the [Spotify Developer Dashboard](https://developer.spotify.com/dashboard).
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.)
1. Create two json files in `~/.config/shellify`
`config.json`
```json
{
"client_id": "xxxxxxxxxxxxxxxx",
"client_secret": "xxxxxxxxxxxxxxx"
}
```

`spotify_user.json`
```json
{
"id": "spotify_user_id",
"token": "xxxxxxxxxxxxxxx",
"refresh_token": "xxxxxxxxxxxxxxx"
}
```

Commands
--------

```
$ shellify
Now Playing:
3 Libras - A Perfect Circle - 02:24/03:39 - ♥
```
See `shellify help` for a full list of commands.
RSpotify
--------
Shellify is basically a just a wrapper for [RSpotify](https://github.com/guilhermesad/rspotify) and wouldn't be possible (without significantly more effort) thanks to the work that's already been done over there.
License
-------
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
10 changes: 10 additions & 0 deletions exe/shellify
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

require 'shellify'

begin
Shellify::Cli.new.run
rescue Interrupt
exit 1
end
12 changes: 12 additions & 0 deletions lib/shellify.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# frozen_string_literal: true

require 'fileutils'
require 'json'
require 'rspotify'
require 'shellify/version'

module Shellify
autoload :Cli, 'shellify/cli'
autoload :Config, 'shellify/config'
autoload :User, 'shellify/user'
end
166 changes: 166 additions & 0 deletions lib/shellify/cli.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# frozen_string_literal: true

require 'commander'
require 'shellify/utils'

module Shellify
class Cli
include Commander::Methods
include Shellify::Utils

def initialize
$VERBOSE = nil # Suppress warnings from RSpotify's rest-client implementation by default
@config = Shellify::Config.new
@user = Shellify::User.new(@config.config_dir)
end

def run
program :name, 'Shellify'
program :version, Shellify::VERSION
program :description, 'Use Spotify from the command line'

command :devices do |c|
c.description = 'List available playback devices'
c.action do
devices = @user.devices
devices.each do |device|
puts " #{device.name}#{" - \e[1m𝅘𝅥𝅮\e[22m" if device.is_active}"
end
end
end

command :playing do |c|
c.description = 'List information about the current song'
c.action do
return puts " Nothing playing" unless @user.player.playing?

print_current_song
end
end

command :volume do |c|
c.description = 'Set the volume of the current playback device'
c.action do |args, options|
@user.player.volume(args[0])
end
end

command :like do |c|
c.description = 'Save the current song to your library'
c.action do
@user.save_tracks!([@user.player.currently_playing])
end
end

command :unlike do |c|
c.description = 'Remove the current song from your library'
c.action do
@user.remove_tracks!([@user.player.currently_playing])
end
end

command :playlists do |c|
c.description = 'List your playlists'
c.action do
@user.playlists.each do |playlist|
puts " #{playlist.name} - #{playlist.owner.display_name}#{" - Collaborative" if playlist.collaborative}"
end
end
end

command :add do |c|
c.description = 'Add the current song to the provided playlist'
c.action do |args, options|
return puts " Nothing playing" unless @user.player.playing?

playlist = @user.playlists.find { |p| p.name == args[0] }
return puts " Playlist not found" unless playlist

playlist.add_tracks!([@user.player.currently_playing])
end
end

command :remove do |c|
c.description = 'Remove the currently playing song from the provided playlist'
c.action do |args, options|
return puts " Nothing playing" unless @user.player.playing?

playlist = @user.playlists.find { |p| p.name == args[0] }
return puts " Playlist not found" unless playlist

playlist.remove_tracks!([@user.player.currently_playing])
end
end

command :play do |c|
c.description = 'Play or Pause on the currently playing device'
c.action do
begin
if @user.player.playing?
@user.player.pause
else
@user.player.play
print_current_song
end
rescue RestClient::NotFound
@user.player.play(@user.devices.first.id)
end
end
end

command :next do |c|
c.description = 'Skip to the next song in the queue'
c.action do
@user.player.next
print_current_song
end
end

command :previous do |c|
c.description = 'Skip the the previous song in the queue'
c.action do
@user.player.previous
print_current_song
end
end

command :restart do |c|
c.description = 'Restart the currently playing song'
c.action do
@user.player.seek 0
print_current_song
end
end

command :seek do |c|
c.description = 'Seek to the specified time in the current song'
c.action do |args, option|
@user.player.seek(time_to_ms(args[0]))
print_current_song
end
end

default_command :playing
alias_command :pause, :play
alias_command :back, :previous
alias_command :skip, :next

run!
end

private

def print_current_song
playing = @user.player.currently_playing
puts ' Now Playing:'
puts " #{playing.name} - #{playing.artists.first.name} - "\
"#{duration_to_s(@user.player.progress)}/#{duration_to_s(playing.duration_ms)}"\
"#{" - ♥" if @user.saved_tracks?([playing]).first}"
end

def exit_with_message(message, code = 1)
puts message
exit code
end
end
end
38 changes: 38 additions & 0 deletions lib/shellify/config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# frozen_string_literal: true

module Shellify
class Config
attr_reader :client_id, :client_secret, :config_dir

CONFIG_DIR = ENV['HOME'] + '/.config/shellify'
CONFIG_FILE = CONFIG_DIR + '/config.json'

def initialize
@config_dir = CONFIG_DIR
@config_file = CONFIG_FILE
create_config_file
load_config
RSpotify.authenticate(@client_id, @client_secret)
end

private

def load_config
JSON.parse(File.read(CONFIG_FILE)).each_pair { |k,v| instance_variable_set("@#{k}", v) }
end

def create_config_file
return if File.exists?(CONFIG_FILE)

FileUtils.mkdir_p(CONFIG_DIR)
FileUtils.touch(CONFIG_FILE)
write_default_config
end

def write_default_config
File.open(CONFIG_FILE, 'w') do |file|
file.write(JSON.pretty_generate({client_id: '', client_secret: ''}))
end
end
end
end
49 changes: 49 additions & 0 deletions lib/shellify/user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true

module Shellify
class User < RSpotify::User

USER_FILE = '/spotify_user.json'

def initialize(config_dir)
@config_dir = config_dir
create_user_file
@spotify_user = load_persisted_user
super({
'credentials' => {
'token' => @spotify_user.token,
'refresh_token' => @spotify_user.refresh_token,
'access_refresh_callback' => access_refresh_callback,
},
'id' => @spotify_user.id,
})
end

private

def load_persisted_user
OpenStruct.new(JSON.parse(File.read(@config_dir + USER_FILE)))
end

def persist_user(access_token)
@spotify_user.token = access_token

File.open(@config_dir + USER_FILE, 'w') do |file|
file.write(JSON.pretty_generate(@spotify_user.to_h))
end
end

def access_refresh_callback
Proc.new do |new_access_token, _token_lifetime|
persist_user(new_access_token)
end
end

def create_user_file
return if File.exists?(@config_dir + USER_FILE)

FileUtils.mkdir_p(CONFIG_DIR)
FileUtils.touch(@config_dir + USER_FILE)
end
end
end
Loading

0 comments on commit 35cc5c4

Please sign in to comment.