spotify_rec 1.1 → 1.6
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/bin/spotify_rec +33 -26
- data/lib/error.rb +10 -0
- data/lib/login.rb +154 -117
- data/lib/menu.rb +116 -67
- data/lib/my_list.rb +162 -137
- data/lib/playlist.rb +103 -67
- data/lib/rec.rb +54 -44
- data/lib/spec/rec_spec.rb +24 -0
- data/lib/spec/user_spec.rb +34 -0
- data/lib/tutorial.rb +56 -0
- data/lib/user.rb +93 -69
- data/public/users.json +1 -13
- metadata +64 -5
- data/lib/run.rb +0 -47
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 46b5ac675047a8a001ddf4d1a2e97d7e34487042ab39b87ccde12b9a3845ac3f
|
4
|
+
data.tar.gz: d950531fc469f552fcae747b6cf06437112fc63e5d81335dae220f7394971b86
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 21762354e3b8a9b8bcc4d63fadc9336dc078065b5852de877765ae578ec51aa7c28fe41319df1148f36a86e8e0b0338e185d3c78c0c26537e793edf39ae43f80
|
7
|
+
data.tar.gz: 584b1fdafd33153d11f07e169b0f31774b8f70b266181d7cab3aa46a1f3d95f0b5042b0a1f7032b7d696aa016a0ec187da7bd9dc54ed134021bbccb401f6f470
|
data/bin/spotify_rec
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
# gems
|
4
5
|
require 'rspotify'
|
@@ -15,40 +16,46 @@ require_relative '../lib/login'
|
|
15
16
|
require_relative '../lib/rec'
|
16
17
|
require_relative '../lib/menu'
|
17
18
|
require_relative '../lib/my_list'
|
19
|
+
require_relative '../lib/tutorial'
|
20
|
+
require_relative '../lib/error'
|
18
21
|
|
19
|
-
#modules
|
22
|
+
# modules
|
20
23
|
include Login
|
24
|
+
include Tutorial
|
21
25
|
|
22
|
-
RSpotify.authenticate(
|
23
|
-
$prompt = TTY::Prompt.new
|
26
|
+
RSpotify.authenticate('712ff89a218a4e6dbe1f169e06f949b9', 'e9e0517f405b4a01a1be8823126459b7')
|
24
27
|
|
25
|
-
VERSION = 1.
|
28
|
+
VERSION = 1.6
|
26
29
|
|
27
30
|
ARGV << '--run' if ARGV.empty?
|
28
31
|
|
29
32
|
options = {}
|
30
33
|
parser = OptionParser.new do |opts|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
34
|
+
opts.banner = 'Welcome to Spotify Recommendations! Usage: spotify_rec [options]'.colorize(:light_green)
|
35
|
+
|
36
|
+
opts.on('-v', '--version', 'Display the version') do
|
37
|
+
puts "Spotify Recommendations version #{VERSION}".colorize(:light_green)
|
38
|
+
end
|
39
|
+
|
40
|
+
opts.on('-h', '--help', 'Display the help message') do
|
41
|
+
puts opts
|
42
|
+
exit
|
43
|
+
end
|
44
|
+
|
45
|
+
opts.on('-t', '--tutorial', 'View a brief tutorial') do
|
46
|
+
Tutorial.start
|
47
|
+
end
|
48
|
+
|
49
|
+
opts.on('-qGENRE', '--quick=GENRE', 'Generate a quick recommendation with the chosen genre') do |genre|
|
50
|
+
recommendations = RSpotify::Recommendations.generate(limit: 5, seed_genres: [genre])
|
51
|
+
cleaned_recs = recommendations.tracks.map { |t| "#{t.name} by #{t.artists[0].name}" }
|
52
|
+
puts '》 RECOMMENDATIONS 《'.colorize(:light_green)
|
53
|
+
cleaned_recs.each { |track| puts track }
|
54
|
+
end
|
55
|
+
|
56
|
+
opts.on('-r', '--run', 'Run the application') do
|
57
|
+
Login.initial_login
|
58
|
+
end
|
52
59
|
end
|
53
60
|
|
54
|
-
parser.parse!
|
61
|
+
parser.parse!
|
data/lib/error.rb
ADDED
data/lib/login.rb
CHANGED
@@ -1,117 +1,154 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
puts
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
def
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
puts "
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
puts
|
79
|
-
username =
|
80
|
-
|
81
|
-
authenticate(username,
|
82
|
-
end
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
end
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
end
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'menu'
|
4
|
+
|
5
|
+
module Login
|
6
|
+
@prompt = TTY::Prompt.new
|
7
|
+
@count = 0
|
8
|
+
|
9
|
+
# Login Logic Section
|
10
|
+
# Prompts the user to indicate whether they are a new or returning user
|
11
|
+
def initial_login
|
12
|
+
clear
|
13
|
+
ascii_art
|
14
|
+
puts '》 Welcome to the Spotify Recommendations App! 《'.colorize(:light_green)
|
15
|
+
puts
|
16
|
+
selection = @prompt.select("Are you a new or returning user? \n", %w[New Returning])
|
17
|
+
new_user if selection == 'New'
|
18
|
+
returning_user if selection == 'Returning'
|
19
|
+
end
|
20
|
+
|
21
|
+
# New user
|
22
|
+
# Prompts user to enter username for new account. Checks name for any special characters.
|
23
|
+
# Raises error if special character is included, and prompts the user again.
|
24
|
+
def new_user
|
25
|
+
system('clear')
|
26
|
+
File.open(userdata, 'w') { |f| f.write([].to_json) } unless File.exist?(userdata)
|
27
|
+
puts 'Welcome! Please register for an account to continue.'.colorize(:light_green)
|
28
|
+
username = @prompt.ask('Username >')
|
29
|
+
raise RequirementError.new, 'Requirements not met' if username.match?(/[!@#$%^&*(),.?":{}|<>]/)
|
30
|
+
|
31
|
+
new_user_password(username)
|
32
|
+
rescue RequirementError
|
33
|
+
puts 'Username cannot contain special characters. Please try again!'.colorize(:light_red)
|
34
|
+
new_user
|
35
|
+
end
|
36
|
+
|
37
|
+
# Prompts for new password. Validates password so no special characters are included.
|
38
|
+
def new_user_password(username)
|
39
|
+
password = @prompt.mask('Password >')
|
40
|
+
raise RequirementError.new, 'Requirements not met' if password.match?(/[!@#$%^&*(),.?":{}|<>]/)
|
41
|
+
|
42
|
+
@user = User.new(username, password, gen_uid)
|
43
|
+
@user_playlist = Playlist.new(@user)
|
44
|
+
store_user
|
45
|
+
rescue RequirementError
|
46
|
+
puts 'Password cannot contain special characters. Please try again!'.colorize(:light_red)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Converts user details into a hash, ready to be stored in JSON file
|
50
|
+
def store_user
|
51
|
+
data = load_data
|
52
|
+
user_details = {
|
53
|
+
'id' => @user.uid.to_s,
|
54
|
+
'username' => @user.username,
|
55
|
+
'password' => @user.password,
|
56
|
+
'playlist' => @user.playlist,
|
57
|
+
'mylist' => @user.mylist
|
58
|
+
}
|
59
|
+
data << user_details
|
60
|
+
write_user(data)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Writes the hash of user data to the JSON file
|
64
|
+
def write_user(data)
|
65
|
+
File.open(userdata, 'w') do |f|
|
66
|
+
f.puts JSON.pretty_generate(data)
|
67
|
+
end
|
68
|
+
puts "You're now registered! Logging you in..".colorize(:light_green)
|
69
|
+
sleep(1)
|
70
|
+
go_to_menu
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returning user
|
74
|
+
# Prompts user to enter username and password
|
75
|
+
def returning_user
|
76
|
+
system('clear')
|
77
|
+
File.open(userdata, 'w') { |f| f.write([].to_json) } unless File.exist?(userdata)
|
78
|
+
puts 'Welcome back! Please login to continue.'.colorize(:light_green)
|
79
|
+
username = @prompt.ask('Username >')
|
80
|
+
user_password = @prompt.mask('Password >')
|
81
|
+
authenticate(username, user_password)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Loads data from JSON file
|
85
|
+
def authenticate(username, user_password)
|
86
|
+
data_arr = load_data
|
87
|
+
user_data(data_arr, username, user_password)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Checks whether supplied username and password match any in the userfile
|
91
|
+
def user_data(data, username, user_password)
|
92
|
+
data.each do |hash|
|
93
|
+
next unless hash['username'].downcase == username.downcase
|
94
|
+
|
95
|
+
next unless hash['password'] == user_password
|
96
|
+
|
97
|
+
@user = User.new(username, user_password, hash['id'], hash['playlist'], hash['mylist'])
|
98
|
+
go_to_menu
|
99
|
+
end
|
100
|
+
@count += 1
|
101
|
+
no_auth
|
102
|
+
end
|
103
|
+
|
104
|
+
# Method exits application if user is unsuccessful 3 times or more. Otherwise returns to login
|
105
|
+
def no_auth
|
106
|
+
puts 'Incorrect username or password!'.colorize(:red)
|
107
|
+
sleep(1)
|
108
|
+
if @count >= 3
|
109
|
+
puts 'You have tried too many times! The application will now close..'.colorize(:light_red)
|
110
|
+
exit
|
111
|
+
end
|
112
|
+
returning_user
|
113
|
+
end
|
114
|
+
|
115
|
+
# Helper Module Methods
|
116
|
+
def user
|
117
|
+
@user
|
118
|
+
end
|
119
|
+
|
120
|
+
def gen_uid
|
121
|
+
load_data.length + 1
|
122
|
+
end
|
123
|
+
|
124
|
+
def userdata
|
125
|
+
path = File.join(File.dirname(File.dirname(File.absolute_path(__FILE__))))
|
126
|
+
"#{path}/public/users.json"
|
127
|
+
end
|
128
|
+
|
129
|
+
def load_data
|
130
|
+
file = File.read(userdata)
|
131
|
+
JSON.parse(file)
|
132
|
+
end
|
133
|
+
|
134
|
+
def clear
|
135
|
+
system('clear')
|
136
|
+
end
|
137
|
+
|
138
|
+
def go_to_menu
|
139
|
+
menu = Menu.new(@user)
|
140
|
+
menu.menu_router
|
141
|
+
end
|
142
|
+
|
143
|
+
def ascii_art
|
144
|
+
puts " ;;;;;;;;;;;;;;;;;;;
|
145
|
+
; ;
|
146
|
+
; ;
|
147
|
+
; ;
|
148
|
+
; ;
|
149
|
+
; ;
|
150
|
+
,;;;;; ,;;;;;
|
151
|
+
;;;;;; ;;;;;;
|
152
|
+
`;;;;' `;;;;'\n".colorize(:light_green)
|
153
|
+
end
|
154
|
+
end
|
data/lib/menu.rb
CHANGED
@@ -1,67 +1,116 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
@
|
6
|
-
@
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
case selection
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Menu
|
4
|
+
def initialize(user)
|
5
|
+
@user = user
|
6
|
+
@prompt = TTY::Prompt.new
|
7
|
+
@playlist = Playlist.new(@user)
|
8
|
+
@list_length = @user.mylist.length
|
9
|
+
end
|
10
|
+
|
11
|
+
# Prompts the user to select an item on the menu
|
12
|
+
def display_menu
|
13
|
+
system('clear')
|
14
|
+
arr = ['My List', 'Recommendations', 'Playlist', 'Account Details', 'Exit']
|
15
|
+
@prompt.select("》 MAIN MENU 《\n".colorize(:light_green), arr)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Begins to case the user selection from the menu
|
19
|
+
def menu_router
|
20
|
+
selection = display_menu
|
21
|
+
case selection
|
22
|
+
when 'My List'
|
23
|
+
my_list
|
24
|
+
when 'Recommendations'
|
25
|
+
recommendations_menu
|
26
|
+
else
|
27
|
+
case_menu(selection)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Opens the recommendations menu
|
32
|
+
def recommendations_menu
|
33
|
+
if @list_length.positive?
|
34
|
+
recommendation = Rec.new(@user)
|
35
|
+
recommendation.amount_of_suggestions
|
36
|
+
else
|
37
|
+
no_items
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Sends user back to menu if they don't have any items in 'MyList' yet
|
42
|
+
def no_items
|
43
|
+
puts "Uh oh! You don't have any items in your list yet, so we can't generate any".colorize(:light_red)
|
44
|
+
puts 'recommendations. Please add some before doing this!'.colorize(:light_red)
|
45
|
+
@prompt.keypress('Press any key to return to the previous menu..')
|
46
|
+
display_menu
|
47
|
+
end
|
48
|
+
|
49
|
+
# Continues to case the menu selection and route the user
|
50
|
+
def case_menu(selection)
|
51
|
+
case selection
|
52
|
+
when 'Playlist'
|
53
|
+
@playlist.menu
|
54
|
+
when 'Account Details'
|
55
|
+
account_details
|
56
|
+
when 'Exit'
|
57
|
+
exit
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Shows the MyList menu
|
62
|
+
def my_list
|
63
|
+
system('clear')
|
64
|
+
selection = @prompt.select("》 MY LIST 《\n".colorize(:light_green), %w[Display Add Remove Back])
|
65
|
+
mylist = MyList.new(@user)
|
66
|
+
case_my_list(selection, mylist)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Cases the selection from the MyList menu and routes to the required method
|
70
|
+
def case_my_list(selection, mylist)
|
71
|
+
case selection
|
72
|
+
when 'Display'
|
73
|
+
mylist.list
|
74
|
+
when 'Add'
|
75
|
+
mylist.add_to_list
|
76
|
+
when 'Remove'
|
77
|
+
mylist.remove_from_list
|
78
|
+
when 'Back'
|
79
|
+
menu_router
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Prompts the user to select from the Account details menu
|
84
|
+
def account_details_select
|
85
|
+
system('clear')
|
86
|
+
arr = ['View Details', 'Change Username', 'Change Password', 'Delete Account', 'Back']
|
87
|
+
@prompt.select('》 ACCOUNT DETAILS 《\n'.colorize(:light_green), arr)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Cases the account details menu and routes the user
|
91
|
+
def account_details
|
92
|
+
selection = account_details_select
|
93
|
+
case selection
|
94
|
+
when 'View Details'
|
95
|
+
@user.details
|
96
|
+
when 'Change Username'
|
97
|
+
username = @prompt.ask('Please enter your new username >')
|
98
|
+
@user.change_username(username)
|
99
|
+
else
|
100
|
+
case_account_details(selection)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Continues to case the account details menu and route the user
|
105
|
+
def case_account_details(selection)
|
106
|
+
case selection
|
107
|
+
when 'Change Password'
|
108
|
+
password = @prompt.ask('Please enter your new password >')
|
109
|
+
@user.change_password(password)
|
110
|
+
when 'Delete Account'
|
111
|
+
@user.delete_account
|
112
|
+
when 'Back'
|
113
|
+
menu_router
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|