├── .rubocop.yml
├── .travis.yml
├── Gemfile
├── Gemfile.lock
├── README.md
├── app.rb
├── maestro.png
├── roboto.png
├── shpotify.cfg
├── spec
├── maestro_spec.rb
└── spotify_spec.rb
├── spotify.rb
└── spotify.sh
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | AllCops:
2 | TargetRubyVersion: 2.3
3 | Exclude:
4 | - "vendor/**/*"
5 | - "db/schema.rb"
6 | - "db/migrate/*"
7 | - "bin/*"
8 | - "lib/files/cucumber.rake"
9 | - "lib/tasks/*"
10 | - "config/initializers/*"
11 | - "Vagrantfile"
12 | - "Rakefile"
13 | - "tmp/*"
14 | UseCache: false
15 | Bundler/OrderedGems:
16 | Enabled: false
17 | Layout/TrailingBlankLines:
18 | Description: 'Checks trailing blank lines and final newline.'
19 | StyleGuide: '#newline-eof'
20 | Enabled: false
21 | Naming/AccessorMethodName:
22 | Description: Check the naming of accessor methods for get_/set_.
23 | Enabled: false
24 | Naming/FileName:
25 | Description: Use snake_case for source file names.
26 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#snake-case-files
27 | Enabled: false
28 | Exclude: []
29 | Naming/PredicateName:
30 | Description: Check the names of predicate methods.
31 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark
32 | Enabled: false
33 | NamePrefix:
34 | - is_
35 | - has_
36 | - have_
37 | NamePrefixBlacklist:
38 | - is_
39 | Exclude:
40 | - spec/**/*
41 | Style/CollectionMethods:
42 | Description: Preferred collection methods.
43 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#map-find-select-reduce-size
44 | Enabled: false
45 | PreferredMethods:
46 | collect: map
47 | collect!: map!
48 | find_all: select
49 | reduce: inject
50 | Layout/DotPosition:
51 | Description: Checks the position of the dot in multi-line method calls.
52 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains
53 | Enabled: true
54 | EnforcedStyle: leading
55 | SupportedStyles:
56 | - leading
57 | - trailing
58 | Style/AsciiComments:
59 | Enabled: false
60 | Style/Lambda:
61 | Enabled: false
62 | Style/GuardClause:
63 | Description: Check for conditionals that can be replaced with guard clauses
64 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals
65 | Enabled: false
66 | MinBodyLength: 1
67 | Style/IfUnlessModifier:
68 | Description: Favor modifier if/unless usage when you have a single-line body.
69 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier
70 | Enabled: false
71 | Style/OptionHash:
72 | Description: Don't use option hashes when you can use keyword arguments.
73 | Enabled: false
74 | Style/PercentLiteralDelimiters:
75 | Description: Use `%`-literal delimiters consistently
76 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#percent-literal-braces
77 | Enabled: false
78 | PreferredDelimiters:
79 | "%": "()"
80 | "%i": "()"
81 | "%q": "()"
82 | "%Q": "()"
83 | "%r": "{}"
84 | "%s": "()"
85 | "%w": "()"
86 | "%W": "()"
87 | "%x": "()"
88 | Style/RaiseArgs:
89 | Description: Checks the arguments passed to raise/fail.
90 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#exception-class-messages
91 | Enabled: false
92 | EnforcedStyle: exploded
93 | SupportedStyles:
94 | - compact
95 | - exploded
96 | Style/SignalException:
97 | Description: Checks for proper usage of fail and raise.
98 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#fail-method
99 | Enabled: false
100 | EnforcedStyle: semantic
101 | SupportedStyles:
102 | - only_raise
103 | - only_fail
104 | - semantic
105 | Style/SingleLineBlockParams:
106 | Description: Enforces the names of some block params.
107 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#reduce-blocks
108 | Enabled: false
109 | Methods:
110 | - reduce:
111 | - a
112 | - e
113 | - inject:
114 | - a
115 | - e
116 | Style/SingleLineMethods:
117 | Description: Avoid single-line methods.
118 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-single-line-methods
119 | Enabled: false
120 | AllowIfMethodIsEmpty: true
121 | Style/StringLiterals:
122 | Enabled: true
123 | EnforcedStyle: double_quotes
124 | Style/StringLiteralsInInterpolation:
125 | Description: Checks if uses of quotes inside expressions in interpolated strings
126 | match the configured preference.
127 | Enabled: false
128 | EnforcedStyle: single_quotes
129 | SupportedStyles:
130 | - double_quotes
131 | - single_quotes
132 | Style/TrailingCommaInArguments:
133 | Description: Checks for trailing comma in argument lists.
134 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas
135 | Enabled: false
136 | EnforcedStyleForMultiline: no_comma
137 | SupportedStylesForMultiline:
138 | - comma
139 | - consistent_comma
140 | - no_comma
141 | Style/TrailingCommaInArrayLiteral:
142 | Description: Checks for trailing comma in hash literals.
143 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas
144 | Enabled: false
145 | EnforcedStyleForMultiline: no_comma
146 | SupportedStylesForMultiline:
147 | - comma
148 | - consistent_comma
149 | - no_comma
150 | Metrics/AbcSize:
151 | Description: A calculated magnitude based on number of assignments, branches, and
152 | conditions.
153 | Enabled: false
154 | Max: 15
155 | Metrics/BlockLength:
156 | Enabled: false
157 | Metrics/ClassLength:
158 | Description: Avoid classes longer than 100 lines of code.
159 | Enabled: false
160 | CountComments: false
161 | Max: 100
162 | Metrics/ModuleLength:
163 | CountComments: false
164 | Max: 100
165 | Description: Avoid modules longer than 100 lines of code.
166 | Enabled: false
167 | Metrics/CyclomaticComplexity:
168 | Description: A complexity metric that is strongly correlated to the number of test
169 | cases needed to validate a method.
170 | Enabled: false
171 | Max: 6
172 | Metrics/MethodLength:
173 | Description: Avoid methods longer than 10 lines of code.
174 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#short-methods
175 | Enabled: false
176 | CountComments: false
177 | Max: 10
178 | Metrics/ParameterLists:
179 | Description: Avoid parameter lists longer than three or four parameters.
180 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#too-many-params
181 | Enabled: false
182 | Max: 5
183 | CountKeywordArgs: true
184 | Metrics/PerceivedComplexity:
185 | Description: A complexity metric geared towards measuring complexity for a human
186 | reader.
187 | Enabled: false
188 | Max: 7
189 | Metrics/LineLength:
190 | Max: 100
191 | # To make it possible to copy or click on URIs in the code, we allow lines
192 | # contaning a URI to be longer than Max.
193 | IgnoredPatterns: ['\A\s*#']
194 | AllowURI: true
195 | URISchemes:
196 | - http
197 | - https
198 | Lint/AssignmentInCondition:
199 | Description: Don't use assignment in conditions.
200 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#safe-assignment-in-condition
201 | Enabled: false
202 | AllowSafeAssignment: true
203 | Style/BlockComments:
204 | Enabled: false
205 | Style/ClassAndModuleChildren:
206 | Enabled: false
207 | Style/InlineComment:
208 | Description: Avoid inline comments.
209 | Enabled: false
210 | Style/Alias:
211 | Description: Use alias_method instead of alias.
212 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#alias-method
213 | Enabled: false
214 | Style/Documentation:
215 | Description: Document classes and non-namespace modules.
216 | Enabled: false
217 | Style/DoubleNegation:
218 | Description: Checks for uses of double negation (!!).
219 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-bang-bang
220 | Enabled: false
221 | Style/EachWithObject:
222 | Description: Prefer `each_with_object` over `inject` or `reduce`.
223 | Enabled: false
224 | Style/EmptyLiteral:
225 | Description: Prefer literals to Array.new/Hash.new/String.new.
226 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#literal-array-hash
227 | Enabled: false
228 | Style/ModuleFunction:
229 | Description: Checks for usage of `extend self` in modules.
230 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#module-function
231 | Enabled: false
232 | Style/OneLineConditional:
233 | Description: Favor the ternary operator(?:) over if/then/else/end constructs.
234 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#ternary-operator
235 | Enabled: false
236 | Style/PerlBackrefs:
237 | Description: Avoid Perl-style regex back references.
238 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers
239 | Enabled: false
240 | Style/RegexpLiteral:
241 | EnforcedStyle: mixed
242 | AllowInnerSlashes: false
243 | Style/Send:
244 | Description: Prefer `Object#__send__` or `Object#public_send` to `send`, as `send`
245 | may overlap with existing methods.
246 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#prefer-public-send
247 | Enabled: false
248 | Style/SpecialGlobalVars:
249 | Description: Avoid Perl-style global variables.
250 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-cryptic-perlisms
251 | Enabled: false
252 | Style/VariableInterpolation:
253 | Description: Don't interpolate global, instance and class variables directly in
254 | strings.
255 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#curlies-interpolate
256 | Enabled: false
257 | Style/WhenThen:
258 | Description: Use when x then ... for one-line cases.
259 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#one-line-cases
260 | Enabled: false
261 | Style/FrozenStringLiteralComment:
262 | Enabled: false
263 | Style/FormatStringToken:
264 | Enabled: false
265 | Lint/EachWithObjectArgument:
266 | Description: Check for immutable argument given to each_with_object.
267 | Enabled: true
268 | Lint/HandleExceptions:
269 | Description: Don't suppress exception.
270 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions
271 | Enabled: false
272 | Lint/LiteralInInterpolation:
273 | Description: Checks for literals used in interpolation.
274 | Enabled: false
275 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | script:
3 | - bundle exec rubocop
4 | - bundle exec rspec
5 | - bundle exec bundler-audit
6 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gem "sinatra"
4 |
5 | group :development, :test do
6 | gem "pry-byebug"
7 | gem "rubocop"
8 | end
9 |
10 | group :test do
11 | gem "rspec"
12 | gem "rack-test"
13 | gem "bundler-audit"
14 | end
15 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | ast (2.4.0)
5 | bundler-audit (0.6.0)
6 | bundler (~> 1.2)
7 | thor (~> 0.18)
8 | byebug (9.1.0)
9 | coderay (1.1.2)
10 | diff-lcs (1.3)
11 | method_source (0.9.0)
12 | mustermann (1.0.2)
13 | parallel (1.12.1)
14 | parser (2.5.0.3)
15 | ast (~> 2.4.0)
16 | powerpack (0.1.1)
17 | pry (0.11.3)
18 | coderay (~> 1.1.0)
19 | method_source (~> 0.9.0)
20 | pry-byebug (3.5.0)
21 | byebug (~> 9.1)
22 | pry (~> 0.10)
23 | rack (2.0.5)
24 | rack-protection (2.0.3)
25 | rack
26 | rack-test (0.8.3)
27 | rack (>= 1.0, < 3)
28 | rainbow (3.0.0)
29 | rspec (3.7.0)
30 | rspec-core (~> 3.7.0)
31 | rspec-expectations (~> 3.7.0)
32 | rspec-mocks (~> 3.7.0)
33 | rspec-core (3.7.1)
34 | rspec-support (~> 3.7.0)
35 | rspec-expectations (3.7.0)
36 | diff-lcs (>= 1.2.0, < 2.0)
37 | rspec-support (~> 3.7.0)
38 | rspec-mocks (3.7.0)
39 | diff-lcs (>= 1.2.0, < 2.0)
40 | rspec-support (~> 3.7.0)
41 | rspec-support (3.7.1)
42 | rubocop (0.52.1)
43 | parallel (~> 1.10)
44 | parser (>= 2.4.0.2, < 3.0)
45 | powerpack (~> 0.1)
46 | rainbow (>= 2.2.2, < 4.0)
47 | ruby-progressbar (~> 1.7)
48 | unicode-display_width (~> 1.0, >= 1.0.1)
49 | ruby-progressbar (1.9.0)
50 | sinatra (2.0.3)
51 | mustermann (~> 1.0)
52 | rack (~> 2.0)
53 | rack-protection (= 2.0.3)
54 | tilt (~> 2.0)
55 | thor (0.20.0)
56 | tilt (2.0.8)
57 | unicode-display_width (1.3.0)
58 |
59 | PLATFORMS
60 | ruby
61 |
62 | DEPENDENCIES
63 | bundler-audit
64 | pry-byebug
65 | rack-test
66 | rspec
67 | rubocop
68 | sinatra
69 |
70 | BUNDLED WITH
71 | 1.16.1
72 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Maestro
2 |
3 |
4 |
5 | A Slack-Powered music bot for Spotify, using [Sinatra](http://sinatrarb.com/) and [shpotify](https://github.com/hnarayanan/shpotify).
6 |
7 | ## Getting Started
8 |
9 | The first thing you'll need to run Maestro is a computer to run it from. It'll need to be running
10 | **OSX, with [homebrew](https://brew.sh/) installed**. It'll also need **Ruby**, which comes
11 | pre-installed with OSX, so you should be good there. Lastly, but most importantly, this computer
12 | is the one that will be playing the music, so it'll either need
13 | **good speakers, or a headphone jack** to plug a sound system into.
14 |
15 | Once you've got a computer to run it on, you can install Maestro by running the following commands
16 | in a terminal:
17 |
18 | ```sh
19 | brew install shpotify
20 |
21 | git clone https://github.com/smashingboxes/maestro.git
22 | cd maestro
23 | bundle install
24 | ```
25 |
26 | ### Connecting to Spotify's api
27 |
28 | shpotify needs to connect to Spotify’s API in order to find music by
29 | name. It is very likely you want this feature!
30 |
31 | To get this to work, you first need to sign up (or into) Spotify’s
32 | developer site and [create an *Application*][spotify-dev]. Once you’ve
33 | done so, you can find its `Client ID` and `Client Secret` values and
34 | enter them into your shpotify config file at `${HOME}/.shpotify.cfg`.
35 |
36 | Be sure to quote your values and don’t add any extra spaces. When
37 | done, it should look like the following (but with your own values):
38 |
39 | ```sh
40 | CLIENT_ID="abc01de2fghijk345lmnop"
41 | CLIENT_SECRET="qr6stu789vwxyz"
42 | ```
43 |
44 | ## Usage
45 |
46 | ```sh
47 | ruby app.rb
48 | ```
49 |
50 | This will start up Maestro on port 4567. To use it with Slack, you'll want to configure an external
51 | URL (see "Obtaining an external URL" below), and set up a slash command (see "Creating a slash
52 | command" below).
53 |
54 | Once that's done, you can interact with it via any command
55 | [shpotify](https://github.com/hnarayanan/shpotify) supports. Here are the most common commands:
56 |
57 | ```
58 | /maestro play
59 | /maestro next
60 | /maestro vol up
61 | /maestro vol down
62 | /maestro status
63 | ```
64 |
65 | ## Configuring Slack
66 |
67 | ### Obtaining an external URL
68 |
69 | There are many ways to get an external URL or static IP. The easiest is to use [ngrok]():
70 |
71 | ```sh
72 | brew cask install ngrok
73 | ngrok http 4567
74 | ```
75 |
76 | In the output, ngrok will provide you with an external url such as `http://71ca42f4.ngrok.io`,
77 | you'll need that for the next section.
78 |
79 | **NOTE: If ngrok gets restarted (during a computer restart, for example), a new URL will be
80 | generated. You'll need to update your slash command (created in the next section) with the
81 | new one.**
82 |
83 | ### Creating a slash command
84 |
85 | To create a slash command in Slack, go to https://slack.com/apps/A0F82E8CA-slash-commands, click "Add Configuration", and fill in the following settings:
86 |
87 | - Command: "/maestro"
88 | - URL: Your external URL (from previous section), followed by `/maestro`. So, for example, if your ngrok url is `http://71ca42f4.ngrok.io`, you'd enter `http://71ca42f4.ngrok.io/maestro`
89 | - Method: POST
90 | - Customize Name: Maestro
91 | - Customize Icon: Any icon you'd like. Feel free to use [ours](./maestro.png)
92 | - Autocomplete help text:
93 | - Check "Show this command in the autocomplete list"
94 | - Description: https://github.com/hnarayanan/shpotify
95 | - Usage hint: [shpotify command]
96 |
--------------------------------------------------------------------------------
/app.rb:
--------------------------------------------------------------------------------
1 | require "sinatra"
2 | require "json"
3 | require "./spotify.rb"
4 |
5 | HELP_TEXT = <<~HELP_TEXT.freeze
6 | Usage:
7 | `/maestro play` -- Resumes playback where Spotify last left off.
8 | `/maestro play ` -- Finds a song by name and plays it.
9 | `/maestro play album ` -- Finds an album by name and plays it.
10 | `/maestro play artist ` -- Finds an artist by name and plays it.
11 | `/maestro play list ` -- Finds a playlist by name and plays it.
12 | `/maestro play uri ` -- Play songs from specific uri.
13 |
14 | `/maestro next` -- Skips to the next song in a playlist.
15 | `/maestro prev` -- Returns to the previous song in a playlist.
16 | `/maestro replay` -- Replays the current track from the begining.
17 | `/maestro pos