├── .appcast.xml ├── .gitignore ├── LICENSE ├── README.md ├── assets ├── icon.png ├── next-dark.png ├── next.png ├── playpause-dark.png ├── playpause.png ├── prev-dark.png └── prev.png ├── package-lock.json ├── package.json └── src ├── applescript.js ├── data.js ├── manifest.json └── sketchpotify.js /.appcast.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build artifacts 2 | sketchpotify.sketchplugin 3 | 4 | # npm 5 | node_modules 6 | .npm 7 | npm-debug.log 8 | 9 | # mac 10 | .DS_Store 11 | 12 | # WebStorm 13 | .idea 14 | 15 | # sketch 16 | # sketch-assets 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ale Muñoz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://repository-images.githubusercontent.com/208445060/f283a480-d706-11e9-88c2-bdecad6fd3cd) 2 | 3 | # Sketchpotify 4 | 5 | A silly hack to control Spotify from Sketch, because why not. 6 | 7 | I take absolutely no credit for this, and I blame Russ Pate for coming up with the idea: 8 | 9 |

Has anyone built a @sketch plugin that can control @Spotify from within the app?

— Russ Pate (@RussPate) September 10, 2019
10 | 11 | ## Toolbar Icons 12 | 13 | I have added a set of toolbar icons (thanks @abynim for makind this ridiculously easy with https://github.com/abynim/sketch-toolbar-item). To use them, just right click Sketch's toolbar, choose 'Customize Toolbar…', and drag the icon group to the toolbar: 14 | 15 | ![sketch-toolbar-custom](https://user-images.githubusercontent.com/3832/64909673-adb03b80-d70f-11e9-9fea-677b0b82918e.gif) 16 | 17 | ## Bonus Feature: Insert Current Track data in Document 18 | 19 | We all know life's too short to spend it looking for random text or images for your designs. So you can insert the current track's Artwork, Artist, Album or Track Name as layers in your document (or all at once!). 20 | 21 | ## Installation 22 | 23 | - [Download](../../releases/latest/download/sketchpotify.sketchplugin.zip) the latest release of the plugin 24 | - Un-zip 25 | - Double-click on sketchpotify.sketchplugin 26 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketchpotify/27754f47b89f589b39bc5d7edfc746a069c2b893/assets/icon.png -------------------------------------------------------------------------------- /assets/next-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketchpotify/27754f47b89f589b39bc5d7edfc746a069c2b893/assets/next-dark.png -------------------------------------------------------------------------------- /assets/next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketchpotify/27754f47b89f589b39bc5d7edfc746a069c2b893/assets/next.png -------------------------------------------------------------------------------- /assets/playpause-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketchpotify/27754f47b89f589b39bc5d7edfc746a069c2b893/assets/playpause-dark.png -------------------------------------------------------------------------------- /assets/playpause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketchpotify/27754f47b89f589b39bc5d7edfc746a069c2b893/assets/playpause.png -------------------------------------------------------------------------------- /assets/prev-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketchpotify/27754f47b89f589b39bc5d7edfc746a069c2b893/assets/prev-dark.png -------------------------------------------------------------------------------- /assets/prev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketchpotify/27754f47b89f589b39bc5d7edfc746a069c2b893/assets/prev.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sketchpotify", 3 | "description": "A silly hack to control Spotify from Sketch, because why not.", 4 | "license": "MIT", 5 | "version": "0.4.1", 6 | "engines": { 7 | "sketch": ">=49.0" 8 | }, 9 | "skpm": { 10 | "name": "Sketchpotify", 11 | "manifest": "src/manifest.json", 12 | "main": "sketchpotify.sketchplugin", 13 | "assets": [ 14 | "assets/**/*" 15 | ], 16 | "sketch-assets-file": "sketch-assets/icons.sketch" 17 | }, 18 | "scripts": { 19 | "build": "skpm-build", 20 | "watch": "skpm-build --watch", 21 | "start": "skpm-build --watch --run", 22 | "postinstall": "npm run build && skpm-link" 23 | }, 24 | "devDependencies": { 25 | "@skpm/builder": "^0.7.5", 26 | "serialize-javascript": "^3.1.0" 27 | }, 28 | "author": "Ale M ", 29 | "repository": "https://github.com/bomberstudios/sketchpotify.git", 30 | "dependencies": { 31 | "@skpm/fs": "^0.2.5", 32 | "@skpm/xcodeproj-loader": "^0.1.6", 33 | "sketch-image-downloader": "^1.0.1", 34 | "sketch-toolbar-item": "^0.1.4" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/applescript.js: -------------------------------------------------------------------------------- 1 | export default function(script) { 2 | let appleScriptString = `tell application "Spotify" 3 | ${script} 4 | end tell` 5 | let as = NSAppleScript.alloc().initWithSource(appleScriptString) 6 | return as.executeAndReturnError(nil).stringValue() 7 | } -------------------------------------------------------------------------------- /src/data.js: -------------------------------------------------------------------------------- 1 | import sketch from 'sketch' 2 | import doAppleScript from './applescript' 3 | import insertImage, { getImageFromURL } from 'sketch-image-downloader' 4 | 5 | export function getArtwork(context) { 6 | let artworkURL = doAppleScript('set theURL to artwork url of current track') 7 | // For some reason, Spotify returns non-secure URLs and those won't work in Sketch. Let's fix that: 8 | artworkURL = artworkURL.replace('http:','https:') 9 | 10 | return insertImage(artworkURL, sketch.getSelectedDocument().selectedPage) 11 | } 12 | 13 | export function getArtist(context) { 14 | let data = doAppleScript('set theArtist to artist of current track') 15 | if (data != '') { 16 | return new Promise.resolve(insertTextLayer(data)) 17 | } 18 | } 19 | export function getAlbum(context) { 20 | let data = doAppleScript('set theAlbum to album of current track') 21 | if (data != '') { 22 | return new Promise.resolve(insertTextLayer(data)) 23 | } 24 | } 25 | export function getTrack(context) { 26 | let data = doAppleScript('set theTrack to name of current track') 27 | if (data != '') { 28 | return new Promise.resolve(insertTextLayer(data)) 29 | } 30 | } 31 | export function getEverything(context) { 32 | sketch.getSelectedDocument().selectedLayers.clear() 33 | 34 | let group = new sketch.Group({ 35 | parent: sketch.getSelectedDocument().selectedPage 36 | }) 37 | group.selected = true 38 | 39 | getArtwork().then(artwork => { 40 | artwork.parent = group 41 | group.adjustToFit() 42 | }) 43 | 44 | getArtist().then(artist => { 45 | group.name = artist.name 46 | artist.parent = group 47 | artist.frame.x += 340 48 | }) 49 | 50 | getAlbum().then(album => { 51 | group.name += ` - ${album.name}` 52 | album.parent = group 53 | album.frame.x += 340 54 | album.frame.y += 20 55 | }) 56 | 57 | getTrack().then(track => { 58 | track.parent = group 59 | track.frame.x += 340 60 | track.frame.y += 40 61 | }) 62 | } 63 | 64 | function insertTextLayer(txt) { 65 | return new sketch.Text({ 66 | text: txt, 67 | parent: sketch.getSelectedDocument().selectedPage 68 | }) 69 | } 70 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/BohemianCoding/SketchAPI/develop/docs/sketch-plugin-manifest-schema.json", 3 | "icon": "icon.png", 4 | "commands": [ 5 | { 6 | "name": "Play", 7 | "identifier": "sketchpotify.playpause", 8 | "handler": "onPlayPause", 9 | "script": "./sketchpotify.js" 10 | }, 11 | { 12 | "name": "Prev", 13 | "identifier": "sketchpotify.prev", 14 | "handler": "onPrevious", 15 | "script": "./sketchpotify.js" 16 | }, 17 | { 18 | "name": "Next", 19 | "identifier": "sketchpotify.next", 20 | "handler": "onNext", 21 | "script": "./sketchpotify.js" 22 | }, 23 | { 24 | "name": "Shuffle", 25 | "identifier": "sketchpotify.shuffle", 26 | "handler": "onShuffle", 27 | "script": "./sketchpotify.js" 28 | }, 29 | { 30 | "name": "Insert Current Artwork in Document", 31 | "identifier": "sketchpotify.artwork", 32 | "handler": "getArtwork", 33 | "script": "./data.js" 34 | }, 35 | { 36 | "name": "Insert Current Artist as Text", 37 | "identifier": "sketchpotify.artist", 38 | "handler": "getArtist", 39 | "script": "./data.js" 40 | }, 41 | { 42 | "name": "Insert Current Album as Text", 43 | "identifier": "sketchpotify.album", 44 | "handler": "getAlbum", 45 | "script": "./data.js" 46 | }, 47 | { 48 | "name": "Insert Current Track as Text", 49 | "identifier": "sketchpotify.track", 50 | "handler": "getTrack", 51 | "script": "./data.js" 52 | }, 53 | { 54 | "name": "Insert All Data", 55 | "identifier": "sketchpotify.everything", 56 | "handler": "getEverything", 57 | "script": "./data.js" 58 | }, 59 | { 60 | "name": "Toolbar", 61 | "script": "./sketchpotify.js", 62 | "handlers": { 63 | "actions": { 64 | "Startup": "registerToolbarActions" 65 | } 66 | } 67 | } 68 | ], 69 | "menu": { 70 | "title": "Sketchpotify", 71 | "items": [ 72 | "sketchpotify.playpause", 73 | "sketchpotify.prev", 74 | "sketchpotify.next", 75 | "sketchpotify.shuffle", 76 | "-", 77 | "sketchpotify.artwork", 78 | "sketchpotify.artist", 79 | "sketchpotify.album", 80 | "sketchpotify.track", 81 | "sketchpotify.everything" 82 | ] 83 | } 84 | } -------------------------------------------------------------------------------- /src/sketchpotify.js: -------------------------------------------------------------------------------- 1 | import sketch, { UI } from 'sketch' 2 | import SketchToolbar from 'sketch-toolbar-item' 3 | import doAppleScript from './applescript' 4 | 5 | export function registerToolbarActions(context) { 6 | let prev = SketchToolbar.specifierForToolbarAction(context, 'sketchpotify.prev', 'prev.png|prev-dark.png') 7 | let playpause = SketchToolbar.specifierForToolbarAction(context, 'sketchpotify.playpause', 'playpause.png|playpause-dark.png') 8 | let next = SketchToolbar.specifierForToolbarAction(context, 'sketchpotify.next', 'next.png|next-dark.png') 9 | SketchToolbar.registerToolbarGroup(context, 'sketchpotify', [prev, playpause, next]) 10 | } 11 | 12 | export function onPlayPause () { 13 | doAppleScript('playpause') 14 | } 15 | export function onPrevious () { 16 | doAppleScript('previous track') 17 | } 18 | export function onNext () { 19 | doAppleScript('next track') 20 | } 21 | export function onShuffle () { 22 | doAppleScript('set shuffling to true\nplaypause') 23 | } 24 | 25 | --------------------------------------------------------------------------------