├── .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