├── README.md ├── config-example.json ├── package.json ├── .gitignore └── index.iced /README.md: -------------------------------------------------------------------------------- 1 | # Slack Integration for Sonos 2 | 3 | Nice and simple - it'll post the currently playing song to Slack. 4 | 5 | 1. Install node.js 6 | 1. Create a new incoming Slack webhook at https://yoursite.slack.com/services/new/incoming-webhook and record the Webhook URL. 7 | 1. In the Sonos application's About screen, record the Sonos box's IP address. 8 | 1. Copy `config-example.json` to `config.json` and edit in your webhook URL and Sonos IP address. 9 | 1. Run `npm install` to install dependencies. 10 | 1. Run `./index.iced`. -------------------------------------------------------------------------------- /config-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "slackIncomingWebHookUrl": "https://mysite.slack.com/services/hooks/incoming-webhook?token=seofiu94sudjrkjes93w", 3 | "sonosIpAddress": "192.168.1.105", // or "detect" to detect your Sonos IP address 4 | "username": "SonosBot", // or null to use the name set up on Slack 5 | "icon_emoji": ":speaker:", // or null to use the icon/image set up on Slack 6 | "favorites": { 7 | // notify these people when their favorite artist comes on 8 | "@bobby": ["Taylor Swift", "Miley Cyrus"] 9 | } 10 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sonoslack", 3 | "version": "1.0.0", 4 | "description": "Slack integration for Sonos Audio", 5 | "main": "index.iced", 6 | "dependencies": { 7 | "imgur": "^0.1.5", 8 | "randomcolor": "^0.1.1", 9 | "request": "^2.44.0", 10 | "sonos": "^0.6.1" 11 | }, 12 | "devDependencies": { 13 | "iced-coffee-script": "^1.7.1-g" 14 | }, 15 | "scripts": { 16 | "test": "echo \"Error: no test specified\" && exit 1" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "http://github.com/ewiner/sonoslack" 21 | }, 22 | "keywords": [ 23 | "sonos", 24 | "slack", 25 | "chat", 26 | "music" 27 | ], 28 | "author": "Eric Winer", 29 | "license": "ISC", 30 | "bugs": { 31 | "url": "https://github.com/ewiner/sonoslack/issues" 32 | }, 33 | "homepage": "https://github.com/ewiner/sonoslack" 34 | } 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /config.json 2 | 3 | # Logs 4 | logs 5 | *.log 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | 21 | # Compiled binary addons (http://nodejs.org/api/addons.html) 22 | build/Release 23 | 24 | # Dependency directory 25 | node_modules 26 | 27 | ## Directory-based project format: 28 | .idea/ 29 | 30 | # Users Environment Variables 31 | .lock-wscript 32 | 33 | .DS_Store 34 | .AppleDouble 35 | .LSOverride 36 | 37 | # Icon must end with two \r 38 | Icon 39 | 40 | # Album art temp 41 | art.png 42 | 43 | # Thumbnails 44 | ._* 45 | 46 | # Files that might appear on external disk 47 | .Spotlight-V100 48 | .Trashes 49 | 50 | # Directories potentially created on remote AFP share 51 | .AppleDB 52 | .AppleDesktop 53 | Network Trash Folder 54 | Temporary Items 55 | .apdisk 56 | -------------------------------------------------------------------------------- /index.iced: -------------------------------------------------------------------------------- 1 | #!./node_modules/.bin/iced 2 | 3 | sonos = require('sonos') 4 | request = require("request") 5 | randomColor = require("randomcolor") 6 | fs = require('fs') 7 | request = require('request') 8 | imgur = require('imgur') 9 | 10 | download = (uri, filename, callback) -> 11 | f = (err, res, body) -> 12 | request(uri).pipe(fs.createWriteStream(filename)).on('close', callback) 13 | request.head(uri, f) 14 | 15 | 16 | config = require('./config.json') 17 | 18 | lastArtist = null 19 | lastTitle = null 20 | isBroke = false 21 | 22 | mySonos = null 23 | if config.sonosIpAddress == "detect" 24 | await(sonos.search(defer(mySonos))) 25 | else 26 | mySonos = new sonos.Sonos(config.sonosIpAddress) 27 | 28 | postToChat = (message) -> 29 | await(request.post({ 30 | uri: config.slackIncomingWebHookUrl 31 | body: JSON.stringify(message) 32 | }, defer(err))) 33 | if err? 34 | console.error("Error posting to chat: #{err}") 35 | 36 | 37 | checkSong = -> 38 | await(mySonos.currentTrack(defer(err, track))) 39 | if err? or not track? 40 | if not isBroke 41 | isBroke = true 42 | console.error("Error connecting to Sonos: #{err}") 43 | postToChat(text: "Error: #{err}") 44 | else if track.artist != lastArtist or track.title != lastTitle 45 | postSong(track) 46 | 47 | postSong = (track) -> 48 | isBroke = false 49 | albumArtURL = track.albumArtURL 50 | lastArtist = track.artist 51 | lastTitle = track.title 52 | oneLiner = "#{track.artist} - #{track.title}" 53 | 54 | console.log(oneLiner) 55 | 56 | await(download(albumArtURL, "./art.png", defer())) 57 | await(imgur.uploadFile("./art.png").then(defer(imgurData))) 58 | 59 | postOptions = 60 | link_names: 1 61 | attachments: [ 62 | { 63 | fallback: oneLiner 64 | color: randomColor() 65 | fields: [ 66 | { 67 | title: track.artist 68 | value: track.title 69 | short: true 70 | } 71 | ] 72 | image_url: imgurData.data.link 73 | } 74 | ] 75 | 76 | for key in ['username', 'icon_emoji'] 77 | if config[key] then postOptions[key] = config[key] 78 | 79 | usersToNotify = [] 80 | for user, favorites of config.favorites 81 | if track.artist in favorites 82 | usersToNotify.push(user) 83 | 84 | if usersToNotify.length > 0 85 | postOptions.text = "hi " + usersToNotify.join(", ") 86 | 87 | postToChat(postOptions) 88 | 89 | checkSong() 90 | setInterval(checkSong, 5000) 91 | --------------------------------------------------------------------------------