├── mediaResources ├── beardedspiceIcon.sketch │ ├── version │ ├── Data │ ├── QuickLook │ │ ├── Preview.png │ │ └── Thumbnail.png │ └── metadata ├── auto.pdf ├── icon16.png ├── icon32.png ├── icon64.png ├── logo36.png ├── logo56.png ├── logo90.png ├── icon1024.png ├── icon128.png ├── icon20x19.pdf ├── icon256.png ├── icon512.png ├── logo36@2x.png ├── logo56@2x.png ├── logo90@2x.png ├── orgLogo128.png ├── orgLogo150.png ├── icon20x19-alt.pdf ├── orgLogo150@2x.png ├── toolbarShortcuts.png └── toolbarShortcuts@2x.png ├── images ├── bs.jpg ├── icon20x19.png └── icon20x19@2x.png ├── BeardedSpiceTests ├── en.lproj │ └── InfoPlist.strings ├── BeardedSpiceTests-Info.plist ├── BSStrategyMockObject.h └── BSMediaStrategyTests.m ├── BeardedSpice ├── Images.xcassets │ ├── Contents.json │ ├── auto.imageset │ │ ├── auto.pdf │ │ └── Contents.json │ ├── custom.imageset │ │ ├── custom.pdf │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── icon16.png │ │ ├── icon32.png │ │ ├── icon64.png │ │ ├── icon1024.png │ │ ├── icon128.png │ │ ├── icon256.png │ │ ├── icon32-1.png │ │ ├── icon512.png │ │ ├── icon256-1.png │ │ ├── icon512-1.png │ │ └── Contents.json │ ├── icon20x19.imageset │ │ ├── icon20x19.pdf │ │ └── Contents.json │ ├── StrategyIcon.iconset │ │ ├── icon_16x16.png │ │ ├── icon_32x32.png │ │ ├── icon_128x128.png │ │ ├── icon_16x16@2x.png │ │ ├── icon_256x256.png │ │ ├── icon_32x32@2x.png │ │ ├── icon_512x512.png │ │ ├── icon_128x128@2x.png │ │ ├── icon_256x256@2x.png │ │ └── icon_512x512@2x.png │ ├── icon20x19-alt.imageset │ │ ├── icon20x19-alt.pdf │ │ └── Contents.json │ └── toolbarShortcuts.imageset │ │ ├── toolbarShortcuts.png │ │ ├── toolbarShortcuts@2x.png │ │ └── Contents.json ├── logos │ ├── logo-peel.png │ ├── logo-banana.png │ ├── logo-jarbeard.png │ ├── logo-plainbeard.png │ ├── logo-spicejar.png │ ├── logo-banana-selected.png │ ├── logo-peel-selected.png │ ├── logo-jarbeard-selected.png │ ├── logo-spicejar-selected.png │ └── logo-plainbeard-selected.png ├── Base.lproj │ └── Localizable.strings ├── Tabs │ ├── DowncastTabAdapter.h │ ├── VLCTabAdapter.h │ ├── VOXTabAdapter.h │ ├── SpotifyTabAdapter.h │ ├── iTunesTabAdapter.h │ ├── SafariTabAdapter.h │ ├── NativeAppTabAdapter.m │ ├── TabAdapter.h │ ├── ChromeTabAdapter.h │ ├── NativeAppTabAdapter.h │ └── Downcast.h ├── main.m ├── Config.xcconfig ├── BeardedSpice-Prefix.pch ├── Preferences │ ├── BSPreferencesWindowController.h │ ├── EHVerticalCenteredTextField.h │ ├── BSShortcutView.h │ ├── BSMediaStrategyEnableButton.h │ ├── MediaControllerObject.h │ ├── BSPreferencesWindowController.m │ ├── ShortcutsPreferencesViewController.h │ ├── BSMediaStrategyEnableButton.m │ ├── MediaControllerObject.m │ └── ShortcutsPreferencesViewController.m ├── Debug-Config.xcconfig ├── MediaStrategies │ ├── FocusAtWill.js │ ├── Rdio.js │ ├── NRK.js │ ├── Slacker.js │ ├── TTMuzik.js │ ├── KollektFm.js │ ├── MusicUnlimited.js │ ├── Vimeo.js │ ├── BandCamp.js │ ├── LogitechMediaServer.js │ ├── HypeMachine.js │ ├── Synology.js │ ├── Stitcher.js │ ├── BandCampWhiteLabel.js │ ├── Saavn.js │ ├── LastFm.js │ ├── XboxMusic.js │ ├── Chorus.js │ ├── MixcloudBeta.js │ ├── ShufflerFm.js │ ├── GrooveShark.js │ ├── Stripewaves.js │ ├── TwentyTwoTracks.js │ ├── NoAdRadio.js │ ├── MixCloud.js │ ├── Zing.js │ ├── Fip.js │ ├── LeTournedisque.js │ ├── AppleDeveloper.js │ ├── PocketCasts.js │ ├── BrainFm.js │ ├── Jango.js │ ├── Beatguide.js │ ├── AmazonMusic.js │ ├── Anghami.js │ ├── Composed.js │ ├── Odnoklassniki.js │ ├── Youtube.js │ ├── PoolsideFM.js │ ├── WatchaPlay.js │ ├── RedditMusic.js │ ├── ListenOnRepeat.js │ ├── Spotify.js │ ├── TuneIn.js │ ├── Gaana.js │ ├── Vessel.js │ ├── Pandora.js │ ├── Deezer.js │ ├── HotNewHipHop.js │ ├── NoonPacific.js │ ├── Netflix.js │ ├── BugsMusic.js │ ├── Coursera.js │ ├── IndieShuffle.js │ ├── KCRW.js │ ├── SomaFm.js │ ├── FocusMusicFM.js │ ├── Subsonic.js │ ├── Blitzr.js │ ├── Udemy.js │ ├── YandexRadio.js │ ├── GoogleMusic.js │ ├── Overcast.js │ ├── YandexMusic.js │ ├── iHeartRadio.js │ ├── Twitch.js │ ├── Songza.js │ ├── WonderFm.js │ ├── MusicForProgramming.js │ ├── StyleJukebox.js │ ├── CozyCloud.js │ ├── Napster.js │ ├── SoundCloud.js │ ├── DigitallyImported.js │ ├── BE-AT.TV.js │ ├── TidalHiFi.js │ ├── Audible.js │ ├── ProductHunt.js │ ├── Beatport.js │ ├── Qobuz.js │ ├── BBCRadio.js │ ├── AudioMack.js │ └── EightTracks.js ├── BSCustomStrategyManager.h ├── runningSBApplication.h ├── NativeAppTabRegistry.h ├── MediaStrategyRegistry.h ├── BSLaunchAtLogin.h ├── BSTrack.h ├── BeardedSpiceSuite.sdef ├── BSAppleScriptSupport.m ├── BeardedSpiceUserDefaults.plist ├── BSStrategyCache.h ├── BSStrategyVersionManager.h └── AppDelegate.h ├── BeardedSpice.xcodeproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── BeardedSpice.xcworkspace ├── xcshareddata │ └── WorkspaceSettings.xcsettings └── contents.xcworkspacedata ├── Utils ├── BSTimeout.h ├── NSArray+Utils.h ├── BSTimeout.m ├── NSException+Utils.h ├── NSArray+Utils.m ├── NSString+Utils.h ├── NSURL+Utils.h ├── NSException+Utils.m └── EHSystemUtils.h ├── .travis.yml ├── .gitignore ├── Podfile ├── BeardedSpiceControllers ├── Controllers │ ├── BSCShortcutMonitor.h │ ├── SPMediaKeyTap │ │ ├── NSObject+SPInvocationGrabbing.h │ │ └── SPMediaKeyTap.h │ ├── DDHidLib │ │ ├── DDHidUsageTables.h │ │ ├── DDHidEvent.h │ │ ├── DDHidUsage.h │ │ ├── NSDictionary+DDHidExtras.h │ │ ├── DDHidAppleMikey.h │ │ ├── DDHidElement.h │ │ └── DDHidEvent.m │ └── BSHeadphoneStatusListener.h ├── BeardedSpiceControllers.h ├── BSCService.h ├── Info.plist └── BeardedSpiceControllers.m ├── Podfile.lock ├── SharedComponents ├── BeardedSpiceHostAppProtocol.h ├── BSSharedDefaults.h ├── BSSharedDefaults.m └── BeardedSpiceControllersProtocol.h ├── release.sh └── template-explained.js /mediaResources/beardedspiceIcon.sketch/version: -------------------------------------------------------------------------------- 1 | 18 -------------------------------------------------------------------------------- /images/bs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/images/bs.jpg -------------------------------------------------------------------------------- /BeardedSpiceTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /images/icon20x19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/images/icon20x19.png -------------------------------------------------------------------------------- /images/icon20x19@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/images/icon20x19@2x.png -------------------------------------------------------------------------------- /mediaResources/auto.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/mediaResources/auto.pdf -------------------------------------------------------------------------------- /mediaResources/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/mediaResources/icon16.png -------------------------------------------------------------------------------- /mediaResources/icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/mediaResources/icon32.png -------------------------------------------------------------------------------- /mediaResources/icon64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/mediaResources/icon64.png -------------------------------------------------------------------------------- /mediaResources/logo36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/mediaResources/logo36.png -------------------------------------------------------------------------------- /mediaResources/logo56.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/mediaResources/logo56.png -------------------------------------------------------------------------------- /mediaResources/logo90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/mediaResources/logo90.png -------------------------------------------------------------------------------- /mediaResources/icon1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/mediaResources/icon1024.png -------------------------------------------------------------------------------- /mediaResources/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/mediaResources/icon128.png -------------------------------------------------------------------------------- /mediaResources/icon20x19.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/mediaResources/icon20x19.pdf -------------------------------------------------------------------------------- /mediaResources/icon256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/mediaResources/icon256.png -------------------------------------------------------------------------------- /mediaResources/icon512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/mediaResources/icon512.png -------------------------------------------------------------------------------- /mediaResources/logo36@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/mediaResources/logo36@2x.png -------------------------------------------------------------------------------- /mediaResources/logo56@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/mediaResources/logo56@2x.png -------------------------------------------------------------------------------- /mediaResources/logo90@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/mediaResources/logo90@2x.png -------------------------------------------------------------------------------- /mediaResources/orgLogo128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/mediaResources/orgLogo128.png -------------------------------------------------------------------------------- /mediaResources/orgLogo150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/mediaResources/orgLogo150.png -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /BeardedSpice/logos/logo-peel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/logos/logo-peel.png -------------------------------------------------------------------------------- /mediaResources/icon20x19-alt.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/mediaResources/icon20x19-alt.pdf -------------------------------------------------------------------------------- /mediaResources/orgLogo150@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/mediaResources/orgLogo150@2x.png -------------------------------------------------------------------------------- /BeardedSpice/logos/logo-banana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/logos/logo-banana.png -------------------------------------------------------------------------------- /mediaResources/toolbarShortcuts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/mediaResources/toolbarShortcuts.png -------------------------------------------------------------------------------- /BeardedSpice/logos/logo-jarbeard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/logos/logo-jarbeard.png -------------------------------------------------------------------------------- /BeardedSpice/logos/logo-plainbeard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/logos/logo-plainbeard.png -------------------------------------------------------------------------------- /BeardedSpice/logos/logo-spicejar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/logos/logo-spicejar.png -------------------------------------------------------------------------------- /mediaResources/toolbarShortcuts@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/mediaResources/toolbarShortcuts@2x.png -------------------------------------------------------------------------------- /BeardedSpice/Base.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/Base.lproj/Localizable.strings -------------------------------------------------------------------------------- /BeardedSpice/logos/logo-banana-selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/logos/logo-banana-selected.png -------------------------------------------------------------------------------- /BeardedSpice/logos/logo-peel-selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/logos/logo-peel-selected.png -------------------------------------------------------------------------------- /mediaResources/beardedspiceIcon.sketch/Data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/mediaResources/beardedspiceIcon.sketch/Data -------------------------------------------------------------------------------- /BeardedSpice/logos/logo-jarbeard-selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/logos/logo-jarbeard-selected.png -------------------------------------------------------------------------------- /BeardedSpice/logos/logo-spicejar-selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/logos/logo-spicejar-selected.png -------------------------------------------------------------------------------- /BeardedSpice/logos/logo-plainbeard-selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/logos/logo-plainbeard-selected.png -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/auto.imageset/auto.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/Images.xcassets/auto.imageset/auto.pdf -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/custom.imageset/custom.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/Images.xcassets/custom.imageset/custom.pdf -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/AppIcon.appiconset/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/Images.xcassets/AppIcon.appiconset/icon16.png -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/AppIcon.appiconset/icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/Images.xcassets/AppIcon.appiconset/icon32.png -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/AppIcon.appiconset/icon64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/Images.xcassets/AppIcon.appiconset/icon64.png -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/AppIcon.appiconset/icon1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/Images.xcassets/AppIcon.appiconset/icon1024.png -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/AppIcon.appiconset/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/Images.xcassets/AppIcon.appiconset/icon128.png -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/AppIcon.appiconset/icon256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/Images.xcassets/AppIcon.appiconset/icon256.png -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/AppIcon.appiconset/icon32-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/Images.xcassets/AppIcon.appiconset/icon32-1.png -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/AppIcon.appiconset/icon512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/Images.xcassets/AppIcon.appiconset/icon512.png -------------------------------------------------------------------------------- /mediaResources/beardedspiceIcon.sketch/QuickLook/Preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/mediaResources/beardedspiceIcon.sketch/QuickLook/Preview.png -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/AppIcon.appiconset/icon256-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/Images.xcassets/AppIcon.appiconset/icon256-1.png -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/AppIcon.appiconset/icon512-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/Images.xcassets/AppIcon.appiconset/icon512-1.png -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/icon20x19.imageset/icon20x19.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/Images.xcassets/icon20x19.imageset/icon20x19.pdf -------------------------------------------------------------------------------- /mediaResources/beardedspiceIcon.sketch/QuickLook/Thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/mediaResources/beardedspiceIcon.sketch/QuickLook/Thumbnail.png -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/StrategyIcon.iconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/Images.xcassets/StrategyIcon.iconset/icon_16x16.png -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/StrategyIcon.iconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/Images.xcassets/StrategyIcon.iconset/icon_32x32.png -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/StrategyIcon.iconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/Images.xcassets/StrategyIcon.iconset/icon_128x128.png -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/StrategyIcon.iconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/Images.xcassets/StrategyIcon.iconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/StrategyIcon.iconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/Images.xcassets/StrategyIcon.iconset/icon_256x256.png -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/StrategyIcon.iconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/Images.xcassets/StrategyIcon.iconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/StrategyIcon.iconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/Images.xcassets/StrategyIcon.iconset/icon_512x512.png -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/StrategyIcon.iconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/Images.xcassets/StrategyIcon.iconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/StrategyIcon.iconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/Images.xcassets/StrategyIcon.iconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/StrategyIcon.iconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/Images.xcassets/StrategyIcon.iconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/icon20x19-alt.imageset/icon20x19-alt.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/Images.xcassets/icon20x19-alt.imageset/icon20x19-alt.pdf -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/toolbarShortcuts.imageset/toolbarShortcuts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/Images.xcassets/toolbarShortcuts.imageset/toolbarShortcuts.png -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/toolbarShortcuts.imageset/toolbarShortcuts@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/beardedspice/master/BeardedSpice/Images.xcassets/toolbarShortcuts.imageset/toolbarShortcuts@2x.png -------------------------------------------------------------------------------- /BeardedSpice.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/auto.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "auto.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/custom.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "custom.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/icon20x19-alt.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "icon20x19-alt.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode", 11 | "template-rendering-intent" : "original" 12 | } 13 | } -------------------------------------------------------------------------------- /BeardedSpice/Tabs/DowncastTabAdapter.h: -------------------------------------------------------------------------------- 1 | // 2 | // DowncastTabAdapter.h 3 | // BeardedSpice 4 | // 5 | // Created by George Cox on 5/2/16. 6 | // Copyright © 2016 BeardedSpice. All rights reserved. 7 | // 8 | 9 | #import "NativeAppTabAdapter.h" 10 | 11 | @interface DowncastTabAdapter : NativeAppTabAdapter 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /BeardedSpice/Tabs/VLCTabAdapter.h: -------------------------------------------------------------------------------- 1 | // 2 | // VLCTabAdapter.h 3 | // BeardedSpice 4 | // 5 | // Created by Max Borghino on 2106-03-06 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | 9 | #import "NativeAppTabAdapter.h" 10 | 11 | @interface VLCTabAdapter : NativeAppTabAdapter 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /BeardedSpice/Tabs/VOXTabAdapter.h: -------------------------------------------------------------------------------- 1 | // 2 | // VOXTabAdapter.h 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 20.06.15. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | 9 | #import "NativeAppTabAdapter.h" 10 | 11 | @interface VOXTabAdapter : NativeAppTabAdapter 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/icon20x19.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "icon20x19.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } -------------------------------------------------------------------------------- /BeardedSpice/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // BeardedSpice 4 | // 5 | // Created by Tyler Rhodes on 12/8/13. 6 | // Copyright (c) 2013 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, const char * argv[]) 12 | { 13 | return NSApplicationMain(argc, argv); 14 | } 15 | -------------------------------------------------------------------------------- /BeardedSpice.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /BeardedSpice/Config.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Config.xcconfig 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 26.06.16. 6 | // Copyright © 2016 BeardedSpice. All rights reserved. 7 | // 8 | 9 | BS_STRATEGY_EXTENSION = bsstrategy 10 | 11 | 12 | 13 | // Marco definition 14 | GCC_PREPROCESSOR_DEFINITIONS = BS_STRATEGY_EXTENSION=@\"$(BS_STRATEGY_EXTENSION)\" 15 | -------------------------------------------------------------------------------- /Utils/BSTimeout.h: -------------------------------------------------------------------------------- 1 | // 2 | // BSTimeout.h 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 12.02.16. 6 | // Copyright © 2016 BeardedSpice. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface BSTimeout : NSObject 12 | 13 | + (id)timeoutWithInterval:(NSTimeInterval)interval; 14 | 15 | - (BOOL)reached; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: osx 2 | osx_image: xcode7.3 3 | language: objective-c 4 | rvm: 5 | - 2.3.1 6 | before_install: 7 | - gem install cocoapods 8 | - gem install xcpretty -N --no-ri --no-rdoc 9 | 10 | script: 11 | - set -o pipefail 12 | - xcodebuild clean test -workspace BeardedSpice.xcworkspace -scheme BeardedSpice -sdk macosx -enableCodeCoverage YES | xcpretty 13 | 14 | -------------------------------------------------------------------------------- /BeardedSpice/BeardedSpice-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | @import Foundation; 9 | @import Cocoa; 10 | 11 | #import "NSURL+Utils.h" // FIXME update to reflect namespaced category BSUtils 12 | #import "NSString+Utils.h" 13 | #endif 14 | -------------------------------------------------------------------------------- /BeardedSpice/Preferences/BSPreferencesWindowController.h: -------------------------------------------------------------------------------- 1 | // 2 | // BSPreferencesWindowController.h 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 09.08.15. 6 | // Copyright (c) 2015 BeardedSpice. All rights reserved. 7 | // 8 | 9 | #import "MASPreferencesWindowController.h" 10 | 11 | @interface BSPreferencesWindowController : MASPreferencesWindowController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/toolbarShortcuts.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "filename" : "toolbarShortcuts.png" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "filename" : "toolbarShortcuts@2x.png" 12 | } 13 | ], 14 | "info" : { 15 | "version" : 1, 16 | "author" : "xcode" 17 | } 18 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # CocoaPods / Ruby env 23 | Pods 24 | .ruby-version 25 | .ruby-gemset 26 | 27 | # DMGs 28 | *.dmg 29 | -------------------------------------------------------------------------------- /BeardedSpice/Preferences/EHVerticalCenteredTextField.h: -------------------------------------------------------------------------------- 1 | // 2 | // EHCenteredTextField.h 3 | // EightHours 4 | // 5 | // Created by Roman Sokolov on 19.06.16. 6 | // Copyright © 2016 Roman Sokolov. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** 12 | Text field class (label) that have define vertical align to center. 13 | */ 14 | @interface EHVerticalCenteredTextField : NSTextField 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /BeardedSpice/Preferences/BSShortcutView.h: -------------------------------------------------------------------------------- 1 | // 2 | // BSShortcutView.h 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 09.08.15. 6 | // Copyright (c) 2015 BeardedSpice. All rights reserved. 7 | // 8 | 9 | #import "MASShortcutView.h" 10 | 11 | @class MASShortcut; 12 | 13 | @interface BSShortcutView : MASShortcutView{ 14 | 15 | BOOL _firstResponder; 16 | MASShortcut *_savedShortcut; 17 | } 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /BeardedSpice/Preferences/BSMediaStrategyEnableButton.h: -------------------------------------------------------------------------------- 1 | // 2 | // BSMediaStrategyEnableButton.h 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 08.08.15. 6 | // Copyright (c) 2015 BeardedSpice. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface BSMediaStrategyEnableButton : NSButton{ 12 | 13 | __weak NSTableView *_tableView; 14 | } 15 | 16 | - (id)initWithTableView:(NSTableView *)tableView; 17 | @end 18 | -------------------------------------------------------------------------------- /BeardedSpice/Tabs/SpotifyTabAdapter.h: -------------------------------------------------------------------------------- 1 | // 2 | // iTunesTabAdapter.h 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 14.03.15. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "NativeAppTabAdapter.h" 11 | #import "Spotify.h" 12 | 13 | @class runningSBApplication, Track; 14 | 15 | @interface SpotifyTabAdapter : NativeAppTabAdapter{ 16 | 17 | } 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /BeardedSpice.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /BeardedSpice/Tabs/iTunesTabAdapter.h: -------------------------------------------------------------------------------- 1 | // 2 | // iTunesTabAdapter.h 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 14.03.15. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "NativeAppTabAdapter.h" 11 | #import "iTunes.h" 12 | 13 | @class runningSBApplication, Track; 14 | 15 | @interface iTunesTabAdapter : NativeAppTabAdapter{ 16 | 17 | BOOL iTunesNeedDisplayNotification; 18 | } 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.9' 2 | project 'BeardedSpice' 3 | 4 | source 'https://github.com/CocoaPods/Specs.git' 5 | 6 | target 'BeardedSpiceControllers' do 7 | pod 'MASShortcut', '~> 2.3.3' 8 | 9 | target 'BeardedSpice' do 10 | pod 'MASPreferences', '= 1.1.4' 11 | 12 | # all pods for tests should ONLY go here 13 | target 'BeardedSpiceTests' do 14 | pod 'Kiwi' 15 | # pod 'OCMock' 16 | pod 'VCRURLConnection' 17 | end 18 | end 19 | end 20 | 21 | -------------------------------------------------------------------------------- /Utils/NSArray+Utils.h: -------------------------------------------------------------------------------- 1 | // 2 | // 3 | // Created by Roman Sokolov on 12.02.14. 4 | // Copyright (c) 2014 Roman Sokolov. All rights reserved. 5 | // 6 | 7 | #import 8 | 9 | /// Stack methods for NSMutableArray 10 | @interface NSMutableArray (Stack) 11 | 12 | - (id)pop; 13 | - (void)push:(id)obj; 14 | - (id)peekStack; 15 | 16 | @end 17 | 18 | /// Queue methods for NSMutableArray 19 | @interface NSMutableArray (Queue) 20 | 21 | - (id)dequeue; 22 | - (void)enqueue:(id)obj; 23 | - (id)peekQueue; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /BeardedSpice/Debug-Config.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Config.xcconfig 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 04.06.16. 6 | // Copyright © 2016 BeardedSpice. All rights reserved. 7 | // 8 | 9 | // if DEBUG_STRATEGY set to 1, 10 | // then BeardedSpice uses strategies only from bundle resources folder. 11 | DEBUG_STRATEGY = 1 12 | 13 | 14 | #include "./Config.xcconfig" 15 | 16 | // Marco definition 17 | GCC_PREPROCESSOR_DEFINITIONS = BS_STRATEGY_EXTENSION=@\"$(BS_STRATEGY_EXTENSION)\" DEBUG_STRATEGY=$(DEBUG_STRATEGY) 18 | 19 | -------------------------------------------------------------------------------- /BeardedSpice/Preferences/MediaControllerObject.h: -------------------------------------------------------------------------------- 1 | // 2 | // MediaControllerObject.h 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 02.05.15. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MediaControllerObject : NSObject 12 | 13 | - (id)initWithObject:(id)object; 14 | 15 | @property BOOL isGroup; 16 | @property NSString *name; 17 | @property BOOL isCustom; 18 | @property NSString *version; 19 | @property id representationObject; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /BeardedSpiceControllers/Controllers/BSCShortcutMonitor.h: -------------------------------------------------------------------------------- 1 | // 2 | // BSCShortcutMonitor.h 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 11.03.16. 6 | // Copyright © 2016 BeardedSpice. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class MASShortcut; 12 | 13 | @interface BSCShortcutMonitor : NSObject 14 | 15 | + (instancetype) sharedMonitor; 16 | 17 | - (BOOL) registerShortcut: (MASShortcut*) shortcut withAction: (dispatch_block_t) action; 18 | - (void) unregisterShortcut: (MASShortcut*) shortcut; 19 | - (void) unregisterAllShortcuts; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Kiwi (2.4.0) 3 | - MASPreferences (1.1.4) 4 | - MASShortcut (2.3.3) 5 | - VCRURLConnection (0.2.0) 6 | 7 | DEPENDENCIES: 8 | - Kiwi 9 | - MASPreferences (= 1.1.4) 10 | - MASShortcut (~> 2.3.3) 11 | - VCRURLConnection 12 | 13 | SPEC CHECKSUMS: 14 | Kiwi: f49c9d54b28917df5928fe44968a39ed198cb8a8 15 | MASPreferences: 7bdcfe891d7840453cf48f95fa866c3e152ace7a 16 | MASShortcut: 38a76c9ea927de770c0a97ae28b15d18d6268b24 17 | VCRURLConnection: accd771ebd4be11183a3e4d5a4403f180a9a235e 18 | 19 | PODFILE CHECKSUM: 8cc81dabf4de1b33ff899c97571ab25e98561e61 20 | 21 | COCOAPODS: 1.0.1 22 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/FocusAtWill.js: -------------------------------------------------------------------------------- 1 | // 2 | // FocusAtWill.plist 3 | // BeardedSpice 4 | // 5 | // Created by Ken Mickles on 1/15/15. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"focus@will", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*focusatwill.com*'", 14 | args: ["URL"] 15 | }, 16 | toggle: function () {document.querySelector('a.play').click()}, 17 | next: function () { document.querySelector('a.next').click() }, 18 | pause: function () { document.querySelector('a.play').click() } 19 | } 20 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Rdio.js: -------------------------------------------------------------------------------- 1 | // 2 | // Rdio.plist 3 | // BeardedSpice 4 | // 5 | // Created by Jose Falcon on 1/9/14. 6 | // Copyright (c) 2014 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"Rdio", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*rdio.com*'", 14 | args: ["URL"] 15 | }, 16 | toggle: function () {window.R.player.playPause()}, 17 | next: function () {window.R.player.next()}, 18 | previous: function () {window.R.player.previous()}, 19 | pause: function () {window.R.player.pause()}, 20 | trackInfo: function () {} 21 | } 22 | -------------------------------------------------------------------------------- /mediaResources/beardedspiceIcon.sketch/metadata: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | app 6 | com.bohemiancoding.sketch 7 | build 8 | 5355 9 | commit 10 | b7d299b0a34651d1a0e066786b75aa36168d5809 11 | fonts 12 | 13 | Tahoma-Bold 14 | 15 | length 16 | 2442432 17 | version 18 | 18 19 | 20 | 21 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/NRK.js: -------------------------------------------------------------------------------- 1 | // 2 | // NRKStrategy.h 3 | // BeardedSpice 4 | // 5 | // Created by Theodor Tonum on 8/24/15. 6 | // Copyright (c) 2015 Theodor Tonum. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"NRK", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*radio.nrk.no*'", 14 | args: ["URL"] 15 | }, 16 | toggle: function () {return window.nrk.modules.player.getApi().toggleplay()}, 17 | next: function () {}, 18 | previous: function () {}, 19 | pause: function () {return window.nrk.modules.player.getApi().pause()}, 20 | favorite: function () {}, 21 | trackInfo: function() {} 22 | } 23 | -------------------------------------------------------------------------------- /SharedComponents/BeardedSpiceHostAppProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // BeardedSpiceHostAppProtocol.h 3 | // BeardedSpiceControllers 4 | // 5 | // Created by Roman Sokolov on 05.03.16. 6 | // Copyright © 2016 BeardedSpice. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol BeardedSpiceHostAppProtocol 12 | 13 | - (void)playPauseToggle; 14 | - (void)nextTrack; 15 | - (void)previousTrack; 16 | 17 | - (void)activeTab; 18 | - (void)favorite; 19 | - (void)notification; 20 | - (void)activatePlayingTab; 21 | 22 | - (void)playerNext; 23 | - (void)playerPrevious; 24 | 25 | - (void)volumeUp; 26 | - (void)volumeDown; 27 | 28 | - (void)headphoneUnplug; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /Utils/BSTimeout.m: -------------------------------------------------------------------------------- 1 | // 2 | // BSTimeout.m 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 12.02.16. 6 | // Copyright © 2016 BeardedSpice. All rights reserved. 7 | // 8 | 9 | #import "BSTimeout.h" 10 | 11 | @implementation BSTimeout{ 12 | 13 | NSDate *_startDate; 14 | NSTimeInterval _interval; 15 | } 16 | 17 | + (id)timeoutWithInterval:(NSTimeInterval)interval{ 18 | 19 | BSTimeout *timeout = [BSTimeout new]; 20 | timeout->_startDate = [NSDate date]; 21 | timeout->_interval = -interval; 22 | 23 | return timeout; 24 | } 25 | 26 | - (BOOL)reached{ 27 | 28 | return ([_startDate timeIntervalSinceNow] < _interval); 29 | } 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Slacker.js: -------------------------------------------------------------------------------- 1 | // 2 | // Slacker.plist 3 | // BeardedSpice 4 | // 5 | // Created by Jose Falcon on 1/18/14. 6 | // Copyright (c) 2014 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"Slacker", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*slacker.com*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () {}, 17 | toggle: function () {window.playPause()}, 18 | next: function () {window.skip()}, 19 | favorite: function () {}, 20 | previous: function () {window.skipBack()}, 21 | pause: function () {window.PLAYER_ENGINE.pause()}, 22 | trackInfo: function () {} 23 | } 24 | -------------------------------------------------------------------------------- /BeardedSpiceControllers/BeardedSpiceControllers.h: -------------------------------------------------------------------------------- 1 | // 2 | // BeardedSpiceControllers.h 3 | // BeardedSpiceControllers 4 | // 5 | // Created by Roman Sokolov on 05.03.16. 6 | // Copyright © 2016 BeardedSpice. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "BeardedSpiceControllersProtocol.h" 11 | 12 | // This object implements the protocol which we have defined. It provides the actual behavior for the service. It is 'exported' by the service to make it available to the process hosting the service over an NSXPCConnection. 13 | @interface BeardedSpiceControllers : NSObject 14 | 15 | @property (weak) NSXPCConnection *connection; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/TTMuzik.js: -------------------------------------------------------------------------------- 1 | // 2 | // TTNETMuzik.m 3 | // BeardedSpice 4 | // 5 | // Created by Bilal Demirci on 08/03/16. 6 | // Copyright © 2016 BeardedSpice. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version: 1, 10 | displayName: "TT Muzik", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format: "%K LIKE[c] '*turktelekommuzik.com*'", 14 | args: ["URL"] 15 | }, 16 | toggle: function () { document.querySelector('#player-play').click(); }, 17 | previous: function () { document.querySelector('#player-prev').click(); }, 18 | next: function () { document.querySelector('#player-next').click(); }, 19 | pause: function () { document.querySelector('#player-play').click(); }, 20 | } 21 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/KollektFm.js: -------------------------------------------------------------------------------- 1 | // 2 | // KollektFm.plist 3 | // BeardedSpice 4 | // 5 | // Created by Wiert Omta on 23/1/2015. 6 | // Copyright (c) 2015 Wiert Omta. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"Kollekt.FM", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*kollekt.fm*'", 14 | args: ["URL"] 15 | }, 16 | toggle: function() { $( "i[ng-click='playPause()']" ).click; }, 17 | next: function() { $( "i[ng-click='next()']" ).click; }, 18 | favorite: function() { $( "i[ng-click='favoriteTrack(activeTrack())']" ).click; }, 19 | previous: function() { $( "i[ng-click='previous()']" ).click; }, 20 | pause: function() { $( ".fa-pause" ).click; } 21 | } 22 | -------------------------------------------------------------------------------- /BeardedSpice/BSCustomStrategyManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // BSCustomStrategyManager.h 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 25.06.16. 6 | // Copyright © 2016 BeardedSpice. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class BSMediaStrategy; 12 | 13 | /** 14 | FIXME 15 | */ 16 | extern NSString *BSCStrategyChangedNotification; 17 | 18 | @interface BSCustomStrategyManager : NSObject 19 | 20 | + (BSCustomStrategyManager *)singleton; 21 | 22 | 23 | - (BOOL)importFromPath:(NSString *)path; 24 | 25 | - (BOOL)importFromUrl:(NSURL *)url; 26 | 27 | - (BOOL)exportStrategy:(BSMediaStrategy *)strategy toFolder:(NSURL *)folderURL; 28 | 29 | - (BOOL)removeStrategy:(BSMediaStrategy *)strategy; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /BeardedSpice/Preferences/BSPreferencesWindowController.m: -------------------------------------------------------------------------------- 1 | // 2 | // BSPreferencesWindowController.m 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 09.08.15. 6 | // Copyright (c) 2015 BeardedSpice. All rights reserved. 7 | // 8 | 9 | #import "BSPreferencesWindowController.h" 10 | 11 | @interface BSPreferencesWindowController () 12 | 13 | @end 14 | 15 | @implementation BSPreferencesWindowController 16 | 17 | - (void)windowDidLoad { 18 | [super windowDidLoad]; 19 | 20 | // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file. 21 | } 22 | 23 | // We redefine this method. Now method does nothing. 24 | - (void)patchResponderChain{ 25 | 26 | } 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/MusicUnlimited.js: -------------------------------------------------------------------------------- 1 | // 2 | // MusicUnlimited.plist 3 | // BeardedSpice 4 | // 5 | // Created by Tyler Rhodes on 2/23/14. 6 | // Copyright (c) 2014 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"MusicUnlimited", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*music.sonyentertainmentnetwork.com*'", 14 | args: ["URL"] 15 | }, 16 | toggle: function () { document.querySelector('#PlayerPlayPause').click(); }, 17 | next: function () { document.querySelector('#PlayerNext').click(); }, 18 | previous: function () { document.querySelector('#PlayerPrevious').click(); }, 19 | favorite: function () { document.querySelector('#PlayerLike').click(); } 20 | } 21 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Vimeo.js: -------------------------------------------------------------------------------- 1 | // 2 | // Vimeo.plist 3 | // BeardedSpice 4 | // 5 | // Created by Antoine Hanriat on 08/08/14. 6 | // Copyright (c) 2014 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"Vimeo", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*vimeo.com*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () {}, 17 | toggle: function () {return window.vimeo.active_player.paused?window.vimeo.active_player.play():window.vimeo.active_player.pause()}, 18 | next: function () {}, 19 | favorite: function () {}, 20 | previous: function () {}, 21 | pause: function () {return window.vimeo.active_player.pause()}, 22 | trackInfo: function () {} 23 | } 24 | -------------------------------------------------------------------------------- /SharedComponents/BSSharedDefaults.h: -------------------------------------------------------------------------------- 1 | // 2 | // BSSharedDefaults.h 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 05.03.16. 6 | // Copyright © 2016 BeardedSpice. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | extern NSString *const BeardedSpicePlayPauseShortcut; 12 | extern NSString *const BeardedSpiceNextTrackShortcut; 13 | extern NSString *const BeardedSpicePreviousTrackShortcut; 14 | extern NSString *const BeardedSpiceActiveTabShortcut; 15 | extern NSString *const BeardedSpiceFavoriteShortcut; 16 | extern NSString *const BeardedSpiceNotificationShortcut; 17 | extern NSString *const BeardedSpiceActivatePlayingTabShortcut; 18 | extern NSString *const BeardedSpicePlayerNextShortcut; 19 | extern NSString *const BeardedSpicePlayerPreviousShortcut; 20 | -------------------------------------------------------------------------------- /BeardedSpiceTests/BeardedSpiceTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /BeardedSpice/Tabs/SafariTabAdapter.h: -------------------------------------------------------------------------------- 1 | // 2 | // SafariTabAdapter.h 3 | // BeardedSpice 4 | // 5 | // Created by Jose Falcon on 12/10/13. 6 | // Copyright (c) 2013 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | 9 | #import "TabAdapter.h" 10 | #import "Safari.h" 11 | 12 | #define APPID_SAFARI @"com.apple.Safari" 13 | 14 | @class runningSBApplication; 15 | 16 | @interface SafariTabAdapter : TabAdapter{ 17 | 18 | SafariTab *_previousTab; 19 | SafariWindow *_previousTopWindow; 20 | BOOL _wasWindowActivated; 21 | 22 | } 23 | 24 | +(id) initWithApplication:(runningSBApplication *)application andWindow:(SafariWindow *)window andTab:(SafariTab *)tab; 25 | 26 | @property SafariWindow *window; // we need this for the equality check 27 | @property SafariTab *tab; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/BandCamp.js: -------------------------------------------------------------------------------- 1 | // 2 | // BandCamp.plist 3 | // BeardedSpice 4 | // 5 | // Created by Jose Falcon on 12/16/13. 6 | // Copyright (c) 2013 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"BandCamp", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*bandcamp.com*'", 14 | args: ["URL"] 15 | }, 16 | toggle: function () {gplaylist.playpause()}, 17 | next: function () {gplaylist.next_track()}, 18 | previous: function () {gplaylist.prev_track()}, 19 | pause: function () {gplaylist.pause()}, 20 | trackInfo: function () { 21 | return { 22 | 'artist': EmbedData.artist, 23 | 'album': EmbedData.album_title, 24 | 'track': gplaylist.get_track_info().title 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /BeardedSpice/runningSBApplication.h: -------------------------------------------------------------------------------- 1 | // 2 | // runningApplication.h 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 07.03.15. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface runningSBApplication : NSObject{ 13 | 14 | pid_t _processIdentifier; 15 | } 16 | 17 | @property SBApplication *sbApplication; 18 | @property NSString *bundleIdentifier; 19 | @property (readonly) pid_t processIdentifier; 20 | @property (readonly) BOOL frontmost; 21 | 22 | - (instancetype)initWithApplication:(SBApplication *)application bundleIdentifier:(NSString *)bundleIdentifier; 23 | 24 | - (void)activate; 25 | - (void)hide; 26 | - (void)makeKeyFrontmostWindow; 27 | 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /BeardedSpice/NativeAppTabRegistry.h: -------------------------------------------------------------------------------- 1 | // 2 | // NativeAppTabRegistry.h 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 01.05.15. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "NativeAppTabAdapter.h" 11 | 12 | @interface NativeAppTabRegistry : NSObject{ 13 | 14 | NSMutableArray *_availableAppClasses; 15 | NSMutableDictionary *_availableCache; 16 | } 17 | 18 | + (NativeAppTabRegistry *)singleton; 19 | - (void)setUserDefaultsKey:(NSString *)defaultsKey; 20 | 21 | + (NSArray *)defaultNativeAppClasses; 22 | - (NSArray *)enabledNativeAppClasses; 23 | - (Class)classForBundleId:(NSString *)bundleId; 24 | 25 | - (void)enableNativeAppClass:(Class)appClass; 26 | - (void)disableNativeAppClass:(Class)appClass; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/LogitechMediaServer.js: -------------------------------------------------------------------------------- 1 | // 2 | // Media.plist 3 | // BeardedSpice 4 | // 5 | // Created by Jose Falcon on 12/15/13. 6 | // Copyright (c) 2013 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"Logitech Media Server", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] 'Logitech Media Server'", 14 | args: ["title"] 15 | }, 16 | toggle: function () {return window.SqueezeJS.Controller.togglePause()}, 17 | next: function () {return document.querySelectorAll('#ctrlNext button')[0].click()}, 18 | previous: function () {return document.querySelectorAll('#ctrlPrevious button')[0].click()}, 19 | pause: function () {return window.SqueezeJS.Controller.playerControl(['pause'])}, 20 | trackInfo: function () {} 21 | } 22 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategyRegistry.h: -------------------------------------------------------------------------------- 1 | // 2 | // MediaStrategyRegistry.h 3 | // BeardedSpice 4 | // 5 | // Created by Jose Falcon on 12/15/13. 6 | // Copyright (c) 2013 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | 9 | @class BSMediaStrategy; 10 | @class TabAdapter; 11 | @class BSStrategyCache; 12 | 13 | @interface MediaStrategyRegistry : NSObject 14 | @property (nonatomic, strong, readonly) BSStrategyCache *strategyCache; 15 | 16 | + (MediaStrategyRegistry *)singleton; 17 | 18 | /** 19 | Resets registry. 20 | */ 21 | - (void)setUserDefaults:(NSString *)userDefaultsKey strategyCache:(BSStrategyCache *)cache; 22 | 23 | -(void) addMediaStrategy:(BSMediaStrategy *) strategy; 24 | -(void) removeMediaStrategy:(BSMediaStrategy *) strategy; 25 | -(BSMediaStrategy *) getMediaStrategyForTab:(TabAdapter *) tab; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /BeardedSpiceControllers/BSCService.h: -------------------------------------------------------------------------------- 1 | // 2 | // BSCService.h 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 05.03.16. 6 | // Copyright © 2016 BeardedSpice. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "BSHeadphoneStatusListener.h" 11 | #import "Shortcut.h" 12 | 13 | @interface BSCService : NSObject < BSHeadphoneStatusListenerProtocol > 14 | 15 | + (BSCService *)singleton; 16 | 17 | - (void)setShortcuts:(NSDictionary *)shortcuts; 18 | 19 | - (void)setMediaKeysSupportedApps:(NSArray *)bundleIds; 20 | 21 | - (void)setPhoneUnplugActionEnabled:(BOOL)enabled; 22 | 23 | - (void)setUsingAppleRemoteEnabled:(BOOL)enabled; 24 | 25 | 26 | - (void)addConnection:(NSXPCConnection *)connection; 27 | - (void)removeConnection:(NSXPCConnection *)connection; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /BeardedSpice/BSLaunchAtLogin.h: -------------------------------------------------------------------------------- 1 | // 2 | // BSLaunchAtLogin.h 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 21.06.15. 6 | // Copyright (c) 2015 BeardedSpice. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | ///////////////////////////////////////////////////////////////////// 12 | #pragma mark - BSLaunchAtLogin 13 | ///////////////////////////////////////////////////////////////////// 14 | 15 | /** 16 | Manipulation of system preferences user login item. 17 | */ 18 | @interface BSLaunchAtLogin : NSObject 19 | 20 | ///////////////////////////////////////////////////////////////////// 21 | #pragma mark Properties and public methods 22 | ///////////////////////////////////////////////////////////////////// 23 | 24 | + (BOOL)isLaunchAtStartup; 25 | + (void)launchAtStartup:(BOOL)shouldBeLaunchAtLogin; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/HypeMachine.js: -------------------------------------------------------------------------------- 1 | // 2 | // HypeMachine.plist 3 | // BeardedSpice 4 | // 5 | // Created by Jose Falcon on 12/16/13. 6 | // Copyright (c) 2013 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"HypeMachine", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*hypem.com*'", 14 | args: ["URL"] 15 | }, 16 | toggle: function () {return window.togglePlay()}, 17 | next: function () {return window.nextTrack()}, 18 | previous: function () {return window.prevTrack()}, 19 | favorite: function (){return window.toggleFavoriteItem()}, 20 | pause: function () {return window.currentPlayerObj[0].pause()}, 21 | trackInfo: function () { 22 | return { 23 | 'artist': now_playing[0].text, 24 | 'track': now_playing[2].text 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Synology.js: -------------------------------------------------------------------------------- 1 | // 2 | // Synology.plist 3 | // BeardedSpice 4 | // 5 | // Created by Stephan van Diepen on 16/01/2014. 6 | // Copyright (c) 2013 Stephan van Diepen. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"Synology Audio Station", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*synology.me*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () {}, 17 | toggle: function () {document.querySelectorAll('.player-play button')[0].click()}, 18 | next: function () {document.querySelectorAll('.player-next button')[0].click()}, 19 | favorite: function () {}, 20 | previous: function () {document.querySelectorAll('.player-prev button')[0].click()}, 21 | pause: function () {document.querySelectorAll('.player-stop button')[0].click()}, 22 | trackInfo: function () {} 23 | } 24 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Stitcher.js: -------------------------------------------------------------------------------- 1 | // 2 | // Stitcher.plist 3 | // BeardedSpice 4 | // 5 | // Created by Christopher Williams on 3/24/14. 6 | // Copyright (c) 2014 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"Stitcher", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*stitcher.com*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () { return document.getElementById('jp_audio_0').paused; }, 17 | toggle:function () { 18 | var player = document.getElementById('jp_audio_0'); 19 | if (player.paused) { player.play() } 20 | else { player.pause() } 21 | }, 22 | next: function () {}, 23 | favorite: function () {}, 24 | previous: function () {}, 25 | pause: function () { return document.getElementById('jp_audio_0').pause(); }, 26 | trackInfo: function () {} 27 | } 28 | -------------------------------------------------------------------------------- /Utils/NSException+Utils.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSException+Utils.h 3 | // Commons 4 | // 5 | // Created by Roman Sokolov on 05.02.14. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface NSException (Utils) 12 | 13 | + (NSException *)argumentException:(NSString *)agrumentName; 14 | 15 | /** 16 | Create NSException on allocation memory error. 17 | 18 | @param objectName Name of object. May be nil. 19 | 20 | */ 21 | + (NSException *)mallocException:(NSString *)objectName; 22 | 23 | /** 24 | Create NSException on application resource available error. 25 | 26 | @param resourceName name of app resource (file name and so on). May be nil. 27 | 28 | */ 29 | + (NSException *)appResourceUnavailableException:(NSString *)resourceName; 30 | 31 | /** 32 | Create NSException on selector that does not implemented. 33 | */ 34 | + (NSException *)notImplementedException; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/BandCampWhiteLabel.js: -------------------------------------------------------------------------------- 1 | // 2 | // BandCamp.plist 3 | // BeardedSpice 4 | // 5 | // Copied from BandCamp.js and editted by Jon Bramley on 23/11/2016. 6 | // Copyright (c) 2015-2016 GPL v3 http://www.gnu.org/licenses/gpl.html 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"BandCampWhiteLabel", 11 | accepts: { 12 | method: "script", 13 | script: function () { 14 | return (RegExp("https?://bandcamp\.com").test(siteroot)); 15 | } 16 | }, 17 | toggle: function () {gplaylist.playpause()}, 18 | next: function () {gplaylist.next_track()}, 19 | previous: function () {gplaylist.prev_track()}, 20 | pause: function () {gplaylist.pause()}, 21 | trackInfo: function () { 22 | return { 23 | 'artist': EmbedData.artist, 24 | 'album': EmbedData.album_title, 25 | 'track': gplaylist.get_track_info().title 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Saavn.js: -------------------------------------------------------------------------------- 1 | // 2 | // Saavn.plist 3 | // BeardedSpice 4 | // 5 | // Created by Yash Aggarwal on 1/6/15. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"Saavn", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*saavn.com*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () {}, 17 | toggle: function () { 18 | var e = document.getElementById('play'); 19 | var t = document.getElementById('pause'); 20 | if (t.className.indexOf('hide')===-1) { t.click(); } 21 | else { e.click(); } 22 | }, 23 | next: function () { document.getElementById('fwd').click();}, 24 | previous: function () { document.getElementById('rew').click();}, 25 | pause: function () { document.getElementById('pause').click();}, 26 | trackInfo: function () {} 27 | } 28 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/LastFm.js: -------------------------------------------------------------------------------- 1 | // 2 | // LastFm.plist 3 | // BeardedSpice 4 | // 5 | // Created by Tyler Rhodes on 12/19/13. 6 | // Copyright (c) 2013 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"LastFM", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*last.fm/listen*'", 14 | args: ["URL"] 15 | }, 16 | toggle:function () { 17 | var e = document.querySelectorAll('#radioControlPlay')[0]; 18 | var t = document.querySelectorAll('#radioControlPause')[0]; 19 | var m = document.querySelectorAll('#webRadio')[0]; 20 | if(m.classList.contains('paused')) { e.click() } 21 | else { t.click() } 22 | }, 23 | next: function () {return document.querySelectorAll('#radioControlSkip')[0].click()}, 24 | pause: function () {var t=document.querySelectorAll('#radioControlPause')[0].click()} 25 | } 26 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/XboxMusic.js: -------------------------------------------------------------------------------- 1 | // 2 | // XboxMusic.plist 3 | // BeardedSpice 4 | // 5 | // Created by Jonathan Ruiz on 5/20/14. 6 | // Copyright (c) 2014 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:2, 10 | displayName:"Xbox Music", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*music.microsoft.com*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () {}, 17 | toggle: function () {window.app.mainViewModel.playerVM.togglePause()}, 18 | next: function () {window.app.mainViewModel.playerVM.next()}, 19 | favorite: function () {}, 20 | previous: function () {window.app.mainViewModel.playerVM.previous()}, 21 | pause: function () { 22 | var app = window.app.mainViewModel.playerVM; 23 | if(app.isPlayingOrLoading()) { 24 | app.togglePause() 25 | } 26 | }, 27 | trackInfo: function () {} 28 | } 29 | -------------------------------------------------------------------------------- /BeardedSpice/Tabs/NativeAppTabAdapter.m: -------------------------------------------------------------------------------- 1 | // 2 | // NativeAppTabAdapter.m 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 26.04.15. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | 9 | #import "NativeAppTabAdapter.h" 10 | #import "runningSBApplication.h" 11 | 12 | #pragma clang diagnostic push 13 | #pragma clang diagnostic ignored "-Wincomplete-implementation" 14 | 15 | @implementation NativeAppTabAdapter 16 | 17 | +(id)tabAdapterWithApplication:(runningSBApplication *)application{ 18 | 19 | NativeAppTabAdapter *tab = [[self class] new]; 20 | 21 | tab.application = application; 22 | return tab; 23 | } 24 | 25 | + (NSString *)displayName{ 26 | return nil; 27 | } 28 | 29 | + (NSString *)bundleId{ 30 | return nil; 31 | } 32 | 33 | - (BOOL)showNotifications{ 34 | return YES; 35 | } 36 | 37 | 38 | @end 39 | 40 | #pragma clang diagnostic pop -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Chorus.js: -------------------------------------------------------------------------------- 1 | // 2 | // Chorus.plist 3 | // BeardedSpice 4 | // 5 | // Created by Mark Reid on 10/01/14. 6 | // Copyright (c) 2013 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"Chorus", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '▶ * | Chorus.'", 14 | args: ["title"] 15 | }, 16 | toggle: function () {return app.audioStreaming.getPlayer() === 'local' ? app.audioStreaming.togglePlay() : app.shellView.playerPlay() }, 17 | next: function () {return app.audioStreaming.getPlayer() === 'local' ? app.audioStreaming.next() : app.shellView.playerNext()}, 18 | previous: function () {return app.audioStreaming.getPlayer() === 'local' ? app.audioStreaming.prev() : app.shellView.playerPrev() }, 19 | pause: function () {return app.audioStreaming.getPlayer() === 'local' ? app.audioStreaming.pause(): true }, 20 | } 21 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/MixcloudBeta.js: -------------------------------------------------------------------------------- 1 | // 2 | // MixcloudBeta.plist 3 | // BeardedSpice 4 | // 5 | BSStrategy = { 6 | version:1, 7 | displayName:"Mixcloud Beta", 8 | accepts: { 9 | method: "predicateOnTab", 10 | format:"%K LIKE[c] '*beta.mixcloud.com*'", 11 | args: ["URL"] 12 | }, 13 | isPlaying: function() {return (document.querySelector('.mz-player-control.mz-pause-state') != null)}, 14 | pause: function () { var aButton = document.querySelector('.mz-player-control.mz-pause-state'); if(aButton) aButton.click() }, 15 | toggle: function () { document.querySelector('.mz-player-control').click(); }, 16 | trackInfo: function () { 17 | return { 18 | 'track': document.querySelector('.mz-player-cloudcast-title').text, 19 | 'artist': document.querySelector('.mz-player-cloudcast-author-link').text, 20 | 'image' : document.querySelector('div.mz-player img.loaded').getAttribute('src') 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BeardedSpice/Tabs/TabAdapter.h: -------------------------------------------------------------------------------- 1 | // 2 | // TabAdapter.h 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 11.04.15. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | 9 | @class runningSBApplication; 10 | 11 | @interface TabAdapter : NSObject{ 12 | 13 | BOOL _wasActivated; 14 | } 15 | 16 | -(id) executeJavascript:(NSString *) javascript; 17 | -(NSString *) title; 18 | -(NSString *) URL; 19 | -(NSString *) key; 20 | - (BOOL)check; 21 | 22 | - (void)activateTab; 23 | - (BOOL)isActivated; 24 | - (void)toggleTab; 25 | - (BOOL)frontmost; 26 | 27 | @property runningSBApplication *application; 28 | 29 | /** 30 | Copying of the variables, which reflect state of the object. 31 | 32 | @param tab Object from which performed copying. 33 | 34 | @return Returns self. 35 | */ 36 | - (instancetype)copyStateFrom:(TabAdapter *)tab; 37 | 38 | -(BOOL) isEqual:(__autoreleasing id)otherTab; 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/ShufflerFm.js: -------------------------------------------------------------------------------- 1 | // 2 | // ShufflerFm.plist 3 | // BeardedSpice 4 | // 5 | // Created by Breyten Ernsting on 1/16/14. 6 | // Copyright (c) 2013 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"Shuffler.fm", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*shuffler.fm/tracks*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () {}, 17 | toggle: function () { 18 | var ap=window.SHUFFLER.audioPlayer; 19 | if(ap.playing()) { 20 | ap.pause(); 21 | } else { 22 | ap.play(); 23 | } 24 | }, 25 | next: function () {window.SHUFFLER.playerController.onAudioPlayerPlaybackEndHandler();}, 26 | favorite: function () {}, 27 | previous: function () {SHUFFLER.playerController.onPlayerUiButtonPrevHandler();}, 28 | pause: function () {window.SHUFFLER.audioPlayer.pause();}, 29 | trackInfo: function () {} 30 | } 31 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/GrooveShark.js: -------------------------------------------------------------------------------- 1 | // 2 | // GrooveShark.plist 3 | // BeardedSpice 4 | // 5 | // Created by Jose Falcon on 12/16/13. 6 | // Copyright (c) 2013 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"Grooveshark", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*grooveshark.im*'", 14 | args: ["URL"] 15 | }, 16 | toggle: function () {return window.Grooveshark.togglePlayPause()}, 17 | next: function () { playNextSong(0); }, 18 | favorite: function () {return window.Grooveshark.favoriteCurrentSong()}, 19 | previous: function () { playBackSong(); }, 20 | pause: function () { pause(); }, 21 | trackInfo: function () { 22 | var data = window.Grooveshark.getCurrentSongStatus()["song"]; 23 | return { 24 | 'track': data["songName"], 25 | 'album': data["albumName"], 26 | 'artist': data["artistName"], 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Stripewaves.js: -------------------------------------------------------------------------------- 1 | // 2 | // Stripewaves.plist 3 | // BeardedSpice 4 | // 5 | // Created by Wouter Beugelsdijk on 31/08/16. 6 | // 7 | BSStrategy = { 8 | version:1, 9 | displayName:"Stripewaves", 10 | accepts: { 11 | method: "predicateOnTab", 12 | format:"%K LIKE[c] '*stripewaves.com*'", 13 | args: ["URL"] 14 | }, 15 | isPlaying: function () {$('.playButton').hasClass('is-playing')}, 16 | toggle: function () {$('.playButton').click()}, 17 | next: function () {$('.nextButton').click()}, 18 | favorite: function () {}, 19 | previous: function () {$('.prevButton').click()}, 20 | pause: function () {$('.playButton').hasClass('is-playing') && $('.playButton').click()}, 21 | trackInfo: function () { 22 | return { 23 | 'image': $(".is-playing .track-artwork").attr('src'), 24 | 'track': $(".currentTrack-title").text(), 25 | 'artist': $(".currentTrack-user").text() 26 | }; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/TwentyTwoTracks.js: -------------------------------------------------------------------------------- 1 | // 2 | // TwentyTwoTracks.plist 3 | // BeardedSpice 4 | // 5 | // Created by Jan Pochyla on 08/26/14. 6 | // Copyright (c) 2013 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"22tracks", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*22tracks.com*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () {}, 17 | toggle: function () {angular.element(document.querySelector('.player .ng-scope')).scope().Audio.playpause()}, 18 | next: function () {angular.element(document.querySelector('.player .ng-scope')).scope().Audio.next()}, 19 | favorite: function () {}, 20 | previous: function () {angular.element(document.querySelector('.player .ng-scope')).scope().Audio.previous()}, 21 | pause: function () {angular.element(document.querySelector('.player .ng-scope')).scope().Audio.pause()}, 22 | trackInfo: function () {} 23 | } 24 | -------------------------------------------------------------------------------- /BeardedSpice/Tabs/ChromeTabAdapter.h: -------------------------------------------------------------------------------- 1 | // 2 | // ChromeTabAdapter.h 3 | // BeardedSpice 4 | // 5 | // Created by Jose Falcon on 12/10/13. 6 | // Copyright (c) 2013 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | 9 | #import "TabAdapter.h" 10 | #import "Chrome.h" 11 | 12 | #define APPID_CHROME @"com.google.Chrome" 13 | #define APPID_CANARY @"com.google.Chrome.canary" 14 | #define APPID_YANDEX @"ru.yandex.desktop.yandex-browser" 15 | #define APPID_CHROMIUM @"org.chromium.Chromium" 16 | 17 | @class runningSBApplication; 18 | 19 | @interface ChromeTabAdapter : TabAdapter { 20 | 21 | NSInteger _previousTabId; 22 | ChromeWindow *_previousTopWindow; 23 | BOOL _wasWindowActivated; 24 | } 25 | 26 | +(id) initWithApplication:(runningSBApplication *)application andWindow:(ChromeWindow *)window andTab:(ChromeTab *)tab; 27 | 28 | @property ChromeTab *tab; 29 | @property ChromeWindow *window; 30 | @property BOOL applescriptIsolatedVersion; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /Utils/NSArray+Utils.m: -------------------------------------------------------------------------------- 1 | // 2 | // 3 | // Created by Roman Sokolov on 12.02.14. 4 | // Copyright (c) 2014 Roman Sokolov. All rights reserved. 5 | // 6 | 7 | #import "NSArray+Utils.h" 8 | 9 | 10 | /// Stack methods for NSMutableArray 11 | @implementation NSMutableArray (Stack) 12 | 13 | - (id)pop 14 | { 15 | id obj = [self lastObject]; 16 | if (obj) { 17 | [self removeLastObject]; 18 | } 19 | 20 | return obj; 21 | } 22 | - (void)push:(id)obj 23 | { 24 | [self addObject:obj]; 25 | } 26 | - (id)peekStack 27 | { 28 | return [self lastObject]; 29 | } 30 | 31 | @end 32 | 33 | /// Queue methods for NSMutableArray 34 | @implementation NSMutableArray (Queue) 35 | 36 | - (id)dequeue; 37 | { 38 | id obj = [self firstObject]; 39 | if (obj) { 40 | [self removeObjectAtIndex:0]; 41 | } 42 | 43 | return obj; 44 | } 45 | - (void)enqueue:(id)obj 46 | { 47 | [self addObject:obj]; 48 | } 49 | - (id)peekQueue 50 | { 51 | return [self firstObject]; 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/NoAdRadio.js: -------------------------------------------------------------------------------- 1 | // 2 | // NoAdRadio.plist 3 | // BeardedSpice 4 | // 5 | BSStrategy = { 6 | version:1, 7 | displayName:"NoAdRadio", 8 | accepts: { 9 | method: "predicateOnTab", 10 | format:"%K LIKE[c] '*noadradio.com*'", 11 | args: ["URL"] 12 | }, 13 | isPlaying: function () { return (document.querySelector('#btn-playpause.pause') != null);}, 14 | toggle: function () {document.querySelector('a#btn-playpause').click()}, 15 | previous: function () {}, 16 | next: function () {document.querySelector('a#btn-ff').click()}, 17 | favorite: function () {}, 18 | pause:function () { 19 | var e = document.querySelector('a#btn-playpause.pause'); 20 | if(e != null) { e.click(); } 21 | }, 22 | trackInfo: function () { 23 | return { 24 | 'track': $('#current-song')[0].innerText, 25 | 'artist': $('#player_current_artist a')[0].innerText, 26 | 'favorited': false, 27 | 'image': $('#player_main_pic_img').attr('src') 28 | }; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /BeardedSpiceControllers/Controllers/SPMediaKeyTap/NSObject+SPInvocationGrabbing.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface SPInvocationGrabber : NSObject { 4 | id _object; 5 | NSInvocation *_invocation; 6 | int frameCount; 7 | char **frameStrings; 8 | BOOL backgroundAfterForward; 9 | BOOL onMainAfterForward; 10 | BOOL waitUntilDone; 11 | } 12 | -(id)initWithObject:(id)obj; 13 | -(id)initWithObject:(id)obj stacktraceSaving:(BOOL)saveStack; 14 | @property (readonly, retain, nonatomic) id object; 15 | @property (readonly, retain, nonatomic) NSInvocation *invocation; 16 | @property BOOL backgroundAfterForward; 17 | @property BOOL onMainAfterForward; 18 | @property BOOL waitUntilDone; 19 | -(void)invoke; // will release object and invocation 20 | -(void)printBacktrace; 21 | -(void)saveBacktrace; 22 | @end 23 | 24 | @interface NSObject (SPInvocationGrabbing) 25 | -(id)grab; 26 | -(id)invokeAfter:(NSTimeInterval)delta; 27 | -(id)nextRunloop; 28 | -(id)inBackground; 29 | -(id)onMainAsync:(BOOL)async; 30 | @end 31 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/MixCloud.js: -------------------------------------------------------------------------------- 1 | // 2 | // MixCloud.plist 3 | // BeardedSpice 4 | // 5 | // Created by Tyler Rhodes on 2/23/14. 6 | // Copyright (c) 2014 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:3, 10 | displayName:"Mixcloud", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*www.mixcloud.com*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function() {return (document.querySelector('.player-control.pause-state') != null)}, 17 | pause: function () { var aButton = document.querySelector('.player-control.pause-state'); if(aButton) aButton.click() }, 18 | toggle: function () { document.querySelector('.player-control').click(); }, 19 | trackInfo: function () { 20 | return { 21 | 'track': document.querySelector('.player-cloudcast-title').text, 22 | 'artist': document.querySelector('.player-cloudcast-author-link').text, 23 | 'image' : document.querySelector('div.player img.loaded').getAttribute('src') 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Zing.js: -------------------------------------------------------------------------------- 1 | // 2 | // Zing.plist 3 | // BeardedSpice 4 | // 5 | // Created by Alvin Nguyen on 06/23/16. 6 | // 7 | BSStrategy = { 8 | version: 1, 9 | displayName: "Zing MP3", 10 | accepts: { 11 | method: "predicateOnTab", 12 | format: "%K LIKE[c] '*mp3.zing.vn/*'", 13 | args: ["URL"] 14 | }, 15 | toggle: function(){ if (document.querySelector('.jp-play').style.display === 'none') { document.querySelector('.jp-pause').click(); } else { document.querySelector('.jp-play').click();} }, 16 | previous: function(){ document.querySelector('.fn-prev').click(); }, 17 | next: function(){ document.querySelector('.fn-next').click(); }, 18 | pause: function(){ document.querySelector('.jp-pause').click(); }, 19 | trackInfo: function () { 20 | return { 21 | 'image': document.querySelector('.pthumb').getAttribute('src'), 22 | 'track': document.querySelector('.fn-song.fn-current .fn-name').innerText, 23 | 'artist': document.querySelector('.fn-song.fn-current h4 a').innerText 24 | }; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Fip.js: -------------------------------------------------------------------------------- 1 | // 2 | // YouTube.plist 3 | // BeardedSpice 4 | // 5 | // Created by Jean Bertrand on 10/10/16. 6 | // Copyright (c) 2016 GPL v3 http://www.gnu.org/licenses/gpl.html 7 | // 8 | BSStrategy = { 9 | version: 1, 10 | displayName: "Fip", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format: "%K LIKE[c] '*fipradio.fr/player*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () { return (document.querySelector('#player-controls .stop') !== null); }, 17 | toggle: function () { document.querySelector('#player-controls button').click();; }, 18 | previous: function () { }, 19 | next: function () { }, 20 | pause: function () { document.querySelector('#player-controls button').click(); }, 21 | trackInfo: function () { 22 | return { 23 | 'image': document.querySelector('.cover .picture').getAttribute('src'), 24 | 'track': document.querySelector('.infos .title').getAttribute('title'), 25 | 'artist': document.querySelector('.infos .author .name').getAttribute('title') 26 | }; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/LeTournedisque.js: -------------------------------------------------------------------------------- 1 | // 2 | // LeTournedisque.plist 3 | // BeardedSpice 4 | // 5 | // Created by Jonas Friedmann on 18.05.15. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"LeTournedisque", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*letournedisque.com*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () {return (document.querySelectorAll('.playing')[0]) ? true : false}, 17 | toggle: function () {return document.querySelectorAll('div.play')[0].click()}, 18 | next: function () {return document.querySelectorAll('div.next')[0].click()}, 19 | previous: function () {return document.querySelectorAll('div.prev')[0].click()}, 20 | pause: function () {return document.querySelectorAll('div.playing')[0].click()}, 21 | trackInfo: function () { 22 | return { 23 | 'artist': $('.info-text .artiste strong, .info-text .artiste a').text(), 24 | 'track': $.trim($('.info-text .name').text()) 25 | }; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/AppleDeveloper.js: -------------------------------------------------------------------------------- 1 | // 2 | // AppleDeveloper.js 3 | // BeardedSpice 4 | // 5 | // Created by Chloe Stars on 8/16/16. 6 | // Copyright © 2016 BeardedSpice. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"Apple Developer", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*developer.apple.com/videos/play/*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () {return !(document.querySelector('video').paused);}, 17 | toggle: function () { 18 | var theVideo = document.getElementsByTagName("video")[0]; 19 | if (theVideo.paused) { theVideo.play(); } 20 | else { theVideo.pause() } 21 | }, 22 | next: function () {}, 23 | favorite: function () {}, 24 | previous: function () {}, 25 | pause: function () {document.getElementsByTagName("video")[0].pause();}, 26 | trackInfo: function () { 27 | return { 28 | 'track': document.getElementsByClassName("supplement details active")[0].getElementsByTagName("h1")[0].textContent, 29 | 'album': 'Apple' 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/PocketCasts.js: -------------------------------------------------------------------------------- 1 | // 2 | // PocketCasts.plist 3 | // BeardedSpice 4 | // 5 | // Created by Dmytro Piliugin on 1/23/15. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"PocketCasts", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*play.pocketcasts.com*'", 14 | args: ["URL"] 15 | }, 16 | toggle: function () {document.querySelector('div.play_pause_button').click()}, 17 | next: function () {document.querySelector('div.skip_forward_button').click()}, 18 | previous: function () {document.querySelector('div.skip_back_button').click()}, 19 | pause: function () {document.querySelector('div.pause_button').click()}, 20 | trackInfo: function () { 21 | return { 22 | 'track': document.querySelector('div.player_top div.player_episode').innerText, 23 | 'album': document.querySelector('div.player_top div.player_podcast_title').innerText, 24 | 'image': document.querySelector('div.player_top div.player_artwork img').src, 25 | }; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/BrainFm.js: -------------------------------------------------------------------------------- 1 | // 2 | // BrainFmStrategy.m 3 | // BeardedSpice 4 | // 5 | // Created by James Greenleaf on 03/05/16. 6 | // Copyright (c) 2013 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version: 1, 10 | displayName: "Brain.fm", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format: "%K LIKE[c] '*brain.fm/app*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () { 17 | var p = document.querySelector('#play_button'); 18 | return p.classList.contains('tc_pause'); 19 | }, 20 | toggle: function () {document.querySelectorAll('#play_button')[0].click();}, 21 | previous: function () {}, 22 | next: function () {return document.querySelectorAll('#skip_button')[0].click()}, 23 | pause: function () { 24 | var p = document.querySelectorAll('#play_button')[0]; 25 | if(p.classList.contains('tc_pause')){ 26 | p.click(); 27 | } 28 | }, 29 | favorite: function () {}, 30 | trackInfo: function (){ 31 | return { 32 | track: document.querySelector('#playing_title').textContent 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Jango.js: -------------------------------------------------------------------------------- 1 | // 2 | // JangoMedia.plist 3 | // BeardedSpice 4 | // 5 | // Created by Stanislav Sidelnikov on 09/11/15. 6 | // Copyright © 2015 BeardedSpice. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"Jango", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*jango.com*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () { return (document.querySelector('#btn-playpause.pause') != null);}, 17 | toggle: function () {document.querySelector('a#btn-playpause').click()}, 18 | next: function () {document.querySelector('a#btn-ff').click()}, 19 | previous: function () {}, 20 | favorite: function () {}, 21 | pause:function () { 22 | var e = document.querySelector('a#btn-playpause.pause'); 23 | if(e != null) { e.click(); } 24 | }, 25 | trackInfo: function () { 26 | return { 27 | 'track': $('#current-song')[0].innerText, 28 | 'artist': $('#player_current_artist a')[0].innerText, 29 | 'favorited': false, 30 | 'image': $('#player_main_pic_img').attr('src') 31 | }; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Beatguide.js: -------------------------------------------------------------------------------- 1 | // 2 | // Beatguide.plist 3 | // BeardedSpice 4 | // 5 | // Created by Colin White on 08/04/15. 6 | // Copyright (c) 2013 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"Beatguide", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*beatguide.me*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () {return ($('.pause-icon').css('display') == 'block');}, 17 | toggle: function () {return document.querySelectorAll('.play-icon')[0].click()}, 18 | next: function () {return document.querySelectorAll('.fa-forward')[0].click()}, 19 | previous: function () {return document.querySelectorAll('.fa-backward')[0].click()}, 20 | pause: function () {return document.querySelectorAll('.fa-pause')[0].click()}, 21 | trackInfo: function () { 22 | return { 23 | 'track': document.querySelectorAll('.track-title')[0].innerText, 24 | 'artist': document.querySelectorAll('.artist-name')[0].innerText, 25 | 'image': document.querySelectorAll('.track-artwork')[0].src 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /SharedComponents/BSSharedDefaults.m: -------------------------------------------------------------------------------- 1 | // 2 | // BSSharedDefaults.m 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 05.03.16. 6 | // Copyright © 2016 BeardedSpice. All rights reserved. 7 | // 8 | 9 | #import "BSSharedDefaults.h" 10 | 11 | #define HOST_APP_BUNDLE_ID @"com.beardedspice.BeardedSpice" 12 | 13 | NSString *const BeardedSpicePlayPauseShortcut = @"BeardedSpicePlayPauseShortcut"; 14 | NSString *const BeardedSpiceNextTrackShortcut = @"BeardedSpiceNextTrackShortcut"; 15 | NSString *const BeardedSpicePreviousTrackShortcut = @"BeardedSpicePreviousTrackShortcut"; 16 | NSString *const BeardedSpiceActiveTabShortcut = @"BeardedSpiceActiveTabShortcut"; 17 | NSString *const BeardedSpiceFavoriteShortcut = @"BeardedSpiceFavoriteShortcut"; 18 | NSString *const BeardedSpiceNotificationShortcut = @"BeardedSpiceNotificationShortcut"; 19 | NSString *const BeardedSpiceActivatePlayingTabShortcut = @"BeardedSpiceActivatePlayingTabShortcut"; 20 | NSString *const BeardedSpicePlayerNextShortcut = @"BeardedSpicePlayerNextShortcut"; 21 | NSString *const BeardedSpicePlayerPreviousShortcut = @"BeardedSpicePlayerPreviousShortcut"; 22 | 23 | -------------------------------------------------------------------------------- /BeardedSpice/Tabs/NativeAppTabAdapter.h: -------------------------------------------------------------------------------- 1 | // 2 | // NativeAppTabAdapter.h 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 26.04.15. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | 9 | #import "TabAdapter.h" 10 | 11 | @class BSTrack; 12 | 13 | @interface NativeAppTabAdapter : TabAdapter 14 | 15 | +(id)tabAdapterWithApplication:(runningSBApplication *)application; 16 | 17 | ////////////////////////////////////////////////////////////// 18 | #pragma mark Player control methods 19 | ////////////////////////////////////////////////////////////// 20 | 21 | /** 22 | Returns name of that native app. 23 | */ 24 | + (NSString *)displayName; // Required override in subclass. 25 | 26 | /** 27 | Returns bundle identifier of that native app. 28 | */ 29 | + (NSString *)bundleId; // Required override in subclass. 30 | 31 | - (void)toggle; 32 | - (void)pause; 33 | - (void)next; 34 | - (void)previous; 35 | - (void)favorite; 36 | 37 | - (BSTrack *)trackInfo; 38 | - (BOOL)isPlaying; 39 | 40 | /** 41 | Indicates when BeardedSpice may display notifications. 42 | */ 43 | - (BOOL)showNotifications; 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/AmazonMusic.js: -------------------------------------------------------------------------------- 1 | // 2 | // AmazonMusic.plist 3 | // BeardedSpice 4 | // 5 | // Created by Brandon P Smith on 7/23/14. 6 | // Copyright (c) 2014 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"Amazon Music", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*music.amazon.*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () {return window.amznMusic.widgets.player.isPlaying();}, 17 | toggle: function () {return window.amznMusic.widgets.player.playHash('togglePlay')}, 18 | next: function () {return window.amznMusic.widgets.player.playHash('next')}, 19 | previous: function () {return window.amznMusic.widgets.player.playHash('previous')}, 20 | pause: function () {window.amznMusic.widgets.player.pause();}, 21 | trackInfo: function () { 22 | var data = window.amznMusic.widgets.player.getCurrent()['metadata']; 23 | return { 24 | 'track': data['title'], 25 | 'artist': data['albumName'], 26 | 'album': data['artistName'], 27 | 'image': data['albumCoverImageSmall'] 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Anghami.js: -------------------------------------------------------------------------------- 1 | // 2 | // Anghami.js 3 | // BeardedSpice 4 | // 5 | // Created by Raja Baz on 08/24/2016. 6 | // 7 | 8 | BSStrategy = { 9 | version:1, 10 | displayName:"Anghami", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*play.anghami.com*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () { 17 | return ($(".action.play .icon-pause").length + $(".action.play .loader").length) > 0; 18 | }, 19 | toggle: function () { 20 | $(".action.play").click(); 21 | }, 22 | next: function () { 23 | $('.action.next').click(); 24 | }, 25 | favorite: function () { 26 | $(".action.extras .icon-like").click(); 27 | }, 28 | previous: function () { 29 | $('.action.previous').click(); 30 | }, 31 | pause: function () { 32 | $(".action.play .icon-pause").click(); 33 | }, 34 | trackInfo: function () { 35 | return { 36 | 'track': $("a.track-title").text(), 37 | 'artist': $("a.track-artist").text(), 38 | 'image': $(".cover-art img")[0].src, 39 | }; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /BeardedSpice/Preferences/ShortcutsPreferencesViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AdvansedPreferencesViewController.h 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 13.03.15. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MASPreferencesViewController.h" 11 | #import "Shortcut.h" 12 | 13 | @interface ShortcutsPreferencesViewController : NSViewController 14 | 15 | @property (nonatomic, weak) IBOutlet MASShortcutView *playPauseShortcut; 16 | @property (nonatomic, weak) IBOutlet MASShortcutView *nextTrackShortcut; 17 | @property (nonatomic, weak) IBOutlet MASShortcutView *previousTrackShortcut; 18 | @property (nonatomic, weak) IBOutlet MASShortcutView *activatePlayingTabShortcut; 19 | @property (nonatomic, weak) IBOutlet MASShortcutView *setActiveTabShortcut; 20 | @property (nonatomic, weak) IBOutlet MASShortcutView *favoriteShortcut; 21 | @property (nonatomic, weak) IBOutlet MASShortcutView *notificationShortcut; 22 | @property (nonatomic, weak) IBOutlet MASShortcutView *playerNextShortcut; 23 | @property (nonatomic, weak) IBOutlet MASShortcutView *playerPreviousShortcut; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Composed.js: -------------------------------------------------------------------------------- 1 | // 2 | // Composed.plist 3 | // BeardedSpice 4 | // 5 | // Created by Daniel Roseman on 23/06/2015. 6 | // Copyright (c) 2015 BeardedSpice. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"Composed", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*play.composed.com*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () {return document.querySelectorAll('.player-buttons__pause').length != 0}, 17 | toggle: function () {document.querySelectorAll('.player-buttons button')[1].click()}, 18 | next: function () {document.querySelectorAll('.player-buttons__next')[0].click()}, 19 | previous: function () {document.querySelectorAll('.player-buttons__previous')[0].click()}, 20 | pause: function () {document.querySelectorAll('.player-buttons__pause')[0].click()}, 21 | trackInfo: function () { 22 | return { 23 | 'track': document.querySelectorAll('.player-controls__track')[0].title, 24 | 'artist': document.querySelectorAll('.player-controls__composer')[0].textContent, 25 | 'image': document.querySelectorAll('.player-controls__packshot img')[0].src 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Odnoklassniki.js: -------------------------------------------------------------------------------- 1 | // 2 | // Odnoklassniki.plist 3 | // BeardedSpice 4 | // 5 | // Created by Alexander Chuprin on 2/16/2015. 6 | // Copyright (c) 2015 Alexander Chuprin. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"Odnoklassniki", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*ok.ru*' OR %@ LIKE[c] '*odnoklassniki.ru*'", 14 | args: ["URL", "URL"] 15 | }, 16 | toggle: function () { 17 | if (odklMusic.playingTrack() == "") { 18 | if (window['__getMusicFlash']) { 19 | __getMusicFlash().lcResume() 20 | } else { 21 | odklMusic.openAndLaunchMusicPlaying(); 22 | } 23 | } else { 24 | __getMusicFlash().lcPause(); 25 | } 26 | }, 27 | next: function () { __getMusicFlash().lcNext() }, 28 | previous: function () { __getMusicFlash().lcPrev() }, 29 | pause: function () { __getMusicFlash().lcPause(); }, 30 | trackInfo: function () { 31 | return { 32 | 'track': document.querySelector('#mmpcw .mus_player_song').firstChild.nodeValue, 33 | 'artist': document.querySelector('#mmpcw .mus_player_artist').firstChild.nodeValue 34 | }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Youtube.js: -------------------------------------------------------------------------------- 1 | // 2 | // YouTube.plist 3 | // BeardedSpice 4 | // 5 | // Created by Jose Falcon on 12/15/13. 6 | // Copyright (c) 2013 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version: 1, 10 | displayName: "Youtube", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format: "%K LIKE[c] '*youtube.com/watch*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () { return !document.querySelector('#movie_player video').paused; }, 17 | toggle: function () { document.querySelector('#movie_player .ytp-play-button').click(); }, 18 | previous: function () { document.querySelector('#movie_player .ytp-prev-button').click(); }, 19 | next: function () { document.querySelector('#movie_player .ytp-next-button').click(); }, 20 | pause: function () { document.querySelector('#movie_player video').pause(); }, 21 | trackInfo: function () { 22 | return { 23 | 'image': document.querySelector('link[itemprop=thumbnailUrl]').getAttribute('href'), 24 | 'track': document.querySelector('meta[itemprop=name]').getAttribute('content'), 25 | 'artist': document.querySelector('.yt-user-info').innerText 26 | }; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/PoolsideFM.js: -------------------------------------------------------------------------------- 1 | // 2 | // PoolsideFM.js 3 | // BeardedSpice 4 | // 5 | // Created by Coder-256 on 6/13/16. 6 | // Copyright (c) 2016 Coder-256. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version: 1, 10 | displayName: "Poolside FM", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format: "%K LIKE[c] '*poolside.fm*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () {return document.querySelectorAll(".play:not(.pause)").length == 0 }, 17 | toggle: function () {return document.querySelector(".play").click() }, 18 | next: function () {return document.querySelector(".skip").click() }, 19 | previous: function () {return true}, // No feature exists 20 | pause: function () { return document.querySelector(".pause").click() }, 21 | play: function() { return document.querySelector(".play:not(.pause)").click() }, 22 | trackInfo: function () { 23 | return { 24 | 'track': document.querySelector(".title").innerText.trim().split("\n")[0], 25 | 'artist': document.querySelector(".title").innerText.trim().split("\n")[1], 26 | 'album': "", // No feature exists 27 | 'image': "" // No feature exists 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/WatchaPlay.js: -------------------------------------------------------------------------------- 1 | // 2 | // WatchaPlayStrategy.m 3 | // BeardedSpice 4 | // 5 | // Created by KimJongMin on 2016. 3. 1.. 6 | // Copyright © 2016년 BeardedSpice. All rights reserved. 7 | // 8 | // strategy/site notes 9 | // - favorite, not implemented on this site 10 | // - next/prev not solved here, TODO: send left/right arrow key events for 5 second skips 11 | BSStrategy = { 12 | version:1, 13 | displayName:"Watcha Play", 14 | accepts: { 15 | method: "predicateOnTab", 16 | format:"%K LIKE[c] '*play.watcha.net/watch*'", 17 | args: ["URL"] 18 | }, 19 | isPlaying:function () { 20 | var v = document.querySelector('video'); 21 | return v && !v.paused; 22 | }, 23 | toggle:function () { 24 | var v=document.querySelector('video'); 25 | if (v) { v.paused ? v.play() : v.pause(); } 26 | }, 27 | next: function () {}, 28 | favorite: function () {}, 29 | previous: function () {}, 30 | pause:function () { 31 | var v=document.querySelector('video'); 32 | v && v.pause(); 33 | }, 34 | trackInfo: function () { 35 | var track = document.querySelector('.vjs-display'); 36 | return { 37 | track: track ? track.innerText : '' 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /BeardedSpice/Preferences/BSMediaStrategyEnableButton.m: -------------------------------------------------------------------------------- 1 | // 2 | // BSMediaStrategyEnableButton.m 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 08.08.15. 6 | // Copyright (c) 2015 BeardedSpice. All rights reserved. 7 | // 8 | 9 | #import "BSMediaStrategyEnableButton.h" 10 | 11 | @implementation BSMediaStrategyEnableButton 12 | 13 | - (id)initWithTableView:(NSTableView *)tableView{ 14 | if (!tableView) { 15 | return nil; 16 | } 17 | self = [super init]; 18 | if (self) { 19 | 20 | _tableView = tableView; 21 | } 22 | 23 | return self; 24 | } 25 | 26 | - (void)drawRect:(NSRect)dirtyRect { 27 | [super drawRect:dirtyRect]; 28 | 29 | // Drawing code here. 30 | } 31 | 32 | - (BOOL)acceptsFirstResponder{ 33 | 34 | if (_tableView) { 35 | 36 | NSInteger row = [_tableView rowForView:self]; 37 | NSInteger selectedRow = [_tableView selectedRow]; 38 | if (row > -1 && selectedRow > -1) { 39 | return (row == selectedRow); 40 | } 41 | } 42 | 43 | return NO; 44 | } 45 | 46 | 47 | - (void)mouseDown:(NSEvent *)theEvent{ 48 | 49 | [super mouseDown:theEvent]; 50 | } 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/RedditMusic.js: -------------------------------------------------------------------------------- 1 | // 2 | // RedditMusic.plist 3 | // BeardedSpice 4 | // 5 | // Created by Travis Emery on 11/16/16. 6 | // Copyright (c) 2015-2016 GPL v3 http://www.gnu.org/licenses/gpl.html 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"RedditMusic", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*reddit.musicplayer.io*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () { return !document.querySelector('#movie_player video').paused; }, 17 | toggle: function () { document.querySelector('.controls .item.play.button').click(); }, 18 | previous: function () { document.querySelector('.controls .item.backward.button').click(); }, 19 | next: function () { document.querySelector('.controls .item.forward.button').click(); }, 20 | pause: function () { document.querySelector('#movie_player video').pause(); }, 21 | trackInfo: function () { 22 | return { 23 | 'image': document.querySelector('link[itemprop=thumbnailUrl]').getAttribute('href'), 24 | 'track': document.querySelector('meta[itemprop=name]').getAttribute('content'), 25 | 'artist': document.querySelector('.yt-user-info').innerText 26 | }; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/ListenOnRepeat.js: -------------------------------------------------------------------------------- 1 | // 2 | // ListenOnRepeat 3 | // BeardedSpice 4 | // Created by Alexandre Daussy (Kureb) on 05/23/16. 5 | // Copyright (c) 2015 GPL v3 http://www.gnu.org/licenses/gpl.html 6 | // 7 | BSStrategy = { 8 | version:1, 9 | displayName: "ListenOnRepeat", 10 | accepts: { 11 | method: "predicateOnTab", 12 | format:"%K LIKE[c] '*listenonrepeat.com*'", 13 | args: ["URL"] 14 | }, 15 | isPlaying: function () { return document.querySelector('i.mdi-av-pause') == null; } , 16 | toggle: function () { return document.querySelector('div.control-play-pause').click(); }, 17 | previous: function () { return document.querySelector('i.mdi-av-skip-previous').click(); }, 18 | next: function () { return document.querySelector('i.mdi-av-skip-next').click(); }, 19 | pause: function () { document.querySelector('i.mdi-av-pause').click(); }, 20 | favorite: function () { document.querySelector('mdi-action-favorite-outline').click(); }, 21 | trackInfo: function() { 22 | return { 23 | 'track': document.querySelector('div.video-title').innerHTML, 24 | 'favorite': document.querySelector('div.control-heart > i:nth-child(1)').getAttribute("title").indexOf("Add") == -1 25 | }; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Spotify.js: -------------------------------------------------------------------------------- 1 | // 2 | // Spotify.plist 3 | // BeardedSpice 4 | // 5 | // Created by Jose Falcon on 12/19/13. 6 | // Copyright (c) 2013 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"Spotify", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*play*.spotify.com*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying:function() { document.querySelector('#app-player').contentWindow.document.querySelector('#play-pause').classList.contains('playing') }, 17 | toggle: function () {document.querySelectorAll('#app-player')[0].contentWindow.document.querySelectorAll('#play-pause')[0].click()}, 18 | next: function () {document.querySelectorAll('#app-player')[0].contentWindow.document.querySelectorAll('#next')[0].click()}, 19 | favorite: function () {}, 20 | previous: function () {document.querySelectorAll('#app-player')[0].contentWindow.document.querySelectorAll('#previous')[0].click()}, 21 | pause:function () { 22 | var e = document.querySelectorAll('#app-player')[0].contentWindow.document.querySelectorAll('#play-pause')[0]; 23 | if(e.classList.contains('playing')) { e.click() } 24 | }, 25 | trackInfo: function () {} 26 | } 27 | -------------------------------------------------------------------------------- /BeardedSpice/BSTrack.h: -------------------------------------------------------------------------------- 1 | // 2 | // BSTrack.m 3 | // BeardedSpice 4 | // 5 | // Created by Alex Evers on 12/01/15. 6 | // Copyright (c) 2015 GPL v3 http://www.gnu.org/licenses/gpl.html 7 | // 8 | 9 | extern NSString *const kBSTrackNameImage; 10 | extern NSString *const kBSTrackNameTrack; 11 | extern NSString *const kBSTrackNameAlbum; 12 | extern NSString *const kBSTrackNameArtist; 13 | extern NSString *const kBSTrackNameFavorited; 14 | 15 | @interface BSTrack : NSObject 16 | 17 | @property (nonatomic, strong) NSString *track; 18 | @property (nonatomic, strong) NSString *album; 19 | @property (nonatomic, strong) NSString *artist; 20 | 21 | @property (nonatomic, strong) NSImage *image; 22 | @property (nonatomic, strong) NSNumber *favorited; 23 | 24 | @property (nonatomic, strong) NSMutableDictionary *trackData; 25 | 26 | /** Constructor for encapsulating Track object 27 | @param info A pre-calculated dictionary of values that directly correspond to property key names 28 | Includes validations 29 | @return A fully initialized and validated BSTrack object with all avalailable values, placeholders otherwise. 30 | */ 31 | - (instancetype)initWithInfo:(NSDictionary *)info; 32 | 33 | - (NSUserNotification *)asNotification; 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/TuneIn.js: -------------------------------------------------------------------------------- 1 | // 2 | // TuneIn.plist 3 | // BeardedSpice 4 | // 5 | // Created by Michael Alden on 6/16/15. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"TuneIn", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*tunein.com*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () { return $('#tuner').attr('class') === 'playing' }, 17 | toggle: function() { document.querySelector('.playbutton-cont').click(); }, 18 | next: function () {}, 19 | favorite: function () { $('.icon.follow').click() }, 20 | previous: function () {}, 21 | pause: function () { 22 | if($('#tuner').attr('class') == 'playing'){ 23 | document.querySelector('.playbutton-cont').click(); 24 | } 25 | }, 26 | trackInfo: function () { 27 | var ret = TuneIn.app.nowPlaying.broadcast; 28 | return { 29 | 'track': ret.DisplaySubtitle, 30 | 'album': ret.EchoData.title, 31 | 'artist': ret.Location, 32 | 'favorited': $('#tuner div.icon.follow').hasClass('in'), 33 | 'image': $('.artwork.col._navigateNowPlaying').children('.image').children('.logo.loaded').attr('src'), 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /BeardedSpice/Tabs/Downcast.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Downcast.h 3 | */ 4 | 5 | #import 6 | #import 7 | 8 | 9 | @class DowncastApplication, DowncastNowPlayingInfo; 10 | 11 | 12 | 13 | /* 14 | * Downcast Script Suite 15 | */ 16 | 17 | // The application's top-level scripting object. 18 | @interface DowncastApplication : SBApplication 19 | 20 | @property (copy, readonly) DowncastNowPlayingInfo *nowPlayingInfo; 21 | 22 | - (void) playpause; // Toggles play/pause state. Pauses if currently playing, plays if currently paused 23 | - (void) play; // Plays if currently paused 24 | - (void) pause; // Pauses if currently playing 25 | - (void) next; 26 | - (void) previous; 27 | 28 | @end 29 | 30 | @interface DowncastNowPlayingInfo : SBObject 31 | 32 | @property (copy, readonly) NSString *episodeTitle; 33 | @property (copy, readonly) NSString *sourceTitle; // The title of the podcast or playlist this episode is being played from. 34 | @property NSInteger duration; 35 | @property NSInteger playPosition; 36 | @property (copy, readonly) NSString *mediaURL; 37 | @property (copy, readonly) NSString *publisher; 38 | @property (copy, readonly) NSData *artworkData; 39 | @property (readonly) BOOL isPlaying; 40 | 41 | 42 | @end 43 | 44 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Gaana.js: -------------------------------------------------------------------------------- 1 | // 2 | // Gaana.js 3 | // BeardedSpice 4 | // 5 | // Created by Coder-256 on 6/12/16. 6 | // Copyright (c) 2016 Coder-256. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version: 1, 10 | displayName: "Gaana", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format: "%K LIKE[c] '*gaana.com*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () {return document.querySelectorAll(".pause").length > 0 }, 17 | toggle: function () {return document.querySelector(".playPause").click() }, 18 | next: function () {return document.querySelector(".next").click() }, 19 | previous: function () {return document.querySelector(".prev").click() }, 20 | pause: function () { return document.querySelector(".pause").click() }, 21 | play: function() { return document.querySelector(".play").click() }, 22 | trackInfo: function () { 23 | return { 24 | 'track': document.querySelector("#tx").innerHTML.split(//)[0], 25 | 'artist': document.querySelector("#tx > span:nth-child(2) > a").innerText, 26 | 'album': document.querySelector("#tx > span:nth-child(1) > a").innerHTML.split(//)[0], 27 | 'image': document.querySelector(".thumbHolder > img").src 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Vessel.js: -------------------------------------------------------------------------------- 1 | // 2 | // VesselStrategy.m 3 | // BeardedSpice 4 | // 5 | // Created by Coder-256 on 2/7/16. 6 | // Copyright © 2016 BeardedSpice. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version: 1, 10 | displayName: "Vessel", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format: "%K LIKE[c] '*vessel.com/videos/*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () { return !(document.querySelector('video.video-show').paused)}, 17 | toggle: function () { 18 | v = document.querySelector('video.video-show'); 19 | if (v.paused) { 20 | v.play(); 21 | } 22 | else { 23 | v.pause(); 24 | } 25 | }, 26 | previous: function () {}, 27 | next: function () {}, 28 | pause: function () {document.querySelector('video.video-show').pause()}, 29 | play: function play () {document.querySelector('video.video-show').play()}, 30 | favorite: function () {}, 31 | trackInfo: function () { 32 | return { 33 | 'track': document.title.substr(6), 34 | // FIXME selector currently is invalid 35 | //'image': document.querySelector('img[style="width:34px;height:34px;border-bottom-left-radius:4px;border-top-left-radius:4px;"]').src.replace(/?w=.*/, '') 36 | }; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Pandora.js: -------------------------------------------------------------------------------- 1 | // 2 | // Pandora.plist 3 | // BeardedSpice 4 | // 5 | // Created by Jose Falcon on 12/16/13. 6 | // Copyright (c) 2013 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"Pandora", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*pandora.com*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () { 17 | var t = document.querySelector('.pauseButton'); 18 | return (t.style.display === 'block'); 19 | }, 20 | toggle: function () { 21 | var e = document.querySelector('.playButton'); 22 | var t = document.querySelector('.pauseButton'); 23 | if(t.style.display==='block') { t.click() } 24 | else { e.click() } 25 | }, 26 | next: function () { document.querySelector('.skipButton').click(); }, 27 | pause: function () { document.querySelector('.pauseButton').click(); }, 28 | trackInfo: function () { 29 | return { 30 | 'track': document.querySelector('.playerBarSong').innerText, 31 | 'artist': document.querySelector('.playerBarArtist').innerText, 32 | 'album': document.querySelector('.playerBarAlbum').innerText, 33 | 'image': document.querySelector('.playerBarArt').src 34 | }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /BeardedSpiceControllers/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | BeardedSpiceControllers 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | NSHumanReadableCopyright 26 | Copyright © 2016 BeardedSpice. All rights reserved. 27 | XPCService 28 | 29 | JoinExistingSession 30 | 31 | RunLoopType 32 | NSRunLoop 33 | ServiceType 34 | Application 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Deezer.js: -------------------------------------------------------------------------------- 1 | // 2 | // Deezer.plist 3 | // BeardedSpice 4 | // 5 | // Created by Greg Woodcock on 06/01/2015. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | 9 | BSStrategy = { 10 | version:2, 11 | displayName:"Deezer", 12 | accepts: { 13 | method: "predicateOnTab", 14 | format:"%K LIKE[c] '*deezer.com*'", 15 | args: ["URL"] 16 | }, 17 | isPlaying: function(){return dzPlayer.isPlaying();}, 18 | toggle: function () {dzPlayer.control.togglePause()}, 19 | next: function () {dzPlayer.control.nextSong()}, 20 | favorite: function (){document.querySelector('div.player-actions span.icon-love').click()}, 21 | previous: function () {dzPlayer.control.prevSong()}, 22 | pause: function () {dzPlayer.control.pause()}, 23 | 24 | trackInfo: function () { 25 | var info = dzPlayer.getCurrentSong(); 26 | return { 27 | 'track': info["SNG_TITLE"] + (info["VERSION"] == "" ? "" : " " + info["VERSION"]), 28 | 'album': info["ALB_TITLE"], 29 | 'artist': dzPlayer.getArtistName(), 30 | 'image': 'http://cdn-images.deezer.com/images/cover/' + info["ALB_PICTURE"]+ '/250x250.jpg', 31 | 'favorited': userData.isFavorite('song',info["SNG_ID"]) 32 | }; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/HotNewHipHop.js: -------------------------------------------------------------------------------- 1 | // 2 | // HotNewHipHop.plist 3 | // BeardedSpice 4 | // 5 | // Created by Ivan Doroshenko on 11/7/15. 6 | // Copyright © 2015 BeardedSpice. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"HotNewHipHop", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*hotnewhiphop.com*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () { return document.getElementById('jp_audio_0').paused; }, 17 | toggle: function () { 18 | var player = document.getElementById('jp_audio_0'); 19 | if (player.paused) { player.play() } 20 | else { player.pause() } 21 | }, 22 | next: function () {$(".jp-next").click();}, 23 | previous: function () {$(".jp-previous").click();}, 24 | pause: function () {$("#jquery_jplayer_playlist").jPlayer("pause");}, 25 | trackInfo: function () { 26 | var album = $('.mixtape-info-title')[0].innerText; 27 | var artist = $('.mixtape-info-artist')[0].innerText; 28 | return { 29 | 'track': $('.jp-playlist-current .mixtape-trackTitle .display')[0].innerText, 30 | 'album': album.replace(/\s+/, ''), 31 | 'artist': artist.replace(/\s+/, ''), 32 | 'image': $('.mixtape-cover-img img')[0].getAttribute('src') 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/NoonPacific.js: -------------------------------------------------------------------------------- 1 | // 2 | // NoonPacific.m 3 | // BeardedSpice 4 | // 5 | // Created by Tomas on 07/05/15. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"Noon Pacific", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*noonpacific.com*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function() { return document.querySelector('.fa-pause') ? true:false;}, 17 | toggle: function () {return document.querySelectorAll('.fa-fw')[1].click()}, 18 | next: function () {return document.querySelector('.fa-forward').click()}, 19 | previous: function () {return document.querySelector('.fa-backward').click()}, 20 | pause: function () {return document.querySelector('.fa-pause').click()}, 21 | trackInfo: function () { 22 | var track = document.querySelectorAll('.track-info div p'); 23 | var imgSrc = document.querySelector('.mixtape-container img.mixtape').getAttribute('src'); 24 | var album = document.querySelector('.mixtape-container div.mixtape-label h3').innerText; 25 | return { 26 | 'track':track[0].firstChild.nodeValue, 27 | 'artist':track[1].firstChild.nodeValue, 28 | 'album':album, 29 | 'image':imgSrc 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /BeardedSpiceTests/BSStrategyMockObject.h: -------------------------------------------------------------------------------- 1 | // 2 | // BSStrategyMockObject.h 3 | // BeardedSpice 4 | // 5 | // Created by Alex Evers on 12/01/15. 6 | // Copyright (c) 2015 GPL v3 http://www.gnu.org/licenses/gpl.html 7 | // 8 | 9 | @import WebKit; 10 | 11 | typedef void (^BSVoidBlock)(void); 12 | 13 | /** 14 | This object is intended to simulate a browser environment that executes/tests 15 | the individual strategies. The environment would load a cached copy of each 16 | website and inject the strategy code to test for response. This also would 17 | provide a test against future media site changes that break existing strategies. 18 | */ 19 | @interface BSStrategyMockObject : NSObject 20 | 21 | @property (nonatomic, assign) BOOL finishedLoading; 22 | @property (nonatomic, strong) NSString *file; 23 | @property (nonatomic, strong) WebView *webView; 24 | @property (nonatomic, strong) NSString *strategyName; 25 | 26 | + (NSDictionary *)strategies; 27 | 28 | - (instancetype)initWithStrategyName:(NSString *)strategyName; 29 | 30 | - (BOOL)start; 31 | 32 | - (NSString *)evaluateScript:(NSString *)script; 33 | 34 | - (NSURL *)strategyServiceURL; 35 | - (NSURL *)strategyTemplateURL; 36 | - (NSURL *)vcrFileURL; 37 | 38 | - (void)recordStrategySite; 39 | - (void)replayStrategySite; 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Netflix.js: -------------------------------------------------------------------------------- 1 | // 2 | // Netflix.plist 3 | // BeardedSpice 4 | // 5 | // Created by Max Borghino on 12/06/15. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | // strategy/site notes 9 | // - favorite, not implemented on this site 10 | // - next/prev not solved here, TODO: send left/right arrow key events for 10 second skips 11 | // - track info consists only of the show name, no artist or artwork 12 | BSStrategy = { 13 | version:1, 14 | displayName:"Netflix", 15 | accepts: { 16 | method: "predicateOnTab", 17 | format:"%K LIKE[c] '*netflix.com/watch/*'", 18 | args: ["URL"] 19 | }, 20 | isPlaying:function () { 21 | var v=document.querySelector('video'); 22 | if (v) {v.paused ? v.play() : v.pause();} 23 | }, 24 | toggle:function () { 25 | var v=document.querySelector('video'); 26 | if (v) {v.paused ? v.play() : v.pause();} 27 | }, 28 | next: function () {}, 29 | favorite: function () {}, 30 | previous: function () {}, 31 | pause:function () { 32 | var v=document.querySelector('video'); 33 | v && v.pause(); 34 | }, 35 | trackInfo: function () { 36 | var track=document.querySelector('.player-status-main-title'); 37 | return { 38 | 'track': track ? track.innerText : '' 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /BeardedSpiceControllers/BeardedSpiceControllers.m: -------------------------------------------------------------------------------- 1 | // 2 | // BeardedSpiceControllers.m 3 | // BeardedSpiceControllers 4 | // 5 | // Created by Roman Sokolov on 05.03.16. 6 | // Copyright © 2016 BeardedSpice. All rights reserved. 7 | // 8 | 9 | #import "BeardedSpiceControllers.h" 10 | #import "BSCService.h" 11 | 12 | @implementation BeardedSpiceControllers 13 | 14 | - (void)setShortcuts:(NSDictionary *)shortcuts{ 15 | NSLog(@"setShortcuts"); 16 | 17 | [[BSCService singleton] setShortcuts:shortcuts]; 18 | } 19 | 20 | - (void)setMediaKeysSupportedApps:(NSArray *)bundleIds{ 21 | NSLog(@"setMediaKeysSupportedApps"); 22 | 23 | [[BSCService singleton] setMediaKeysSupportedApps:bundleIds]; 24 | } 25 | 26 | - (void)setPhoneUnplugActionEnabled:(BOOL)enabled{ 27 | NSLog(@"setPhoneUnplugActionEnabled"); 28 | 29 | [[BSCService singleton] setPhoneUnplugActionEnabled:enabled]; 30 | } 31 | 32 | - (void)setUsingAppleRemoteEnabled:(BOOL)enabled{ 33 | NSLog(@"setUsingAppleRemoteEnabled"); 34 | 35 | [[BSCService singleton] setUsingAppleRemoteEnabled:enabled]; 36 | } 37 | 38 | - (void)prepareForClosingConnectionWithCompletion:(void (^)(void))completion{ 39 | 40 | [[BSCService singleton] removeConnection:_connection]; 41 | 42 | completion(); 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /BeardedSpice/BeardedSpiceSuite.sdef: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/BugsMusic.js: -------------------------------------------------------------------------------- 1 | // 2 | // BugsMusicStrategy.m 3 | // BeardedSpice 4 | // 5 | // Created by Jinseop Kim on 01/03/16. 6 | // Copyright © 2016 BeardedSpice. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"Bugs Music", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*music.bugs.co.kr/newPlayer*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () { return bugs.player.isPlayingTrack; }, 17 | toggle: function () { bugs.player.playButtonHandler().call(); }, 18 | next: function () { bugs.player.nextButtonHandler().call(); }, 19 | favorite: function (){ 20 | if (document.querySelector('.btnLikeTrackCancel').style.display == "none") { 21 | bugs.player.likeButtonHandler().call(); 22 | } 23 | bugs.player.likeCancelButtonHandler().call(); 24 | }, 25 | previous: function () { bugs.player.prevButtonHandler().call(); }, 26 | pause:function () { 27 | if (bugs.player.isPlayingTrack) { 28 | bugs.player.playButtonHandler().call(); 29 | } 30 | }, 31 | trackInfo: function () { 32 | return { 33 | image: document.querySelector('.thumbnail > img').getAttribute('src'), 34 | track: bugs.player.getCurrentTrackInfo().track_title, 35 | artist: bugs.player.getCurrentTrackInfo().artist_nm, 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Coursera.js: -------------------------------------------------------------------------------- 1 | // 2 | // Odnoklassniki.plist 3 | // BeardedSpice 4 | // 5 | // Created by Andrei Glingeanu on 7/29/15. 6 | // Copyright (c) 2015 BeardedSpice. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"Coursera", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*coursera.org*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () { 17 | var v = vjs(document.querySelectorAll('.video-js')[0].querySelector('video').id); 18 | return ! v.paused(); 19 | }, 20 | toggle: function () { 21 | var v = vjs(document.querySelectorAll('.video-js')[0].querySelector('video').id); 22 | if (v.paused()) { 23 | v.play(); 24 | } else { 25 | v.pause(); 26 | } 27 | }, 28 | next: function () {return document.querySelectorAll('.c-item-side-nav-right .c-block-icon-link')[0].click()}, 29 | previous: function () {return document.querySelectorAll('.c-item-side-nav-left .c-block-icon-link')[0].click()}, 30 | pause: function () { 31 | var v = vjs(document.querySelectorAll('.video-js')[0].querySelector('video').id); 32 | v.pause(); 33 | }, 34 | trackInfo: function () { 35 | return { 36 | 'track': document.querySelector('.c-video-title').firstChild.nodeValue, 37 | 'artist': 'Coursera' 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Utils/NSString+Utils.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+Utils.h 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 14.03.15. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | 9 | @import Foundation; 10 | 11 | // FIXME change filename to match namespacing of category 12 | @interface NSString (BSUtils) 13 | 14 | + (BOOL)isNullOrEmpty:(NSString * _Nullable)str; 15 | + (BOOL)isNullOrWhiteSpace:(NSString * _Nullable)str; 16 | + (NSString * _Nullable)stringByTrim:(NSString * _Nonnull)str; 17 | 18 | /** 19 | */ 20 | -(NSString * _Nonnull)trimToLength:(NSInteger)max; 21 | 22 | /** 23 | @return index of string into receiver, or -1 if not found 24 | */ 25 | - (NSInteger)indexOf:(NSString * _Nonnull)string fromIndex:(NSUInteger)index; 26 | /** 27 | @return index of string into receiver, or -1 if not found 28 | */ 29 | - (NSInteger)indexOf:(NSString * _Nonnull)string; 30 | 31 | 32 | - (BOOL)contains:(NSString * _Nonnull)str caseSensitive:(BOOL)sensitive; 33 | 34 | /** 35 | @return the 'self' script string with '()' added to the end 36 | */ 37 | - (NSString * _Nonnull)addExecutionStringToScript; 38 | 39 | /** 40 | Returns converted string where: 41 | \ symbol replaced on \\, 42 | ' symbol replaced on \', 43 | " symbol replaced on \". 44 | */ 45 | - (NSString *_Nonnull)stringForSubstitutionInJavascriptPlaceholder; 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/IndieShuffle.js: -------------------------------------------------------------------------------- 1 | // 2 | // IndieShuffle.plist 3 | // BeardedSpice 4 | // 5 | // Created by David Davis on 2015-06-30. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"IndieShuffle", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*indieshuffle.com*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying:function () { return document.querySelector('#currentSong .commontrack.active') != undefined;}, 17 | toggle: function () {document.querySelector('#currentSong .commontrack').click()}, 18 | next: function () { 19 | if(p=document.querySelector('#playNextSong')){ 20 | p.click(); 21 | } 22 | }, 23 | previous: function () { 24 | if(p=document.querySelector('#prevSong .song_artwork')){ 25 | p.click(); 26 | } 27 | }, 28 | pause: function () { 29 | if(p=document.querySelector('#currentSong .commontrack.active')){ 30 | p.click(); 31 | } 32 | }, 33 | trackInfo: function () { 34 | var song = document.querySelector('#currentSong'); 35 | return { 36 | 'artist': song.querySelector('.artist_name').innerText, 37 | 'track': song.querySelector('.song-details').innerText, 38 | 'image':song.querySelector('img.song_artwork').getAttribute('src') 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/KCRW.js: -------------------------------------------------------------------------------- 1 | // 2 | // KCRW.js 3 | // BeardedSpice 4 | // 5 | // Created by Alan Ramos on 5/20/2016. 6 | // Copyright (c) 2016 GPL v3 http://www.gnu.org/licenses/gpl.html 7 | // 8 | 9 | BSStrategy = { 10 | version: 1, 11 | displayName: "KCRW", 12 | accepts: { 13 | method: "predicateOnTab", 14 | format: "%K LIKE[c] '*kcrw.com*'", 15 | args: ["URL"] 16 | }, 17 | 18 | isPlaying: function () { return document.querySelector('#player_start_stop').classList.contains('active'); }, 19 | toggle: function () { return document.querySelectorAll('#player_start_stop')[0].click() }, 20 | previous: function () { return document.querySelectorAll('#player_back')[0].click() }, 21 | next: function () { return document.querySelectorAll('#player_fwd')[0].click() }, 22 | pause: function () { return document.querySelectorAll('#player_start_stop')[0].click() }, 23 | favorite: function () { /* toggles favorite on/off */ }, 24 | trackInfo: function () { 25 | var meta = document.querySelector('a.playbackSoundBadge__title.sc-truncate'); 26 | return { 27 | 'track': document.querySelector('#player_subtitle>em').innerText.replace(/['"]+/g, ''), 28 | 'artist': document.getElementById("player_subtitle").childNodes[0], 29 | 'album': document.querySelector('#player_main_title').innerText 30 | }; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /BeardedSpice/Preferences/MediaControllerObject.m: -------------------------------------------------------------------------------- 1 | // 2 | // MediaControllerObject.m 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 02.05.15. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | 9 | #import "MediaControllerObject.h" 10 | #import "BSMediaStrategy.h" 11 | 12 | @implementation MediaControllerObject 13 | 14 | #pragma clang diagnostic push 15 | #pragma clang diagnostic ignored "-Wundeclared-selector" 16 | 17 | - (id)initWithObject:(id)object{ 18 | 19 | self = [super init]; 20 | if (self) { 21 | if ([object respondsToSelector:@selector(displayName)]) { 22 | _name = [object displayName]; 23 | } 24 | 25 | if ([object isMemberOfClass:BSMediaStrategy.class]) { 26 | _isCustom = [object custom]; 27 | _version = [NSString stringWithFormat:@"%ld", [object strategyVersion]]; 28 | } else if ([[object class] instancesRespondToSelector:@selector(isPlaying)] || [[object class] instancesRespondToSelector:@selector(isPlaying:)]) { 29 | _isCustom = NO; 30 | _version = [NSString string]; 31 | } 32 | 33 | _representationObject = object; 34 | } 35 | 36 | return self; 37 | } 38 | 39 | #pragma clang diagnostic pop 40 | 41 | - (id)init 42 | { 43 | return [self initWithObject:nil]; 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/SomaFm.js: -------------------------------------------------------------------------------- 1 | // 2 | // SomaFm.plist 3 | // BeardedSpice 4 | // 5 | // Created by Max Borghino on 1/28/15. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"SomaFM", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*somafm.com/player/*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () {return ( (document.querySelector('#stopBtn:not(.ng-hide)') ? true : false));}, 17 | toggle: function () {(document.querySelector('#playBtn:not(.ng-hide)') || document.querySelector('#stopBtn:not(.ng-hide)')).click()}, 18 | next: function () {}, 19 | favorite: function () {document.querySelector('.row.card').querySelector('button').click()}, 20 | previous: function () {}, 21 | pause: function () { 22 | if(p=document.querySelector('#stopBtn:not(.ng-hide)')){ 23 | p.click(); 24 | } 25 | }, 26 | trackInfo: function () { 27 | var art = document.querySelector('.img-responsive').getAttribute('src'); 28 | var card = document.querySelector('.row.card').querySelectorAll('div'); 29 | return { 30 | 'track': card[1].firstChild.innerText, 31 | 'artist': card[2].firstChild.innerText, 32 | 'favorited': card[3].firstChild.className.indexOf('btn-fav') > -1, 33 | 'image': art 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/FocusMusicFM.js: -------------------------------------------------------------------------------- 1 | // 2 | // FocusMusicFM.js 3 | // BeardedSpice 4 | // 5 | // Created by Adam Albrecht on 10/19/2016 6 | // Copyright (c) 2016 GPL v3 http://www.gnu.org/licenses/gpl.html 7 | // 8 | 9 | BSStrategy = { 10 | version: 1, 11 | displayName: "focusmusic.fm", 12 | accepts: { 13 | method: "predicateOnTab", 14 | format: "%K LIKE[c] '*focusmusic.fm*'", 15 | args: ["URL"] 16 | }, 17 | 18 | isPlaying: function () { return document.querySelector(".fa-play-circle").classList.contains('hidden'); }, 19 | toggle: function () { 20 | if (document.querySelector(".fa-play-circle").classList.contains('hidden')) { 21 | document.querySelector(".fa-pause-circle").click(); 22 | } else { 23 | document.querySelector(".fa-play-circle").click(); 24 | } 25 | }, 26 | previous: function () { 27 | document.querySelector(".controls.previous").click(); 28 | }, 29 | next: function () { 30 | document.querySelector(".controls.next").click(); 31 | }, 32 | pause: function () { 33 | document.querySelector(".fa-pause-circle").click(); 34 | }, 35 | favorite: function () { /* toggles favorite on/off */}, 36 | trackInfo: function () { 37 | return { 38 | 'track': document.querySelector(".track-title").innerText, 39 | 'artist': document.querySelector(".artist").innerText 40 | }; 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Subsonic.js: -------------------------------------------------------------------------------- 1 | // 2 | // Subsonic.plist 3 | // BeardedSpice 4 | // 5 | // Created by Michael Alden on 6/16/2015. 6 | // Copyright (c) 2014 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"Subsonic", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*Subsonic*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () { return window.frames['playQueue'].jwplayer().getState() === 'PLAYING' }, 17 | toggle:function () { window.frames['playQueue'].jwplayer().play() }, 18 | next:function () { window.frames['playQueue'].onNext() }, 19 | favorite: function () { window.frames['playQueue'].onStar(window.frames['playQueue']).getCurrentSongIndex() }, 20 | previous:function () { window.frames['playQueue'].onPrevious() }, 21 | pause:function () { window.frames['playQueue'].jwplayer().pause(true) }, 22 | trackInfo: function () { 23 | var index = window.frames['playQueue'].getCurrentSongIndex(); 24 | var playQueue = window.frames['playQueue'].songs[index]; 25 | var ret = playQueue.getCurrentSongIndex(); 26 | return { 27 | 'title': ret.title, 28 | 'album': ret.album, 29 | 'artist': ret.artist, 30 | 'favorited': ret.starred, 31 | 'image': ret.albumUrl.replace('main', 'coverArt').concat('&size=128'), 32 | }; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Blitzr.js: -------------------------------------------------------------------------------- 1 | // 2 | // Blitzr.plist 3 | // BeardedSpice 4 | // 5 | // Created by Pascal Fouque on 23/07/2015. 6 | // Copyright (c) 2015 BeardedSpice. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"Blitzr", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*blitzr.com*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () { return document.querySelector('#blitzr_playpause span.fa').className.indexOf('fa-play') == -1 }, 17 | toggle: function () {document.querySelector('#blitzr_playpause').click()}, 18 | next: function () {document.querySelector('#blitzr_next').click()}, 19 | previous: function () {document.querySelector('#blitzr_prev').click()}, 20 | pause: function () { 21 | if (document.querySelector('#blitzr_playpause span.fa').className.indexOf('fa-play') == -1) { 22 | document.querySelector('#blitzr_playpause').click() 23 | } 24 | }, 25 | trackInfo: function () { 26 | return { 27 | 'track': document.querySelector('#playerTitle strong').innerText, 28 | 'album': document.querySelector('#playerInfo .media-left a').title, 29 | 'artist': document.querySelectorAll('#playerArtists')[0].querySelector('a').innerText, 30 | 'image': document.querySelector('#playerInfo .media-left a img').style['background-image'].slice(4, -1), 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Utils/NSURL+Utils.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSURL+Utils.h 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 04.05.15. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | 9 | @import Foundation; 10 | 11 | @interface NSURL (BSUtils) 12 | 13 | /** 14 | */ 15 | - (BOOL)createDirectoriesToURL; 16 | 17 | /** 18 | Downloads data from that URL. 19 | @return NSData object, which contains requested data, or nil on failure. 20 | */ 21 | - (NSData * _Nullable)getDataWithTimeout:(NSTimeInterval)timeout; 22 | 23 | /** 24 | Application Support path to the Bearded Spice official strategy folder. 25 | */ 26 | + (NSURL * _Nonnull)URLForSavedStrategies; 27 | 28 | /** 29 | Application Support path to the Bearded Spice third-party/custom strategy folder. 30 | This path is manually managed by any given client. 31 | */ 32 | + (NSURL * _Nonnull)URLForCustomStrategies; 33 | 34 | /** 35 | Path to the BeardedSpice bundle strategy folder. 36 | */ 37 | + (NSURL * _Nonnull)URLForBundleStrategies; 38 | 39 | /** 40 | */ 41 | + (NSURL * _Nonnull)URLForFileName:(NSString * _Nullable)fileName; 42 | 43 | /** 44 | */ 45 | + (NSURL * _Nonnull)URLForFileName:(NSString * _Nonnull)fileName ofType:(NSString * _Nonnull)typeString; 46 | 47 | /** 48 | Simple wrapper for checking if the given file path exists 49 | @return BOOL yes/no if a file exists at the given filepath. 50 | */ 51 | - (BOOL)fileExists; 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Udemy.js: -------------------------------------------------------------------------------- 1 | // 2 | // Udemy.plist 3 | // BeardedSpice 4 | // 5 | // Created by Coder-256 on 10/3/15. 6 | // Copyright © 2015 BeardedSpice. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"Udemy", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*udemy.com*/lecture/*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () {return !(document.querySelector('div.ud-lectureangular > iframe').contentWindow.document.querySelector('video').paused);}, 17 | toggle: function () { 18 | var theVideo = document.querySelector('div.ud-lectureangular > iframe').contentWindow.document.getElementsByTagName("video")[0]; 19 | if (theVideo.paused) { theVideo.play(); } 20 | else { theVideo.pause() } 21 | }, 22 | next: function () {document.querySelector('div.ud-lectureangular > iframe').parent().parent().parent().find(".prev-lecture")[0].click();}, 23 | favorite: function () {}, 24 | previous: function () {document.querySelector('div.ud-lectureangular > iframe').parent().parent().next().find(".next-lecture")[0].click();}, 25 | pause: function () {document.querySelector('div.ud-lectureangular > iframe').contentWindow.document.getElementsByTagName("video")[0].pause();}, 26 | trackInfo: function () { 27 | return { 28 | 'track': document.querySelector('.curriculum-item.on .ci-title').innerText, 29 | 'album': 'Udemy' 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/YandexRadio.js: -------------------------------------------------------------------------------- 1 | // 2 | // YandexMusic.plist 3 | // BeardedSpice 4 | // 5 | // Created by Leonid Ponomarev 15.06.15 6 | // Copyright (c) 2014 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:2, 10 | displayName:"YandexRadio", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*radio.yandex.*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () { return !!document.querySelector('body.body_state_playing'); }, 17 | toggle: function () { document.querySelector('.player-controls__play').click(); }, 18 | next: function () { document.querySelector('.slider__item_track.slider__item_next .slider__item-bar').click(); }, 19 | favorite: function () { document.querySelector('.player-controls__bar .button.like.like_action_like').click(); }, 20 | previous: function () {}, 21 | pause: function () { document.querySelector('.player-controls__play').click(); }, 22 | trackInfo:function () { 23 | return { 24 | track: document.querySelector('.player-controls__title').title, 25 | artist: document.querySelector('.player-controls__artists').title, 26 | favorited: !!document.querySelector('.player-controls__bar .button.like.like_action_like.button_checked'), 27 | image: document.querySelector('.slider__item_track.slider__item_playing .track__cover') 28 | .style.backgroundImage.match(/url\(\"\/\/(.*)\"/)[1] 29 | }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/GoogleMusic.js: -------------------------------------------------------------------------------- 1 | // 2 | // GoogleMusic.plist 3 | // BeardedSpice 4 | // 5 | // Created by Jose Falcon on 1/9/14. 6 | // Copyright (c) 2014 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:2, 10 | displayName:"GoogleMusic", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*play.google.com/music/*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying:function () { 17 | var e = document.querySelector('[data-id=play-pause]'); 18 | return e.classList.contains('playing'); 19 | }, 20 | toggle: function () {document.querySelector('[data-id=play-pause]').click()}, 21 | next: function () {document.querySelector('[data-id=forward]').click()}, 22 | favorite: function () { document.querySelector('paper-icon-button[data-rating="5"]').click() }, 23 | previous: function () {document.querySelector('[data-id=rewind]').click()}, 24 | pause: function () { 25 | var e = document.querySelector('[data-id=play-pause]'); 26 | if(e.classList.contains('playing')){ 27 | e.click() 28 | } 29 | }, 30 | trackInfo: function () { 31 | return { 32 | 'track': document.getElementById('currently-playing-title').innerText, 33 | 'album': document.getElementsByClassName('player-album')[0].innerText, 34 | 'artist': document.getElementById('player-artist').innerText, 35 | 'image': document.getElementById('playerBarArt').getAttribute('src') 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Overcast.js: -------------------------------------------------------------------------------- 1 | // 2 | // Overcast.plist 3 | // BeardedSpice 4 | // 5 | // Created by Alan Clark 08/06/2014 6 | // Copyright (c) 2014 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | // strategy/site notes 9 | // - favorite: not implemented by site 10 | BSStrategy = { 11 | version:1, 12 | displayName:"Overcast.fm", 13 | accepts: { 14 | method: "predicateOnTab", 15 | format:"%K LIKE[c] '*overcast.fm*'", 16 | args: ["URL"] 17 | }, 18 | isPlaying:function () { 19 | var p=document.querySelector('#playpausebutton_playicon'); 20 | return (p && p.style.display==='none'); 21 | }, 22 | toggle: function () { document.getElementById('playpausebutton').click();}, 23 | next: function () { document.getElementById('seekforwardbutton').click();}, 24 | previous: function () { document.getElementById('seekbackbutton').click();}, 25 | pause: function () { 26 | var p=document.querySelector('#playpausebutton_playicon'); 27 | if(p && p.style.display==='none'){ 28 | document.getElementById('playpausebutton').click(); 29 | } 30 | }, 31 | trackInfo: function () { 32 | var artist=document.querySelector('.caption2 a'); 33 | var track=document.querySelector('.title'); 34 | var art=document.querySelector('.art.fullart'); 35 | return { 36 | 'artist': artist ? artist.innerText : null, 37 | 'track': track ? track.innerText : null, 38 | 'image': art ? art.getAttribute('src') : null 39 | }; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /BeardedSpiceControllers/Controllers/SPMediaKeyTap/SPMediaKeyTap.h: -------------------------------------------------------------------------------- 1 | #include 2 | #import 3 | #import 4 | 5 | // http://overooped.com/post/2593597587/mediakeys 6 | 7 | #define SPSystemDefinedEventMediaKeys 8 8 | 9 | @interface SPMediaKeyTap : NSObject { 10 | EventHandlerRef _app_switching_ref; 11 | EventHandlerRef _app_terminating_ref; 12 | CFMachPortRef _eventPort; 13 | CFRunLoopSourceRef _eventPortSource; 14 | CFRunLoopRef _tapThreadRL; 15 | BOOL _shouldInterceptMediaKeyEvents; 16 | id _delegate; 17 | // The app that is frontmost in this list owns media keys 18 | NSMutableArray *_mediaKeyAppList; 19 | } 20 | + (NSArray*)defaultMediaKeyUserBundleIdentifiers; 21 | 22 | -(id)initWithDelegate:(id)delegate; 23 | 24 | +(BOOL)usesGlobalMediaKeyTap; 25 | -(void)startWatchingMediaKeys; 26 | -(void)stopWatchingMediaKeys; 27 | -(void)handleAndReleaseMediaKeyEvent:(NSEvent *)event; 28 | 29 | @property NSArray *blackListBundleIdentifiers; 30 | 31 | @end 32 | 33 | @interface NSObject (SPMediaKeyTapDelegate) 34 | -(void)mediaKeyTap:(SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)event; 35 | @end 36 | 37 | #ifdef __cplusplus 38 | extern "C" { 39 | #endif 40 | 41 | extern NSString *kMediaKeyUsingBundleIdentifiersDefaultsKey; 42 | extern NSString *kMediaKeyUsingBlackListBundleIdentifiersDefaultsKey; 43 | extern NSString *kIgnoreMediaKeysDefaultsKey; 44 | 45 | #ifdef __cplusplus 46 | } 47 | #endif -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/YandexMusic.js: -------------------------------------------------------------------------------- 1 | // 2 | // YandexMusic.plist 3 | // BeardedSpice 4 | // 5 | // Created by Vladimir Burdukov on 3/14/14. 6 | // Copyright (c) 2014 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:2, 10 | displayName:"YandexMusic", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*music.yandex.*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () {return !!document.querySelector('body.body_show-pause');}, 17 | toggle: function () {document.querySelector('div.b-jambox__play, .player-controls__btn_play').click();}, 18 | next: function () {document.querySelector('div.b-jambox__next, .player-controls__btn_next').click();}, 19 | favorite: function () { document.querySelector('.player-controls .like.player-controls__btn').click(); }, 20 | previous: function () {document.querySelector('div.b-jambox__prev, .player-controls__btn_prev').click();}, 21 | pause: function () {document.querySelector('.player-controls__btn.player-controls__btn_play').click();}, 22 | trackInfo: function () { 23 | return { 24 | track: document.querySelector('.track.track_type_player .track__title').innerText, 25 | artist: document.querySelector('.track.track_type_player .track__artists').innerText, 26 | favorited: !!document.querySelector('.player-controls__track-controls .like.like_on'), 27 | image: document.querySelector('.album-cover').src.replace('50x50', '600x600') 28 | }; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/iHeartRadio.js: -------------------------------------------------------------------------------- 1 | // 2 | // iHeartRadioStrategy.m 3 | // BeardedSpice 4 | // 5 | // Created by Coder-256 on 2/7/16. 6 | // Copyright © 2016 BeardedSpice. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"iHeartRadio", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*iheart.com*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function() { document.querySelectorAll('[aria-label="Stop"], [aria-label="Pause"]').length > 0 }, 17 | toggle:function () { 18 | if (document.querySelectorAll('[aria-label="Stop"], [aria-label="Pause"]').length > 0) { 19 | try{ 20 | document.querySelector('[aria-label="Stop"]').click(); 21 | } catch(e){ 22 | document.querySelector('[aria-label="Pause"]').click(); 23 | } 24 | } else { 25 | var plays = document.querySelectorAll('[aria-label="Play Station"]'); 26 | plays[plays.length-1].click(); 27 | } 28 | }, 29 | next: function () {document.querySelector('[aria-label="Skip"]').click();}, 30 | favorite: function () {}, 31 | previous: function () {}, 32 | pause: function () {document.querySelector('[aria-label="Stop"]').click();}, 33 | trackInfo: function () { 34 | return { 35 | 'track': document.querySelector(".player-song").textContent, 36 | 'album': document.querySelector(".player-artist").textContent, 37 | 'image': document.querySelector(".player-art > img").src.split("?")[0] 38 | }; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Twitch.js: -------------------------------------------------------------------------------- 1 | // 2 | // Twitch.plist 3 | // BeardedSpice 4 | // 5 | // Copyright (c) 2015 GPL v3 http://www.gnu.org/licenses/gpl.html 6 | // 7 | BSStrategy = { 8 | version:1, 9 | displayName:"twitch.tv", 10 | accepts: { 11 | method: "predicateOnTab", 12 | format:"%K LIKE[c] '*twitch.tv/*'", 13 | args: ["URL"] 14 | }, 15 | isPlaying: function () { 16 | var doc = document; 17 | var frame = $('iframe[src^=\'http://player.twitch.tv/?channel=\']').get(0); 18 | if (frame) { 19 | doc = frame.contentDocument || frame.contentWindow.document; 20 | } 21 | return (doc.querySelector('.player[data-paused=\"false\"]') != null); 22 | }, 23 | toggle: function () { 24 | var doc = document; 25 | var frame = $("iframe[src^='http://player.twitch.tv/?channel=']").get(0); 26 | if (frame) { 27 | doc = frame.contentDocument || frame.contentWindow.document; 28 | } 29 | doc.querySelector('.js-control-playpause-button').click() 30 | }, 31 | next: function () {}, 32 | favorite: function () {}, 33 | previous: function () {}, 34 | pause: function () { 35 | var doc = document; 36 | var frame = $('iframe[src^=\'http://player.twitch.tv/?channel=\']').get(0); 37 | if (frame) { 38 | doc = frame.contentDocument || frame.contentWindow.document; 39 | } 40 | doc.querySelector('.player[data-paused="false"] .js-control-playpause-button').click() 41 | }, 42 | trackInfo: function () {} 43 | } 44 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Songza.js: -------------------------------------------------------------------------------- 1 | // 2 | // Songza.plist 3 | // BeardedSpice 4 | // 5 | // Created by Jayson Rhynas on 1/18/2014. 6 | // Copyright (c) 2014 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"Songza", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*songza.com*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () {return document.querySelector('.player-wrapper').classList.contains('player-state-play');}, 17 | toggle: function () {document.querySelector('.miniplayer-control-play-pause').click()}, 18 | next: function () {return document.querySelector('.miniplayer-control-skip').click()}, 19 | favorite: function () {document.querySelector('.miniplayer-info-playlist-favorite-status').click()}, 20 | previous: function () {}, 21 | pause: function () { 22 | if (document.querySelector('.player-wrapper').classList.contains('player-state-play')) { 23 | document.querySelector('.miniplayer-control-play-pause').click() 24 | } 25 | }, 26 | trackInfo: function () { 27 | var track = document.querySelector('.miniplayer-info-track-title > a').getAttribute('title'); 28 | var artist = document.querySelector('.miniplayer-info-artist-name > a').getAttribute('title'); 29 | var albumArt = document.querySelector('.miniplayer-album-art').getAttribute('src'); 30 | return { 31 | 'track': track, 32 | 'artist': artist, 33 | 'image': albumArt 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/WonderFm.js: -------------------------------------------------------------------------------- 1 | // 2 | // WonderFm.plist 3 | // BeardedSpice 4 | // 5 | // Created by Kyle Conarro on 2/3/15. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"WonderFM", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*wonder.fm*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () {return $('div.jp-audio').hasClass('jp-state-playing');}, 17 | toggle: function () { 18 | var e = document.querySelector('.jp-type-single'); 19 | var u = 'none' === getComputedStyle(e,null).display; 20 | var n = 'none' === getComputedStyle(l,null).display; 21 | if (u) { 22 | var c = document.querySelector('.track_play'); 23 | c.click(); 24 | } 25 | else if (n) { 26 | var t = document.querySelector('a.jp-pause'); 27 | t.click(); 28 | } 29 | else { 30 | var l = document.querySelector('a.jp-play'); 31 | l.click(); 32 | } 33 | }, 34 | next: function () {document.querySelector('a.jp-next').click()}, 35 | favorite:function () { document.querySelector('.track_active .track_fav').click() }, 36 | previous: function () {}, 37 | pause: function () {document.querySelector('a.jp-pause').click()}, 38 | trackInfo: function () { 39 | return { 40 | 'track': document.querySelector('.track_active .track_name > a').text, 41 | 'artist': document.querySelector('.track_active .track_artist > a').text 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/MusicForProgramming.js: -------------------------------------------------------------------------------- 1 | // 2 | // MusicForProgramming.plist 3 | // BeardedSpice 4 | // 5 | // Created by Max Borghino on 12/01/15. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | // strategy/site notes 9 | // - favorite, not implemented on this site 10 | // - single sets are long, so next/prev implements the site's forward/rewind on the set 11 | // - track info consists only of the set number and name, no artist or artwork 12 | BSStrategy = { 13 | version:1, 14 | displayName:"MusicForProgramming", 15 | accepts: { 16 | method: "predicateOnTab", 17 | format:"%K LIKE[c] '*musicforprogramming.net/*'", 18 | args: ["URL"] 19 | }, 20 | isPlaying: function () {return ( (document.querySelector('.playerControls #player_playpause').innerText === '[PAUSE]'));}, 21 | toggle: function () {document.querySelector('.playerControls #player_playpause').click();}, 22 | next: function () {document.querySelector('.playerControls #player_ffw').click()}, 23 | favorite: function () {}, 24 | previous: function () {document.querySelector('.playerControls #player_rew').click()}, 25 | pause:function () { 26 | var playPause=document.querySelector('.playerControls #player_playpause'); 27 | if(playPause && playPause.innerText === '[PAUSE]'){ 28 | playPause.click(); 29 | } 30 | }, 31 | trackInfo: function () { 32 | var track=document.querySelector('.selected'); 33 | return { 34 | 'track': track ? track.innerText : '' 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/StyleJukebox.js: -------------------------------------------------------------------------------- 1 | // 2 | // StyleJukebox.js 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 06/18/2016. 6 | // Copyright (c) 2016 Bearded Spice. All rights reserved. 7 | // 8 | 9 | BSStrategy = { 10 | 11 | version:1, 12 | displayName:"Style Jukebox", 13 | accepts: { 14 | 15 | method: "predicateOnTab", 16 | format:"%K LIKE[c] '*play.stylejukebox.com*'", 17 | args: ["URL"] 18 | }, 19 | 20 | isPlaying: function(){return $('div.player-content span.play-button').hasClass('ng-hide');}, 21 | toggle: function () {$('div.player-content span.playpause').click();}, 22 | next: function () {$('div.player-content span.next').click();}, 23 | favorite: function (){$('div.player-content span.favoriteImage').click();}, 24 | previous: function () {$('div.player-content span.prev').click();}, 25 | pause: function () {if($('div.player-content span.play-button').hasClass('ng-hide')) $('div.player-content span.playpause').click();}, 26 | 27 | trackInfo: function () { 28 | var playerContent = $('div.player-content'); 29 | return { 30 | 31 | 'track': playerContent.find('span.song-title').get(0).innerText, 32 | 'artist': playerContent.find('span.song-artist').get(0).innerText, 33 | 'image': playerContent.find('div.playerAlbumArt img').attr('src'), 34 | 'favorited': playerContent.find('img.favImage').attr('src').includes('favorites_active.') 35 | }; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/CozyCloud.js: -------------------------------------------------------------------------------- 1 | // 2 | // CozyCloud.js 3 | // BeardedSpice 4 | // 5 | // Created by Cédric Patchane on 08/02/16. 6 | // Copyright (c) 2016 GPL v3 http://www.gnu.org/licenses/gpl.html 7 | // 8 | 9 | BSStrategy = { 10 | version: 1, 11 | displayName: "Cozy Cloud", 12 | accepts: { 13 | method: "predicateOnTab", 14 | format: "%K LIKE[c] '*cozycloud.cc/#apps/cozy-music/*'", 15 | args: ["URL"] 16 | }, 17 | isPlaying: function () { 18 | iFrameDoc = document.querySelector('iframe').contentWindow.document; 19 | return iFrameDoc.querySelector('#play svg use').href === '#pause-lg'; 20 | }, 21 | toggle: function () { 22 | iFrameDoc = document.querySelector('iframe').contentWindow.document; 23 | iFrameDoc.querySelector('#play').click(); 24 | }, 25 | previous: function () { 26 | iFrameDoc = document.querySelector('iframe').contentWindow.document; 27 | iFrameDoc.querySelector('#prev').click(); 28 | }, 29 | next: function () { 30 | iFrameDoc = document.querySelector('iframe').contentWindow.document; 31 | iFrameDoc.querySelector('#next').click(); 32 | }, 33 | favorite: function () {}, 34 | pause: function () {}, 35 | trackInfo: function () { 36 | iFrameDoc = document.querySelector('iframe').contentWindow.document; 37 | return { 38 | //'image': '', 39 | 'track': iFrameDoc.querySelector('#track-list li.playing .song-column-cell').innerText, 40 | 'artist': iFrameDoc.querySelector('#track-list li.playing .artist-column-cell').innerText 41 | }; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /BeardedSpice/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "icon16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "icon32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "icon32-1.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "icon64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "icon128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "icon256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "icon256-1.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "icon512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "icon512-1.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "icon1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Napster.js: -------------------------------------------------------------------------------- 1 | // 2 | // Napster.plist 3 | // BeardedSpice 4 | // 5 | // Created by Aaron Pollack on 11/17/15. 6 | // Copyright © 2015 BeardedSpice. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:2, 10 | displayName:"Napster", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*app.napster.com*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () {return !!$('.player-play-button .icon-pause2').length;}, 17 | toggle: function () { 18 | if ($('.player-play-button .icon-pause2').length) { 19 | $('.player-play-button .icon-pause2').click(); 20 | } else { 21 | $('.player-play-button .icon-play-button').click() 22 | } 23 | }, 24 | next: function () {$('.player-advance-button').click();}, 25 | favorite: function () {$('.favorite-button').click()}, 26 | previous: function () {$('.player-rewind-button').click();}, 27 | pause: function () {$('.player-play-button .icon-pause2').click();}, 28 | trackInfo: function () { 29 | function titleize(slug) { 30 | var words = slug.split('-'); 31 | return words.map(function(word) { 32 | return word.charAt(0).toUpperCase() + word.substring(1).toLowerCase(); 33 | }).join(' '); 34 | } 35 | return { 36 | 'track': $('.player-track a')[0].innerText, 37 | 'artist': ($('.player-artist a')[0].innerText).split('- ').slice(1).join('- ').trim(), 38 | 'album': titleize($('.player-wrapper a').attr('href').split('album/')[1]), 39 | 'image': $('.player-album-thumbnail img')[0].src 40 | }; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/SoundCloud.js: -------------------------------------------------------------------------------- 1 | // 2 | // SoundCloud.plist 3 | // BeardedSpice 4 | // 5 | // Created by Jose Falcon on 12/16/13. 6 | // Copyright (c) 2013 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:2, 10 | displayName:"SoundCloud", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*soundcloud.com*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying:function () { 17 | var play = document.querySelector('.playControl'); 18 | return play.classList.contains('playing'); 19 | }, 20 | toggle: function () {return document.querySelectorAll('.playControl')[0].click()}, 21 | next: function () {return document.querySelectorAll('.skipControl__next')[0].click()}, 22 | favorite:function () {return document.querySelector('div.playControls button.playbackSoundBadge__like').click()}, 23 | previous: function () {return document.querySelectorAll('.skipControl__previous')[0].click()}, 24 | pause: function (){ 25 | var play = document.querySelector('.playControl'); 26 | if(play.classList.contains('playing')) { play.click(); } 27 | }, 28 | trackInfo: function () { 29 | var meta = document.querySelector('a.playbackSoundBadge__title.sc-truncate'); 30 | return { 31 | 'track': meta.title, 32 | 'album': meta.href.split('/')[3], 33 | 'image': document.querySelector('div.playControls span.sc-artwork').style['background-image'].slice(4, -1), 34 | 'favorited': document.querySelector('div.playControls button.playbackSoundBadge__like').classList.contains('sc-button-selected') 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Utils/NSException+Utils.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSException+Utils.m 3 | // Commons 4 | // 5 | // Created by Roman Sokolov on 05.02.14. 6 | // 7 | // 8 | 9 | #import "NSException+Utils.h" 10 | 11 | @implementation NSException (Utils) 12 | 13 | + (NSException *)argumentException:(NSString *)argumentName{ 14 | if (!argumentName) 15 | argumentName = @"(NONE)"; 16 | 17 | return [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"Method argument error: %@", argumentName] userInfo:nil]; 18 | } 19 | 20 | + (NSException *)mallocException:(NSString *)objectName{ 21 | 22 | NSString *descriprion = @"Memory allocation error."; 23 | 24 | if (objectName) { 25 | 26 | descriprion = [descriprion stringByAppendingFormat:@" Attempt allocate memory for (%@).", objectName]; 27 | } 28 | return [NSException exceptionWithName:NSMallocException reason:descriprion userInfo:nil]; 29 | } 30 | 31 | + (NSException *)appResourceUnavailableException:(NSString *)resourceName{ 32 | 33 | NSString *descriprion = @"Application resource available error."; 34 | 35 | if (resourceName) { 36 | 37 | descriprion = [descriprion stringByAppendingFormat:@" Attempt load resource with name: %@.", resourceName]; 38 | } 39 | return [NSException exceptionWithName:NSInternalInconsistencyException reason:descriprion userInfo:nil]; 40 | 41 | } 42 | 43 | + (NSException *)notImplementedException{ 44 | 45 | return [NSException exceptionWithName:NSInvalidArgumentException reason:@"Selector not implemented" userInfo:nil]; 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/DigitallyImported.js: -------------------------------------------------------------------------------- 1 | // 2 | // DigitallyImported.plist 3 | // BeardedSpice 4 | // 5 | // Created by Dennis Lysenko on 4/4/15. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"Digitally Imported", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*di.fm*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying:function () { 17 | var pause = $('#webplayer-region .controls .ico.icon-pause').get(0); 18 | var spinner = $('#webplayer-region .controls .ico.icon-spinner3').get(0); 19 | var sponsor = $('#webplayer-region .metadata-container .track-title .sponsor').get(0); 20 | return pause ? true : (spinner && sponsor ); 21 | }, 22 | toggle: function () { return document.querySelectorAll('div.controls a')[0].click() }, 23 | favorite: function () { $('.vote-btn.up').click(); }, 24 | pause:function () { 25 | var pause = document.querySelectorAll('div.controls a')[0]; 26 | if(pause.classList.contains('icon-pause')){ 27 | pause.click(); 28 | } 29 | }, 30 | trackInfo: function () { 31 | var artistName = $('.artist-name').text(); 32 | var trackName = $('.track-name').text().replace(artistName, ""); 33 | if (artistName.length > 3) { 34 | artistName = artistName.substring(0, artistName.length - 3); 35 | } 36 | return { 37 | 'artist': artistName, 38 | 'track': trackName.replace(/\s+/, ''), 39 | 'favorited': ($('.icon-thumbs-up-filled').get(0) ? true : false), 40 | 'image': $('#webplayer-region .track-region .artwork img').attr('src') 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /BeardedSpiceControllers/Controllers/DDHidLib/DDHidUsageTables.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2007 Dave Dribin 3 | * 4 | * Permission is hereby granted, free of charge, to any person 5 | * obtaining a copy of this software and associated documentation 6 | * files (the "Software"), to deal in the Software without 7 | * restriction, including without limitation the rights to use, copy, 8 | * modify, merge, publish, distribute, sublicense, and/or sell copies 9 | * 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 13 | * included in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 19 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 20 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #import 26 | 27 | 28 | @interface DDHidUsageTables : NSObject 29 | { 30 | NSDictionary * mLookupTables; 31 | } 32 | 33 | + (DDHidUsageTables *) standardUsageTables; 34 | 35 | - (id) initWithLookupTables: (NSDictionary *) lookupTables; 36 | 37 | - (NSString *) descriptionForUsagePage: (unsigned) usagePage 38 | usage: (unsigned) usage; 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/BE-AT.TV.js: -------------------------------------------------------------------------------- 1 | // 2 | // BE-AT.TV.js 3 | // BeardedSpice 4 | // 5 | // Created by Marvin Tam on 10/25/2016. 6 | // Copyright (c) 2016 GPL v3 http://www.gnu.org/licenses/gpl.html 7 | // 8 | BSStrategy = { 9 | version: 1, 10 | displayName: "BE-AT.TV", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format: "%K LIKE[c] '*be-at.tv*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function() { 17 | return document.querySelector('#radio .playbutton') 18 | .style.display === 'none'; 19 | }, 20 | toggle: function() { 21 | var playButton = document.querySelector('#radio .playbutton'); 22 | var pauseButton = document.querySelector('#radio .pausebutton'); 23 | 24 | playButton.style.display === 'none' ? pauseButton.click() : 25 | playButton.click(); 26 | }, 27 | next: function() { 28 | document.querySelector('#radio .next').click(); 29 | }, 30 | favorite: function() {}, // not applicable here 31 | previous: function() { 32 | document.querySelector('#radio .back').click(); 33 | }, 34 | pause: function() { 35 | document.querySelector('#radio .pausebutton').click(); 36 | }, 37 | trackInfo: function() { 38 | // Ticker format: "artist : track" 39 | var items = document.querySelector('#radio .ticker').textContent 40 | .split(':', 2); 41 | 42 | // Get the un-resized thumbnail by removing the ?w=36&h36 query 43 | var imageUrl = document.querySelector('#radio img').src; 44 | imageUrl = imageUrl.slice(0, imageUrl.indexOf('?')); 45 | 46 | return { 47 | artist: items[0].trim(), 48 | track: items[1].trim(), 49 | image: imageUrl 50 | }; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/TidalHiFi.js: -------------------------------------------------------------------------------- 1 | // 2 | // TidalHiFi.plist 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 04.03.15. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"TidalHiFi", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*listen.tidal.com*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying:function () { 17 | var player = require('media/playbackController'); 18 | return player.isPlaying(); 19 | }, 20 | toggle:function () { 21 | var player = require('media/playbackController'); 22 | if (player.isPlaying()) { player.pause(); } 23 | else { player.resume(); } 24 | }, 25 | next: function () {require('media/playbackController').playNext();}, 26 | favorite:function () { 27 | var obj = require('media/playbackController').getCurrentTrack(); 28 | var event = { 29 | 'isFavorited':(obj.get('favoriteDate') === undefined), 30 | 'data': obj, 31 | 'type':'track' 32 | }; 33 | require('controllers/favorites').favoriteEventHandler(event); 34 | }, 35 | previous: function () {require('media/playbackController').playPrevious();}, 36 | pause: function () {require('media/playbackController').pause();}, 37 | trackInfo: function () { 38 | var obj = require('media/playbackController').getCurrentTrack().attributes; 39 | return { 40 | 'track':obj.title, 41 | 'artist':obj.artist.name, 42 | 'album':obj.album.title, 43 | 'image':$('div.player div.image--player img[data-bind-src="imageUrl"]').attr('src'), 44 | 'favorited':(obj.favoriteDate !== undefined) 45 | }; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # by Andy Maloney 4 | # http://asmaloney.com/2013/07/howto/packaging-a-mac-os-x-application-using-a-dmg/ 5 | 6 | set -e 7 | 8 | # make sure we are in the correct dir when we double-click a .command file 9 | dir=${0%/*} 10 | if [ -d "$dir" ]; then 11 | cd "$dir" 12 | fi 13 | 14 | # set up your app name, version number, and background image file name 15 | APP_NAME="BeardedSpice" 16 | VERSION="0.1.0" 17 | 18 | # you should not need to change these 19 | APP_EXE="${APP_NAME}.app/Contents/MacOS/${APP_NAME}" 20 | 21 | VOL_NAME="${APP_NAME}-${VERSION}" # volume name will be "SuperCoolApp-1.0.0" 22 | DMG_TMP="${VOL_NAME}-temp.dmg" 23 | DMG_FINAL="${VOL_NAME}.dmg" # final DMG name will be "SuperCoolApp-1.0.0.dmg" 24 | 25 | CWD=`pwd` 26 | RESOURCE_DIR="${CWD}/BeardedSpice" 27 | BUILD_DIR="${CWD}/build/Release" 28 | STAGING_DIR="${CWD}/build/packaged" # we copy all our stuff into this dir 29 | 30 | DMG_BACKGROUND_IMG_NAME="beard.png" 31 | 32 | echo 'Cleaning.' 33 | # clear out any old data 34 | rm -rf "${STAGING_DIR}" "${DMG_TMP}" "${DMG_FINAL}" 35 | 36 | echo 'Building.' 37 | # build the project 38 | 39 | xcodebuild -workspace BeardedSpice.xcworkspace -scheme BeardedSpice -configuration Release 40 | 41 | echo 'Copying to staging directory.' 42 | # copy over the stuff we want in the final disk image to our staging dir 43 | mkdir -p "${STAGING_DIR}" 44 | cp -rpf "${BUILD_DIR}/${APP_NAME}.app" "${STAGING_DIR}" 45 | 46 | pushd "${STAGING_DIR}" 47 | 48 | # . perform any other stripping/compressing of libs and executables 49 | 50 | popd 51 | 52 | # clean up 53 | echo 'Cleaning up.' 54 | rm -rf "${DMG_TMP}" 55 | rm -rf "${STAGING_DIR}" 56 | 57 | echo 'Done.' 58 | 59 | exit 60 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Audible.js: -------------------------------------------------------------------------------- 1 | // 2 | // Audible.plist 3 | // BeardedSpice 4 | // 5 | // Created by Max Borghino on 12/06/15. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | // strategy/site notes 9 | // - favorite: sets a bookmark 10 | // - prev: implements skip back 30 seconds 11 | // - next: not used (alternative: we could do prev/next chapter, but this is not very useful) 12 | // - track info: book title and author not in the player, only artwork, chapter, time/time left 13 | BSStrategy = { 14 | version:1, 15 | displayName:"Audible", 16 | accepts: { 17 | method: "predicateOnTab", 18 | format:"%K LIKE[c] '*audible.com/cloud-player*'", 19 | args: ["URL"] 20 | }, 21 | isPlaying:function () { 22 | var p=document.querySelector('.pause'); 23 | return (p && !p.classList.contains('hide')); 24 | }, 25 | toggle: function () {document.querySelector('.play').click();}, 26 | next: function () {document.querySelector('.fav').click();}, 27 | favorite: function () {}, 28 | previous: function () {document.querySelector('.repeat').click()}, 29 | pause:function () { 30 | var p=document.querySelector('.pause'); 31 | if(p && !p.classList.contains('hide')){ p.click();} 32 | }, 33 | trackInfo: function () { 34 | var art = document.querySelector('.item img'); 35 | var chapter = document.querySelector('.chapter'); 36 | var timeCur = document.querySelector('.cur'); 37 | var timeRem = document.querySelector('.rem'); 38 | return { 39 | 'image': art ? art.getAttribute('src') : null, 40 | 'track': chapter ? chapter.innerText : null, 41 | 'artist': (timeCur ? timeCur.innerText : null) + '/' + (timeRem ? timeRem.innerText : null), 42 | }; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/ProductHunt.js: -------------------------------------------------------------------------------- 1 | // 2 | // ProductHunt.plist 3 | // BeardedSpice 4 | // 5 | // Created by Alexandre Daussy (Kureb) on 05/16/2016. 6 | // Copyright (c) 2015 GPL v3 http://www.gnu.org/licenses/gpl.html 7 | // 8 | BSStrategy = { 9 | version: 1, 10 | displayName: "ProductHunt", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format: "%K LIKE[c] '*producthunt.com/podcasts*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () { 17 | var canToggle = document.querySelector('span.player--button.v-toggle') != null; 18 | if (canToggle) 19 | return document.querySelector('span.player--button.v-toggle').getAttribute("data-reactid").indexOf("play") == -1; 20 | return false; 21 | }, 22 | toggle: function () { document.querySelector('span.player--button.v-toggle').click() }, 23 | previous: function () { document.querySelector('div.player--controls > span:nth-child(1)').click() }, 24 | next: function () { document.querySelector('div.player--controls > span:nth-child(3)').click() }, 25 | pause: function () { 26 | var doesPlayerExist = document.querySelector('body.m-player-active') != null; 27 | var isPlaying = document.querySelector('span.player--button.v-toggle').getAttribute("data-reactid").indexOf("play") == -1; 28 | if (doesPlayerExist && isPlaying) 29 | document.querySelector('span.player--button.v-toggle').click(); 30 | }, 31 | favorite: function () { document.querySelector('a[rel=save-button]').click() }, 32 | trackInfo: function () { 33 | return { 34 | 'track': document.querySelector('a.player--media--name').innerHTML, 35 | 'image': document.querySelector('a.player--media--coverart > img').getAttribute('src'), 36 | }; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /BeardedSpiceControllers/Controllers/DDHidLib/DDHidEvent.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2007 Dave Dribin 3 | * 4 | * Permission is hereby granted, free of charge, to any person 5 | * obtaining a copy of this software and associated documentation 6 | * files (the "Software"), to deal in the Software without 7 | * restriction, including without limitation the rights to use, copy, 8 | * modify, merge, publish, distribute, sublicense, and/or sell copies 9 | * 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 13 | * included in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 19 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 20 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #import 26 | #include 27 | 28 | @interface DDHidEvent : NSObject 29 | { 30 | IOHIDEventStruct mEvent; 31 | } 32 | 33 | + (DDHidEvent *) eventWithIOHIDEvent: (IOHIDEventStruct *) event; 34 | 35 | - (id) initWithIOHIDEvent: (IOHIDEventStruct *) event; 36 | 37 | - (IOHIDElementType) type; 38 | - (IOHIDElementCookie) elementCookie; 39 | - (unsigned) elementCookieAsUnsigned; 40 | - (SInt32) value; 41 | - (AbsoluteTime) timestamp; 42 | - (UInt32) longValueSize; 43 | - (void *) longValue; 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /Utils/EHSystemUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // EHSystemUtils.h 3 | // EightHours 4 | // 5 | // Created by Roman Sokolov on 01.03.16. 6 | // Copyright © 2016 Roman Sokolov. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | ///////////////////////////////////////////////////////////////////// 12 | #pragma mark - EHSystemUtils 13 | ///////////////////////////////////////////////////////////////////// 14 | 15 | /** 16 | Contains utilities for wrapping unix functions. 17 | */ 18 | @interface EHSystemUtils : NSObject 19 | 20 | ///////////////////////////////////////////////////////////////////// 21 | #pragma mark Init and Class methods 22 | ///////////////////////////////////////////////////////////////////// 23 | 24 | /** 25 | Checks that current process have root privileges. 26 | */ 27 | + (BOOL)rootPrivileges; 28 | 29 | /** 30 | Launches command line utility. 31 | @param utilPath Full path of cli utility. You MUST specify full path. 32 | @param arguments array with NSString objects, may be nil. 33 | @param outputData returning parameter. Set to NULL if we do not need it. 34 | */ 35 | + (int)cliUtil:(NSString *)utilPath arguments:(NSArray *)arguments outputData:(NSData **)outputData; 36 | 37 | /** 38 | Launches command line utility. 39 | @param utilPath Full path of cli utility. You MUST specify full path. 40 | @param arguments array with NSString objects, may be nil. 41 | @param output returning parameter. Set to NULL if we do not need it. 42 | */ 43 | + (int)cliUtil:(NSString *)utilPath arguments:(NSArray *)arguments output:(NSString **)output; 44 | 45 | /** 46 | Performs block on main queue synchronously. 47 | */ 48 | + (void)callOnMainQueue:(dispatch_block_t)block; 49 | 50 | /** 51 | Returns UUID (GUID). 52 | */ 53 | + (NSString *)createUUID; 54 | 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /SharedComponents/BeardedSpiceControllersProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // BeardedSpiceControllersProtocol.h 3 | // BeardedSpiceControllers 4 | // 5 | // Created by Roman Sokolov on 05.03.16. 6 | // Copyright © 2016 BeardedSpice. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class MASShortcut; 12 | 13 | // The protocol that this service will vend as its API. This header file will also need to be visible to the process hosting the service. 14 | @protocol BeardedSpiceControllersProtocol 15 | 16 | - (void)setShortcuts:(NSDictionary *)shortcuts; 17 | 18 | - (void)setMediaKeysSupportedApps:(NSArray *)bundleIds; 19 | 20 | - (void)setPhoneUnplugActionEnabled:(BOOL)enabled; 21 | 22 | - (void)setUsingAppleRemoteEnabled:(BOOL)enabled; 23 | 24 | - (void)prepareForClosingConnectionWithCompletion:(void (^)(void))completion; 25 | 26 | @end 27 | 28 | /* 29 | To use the service from an application or other process, use NSXPCConnection to establish a connection to the service by doing something like this: 30 | 31 | _connectionToService = [[NSXPCConnection alloc] initWithServiceName:@"com.beardedspice.BeardedSpiceControllers"]; 32 | _connectionToService.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(StringModifing)]; 33 | [_connectionToService resume]; 34 | 35 | Once you have a connection to the service, you can use it like this: 36 | 37 | [[_connectionToService remoteObjectProxy] upperCaseString:@"hello" withReply:^(NSString *aString) { 38 | // We have received a response. Update our text field, but do it on the main thread. 39 | NSLog(@"Result string was: %@", aString); 40 | }]; 41 | 42 | And, when you are finished with the service, clean up the connection like this: 43 | 44 | [_connectionToService invalidate]; 45 | */ 46 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Beatport.js: -------------------------------------------------------------------------------- 1 | // Beatport.plist 2 | // BeardedSpice 3 | 4 | // Created by Daniel Bayley on 01/07/16. 5 | // Copyright (c) 2016 Daniel Bayley. All rights reserved. 6 | 7 | BSStrategy = { 8 | version: 1, 9 | displayName: "Beatport", 10 | accepts: { 11 | method: "predicateOnTab", 12 | format: "%K LIKE[c] '*beatport.com*'", 13 | args: ["URL"] 14 | }, 15 | isPlaying: function () { return document.querySelector('.play-button.play') === null }, 16 | toggle: function () { 17 | var play = document.querySelector('.play-button.play'), 18 | pause = document.querySelector('.play-button.pause'); 19 | if (pause != null) { pause.click();} else { play.click();} 20 | }, 21 | previous: function () { document.querySelector('.prev-button').click();}, 22 | next: function () { document.querySelector('.next-button').click();}, 23 | pause: function () { document.querySelector('.play-button.pause').click();}, 24 | favorite: function () { document.querySelector('.add-to-default').click();}, 25 | trackInfo: function () { 26 | var info = document.querySelectorAll('.track-artist a'), 27 | artists = []; 28 | for (i = 0; i < info.length; i++) { artists.push(info[i].innerText);} 29 | 30 | artists = artists.filter(function(item, pos, self) { 31 | return self.indexOf(item) == pos; 32 | }); 33 | var track = document.querySelector('.primary-title').innerText + 34 | ' ('+ document.querySelector('.remixed').innerText +')'; 35 | 36 | var artwork = document.querySelector('.track-artwork').getAttribute('src') 37 | .replace(/[0-9]+x[0-9]+/,'600x600'); 38 | return { 39 | track: track, 40 | //album: album, 41 | artist: artists.join(", "), //(" & ") 42 | image: artwork, 43 | favorited: document.querySelector('.buy-button.in-cart') != null 44 | }; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/Qobuz.js: -------------------------------------------------------------------------------- 1 | // 2 | // Qobuz.js 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 15.10.16. 6 | // Copyright (c) 2016 BeardedSpice. All rights reserved 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"Qobuz", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*player.qobuz.com*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () {return qbPlayer.playerManager.isPlaying();}, 17 | toggle: function () {qbPlayer.playerManager.togglePause();}, 18 | next: function () {qbPlayer.playerManager.next();}, 19 | previous: function () {qbPlayer.playerManager.previous();}, 20 | pause: function () {if (qbPlayer.playerManager.isPlaying()) qbPlayer.playerManager.togglePause();}, 21 | favorite: function () { 22 | if (qbPlayer.globalManager.isFavorite("track", qbPlayer.playerManager.currentTrack.id)) { 23 | qbPlayer.actionManager.triggerEvent("deleteFromFavorites", { 24 | type: "track", 25 | ids: [qbPlayer.playerManager.currentTrack.id], 26 | label: qbPlayer.playerManager.currentTrack.metadata.title}); 27 | } 28 | else { 29 | qbPlayer.actionManager.triggerEvent("addToFavorites", { 30 | type: "track", 31 | ids: [qbPlayer.playerManager.currentTrack.id], 32 | label: qbPlayer.playerManager.currentTrack.metadata.title}); 33 | } 34 | }, 35 | trackInfo: function () { 36 | return { 37 | track: qbPlayer.playerManager.currentTrack.metadata.title, 38 | artist: qbPlayer.playerManager.currentTrack.metadata.artistName, 39 | album: qbPlayer.playerManager.currentTrack.metadata.albumTitle, 40 | favorited: qbPlayer.globalManager.isFavorite("track", qbPlayer.playerManager.currentTrack.id), 41 | image: qbPlayer.playerManager.currentTrack.metadata.picture 42 | }; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /BeardedSpiceControllers/Controllers/DDHidLib/DDHidUsage.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2007 Dave Dribin 3 | * 4 | * Permission is hereby granted, free of charge, to any person 5 | * obtaining a copy of this software and associated documentation 6 | * files (the "Software"), to deal in the Software without 7 | * restriction, including without limitation the rights to use, copy, 8 | * modify, merge, publish, distribute, sublicense, and/or sell copies 9 | * 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 13 | * included in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 19 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 20 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #import 26 | 27 | 28 | @interface DDHidUsage : NSObject 29 | { 30 | unsigned mUsagePage; 31 | unsigned mUsageId; 32 | } 33 | 34 | + (DDHidUsage *) usageWithUsagePage: (unsigned) usagePage 35 | usageId: (unsigned) usageId; 36 | 37 | - (id) initWithUsagePage: (unsigned) usagePage 38 | usageId: (unsigned) usageId; 39 | 40 | - (unsigned) usagePage; 41 | 42 | - (unsigned) usageId; 43 | 44 | - (NSString *) usageName; 45 | 46 | - (NSString *) usageNameWithIds; 47 | 48 | - (NSString *) description; 49 | 50 | - (BOOL) isEqualToUsagePage: (unsigned) usagePage usageId: (unsigned) usageId; 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /BeardedSpiceControllers/Controllers/DDHidLib/NSDictionary+DDHidExtras.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2007 Dave Dribin 3 | * 4 | * Permission is hereby granted, free of charge, to any person 5 | * obtaining a copy of this software and associated documentation 6 | * files (the "Software"), to deal in the Software without 7 | * restriction, including without limitation the rights to use, copy, 8 | * modify, merge, publish, distribute, sublicense, and/or sell copies 9 | * 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 13 | * included in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 19 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 20 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #import 26 | 27 | 28 | @interface NSDictionary (DDHidExtras) 29 | 30 | - (unsigned) ddhid_unsignedForKey: (NSString *) key; 31 | 32 | - (id) ddhid_objectForString: (const char *) key; 33 | 34 | - (NSString *) ddhid_stringForString: (const char *) key; 35 | - (long) ddhid_longForString: (const char *) key; 36 | - (unsigned int) ddhid_unsignedIntForString: (const char *) key; 37 | - (BOOL) ddhid_boolForString: (const char *) key; 38 | 39 | @end 40 | 41 | @interface NSMutableDictionary (DDHidExtras) 42 | 43 | - (void) ddhid_setObject: (id) object forString: (const char *) key; 44 | - (void) ddhid_setInt: (int) i forKey: (id) key; 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /template-explained.js: -------------------------------------------------------------------------------- 1 | // 2 | // NewStrategyName.js 3 | // BeardedSpice 4 | // 5 | // Created by You on Today's Date. 6 | // Copyright (c) 2015-2016 GPL v3 http://www.gnu.org/licenses/gpl.html 7 | // 8 | 9 | // We put the copyright inside the plist to retain consistent syntax coloring. 10 | 11 | // Use a syntax checker to ensure validity. One is provided by nodejs (`node -c filename.js`) 12 | // Normal formatting is supported (can copy/paste with newlines and indentations) 13 | 14 | BSStrategy = { 15 | version: 1, 16 | displayName: "Strategy Name", 17 | accepts: { 18 | method: "predicateOnTab" /* OR "script" */, 19 | /* Use these if "predicateOnTab" */ 20 | format: "%K LIKE[c] '*[YOUR-URL-DOMAIN-OR-TITLE-HERE]*'", 21 | args: ["URL" /* OR "title" */] 22 | /* Use "script" if method is "script" */ 23 | /* [ex] script: "some javascript here that returns a boolean value" */ 24 | }, 25 | 26 | isPlaying: function () { /* javascript that returns a boolean */ }, 27 | toggle: function () { /* toggle site playing */ }, 28 | previous: function () { /* switch to previous track if any */ }, 29 | next: function () { /* switch to next track if any */ }, 30 | pause: function () { /* pause site playing */ }, 31 | favorite: function () { /* toggles favorite on/off */}, 32 | /* 33 | - Return a dictionary of namespaced key/values here. 34 | All manipulation should be supported in javascript. 35 | 36 | - Namespaced keys currently supported include: track, album, artist, favorited, image (URL) 37 | */ 38 | trackInfo: function () { 39 | return { 40 | 'track': 'the name of the track', 41 | 'album': 'the name of the current album', 42 | 'artist': 'the name of the current artist', 43 | 'image': 'http://www.example.com/some/album/artwork.png', 44 | 'favorited': 'true/false if the track has been favorited', 45 | }; 46 | } 47 | } 48 | // The file must have an empty line at the end. 49 | -------------------------------------------------------------------------------- /BeardedSpice/BSAppleScriptSupport.m: -------------------------------------------------------------------------------- 1 | // 2 | // AFSAppleScriptSupport.m 3 | // BeardedSpice 4 | // 5 | // Created by Quentin Carnicelli on 9/7/16. 6 | // Copyright (c) 2015 GPL v3 http://www.gnu.org/licenses/gpl.html 7 | // 8 | 9 | 10 | #import "AppDelegate.h" 11 | 12 | @interface BSAppleScriptPlayPauseCommand: NSScriptCommand { } @end 13 | @interface BSAppleScriptNextCommand: NSScriptCommand { } @end 14 | @interface BSAppleScriptPrevCommand: NSScriptCommand { } @end 15 | 16 | @implementation AppDelegate (AppleScriptAdditions) 17 | 18 | - (BOOL)application:(NSApplication *)sender delegateHandlesKey:(NSString *)key 19 | { 20 | return [[NSSet setWithObjects: @"fullTitle", nil] containsObject:key]; 21 | } 22 | 23 | - (NSString*)fullTitle 24 | { 25 | //Ok this is cheap, but we wanna force an update. This doesn't -quite- work either, 26 | //because the update is async, and we won't get the results back in time. 27 | //But at least they'll update -eventually-, so your next call will get something useful 28 | 29 | [self menuNeedsUpdate: statusMenu]; 30 | 31 | //Safe to access activeTab ivar here? 32 | return [activeTab title]; 33 | } 34 | 35 | @end 36 | 37 | @implementation BSAppleScriptPlayPauseCommand 38 | 39 | - (id)performDefaultImplementation 40 | { 41 | AppDelegate* delegate = (AppDelegate*)[[NSApplication sharedApplication] delegate]; 42 | [delegate playPauseToggle]; 43 | return nil; 44 | } 45 | 46 | @end 47 | 48 | @implementation BSAppleScriptNextCommand 49 | 50 | - (id)performDefaultImplementation 51 | { 52 | AppDelegate* delegate = (AppDelegate*)[[NSApplication sharedApplication] delegate]; 53 | [delegate nextTrack]; 54 | return nil; 55 | } 56 | 57 | @end 58 | 59 | @implementation BSAppleScriptPrevCommand 60 | 61 | - (id)performDefaultImplementation 62 | { 63 | AppDelegate* delegate = (AppDelegate*)[[NSApplication sharedApplication] delegate]; 64 | [delegate previousTrack]; 65 | return nil; 66 | } 67 | 68 | @end -------------------------------------------------------------------------------- /BeardedSpice/BeardedSpiceUserDefaults.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BeardedSpiceActivatePlayingTabShortcut 6 | YnBsaXN0MDDUAQIDBAUGFhdYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoKMHCA9VJG51bGzTCQoLDA0OV0tleUNvZGVWJGNsYXNzXU1vZGlmaWVyRmxhZ3MQYYACEgAQAADSEBESE1okY2xhc3NuYW1lWCRjbGFzc2VzW01BU1Nob3J0Y3V0ohQVW01BU1Nob3J0Y3V0WE5TT2JqZWN0XxAPTlNLZXllZEFyY2hpdmVy0RgZVHJvb3SAAQgRGiMtMjc7QUhQV2VnaW5zfoeTlqKrvcDFAAAAAAAAAQEAAAAAAAAAGgAAAAAAAAAAAAAAAAAAAMc= 7 | BeardedSpiceActiveTabShortcut 8 | YnBsaXN0MDDUAQIDBAUGFhdYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoKMHCA9VJG51bGzTCQoLDA0OV0tleUNvZGVWJGNsYXNzXU1vZGlmaWVyRmxhZ3MQZIACEgAQAADSEBESE1okY2xhc3NuYW1lWCRjbGFzc2VzW01BU1Nob3J0Y3V0ohQVW01BU1Nob3J0Y3V0WE5TT2JqZWN0XxAPTlNLZXllZEFyY2hpdmVy0RgZVHJvb3SAAQgRGiMtMjc7QUhQV2VnaW5zfoeTlqKrvcDFAAAAAAAAAQEAAAAAAAAAGgAAAAAAAAAAAAAAAAAAAMc= 9 | BeardedSpiceAlwaysShowNotification 10 | 11 | BeardedSpiceFavoriteShortcut 12 | YnBsaXN0MDDUAQIDBAUGFhdYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoKMHCA9VJG51bGzTCQoLDA0OV0tleUNvZGVWJGNsYXNzXU1vZGlmaWVyRmxhZ3MQbYACEgAQAADSEBESE1okY2xhc3NuYW1lWCRjbGFzc2VzW01BU1Nob3J0Y3V0ohQVW01BU1Nob3J0Y3V0WE5TT2JqZWN0XxAPTlNLZXllZEFyY2hpdmVy0RgZVHJvb3SAAQgRGiMtMjc7QUhQV2VnaW5zfoeTlqKrvcDFAAAAAAAAAQEAAAAAAAAAGgAAAAAAAAAAAAAAAAAAAMc= 13 | BeardedSpiceRemoveHeadphonesAutopause 14 | 15 | BeardedSpiceNotificationShortcut 16 | YnBsaXN0MDDUAQIDBAUGFhdYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoKMHCA9VJG51bGzTCQoLDA0OV0tleUNvZGVWJGNsYXNzXU1vZGlmaWVyRmxhZ3MQZ4ACEgAQAADSEBESE1okY2xhc3NuYW1lWCRjbGFzc2VzW01BU1Nob3J0Y3V0ohQVW01BU1Nob3J0Y3V0WE5TT2JqZWN0XxAPTlNLZXllZEFyY2hpdmVy0RgZVHJvb3SAAQgRGiMtMjc7QUhQV2VnaW5zfoeTlqKrvcDFAAAAAAAAAQEAAAAAAAAAGgAAAAAAAAAAAAAAAAAAAMc= 17 | 18 | 19 | -------------------------------------------------------------------------------- /BeardedSpice/Preferences/ShortcutsPreferencesViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AdvansedPreferencesViewController.m 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 13.03.15. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | 9 | #import "ShortcutsPreferencesViewController.h" 10 | #import "BSSharedDefaults.h" 11 | 12 | @implementation ShortcutsPreferencesViewController 13 | 14 | - (id)init 15 | { 16 | self = [super initWithNibName:@"ShortcutsPreferencesView" bundle:nil]; 17 | if (self) { 18 | } 19 | return self; 20 | } 21 | 22 | - (void)awakeFromNib 23 | { 24 | // associate view with userdefaults 25 | [self.playPauseShortcut setAssociatedUserDefaultsKey:BeardedSpicePlayPauseShortcut]; 26 | [self.nextTrackShortcut setAssociatedUserDefaultsKey:BeardedSpiceNextTrackShortcut]; 27 | [self.previousTrackShortcut setAssociatedUserDefaultsKey:BeardedSpicePreviousTrackShortcut]; 28 | [self.setActiveTabShortcut setAssociatedUserDefaultsKey:BeardedSpiceActiveTabShortcut]; 29 | [self.favoriteShortcut setAssociatedUserDefaultsKey:BeardedSpiceFavoriteShortcut]; 30 | [self.notificationShortcut setAssociatedUserDefaultsKey:BeardedSpiceNotificationShortcut]; 31 | [self.activatePlayingTabShortcut setAssociatedUserDefaultsKey:BeardedSpiceActivatePlayingTabShortcut]; 32 | [self.playerNextShortcut setAssociatedUserDefaultsKey:BeardedSpicePlayerNextShortcut]; 33 | [self.playerPreviousShortcut setAssociatedUserDefaultsKey:BeardedSpicePlayerPreviousShortcut]; 34 | } 35 | 36 | - (NSString *)identifier 37 | { 38 | return @"ShortcutsPreferences"; 39 | } 40 | 41 | - (NSImage *)toolbarItemImage 42 | { 43 | return [NSImage imageNamed:@"toolbarShortcuts"]; 44 | } 45 | 46 | - (NSString *)toolbarItemLabel 47 | { 48 | return NSLocalizedString(@"Shortcuts", @"Toolbar item name for the Shortcuts preference pane"); 49 | } 50 | 51 | - (NSView *)initialKeyView{ 52 | 53 | return self.playPauseShortcut; 54 | } 55 | 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /BeardedSpice/BSStrategyCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // BSStrategyCache.h 3 | // BeardedSpice 4 | // 5 | // Created by Alex Evers on 05/28/2016 6 | // Copyright (c) 2015 GPL v3 http://www.gnu.org/licenses/gpl.html 7 | // 8 | 9 | @class BSMediaStrategy; 10 | 11 | extern NSString * _Nonnull BSStrategyCacheErrorDomain; 12 | #define BSSC_ERROR_STARTEGY_NOTFOUND 100 13 | 14 | @interface BSStrategyCache : NSObject 15 | 16 | @property (nonatomic, strong, readonly) NSMutableDictionary * _Nonnull cache; 17 | 18 | /** 19 | FIXME documentation about loading strategies and how they're cached 20 | */ 21 | - (BOOL)loadStrategies; 22 | 23 | /** 24 | FIXME documentation 25 | */ 26 | - (BOOL)updateStrategiesFromSourceURL:(NSURL * _Nonnull)path; 27 | 28 | /** 29 | FIXME documentation 30 | */ 31 | - (NSArray * _Nonnull)allKeys; 32 | 33 | /** 34 | */ 35 | - (NSArray * _Nonnull)allStrategies; 36 | 37 | /** 38 | Updates the strategy at the given URL to the object's cache. 39 | @param strategyURL the URL which contains the strategy data 40 | */ 41 | - (NSError * _Nullable)updateStrategyWithURL:(NSURL * _Nonnull)strategyURL; 42 | 43 | /** 44 | Addes the strategy at the given URL to the object's cache. 45 | @param strategyURL the URL which contains the strategy data 46 | @return Returns BSMediaStrategy object, which was added or nil if failure. 47 | */ 48 | - (BSMediaStrategy * _Nullable)addStrategyWithURL:(NSURL * _Nonnull)strategyURL; 49 | 50 | /** 51 | FIXME simple remove docs 52 | */ 53 | - (void)removeStrategyFromCache:(NSString * _Nonnull)strategyName; 54 | 55 | /** 56 | Fetches the loaded strategies for reuse and requerying without hitting the disk. 57 | @param strategyName the name of the strategy file to be accessed. Case Sensitive. 58 | @returns A reference to the cached BSMediaStrategy object associated with the given strategyName 59 | */ 60 | - (BSMediaStrategy * _Nullable)strategyForFileName:(NSString * _Nonnull)strategyName; 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/BBCRadio.js: -------------------------------------------------------------------------------- 1 | // 2 | // BBCRadio.plist 3 | // BeardedSpice 4 | // 5 | // Created by Max Borghino on 12/13/15. 6 | // Copyright (c) 2015 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | // strategy/site notes 9 | // - no previous and next available on site 10 | BSStrategy = { 11 | version:1, 12 | displayName:"BBC Radio", 13 | accepts: { 14 | method: "predicateOnTab", 15 | format:"%K LIKE[c] '*bbc.co.uk/radio/player/*'", 16 | args: ["URL"] 17 | }, 18 | isPlaying:function () { 19 | var s=document.querySelector('#controls'); 20 | return (s && (s.classList.contains('stoppable') || s.classList.contains('pausable'))); 21 | }, 22 | toggle:function () { 23 | var s = document.querySelector('#controls'); 24 | var play = document.querySelector('#btn-play'); 25 | var pause = document.querySelector('#btn-pause'); 26 | if (s && (s.classList.contains('stoppable') || s.classList.contains('pausable'))) { 27 | pause.click(); 28 | } else { 29 | play.click(); 30 | } 31 | }, 32 | next: function () {}, 33 | favorite: function () {document.querySelector('#toggle-mystations').click();}, 34 | previous: function () {}, 35 | pause: function () {document.querySelector('#btn-pause').click();}, 36 | trackInfo: function () { 37 | var playlister=document.querySelector('.playlister'), art, title, artist; 38 | if (playlister) { 39 | art=document.querySelector('.playlister img'), 40 | title=playlister.querySelector('.track .title'), 41 | artist=playlister.querySelector('.track .artist'); 42 | } else { 43 | art=document.querySelector('#main-image-wrapper img'), 44 | title=document.querySelector('#parent-title a'), 45 | artist=document.querySelector('#title a'); 46 | } 47 | return {'image': art ? art.getAttribute('src') : null, 48 | 'track': title ? title.innerText : document.title, 49 | 'artist': artist ? artist.innerText : null 50 | }; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/AudioMack.js: -------------------------------------------------------------------------------- 1 | // 2 | // AudioMack.plist 3 | // BeardedSpice 4 | // 5 | // Created by Sean Coker on 12/11/14. 6 | // Copyright (c) 2014 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version:1, 10 | displayName:"AudioMack", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format:"%K LIKE[c] '*audiomack.com*'", 14 | args: ["URL"] 15 | }, 16 | toggle: function () { 17 | var player = document.getElementById('listplayer'); 18 | var play_button = document.getElementById('play-button'); 19 | if (player && player.clientHeight) { 20 | play_button.click(); 21 | return; 22 | } 23 | var feed_buttons = document.querySelectorAll('.feed a.play'); 24 | if (feed_buttons.length) { 25 | feed_buttons[0].click(); 26 | return; 27 | } 28 | if (play_button) { play_button.click(); } 29 | }, 30 | next: function () { 31 | var player = document.getElementById('listplayer'); 32 | if (player && player.clientHeight) { 33 | var next_button = player.querySelector('.next-track'); 34 | next_button.click(); 35 | return; 36 | } 37 | var feed_buttons = document.querySelectorAll('.feed a.play'); 38 | if (feed_buttons.length) { 39 | feed_buttons[0].click(); 40 | return; 41 | } 42 | }, 43 | previous: function () { 44 | var player = document.getElementById('listplayer'); 45 | if (player && player.clientHeight) { 46 | var prev_button = player.querySelector('.prev-track'); 47 | prev_button.click(); 48 | return; 49 | } 50 | var feed_buttons = document.querySelectorAll('.feed a.play'); 51 | if (feed_buttons.length) { 52 | feed_buttons[0].click(); 53 | return; 54 | } 55 | }, 56 | pause: function () { 57 | var play_button = document.getElementById('play-button'); 58 | if (play_button.className.indexOf('pause') > 1) { 59 | play_button.click(); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /BeardedSpice/BSStrategyVersionManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // BSStrategyVersionManager.h 3 | // BeardedSpice 4 | // 5 | // Created by Alex Evers on 12/01/15. 6 | // Copyright (c) 2015 GPL v3 http://www.gnu.org/licenses/gpl.html 7 | // 8 | 9 | /** 10 | FIXME 11 | */ 12 | extern NSString *BSVMStrategyChangedNotification; 13 | 14 | @class BSStrategyCache; 15 | 16 | /** 17 | Load currently saved version index 18 | At specified time, download a copy of the remote version index from the git repo 19 | Save the new index and download updated plists if any exist 20 | At query time, if a strategy is being or will be used, reload the cached strategy object. 21 | */ 22 | @interface BSStrategyVersionManager : NSObject 23 | 24 | @property (nonatomic, strong, readonly) NSDate *lastUpdated; 25 | @property (nonatomic, strong, readonly) NSURL *versionURL; 26 | @property (nonatomic, strong, readonly) BSStrategyCache *strategyCache; 27 | 28 | /** 29 | FIXME documentation about how strategyCache is the central point of ref 30 | */ 31 | - (instancetype)initWithStrategyCache:(BSStrategyCache *)cache; 32 | 33 | ///** 34 | // @param mediaStrategy The filename of the strategy template to check. 35 | // @return returns the version number for the version of the strategy found in the index plist (versions.plist) 36 | // */ 37 | //- (long)versionForMediaStrategy:(NSString *)mediaStrategy; 38 | 39 | /** 40 | Downloads the versions.plist file from the target repository URL and checks if any new strategy template 41 | versions are marked as higher version than the currently loaded number. 42 | */ 43 | - (void)performUpdateCheck; 44 | 45 | /** 46 | Performs the same function as performUpdateCheck 47 | @return returns the number of strategies that were updated. 48 | */ 49 | - (NSUInteger)performSyncUpdateCheck; 50 | 51 | /** 52 | Subfunction of performUpdateCheck. 53 | @param mediaStrategy the name of the strategy template to download to file and reload into memory. 54 | @return Boolean saying whether the operation was successful. 55 | */ 56 | - (BOOL)performUpdateOfMediaStrategy:(NSString *)mediaStrategy; 57 | 58 | @end 59 | -------------------------------------------------------------------------------- /BeardedSpiceControllers/Controllers/BSHeadphoneStatusListener.h: -------------------------------------------------------------------------------- 1 | // 2 | // BSHeadphoneUnplugListener.h 3 | // BeardedSpice 4 | // 5 | // Created by Roman Sokolov on 08.08.15. 6 | // Copyright (c) 2015 BeardedSpice. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | ///////////////////////////////////////////////////////////////////// 13 | #pragma mark - BSHeadphoneUnplugListener 14 | ///////////////////////////////////////////////////////////////////// 15 | 16 | @protocol BSHeadphoneStatusListenerProtocol 17 | 18 | /** 19 | Action is called when headphone is unplugged. 20 | */ 21 | - (void)headphoneUnplugAction; 22 | 23 | /** 24 | Action is called when headphone is plugged in. 25 | */ 26 | - (void)headphonePlugAction; 27 | 28 | @end 29 | 30 | ///////////////////////////////////////////////////////////////////// 31 | #pragma mark - BSHeadphoneUnplugListener 32 | ///////////////////////////////////////////////////////////////////// 33 | 34 | /** 35 | Monitoring mini-jack connection. 36 | Attempt to determine unplugging headphone from it. 37 | And perform action when raises this event. 38 | */ 39 | @interface BSHeadphoneStatusListener : NSObject{ 40 | 41 | AudioDeviceID _defaultDevice; 42 | UInt32 _sourceId; 43 | AudioObjectPropertyAddress _sourceAddr; 44 | 45 | AudioObjectPropertyListenerBlock _listenerBlock; 46 | dispatch_queue_t _listenerQueue; 47 | 48 | BOOL _enabled; 49 | } 50 | 51 | ///////////////////////////////////////////////////////////////////// 52 | #pragma mark Init and class methods 53 | ///////////////////////////////////////////////////////////////////// 54 | 55 | - (BSHeadphoneStatusListener *)initWithDelegate:(id)delegate; 56 | 57 | ///////////////////////////////////////////////////////////////////// 58 | #pragma mark Properties and public methods 59 | ///////////////////////////////////////////////////////////////////// 60 | 61 | @property (weak, readonly, nonatomic) id delegate; 62 | 63 | @property BOOL enabled; 64 | 65 | @end 66 | -------------------------------------------------------------------------------- /BeardedSpiceControllers/Controllers/DDHidLib/DDHidAppleMikey.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2007 Dave Dribin 3 | * 4 | * Permission is hereby granted, free of charge, to any person 5 | * obtaining a copy of this software and associated documentation 6 | * files (the "Software"), to deal in the Software without 7 | * restriction, including without limitation the rights to use, copy, 8 | * modify, merge, publish, distribute, sublicense, and/or sell copies 9 | * 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 13 | * included in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 19 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 20 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #import 26 | #import "DDHidDevice.h" 27 | 28 | @class DDHidElement; 29 | @class DDHidQueue; 30 | 31 | @interface DDHidAppleMikey : DDHidDevice 32 | { 33 | NSMutableArray * mPressElements; 34 | 35 | id mDelegate; 36 | } 37 | 38 | + (NSArray *) allMikeys; 39 | 40 | - (id) initWithDevice: (io_object_t) device error: (NSError **) error_; 41 | 42 | #pragma mark - 43 | #pragma mark Elements 44 | 45 | - (NSArray *) pressElements; 46 | 47 | - (unsigned) numberOfKeys; 48 | 49 | - (void) addElementsToQueue: (DDHidQueue *) queue; 50 | 51 | #pragma mark - 52 | #pragma mark Asynchronous Notification 53 | 54 | - (void) setDelegate: (id) delegate; 55 | 56 | - (void) addElementsToDefaultQueue; 57 | 58 | @end 59 | 60 | @interface NSObject (DDHidAppleMikeyDelegate) 61 | 62 | - (void) ddhidAppleMikey: (DDHidAppleMikey *) mikey 63 | press: (unsigned) usageId 64 | upOrDown:(BOOL)upOrDown; 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /BeardedSpice/MediaStrategies/EightTracks.js: -------------------------------------------------------------------------------- 1 | // 2 | // EightTracks.plist 3 | // BeardedSpice 4 | // 5 | // Created by Jayson Rhynas on 1/15/2014. 6 | // Copyright (c) 2014 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | BSStrategy = { 9 | version: 1, 10 | displayName: "8tracks", 11 | accepts: { 12 | method: "predicateOnTab", 13 | format: "%K LIKE[c] '*8tracks.com*'", 14 | args: ["URL"] 15 | }, 16 | isPlaying: function () { 17 | var pause = document.querySelector('#player_pause_button'); 18 | return pause !== null && pause !== undefined &&pause.style.display !== 'none'; 19 | }, 20 | toggle: function () { 21 | var play = document.querySelector('#player_play_button'); 22 | var pause = document.querySelector('#player_pause_button'); 23 | var overlay = document.querySelector('#play_overlay'); 24 | if (play !== undefined && play !== null && play.style.display !== 'none') { play.click(); } 25 | else if (pause !== undefined && pause !== null && pause.style.display !== 'none') { pause.click(); } 26 | else if (overlay !== undefined) { overlay.click(); } 27 | }, 28 | next: function () { 29 | var skip = document.querySelector('#player_skip_button'); 30 | if (skip !== undefined && skip !== null) skip.click(); 31 | }, 32 | favorite: function () { 33 | var fav = document.querySelector('#now_playing a.fav'); 34 | if (fav !== null && fav !== undefined) fav.click() 35 | }, 36 | pause: function () { 37 | var pause = document.querySelector('#player_pause_button'); 38 | if (pause !== null && pause !== undefined) pause.click() 39 | }, 40 | trackInfo: function () { 41 | var nowPlaying = document.querySelector('#now_playing'); 42 | var titleArtist = nowPlaying.querySelector('.title_artist'); 43 | return { 44 | 'title': titleArtist.querySelector('.t').textContent, 45 | 'artist': titleArtist.querySelector('.a').textContent, 46 | 'album': nowPlaying.querySelector('.track_details .track_metadata .album .detail').textContent, 47 | 'favorited': nowPlaying.querySelector('a.fav').classList.contains('active'), 48 | 'image': document.querySelector('#mix_player_details a.thumb img').src, 49 | }; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /BeardedSpiceControllers/Controllers/DDHidLib/DDHidElement.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2007 Dave Dribin 3 | * 4 | * Permission is hereby granted, free of charge, to any person 5 | * obtaining a copy of this software and associated documentation 6 | * files (the "Software"), to deal in the Software without 7 | * restriction, including without limitation the rights to use, copy, 8 | * modify, merge, publish, distribute, sublicense, and/or sell copies 9 | * 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 13 | * included in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 19 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 20 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #import 26 | #include 27 | 28 | @class DDHidUsage; 29 | 30 | @interface DDHidElement : NSObject 31 | { 32 | NSDictionary * mProperties; 33 | DDHidUsage * mUsage; 34 | NSArray * mElements; 35 | } 36 | 37 | + (NSArray *) elementsWithPropertiesArray: (NSArray *) propertiesArray; 38 | 39 | + (DDHidElement *) elementWithProperties: (NSDictionary *) properties; 40 | 41 | - (id) initWithProperties: (NSDictionary *) properties; 42 | 43 | - (NSDictionary *) properties; 44 | 45 | - (NSString *) stringForKey: (NSString *) key; 46 | 47 | - (NSString *) description; 48 | 49 | - (IOHIDElementCookie) cookie; 50 | - (unsigned) cookieAsUnsigned; 51 | 52 | - (NSArray *) elements; 53 | - (DDHidUsage *) usage; 54 | - (NSString *) name; 55 | - (BOOL) hasNullState; 56 | - (BOOL) hasPreferredState; 57 | - (BOOL) isArray; 58 | - (BOOL) isRelative; 59 | - (BOOL) isWrapping; 60 | - (long) maxValue; 61 | - (long) minValue; 62 | 63 | - (NSComparisonResult) compareByUsage: (DDHidElement *) device; 64 | 65 | @end 66 | -------------------------------------------------------------------------------- /BeardedSpice/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // BeardedSpice 4 | // 5 | // Created by Tyler Rhodes on 12/8/13. 6 | // Copyright (c) 2013 Tyler Rhodes / Jose Falcon. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "Chrome.h" 12 | #import "Safari.h" 13 | #import "iTunes.h" 14 | #import "TabAdapter.h" 15 | #import "MediaStrategyRegistry.h" 16 | #import "NativeAppTabRegistry.h" 17 | #import "BeardedSpiceHostAppProtocol.h" 18 | 19 | #import "BSMediaStrategy.h" 20 | 21 | #define APPDELEGATE (AppDelegate *)([[NSApplication sharedApplication] delegate]) 22 | 23 | @class runningSBApplication, BSStrategyVersionManager; 24 | 25 | extern BOOL accessibilityApiEnabled; 26 | 27 | @interface AppDelegate : NSObject { 28 | 29 | IBOutlet NSMenu *statusMenu; 30 | NSUInteger statusMenuCount; 31 | NSStatusItem *statusItem; 32 | 33 | runningSBApplication *chromeApp; 34 | runningSBApplication *canaryApp; 35 | runningSBApplication *yandexBrowserApp; 36 | runningSBApplication *chromiumApp; 37 | 38 | runningSBApplication *safariApp; 39 | NSMutableSet *SafariTabKeys; 40 | 41 | NSMutableArray *nativeApps; 42 | 43 | TabAdapter *activeTab; 44 | NSString *activeTabKey; 45 | 46 | NSMutableArray *menuItems; 47 | NSMutableArray *playingTabs; 48 | 49 | MediaStrategyRegistry *mediaStrategyRegistry; 50 | NativeAppTabRegistry *nativeAppRegistry; 51 | 52 | NSWindowController *_preferencesWindowController; 53 | 54 | NSMutableSet *openedWindows; 55 | 56 | dispatch_queue_t workingQueue; 57 | dispatch_queue_t notificationQueue; 58 | 59 | NSXPCConnection *_connectionToService; 60 | 61 | BOOL _AXAPIEnabled; 62 | } 63 | 64 | @property (nonatomic, readonly) NSWindowController *preferencesWindowController; 65 | @property (nonatomic, strong) BSStrategyVersionManager *versionManager; 66 | 67 | - (IBAction)checkForUpdates:(id)sender; 68 | - (IBAction)openPreferences:(id)sender; 69 | - (void)showNotification; 70 | 71 | ///////////////////////////////////////////////////////////////////// 72 | #pragma mark Windows control methods 73 | 74 | -(void)windowWillBeVisible:(id)window; 75 | -(void)removeWindow:(id)obj; 76 | 77 | @end 78 | -------------------------------------------------------------------------------- /BeardedSpiceControllers/Controllers/DDHidLib/DDHidEvent.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2007 Dave Dribin 3 | * 4 | * Permission is hereby granted, free of charge, to any person 5 | * obtaining a copy of this software and associated documentation 6 | * files (the "Software"), to deal in the Software without 7 | * restriction, including without limitation the rights to use, copy, 8 | * modify, merge, publish, distribute, sublicense, and/or sell copies 9 | * 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 13 | * included in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 19 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 20 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #import "DDHidEvent.h" 26 | 27 | 28 | @implementation DDHidEvent 29 | 30 | + (DDHidEvent *) eventWithIOHIDEvent: (IOHIDEventStruct *) event; 31 | { 32 | return [[[self alloc] initWithIOHIDEvent: event] autorelease]; 33 | } 34 | 35 | - (id) initWithIOHIDEvent: (IOHIDEventStruct *) event; 36 | { 37 | self = [super init]; 38 | if (self == nil) 39 | return nil; 40 | 41 | mEvent = *event; 42 | 43 | return self; 44 | } 45 | 46 | - (IOHIDElementType) type; 47 | { 48 | return mEvent.type; 49 | } 50 | 51 | - (IOHIDElementCookie) elementCookie; 52 | { 53 | return mEvent.elementCookie; 54 | } 55 | 56 | - (unsigned) elementCookieAsUnsigned; 57 | { 58 | return (unsigned) mEvent.elementCookie; 59 | } 60 | 61 | - (SInt32) value; 62 | { 63 | return mEvent.value; 64 | } 65 | 66 | - (AbsoluteTime) timestamp; 67 | { 68 | return mEvent.timestamp; 69 | } 70 | 71 | - (UInt32) longValueSize; 72 | { 73 | return mEvent.longValueSize; 74 | } 75 | 76 | - (void *) longValue; 77 | { 78 | return mEvent.longValue; 79 | } 80 | 81 | @end 82 | -------------------------------------------------------------------------------- /BeardedSpiceTests/BSMediaStrategyTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // BSMediaStrategyTests.m 3 | // BeardedSpice 4 | // 5 | // Created by Alex Evers on 12/01/15. 6 | // Copyright (c) 2015 GPL v3 http://www.gnu.org/licenses/gpl.html 7 | // 8 | 9 | #import "Kiwi.h" 10 | #import "BSMediaStrategy.h" 11 | 12 | SPEC_BEGIN(BSMediaStrategyTests) 13 | 14 | /* 15 | describe(@"Create an empty strategy", ^{ 16 | let(path, ^{ return [[[NSBundle mainBundle] resourceURL] URLByAppendingPathComponent:@"MediaStrategies"]; }); 17 | 18 | it(@"will initialize defaults without template data", ^{ 19 | NSString *fileName = @"random-file-name"; 20 | NSURL *fileURL = [[NSURL alloc] initWithString:fileName relativeToURL:path]; 21 | BSMediaStrategy *strategy = [[BSMediaStrategy alloc] initWithStrategyURL:fileURL]; 22 | [[strategy should] beMemberOfClass:BSMediaStrategy.class]; 23 | 24 | [[strategy.scripts should] beNil]; 25 | [[theValue(strategy.strategyVersion) should] equal:theValue(0)]; 26 | 27 | [[strategy.displayName should] equal:fileName]; 28 | [[strategy.fileName should] equal:fileName]; 29 | }); 30 | }); 31 | */ 32 | 33 | describe(@"Load the Youtube strategy", ^{ 34 | let(path, ^{ return [[[NSBundle mainBundle] resourceURL] URLByAppendingPathComponent:@"MediaStrategies"]; }); 35 | 36 | it(@"will load the strategy properly", ^{ 37 | NSString *youtubeName = @"Youtube"; 38 | NSString *fileName = [NSString stringWithFormat:@"%@.js", youtubeName]; 39 | NSURL *fileURL = [[NSURL alloc] initWithString:fileName relativeToURL:path]; 40 | BSMediaStrategy *strategy = [BSMediaStrategy mediaStrategyWithURL:fileURL error:nil]; 41 | [[strategy should] beMemberOfClass:BSMediaStrategy.class]; 42 | 43 | [[strategy.scripts shouldNot] beNil]; 44 | [[theValue(strategy.strategyVersion) shouldNot] equal:theValue(0)]; 45 | 46 | [[strategy.displayName should] equal:@"Youtube"]; // pasted from js 47 | [[strategy.fileName should] equal:@"Youtube.js"]; 48 | [[strategy.displayName shouldNot] equal:strategy.fileName]; 49 | 50 | [[strategy.toggle shouldNot] beNil]; 51 | [[strategy.previous shouldNot] beNil]; 52 | [[strategy.next shouldNot] beNil]; 53 | [[strategy.pause shouldNot] beNil]; 54 | }); 55 | }); 56 | 57 | SPEC_END 58 | --------------------------------------------------------------------------------