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