You've successfully opted out of using a Spotify app.
32 |
33 | You can always opt back in using the settings menu in
34 | Spotifious. Just type s in Spotifious.
35 |
36 |
You can now reopen Spotifious to continue setup.
37 |
38 |
39 |
40 |
41 |
42 | requestCredentialsToken($scopes);
63 | } catch(SpotifyWebAPI\SpotifyWebAPIException $e) {
64 | $response["status"] = "error";
65 |
66 | $response["message"] = "Invalid data - ";
67 | if ($e->getMessage() == "Invalid client") {
68 | $response["message"] .= "invalid ID";
69 | } elseif($e->getMessage() == "Invalid client secret") {
70 | $response["message"] .= "incorrect secret";
71 | } else {
72 | $response["message"] .= $e->getMessage();
73 | }
74 |
75 | echo json_encode($response);
76 | exit();
77 | }
78 |
79 | // Save data
80 | $alfred->options('spotify_client_id', $_GET["id"]);
81 | $alfred->options('spotify_secret', $_GET["secret"]);
82 | $alfred->options('spotify_access_token', '');
83 | $alfred->options('spotify_access_token_expires', '');
84 | $alfred->options('spotify_refresh_token', '');
85 | $alfred->options('spotify_app_opt_out', 'false');
86 |
87 | $response["status"] = "success";
88 | $response["message"] = "Saved your information! Make sure to do step 8 :)";
89 | echo json_encode($response);
90 | exit();
--------------------------------------------------------------------------------
/src/citelao/Spotifious/Menus/DetailArtist.php:
--------------------------------------------------------------------------------
1 | alfred = $alfred;
22 | $locale = $this->alfred->options('country');
23 |
24 | $this->currentURI = $options['currentURI'];
25 | $this->query = $options['query'];
26 | $this->originalQuery = $options['originalQuery'];
27 | $this->search = $options['search'];
28 |
29 | if($api) {
30 | $artistJson = $api->getArtist($options['id']);
31 | } else {
32 | $artistFetcher = new JsonFetcher("https://api.spotify.com/v1/artists/{$options['id']}");
33 | $artistJson = $artistFetcher->run();
34 | }
35 |
36 | $this->name = $artistJson->name;
37 | $this->type = $artistJson->type;
38 |
39 | if($api) {
40 | if($locale == 'not-given') {
41 | $locale = '';
42 | }
43 | $albumsJson = $api->getArtistAlbums($options['id'], [$locale]);
44 | } else {
45 | $url = "https://api.spotify.com/v1/artists/{$options['id']}/albums";
46 | if($locale != 'not-given') {
47 | $url .= "?market=$locale";
48 | }
49 | $albumFetcher = new JsonFetcher($url);
50 | $albumsJson = $albumFetcher->run();
51 | }
52 |
53 | $this->albums = array();
54 | foreach ($albumsJson->items as $key => $value) {
55 | $this->albums[] = array(
56 | 'uri' => $value->uri,
57 | 'name' => $value->name,
58 | 'type' => $value->album_type
59 | );
60 | }
61 |
62 | // TODO search for more!
63 | }
64 |
65 | public function output() {
66 | $results = array();
67 |
68 | foreach ($this->albums as $key => $current) {
69 | $currentResult = array(
70 | 'title' => $current['name'],
71 | 'subtitle' => "Browse this {$current['type']}",
72 | 'valid' => false,
73 | 'autocomplete' => "{$this->currentURI} ⟩ {$current['uri']} ⟩ {$this->query} ⟩{$this->search}⟩",
74 | 'copy' => $this->currentURI,
75 | 'icon' => array('path' => "include/images/album.png")
76 | );
77 |
78 | if($this->search != '' && !mb_stristr($currentResult['title'], $this->search))
79 | continue;
80 |
81 | $results[] = $currentResult;
82 | }
83 |
84 | $scope['title'] = $this->name;
85 | $scope['subtitle'] = "Browse this {$this->type} in Spotify";
86 | $scope['arg'] = "spotify⟩activate (open location \"{$this->currentURI}\")";
87 | $scope['autocomplete'] = $this->originalQuery;
88 | $scope['copy'] = $this->currentURI;
89 | $scope['icon'] = array('path' => "include/images/{$this->type}.png");
90 |
91 | if ($this->search == null) {
92 | array_unshift($results, $scope);
93 | } else {
94 | array_push($results, $scope);
95 | }
96 |
97 | return $results;
98 | }
99 | }
--------------------------------------------------------------------------------
/src/citelao/Spotifious/Menus/DetailAlbum.php:
--------------------------------------------------------------------------------
1 | alfred = $alfred;
21 | $locale = $this->alfred->options('country');
22 |
23 | $this->currentURI = $options['currentURI'];
24 | $this->query = $options['query'];
25 | $this->originalQuery = $options['originalQuery'];
26 | $this->search = $options['search'];
27 |
28 | if(!$api) {
29 | $artistFetcher = new JsonFetcher("https://api.spotify.com/v1/albums/{$options['id']}");
30 | $json = $artistFetcher->run();
31 | } else {
32 | $json = $api->getAlbum($options['id']);
33 | }
34 |
35 | $this->name = $json->name;
36 | $this->type = $json->type;
37 |
38 | $this->tracks = array();
39 | foreach ($json->tracks->items as $key => $value) {
40 | $this->tracks[] = array(
41 | 'uri' => $value->uri,
42 | 'name' => $value->name,
43 | 'type' => $value->type,
44 | 'number' => $value->track_number,
45 | 'duration' => $value->duration_ms,
46 | 'explicit' => ($value->explicit == 'true')
47 | );
48 | }
49 | }
50 |
51 | public function output() {
52 | $results = array();
53 |
54 | foreach ($this->tracks as $key => $current) {
55 | $explicit = $current['explicit'] ? " (explicit)" : "";
56 |
57 | $currentResult = array(
58 | 'title' => "{$current['number']}. {$current['name']}",
59 | 'subtitle' => $this->prettifyTime($current['duration']) . $explicit,
60 | 'valid' => true,
61 | 'arg' => "spotify⟩play track \"{$current['uri']}\" in context \"{$this->currentURI}\"",
62 | 'copy' => $current['uri'],
63 | 'icon' => array('path' => "include/images/track.png")
64 | );
65 |
66 | if($this->search != '' && !mb_stristr($currentResult['title'], $this->search))
67 | continue;
68 |
69 | $results[] = $currentResult;
70 | }
71 |
72 | $scope['title'] = $this->name;
73 | $scope['subtitle'] = "Browse this {$this->type} in Spotify";
74 | $scope['arg'] = "spotify⟩activate (open location \"{$this->currentURI}\")";
75 | $scope['autocomplete'] = $this->originalQuery;
76 | $scope['copy'] = $this->currentURI;
77 | $scope['icon'] = array('path' => "include/images/{$this->type}.png");
78 |
79 | if ($this->search == null) {
80 | array_unshift($results, $scope);
81 | } else {
82 | array_push($results, $scope);
83 | }
84 |
85 | return $results;
86 | }
87 |
88 | protected function prettifyTime($time_ms) {
89 | $secondsForm = $time_ms / 1000;
90 |
91 | $seconds = $secondsForm % 60;
92 | $minutes = floor($secondsForm / 60);
93 |
94 | $seconds_string = ($seconds < 10)
95 | ? "0" . $seconds
96 | : $seconds;
97 |
98 | return $minutes . ":" . $seconds_string;
99 | }
100 | }
--------------------------------------------------------------------------------
/src/citelao/Spotifious/Menus/Settings.php:
--------------------------------------------------------------------------------
1 | alfred = $alfred;
19 | $this->api = $api;
20 |
21 | $this->trackNotificationsEnabled = ($this->alfred->options('track_notifications') == 'true');
22 | $this->playlists_cache_date = $this->alfred->options('playlists_cache_date');
23 | $this->countryCode = $this->alfred->options('country');
24 | $this->optedOut = ($this->alfred->options('spotify_app_opt_out') == 'true');
25 | }
26 |
27 | public function output() {
28 | $results[] = array(
29 | 'title' => 'Settings',
30 | 'subtitle' => 'Here you can configure any options you want.',
31 | 'icon' => array('path' => 'include/images/configuration.png'),
32 | 'valid' => false
33 | );
34 |
35 | $results[] = array(
36 | 'title' => 'Track notifications',
37 | 'subtitle' => "Check this if you'd like to enable track change notifications.",
38 | 'icon' => array('path' => $this->trackNotificationsEnabled ? 'include/images/checked.png' : 'include/images/unchecked.png'),
39 | 'arg' => 'togglenotifications⟩'
40 | );
41 |
42 | if($this->api) {
43 | $last_update = Helper::human_ago($this->playlists_cache_date);
44 | $results[] = array(
45 | 'title' => 'Update playlists cache',
46 | 'subtitle' => "Last updated $last_update. If your playlists are not appearing, try this.",
47 | 'icon' => array('path' => 'include/images/dash.png'),
48 | 'arg' => 'update_playlists_cache⟩'
49 | );
50 | }
51 |
52 | $results[] = array(
53 | 'title' => 'Configure country code',
54 | 'subtitle' => 'Currently set to "' . $this->countryCode . '."',
55 | 'autocomplete' => 'Country Code ⟩',
56 | 'valid' => false,
57 | 'icon' => array('path' => 'include/images/dash.png')
58 | );
59 |
60 | $results[] = array(
61 | 'title' => ($this->optedOut) ?
62 | 'Link a Spotify app' :
63 | 'Change my linked Spotify application',
64 | 'subtitle' => ($this->optedOut) ?
65 | "Opt-in to using a Spotify app. The dark side's not so bad!" :
66 | 'If you want to link a new Spotify app or update key information. You will need to login again.',
67 | 'arg' => 'appsetup⟩',
68 | 'icon' => array('path' => 'include/images/dash.png')
69 | );
70 |
71 | if(!$this->optedOut) {
72 | $results[] = array(
73 | 'title' => 'Login again to my Spotify application',
74 | 'subtitle' => 'If you want to login again, do it!',
75 | 'arg' => 'applink⟩',
76 | 'icon' => array('path' => 'include/images/dash.png')
77 | );
78 | }
79 |
80 | $results[] = array(
81 | 'title' => 'You can access settings easily.',
82 | 'subtitle' => 'Type `s` from the main menu.',
83 | 'icon' => array('path' => 'include/images/info.png'),
84 | 'valid' => false
85 | );
86 |
87 | return $results;
88 | }
89 | }
--------------------------------------------------------------------------------
/doc/spec.md:
--------------------------------------------------------------------------------
1 | # How Spotifious should work #
2 |
3 | There are four specific types of output:
4 |
5 | 1. Main Menu — first activation
6 | 4. Generic Menu — list of options, used for settings menu.
7 | 2. Search Menu — user typing
8 | 3. Detail Menu — autocompleted text with Spotify URL
9 |
10 | ## Main Menu ##
11 |
12 | `^⌘⏎` should show the main menu.
13 |
14 | It should check for updates every so often and display an update bar occasionally.
15 |
16 | 1. ► Crooked Teeth ⏎ `playpause`
17 | - ???
18 | 2. Plans ⏎ album detail
19 | - More from this album...
20 | 3. Death Cab for Cutie ⏎ artist detail
21 | - More from this artist...
22 | 4. Search for music...
23 | - Begin typing to search
24 |
25 | ## Generic Menu ##
26 |
27 | We use this for the settings & setup menus.
28 |
29 | ### Setup menu ###
30 |
31 | These are the options required for initial operation.
32 |
33 | - Country Code
34 | - should trigger a list of countries.
35 | - Create a Spotify application
36 | - should trigger the application-creation web server.
37 | - Link Spotify application
38 | - should open the Spotify login page for the new app.
39 |
40 | ### Settings Menu ###
41 |
42 | These are all the original setup options, plus some additional ones.
43 |
44 | - Track Notifications
45 | - toggle notifications of next playing tracks
46 |
47 | ## Search Menu ##
48 |
49 | After activation, any typing (3+ chars should work) should show the search menu, unless the query requires a detail menu (see below). The results should be weighted so artists>albums>songs— but only if the query is completely contained by the result. `Lady Danv` should return Lady Danville, then Lady Danville EP, then songs. Otherwise sort by popularity; use a unique id for each query so popular searches follow Alfred's smart order.
50 |
51 | If the query starts with a `c`, include control items, like play, pause, shuffle, etc.
52 |
53 | 1. Lady Danville ⏎ artist detail
54 | - ★★★★☆ Artist
55 | 2. Lady Danville EP ⏎ album detail
56 | - ★★★☆☆ Album by Lady Danville
57 | 3. Love to Love ⏎ `open location <song>`
58 | - ★★☆☆☆ Lady Danville EP by Lady Danville
59 |
60 | ## Detail Menu ##
61 |
62 | I want to maintain easily navigable menus & submenus, but need to provide the Spotify URL in order to perform an artist or album lookup. To that end, the detail menu uses a smart syntax, separated by some unicode glyphs (`►` & `⟩` maybe).
63 |
64 | The query should have all URLs in hierarchical order, than all the queries in hierarchical order, followed by a closing separator:
65 |
66 | `a menu url⟩a submenu url⟩another submenu url►query used in menu⟩query used in submenu⟩query used in final submenu⟩`
67 |
68 | The different separator should be a sign that the query is parsable. If it isn't present, ignore the query.
69 |
70 | This syntax will not be compatible with the old syntax.
71 |
72 | ### Playlist Detail ###
73 |
74 | 1. Liked from Radio ⏎ view in Spotify
75 | - View playlist in Spotify
76 | 2. Sticking it to Myself ⏎ `open location <song>`
77 | - Jonathan Coulton - Artificial Heart 2:20 ★★★☆☆
78 |
79 | ### Artist Detail ###
80 |
81 | 1. Jonathan Coulton ⏎ view in Spotify
82 | - View artist in Spotify
83 | 2. One Christmas at a Time ⏎ album detail
84 | - Open this album...
85 | 3. ...
86 |
87 | ### Album Detail ###
88 |
89 | 1. Artificial Heart ⏎ view in Spotify
90 | - View album in Spotify
91 | 2. 1. Sticking it to Myself ⏎ `open location <song>`
92 | - Jonathan Coulton 2:20 ★★★☆☆
93 | 3. ...
--------------------------------------------------------------------------------
/include/setup/style/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | *:focus {
6 | -webkit-animation: bounce 0.25s 0s;
7 | -moz-animation: bounce 0.25s 0s;
8 | -o-animation: bounce 0.25s 0s;
9 | animation: bounce 0.25s 0s;
10 | }
11 |
12 | *:target {
13 | background: #93FF74;
14 | -webkit-animation: bounce 0.25s 0s;
15 | -moz-animation: bounce 0.25s 0s;
16 | -o-animation: bounce 0.25s 0s;
17 | animation: bounce 0.25s 0s;
18 | }
19 |
20 | a,
21 | input[type=submit] {
22 | position: relative;
23 | bottom: 0;
24 | -webkit-transition: all 0.1s ease-in;
25 | }
26 |
27 | a:hover,
28 | input[type=submit]:hover {
29 | position: relative;
30 | bottom: .1em;
31 | -webkit-transition: none;
32 | }
33 |
34 | body {
35 | color: #333;
36 | line-height: 1.5;
37 | }
38 |
39 | .clear {
40 | clear: both;
41 | }
42 |
43 | .wrapper {
44 | width: 40em;
45 | margin: 0 auto;
46 | }
47 |
48 | code, kbd {
49 | background: #ddd;
50 | padding: .5em;
51 |
52 | border: .1em solid #999;
53 | border-radius: .5em;
54 | }
55 |
56 | figure {
57 | margin: 1em 0;
58 | padding: 0;
59 | }
60 |
61 | figcaption {
62 | text-align: center;
63 | padding: .5em;
64 |
65 | background: #333;
66 | color: #fff;
67 | }
68 |
69 | img {
70 | max-width: 100%;
71 | border: .5em solid #333;
72 | }
73 |
74 | table {
75 | width: 100%;
76 | }
77 |
78 | td + td {
79 | padding-left: .5em;
80 | }
81 |
82 | label {
83 | color: #999;
84 | }
85 |
86 | input {
87 | padding: .5em;
88 | width: 100%;
89 | border: .1em solid #999;
90 | }
91 |
92 | input[type=submit] {
93 | width: auto;
94 | float: right;
95 | margin: .5em 0;
96 | padding: .5em 2em;
97 |
98 | background: rgb(81, 37, 132);
99 | color: #fff;
100 | border: 0;
101 | border-radius: 1em;
102 | }
103 |
104 | input[disabled] {
105 | color: #999;
106 | }
107 |
108 | .cancel {
109 | float: right;
110 | display: inline-block;
111 | margin: .5rem 2rem;
112 |
113 | color: #627699;
114 | font-size: 0.75em;
115 | }
116 |
117 | .response, .info {
118 | text-align: center;
119 | padding: 1em 0;
120 | }
121 | .response {
122 | display: none;
123 | }
124 |
125 | .error {
126 | background: #E52C48;
127 | color: #fff;
128 | }
129 |
130 | .success {
131 | background: #6CD19B;
132 | }
133 |
134 | .note {
135 | background: #80A0FF;
136 | }
137 |
138 | .warning {
139 | background: #93FF74;
140 | }
141 |
142 | @-webkit-keyframes bounce {
143 | 0% { -webkit-transform: scale(1); transform: scale(1);}
144 | 60% { -webkit-transform: scale(1.4); transform: scale(1.4); }
145 | 90% { -webkit-transform: scale(0.95); transform: scale(0.95); }
146 | 100% { -webkit-transform: scale(1); transform: scale(1); }
147 | }
148 |
149 | @-moz-keyframes bounce {
150 | 0% { -moz-transform: scale(1); transform: scale(1);}
151 | 60% { -moz-transform: scale(1.4); transform: scale(1.4); }
152 | 90% { -moz-transform: scale(0.95); transform: scale(0.95); }
153 | 100% { -moz-transform: scale(1); transform: scale(1); }
154 | }
155 |
156 | @-o-keyframes bounce {
157 | 0% { -o-transform: scale(1); transform: scale(1);}
158 | 60% { -o-transform: scale(1.4); transform: scale(1.4); }
159 | 90% { -o-transform: scale(0.95); transform: scale(0.95); }
160 | 100% { -o-transform: scale(1); transform: scale(1); }
161 | }
162 |
163 | @keyframes bounce {
164 | 0% { transform: scale(1); }
165 | 60% { transform: scale(1.4); }
166 | 90% { transform: scale(0.95); }
167 | 100% { transform: scale(1); }
168 | }
--------------------------------------------------------------------------------
/src/citelao/Spotifious/Menus/DetailPlaylist.php:
--------------------------------------------------------------------------------
1 | alfred = $alfred;
22 | $locale = $this->alfred->options('country');
23 |
24 | $this->currentURI = $options['currentURI'];
25 | $this->query = $options['query'];
26 | $this->originalQuery = $options['originalQuery'];
27 | $this->search = $options['search'];
28 |
29 | $explodedURI = explode(":", $this->currentURI);
30 | $user = $explodedURI[count($explodedURI) - 3];
31 |
32 | if(!$api) {
33 | throw new StatefulException("You should have an API at this point");
34 | } else {
35 | $json = $api->getUserPlaylist($user, $options['id']);
36 | }
37 |
38 | $this->name = $json->name;
39 | $this->type = $json->type;
40 |
41 | $this->tracks = array();
42 | foreach ($json->tracks->items as $key => $value) {
43 | $this->tracks[] = array(
44 | 'uri' => $value->track->uri,
45 | 'name' => $value->track->name,
46 | 'type' => $value->track->type,
47 | 'number' => $key + 1,
48 | 'duration' => $value->track->duration_ms,
49 | 'explicit' => ($value->track->explicit == 'true')
50 | );
51 | }
52 |
53 | $this->overflow = false;
54 | if($json->tracks->total > $json->tracks->limit) {
55 | $this->overflow = true;
56 | $this->overflow_count = $json->tracks->total - $json->tracks->limit;
57 | }
58 | }
59 |
60 | public function output() {
61 | $results = array();
62 |
63 | foreach ($this->tracks as $key => $current) {
64 | $explicit = $current['explicit'] ? " (explicit)" : "";
65 |
66 | $currentResult = array(
67 | 'title' => "{$current['number']}. {$current['name']}",
68 | 'subtitle' => $this->prettifyTime($current['duration']) . $explicit,
69 | 'valid' => true,
70 | 'arg' => "spotify⟩play track \"{$current['uri']}\" in context \"{$this->currentURI}\"",
71 | 'copy' => $current['uri'],
72 | 'icon' => array('path' => "include/images/track.png")
73 | );
74 |
75 | if($this->search != '' && !mb_stristr($currentResult['title'], $this->search))
76 | continue;
77 |
78 | $results[] = $currentResult;
79 | }
80 |
81 | if($this->overflow) {
82 | $overflow = array(
83 | 'title' => 'This is a large playlist',
84 | 'subtitle' => ($this->overflow_count === 1)
85 | ? "1 song could not be displayed; you can view it in Spotify."
86 | : "$this->overflow_count songs could not be displayed; you can view them in Spotify.",
87 | 'arg' => "spotify⟩activate (open location \"{$this->currentURI}\")",
88 | 'autocomplete' => $this->originalQuery,
89 | 'copy' => $this->currentURI,
90 | 'icon' => array('path' => "include/images/info.png")
91 | );
92 | }
93 |
94 | $scope['title'] = $this->name;
95 | $scope['subtitle'] = "Browse this {$this->type} in Spotify";
96 | $scope['arg'] = "spotify⟩activate (open location \"{$this->currentURI}\")";
97 | $scope['autocomplete'] = $this->originalQuery;
98 | $scope['copy'] = $this->currentURI;
99 | $scope['icon'] = array('path' => "include/images/{$this->type}.png");
100 |
101 | if ($this->search == null) {
102 | if($this->overflow) {
103 | array_unshift($results, $overflow);
104 | }
105 | array_unshift($results, $scope);
106 | } else {
107 | array_push($results, $scope);
108 | if($this->overflow) {
109 | array_push($results, $overflow);
110 | }
111 | }
112 |
113 | return $results;
114 | }
115 |
116 | protected function prettifyTime($time_ms) {
117 | $secondsForm = $time_ms / 1000;
118 |
119 | $seconds = $secondsForm % 60;
120 | $minutes = floor($secondsForm / 60);
121 |
122 | $seconds_string = ($seconds < 10)
123 | ? "0" . $seconds
124 | : $seconds;
125 |
126 | return $minutes . ":" . $seconds_string;
127 | }
128 | }
--------------------------------------------------------------------------------
/_remote/pages/BF8D740B-C9D0-4622-8695-FC0CCA97B37F.data:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | backgroundcolor
6 |
7 | 0.64910906553268433
8 | 0.56339049339294434
9 | 0.66290569305419922
10 |
11 | iconid
12 | 396A6C7D-DEF5-4580-8F15-927EFB5D6BFC
13 | iconisuserspecified
14 |
15 | items
16 |
17 |
18 | actionconfig
19 |
20 | autoworkflowuid
21 |
22 | triggerid
23 | com.citelao.spotifious.previous
24 |
25 | actionuid
26 | remote.alfred.runworkflow
27 | buttoniconid
28 | 5A4E8FCB-5384-41CC-8B7C-97A263163633
29 | buttonlabel
30 | Previous Track
31 | itemuid
32 | 3E137BFB-62B6-49F5-881F-753A2A7A3523
33 |
34 |
35 | actionconfig
36 |
37 | autoworkflowuid
38 |
39 | triggerid
40 | com.citelao.spotifious.playpause
41 |
42 | actionuid
43 | remote.alfred.runworkflow
44 | buttoniconid
45 | 3277740D-DEEE-4C08-8B1D-E7B962742990
46 | buttonlabel
47 | Play/Pause
48 | itemuid
49 | 60010EB8-95D0-414B-8732-2DEABA8D7D0E
50 |
51 |
52 | actionconfig
53 |
54 | autoworkflowuid
55 |
56 | triggerid
57 | com.citelao.spotifious.search
58 |
59 | actionuid
60 | remote.alfred.runworkflow
61 | buttoniconid
62 | B476C144-0CAF-4BC7-8609-BD97EBFCCF3F
63 | buttonlabel
64 | Search
65 | itemuid
66 | D1B96059-3586-42DD-8CBB-13A9AA19574D
67 |
68 |
69 | actionconfig
70 |
71 | autoworkflowuid
72 |
73 | triggerid
74 | com.citelao.spotifious.next
75 |
76 | actionuid
77 | remote.alfred.runworkflow
78 | buttoniconid
79 | D113E787-2890-471A-B3E8-126F562439DC
80 | buttonlabel
81 | Next Track
82 | itemuid
83 | FC6BDC0F-E523-4849-A970-1F8026B4B40B
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 | actionconfig
95 |
96 | autoworkflowuid
97 |
98 | triggerid
99 | com.citelao.spotifious.voldown
100 |
101 | actionuid
102 | remote.alfred.runworkflow
103 | buttoniconid
104 | 3A89A720-DC62-42BE-9FB6-2BFEA717BCE9
105 | buttonlabel
106 | Volume Down
107 | itemuid
108 | 45153A8C-C2BC-4DF3-85C3-DA09FD30EE7D
109 |
110 |
111 |
112 |
113 | actionconfig
114 |
115 | autoworkflowuid
116 |
117 | triggerid
118 | com.citelao.spotifious.volup
119 |
120 | actionuid
121 | remote.alfred.runworkflow
122 | buttoniconid
123 | 9AF9D5D1-FA74-4419-9B9C-C075BEC1AD75
124 | buttonlabel
125 | Volume Up
126 | itemuid
127 | FBE348BE-7495-4997-BC2C-4FA298E552FF
128 |
129 |
130 | subtitle
131 | Control Spotify
132 | title
133 | Spotifious
134 |
135 |
136 |
--------------------------------------------------------------------------------
/src/citelao/Spotifious/Menus/Setup.php:
--------------------------------------------------------------------------------
1 | alfred = $alfred;
18 |
19 | $this->wasOptOut = $this->alfred->options('spotify_app_opt_out') == 'true';
20 | $this->hasInstalledSpotify = $this->alfred->options('has_installed_spotify') == 'true';
21 |
22 | $this->countryCodeConfigured = !($this->alfred->options('country') == '');
23 | $this->applicationCreated = !($this->alfred->options('spotify_client_id') == '' || $this->alfred->options('spotify_secret') == '');
24 | $this->applicationLinked = !($this->alfred->options('spotify_access_token') == '' ||
25 | $this->alfred->options('spotify_refresh_token') == '' ||
26 | $this->alfred->options('spotify_access_token_expires') == '' ||
27 | $this->alfred->options('desired_scopes') != $this->alfred->options('registered_scopes'));
28 |
29 | $this->applicationPreviouslyLinked = !($this->alfred->options('spotify_access_token') == '');
30 | }
31 |
32 | public function output() {
33 | if($this->wasOptOut) {
34 | $results[] = array(
35 | 'title' => 'Sorry, but the Spotify API changed.',
36 | 'subtitle' => 'You must now create a Spotify application to use Spotifious.',
37 | 'icon' => array('path' => 'include/images/configuration.png'),
38 | 'valid' => false
39 | );
40 | } else {
41 | $results[] = array(
42 | 'title' => 'Welcome to Spotifious!',
43 | 'subtitle' => 'You need to configure a few more things before you can use Spotifious.',
44 | 'icon' => array('path' => 'include/images/configuration.png'),
45 | 'valid' => false
46 | );
47 | }
48 |
49 | $results[] = array(
50 | 'title' => '1. Download & install Spotify',
51 | 'subtitle' => 'Spotifious only works with the Spotify desktop app.',
52 | 'icon' => array('path' => $this->hasInstalledSpotify ? 'include/images/checked.png' : 'include/images/unchecked.png'),
53 | 'arg' => '{"action":"command", "options": {"command": "open https://www.spotify.com/us/download/mac/"}}',
54 | );
55 |
56 | $results[] = array(
57 | 'title' => '2. Set your country code',
58 | 'subtitle' => 'Choosing the correct country code makes sure you can play songs you select.',
59 | 'icon' => array('path' => $this->countryCodeConfigured ? 'include/images/checked.png' : 'include/images/unchecked.png'),
60 | 'autocomplete' => 'Country Code ⟩',
61 | 'valid' => false
62 | );
63 |
64 | $results[] = array(
65 | 'title' => '3. Create a Spotify application',
66 | 'subtitle' => 'Set up a Spotify application so you can search playlists!',
67 | 'icon' => array('path' => $this->applicationCreated ? 'include/images/checked.png' : 'include/images/unchecked.png'),
68 | 'arg' => 'appsetup⟩'
69 | );
70 |
71 | if($this->applicationPreviouslyLinked) {
72 | $results[] = array(
73 | 'title' => '4. Relink your Spotify application',
74 | 'subtitle' => "We've added new features to Spotifious, but you need to login again to use them.",
75 | 'icon' => array('path' => $this->applicationCreated ? $this->applicationLinked ? 'include/images/checked.png' : 'include/images/unchecked.png' : 'include/images/disabled.png'),
76 | 'arg' => 'applink⟩',
77 | 'valid' => $this->applicationCreated ? true : false
78 | );
79 |
80 | } else {
81 | $results[] = array(
82 | 'title' => '4. Link your Spotify application',
83 | 'subtitle' => 'Connect your Spotify application to Spotifious to search your playlists.',
84 | 'icon' => array('path' => $this->applicationCreated ? $this->applicationLinked ? 'include/images/checked.png' : 'include/images/unchecked.png' : 'include/images/disabled.png'),
85 | 'arg' => 'applink⟩',
86 | 'valid' => $this->applicationCreated ? true : false
87 | );
88 | }
89 |
90 | $results[] = array(
91 | 'title' => 'You can access settings easily.',
92 | 'subtitle' => 'Type `s` from the main menu.',
93 | 'icon' => array('path' => 'include/images/info.png'),
94 | 'valid' => false
95 | );
96 |
97 | return $results;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/citelao/Spotifious/Menus/Main.php:
--------------------------------------------------------------------------------
1 | now();
17 |
18 | $this->currentTrack = $current[0];
19 | $this->currentAlbum = $current[1];
20 | $this->currentArtist = $current[2];
21 | $this->currentURL = $current[3];
22 | $this->currentStatus = ($current[4] == 'playing') ? "include/images/paused.png" : "include/images/playing.png";
23 | }
24 |
25 | public function output() {
26 | $results[0] = array(
27 | 'title' => "$this->currentTrack",
28 | 'subtitle' => "$this->currentAlbum by $this->currentArtist",
29 | 'arg' => "playpause⟩",
30 | 'copy' => $this->currentURL,
31 | 'icon' => array(
32 | 'path' => $this->currentStatus
33 | ),
34 | // 'mods' => array(
35 | // 'alt' => array(
36 | // 'subtitle' => "Browse to artist ($this->currentArtist)...",
37 | // 'arg' => '{"action": "spotifious", "options": { "command": "artist:' . $this->currentArtist . '" }}'
38 | // ),
39 | // 'ctrl' => array(
40 | // 'subtitle' => "Browse to album ($this->currentAlbum)..."
41 | // ),
42 | // 'cmd' => array(
43 | // 'subtitle' => 'Queue this song'
44 | // ),
45 | // )
46 | );
47 |
48 | $results[1] = array(
49 | 'title' => "$this->currentAlbum",
50 | 'subtitle' => "More from this album...",
51 | 'autocomplete' => "artist:$this->currentArtist album:$this->currentAlbum", // TODO change to albumdetail
52 | 'copy' => "$this->currentAlbum", // TODO change to albumdetail
53 | 'valid' => false,
54 | 'icon' => array(
55 | 'path' => 'include/images/album.png'
56 | ),
57 | // 'mods' => array(
58 | // 'alt' => array(
59 | // 'subtitle' => "Browse to artist ($this->currentArtist)..."
60 | // ),
61 | // 'cmd' => array(
62 | // 'subtitle' => 'Queue this album'
63 | // ),
64 | // )
65 | );
66 |
67 | $results[2] = array(
68 | 'title' => "$this->currentArtist",
69 | 'subtitle' => "More by this artist...",
70 | 'autocomplete' => "artist:$this->currentArtist", // TODO change to artistdetail
71 | 'copy' => $this->currentArtist, // TODO change to artistdetail
72 | 'valid' => false,
73 | 'icon' => array('path' => 'include/images/artist.png')
74 | );
75 |
76 | $results[3] = array(
77 | 'title' => "Search for music...",
78 | 'subtitle' => "Begin typing to search",
79 | 'valid' => false,
80 | 'icon' => array('path' => "include/images/search.png"),
81 | 'mods' => array(
82 | 'ctrl' => array(
83 | 'valid' => true,
84 | 'subtitle' => 'Open controls...',
85 | 'arg' => '{"action": "spotifious", "options": { "command": "c" }}',
86 | 'variables' => array(
87 | 'keepalive' => 'true'
88 | )
89 | ),
90 | 'cmd' => array(
91 | 'valid' => true,
92 | 'subtitle' => 'Open settings...',
93 | 'arg' => '{"action": "spotifious", "options": { "command": "s" }}',
94 | 'icon' => array('path' => "include/images/configuration.png"),
95 | 'variables' => array(
96 | 'keepalive' => 'true'
97 | )
98 | ),
99 | 'shift' => array(
100 | 'valid' => true,
101 | 'subtitle' => 'Activate Spotify',
102 | 'arg' => '{"action": "applescript", "options": { "application": "Spotify", "command": "activate" }}'
103 | )
104 | )
105 | );
106 |
107 | // Overrides for no track
108 | if($this->currentTrack == "No track playing") {
109 | $results[0]['subtitle'] = "";
110 |
111 | $results[1]['subtitle'] = "";
112 | $results[1]['autocomplete'] = "";
113 | $results[1]['copy'] = "";
114 |
115 | $results[2]['subtitle'] = "";
116 | $results[2]['autocomplete'] = "";
117 | $results[2]['copy'] = "";
118 | }
119 |
120 | return $results;
121 | }
122 |
123 | protected function now() {
124 | $spotQuery = new ApplicationApplescript('Spotify', 'return name of current track & "✂" & album of current track & "✂" & artist of current track & "✂" & spotify url of current track & "✂" & player state');
125 |
126 | $data = $spotQuery->run();
127 |
128 | $array = explode("✂", $data);
129 |
130 | if($array[0] == "") {
131 | $array[0] = "No track playing";
132 | $array[1] = "No album";
133 | $array[2] = "No artist";
134 | $array[3] = "";
135 | $array[4] = "paused";
136 | }
137 |
138 | return $array;
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog #
2 |
3 | ## v0.13.9 ##
4 | - Fixed: Deprecation error thrown in newer versions of PHP for unparenthesized
5 | left-associative ternary operator in `Spotifious.php`
6 | - Changed: Updated `spotifious.sublime-project` configuration to use relative
7 | path of the workflow directory
8 |
9 | ## v0.13.8 ##
10 | - Fixed: Support new Spotify playlist syntax (without `user:` prefix).
11 |
12 | ## v0.13.7 ##
13 | - Fixed: Bump up the Spotify API version so Spotifious works again
14 |
15 | ## v0.13.6 ##
16 | - Added: Version checker added to buildscript so I stop shipping incorrect versions numbers
17 | - Fixed: Addressed a syntax error that prevented Spotify from returning properly
18 |
19 | ## v0.13.5 ##
20 | - Added: Now checks for installed Spotify on startup
21 | - Added: New hotkeys for playing albums and artists directly from search
22 | - Changed: @philihp clarrified where build location is
23 |
24 | ## v0.13.4 ##
25 | - Fixed: Compilation albums now have an icon
26 | - Fixed: Can now add playlists with `\` in their titles
27 | - Fixed: Removed light-colored edge of some icons
28 |
29 | ## v0.13.3.2 ##
30 | - Fixed: Alfred variables are now more reliably passed to the workflow. Should
31 | fix several people's configuration bugs
32 |
33 | ## v0.13.3.1 ##
34 | - Added: there's a small install guide inside the Workflow menu
35 | - Fixed: Small playlists now appear correctly
36 | - Fixed: Playlist cache correctly on first run (thanks, @chrsblck!)
37 |
38 | ## v0.13.3 ##
39 | - Added: Alfred-native hotkey activation means a huge speedboost to hotkeys
40 | - Added: Cache your playlists to add them to search
41 | - Fixed: API search now uses Authentication all the time
42 | - Changed: Do not source control vendor/
43 | - Changed: Use a newer version of JWilsson's API.
44 | - Removed: Opt-out does not work with the Spotify API anymore, so it's removed (thanks @atabbott!)
45 |
46 | ## v0.13.2.1 ##
47 | - Fixed: Setup bug where Spotifious expects client ID before it exists
48 |
49 | ## v0.13.2 ##
50 | - Added: Use JSON output when possible
51 | - Added: Exceptions appear in debugger for actions
52 | - Changed: Search uses API with auth
53 | - Changed: Album pages use API with auth
54 | - Changed: Album pages load faster
55 | - Changed: Info loads much faster (but may break in edge cases)
56 | - Changed: Popularity boxes look pretty now (thanks, danielma!)
57 | - Changed: Build size is much smaller
58 | - Fixed: Single-digit seconds are now prefixed with a 0
59 | - Fixed: Add Homebrew to path if needed (thanks, mieubrisse!)
60 | - Fixed: Setup in Alfred 3 works
61 | - Fixed: The UK shows up at the top of country selection
62 |
63 | ## v0.13.1 ##
64 | - Fixed: Alfred 3 compatibility
65 | - Fixed: America appears first in country selection
66 |
67 | ## v0.13 ##
68 | - Added: preliminary scaffolding for web API (playlists, starring, all of it is coming)
69 | - Added: new settings menu.
70 | - Added: you can now *disable* track notifications
71 | - Fixed: track notifications say the right thing in Spotify 1.0
72 | - Fixed: control panel stays open properly
73 | - Fixed: weird looping volume changes
74 |
75 | ## v0.12 ##
76 | - Added: compatiblity with Alfred Remote
77 |
78 | ## v0.11.2 ##
79 | - Added: opt-out feature for location information
80 | - Changed: better installation guide
81 |
82 | ## v0.11.1 ##
83 | - Fixed: non-responsiveness after some requests to Spotify
84 |
85 | ## v0.11 ##
86 | - Added: hotkeys, configurable via Alfred
87 | - Added: notifications
88 | - Added: controls menu (type `c`)
89 | - Changed: using Spotify's faster web API!
90 | - Changed: non-unicode popularity glyphs
91 |
92 | ## v0.10 ##
93 | - Added: Settings menu (accessible using "s")
94 | - Added: Location-based filtering of search results
95 | - Changed: Actions route through `action.php` now, instead of direct Applescript execution.
96 |
97 | ## v0.9.4 ##
98 | - Changed: more compatibility changes for PHP v5.3. These did not get included in v0.9.3.
99 |
100 | ## v0.9.3 ##
101 | - Changed: more compatibility changes for PHP v5.3
102 |
103 | ## v0.9.2 ##
104 | - Changed: compatibility changes for PHP v5.3
105 |
106 | ## v0.9.1 ##
107 | - Changed: prevent breakage if no track playing.
108 |
109 | ## v0.9 ##
110 | - Added: created changelog
111 | - Added: error reporting
112 | - Added: context-based searching
113 | - Added: README in Alfred.
114 | - Added: Packal support
115 | - Changed: restructured to use Composer
116 | - Changed: divided Alfred output code into `OhAlfred`
117 | - Changed: uses more extensible menu system.
118 | - Removed: Alleyoop support
119 | - Removed: album art (as I never use it and it was becoming increasingly untested)
120 |
121 | ## v0.8 ##
122 | - Added: new icons; set them as default
123 |
124 | ## v0.7 ##
125 | - Added: artist- and album-specific menus
126 | - Changed: general code clean-up
127 |
128 | ## v0.6 ##
129 | - Added: Alleyoop support
130 |
131 | ## v0.5 ##
132 | - Added: menu system
133 |
134 | ## v0.1 ##
135 | - Added: initial search system
136 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Spotifious #
2 | ## a natural Spotify controller for Alfred ##
3 |
4 | 
5 |
6 | So, you've heard of **Spotifious**, eh? *A natural Spotify controller for
7 | Alfred*? Searches Spotify, controls your music, generally rocks?
8 |
9 | It's built off a lot of other people's work—
10 | [PHPFunk](https://github.com/phpfunk/alfred-spotify-controls) and
11 | [David Ferguson](http://jdfwarrior.tumblr.com/) especially— and tries to match
12 | the functionality of Alfred's integrated iTunes player. It's not perfect, but it does the job. And I think it's pretty cool.
13 |
14 | ## Features ##
15 |
16 | A quick rundown of its coolness:
17 |
18 | ### A controller, not a workflow ###
19 |
20 | Spotifious just *feels* fun to use, like a real plugin for Alfred, not a workflow. It
21 | can do in-depth, album-scouring searches and gives you useful information even if something goes wrong. Filter by artist or album, search for specific tracks or just plain see what's playing. It's all there.
22 |
23 | Not unlike the iTunes Mini Player.
24 |
25 | ### Alfred-like ###
26 |
27 | Spotifious is also especially Alfred-like. Just start typing to scour Spotify's servers for music. Browse through artists and albums like nobody's business and drill down through Queen's huge anthologies with a snappy context-based search and filtering. Your frequent searches rise to the top, and your controls pop up with the letter `c`.
28 |
29 | ### Smart ###
30 |
31 | Spotifious uses Packal to make sure you always have the latest version. It gives you a heads-up about what's going on with Spotify *the moment you start it*. And don't worry about errors— we'll handle those. All you have to do is choose how you want to pop it up on screen.
32 |
33 | ## Download & Install ##
34 |
35 | Latest version: [v0.13.9](https://github.com/citelao/Spotify-for-Alfred/archive/master.zip) | Latest dev build: [v0.13.9](https://github.com/citelao/Spotify-for-Alfred/archive/dev.zip)
36 |
37 | An in-depth [installation guide](http://ben.stolovitz.com/Spotify-for-Alfred/download/) is available on the Spotifious website.
38 |
39 | 1. [Download](https://github.com/citelao/Spotify-for-Alfred/archive/master.zip)
40 | this repository.
41 | 2. Open `Spotifious.alfredworkflow` in the `dist` folder by double-clicking it or dragging it into
42 | Alfred.
43 | 3. Double-click the first thingy marked `Hotkey`.
44 | 4. Click the textfield labeled `Hotkey` and press `^⌘⏎`.
45 | 5. Click `Save` to store the binding.
46 | 6. Bind the other hotkeys as you wish. Their actions are visible in the `Text` field, under `Argument`.
47 | 7. Pop up Spotifious with the key command you set— I would have `^⌘⏎`.
48 | 8. Follow the instructions on the set-up menu.
49 | 1. You'll be asked to choose your country (for better search results— you can opt out).
50 | 2. You'll create a Spotify application (or not, you can opt out).
51 | 3. You'll link the new app to Spotifious.
52 | 10. Continue on with your merry day— but now with Spotifious.
53 |
54 | 
55 |
56 | ## How to Use ##
57 |
58 | So let's assume you've downloaded and installed the workflow. Now what?
59 |
60 | 1. Press `^⌘⏎`.
61 |
62 | Good! You should briefly see a loading entry, then the main menu:
63 |
64 | 
65 | 
66 |
67 | You can action the song title (press `⏎`) to play or pause the song, action
68 | the album or artist to search for that album or artist, or just start typing to
69 | search for cool music.
70 |
71 | 
72 |
73 | Once you've searched for something, you can continue to browse albums and arists through Spotifious. Actioning an artist will bring up a list of their albums, and actioning an album will bring up the track list.
74 |
75 | If you're looking through a really long album or a particularly prodigious artist, you can also narrow your search by typing a little more— Spotifious will search for results within the album or artist.
76 |
77 | **Note:** You can always leave a menu and go back just by pressing `⌫`.
78 |
79 | You can access a small controls menu by typing `c` at the main menu.
80 |
81 | 
82 |
83 | You can change your settings by typing `s` at the main menu.
84 |
85 | 
86 |
87 | ## Acknowledgements ##
88 |
89 | A lot of people helped out on this:
90 |
91 | - **vdesabou** and I trade ideas a ton
92 | - **PHPfunk** and his PHP Alfred workflow inspired much of `OhAlfred`
93 | - **David Ferguson** helped with some iffy Alfred glitches
94 | - **jwilsson**'s [Spotify PHP web API](http://jwilsson.github.io/spotify-web-api-php/).
95 | - a slightly modified version of [Entypo](http://www.entypo.com/) icon font face for icons.
96 | - and many other people mentioned in the code.
97 |
98 | Thanks to everyone who helped, and thanks to everyone who uses my work!
99 | Oh, and thanks for reading this whole README. I hope you enjoy Spotifious!
100 |
101 | ## Development ##
102 |
103 | You must install dependencies manually. Install
104 | [Composer](https://getcomposer.org/) and then run `make init`. Then you can
105 | proceed with development! (if you do not want to do a global install, run
106 | `php composer.phar install` from this directory)
107 |
108 | Command line development for Alfred 3 is currently not working, since we
109 | depend on several environment variables. Eventually, if you are developing for
110 | Alfred version 3 and want to run these files from
111 | the command line (`php -f main.php -- "args"`), you will need to run in debug
112 | mode : `DEBUG=true php -f main.php -- "args"`.
113 |
114 | ### Deployment ###
115 |
116 | 1. Bump version here
117 | 2. Update `CHANGELOG`
118 | 3. Update `.alfredworkflow` version
119 | 4. Update Alfred forum
120 | 5. Update Packal
121 | 6. Update GitHub Pages site
122 |
123 | ## TODO ##
124 |
125 | - Allow `⎇`, `^`, and `⌘` to function as modifiers (Open in Spotify, other things?).
126 | - More in-depth Spotify control (radio, queuing, playlists)
127 | - Make main menu links go directly to menus.
128 |
129 | ## Changelog ##
130 |
131 | This project uses pseudo-SemVer. It will use SemVer when we hit v1.0.0.
132 |
133 | See `CHANGELOG.md`.
134 |
135 | ## License ##
136 |
137 | This code is licensed as written in the LICENSE.md file.
138 |
--------------------------------------------------------------------------------
/include/setup/index.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | Spotifious Setup
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
Spotifious Setup
13 |
14 | Hi! In order to fully use Spotifious (search playlists, star
15 | tracks, etc.), you need to generate a Spotify API key by
16 | creating a Spotify app.
17 |
18 |
19 | NOTE: this server will automatically close in 5
20 | minutes. If you get errors like "page not found," just reopen
21 | Spotifious.
22 |
23 |
24 |
25 |
26 |
Instructions
27 |
28 | Setting up the app is not very hard, but it has a couple
29 | specific steps. Just follow these instructions, and you'll be
30 | fine!
31 |
32 |
33 | If you're worried about privacy,
34 | here’s what I have to say.
35 |
36 |
37 | If you already know how to make a Spotify app, you can enter the
38 | ID and secret here. Please set the callback
39 | URL correctly, though (step 8).
40 |
46 | Click "Create an app."
47 |
48 |
51 | The 'Create an app' button
52 |
53 |
54 |
55 | Enter an application name and description. They can be anything you want.
56 |
57 |
60 | This data will do nicely.
61 |
62 |
63 |
Click "Create"!
64 |
65 | Note the "Client ID" and "Client Secret." You'll enter them
66 | later.
67 |
68 |
Click the "Add URI" button to add a new Redirect URI.
69 |
70 | Type http://localhost:11114/callback.php and click "Add"
71 |
72 |
75 | Just type it in!
76 |
77 |
78 |
79 | Save those changes!
80 |
81 |
84 | The green one, please.
85 |
86 |
87 |
Copy and paste your Client ID and Client Secret below:
88 |
106 |
107 |
108 |
111 | This will not be a blur on your screen.
112 |
113 |
114 |
Go back to Spotifious and finish your setup!
115 |
116 |
117 |
118 |
119 |
Privacy
120 |
121 | Spotifious merely reads and writes your keys from and to your
122 | hard drive. They are never used in a way that could compromise
123 | their integrity other than in the Spotify query, as required by
124 | the API. Spotifious itself never sends the data
125 | anywhere else, for any reason, at any time.
126 |
127 |
128 | You do not need to set up a Spotify app to use
129 | Spotifious, but you lose a lot of functionality. If you would
130 | prefer not to use this part of Spotify,
131 | click here.
133 |