├── .github ├── FUNDING.yml └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── Caffeinate.lbaction └── Contents │ ├── Info.plist │ ├── Resources │ └── Coffee.icns │ └── Scripts │ ├── caffeinate.sh │ └── stop.sh ├── Dismiss Notifications.lbaction └── Contents │ ├── Info.plist │ └── Scripts │ ├── dismiss.applescript │ └── dismiss.js ├── Dump Item.lbaction └── Contents │ ├── Info.plist │ ├── Resources │ ├── equipment.icns │ ├── headphones.icns │ └── microphone.icns │ └── Scripts │ └── dump.js ├── Expand URL.lbaction └── Contents │ ├── Info.plist │ └── Scripts │ ├── expand.js │ └── resolve.sh ├── Forecast.lbaction └── Contents │ ├── Info.plist │ ├── Resources │ ├── Cloud-Fog.png │ ├── Cloud-Hail-Alt.png │ ├── Cloud-Hail.png │ ├── Cloud-Moon.png │ ├── Cloud-Rain.png │ ├── Cloud-Sun.png │ ├── Cloud.png │ ├── Moon.png │ ├── Snowflake.png │ ├── Sun-Low.png │ ├── Sun.png │ ├── Sunrise.png │ ├── Sunset.png │ ├── Thermometer-25.png │ ├── Thermometer-50.png │ ├── Thermometer-75.png │ ├── Tornado.png │ ├── Umbrella.png │ ├── Wind.png │ ├── airplane.png │ ├── bosnia.icns │ ├── canada.icns │ ├── en.lproj │ │ └── default.strings │ ├── forecastio.png │ ├── france.icns │ ├── germany.icns │ ├── indonesia.icns │ ├── italy.icns │ ├── netherlands.icns │ ├── poland.icns │ ├── portugal.icns │ ├── russia.icns │ ├── si.png │ ├── spain.icns │ ├── uk.icns │ └── us.icns │ └── Scripts │ ├── forecast-main.js │ ├── forecast.js │ ├── location.js │ ├── moment-timezone.js │ ├── moment.js │ ├── search.js │ └── settings.js ├── Generate Password.lbaction └── Contents │ ├── Info.plist │ └── Scripts │ ├── generate.js │ └── hsxkpasswd-config.json ├── Incognito.lbaction └── Contents │ ├── Info.plist │ ├── Resources │ └── incognito.png │ └── Scripts │ └── incognito.applescript ├── JIRA.lbaction └── Contents │ ├── Info.plist │ ├── Resources │ └── jira.png │ └── Scripts │ └── jira.js ├── LICENSE ├── MailMate.applescript ├── README.md ├── Read-Later.lbaction └── Contents │ ├── Info.plist │ └── Scripts │ └── readlater.js ├── Recents.lbaction └── Contents │ ├── Info.plist │ └── Scripts │ └── recents.js ├── Screenshot.lbaction └── Contents │ ├── Info.plist │ └── Scripts │ ├── imgcopy │ ├── screenshot.js │ ├── trash │ └── trash.swift ├── Share Safari Link.lbaction └── Contents │ ├── Info.plist │ └── Scripts │ └── share.applescript ├── Share.lbaction └── Contents │ ├── Info.plist │ └── Scripts │ ├── share.js │ ├── share.sh │ └── zip.sh ├── Switch Audio.lbaction └── Contents │ ├── Info.plist │ ├── Resources │ ├── equipment.icns │ ├── headphones.icns │ └── microphone.icns │ └── Scripts │ ├── audio.js │ └── audio.sh ├── Things.lbaction └── Contents │ ├── Info.plist │ └── Scripts │ └── things.applescript ├── Timer.lbaction └── Contents │ ├── Info.plist │ └── Scripts │ └── timer.js ├── Today.lbaction └── Contents │ ├── Info.plist │ ├── Resources │ └── icalb.png │ └── Scripts │ ├── call.applescript │ ├── moment-with-locales.js │ └── today.js ├── Updates.lbaction └── Contents │ ├── Info.plist │ ├── Resources │ ├── actionTemplate.png │ ├── alertTemplate.png │ ├── cautionTemplate.png │ ├── checkTemplate.png │ ├── downloadTemplate.png │ ├── logTemplate.png │ ├── prefTemplate.png │ ├── skipTemplate.png │ └── urlTemplate.png │ └── Scripts │ ├── README.md │ └── updates.js └── docs ├── index.html └── updates.html /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: "https://www.buymeacoffee.com/padraic" 14 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '26 0 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v2 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v2 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _CodeSignature 2 | *.scpt 3 | *.dSYM 4 | bin 5 | .DS_Store 6 | .localized 7 | 8 | Caffeinate.lbaction/Contents/Scripts/0 9 | -------------------------------------------------------------------------------- /Caffeinate.lbaction/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.renaghan.launchbar.Caffeinate 7 | CFBundleName 8 | Caffeinate 9 | CFBundleVersion 10 | 4.2 11 | CFBundleIconFile 12 | font-awesome:fa-coffee 13 | LBDebugLogEnabled 14 | 15 | LBTextInputTitle 16 | Duration e.g. 0/10m/2h 17 | LBScripts 18 | 19 | LBDefaultScript 20 | 21 | LBScriptName 22 | caffeinate.sh 23 | LBRunInBackground 24 | 25 | LBRequiresArgument 26 | 27 | LBAcceptedArgumentTypes 28 | 29 | string 30 | 31 | LBReturnsResult 32 | 33 | 34 | 35 | LBDescription 36 | 37 | LBSummary 38 | Keep system awake, as caffeine does, for a specified period of time. Uses built in system caffeinate command. 39 | LBAuthor 40 | Padraic Renaghan 41 | LBEmail 42 | prenagha@renaghan.com 43 | LBWebsiteURL 44 | https://renaghan.com/launchbar/caffeinate/ 45 | LBTwitter 46 | @prenagha 47 | LBArgument 48 | Duration to stay awake, e.g. "10m", "2h". Hours by default. Use "0" to caffeinate forever. 49 | LBUpdateURL 50 | https://raw.githubusercontent.com/prenagha/launchbar/master/Caffeinate.lbaction/Contents/Info.plist 51 | LBDownloadURL 52 | https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/prenagha/launchbar/tree/main/Caffeinate.lbaction 53 | LBChangelog 54 | 55 | 4.2: Fix download URL 56 | 4.0: More aggressive use of caffeinate, changed -u to -dimsu 57 | 3.2: Fix search for existing caffeinate 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /Caffeinate.lbaction/Contents/Resources/Coffee.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Caffeinate.lbaction/Contents/Resources/Coffee.icns -------------------------------------------------------------------------------- /Caffeinate.lbaction/Contents/Scripts/caffeinate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | STP="/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/AlertStopIcon.icns" 3 | CLK="Coffee.icns" 4 | 5 | # orig idea and some code from https://github.com/shawnrice/alfred-2-caffeinate-workflow 6 | # Coffee.icns from http://www.everaldo.com 7 | 8 | toSeconds() { 9 | arg=$1 10 | (( sec=60*60*2 )) 11 | if [[ $arg =~ ([0-9]+)$ ]] 12 | then 13 | (( sec=$arg*60*60 )) 14 | else 15 | [[ $arg =~ ([0-9]+)([hHmM])$ ]] 16 | time=${BASH_REMATCH[1]} 17 | unit=${BASH_REMATCH[2]} 18 | if [[ $unit =~ ([hH]) ]] 19 | then 20 | (( sec=$time*60*60 )) 21 | elif [[ $unit =~ ([mM]) ]] 22 | then 23 | (( sec=$time*60 )) 24 | fi 25 | fi 26 | echo $sec 27 | } 28 | 29 | toString() { 30 | sec=$1 31 | if [ $sec == 0 ] 32 | then 33 | echo "ever" 34 | else 35 | hour=$(($sec/3600)) 36 | min=$((($sec/60)%60)) 37 | printf " %02d:%02d" $hour $min 38 | fi 39 | } 40 | 41 | # if no argument then show status 42 | if [ -z "$1" ] 43 | then 44 | PROC=`ps -eo etime,args | grep caffeinate | grep -v grep | grep -v caffeinate.sh | head -n 1` 45 | if [ -z "$PROC" ] 46 | then 47 | echo "[{\"title\":\"Not caffeinated\",\"icon\":\"NotFound.icns\"}]" 48 | else 49 | # 03:26 caffeinate -u -t 1200 50 | # 01:03:26 caffeinate -u -t 1200 51 | [[ "$PROC" =~ ([0-9]+)$ ]] 52 | dur=${BASH_REMATCH[1]} 53 | 54 | if [[ "$PROC" =~ (-w [0-9]+) ]] 55 | then 56 | # if caffeinate running while another process is running 57 | str=" running PID ${dur}" 58 | elif [[ "$PROC" =~ ([0-9]+):([0-9]+):([0-9]+) ]] 59 | then 60 | (( elapsed=${BASH_REMATCH[1]}*60*60 + ${BASH_REMATCH[2]}*60 )) 61 | else 62 | [[ $PROC =~ ([0-9]+):([0-9]+) ]] 63 | (( elapsed=${BASH_REMATCH[1]}*60 )) 64 | fi 65 | if [ -z "${str}" ] 66 | then 67 | if [ -z "${dur}" -o "${dur}" == 0 ] 68 | then 69 | remain=0 70 | else 71 | (( remain=$dur - $elapsed )) 72 | fi 73 | str=`toString $remain` 74 | fi 75 | echo "[{\"title\":\"Caffeinated awake for${str}\",\"icon\":\"$CLK\"},\ 76 | {\"title\":\"Stop caffeinating\",\"icon\":\"$STP\",\"action\":\"stop.sh\"}]" 77 | fi 78 | else 79 | # if we have an argument then process it as a new caffeinate time 80 | secArg= 81 | sec=`toSeconds $1` 82 | if [ $sec > 0 ] 83 | then 84 | secArg="-t $sec" 85 | fi 86 | str=`toString $sec` 87 | killall caffeinate 2>/dev/null 88 | caffeinate -dimsu $secArg 1>/dev/null 2>&1 & 89 | echo "[{\"title\":\"Caffeinated awake for${str}\",\"icon\":\"$CLK\"}]" 90 | fi 91 | exit 0 -------------------------------------------------------------------------------- /Caffeinate.lbaction/Contents/Scripts/stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | STP="/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/AlertStopIcon.icns" 3 | killall caffeinate 2>/dev/null 4 | echo "[{\"title\":\"Caffeinating stopped\",\"icon\":\"$STP\"}]" 5 | exit 0 -------------------------------------------------------------------------------- /Dismiss Notifications.lbaction/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.renaghan.launchbar.Dismiss 7 | CFBundleName 8 | Dismiss Notifications 9 | CFBundleVersion 10 | 2.5 11 | CFBundleIconFile 12 | /System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/Notifications.icns 13 | LBDebugLogEnabled 14 | 15 | LBScripts 16 | 17 | LBDefaultScript 18 | 19 | LBScriptName 20 | dismiss.js 21 | LBRunInBackground 22 | 23 | LBRequiresArgument 24 | 25 | LBReturnsResult 26 | 27 | 28 | 29 | LBDescription 30 | 31 | LBSummary 32 | Dismiss all notifications from Notification Center. Will "Snooze" calendar items, "Close" on all others. Adjust preferences for other languages. 33 | LBAuthor 34 | Padraic Renaghan 35 | LBEmail 36 | prenagha@renaghan.com 37 | LBWebsiteURL 38 | https://renaghan.com/launchbar/dismiss-notifications/ 39 | LBTwitter 40 | @prenagha 41 | LBUpdateURL 42 | https://raw.githubusercontent.com/prenagha/launchbar/master/Dismiss%20Notifications.lbaction/Contents/Info.plist 43 | LBDownloadURL 44 | https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/prenagha/launchbar/tree/main/Dismiss%20Notifications.lbaction&fileName=Dismiss-Notifications&rootDirectory=Dismiss-Notifications.lbaction 45 | LBChangelog 46 | 47 | 2.5: Fix download URL 48 | 2.3: Support other languages via preferences 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Dismiss Notifications.lbaction/Contents/Scripts/dismiss.applescript: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Dismiss Notifications.lbaction/Contents/Scripts/dismiss.applescript -------------------------------------------------------------------------------- /Dismiss Notifications.lbaction/Contents/Scripts/dismiss.js: -------------------------------------------------------------------------------- 1 | 2 | function run(arg) { 3 | try { 4 | if (!Action.preferences.snoozeButtonName) 5 | Action.preferences.snoozeButtonName = "Snooze"; 6 | if (!Action.preferences.whenIsNowText) 7 | Action.preferences.whenIsNowText = "now"; 8 | if (!Action.preferences.closeButtonName) 9 | Action.preferences.closeButtonName = "Close"; 10 | if (!Action.preferences.okButtonName) 11 | Action.preferences.okButtonName = "OK"; 12 | if (!Action.preferences.numbersInConferenceCall) 13 | Action.preferences.numbersInConferenceCall = 14; 14 | 15 | let ascript = Action.path + '/Contents/Scripts/dismiss.scpt'; 16 | if (!File.exists(ascript)) { 17 | ascript = Action.path + '/Contents/Scripts/dismiss.applescript'; 18 | } 19 | if (!File.exists(ascript)) { 20 | LaunchBar.alert('Error', 'Applescript not found ' + ascript); 21 | } 22 | 23 | LaunchBar.executeAppleScriptFile(ascript, 24 | Action.preferences.snoozeButtonName 25 | ,Action.preferences.whenIsNowText 26 | ,Action.preferences.closeButtonName 27 | ,Action.preferences.okButtonName 28 | ,Action.preferences.numbersInConferenceCall 29 | ); 30 | 31 | } catch (exception) { 32 | LaunchBar.log('Error ' + exception); 33 | LaunchBar.alert('Error', exception); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Dump Item.lbaction/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.renaghan.launchbar.DumpItem 7 | CFBundleName 8 | Dump Item 9 | LBAbbreviation 10 | debug log 11 | CFBundleVersion 12 | 2.3 13 | CFBundleIconFile 14 | UnixExecutable.icns 15 | LBDebugLogEnabled 16 | 17 | LBScripts 18 | 19 | LBDefaultScript 20 | 21 | LBScriptName 22 | dump.js 23 | LBRunInBackground 24 | 25 | LBRequiresArgument 26 | 27 | LBReturnsResult 28 | 29 | LBAcceptedArgumentTypes 30 | 31 | string 32 | path 33 | url 34 | item 35 | 36 | 37 | 38 | LBDescription 39 | 40 | LBSummary 41 | Dumps contents of passed in item back as LaunchBar results. Useful for LaunchBar action developers to see the kind and type of data is being passed around. 42 | LBAuthor 43 | Padraic Renaghan 44 | LBEmail 45 | prenagha@renaghan.com 46 | LBWebsiteURL 47 | https://renaghan.com/launchbar/dump-item/ 48 | LBTwitter 49 | @prenagha 50 | LBUpdateURL 51 | https://raw.githubusercontent.com/prenagha/launchbar/master/Dump%20Item.lbaction/Contents/Info.plist 52 | LBDownloadURL 53 | https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/prenagha/launchbar/tree/main/Dump%20Item.lbaction&fileName=Dump-Item&rootDirectory=Dump-Item.lbaction 54 | LBChangelog 55 | 56 | 2.3: Fix download URL 57 | Support action updates 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /Dump Item.lbaction/Contents/Resources/equipment.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Dump Item.lbaction/Contents/Resources/equipment.icns -------------------------------------------------------------------------------- /Dump Item.lbaction/Contents/Resources/headphones.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Dump Item.lbaction/Contents/Resources/headphones.icns -------------------------------------------------------------------------------- /Dump Item.lbaction/Contents/Resources/microphone.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Dump Item.lbaction/Contents/Resources/microphone.icns -------------------------------------------------------------------------------- /Dump Item.lbaction/Contents/Scripts/dump.js: -------------------------------------------------------------------------------- 1 | var ICON = "GrayInfo.tiff"; 2 | 3 | function dumpAction() { 4 | var items = []; 5 | items.push({title:'path: ' + Action.path, label: typeStr(Action.path)}); 6 | items.push({title:'scriptType: ' + Action.scriptType, label: typeStr(Action.scriptType)}); 7 | items.push({title:'version: ' + Action.version, label: typeStr(Action.version)}); 8 | items.push({title:'bundleIdentifier: ' + Action.bundleIdentifier, label: typeStr(Action.bundleIdentifier)}); 9 | items.push({title:'cachePath: ' + Action.cachePath, label: typeStr(Action.cachePath)}); 10 | items.push({title:'supportPath: ' + Action.supportPath, label: typeStr(Action.supportPath)}); 11 | items.push({title:'debugLogEnabled: ' + Action.debugLogEnabled, label: typeStr(Action.debugLogEnabled)}); 12 | items.push({title:'preferences ➔', label: typeStr(Action.preferences), children:printItem(Action.preferences)}); 13 | 14 | var out = []; 15 | out.push(dumpLaunchBar()); 16 | out.push({title:'Action ➔',children:items}); 17 | return out; 18 | } 19 | 20 | function dumpLaunchBar() { 21 | var items = []; 22 | items.push({title:'systemVersion: ' + LaunchBar.systemVersion, label: typeStr(LaunchBar.systemVersion)}); 23 | items.push({title:'currentLocale: ' + LaunchBar.currentLocale, label: typeStr(LaunchBar.currentLocale)}); 24 | items.push({title:'path: ' + LaunchBar.path, label: typeStr(LaunchBar.path)}); 25 | items.push({title:'version: ' + LaunchBar.version, label: typeStr(LaunchBar.version)}); 26 | items.push({title:'bundleIdentifier: ' + LaunchBar.bundleIdentifier, label: typeStr(LaunchBar.bundleIdentifier)}); 27 | items.push({title:'homeDirectory: ' + LaunchBar.homeDirectory, label: typeStr(LaunchBar.homeDirectory)}); 28 | items.push({title:'userName: ' + LaunchBar.userName, label: typeStr(LaunchBar.userName)}); 29 | items.push({title:'userID: ' + LaunchBar.userID, label: typeStr(LaunchBar.userID)}); 30 | items.push({title:'hostName: ' + LaunchBar.hostName, label: typeStr(LaunchBar.hostName)}); 31 | items.push({title:'computerName: ' + LaunchBar.computerName, label: typeStr(LaunchBar.computerName)}); 32 | items.push({title:'options ➔', label: typeStr(LaunchBar.options), children: printItem(LaunchBar.options)}); 33 | 34 | return {title:'LaunchBar ➔',children:items}; 35 | } 36 | 37 | function printArg(arg) { 38 | if (typeof arg === 'undefined') { 39 | return '*undefined*'; 40 | } else if (arg === null) { 41 | return '*null*'; 42 | } else { 43 | return '"' + arg + '"'; 44 | } 45 | } 46 | 47 | function typeStr(arg) { 48 | if (typeof(arg) === 'undefined') { 49 | return '*undefined*'; 50 | } else if (arg === null) { 51 | return '*null*'; 52 | } else { 53 | return typeof(arg); 54 | } 55 | } 56 | 57 | function printItem(arg) { 58 | if (typeof(arg) === 'undefined') { 59 | return [{title: '*undefined*', icon:ICON}]; 60 | } else if (arg === null) { 61 | return [{title: '*null*', icon:ICON}]; 62 | } else if (typeof(arg) !== 'object') { 63 | return [{title: '"' + arg + '"', icon:ICON}]; 64 | } 65 | 66 | var empty = true; 67 | var items = []; 68 | for (var p in arg) { 69 | if (!arg.hasOwnProperty(p)) 70 | continue; 71 | empty = false; 72 | var v = arg[p]; 73 | var t = typeof(v); 74 | if (t === 'object') { 75 | items.push({title: p + ' ➔', label: t, icon: ICON, children: printItem(v)}); 76 | } else { 77 | items.push({title: p + ': ' + printArg(v), label: typeStr(v), icon:ICON}); 78 | } 79 | } 80 | if (empty) 81 | return [{title: '*empty*', icon:ICON}]; 82 | return items; 83 | } 84 | 85 | function run(arg) { 86 | var items = dumpAction(); 87 | items.push({title: 'run() handler called', icon:"GrayInfoPressed.tiff"}); 88 | items.push({title: 'Argument ➔', label: typeStr(arg), icon:ICON, children:printItem(arg)}); 89 | return items; 90 | } 91 | 92 | function runWithItem(item) { 93 | var items = dumpAction(); 94 | items.push({title: 'runWithItem() handler called', icon:"ClipObject.icns"}); 95 | items.push({title: 'Argument ➔', label: typeStr(item), icon:ICON, children:printItem(item)}); 96 | return items; 97 | } 98 | 99 | function runWithPaths(paths) { 100 | var items = dumpAction(); 101 | items.push({title: 'runWithPaths() handler called', icon:"FileOperationMove.icns"}); 102 | paths.forEach(function(p) { 103 | items.push({title: 'Argument ➔', path:p, label: typeStr(p), icon:"FileOperationMove.icns", children:printItem(p)}); 104 | }); 105 | return items; 106 | } 107 | 108 | function runWithString(str) { 109 | var items = dumpAction(); 110 | items.push({title: 'runWithString() handler called', icon:"EnterText.icns"}); 111 | items.push({title: 'Argument ➔', label: typeStr(str), icon:"EnterText.icns", children:printItem(str)}); 112 | return items; 113 | } 114 | 115 | function runWithURL(theURL, details) { 116 | var items = dumpAction(); 117 | items.push({title: 'runWithURL() handler called', url: theURL, icon:"URL.icns"}); 118 | items.push({title: 'URL Argument ➔', label: typeStr(theURL), icon:"URL.icns", url: theURL, children:printItem(theURL)}); 119 | items.push({title: 'Details Argument ➔', label: typeStr(details), icon:ICON, children:printItem(details)}); 120 | return items; 121 | } -------------------------------------------------------------------------------- /Expand URL.lbaction/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.renaghan.launchbar.ExpandURL 7 | CFBundleName 8 | Expand URL 9 | CFBundleVersion 10 | 3.5 11 | CFBundleIconFile 12 | URL.icns 13 | LBDebugLogEnabled 14 | 15 | LBTextInputTitle 16 | URL 17 | LBScripts 18 | 19 | LBDefaultScript 20 | 21 | LBScriptName 22 | expand.js 23 | LBRunInBackground 24 | 25 | LBRequiresArgument 26 | 27 | LBAcceptedArgumentTypes 28 | 29 | string 30 | 31 | LBReturnsResult 32 | 33 | 34 | 35 | LBDescription 36 | 37 | LBSummary 38 | Expand a short URL. Follows the URL and returns the last Location HTTP header which will point to the real URL. Returns URL back to LaunchBar. 39 | LBRequirements 40 | curl 41 | LBAuthor 42 | Padraic Renaghan 43 | LBEmail 44 | prenagha@renaghan.com 45 | LBWebsiteURL 46 | https://renaghan.com/launchbar/expand-url/ 47 | LBTwitter 48 | @prenagha 49 | LBArgument 50 | URL to expand 51 | LBUpdateURL 52 | https://raw.githubusercontent.com/prenagha/launchbar/master/Expand%20URL.lbaction/Contents/Info.plist 53 | LBDownloadURL 54 | https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/prenagha/launchbar/tree/main/Expand%20URL.lbaction&fileName=Expand-URL&rootDirectory=Expand-URL.lbaction 55 | LBChangelog 56 | 57 | 3.5: Improved curl command, thanks Keith Bolland 58 | 3.4: Fix download URL 59 | Support action updates 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /Expand URL.lbaction/Contents/Scripts/expand.js: -------------------------------------------------------------------------------- 1 | 2 | function runWithString(string) { 3 | return go(string); 4 | } 5 | 6 | function runWithItem(item) { 7 | if (item && item.url && item.url.length > 0) { 8 | return go(item.url); 9 | } 10 | } 11 | 12 | function runWithURL(url, details) { 13 | return go(url); 14 | } 15 | 16 | function run() { 17 | return go(LaunchBar.getClipboardString()); 18 | } 19 | 20 | function go(url) { 21 | if (!url || url == undefined || url.length == 0) { 22 | LaunchBar.alert('No URL found to expand'); 23 | return; 24 | } 25 | try { 26 | LaunchBar.debugLog('URL=' + url); 27 | var exp = LaunchBar.execute('/bin/bash', 'resolve.sh', url); 28 | LaunchBar.debugLog('Expanded' + exp); 29 | if (!exp || exp == undefined || exp.length == 0) 30 | exp = url; 31 | return {'title':exp 32 | ,'subtitle':url 33 | ,'url':exp 34 | ,'quickLookURL':exp}; 35 | } catch (exception) { 36 | LaunchBar.log('Error ' + exception); 37 | LaunchBar.alert('Error expanding ' + url, exception); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Expand URL.lbaction/Contents/Scripts/resolve.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # original idea from https://gist.github.com/Zettt/88ef3112c04ebecf475b 3 | EXP=`curl -siL -w '%{url_effective}' -o /dev/null "$1"` 4 | if [ -z "$EXP" ] 5 | then 6 | echo "$1" 7 | else 8 | echo "$EXP" 9 | fi 10 | exit 0 11 | -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.renaghan.launchbar.Forecast 7 | CFBundleName 8 | Forecast 9 | CFBundleVersion 10 | 4.6 11 | CFBundleIconFile 12 | Sun-Low.png 13 | LBAbbreviation 14 | weather temperature precipitation 15 | LBDebugLogEnabled 16 | 17 | LBMinimumLaunchBarVersion 18 | 6101 19 | LBTextInputTitle 20 | Location 21 | LBScripts 22 | 23 | LBDefaultScript 24 | 25 | LBScriptName 26 | forecast-main.js 27 | LBActionURLScript 28 | forecast-main.js 29 | LBRunInBackground 30 | 31 | LBRequiresArgument 32 | 33 | LBAcceptedArgumentTypes 34 | 35 | string 36 | 37 | LBReturnsResult 38 | 39 | 40 | 41 | LBDescription 42 | 43 | LBSummary 44 | Forecast the weather for a location using Forecast.io forecast data 45 | LBRequirements 46 | curl 47 | LBAuthor 48 | Padraic Renaghan 49 | LBEmail 50 | prenagha@renaghan.com 51 | LBWebsiteURL 52 | https://renaghan.com/launchbar/forecast/ 53 | LBTwitter 54 | @prenagha 55 | LBArgument 56 | Location 57 | LBUpdateURL 58 | https://raw.githubusercontent.com/prenagha/launchbar/master/Forecast.lbaction/Contents/Info.plist 59 | LBDownloadURL 60 | https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/prenagha/launchbar/tree/main/Forecast.lbaction 61 | LBChangelog 62 | 63 | 4.6: moment.js updates 64 | 4.5: Fix download URL 65 | 4.4: Removed use of Location Helper since Google now requires API key, now using ip-api.com for current location 66 | 4.2: Forecast.io branding change to Dark Sky 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/Cloud-Fog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/Cloud-Fog.png -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/Cloud-Hail-Alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/Cloud-Hail-Alt.png -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/Cloud-Hail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/Cloud-Hail.png -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/Cloud-Moon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/Cloud-Moon.png -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/Cloud-Rain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/Cloud-Rain.png -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/Cloud-Sun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/Cloud-Sun.png -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/Cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/Cloud.png -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/Moon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/Moon.png -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/Snowflake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/Snowflake.png -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/Sun-Low.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/Sun-Low.png -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/Sun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/Sun.png -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/Sunrise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/Sunrise.png -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/Sunset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/Sunset.png -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/Thermometer-25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/Thermometer-25.png -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/Thermometer-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/Thermometer-50.png -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/Thermometer-75.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/Thermometer-75.png -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/Tornado.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/Tornado.png -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/Umbrella.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/Umbrella.png -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/Wind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/Wind.png -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/airplane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/airplane.png -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/bosnia.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/bosnia.icns -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/canada.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/canada.icns -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/en.lproj/default.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/en.lproj/default.strings -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/forecastio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/forecastio.png -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/france.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/france.icns -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/germany.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/germany.icns -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/indonesia.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/indonesia.icns -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/italy.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/italy.icns -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/netherlands.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/netherlands.icns -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/poland.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/poland.icns -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/portugal.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/portugal.icns -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/russia.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/russia.icns -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/si.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/si.png -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/spain.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/spain.icns -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/uk.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/uk.icns -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Resources/us.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Forecast.lbaction/Contents/Resources/us.icns -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Scripts/forecast-main.js: -------------------------------------------------------------------------------- 1 | String.prototype.localizationTable = 'default'; 2 | 3 | include('forecast.js'); 4 | include('location.js'); 5 | include('search.js'); 6 | include('settings.js'); 7 | include('moment.js'); 8 | include('moment-timezone.js'); 9 | 10 | function isDebug() { 11 | return Action.preferences.debug || Action.debugLogEnabled; 12 | } 13 | 14 | function debugLog(l) { 15 | if (isDebug()) 16 | LaunchBar.log(l); 17 | } 18 | 19 | 20 | function runWithString(string) { 21 | return locationSearch(string); 22 | } 23 | 24 | function runWithItem(item) { 25 | return locationSearch(item.title); 26 | } 27 | 28 | function runWithURL(url, details) { 29 | if (details && details.queryParameters && details.queryParameters.q) 30 | return locationSearch(details.queryParameters.q); 31 | } 32 | 33 | function run() { 34 | var items = []; 35 | var loc = selectedLoc(); 36 | if (loc == null) { 37 | items.push({'title':'locationNotFound'.localize(),'icon':'NotFound.icns'}); 38 | } else { 39 | items = items.concat(forecast(loc)); 40 | } 41 | items.push({'title':'Locations'.localize() 42 | ,'icon':DEFAULT_ICON 43 | ,'action':'actionLocations' 44 | ,'actionReturnsItems':true}); 45 | return items; 46 | } 47 | 48 | function actionLocations(item) { 49 | var items = getLocations(); 50 | return items.concat(getSettings()); 51 | } 52 | -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Scripts/forecast.js: -------------------------------------------------------------------------------- 1 | 2 | var ALERT_ICON = '/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/AlertStopIcon.icns'; 3 | var imap = {}; 4 | imap['clear-day'] = 'Sun.png'; 5 | imap['clear-night'] = 'Moon.png'; 6 | imap['rain'] = 'Cloud-Rain.png'; 7 | imap['snow'] = 'Snowflake.png'; 8 | imap['sleet'] = 'Cloud-Hail-Alt.png'; 9 | imap['wind'] = 'Wind.png'; 10 | imap['fog'] = 'Cloud-Fog.png'; 11 | imap['cloudy'] = 'Cloud.png'; 12 | imap['partly-cloudy-day'] = 'Cloud-Sun.png'; 13 | imap['partly-cloudy-night'] = 'Cloud-Moon.png'; 14 | imap['hail'] = 'Cloud-Hail.png'; 15 | imap['thunderstorm'] = 'Umbrella.png'; 16 | imap['tornado'] = 'Tornado.png'; 17 | 18 | var wmap = {}; 19 | wmap['us'] = 'mph'; 20 | wmap['si'] = 'm/s'; 21 | wmap['ca'] = 'km/h'; 22 | wmap['uk'] = 'mph'; 23 | 24 | function getAPIKey() { 25 | if (Action.preferences.apiKey == undefined) { 26 | Action.preferences.apiKey = ''; 27 | } 28 | if (Action.preferences.apiKey.length == 0) { 29 | LaunchBar.alert('addKey'.localize()); 30 | LaunchBar.openURL('https://darksky.net/dev/'); 31 | var key = LaunchBar.executeAppleScript( 32 | 'return text returned of (display dialog "Dark Sky API Key:" default answer "" giving up after 120 with icon note)'); 33 | Action.preferences.apiKey = key && key != undefined ? key.trim() : ''; 34 | } 35 | return Action.preferences.apiKey; 36 | } 37 | 38 | function getIcon(i) { 39 | var icon = imap[i]; 40 | if (!icon || icon == undefined || icon.length == 0) { 41 | LaunchBar.log('Need weather icon map for ' + i); 42 | return 'NotFound.icns'; 43 | } 44 | return icon; 45 | } 46 | 47 | function getLocalTime(offset, t) { 48 | return moment.unix(t).zone(offset).format('HH:mm'); 49 | } 50 | 51 | var sparks = '▁▂▃▄▅▆▇█' 52 | function getSparkline(values) { 53 | } 54 | 55 | function getPrecipLevel(much) { 56 | if (!much || much == undefined || much == 0.0) 57 | return ''; 58 | if (much >= 0.4) 59 | return 'heavy'; 60 | if (much >= 0.1) 61 | return 'moderate'; 62 | if (much >= 0.017) 63 | return 'light'; 64 | return 'very light'; 65 | } 66 | 67 | function getTemps(real, app) { 68 | var d = Math.abs(real - app); 69 | if (d > 5) 70 | return Math.round(real) + '°/' + Math.round(app) + '°'; 71 | return Math.round(real) + '°'; 72 | } 73 | 74 | function forecast(loc) { 75 | var url = "unset"; 76 | var name = loc.name; 77 | var latitude = loc.latitude; 78 | var longitude = loc.longitude; 79 | try { 80 | var items = []; 81 | var apiKey = getAPIKey(); 82 | if (apiKey.length == 0) 83 | return; 84 | 85 | url = 'https://api.darksky.net/forecast/' + apiKey + '/' + latitude + ',' + longitude 86 | + '?units=' + Action.preferences.units 87 | + '&lang=' + Action.preferences.lang; 88 | LaunchBar.log('Forecast URL "' + url +'"'); 89 | 90 | var furl = 'https://darksky.net/' + latitude + ',' + longitude; 91 | var result = HTTP.getJSON(url, TIMEOUT); 92 | if (result && result.data && result.data.error) 93 | items.push({'title':'Forecast Error: ' + result.data.error 94 | ,'subtitle':url 95 | ,'icon':ALERT_ICON 96 | ,'url':url}); 97 | var units = ''; 98 | if (result && result.data && result.data.flags && result.data.flags.units) 99 | units = result.data.flags.units; 100 | 101 | if (result && result.data && result.data.timezone ) { 102 | if (result.data.alerts) { 103 | for (var i = 0; i < result.data.alerts.length; i++) { 104 | var a = result.data.alerts[i]; 105 | items.push({ 106 | 'title':a.title 107 | ,'subtitle': 'Expires ' + moment.unix(a.expires).tz(result.data.timezone).calendar() 108 | ,'icon':ALERT_ICON 109 | ,'url':a.uri 110 | ,'text':a.description 111 | }); 112 | } 113 | } 114 | 115 | var hourDetails = []; 116 | if (result.data.hourly) { 117 | for (var i = 1; i < result.data.hourly.data.length; i++) { 118 | if (i >= 12) 119 | break; 120 | var d = result.data.hourly.data[i]; 121 | hourDetails.push({ 122 | 'title':moment.unix(d.time).tz(result.data.timezone).format('h a') 123 | + (d.precipProbability>0?' ' + Math.round(d.precipProbability*100)+'%':'') 124 | + ' ' + d.summary 125 | + ' ' + getTemps(d.temperature,d.apparentTemperature) 126 | ,'icon':getIcon(d.icon)}); 127 | } 128 | } else { 129 | LaunchBar.log('Hourly forecast not available'); 130 | } 131 | 132 | var todayDetails = []; 133 | var todaySummary = ''; 134 | var week = []; 135 | if (result.data.daily) { 136 | var t = result.data.daily.data[0]; 137 | todayDetails = dayDetail(result.data.timezone,units,t); 138 | todaySummary = ', ' + t.summary.substring(0, t.summary.length-1) 139 | + ' ' + getTemps(t.temperatureMax,t.apparentTemperatureMax) ; 140 | for (var i=1; i < result.data.daily.data.length; i++) { 141 | var d = result.data.daily.data[i]; 142 | week.push({ 143 | 'title':moment.unix(d.time).tz(result.data.timezone).format('ddd') + ' ' 144 | + d.summary.substring(0, d.summary.length-1) + ' ' 145 | + getTemps(d.temperatureMax,d.apparentTemperatureMax) 146 | ,'icon':getIcon(d.icon) 147 | ,'url':furl 148 | ,'children':dayDetail(result.data.timezone,units,d) 149 | }); 150 | } 151 | } else { 152 | LaunchBar.log('Daily forecast not available'); 153 | } 154 | 155 | var nowTitle = null; 156 | var nowIcon = null; 157 | var nowTemp = null; 158 | var nowTime = new Date().getTime()/1000; 159 | if (result.data.currently) { 160 | var currently = result.data.currently; 161 | nowTitle = currently.summary; 162 | nowIcon = currently.icon; 163 | nowTemp = getTemps(currently.temperature, currently.apparentTemperature); 164 | nowTime = currently.time; 165 | } else { 166 | LaunchBar.log('Current forecast not available'); 167 | } 168 | if (result.data.minutely) { 169 | var minutely = result.data.minutely; 170 | nowTitle = minutely.summary.substring(0, minutely.summary.length-1); 171 | nowIcon = minutely.icon; 172 | } else { 173 | LaunchBar.log('Next hour forecast not available'); 174 | } 175 | var nowDetails = todayDetails.concat(hourDetails); 176 | items.push({ 177 | 'title':nowTitle + ' ' + nowTemp + todaySummary 178 | ,'icon':getIcon(nowIcon) 179 | ,'url':furl 180 | ,'children':nowDetails 181 | }); 182 | items.push({ 183 | 'title':name + ' ' + moment().tz(result.data.timezone).format('h:mm a') 184 | ,'icon':loc.icon 185 | ,'url':url 186 | }); 187 | items = items.concat(week); 188 | } 189 | if (items.length == 0) 190 | items.push({'title':'Forecast not available','icon':'NotFound.icns','url':url}); 191 | if (isDebug()) { 192 | items.push({'title':'Dark Sky API call','url':url,'icon':'forecastio.png'}); 193 | } 194 | return items; 195 | } catch (exception) { 196 | LaunchBar.log('Error forecast from "' + url + '" ' + exception); 197 | LaunchBar.alert('Error getting forecast from "' + url + '"', exception); 198 | } 199 | } 200 | 201 | function dayDetail(tz, units, d) { 202 | var details = []; 203 | details.push({'title': 'Low ' + getTemps(d.temperatureMin,d.apparentTemperatureMin) 204 | + ' at ' + moment.unix(d.temperatureMinTime).tz(tz).format('h a') 205 | ,'icon':'Thermometer-25.png'}); 206 | details.push({'title':'Sunrise ' + moment.unix(d.sunriseTime).tz(tz).format('h:mm a') 207 | ,'icon':'Sunrise.png'}); 208 | if (d.precipProbability > 0.0) { 209 | details.push({'title': Math.round(d.precipProbability * 100) + '% chance ' 210 | + getPrecipLevel(d.precipIntensityMax) + ' ' + d.precipType 211 | ,'icon':getIcon(d.precipType)}); 212 | } 213 | if (d.windSpeed > 0) { 214 | details.push({'title':'Wind ' + Math.round(d.windSpeed) + ' ' + wmap[units] 215 | ,'icon':'Wind.png'}); 216 | } 217 | details.push({'title': 'High ' + getTemps(d.temperatureMax,d.apparentTemperatureMax) 218 | + ' at ' + moment.unix(d.temperatureMaxTime).tz(tz).format('h a') 219 | ,'icon':'Thermometer-75.png'}); 220 | details.push({'title':'Sunset ' + moment.unix(d.sunsetTime).tz(tz).format('h:mm a') 221 | ,'icon':'Sunset.png'}); 222 | return details; 223 | } 224 | -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Scripts/location.js: -------------------------------------------------------------------------------- 1 | var EXISTS_FILTER = function(x){return (x && x!== undefined && x !== false)}; 2 | var LOC_FILE = Action.supportPath + '/locations.json'; 3 | var HOME_ICON = '/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/HomeFolderIcon.icns'; 4 | var DEFAULT_ICON = 'ABLocation.icns'; 5 | var FOLLOW_NBR = 9999; 6 | var FOLLOW_ICON = '/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/MagnifyingGlassIcon.icns'; 7 | var FOLLOW_NAME = 'Follow-Me'; 8 | var FOLLOW_SUB = 'Dynamic location that follows your current whereabouts'; 9 | var PLANE_ICON = 'airplane.png'; 10 | 11 | function getLocations() { 12 | var kids = []; 13 | var locs = readLocations(); 14 | var followMe = false; 15 | for (var i = 0; i < locs.length; i++) { 16 | var loc = locs[i]; 17 | var admin = []; 18 | if (loc.latitude == FOLLOW_NBR) 19 | followMe = true; 20 | admin.push({'title':'Forecast' 21 | ,'name':loc.name,'latitude':loc.latitude,'longitude':loc.longitude,'ico':loc.icon 22 | ,'icon':'Sun-Low.png' 23 | ,'actionReturnsItems':true 24 | ,'action':'actionForecast'}); 25 | admin.push({'title':'Set as Default Location' 26 | ,'name':loc.name,'latitude':loc.latitude,'longitude':loc.longitude,'ico':loc.icon 27 | ,'icon':loc.icon 28 | ,'actionRunsInBackground':true 29 | ,'action':'actionSelect'}); 30 | admin.push({'title':'Rename ' + loc.name 31 | ,'name':loc.name,'latitude':loc.latitude,'longitude':loc.longitude,'ico':loc.icon 32 | ,'icon':'Text.icns' 33 | ,'actionRunsInBackground':true 34 | ,'action':'actionRename'}); 35 | admin.push({'title':'Change Icon' 36 | ,'name':loc.name,'latitude':loc.latitude,'longitude':loc.longitude,'ico':loc.icon 37 | ,'icon':'Text.icns' 38 | ,'actionRunsInBackground':true 39 | ,'action':'actionIcon'}); 40 | admin.push({'title':'Set Home Icon' 41 | ,'name':loc.name,'latitude':loc.latitude,'longitude':loc.longitude,'ico':loc.icon 42 | ,'icon':HOME_ICON 43 | ,'action':'actionHome'}); 44 | admin.push({'title':'Set Airplane Icon' 45 | ,'name':loc.name,'latitude':loc.latitude,'longitude':loc.longitude,'ico':loc.icon 46 | ,'icon':PLANE_ICON 47 | ,'action':'actionPlane'}); 48 | admin.push({'title':'Remove ' + loc.name 49 | ,'name':loc.name,'latitude':loc.latitude,'longitude':loc.longitude,'ico':loc.icon 50 | ,'actionRunsInBackground':true 51 | ,'icon':'/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/TrashIcon.icns' 52 | ,'action':'actionRemove'}); 53 | kids.push({'title':loc.name 54 | ,'name':loc.name,'latitude':loc.latitude,'longitude':loc.longitude,'ico':loc.icon 55 | ,'subtitle':(loc.latitude == FOLLOW_NBR?FOLLOW_SUB:'Latitude ' + loc.latitude + ' Longitude:' + loc.longitude) 56 | ,'actionRunsInBackground':true 57 | ,'icon':loc.icon 58 | ,'action':'actionSelect' 59 | ,'children':admin}); 60 | } 61 | 62 | if (!followMe) { 63 | kids.push({'title':'Add Follow-Me Current Location' 64 | ,'subtitle':FOLLOW_SUB 65 | ,'name':FOLLOW_NAME,'latitude':FOLLOW_NBR,'longitude':FOLLOW_NBR,'ico':FOLLOW_ICON 66 | ,'icon':FOLLOW_ICON 67 | ,'actionRunsInBackground':true 68 | ,'action':'actionSelect'}); 69 | } 70 | var curr = getCurrentLocation(); 71 | if (curr == null) { 72 | kids.push({'title':'Current Location not available','icon':'NotFound.icns'}); 73 | } else { 74 | var f = []; 75 | f.push({'title':'Forecast' 76 | ,'name':curr.name,'latitude':curr.latitude,'longitude':curr.longitude,'ico':curr.icon 77 | ,'icon':'Sun-Low.png' 78 | ,'actionReturnsItems':true 79 | ,'action':'actionForecast'}); 80 | 81 | kids.push({'title':'Add ' + curr.name + ' Location' 82 | ,'name':curr.name,'latitude':curr.latitude,'longitude':curr.longitude,'ico':curr.icon 83 | ,'icon':DEFAULT_ICON 84 | ,'actionRunsInBackground':true 85 | ,'children':f 86 | ,'action':'actionSelect'}); 87 | } 88 | 89 | if (isDebug()) { 90 | kids.push({'title':'Edit locations.json' 91 | ,'actionRunsInBackground':true 92 | ,'path':LOC_FILE 93 | ,'action':'actionJSON'}); 94 | } 95 | return kids; 96 | } 97 | 98 | 99 | function selectedLoc() { 100 | var locs = readLocations(); 101 | if (locs && locs != undefined && locs.length > 0) { 102 | var loc = locs[0]; 103 | if (loc.latitude == FOLLOW_NBR) { 104 | var curr = getCurrentLocation(); 105 | if (curr == null) 106 | return null; 107 | curr.icon = loc.icon; 108 | return curr; 109 | } else { 110 | return loc; 111 | } 112 | } 113 | } 114 | 115 | function actionSelect(item) { 116 | locationAdd(item.name, item.latitude, item.longitude, item.ico); 117 | } 118 | 119 | function actionForecast(item) { 120 | return forecast({'name':item.name, 'latitude':item.latitude, 'longitude':item.longitude, 'icon':item.ico}); 121 | } 122 | 123 | function actionRename(item) { 124 | var n = LaunchBar.executeAppleScript( 125 | 'return text returned of (display dialog "Name:" default answer "' + item.name + '" giving up after 15 with icon note)'); 126 | if (n && n.length > 0) { 127 | var locs = readLocations(); 128 | for (var i=0; i < locs.length; i++) { 129 | var loc = locs[i]; 130 | if (loc.latitude == item.latitude && loc.longitude == item.longitude) { 131 | loc.name = n.trim(); 132 | writeLocations(locs); 133 | break; 134 | } 135 | } 136 | } 137 | } 138 | 139 | function actionIcon(item) { 140 | var ico = LaunchBar.executeAppleScript( 141 | 'return text returned of (display dialog "Icon:" default answer "' + item.icon + '" giving up after 15 with icon note)'); 142 | if (ico && ico.length > 0) { 143 | var locs = readLocations(); 144 | for (var i=0; i < locs.length; i++) { 145 | var loc = locs[i]; 146 | if (loc.latitude == item.latitude && loc.longitude == item.longitude) { 147 | loc.icon = ico.trim(); 148 | writeLocations(locs); 149 | break; 150 | } 151 | } 152 | } 153 | } 154 | 155 | function actionHome(item) { 156 | var locs = readLocations(); 157 | for (var i=0; i < locs.length; i++) { 158 | var loc = locs[i]; 159 | if (loc.latitude == item.latitude && loc.longitude == item.longitude) { 160 | loc.icon = HOME_ICON; 161 | writeLocations(locs); 162 | break; 163 | } 164 | } 165 | } 166 | 167 | function actionPlane(item) { 168 | var locs = readLocations(); 169 | for (var i=0; i < locs.length; i++) { 170 | var loc = locs[i]; 171 | if (loc.latitude == item.latitude && loc.longitude == item.longitude) { 172 | loc.icon = PLANE_ICON; 173 | writeLocations(locs); 174 | break; 175 | } 176 | } 177 | } 178 | 179 | function actionRemove(item) { 180 | var locs = readLocations(); 181 | for (var i=0; i < locs.length; i++) { 182 | var loc = locs[i]; 183 | if (loc.latitude == item.latitude && loc.longitude == item.longitude) { 184 | locs[i] = false; 185 | writeLocations(locs); 186 | break; 187 | } 188 | } 189 | } 190 | 191 | function actionJSON(item) { 192 | LaunchBar.openURL('file:/' + encodeURIComponent(LOC_FILE), 'TextEdit'); 193 | } 194 | 195 | // add a new location to the top (selected) position 196 | function locationAdd(name, latitude, longitude, icon) { 197 | var locs = []; 198 | locs.push({'name':name,'latitude':latitude,'longitude':longitude 199 | ,'icon':(icon && icon != undefined && icon.length > 0?icon:DEFAULT_ICON)}); 200 | locs = locs.concat(readLocations()); 201 | writeLocations(locs); 202 | } 203 | 204 | function readLocations() { 205 | // locations file is a json Array, of object containing name,latitude,longitude 206 | if (File.exists(LOC_FILE)) { 207 | try { 208 | return File.readJSON(LOC_FILE); 209 | } catch (exception) { 210 | LaunchBar.log('Error readLocations ' + exception); 211 | LaunchBar.alert('Error readLocations', exception); 212 | } 213 | } else { 214 | writeLocations([]); 215 | } 216 | return []; 217 | } 218 | 219 | function writeLocations(locations) { 220 | var locs = locations.filter(EXISTS_FILTER); 221 | // remove duplicates 222 | for (var i=0; i < locs.length; i++) { 223 | var loc = locs[i]; 224 | for (var j=i+1; j < locs.length; j++) { 225 | var other = locs[j]; 226 | if (other !== false && loc.latitude == other.latitude && loc.longitude == other.longitude) { 227 | locs[j] = false; 228 | } 229 | } 230 | } 231 | try { 232 | File.writeJSON(locs.filter(EXISTS_FILTER), LOC_FILE); 233 | } catch (exception) { 234 | LaunchBar.log('Error writeLocations ' + exception); 235 | LaunchBar.alert('Error writeLocations', exception); 236 | } 237 | return loc; 238 | } 239 | -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Scripts/search.js: -------------------------------------------------------------------------------- 1 | var GEOLOCATE = "http://ip-api.com/json/"; 2 | 3 | function getCurrentLocation() { 4 | try { 5 | var result = HTTP.getJSON(GEOLOCATE, TIMEOUT); 6 | if (result && result.data 7 | && result.data.city && result.data.regionName && result.data.country) { 8 | var place = result.data.city 9 | + ', ' + result.data.regionName 10 | + ', ' + result.data.country; 11 | return {'name': place 12 | ,'latitude': result.data.lat 13 | ,'longitude': result.data.lon 14 | ,'icon':DEFAULT_ICON}; 15 | } 16 | } catch (exception) { 17 | LaunchBar.log('Error getCurrentLocation ' + exception); 18 | LaunchBar.alert('Error getCurrentLocation', exception); 19 | } 20 | return null; 21 | } 22 | 23 | function getNameForGeo(latitude, longitude) { 24 | var url = 'https://nominatim.openstreetmap.org/reverse?format=json&zoom=12&addressdetails=1&lat=' 25 | + latitude + '&lon=' + longitude; 26 | try { 27 | var result = HTTP.getJSON(url, TIMEOUT); 28 | if (result && result.data && result.data.address ) { 29 | if (result.data.address.village) 30 | return result.data.address.village + ', ' + result.data.address.state; 31 | if (result.data.address.neighbourhood) 32 | return result.data.address.neighbourhood + ', ' + result.data.address.state; 33 | if (result.data.address.city) 34 | return result.data.address.city + ', ' + result.data.address.state; 35 | } 36 | if (result && result.data && result.data.display_name) 37 | return result.data.display_name; 38 | LaunchBar.log('Cannot find name for geo ' + latitude + ' , ' + longitude 39 | + ' ' + url + ' ' + JSON.stringify(result)); 40 | } catch (exception) { 41 | LaunchBar.log('Error getNameForGeo ' + exception); 42 | LaunchBar.alert('Error getNameForGeo', exception); 43 | } 44 | return 'Name not available'; 45 | } 46 | 47 | function locationSearch(query) { 48 | var url = 'http://nominatim.openstreetmap.org/search?format=json&addressdetails=1&limit=50' 49 | + '&countrycodes=' + encodeURIComponent(Action.preferences.country) 50 | + '&q=' + encodeURIComponent(query); 51 | try { 52 | var items = []; 53 | var result = HTTP.getJSON(url, TIMEOUT); 54 | if (result && result.data) { 55 | for (var i = 0; i < result.data.length; i++) { 56 | var r = result.data[i]; 57 | var n = r.display_name; 58 | if (n.length > 50) 59 | n = r.display_name.substring(0,50); 60 | items.push({'title':n 61 | ,'subtitle':r.display_name 62 | ,'name':n 63 | ,'latitude':r.lat 64 | ,'longitude':r.lon 65 | ,'ico':DEFAULT_ICON 66 | ,'icon':DEFAULT_ICON 67 | ,'actionReturnsItems':true 68 | ,'action':'actionSelectAndForecast' 69 | }); 70 | } 71 | } 72 | } catch (exception) { 73 | LaunchBar.log('Error locationSearch ' + exception); 74 | LaunchBar.alert('Error locationSearch', exception); 75 | } 76 | if (items.length == 0) { 77 | items.push({'title':'No locations found','icon':'NotFound.icns'}); 78 | } 79 | if (isDebug()) { 80 | items.push({'title':'Search API call','url':url}); 81 | } 82 | return items; 83 | } 84 | 85 | function actionSelectAndForecast(item) { 86 | actionSelect(item); 87 | return actionForecast(item); 88 | } -------------------------------------------------------------------------------- /Forecast.lbaction/Contents/Scripts/settings.js: -------------------------------------------------------------------------------- 1 | var PREF_FILE = Action.supportPath + '/Preferences.plist'; 2 | var TIMEOUT = 10.0; 3 | 4 | var langs = {}; 5 | langs['en'] = 'English'; 6 | langs['de'] = 'German'; 7 | langs['es'] = 'Spanish'; 8 | langs['fr'] = 'French'; 9 | langs['nl'] = 'Dutch'; 10 | langs['it'] = 'Italian'; 11 | langs['tet'] = 'Tetum'; 12 | langs['bs'] = 'Bosnian'; 13 | langs['pl'] = 'Polish'; 14 | langs['pt'] = 'Portuguese'; 15 | langs['ru'] = 'Russian'; 16 | 17 | if (Action.preferences.units == undefined) { 18 | Action.preferences.units = 'auto'; 19 | } 20 | 21 | if (Action.preferences.lang == undefined) { 22 | Action.preferences.lang = 'en'; 23 | var l = langs[LaunchBar.currentLocale]; 24 | if (l && l != undefined && l.length > 0) 25 | Action.preferences.lang = LaunchBar.currentLocale; 26 | } 27 | 28 | if (Action.preferences.country == undefined) { 29 | Action.preferences.country = 'US'; 30 | } 31 | 32 | if (Action.preferences.debug == undefined) { 33 | Action.preferences.debug = false; 34 | } 35 | 36 | function getSettings() { 37 | return {'title':'Settings' 38 | ,'icon':'com.apple.systempreferences' 39 | ,'action':'actionSettings' 40 | ,'actionReturnsItems':true}; 41 | } 42 | 43 | function actionSettings() { 44 | var items = []; 45 | 46 | var units = []; 47 | units.push({'title':'United States','units':'us' 48 | ,'icon':'us.icns','action':'actionUnits'}); 49 | units.push({'title':'SI, Standard International, Metric','units':'si' 50 | ,'icon':'si.png','action':'actionUnits'}); 51 | units.push({'title':'Canada','units':'ca' 52 | ,'subtitle':'Like SI but windSpeed is in km/h' 53 | ,'icon':'canada.icns','action':'actionUnits'}); 54 | units.push({'title':'United Kingdom','units':'uk' 55 | ,'subtitle':'Like SI but windSpeed is in miles per hour' 56 | ,'icon':'uk.icns','action':'actionUnits'}); 57 | units.push({'title':'Automatic based on location','units':'auto' 58 | ,'icon':FOLLOW_ICON,'action':'actionUnits'}); 59 | items.push({'title':'Forecast Units - ' + Action.preferences.units.toUpperCase() 60 | ,'subtitle':'Set units for Dark Sky data, see https://darksky.net/dev/docs' 61 | ,'url':'https://darksky.net/dev/docs' 62 | ,'icon':'Thermometer-25.png' 63 | ,'children':units}); 64 | 65 | var langs = []; 66 | langs.push({'title':'English','lang':'en' 67 | ,'icon':'us.icns','action':'actionLang'}); 68 | langs.push({'title':'German','lang':'de' 69 | ,'icon':'germany.icns','action':'actionLang'}); 70 | langs.push({'title':'French','lang':'fr' 71 | ,'icon':'france.icns','action':'actionLang'}); 72 | langs.push({'title':'Spanish','lang':'sp' 73 | ,'icon':'spain.icns','action':'actionLang'}); 74 | langs.push({'title':'Dutch','lang':'nl' 75 | ,'icon':'netherlands.icns','action':'actionLang'}); 76 | langs.push({'title':'Italian','lang':'it' 77 | ,'icon':'italy.icns','action':'actionLang'}); 78 | langs.push({'title':'Tetum','lang':'tet' 79 | ,'icon':'indonesia.icns','action':'actionLang'}); 80 | langs.push({'title':'Bosnian','lang':'bs' 81 | ,'icon':'bosnia.icns','action':'actionLang'}); 82 | langs.push({'title':'Polish','lang':'pl' 83 | ,'icon':'poland.icns','action':'actionLang'}); 84 | langs.push({'title':'Portuguese','lang':'pt' 85 | ,'icon':'portugal.icns','action':'actionLang'}); 86 | langs.push({'title':'Russian','lang':'ru' 87 | ,'icon':'russia.icns','action':'actionLang'}); 88 | 89 | langs.push({'title':'Other' 90 | ,'url':'https://github.com/darkskyapp/forecast-io-translations' 91 | ,'icon':'forecastio.png'}); 92 | items.push({'title':'Forecast Language - ' + Action.preferences.lang.toUpperCase() 93 | ,'subtitle':'Set language for Dark Sky data, see https://darksky.net/dev/docs' 94 | ,'url':'https://darksky.net/dev/docs' 95 | ,'icon':'Thermometer-25.png' 96 | ,'children':langs}); 97 | 98 | var cty = []; 99 | cty.push({'title':'United States','country':'US' 100 | ,'icon':'us.icns','action':'actionCountry'}); 101 | cty.push({'title':'Canada','country':'CA' 102 | ,'icon':'canada.icns','action':'actionCountry'}); 103 | cty.push({'title':'Germany','country':'DE' 104 | ,'icon':'germany.icns','action':'actionCountry'}); 105 | cty.push({'title':'France','country':'FR' 106 | ,'icon':'france.icns','action':'actionCountry'}); 107 | cty.push({'title':'Spain','country':'ES' 108 | ,'icon':'spain.icns','action':'actionCountry'}); 109 | cty.push({'title':'Other Country' 110 | ,'actionRunsInBackground':true 111 | ,'icon':'Text.icns','action':'actionCountryOther'}); 112 | items.push({'title':'Location Search Country - ' + Action.preferences.country 113 | ,'subtitle':'Set ISO country(ies , separated) for location search, see https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2' 114 | ,'url':'https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2' 115 | ,'icon':FOLLOW_ICON 116 | ,'children':cty}); 117 | 118 | items.push({'title':'Dark Sky API Key' 119 | ,'subtitle':'https://darksky.net/dev/' 120 | ,'url':'https://darksky.net/dev/' 121 | ,'actionRunsInBackground':true 122 | ,'icon':'forecastio.png','action':'actionKey'}); 123 | 124 | items.push({'title':'Debug Mode - ' + Action.preferences.debug 125 | ,'subtitle':'Toggle debug mode, adds items that link to API calls' 126 | ,'icon':'com.apple.systempreferences','action':'actionDebug'}); 127 | 128 | var body = 'Forecast version: ' + Action.version 129 | + '\nLaunchBar version: ' + LaunchBar.shortVersion + ' (' + LaunchBar.version + ')' 130 | + '\nLocale: ' + LaunchBar.currentLocale 131 | + '\nUnits: ' + Action.preferences.units 132 | + '\nLanguage: ' + Action.preferences.lang 133 | + '\nCountry: ' + Action.preferences.country 134 | + '\n\n...\n'; 135 | 136 | items.push({'title':'Send Forecast Feedback' 137 | ,'subtitle':'Comments, Suggestions, Bug Reports always welcome' 138 | ,'icon':'com.apple.Mail', 139 | 'url':'mailto:prenagha@renaghan.com?subject=Forecast%20Feedback&body=' 140 | + encodeURIComponent(body)}); 141 | 142 | if (isDebug()) { 143 | items.push({'title':'Edit preferences file'.localize() 144 | ,'actionRunsInBackground':true 145 | ,'path':PREF_FILE 146 | ,'icon':'com.apple.systempreferences' 147 | ,'action':'actionPrefs'}); 148 | } 149 | return items; 150 | } 151 | 152 | function actionUnits(item) { 153 | Action.preferences.units = item.units; 154 | } 155 | 156 | function actionLang(item) { 157 | Action.preferences.lang = item.lang; 158 | } 159 | 160 | function actionCountry(item) { 161 | Action.preferences.country = item.country; 162 | } 163 | 164 | function actionDebug(item) { 165 | Action.preferences.debug = !Action.preferences.debug; 166 | } 167 | 168 | function actionCountryOther(item) { 169 | var c = LaunchBar.executeAppleScript( 170 | 'return text returned of (display dialog "Country Code:" default answer "' 171 | + Action.preferences.country + '" giving up after 15 with icon note)'); 172 | if (c && c.length > 0) { 173 | Action.preferences.country = c.trim(); 174 | } 175 | } 176 | 177 | function actionKey(item) { 178 | var k = LaunchBar.executeAppleScript( 179 | 'return text returned of (display dialog "Dark Sky API Key:" default answer "' 180 | + Action.preferences.apiKey + '" giving up after 15 with icon note)'); 181 | if (k && k.length > 0) { 182 | Action.preferences.apiKey = k.trim(); 183 | } 184 | } 185 | 186 | function actionPrefs(item) { 187 | LaunchBar.openURL('file:/' + encodeURIComponent(PREF_FILE), 'TextEdit'); 188 | } 189 | 190 | -------------------------------------------------------------------------------- /Generate Password.lbaction/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.renaghan.launchbar.GeneratePassword 7 | CFBundleName 8 | Generate Password 9 | CFBundleVersion 10 | 6.2 11 | CFBundleIconFile 12 | font-awesome:fa-lock 13 | LBDebugLogEnabled 14 | 15 | LBScripts 16 | 17 | LBDefaultScript 18 | 19 | LBScriptName 20 | generate.js 21 | LBRunInBackground 22 | 23 | LBRequiresArgument 24 | 25 | LBReturnsResult 26 | 27 | 28 | 29 | LBDescription 30 | 31 | LBSummary 32 | Generate passwords, return results, and copy first to the clipboard. Uses https://github.com/bbusschots/hsxkpasswd for word passwords. 33 | LBAuthor 34 | Padraic Renaghan 35 | LBEmail 36 | prenagha@renaghan.com 37 | LBWebsiteURL 38 | https://renaghan.com/launchbar/generate-password/ 39 | LBTwitter 40 | @prenagha 41 | LBUpdateURL 42 | https://raw.githubusercontent.com/prenagha/launchbar/master/Generate%20Password.lbaction/Contents/Info.plist 43 | LBDownloadURL 44 | https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/prenagha/launchbar/tree/main/Generate%20Password.lbaction&fileName=Generate-Password&rootDirectory=Generate-Password.lbaction 45 | LBChangelog 46 | 47 | 6.2: Fix download URL 48 | 6.0: Returns multiple results, defaults to hsxkpasswd word based password 49 | 5.0: Rewritten in Javascript. Now supports preferences. 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /Generate Password.lbaction/Contents/Scripts/generate.js: -------------------------------------------------------------------------------- 1 | var ALERT_ICON = 'font-awesome:fa-exclamation-triangle'; 2 | var WORD_ICON = 'font-awesome:fa-lock'; 3 | var RANDOM_ICON = 'font-awesome:fa-random'; 4 | var TYPES = "LUNS"; 5 | 6 | function setup() { 7 | // setup default preferences if missing 8 | // standard characters WITHOUT easy to mistake 0Oo, iI1lL 9 | // set a *Min value to -1 to mean NONE of those characters 10 | if (!Action.preferences.lowercase) 11 | Action.preferences.lowercase = 'abcdefghjkmnpqrstuvwxyz'; 12 | if (!Action.preferences.uppercase) 13 | Action.preferences.uppercase = 'ABCDEFGHJKMNPQRSTUVWXYZ'; 14 | if (!Action.preferences.numbers) 15 | Action.preferences.numbers = '23456789'; 16 | if (!Action.preferences.symbols) 17 | Action.preferences.symbols = '@#$%!.=+-_'; 18 | if (!Action.preferences.minLength) 19 | Action.preferences.minLength = 15; 20 | if (!Action.preferences.lowercaseMin) 21 | Action.preferences.lowercaseMin = 1; 22 | if (!Action.preferences.uppercaseMin) 23 | Action.preferences.uppercaseMin = 1; 24 | if (!Action.preferences.numberMin) 25 | Action.preferences.numberMin = 1; 26 | if (!Action.preferences.symbolMin) 27 | Action.preferences.symbolMin = 1; 28 | if (!Action.preferences.hsxkpasswdPath) 29 | Action.preferences.hsxkpasswdPath="/usr/local/bin/hsxkpasswd"; 30 | if (!Action.preferences.hsxkpasswdConfig) 31 | Action.preferences.hsxkpasswdConfig="hsxkpasswd-config.json"; 32 | } 33 | 34 | // return a random from the possible values 35 | function rand(possible) { 36 | return possible.charAt(Math.floor(Math.random() * possible.length)) 37 | } 38 | 39 | function randomPass() { 40 | var lowercaseMin = 0; 41 | var uppercaseMin = 0; 42 | var numberMin = 0; 43 | var symbolMin = 0; 44 | 45 | var type = ""; 46 | var pwd = ""; 47 | // don't start or end on a symbol 48 | while (pwd.length < Action.preferences.minLength 49 | || lowercaseMin < Action.preferences.lowercaseMin 50 | || uppercaseMin < Action.preferences.uppercaseMin 51 | || numberMin < Action.preferences.numberMin 52 | || symbolMin < Action.preferences.symbolMin 53 | || type === "S") { 54 | type = rand(TYPES); 55 | if ("L" === type && Action.preferences.lowercaseMin >= 0) { 56 | pwd += rand(Action.preferences.lowercase); 57 | lowercaseMin++; 58 | } else if ("U" === type && Action.preferences.uppercaseMin >= 0) { 59 | pwd += rand(Action.preferences.uppercase); 60 | uppercaseMin++; 61 | } else if ("N" === type && Action.preferences.numberMin >= 0) { 62 | pwd += rand(Action.preferences.numbers); 63 | numberMin++; 64 | } else if ("S" === type && Action.preferences.symbolMin >= 0 && pwd.length > 0) { 65 | pwd += rand(Action.preferences.symbols); 66 | symbolMin++; 67 | } 68 | } 69 | return pwd; 70 | } 71 | 72 | function run() { 73 | setup(); 74 | var output = []; 75 | var firstPass = ""; 76 | 77 | if (Action.preferences.hsxkpasswdPath.length > 0) { 78 | if (File.isExecutable(Action.preferences.hsxkpasswdPath)) { 79 | var pwds = LaunchBar.execute( 80 | Action.preferences.hsxkpasswdPath 81 | , '--config-file' 82 | , Action.preferences.hsxkpasswdConfig 83 | , '--warn' 84 | , 'NONE' 85 | , '4'); 86 | pwds = pwds.split('\n'); 87 | for (i=0; i 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.renaghan.launchbar.Incognito 7 | CFBundleName 8 | Incognito Browser 9 | LBAbbreviation 10 | incognito private chrome www safari 11 | CFBundleVersion 12 | 3.2 13 | CFBundleIconFile 14 | font-awesome:user-secret 15 | LBARequiredApplication 16 | com.google.Chrome 17 | LBTextInputTitle 18 | URL 19 | LBDebugLogEnabled 20 | 21 | LBMinimumLaunchBarVersion 22 | 6121 23 | LBScripts 24 | 25 | LBDefaultScript 26 | 27 | LBScriptName 28 | incognito.scpt 29 | LBKeepWindowActive 30 | 31 | LBRunInBackground 32 | 33 | LBRequiresArgument 34 | 35 | LBAcceptedArgumentTypes 36 | 37 | string 38 | 39 | LBReturnsResult 40 | 41 | 42 | 43 | LBDescription 44 | 45 | LBSummary 46 | LaunchBar action that opens a Chromium browser window/tab in incognito private browsing mode. Opens to URL from active Safari tab, URL from clipboard, DuckDuckGo Search URL with clipboard term, or blank. If more than one option is available will show LaunchBar items for you to select. 47 | LBRequirements 48 | Chromium Browser https://github.com/Eloston/ungoogled-chromium 49 | LBAuthor 50 | Padraic Renaghan 51 | LBEmail 52 | prenagha@renaghan.com 53 | LBWebsiteURL 54 | https://renaghan.com/launchbar/incognito-browser/ 55 | LBTwitter 56 | @prenagha 57 | LBArgument 58 | URL 59 | LBUpdateURL 60 | https://raw.githubusercontent.com/prenagha/launchbar/master/Incognito.lbaction/Contents/Info.plist 61 | LBDownloadURL 62 | https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/prenagha/launchbar/tree/main/Incognito.lbaction 63 | LBChangelog 64 | 65 | 3.2: Fix download URL 66 | 3.0: Change to Chromium browser 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /Incognito.lbaction/Contents/Resources/incognito.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Incognito.lbaction/Contents/Resources/incognito.png -------------------------------------------------------------------------------- /Incognito.lbaction/Contents/Scripts/incognito.applescript: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Incognito.lbaction/Contents/Scripts/incognito.applescript -------------------------------------------------------------------------------- /JIRA.lbaction/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.renaghan.launchbar.JIRA 7 | CFBundleName 8 | JIRA Search 9 | CFBundleVersion 10 | 2.1 11 | CFBundleIconFile 12 | jira.png 13 | LBDebugLogEnabled 14 | 15 | LBTextInputTitle 16 | Term 17 | LBScripts 18 | 19 | LBDefaultScript 20 | 21 | LBScriptName 22 | jira.js 23 | LBRequiresArgument 24 | 25 | LBAcceptedArgumentTypes 26 | 27 | string 28 | 29 | LBRunInBackground 30 | 31 | LBReturnsResult 32 | 33 | 34 | 35 | LBDescription 36 | 37 | LBSummary 38 | Open JIRA search 39 | LBAuthor 40 | Padraic Renaghan 41 | LBEmail 42 | prenagha@renaghan.com 43 | LBWebsiteURL 44 | https://renaghan.com/launchbar/jira/ 45 | LBTwitter 46 | @prenagha 47 | LBUpdateURL 48 | https://raw.githubusercontent.com/prenagha/launchbar/master/JIRA.lbaction/Contents/Info.plist 49 | LBDownloadURL 50 | https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/prenagha/launchbar/tree/main/JIRA.lbaction 51 | LBChangelog 52 | 53 | 2.1: Fix download URL 54 | 2.0: Configurable default project, query. Removed Crucible. Thanks @alloydwhitlock 55 | 1.0: First release 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /JIRA.lbaction/Contents/Resources/jira.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/JIRA.lbaction/Contents/Resources/jira.png -------------------------------------------------------------------------------- /JIRA.lbaction/Contents/Scripts/jira.js: -------------------------------------------------------------------------------- 1 | 2 | function run(arg) { 3 | if (!arg || arg === null || typeof(arg) != 'string' || arg.length === 0) 4 | return; 5 | 6 | arg = arg.trim(); 7 | 8 | // default jira project 9 | if (!Action.preferences.jiraDefaultProject) 10 | Action.preferences.jiraDefaultProject = ""; 11 | 12 | // base URL to jira server 13 | if (!Action.preferences.jiraBase) 14 | Action.preferences.jiraBase = "https://jira.example.com"; 15 | 16 | // text search query, replaced with the search term 17 | if (!Action.preferences.jiraQuery) 18 | Action.preferences.jiraQuery = "/issues/?jql=text%20~%20%22%22" 19 | +"%20AND%20status%20!%3D%20Closed%20ORDER%20BY%20key%20DESC"; 20 | 21 | var url = ""; 22 | // number assumed to be jira issue id in default project 23 | if (Action.preferences.jiraDefaultProject.length > 0 && arg.match(/^\d+$/)) { 24 | url = Action.preferences.jiraBase 25 | + "/browse/" 26 | + Action.preferences.jiraDefaultProject 27 | + "-" + arg; 28 | 29 | // XX-number assumed to be a fully qualified jira issue id 30 | } else if (arg.match(/^[A-Z]{2,}-\d+$/i)) { 31 | url = Action.preferences.jiraBase + "/browse/" + arg; 32 | 33 | // otherwise string assumed to be a search term 34 | } else { 35 | const term = encodeURIComponent(arg); 36 | url = Action.preferences.jiraBase 37 | + Action.preferences.jiraQuery.replaceAll('', term); 38 | } 39 | 40 | LaunchBar.debugLog("URL " + url); 41 | LaunchBar.openURL(url); 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /MailMate.applescript: -------------------------------------------------------------------------------- 1 | 2 | on dlog(myObj) 3 | set txt to quoted form of (myObj as string) 4 | log txt 5 | do shell script "logger -t 'LaunchBar.MailMate' " & txt 6 | end dlog 7 | 8 | on urlencode(theText) 9 | set theTextEnc to "" 10 | repeat with eachChar in characters of theText 11 | set useChar to eachChar 12 | set eachCharNum to ASCII number of eachChar 13 | if eachCharNum = 32 or eachCharNum > 127 then 14 | set useChar to "%20" 15 | else if (eachCharNum is not equal to 42) and (eachCharNum is not equal to 95) and (eachCharNum < 45 or eachCharNum > 46) and (eachCharNum < 48 or eachCharNum > 57) and (eachCharNum < 65 or eachCharNum > 90) and (eachCharNum < 97 or eachCharNum > 122) then 16 | set firstDig to round (eachCharNum / 16) rounding down 17 | set secondDig to eachCharNum mod 16 18 | if firstDig > 9 then 19 | set aNum to firstDig + 55 20 | set firstDig to ASCII character aNum 21 | end if 22 | if secondDig > 9 then 23 | set aNum to secondDig + 55 24 | set secondDig to ASCII character aNum 25 | end if 26 | set numHex to ("%" & (firstDig as string) & (secondDig as string)) as string 27 | set useChar to numHex 28 | end if 29 | set theTextEnc to theTextEnc & useChar as string 30 | end repeat 31 | return theTextEnc 32 | end urlencode 33 | 34 | on makeTo(_emailAddresses) 35 | set _to to "" 36 | repeat with addr in _emailAddresses 37 | set _to to _to & "&to=" & urlencode(addr) 38 | end repeat 39 | return _to 40 | end makeTo 41 | 42 | on basename(thePath) -- Requires POSIX path 43 | set ASTID to AppleScript's text item delimiters 44 | set AppleScript's text item delimiters to "/" 45 | set thePath to text item -1 of thePath 46 | set AppleScript's text item delimiters to ASTID 47 | return thePath 48 | end basename 49 | 50 | on sendFiles(_files, _emailAddresses) 51 | try 52 | set _mailto to "mailto:?send-now=no" & makeTo(_emailAddresses) 53 | set _names to "" 54 | repeat with _file in _files 55 | set _filePath to POSIX path of _file 56 | set _names to _names & " " & basename(_filePath) 57 | set _mailto to _mailto & "&attachment-url=file://" & urlencode(_filePath) 58 | end repeat 59 | set _mailto to _mailto & "&subject=File:" & urlencode(_names) & "&body=" & urlencode("File attached") 60 | tell application "MailMate" to open location _mailto with trust 61 | tell application "MailMate" to activate 62 | on error error_message number error_number 63 | set msg to "LaunchBar.MailMate.sendFiles ERROR: " & error_message & " #" & error_number 64 | dlog(msg) 65 | display dialog msg 66 | end try 67 | end sendFiles 68 | 69 | on sendText(txt, _emailAddresses) 70 | try 71 | if txt contains " http" then 72 | set myName to text 1 thru ((offset of " http" in txt) - 5) of txt 73 | set myURL to text ((offset of " http" in txt) + 1) thru end of txt 74 | set mySubj to "Link: " & myName 75 | set myBody to myName & return & myURL & return & return & "Enjoy" 76 | set _mailto to "mailto:?send-now=yes&subject=" & urlencode(mySubj) & makeTo(_emailAddresses) & "&body=" & urlencode(myBody) 77 | tell application "MailMate" to open location _mailto with trust 78 | else 79 | set _mailto to "mailto:?send-now=no&" & makeTo(_emailAddresses) & "&body=" & urlencode(txt) 80 | tell application "MailMate" to open location _mailto 81 | tell application "MailMate" to activate 82 | end if 83 | on error error_message number error_number 84 | set msg to "LaunchBar.MailMate.sendText ERROR: " & error_message & " #" & error_number 85 | dlog(msg) 86 | display dialog msg 87 | end try 88 | end sendText 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [LaunchBar Actions web site, documentation, screenshots](https://renaghan.com/launchbar/) 2 | 3 | ### NOTE on `.applescript` files 4 | 5 | Text `.applescript` Apple scripts are used here for ease of integration with version control systems, diff tools... 6 | 7 | The LaunchBar action `Info.plist` refers to a *COMPILED* `.scpt` version of the script. 8 | 9 | You can compile these text `.applescript` files into `.scpt` files using command line `/usr/bin/osacompile` or by exporting within Script Editor 10 | 11 | [My build script to compile `.applescript` to `.scpt`](https://gist.github.com/prenagha/404284fee1b8ff86aec5) 12 | -------------------------------------------------------------------------------- /Read-Later.lbaction/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.renaghan.launchbar.ReadLater 7 | CFBundleName 8 | Read Later 9 | CFBundleVersion 10 | 1.0 11 | CFBundleIconFile 12 | font-awesome:fa-link 13 | LBDebugLogEnabled 14 | 15 | LBScripts 16 | 17 | LBDefaultScript 18 | 19 | LBScriptName 20 | readlater.js 21 | LBRunInBackground 22 | 23 | LBRequiresArgument 24 | 25 | LBReturnsResult 26 | 27 | 28 | 29 | LBDescription 30 | 31 | LBSummary 32 | Manage read later links 33 | LBAuthor 34 | Padraic Renaghan 35 | LBEmail 36 | padraic@renaghan.com 37 | LBWebsiteURL 38 | https://renaghan.com/launchbar/read-later/ 39 | LBUpdateURL 40 | https://raw.githubusercontent.com/prenagha/launchbar/master/ReadLater.lbaction/Contents/Info.plist 41 | LBDownloadURL 42 | https://github.com/prenagha/launchbar/tree/main/ReadLater.lbaction 43 | LBChangelog 44 | 45 | 1.0: Initial 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Read-Later.lbaction/Contents/Scripts/readlater.js: -------------------------------------------------------------------------------- 1 | 2 | function run() { 3 | links = load(); 4 | refreshCounter(); 5 | output = []; 6 | if (links.length == 0) { 7 | output.push({ 8 | 'title': 'Zero links', 9 | 'icon': 'font-awesome:fa-circle-o' 10 | }); 11 | } else { 12 | showLinks = []; 13 | for (let i = 0; i < links.length; i++) { 14 | link = links[i]; 15 | if (link && link.url && !link.linkduplicate) 16 | showLinks.push(link); 17 | } 18 | output.push({ 19 | title: 'Read All ' + showLinks.length + ' ↗️', 20 | icon: 'font-awesome:fa-trash', 21 | action: 'readAll', 22 | actionRunsInBackground: true 23 | }); 24 | output.push({ 25 | title: 'Peek All ' + showLinks.length + ' 👀', 26 | icon: 'font-awesome:fa-eye', 27 | action: 'peekAll', 28 | actionRunsInBackground: true 29 | }); 30 | output.push(...showLinks); 31 | output.push({ 32 | title: 'Remove', 33 | icon: 'font-awesome:file-o', 34 | action: 'removeList', 35 | actionReturnsItems: true 36 | }); 37 | output.push({ 38 | title: 'Remove All ' + links.length + ' 👋🏼', 39 | icon: 'font-awesome:files-o', 40 | action: 'removeAll', 41 | actionRunsInBackground: true 42 | }); 43 | } 44 | return output; 45 | } 46 | 47 | function readAll() { 48 | links = load(); 49 | openAll(links); 50 | deleteAll(links); 51 | refreshCounter(); 52 | } 53 | 54 | function peekAll() { 55 | links = load(); 56 | openAll(links); 57 | } 58 | 59 | function openAll(links) { 60 | for (let i = 0; i < links.length; i++) { 61 | link = links[i]; 62 | if (link && link.url && !link.linkduplicate) 63 | LaunchBar.execute('/usr/bin/open', '--url', link.url); 64 | } 65 | } 66 | 67 | function deleteAll(links) { 68 | for (let i = 0; i < links.length; i++) { 69 | link = links[i]; 70 | if (link && link.linkfile) 71 | removeFile(link.linkfile); 72 | } 73 | } 74 | 75 | function removeAll() { 76 | links = load(); 77 | deleteAll(links); 78 | refreshCounter(); 79 | } 80 | 81 | function removeList() { 82 | output = [] 83 | links = load(); 84 | for (let i = 0; i < links.length; i++) { 85 | link = links[i]; 86 | if (link && link.linkfile && !link.linkduplicate) { 87 | link.icon = 'font-awesome:fa-trash'; 88 | link.action = 'removeOne'; 89 | link.actionArgument = link.url; 90 | output.push(link); 91 | } 92 | } 93 | return output; 94 | } 95 | 96 | function removeOne(url) { 97 | links = load(); 98 | for (let i = 0; i < links.length; i++) { 99 | link = links[i]; 100 | if (url && link && link.url && url === link.url) 101 | removeFile(link.linkfile); 102 | } 103 | refreshCounter(); 104 | } 105 | 106 | function refreshCounter() { 107 | LaunchBar.execute('/usr/bin/open', '--background', '--url', 'swiftbar://refreshplugin?name=links'); 108 | } 109 | 110 | function capDir() { 111 | captureDir = LaunchBar.homeDirectory + '/Library/Mobile Documents/iCloud~is~workflow~my~workflows/Documents/Read-Later/Capture'; 112 | if (Action.preferences.captureDir == undefined 113 | || Action.preferences.captureDir.length == 0) { 114 | Action.preferences.captureDir = captureDir; 115 | } 116 | return Action.preferences.captureDir; 117 | } 118 | 119 | function rdDir() { 120 | readDir = LaunchBar.homeDirectory + '/Archive/Links/Read' 121 | if (Action.preferences.readDir == undefined 122 | || Action.preferences.readDir.length == 0) { 123 | Action.preferences.readDir = readDir; 124 | } 125 | return Action.preferences.readDir; 126 | } 127 | 128 | function load() { 129 | var captureDir = capDir(); 130 | LaunchBar.debugLog("captureDir " + Action.preferences.captureDir); 131 | if (!File.exists(captureDir) || !File.isDirectory(captureDir)) 132 | return [err('Capture dir invalid', captureDir)]; 133 | 134 | var urls = new Set(); 135 | var files = File.getDirectoryContents(Action.preferences.captureDir); 136 | var links = []; 137 | for (let i = 0; i < files.length; i++) { 138 | file = captureDir + '/' + files[i]; 139 | LaunchBar.debugLog('Reading ' + file); 140 | lines = File.readText(file).split('\n'); 141 | if (lines && lines.length >= 2) { 142 | url = lines[0]; 143 | links.push({ 144 | title: lines[1], 145 | subtitle: url, 146 | icon: 'font-awesome:fa-globe', 147 | url: url, 148 | linkfile: file, 149 | linkduplicate: urls.has(url) 150 | }); 151 | urls.add(url); 152 | } else { 153 | links.push(err('Invalid file', file)); 154 | } 155 | }; 156 | links.sort((a,b) => a.title.localeCompare(b.title)); 157 | return links; 158 | } 159 | 160 | function removeFile(file) { 161 | if (file) { 162 | dt = new Date(); 163 | year = dt.getFullYear(); 164 | month = (dt.getMonth() < 10 ? "0" : "") + (dt.getMonth()+1) 165 | day = (dt.getDate() < 10 ? "0" : "") + dt.getDate() 166 | dir = rdDir() + '/' + year + '/' + month + '/' + day; 167 | if (!File.exists(dir)) 168 | File.createDirectory(dir); 169 | LaunchBar.log('Move to read ' + file); 170 | LaunchBar.execute('/bin/mv', '-f', file, dir); 171 | } 172 | } 173 | 174 | function err(msg, detail) { 175 | var m = 'ERROR: ' + msg; 176 | LaunchBar.log(m); 177 | return { 178 | title: m, 179 | icon: 'font-awesome:fa-exclamation-triangle', 180 | subtitle : detail, 181 | alwaysShowSubtitle: true 182 | }; 183 | } 184 | -------------------------------------------------------------------------------- /Recents.lbaction/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.renaghan.launchbar.Recents 7 | CFBundleName 8 | Recent Files 9 | CFBundleVersion 10 | 1.2 11 | CFBundleIconFile 12 | font-awesome:fa-clock-o 13 | LBDebugLogEnabled 14 | 15 | LBMinimumLaunchBarVersion 16 | 6121 17 | LBScripts 18 | 19 | LBDefaultScript 20 | 21 | LBScriptName 22 | recents.js 23 | LBRunInBackground 24 | 25 | LBRequiresArgument 26 | 27 | LBReturnsResult 28 | 29 | 30 | 31 | LBDescription 32 | 33 | LBSummary 34 | LaunchBar action integration to return result of recently used files from Smart Folder/Saved Search 35 | LBRequirements 36 | Smart Folder/Saved Search http://www.macworld.com/article/1154709/business/smartfolders.html 37 | LBAuthor 38 | Padraic Renaghan 39 | LBEmail 40 | prenagha@renaghan.com 41 | LBWebsiteURL 42 | https://renaghan.com/launchbar/recent-files/ 43 | LBTwitter 44 | @prenagha 45 | LBUpdateURL 46 | https://raw.githubusercontent.com/prenagha/launchbar/master/Recents.lbaction/Contents/Info.plist 47 | LBDownloadURL 48 | https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/prenagha/launchbar/tree/main/Recents.lbaction 49 | LBChangelog 50 | 51 | 1.2: Fix download URL 52 | 1.0: Initial Build 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /Recents.lbaction/Contents/Scripts/recents.js: -------------------------------------------------------------------------------- 1 | 2 | function run() { 3 | var homeDir = LaunchBar.homeDirectory; 4 | 5 | if (!Action.preferences.NotFoundMessage) 6 | Action.preferences.NotFoundMessage = 'No recent files found'; 7 | 8 | if (!Action.preferences.FindCommand) 9 | Action.preferences.FindCommand = 10 | '/usr/bin/mdfind,-onlyin,'+homeDir+'/Documents,-onlyin,'+homeDir+'/Desktop,-onlyin,'+homeDir+'/Downloads,-s,Recents'; 11 | 12 | var args = Action.preferences.FindCommand.split(','); 13 | var searchLines = LaunchBar.execute(...args); 14 | searchLines = searchLines.split('\n'); 15 | 16 | var results = []; 17 | 18 | for (i = 0; i < searchLines.length; i++) { 19 | var path = searchLines[i]; 20 | var p = path.substring(homeDir.length+1); 21 | 22 | var parts = p.split('/'); 23 | var label = parts[0]; 24 | var name = parts[parts.length-1]; 25 | var dir = ''; 26 | if (parts.length > 2) { 27 | dir = parts.slice(1, parts.length).join('/'); 28 | } 29 | 30 | results.push({'title': name, 'path': path, 'label': label, 'subtitle': dir}); 31 | } 32 | 33 | if (results.length == 0) { 34 | results.push({'title': Action.preferences.NotFoundMessage, 35 | 'icon': 'font-awesome:fa-clock-o'}); 36 | } 37 | 38 | return results; 39 | } 40 | 41 | -------------------------------------------------------------------------------- /Screenshot.lbaction/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.renaghan.launchbar.Screenshot 7 | CFBundleName 8 | Screenshot 9 | CFBundleVersion 10 | 3.7 11 | CFBundleIconFile 12 | font-awesome:fa-camera 13 | LBDebugLogEnabled 14 | 15 | LBScripts 16 | 17 | LBDefaultScript 18 | 19 | LBScriptName 20 | screenshot.js 21 | LBRunInBackground 22 | 23 | LBRequiresArgument 24 | 25 | LBReturnsResult 26 | 27 | 28 | 29 | LBDescription 30 | 31 | LBSummary 32 | Capture a screenshot, save it to ~/Downloads folder as original and 33 | optimized with ImageAlpha, then pass optimized version back to LaunchBar for 34 | sending to another action 35 | LBRequirements 36 | ImageAlpha http://pngmini.com 37 | LBAuthor 38 | Padraic Renaghan 39 | LBEmail 40 | prenagha@renaghan.com 41 | LBWebsiteURL 42 | https://renaghan.com/launchbar/screenshot/ 43 | LBTwitter 44 | @prenagha 45 | LBResult 46 | Screenshot file optimized through ImageAlpha 47 | LBUpdateURL 48 | https://raw.githubusercontent.com/prenagha/launchbar/master/Screenshot.lbaction/Contents/Info.plist 49 | LBDownloadURL 50 | https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/prenagha/launchbar/tree/main/Screenshot.lbaction 51 | LBChangelog 52 | 53 | 3.7: Fix download URL 54 | 3.5: Fix trash and img copy code 55 | 3.4: No error on missing screenshot, original moved to trash 56 | 3.3: Changed image copy to swift 57 | 3.2: Automatically copy image to clipboard 58 | Support action updates 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /Screenshot.lbaction/Contents/Scripts/imgcopy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Screenshot.lbaction/Contents/Scripts/imgcopy -------------------------------------------------------------------------------- /Screenshot.lbaction/Contents/Scripts/screenshot.js: -------------------------------------------------------------------------------- 1 | /* orig idea from https://github.com/hlissner/launchbar6-scripts */ 2 | 3 | function run() { 4 | var d = new Date(); 5 | var df = "" + d.getFullYear() + (d.getMonth()+1) + d.getDate() 6 | + "_" + d.getHours() + d.getMinutes() + d.getSeconds() 7 | + "_" + d.getMilliseconds(); 8 | var path = LaunchBar.homeDirectory + '/Downloads/sc_' + df + '.png'; 9 | var opt = '/Applications/ImageAlpha.app/Contents/MacOS/pngquant'; 10 | try { 11 | LaunchBar.execute('/usr/sbin/screencapture', '-i', path); 12 | if (!File.exists(path)) 13 | return; 14 | if (File.exists(opt)) { 15 | var orig = LaunchBar.homeDirectory 16 | + '/Downloads/sc_' + df + '_orig.png'; 17 | LaunchBar.execute('/bin/cp', '-p', path, orig); 18 | LaunchBar.execute(opt, '--force', '--ext', '.png', path); 19 | } 20 | 21 | // copy the optimized image to the clipboard 22 | LaunchBar.executeAppleScript('tell app "Finder" to set the clipboard to POSIX file "' + path + '"'); 23 | 24 | // move original image to Trash 25 | LaunchBar.execute(Action.path + '/Contents/Scripts/trash', orig); 26 | 27 | } catch (exception) { 28 | LaunchBar.log('Screenshot Error ' + exception + ' -- ' + path); 29 | LaunchBar.alert('Screenshot Error', exception); 30 | return; 31 | } 32 | 33 | // this action runs in background so screencapture can work 34 | // when it is done tell LB to select the screenshot file which 35 | // will make LB reappear 36 | LaunchBar.openCommandURL('select?file='+encodeURIComponent(path)); 37 | return; 38 | } 39 | -------------------------------------------------------------------------------- /Screenshot.lbaction/Contents/Scripts/trash: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Screenshot.lbaction/Contents/Scripts/trash -------------------------------------------------------------------------------- /Screenshot.lbaction/Contents/Scripts/trash.swift: -------------------------------------------------------------------------------- 1 | // move file to trash 2 | // idea from https://github.com/reklis/recycle 3 | import Cocoa 4 | 5 | let argCount = CommandLine.arguments.count 6 | if argCount > 1 { 7 | let manager = FileManager.default 8 | for path in CommandLine.arguments[1.. 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.renaghan.launchbar.ShareSafariLink 7 | CFBundleName 8 | Share Safari Link 9 | CFBundleVersion 10 | 3.4 11 | CFBundleIconFile 12 | com.apple.Safari 13 | LBDebugLogEnabled 14 | 15 | LBRequiredApplication 16 | com.apple.Safari 17 | LBScripts 18 | 19 | LBDefaultScript 20 | 21 | LBScriptName 22 | share.scpt 23 | LBRunInBackground 24 | 25 | LBRequiresArgument 26 | 27 | LBReturnsResult 28 | 29 | 30 | 31 | LBDescription 32 | 33 | LBSummary 34 | Grab the URL and Title of the currently selected tab in Safari and return it in text form suitable for sending via email. 35 | LBAuthor 36 | Padraic Renaghan 37 | LBEmail 38 | prenagha@renaghan.com 39 | LBWebsiteURL 40 | https://renaghan.com/launchbar/share-safari-link/ 41 | LBTwitter 42 | @prenagha 43 | LBResult 44 | URL and Title of selected tab in Safari 45 | LBUpdateURL 46 | https://raw.githubusercontent.com/prenagha/launchbar/master/Share%20Safari%20Link.lbaction/Contents/Info.plist 47 | LBDownloadURL 48 | https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/prenagha/launchbar/tree/main/Share%20Safari%20Link.lbaction&fileName=Share-Safari-Link&rootDirectory=Share-Safari-Link.lbaction 49 | LBChangelog 50 | 51 | 3.4: Fix download URL 52 | Support action updates 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /Share Safari Link.lbaction/Contents/Scripts/share.applescript: -------------------------------------------------------------------------------- 1 | 2 | property SAFARI : "com.apple.Safari" 3 | 4 | -- is application running? 5 | on app_running(bundleId) 6 | tell application "System Events" 7 | return (bundle identifier of processes) contains bundleId 8 | end tell 9 | end app_running 10 | 11 | -- This handler is called when the user runs the action: 12 | on run 13 | if app_running(SAFARI) then 14 | -- Info the current Safari tab back to Launchbar 15 | tell application "Safari" 16 | set theURL to URL of current tab of window 1 17 | set theTitle to name of current tab of window 1 18 | set theLink to theTitle & " " & theURL as string 19 | return [{title:theLink, subtitle:theURL, icon:"URL.icns"}] 20 | end tell 21 | else 22 | return [{title:"Safari not running", icon:"Caution.icns"}] 23 | end if 24 | end run 25 | -------------------------------------------------------------------------------- /Share.lbaction/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.renaghan.launchbar.Share 7 | CFBundleName 8 | Share File 9 | CFBundleVersion 10 | 2.2 11 | CFBundleIconFile 12 | font-awesome:fa-share 13 | LBDebugLogEnabled 14 | 15 | LBScripts 16 | 17 | LBDefaultScript 18 | 19 | LBScriptName 20 | share.js 21 | LBRunInBackground 22 | 23 | LBRequiresArgument 24 | 25 | LBAcceptedArgumentTypes 26 | 27 | string 28 | path 29 | 30 | LBAllowsInstantReuseOfLastTextInputString 31 | 32 | LBReturnsResult 33 | 34 | 35 | 36 | LBDescription 37 | 38 | LBSummary 39 | Share a file via Amazon AWS S3 bucket 40 | LBRequirements 41 | Amazon AWS S3 bucket, properly configured, Amazon AWS command line interface (CLI) 42 | LBAuthor 43 | Padraic Renaghan 44 | LBEmail 45 | prenagha@renaghan.com 46 | LBWebsiteURL 47 | https://renaghan.com/launchbar/share-file/ 48 | LBTwitter 49 | @prenagha 50 | LBArgument 51 | Selected file in LaunchBar 52 | LBUpdateURL 53 | https://raw.githubusercontent.com/prenagha/launchbar/master/Share.lbaction/Contents/Info.plist 54 | LBDownloadURL 55 | https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/prenagha/launchbar/tree/main/Share.lbaction 56 | LBChangelog 57 | 58 | 2.2: Fix download URL 59 | 2.0: Use AWS S3 pre-signed URL 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /Share.lbaction/Contents/Scripts/share.js: -------------------------------------------------------------------------------- 1 | 2 | var ALERT_ICON = 'font-awesome:fa-exclamation-triangle'; 3 | 4 | function runWithString(string) { 5 | return go(string); 6 | } 7 | 8 | function runWithPaths(paths) { 9 | if (paths.length == 1) 10 | return go(paths[0]); 11 | 12 | // zip up the paths and dropshare the zipped file 13 | var tzip = LaunchBar.execute('/bin/bash', 'zip.sh', ...paths); 14 | LaunchBar.debugLog('Temp zip file ' + tzip); 15 | tzip = chomp(tzip); 16 | var rtn = go(tzip); 17 | LaunchBar.execute('/bin/rm', tzip); 18 | return rtn; 19 | } 20 | 21 | function runWithItem(item) { 22 | return go(item.path); 23 | } 24 | 25 | function runWithURL(url, details) { 26 | return go(url); 27 | } 28 | 29 | function run(arg) { 30 | return go(LaunchBar.getClipboardString()); 31 | } 32 | 33 | function chomp(inp) { 34 | return inp.replace(/(\n|\r)+$/, ''); 35 | } 36 | 37 | function go(file) { 38 | if (!file || file == undefined || file.length == 0) { 39 | return err('Missing file to share', ''); 40 | } 41 | LaunchBar.debugLog("Input File='" + file + "'"); 42 | if (!File.exists(file)) { 43 | return err('File does not exist', file); 44 | } 45 | if (!File.isReadable(file)) { 46 | return err('File is not readable', file); 47 | } 48 | 49 | if (Action.preferences.bucket == undefined 50 | || Action.preferences.bucket.length == 0) { 51 | Action.preferences.bucket = 'mybucket'; 52 | } 53 | LaunchBar.debugLog("Pref bucket='" + Action.preferences.bucket + "'"); 54 | 55 | if (Action.preferences.dir == undefined 56 | || Action.preferences.dir.length == 0) { 57 | Action.preferences.dir = 'share'; 58 | } 59 | LaunchBar.debugLog("Pref dir='" + Action.preferences.dir + "'"); 60 | 61 | if (Action.preferences.profilePut == undefined 62 | || Action.preferences.profilePut.length == 0) { 63 | Action.preferences.profilePut = 'share-file-put'; 64 | } 65 | LaunchBar.debugLog("Pref profilePut='" + Action.preferences.profilePut + "'"); 66 | 67 | if (Action.preferences.profileGet == undefined 68 | || Action.preferences.profileGet.length == 0) { 69 | Action.preferences.profileGet = 'share-file-get'; 70 | } 71 | LaunchBar.debugLog("Pref profileGet='" + Action.preferences.profileGet + "'"); 72 | 73 | try { 74 | var sharedURL = LaunchBar.execute('/bin/bash', 'share.sh', Action.preferences.bucket, Action.preferences.dir, Action.preferences.profilePut, Action.preferences.profileGet, file); 75 | LaunchBar.debugLog("Shared URL='" + sharedURL + "'"); 76 | 77 | return [{ 78 | title: 'Share URL' 79 | ,subtitle: sharedURL 80 | ,badge: 'on clipboard' 81 | ,url: sharedURL 82 | }]; 83 | 84 | } catch (exception) { 85 | return err('Error sharing: ' + exception, file); 86 | } 87 | } 88 | 89 | function err(msg, file) { 90 | var m = 'ERROR: ' + msg; 91 | LaunchBar.log(m); 92 | return [{'title': m, 93 | 'icon': ALERT_ICON, 94 | path: file, 95 | subtitle: file}]; 96 | } 97 | -------------------------------------------------------------------------------- /Share.lbaction/Contents/Scripts/share.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | AWS=/usr/local/bin/aws 4 | TEN_DAYS_SECONDS="864000" 5 | 6 | BUCKET=$1 7 | BUCKET_DIR=$2 8 | PROFILE_PUT=$3 9 | PROFILE_GET=$4 10 | FILE=$5 11 | 12 | if [ ! -f "${AWS}" ] 13 | then 14 | echo "AWS CLI not found ${AWS}" 15 | exit 1 16 | fi 17 | 18 | if [ ! -f "${FILE}" ] 19 | then 20 | echo "File to share not found ${FILE}" 21 | exit 1 22 | fi 23 | 24 | NAME=${FILE##*/} 25 | DEST=s3://${BUCKET}/${BUCKET_DIR}/${NAME} 26 | 27 | # copy the file to S3, private, no public access 28 | $AWS --profile ${PROFILE_PUT} s3 cp \ 29 | --only-show-errors \ 30 | --acl private \ 31 | --cache-control no-cache \ 32 | --storage-class STANDARD \ 33 | --sse AES256 \ 34 | ${FILE} ${DEST} 2>&1 35 | 36 | if [ $? -ne 0 ] 37 | then 38 | exit 2 39 | fi 40 | 41 | TMPFILE=`/usr/bin/mktemp -t signedUrl.txt` || exit 1 42 | 43 | # get a signed URL that will work for 10 days 44 | $AWS --profile ${PROFILE_GET} s3 presign \ 45 | --expires-in ${TEN_DAYS_SECONDS} \ 46 | ${DEST} > ${TMPFILE} 47 | 48 | if [ $? -ne 0 ] 49 | then 50 | /bin/rm ${TMPFILE} 2>/dev/null 51 | exit 3 52 | fi 53 | 54 | # put the signed URL on the clipboard 55 | /bin/cat ${TMPFILE} | /usr/bin/pbcopy 56 | 57 | # return signed URL back to LaunchBar 58 | /bin/cat ${TMPFILE} 59 | 60 | /bin/rm ${TMPFILE} 2>/dev/null 61 | 62 | exit 0 63 | -------------------------------------------------------------------------------- /Share.lbaction/Contents/Scripts/zip.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | TDIR=`/usr/bin/mktemp -d` 3 | BFIRST=`basename $1` 4 | FIRST="${BFIRST%.*}" 5 | TZIP="${TDIR}/${FIRST}.zip" 6 | /usr/bin/zip -jqo "${TZIP}" $* 7 | ##/bin/cp -a $TZIP ~/Desktop 8 | echo "${TZIP}" -------------------------------------------------------------------------------- /Switch Audio.lbaction/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.renaghan.launchbar.SwitchAudio 7 | CFBundleName 8 | Switch Audio 9 | LBAbbreviation 10 | sound speaker headphones microphone 11 | CFBundleVersion 12 | 2.7 13 | CFBundleIconFile 14 | font-awesome:fa-headphones 15 | LBDebugLogEnabled 16 | 17 | LBScripts 18 | 19 | LBDefaultScript 20 | 21 | LBScriptName 22 | audio.js 23 | LBRunInBackground 24 | 25 | LBRequiresArgument 26 | 27 | LBReturnsResult 28 | 29 | 30 | 31 | LBDescription 32 | 33 | LBSummary 34 | Switch Input and Output Audio devices. Requires switchaudio-osx which is mostly easily installed via homebrew: "brew install switchaudio-osx". When invoked it returns items for each output device (headphones) and each input device (microphone). The currently selected item for each is prefixed with *. Select an item and hit enter to switch to that item, the action will respond by refreshing the items and you should note the * by the item you just selected. 35 | LBRequirements 36 | https://github.com/deweller/switchaudio-osx 37 | LBAuthor 38 | Padraic Renaghan 39 | LBEmail 40 | prenagha@renaghan.com 41 | LBWebsiteURL 42 | https://renaghan.com/launchbar/switch-audio/ 43 | LBTwitter 44 | @prenagha 45 | LBUpdateURL 46 | https://raw.githubusercontent.com/prenagha/launchbar/master/Switch%20Audio.lbaction/Contents/Info.plist 47 | LBDownloadURL 48 | https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/prenagha/launchbar/tree/main/Switch%20Audio.lbaction&fileName=Switch-Audio&rootDirectory=Switch-Audio.lbaction 49 | LBChangelog 50 | 51 | 2.7: Another fix for JSON parse error #22 52 | 2.6: Fix JSON parse error #22 53 | 2.5: Fix download URL 54 | 2.4: Adjust homebrew path for Apple Silicon 55 | 2.3: Support space in device names 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /Switch Audio.lbaction/Contents/Resources/equipment.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Switch Audio.lbaction/Contents/Resources/equipment.icns -------------------------------------------------------------------------------- /Switch Audio.lbaction/Contents/Resources/headphones.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Switch Audio.lbaction/Contents/Resources/headphones.icns -------------------------------------------------------------------------------- /Switch Audio.lbaction/Contents/Resources/microphone.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Switch Audio.lbaction/Contents/Resources/microphone.icns -------------------------------------------------------------------------------- /Switch Audio.lbaction/Contents/Scripts/audio.js: -------------------------------------------------------------------------------- 1 | 2 | function run() { 3 | try { 4 | var audioJson = LaunchBar.execute('/bin/bash', 'audio.sh'); 5 | LaunchBar.debugLog('Output ' + audioJson); 6 | var audio = JSON.parse(audioJson); 7 | 8 | var items = []; 9 | 10 | if (audio.outputs && audio.outputs.length > 0) { 11 | for (var i = 0; i < audio.outputs.length; i++) { 12 | var output = audio.outputs[i]; 13 | items.push({ 14 | 'title': (output == audio.currentOutput ? "* " : "") + output, 15 | 'typ': 'output', 16 | 'name': output, 17 | 'action': 'switchto', 18 | 'actionReturnsItems': true, 19 | 'icon':'headphones.icns'}); 20 | } 21 | } else { 22 | LaunchBar.log('Audio outputs not available'); 23 | } 24 | 25 | if (audio.inputs && audio.inputs.length > 0) { 26 | for (var i = 0; i < audio.inputs.length; i++) { 27 | var input = audio.inputs[i]; 28 | items.push({ 29 | 'title': (input == audio.currentInput ? "* " : "") + input, 30 | 'typ': 'input', 31 | 'name': input, 32 | 'action': 'switchto', 33 | 'actionReturnsItems': true, 34 | 'icon':'microphone.icns'}); 35 | } 36 | } else { 37 | LaunchBar.log('Audio inputs not available'); 38 | } 39 | 40 | return items; 41 | } catch (exception) { 42 | LaunchBar.log('Error ' + exception); 43 | LaunchBar.alert('Error', exception); 44 | } 45 | } 46 | 47 | function switchto(item) { 48 | try { 49 | var rtn = LaunchBar.execute('/bin/bash', 'audio.sh', item.name, item.typ); 50 | LaunchBar.debugLog('Output switch ' + rtn); 51 | return run(); 52 | } catch (exception) { 53 | LaunchBar.log('Error switching ' + exception); 54 | LaunchBar.alert('Error switching', exception); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Switch Audio.lbaction/Contents/Scripts/audio.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PATH="${PATH}:/opt/homebrew/bin:/usr/local/bin" 4 | CMD=SwitchAudioSource 5 | 6 | if [ ! -z "$1" ] 7 | then 8 | $CMD -s "$1" -t "$2" 9 | exit $? 10 | fi 11 | 12 | CINPUT=`$CMD -c -t input | tr '\n"' ' '` 13 | COUTPUT=`$CMD -c -t output | tr '\n"' ' '` 14 | INS= 15 | OUTS= 16 | 17 | IFS= 18 | while read -r INPUT 19 | do 20 | if [ ! -z "${INS}" ] 21 | then 22 | INS="${INS}, " 23 | fi 24 | INS="${INS}\"${INPUT}\"" 25 | done <<< `$CMD -a -t input | sed 's/ (input)$//' | tr -d '"'` 26 | 27 | while read -r OUTPUT 28 | do 29 | if [ ! -z "${OUTS}" ] 30 | then 31 | OUTS="${OUTS}, " 32 | fi 33 | OUTS="${OUTS}\"${OUTPUT}\"" 34 | done <<< "`$CMD -a -t output | sed 's/ (output)$//' | tr -d '"'`" 35 | 36 | cat << EOF 37 | { 38 | "currentInput": "${CINPUT}", 39 | "currentOutput": "${COUTPUT}", 40 | "inputs": [ ${INS} ], 41 | "outputs": [ ${OUTS} ] 42 | } 43 | EOF 44 | exit 0 45 | -------------------------------------------------------------------------------- /Things.lbaction/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.renaghan.launchbar.Things 7 | CFBundleName 8 | Things To Do 9 | LBAbbreviation 10 | things todo task 11 | CFBundleVersion 12 | 3.2 13 | CFBundleIconFile 14 | font-awesome:fa-inbox 15 | LBAssociatedApplication 16 | com.culturedcode.things 17 | LBARequiredApplication 18 | com.culturedcode.things 19 | LBTextInputTitle 20 | New To Do 21 | LBDebugLogEnabled 22 | 23 | LBMinimumLaunchBarVersion 24 | 6121 25 | LBScripts 26 | 27 | LBDefaultScript 28 | 29 | LBScriptName 30 | things.scpt 31 | LBKeepWindowActive 32 | 33 | LBRunInBackground 34 | 35 | LBRequiresArgument 36 | 37 | LBAcceptedArgumentTypes 38 | 39 | string 40 | path 41 | 42 | LBReturnsResult 43 | 44 | 45 | 46 | LBDescription 47 | 48 | LBSummary 49 | LaunchBar action integration with Things To Do Mac application from Cultured Code 50 | LBRequirements 51 | Things Application http://culturedcode.com/things/ 52 | LBAuthor 53 | Padraic Renaghan 54 | LBEmail 55 | prenagha@renaghan.com 56 | LBWebsiteURL 57 | https://renaghan.com/launchbar/things-to-do/ 58 | LBTwitter 59 | @prenagha 60 | LBArgument 61 | Text of new to do to create 62 | LBUpdateURL 63 | https://raw.githubusercontent.com/prenagha/launchbar/master/Things.lbaction/Contents/Info.plist 64 | LBDownloadURL 65 | https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/prenagha/launchbar/tree/main/Things.lbaction 66 | LBChangelog 67 | 68 | 3.2: Fix download URL 69 | 3.0: Update for Things 3 70 | 2.2: View URL for URL in notes of todo 71 | 2.1: Support action updates 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /Things.lbaction/Contents/Scripts/things.applescript: -------------------------------------------------------------------------------- 1 | -- 2 | -- launchbar integration for Things 3 | -- 4 | -- NOTE: This is the text AppleScript, the LaunchBar action Info.plist refers to 5 | -- a **COMPILED** .scpt version of this script. You can compile this text AppleScript 6 | -- into .scpt using command line osacompile or by exporting/save-as within Script Editor 7 | -- 8 | 9 | property SAFARI : "com.apple.Safari" 10 | 11 | -- debug logging to console 12 | on dlog(myObj) 13 | set txt to quoted form of (myObj as string) 14 | log txt 15 | do shell script "logger -t 'LaunchBar.Things' " & txt 16 | end dlog 17 | 18 | -- is application running? 19 | on app_running(bundleId) 20 | tell application "System Events" 21 | return (bundle identifier of processes) contains bundleId 22 | end tell 23 | end app_running 24 | 25 | -- called by launchbar when it has string input 26 | on handle_string(todo) 27 | if todo is not "" then 28 | set newTodo to {} 29 | tell application "Things3" 30 | set newTodo to parse quicksilver input todo 31 | end tell 32 | did_it(newTodo, "Created") 33 | return new_todo_items(newTodo) 34 | end if 35 | end handle_string 36 | 37 | -- adds a URL as a new todo 38 | on addURL(todo, theURL) 39 | if todo is not "" then 40 | set newTodo to {} 41 | tell application "Things3" 42 | set newTodo to parse quicksilver input todo 43 | if theURL starts with "http" then 44 | set notes of newTodo to "[url=" & theURL & "] " & theURL & " [/url]" 45 | end if 46 | end tell 47 | did_it(newTodo, "Created from URL") 48 | return new_todo_items(newTodo) 49 | end if 50 | end addURL 51 | 52 | -- create a new todo from active safari tab is selected 53 | on safari_add(todo) 54 | addURL((subtitle of todo), (|url| of todo)) 55 | end safari_add 56 | 57 | -- called by launchbar when it has an item input 58 | on handle_item(todo) 59 | addURL((title of todo), (|url| of todo)) 60 | end handle_item 61 | 62 | -- called by launchbar when it has URL input 63 | on handle_URL(theURL, theDetails) 64 | addURL(theURL, theURL) 65 | end handle_URL 66 | 67 | -- called by launchbar when files are passed to the action 68 | on open (thePaths) 69 | repeat with thePath in thePaths 70 | handle_string("File " & POSIX path of thePath) 71 | end repeat 72 | end open 73 | 74 | on new_todo_items(todo) 75 | set actions to {} 76 | set action to {title:"Today", action:"moveToday", actionArgument:id of todo, icon:"font-awesome:fa-star"} 77 | copy action to end of actions 78 | 79 | set action to {title:"Next", action:"moveNext", actionArgument:id of todo, icon:"font-awesome:fa-thumb-tack"} 80 | copy action to end of actions 81 | 82 | set action to {title:"Someday", action:"moveSomeday", actionArgument:id of todo, icon:"font-awesome:fa-archive"} 83 | copy action to end of actions 84 | 85 | set action to {title:"View", action:"viewTodo", actionArgument:id of todo, icon:"font-awesome:fa-list-ul"} 86 | copy action to end of actions 87 | return actions 88 | end new_todo_items 89 | 90 | -- load all things items 91 | on load_all() 92 | tell application "Things3" 93 | set listsOut to {} 94 | repeat with lst in lists 95 | set listId to id of lst 96 | if listId is in {"TMInboxListSource", "TMTodayListSource", "TMNextListSource", "TMSomedayListSource", "TMCalendarListSource"} then 97 | set todosOut to {} 98 | 99 | set todos to to dos in lst 100 | repeat with todo in todos 101 | try 102 | set sts to status of todo 103 | if sts is open then 104 | set actions to {} 105 | 106 | if listId is not "TMCalendarSource" then 107 | set action to {title:"Complete " & name of todo, subtitle:notes of todo, label:tag names of todo, action:"completeTodo", actionArgument:id of todo, icon:"font-awesome:fa-check-square"} 108 | copy action to end of actions 109 | end if 110 | 111 | set u to stringBetween(notes of todo, "[url=", "]") of me 112 | if u is not "" then 113 | set action to {title:"View " & u, |url|:u} 114 | copy action to end of actions 115 | end if 116 | 117 | if listId is not "TMTodayListSource" and listId is not "TMCalendarListSource" then 118 | set action to {title:"Today", action:"moveToday", actionArgument:id of todo, icon:"font-awesome:fa-star"} 119 | copy action to end of actions 120 | end if 121 | 122 | if listId is not "TMNextListSource" and listId is not "TMCalendarListSource" then 123 | set title to "Next" 124 | if listId is "TMTodayListSource" then 125 | set title to "Not Today" 126 | end if 127 | set action to {title:title, action:"moveNext", actionArgument:id of todo, icon:"font-awesome:fa-thumb-tack"} 128 | copy action to end of actions 129 | end if 130 | 131 | if listId is not "TMSomedayListSource" and listId is not "TMCalendarListSource" then 132 | set action to {title:"Someday", action:"moveSomeday", actionArgument:id of todo, icon:"font-awesome:fa-archive"} 133 | copy action to end of actions 134 | end if 135 | 136 | set action to {title:"View", action:"viewTodo", actionArgument:id of todo, icon:"font-awesome:fa-list-ul"} 137 | copy action to end of actions 138 | 139 | if listId is not "TMCalendarListSource" then 140 | set action to {title:"Cancel", action:"cancelTodo", actionArgument:id of todo, icon:"font-awesome:fa-cancel"} 141 | copy action to end of actions 142 | end if 143 | 144 | set action to {title:"Trash", action:"trashTodo", actionArgument:id of todo, icon:"font-awesome:fa-trash"} 145 | copy action to end of actions 146 | 147 | set icon to "font-awesome:fa-square-o" 148 | if listId is "TMCalendarListSource" then 149 | set icon to "font-awesome:fa-calendar" 150 | end if 151 | set subt to notes of todo 152 | if due date of todo is not missing value then 153 | set icon to "font-awesome:fa-calendar" 154 | set subt to (due date of todo as string) & " " & (notes of todo) 155 | end if 156 | set todoOut to {title:name of todo, icon:icon, subtitle:subt, label:tag names of todo, children:actions} 157 | copy todoOut to end of todosOut 158 | end if 159 | end try 160 | end repeat 161 | 162 | if (count of todosOut) is greater than 0 then 163 | if listId is "TMTodayListSource" then 164 | set icon to "font-awesome:fa-star" 165 | else if listId is "TMNextListSource" then 166 | set icon to "font-awesome:fa-thumb-tack" 167 | else if listId is "TMSomedayListSource" then 168 | set icon to "font-awesome:fa-archive" 169 | else if listId is "TMCalendarListSource" then 170 | set icon to "font-awesome:fa-calendar" 171 | else 172 | set icon to "font-awesome:fa-inbox" 173 | end if 174 | set listOut to {title:name of lst, icon:icon, badge:(count of todosOut) as string, children:todosOut} 175 | copy listOut to end of listsOut 176 | end if 177 | end if 178 | end repeat 179 | end tell 180 | 181 | set theURL to "" 182 | if app_running(SAFARI) then 183 | tell application "Safari" 184 | set theName to name of current tab of window 1 185 | set theURL to URL of current tab of window 1 186 | set x to {title:"Add To Do from Safari", icon:SAFARI, subtitle:theName, |url|:theURL, action:"safari_add"} 187 | copy x to end of listsOut 188 | end tell 189 | end if 190 | 191 | set clip to get_clipboard() 192 | if clip is not "" and clip is not theURL then 193 | set x to {title:"Add To Do from Clipboard", subtitle:clip, icon:"ClipObject.icns", action:"handle_string", actionArgument:clip} 194 | copy x to end of listsOut 195 | end if 196 | 197 | return listsOut 198 | end load_all 199 | 200 | -- get the contents of the clipboard (if any) as plain text 201 | on get_clipboard() 202 | if (the clipboard) is not {} then 203 | return the clipboard as text 204 | end if 205 | return "" 206 | end get_clipboard 207 | 208 | -- get a list by id 209 | on get_list(listId) 210 | tell application "Things3" 211 | repeat with lst in lists 212 | if id of lst is listId then 213 | return lst 214 | end if 215 | end repeat 216 | end tell 217 | tell application "LaunchBar" to display in large type "List not found " & listId 218 | end get_list 219 | 220 | -- get a todo by id 221 | on get_todo(todoId) 222 | tell application "Things3" 223 | repeat with lst in lists 224 | set todos to to dos in lst 225 | repeat with todo in todos 226 | if id of todo is todoId then 227 | return todo 228 | end if 229 | end repeat 230 | end repeat 231 | end tell 232 | tell application "LaunchBar" to display in large type "Todo not found " & todoId 233 | end get_todo 234 | 235 | -- handle complete todo action 236 | on completeTodo(todoId) 237 | set todo to get_todo(todoId) 238 | tell application "Things3" to set status of todo to completed 239 | did_it(todo, "Completed") 240 | end completeTodo 241 | 242 | -- handle cancel todo action 243 | on cancelTodo(todoId) 244 | set todo to get_todo(todoId) 245 | tell application "Things3" to set status of todo to canceled 246 | did_it(todo, "Canceled") 247 | end cancelTodo 248 | 249 | -- handle move to today action 250 | on moveToday(todoId) 251 | set todo to get_todo(todoId) 252 | set lst to get_list("TMTodayListSource") 253 | tell application "Things3" to move todo to lst 254 | did_it(todo, "Moved to Today") 255 | end moveToday 256 | 257 | -- handle move to next action 258 | on moveNext(todoId) 259 | set todo to get_todo(todoId) 260 | set lst to get_list("TMNextListSource") 261 | tell application "Things3" to move todo to lst 262 | did_it(todo, "Moved to Next") 263 | end moveNext 264 | 265 | -- handle move to someday action 266 | on moveSomeday(todoId) 267 | set todo to get_todo(todoId) 268 | set lst to get_list("TMSomedayListSource") 269 | tell application "Things3" to move todo to lst 270 | did_it(todo, "Moved to Someday") 271 | end moveSomeday 272 | 273 | -- OSX notification to tell the user what we just did 274 | on did_it(todo, msg) 275 | log "Todo " & msg & " -- " & (name of todo) & " -- " & (id of todo) 276 | tell application "LaunchBar" 277 | display in notification center with title "To Do " & msg subtitle (name of todo) 278 | hide 279 | end tell 280 | end did_it 281 | 282 | -- view todo in Things application 283 | on viewTodo(todoId) 284 | set todo to get_todo(todoId) 285 | tell application "LaunchBar" to hide 286 | tell application "Things3" 287 | activate 288 | show todo 289 | end tell 290 | end viewTodo 291 | 292 | -- handle trash todo action 293 | on trashTodo(todoId) 294 | set todo to get_todo(todoId) 295 | tell application "Things3" to delete todo 296 | did_it(todo, "Deleted") 297 | end trashTodo 298 | 299 | -- http://applescript.bratis-lover.net/library/string/#explode 300 | on explode(delimiter, input) 301 | local delimiter, input, ASTID 302 | set ASTID to AppleScript's text item delimiters 303 | try 304 | set AppleScript's text item delimiters to delimiter 305 | set input to text items of input 306 | set AppleScript's text item delimiters to ASTID 307 | return input --> list 308 | on error eMsg number eNum 309 | set AppleScript's text item delimiters to ASTID 310 | error "Error in explode: " & eMsg number eNum 311 | end try 312 | end explode 313 | 314 | -- http://applescript.bratis-lover.net/library/string/#explode 315 | on stringBetween(str, head, tail) 316 | local str, head, tail 317 | try 318 | if str is {} then return "" 319 | if str is "" then return "" 320 | if str does not contain head then return "" 321 | if str does not contain tail then return "" 322 | set str to item 2 of my explode(head, str) 323 | set str to item 1 of my explode(tail, str) 324 | return str 325 | on error eMsg number eNum 326 | error "Error in stringBetween: " & eMsg number eNum 327 | end try 328 | end stringBetween 329 | 330 | 331 | -- called by launchbar when enter or browse into from top item 332 | on run 333 | load_all() 334 | end run 335 | -------------------------------------------------------------------------------- /Timer.lbaction/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.renaghan.launchbar.Timer 7 | CFBundleName 8 | Set a Timer 9 | CFBundleVersion 10 | 3.3 11 | CFBundleIconFile 12 | font-awesome:fa-clock-o 13 | LBDebugLogEnabled 14 | 15 | LBTextInputTitle 16 | Reminder [when] 17 | LBScripts 18 | 19 | LBDefaultScript 20 | 21 | LBScriptName 22 | timer.js 23 | LBRunInBackground 24 | 25 | LBRequiresArgument 26 | 27 | LBAcceptedArgumentTypes 28 | 29 | string 30 | 31 | LBReturnsResult 32 | 33 | 34 | 35 | LBDescription 36 | 37 | LBSummary 38 | Create a timer to remind you of something later. Like "Turn off oven 15m". LaunchBar will create the reminder then after the delay time it will show you the reminder text as a large message with an accompanying sound. 39 | LBAuthor 40 | Padraic Renaghan 41 | LBEmail 42 | prenagha@renaghan.com 43 | LBWebsiteURL 44 | https://renaghan.com/launchbar/set-a-timer/ 45 | LBTwitter 46 | @prenagha 47 | LBArgument 48 | Reminder text in the form of "[reminder message] [delay]". Delay a string like 1m, 2m30s, 4h... 49 | LBUpdateURL 50 | https://raw.githubusercontent.com/prenagha/launchbar/master/Timer.lbaction/Contents/Info.plist 51 | LBDownloadURL 52 | https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/prenagha/launchbar/tree/main/Timer.lbaction 53 | LBChangelog 54 | 55 | 3.3: Fix download URL 56 | Support action updates 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Timer.lbaction/Contents/Scripts/timer.js: -------------------------------------------------------------------------------- 1 | 2 | function runWithString(string) { 3 | return go(string); 4 | } 5 | 6 | function runWithItem(item) { 7 | return go(item.title); 8 | } 9 | 10 | function go(str) { 11 | if (!str || str == undefined || str.length == 0) { 12 | return {'title':'Reminder is empty','icon':'NotFound.icns'}; 13 | } 14 | try { 15 | 16 | var parts = str.split(' '); 17 | var delay = parts.pop(); 18 | var remind = parts.join(' '); 19 | LaunchBar.displayInLargeType({ 20 | 'title' : 'Reminder', 21 | 'string' : remind, 22 | 'sound' : 'Tink', 23 | 'delay': delay 24 | }); 25 | 26 | return {'title': 'Reminder in ' + delay 27 | ,'subtitle': remind 28 | ,'icon':'clock.pdf'}; 29 | 30 | } catch (exception) { 31 | return {'title': 'Timer Error ' + exception}; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Today.lbaction/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.renaghan.launchbar.Today 7 | CFBundleName 8 | Today 9 | LBAbbreviation 10 | calendar ical event appointment meeting 11 | CFBundleVersion 12 | 1.5 13 | CFBundleIconFile 14 | font-awesome:fa-calendar 15 | LBDebugLogEnabled 16 | 17 | LBScripts 18 | 19 | LBDefaultScript 20 | 21 | LBScriptName 22 | today.js 23 | LBRunInBackground 24 | 25 | LBRequiresArgument 26 | 27 | LBReturnsResult 28 | 29 | 30 | 31 | LBDescription 32 | 33 | LBSummary 34 | Today view (and optionally beyond) of calendar events. Requires icalBuddy be installed on your system. http://hasseg.org/icalBuddy/. See man page of icalBuddy and customize this actions parameters to it via this action's preferences file. 35 | LBRequiredApplication 36 | com.apple.iCal 37 | LBRequirements 38 | http://hasseg.org/icalBuddy/ 39 | LBAuthor 40 | Padraic Renaghan 41 | LBEmail 42 | prenagha@renaghan.com 43 | LBWebsiteURL 44 | https://renaghan.com/launchbar/today/ 45 | LBTwitter 46 | @prenagha 47 | LBUpdateURL 48 | https://raw.githubusercontent.com/prenagha/launchbar/master/Today.lbaction/Contents/Info.plist 49 | LBDownloadURL 50 | https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/prenagha/launchbar/tree/main/Today.lbaction 51 | LBChangelog 52 | 53 | 1.5: Fix download URL 54 | 1.3: Improved timing of click to call attempts 55 | 1.2: Ability to click the "Call" confirmation from FaceTime when dialing via phone 56 | 1.1: Ability to set time format as preference 57 | 1.0: Initial release 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /Today.lbaction/Contents/Resources/icalb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Today.lbaction/Contents/Resources/icalb.png -------------------------------------------------------------------------------- /Today.lbaction/Contents/Scripts/call.applescript: -------------------------------------------------------------------------------- 1 | 2 | on dlog(myObj) 3 | set txt to quoted form of (myObj as string) 4 | log txt 5 | do shell script "/usr/bin/logger -t 'LaunchBar.Today' " & txt 6 | end dlog 7 | 8 | -- code snippet from UI Browser http://pfiddlesoft.com/uibrowser/index.html 9 | tell application "System Events" to set GUIScriptingEnabled to UI elements enabled 10 | if not GUIScriptingEnabled then 11 | activate 12 | set scriptRunner to name of current application 13 | my dlog("GUI scripting not enabled") 14 | display alert "GUI Scripting is not enabled for " & scriptRunner & "." message "Open System Preferences, unlock the Security & Privacy preference, select " & scriptRunner & " in the Privacy Pane's Accessibility list, and then run this script again." buttons {"Open System Preferences", "Cancel"} default button "Cancel" 15 | if button returned of result is "Open System Preferences" then 16 | tell application "System Preferences" 17 | tell pane id "com.apple.preference.security" to reveal anchor "Privacy_Accessibility" 18 | activate 19 | end tell 20 | end if 21 | return 22 | end if 23 | my dlog("GUI scripting is enabled") 24 | 25 | -- click the Call button on the facetime call confirm window 26 | repeat 5 times 27 | delay 2 28 | my dlog("Looking for FaceTime call confirm window") 29 | tell application "System Events" 30 | if process "FaceTime" exists then 31 | tell process "FaceTime" 32 | if window 1 exists then 33 | if button "Call" of window 1 exists then 34 | my dlog("Clicking on FaceTime Call button") 35 | click button "Call" of window 1 36 | return 37 | else 38 | my dlog("FaceTime window button does not exist yet") 39 | end if 40 | else 41 | my dlog("FaceTime window does not exist yet") 42 | end if 43 | end tell 44 | else 45 | my dlog("FaceTime process does not exist yet") 46 | end if 47 | end tell 48 | end repeat 49 | my dlog("Unable to click FaceTime Call button") 50 | -------------------------------------------------------------------------------- /Today.lbaction/Contents/Scripts/today.js: -------------------------------------------------------------------------------- 1 | include('moment-with-locales.js'); 2 | 3 | var ALERT_ICON = '/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/AlertStopIcon.icns'; 4 | var PREFS = Action.preferences; 5 | var PREFS_RESET = false; 6 | var PLUGIN = false; 7 | //grab first set of numbers, if 10 or 11 long then we have a phone 8 | var PHONE_NBR = /1?\W*([2-9][0-8]\d)\W*([2-9]\d{2})\W*(\d{4})/; 9 | //grab next set of numbers, if any, assume that is PIN 10 | var PHONE_CODE = /\d[\d -]+/; 11 | //remove anything not a number from PIN 12 | var NOT_NBR = /\D/g; 13 | 14 | 15 | function setupPreferences() { 16 | if (!Action.preferences.clickCall) 17 | Action.preferences.clickCall = "true"; 18 | 19 | if (PREFS_RESET || !Action.preferences.icalBuddyPath) { 20 | Action.preferences.icalBuddyPath = "/usr/local/bin/icalBuddy"; 21 | Action.preferences.icalBuddyCommand = "eventsToday+1"; 22 | Action.preferences.icalBuddyPropertySeparator = "^"; 23 | var opts = [ 24 | "--noCalendarNames" 25 | ,"--noRelativeDates" 26 | ,"--includeOnlyEventsFromNowOn" 27 | ,"--maxNumNoteChars" , "100" 28 | ,"--dateFormat", "%Y-%m-%d" 29 | ,"--timeFormat", "%H:%M:%S %z" 30 | ,"--propertyOrder", "title,datetime,location,url,notes" 31 | ,"--propertySeparators", "a" + Action.preferences.icalBuddyPropertySeparator + "a" 32 | ,"--bullet", "" 33 | //,"--includeCals", "Personal,Work" 34 | ,"--includeEventProps", "title,datetime,location,url,notes" 35 | ,"--notesNewlineReplacement" , " " 36 | ]; 37 | Action.preferences.icalBuddyOptions = opts; 38 | Action.preferences.pluginFile = ""; 39 | } 40 | if (!Action.preferences.momentJsFormatToday) 41 | Action.preferences.momentJsFormatToday = "h:mm a"; 42 | if (!Action.preferences.momentJsFormat) 43 | Action.preferences.momentJsFormat = "ddd h:mm a"; 44 | if (!Action.preferences.momentJsFormatAllDay) 45 | Action.preferences.momentJsFormatAllDay = "ddd"; 46 | 47 | if (Action.preferences.pluginFile && Action.preferences.pluginFile.length > 5) { 48 | LaunchBar.log("Loading plugin from " + Action.preferences.pluginFile); 49 | include(Action.preferences.pluginFile); 50 | PLUGIN = true; 51 | } 52 | } 53 | 54 | function updatePreferences() { 55 | return[ 56 | {title: 'Edit Preferences', icon: "Pref_Advanced.icns", action: "editPreferences"} 57 | ,{title: 'iCalBuddy Website', url: 'http://hasseg.org/icalBuddy/', icon:'icalb.png'} 58 | ,{title: 'iCalBuddy Calendar List', action: 'calendarList', icon:'icalb.png'} 59 | ,{title: 'MomentJs Date Time Formats', url: 'http://momentjs.com/docs/#/displaying/format/'} 60 | ]; 61 | } 62 | 63 | function editPreferences() { 64 | LaunchBar.openURL('file://' + encodeURI(Action.supportPath + '/Preferences.plist')); 65 | } 66 | 67 | function calendarList() { 68 | try { 69 | var output = LaunchBar.execute(Action.preferences.icalBuddyPath, 'calendars'); 70 | return output; 71 | } catch (exception) { 72 | LaunchBar.log('Error ' + exception); 73 | LaunchBar.alert('Error', exception); 74 | } 75 | } 76 | 77 | function checkBuddy() { 78 | var err = []; 79 | if (!File.exists(PREFS.icalBuddyPath)) { 80 | err.push({title:'icalBuddy not found at ' + Action.preferences.icalBuddyPath 81 | ,icon: ALERT_ICON, url: 'http://hasseg.org/icalBuddy/'}); 82 | } else if (!File.isExecutable(PREFS.icalBuddyPath)) { 83 | err.push({title:'icalBuddy not executable', path: Action.preferences.icalBuddyPath, icon: ALERT_ICON}); 84 | } 85 | return err.concat(updatePreferences()); 86 | } 87 | 88 | function run() { 89 | try { 90 | setupPreferences(); 91 | var err = checkBuddy(); 92 | if (err.length > 4 || LaunchBar.options.commandKey) 93 | return err; 94 | 95 | moment.locale(LaunchBar.currentLocale); 96 | var now = moment(); 97 | 98 | var items = []; 99 | var args = []; 100 | args.push(Action.preferences.icalBuddyPath); 101 | args = args.concat(Action.preferences.icalBuddyOptions); 102 | args.push(Action.preferences.icalBuddyCommand); 103 | LaunchBar.debugLog('Execute ' + args); 104 | var output = LaunchBar.execute.apply(LaunchBar, args); 105 | LaunchBar.debugLog('Output ' + output); 106 | var lines = output.split("\n"); 107 | var event = {name:"",location:"",url:"",notes:"",start:"",end:"",phone:"",allDay:false}; 108 | for (var i = 0; i < lines.length; i++) { 109 | var fields = lines[i].split(Action.preferences.icalBuddyPropertySeparator); 110 | // extra lines are continuations of the location field 111 | if (fields.length == 1) { 112 | var l = lines[i].trim(); 113 | if (l.length > 1) 114 | event.location += ", " + l; 115 | continue; 116 | } 117 | 118 | if (event.name.length > 0) { 119 | items.push({ 120 | title: t 121 | ,subtitle: event.phone.length > 0 ? event.phone : event.location 122 | ,icon: icon(now, event) 123 | ,children: eventChildren(event) 124 | }); 125 | } 126 | 127 | event = {name:"",location:"",url:"",notes:"",start:"",end:"",phone:"",allDay:false}; 128 | for (var f=0; f 0) { 177 | items.push({ 178 | title: t 179 | ,subtitle: event.phone.length > 0 ? event.phone : event.location 180 | ,icon: icon(now, event) 181 | ,children: eventChildren(event) 182 | }); 183 | } 184 | 185 | if (LaunchBar.options.alternateKey) 186 | return items.concat(updatePreferences()); 187 | return items; 188 | 189 | } catch (exception) { 190 | LaunchBar.log('Error ' + exception); 191 | LaunchBar.alert('Error', exception); 192 | } 193 | } 194 | 195 | function icon(now, event) { 196 | if (now.isSame(event.start,'day')) { 197 | return (event.phone.length > 0 ? 'MobilePhone' : 'CalendarRule') 198 | } else { 199 | return "Calendar"; 200 | } 201 | } 202 | 203 | // 204 | // parse and filter the event data as you wish 205 | // input is the event object with properties (name, start, end, location, url, notes) 206 | // all properties are not null, default is "" 207 | // method should return same 208 | // tell caller that event is filtered out by returning null 209 | // override with your own parse method by creating plugin.js in Action.supportDir 210 | // with method pluginParse 211 | // 212 | function parse(event) { 213 | 214 | // look for a phone in the location field first 215 | event.phone = parsePhone(event.location); 216 | 217 | // if not look in the name 218 | if (event.phone.length == 0) 219 | event.phone = parsePhone(event.name); 220 | 221 | return event; 222 | } 223 | 224 | function parsePhone(str) { 225 | if (!str || str.length < 10) 226 | return ""; 227 | 228 | var match = PHONE_NBR.exec(str); 229 | if (match == null) 230 | return ""; 231 | var phone = "(" + match[1] + ") " + match[2] + "-" + match[3]; 232 | var remain = str.substring(str.indexOf(match[0]) + match[0].length); 233 | var pmatch = PHONE_CODE.exec(remain); 234 | if (pmatch == null) 235 | return phone; 236 | return phone + " ,, " + pmatch[0].replace(NOT_NBR, "") + " #"; 237 | } 238 | 239 | // remove a section of a string 240 | function strDelete(str, start, end) { 241 | } 242 | 243 | function eventChildren(event) { 244 | var items = []; 245 | 246 | if (event.phone.length > 0) { 247 | items.push({title: 'Dial ' + event.phone, subtitle: event.location, 248 | actionArgument: event.phone, action: 'dial', icon: 'MobilePhone'}); 249 | items.push({title: 'Big Display', 250 | subtitle: (event.location.length == 0 ? event.phone : event.location), 251 | actionArgument: (event.location.length == 0 ? event.phone : event.location) 252 | , action: 'big', icon: 'MobilePhone'}); 253 | } 254 | if (event.phone.length == 0 && event.location.length > 0) { 255 | items.push({title: 'Open Map', subtitle: event.location, 256 | actionArgument: event.location, action: 'maps', icon: 'com.apple.Maps'}); 257 | } 258 | if (event.url.length > 0) { 259 | items.push({title: 'Open URL', subtitle: event.url, url: event.url}); 260 | } 261 | if (event.notes.length > 0) { 262 | items.push({title: event.notes, subtitle: 'Notes'}); 263 | } 264 | return items; 265 | } 266 | 267 | function maps(str) { 268 | LaunchBar.openURL("http://maps.apple.com/?q=" + encodeURIComponent(str), "com.apple.Maps"); 269 | } 270 | 271 | function dial(str) { 272 | LaunchBar.performAction("Call With iPhone", str); 273 | big(str); 274 | if (Action.preferences.clickCall === "true") 275 | LaunchBar.executeAppleScriptFile("call.scpt"); 276 | } 277 | 278 | function big(str) { 279 | LaunchBar.displayInLargeType({string: str}); 280 | } 281 | -------------------------------------------------------------------------------- /Updates.lbaction/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.renaghan.launchbar.Updates 7 | CFBundleName 8 | Action Updates 9 | CFBundleVersion 10 | 2.11 11 | CFBundleIconFile 12 | font-awesome:fa-spinner 13 | LBDebugLogEnabled 14 | 15 | LBScripts 16 | 17 | LBDefaultScript 18 | 19 | LBScriptName 20 | updates.js 21 | LBRunInBackground 22 | 23 | LBReturnsResult 24 | 25 | 26 | 27 | LBDescription 28 | 29 | LBSummary 30 | Checks custom actions to check to see if new version is available. Actions should specify a /LBDescription/LBUpdate key in the Info.plist of their action pointing at the URL which returns the Info.plist of the latest version of the action. Additionally they may specify a /LBDescription/LBDownload key specifying the URL to download the latest .lbaction package of the action. 31 | LBAuthor 32 | Padraic Renaghan 33 | LBEmail 34 | prenagha@renaghan.com 35 | LBWebsiteURL 36 | https://renaghan.com/launchbar/action-updates/ 37 | LBTwitter 38 | @prenagha 39 | LBUpdateURL 40 | https://raw.githubusercontent.com/prenagha/launchbar/master/Updates.lbaction/Contents/Info.plist 41 | LBDownloadURL 42 | https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/prenagha/launchbar/tree/main/Updates.lbaction 43 | LBChangelog 44 | 45 | 2.11: Fix download URL 46 | 2.10: Fix for new icons @Ptujec 47 | 2.9: New icons @Ptujec 48 | 2.8: Fix LaunchBar version check 49 | 2.6: Additional error handling 50 | 2.5: Added ability to select an action and set it to skip in the preferences file 51 | 2.4: Proper version compare assuming major[.minor][.patch] format 52 | 2.3: Fix if URLs are already encoded 53 | 2.2: replace startsWith and endsWith for better javascript support 54 | 2.1: Support new and old keys 55 | 2.0: Changed LBUpdateURL, LBWebsiteURL, LBDownloadURL keys per Objective Development standard key names 56 | 1.7: Moved skipped items to own group. 57 | 1.6: Standardized error handling. 58 | 1.5: Support for LBChangelog key. 59 | 1.5: Download via explicitly selecting LB items. 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /Updates.lbaction/Contents/Resources/actionTemplate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Updates.lbaction/Contents/Resources/actionTemplate.png -------------------------------------------------------------------------------- /Updates.lbaction/Contents/Resources/alertTemplate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Updates.lbaction/Contents/Resources/alertTemplate.png -------------------------------------------------------------------------------- /Updates.lbaction/Contents/Resources/cautionTemplate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Updates.lbaction/Contents/Resources/cautionTemplate.png -------------------------------------------------------------------------------- /Updates.lbaction/Contents/Resources/checkTemplate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Updates.lbaction/Contents/Resources/checkTemplate.png -------------------------------------------------------------------------------- /Updates.lbaction/Contents/Resources/downloadTemplate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Updates.lbaction/Contents/Resources/downloadTemplate.png -------------------------------------------------------------------------------- /Updates.lbaction/Contents/Resources/logTemplate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Updates.lbaction/Contents/Resources/logTemplate.png -------------------------------------------------------------------------------- /Updates.lbaction/Contents/Resources/prefTemplate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Updates.lbaction/Contents/Resources/prefTemplate.png -------------------------------------------------------------------------------- /Updates.lbaction/Contents/Resources/skipTemplate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Updates.lbaction/Contents/Resources/skipTemplate.png -------------------------------------------------------------------------------- /Updates.lbaction/Contents/Resources/urlTemplate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prenagha/launchbar/7a717f16d841e5309b2f637ac8a4013c3e260d4d/Updates.lbaction/Contents/Resources/urlTemplate.png -------------------------------------------------------------------------------- /Updates.lbaction/Contents/Scripts/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Action Updates Action 4 | 5 | Custom actions are great, but difficult to keep up to date. Rather than all of us individually build updating mechanisms, and clutter our actions with update related UI and pinging. 6 | 7 | [Action Updates User Information and Download](https://renaghan.com/launchbar/action-updates/) 8 | 9 | This action checks each custom action a user has installed and figures out if a newer version exists. It reports on results and provides options for user 10 | * visit `/LBDescription/LBWebsiteURL` 11 | * see `/LBDescription/LBChangelog` 12 | * download `/LBDescription/LBDownloadURL` (trigger browser open URL) 13 | 14 | The trick is for all custom actions to include the `/LBDescription/LBUpdateURL` key in their `Info.plist` as a string URL reference to a remote server of the `Info.plist` of most recent version 15 | 16 | Also make sure the `CFBundleVersion` key is specified and uses standard version numbering convention `major[.minor][.patch]` where all 3 are integers. 17 | 18 | *Optional* keys to enable changelog and download link features: 19 | * `/LBDescription/LBChangelog` - string changelog text to display to user 20 | * `/LBDescription/LBDownloadURL` - string URL reference to remote server where most recent action package can be downloaded 21 | 22 | ## Preferences 23 | You may override/specify the Update URL for any action you have installed by setting a preference in this action's local preferences file 24 | `~/Library/Application Support/LaunchBar/Action Support/com.renaghan.launchbar.Updates/Preferences.plist` 25 | 26 | You may also tell this action that a custom action should be skipped by setting the LBUpdate URL to `SKIP` in this action's local preferences file. 27 | 28 | The action will search for custom actions in `~/Application Support/LaunchBar/Actions`. You may override this by setting a local `ActionsDir` preference. 29 | 30 | ## Preferences Example 31 | ``` 32 | 33 | 34 | 35 | 36 | ActionsDir 37 | /Users/jsmith/Library/Application Support/LaunchBar/Actions 38 | LBUpdateURL 39 | 40 | com.example.action1 41 | https://example.com/action1.lbaction 42 | com.example.action2 43 | SKIP 44 | 45 | 46 | 47 | ``` 48 | 49 | ## Action Info.plist Example 50 | ``` 51 | 52 | 53 | 54 | 55 | ... 56 | LBDescription 57 | 58 | ... 59 | LBUpdateURL 60 | https://raw.githubusercontent.com/jsmith/launchbar/master/Checker.lbaction/Contents/Info.plist 61 | LBDownloadURL 62 | https://download.com/lbdist/Checker.lbaction 63 | LBChangelog 64 | 65 | 1.2: Fixed bug when user option clicked on 2nd item. 66 | 67 | 68 | 69 | 70 | ``` 71 | -------------------------------------------------------------------------------- /Updates.lbaction/Contents/Scripts/updates.js: -------------------------------------------------------------------------------- 1 | 2 | var TIMEOUT = {timeout: 10.0}; 3 | var ACTION_INFO = 'https://raw.githubusercontent.com/prenagha/launchbar/main/Updates.lbaction/Contents/Info.plist'; 4 | var LB_INFO = 'http://sw-update.obdev.at/update-feeds/launchbar-6.plist'; 5 | var LB_DOWNLOAD = 'http://www.obdev.at/products/launchbar/download.html'; 6 | var ALERT_ICON = 'alertTemplate'; 7 | var CAUTION = 'cautionTemplate'; 8 | var CHECK = "checkTemplate"; 9 | var SKIP = "skipTemplate"; 10 | 11 | function setup() { 12 | if (Action.preferences.ActionsDir) 13 | return; 14 | // load up an initial preferences object with defaults if first time run 15 | Action.preferences.ActionsDir = LaunchBar.homeDirectory + "/Library/Application Support/LaunchBar/Actions"; 16 | var urls = {"com.example.action1": "https://example.com/action1.lbaction" 17 | , "com.example.action2": "SKIP" }; 18 | Action.preferences.LBUpdateURL = urls; 19 | } 20 | 21 | function run(arg) { 22 | setup(); 23 | var actionsDir = Action.preferences.ActionsDir; 24 | 25 | var items = []; 26 | var good = []; 27 | var bad = []; 28 | var skip = []; 29 | var error = []; 30 | 31 | skip.push({'title': 'Edit Preferences', icon: "prefTemplate", action: "editPref"}); 32 | 33 | loadResult(items, good, bad, skip, error, checkLaunchBar()); 34 | 35 | if (File.exists(actionsDir) 36 | && File.isDirectory(actionsDir) 37 | && File.isReadable(actionsDir)) { 38 | LaunchBar.debugLog('Actions dir ' + actionsDir); 39 | var actions = File.getDirectoryContents(actionsDir); 40 | actions.forEach(function(actionPackage) { 41 | loadResult(items, good, bad, skip, error, checkAction(actionsDir, actionPackage)); 42 | }); 43 | } else { 44 | error.push({'title': 'Actions dir not accessible' 45 | ,'subtitle':actionsDir 46 | ,'alwaysShowsSubtitle': true 47 | ,'icon':ALERT_ICON}); 48 | } 49 | 50 | if (error.length > 0) 51 | items.push({'title': 'Error', badge: ""+error.length, icon:ALERT_ICON, children: error}); 52 | 53 | items.push({'title': 'Newer versions available', badge: ""+bad.length, icon:CAUTION, children: bad}); 54 | items.push({'title': 'Up to date', badge: ""+good.length, icon:CHECK, children: good}); 55 | 56 | if (skip.length > 1) 57 | items.push({'title': 'Skipped', badge: ""+(skip.length-1), icon:SKIP, children: skip}); 58 | 59 | return items; 60 | } 61 | 62 | function editPref() { 63 | LaunchBar.openURL('file://' + encodeURI(Action.supportPath + '/Preferences.plist')); 64 | } 65 | 66 | function loadResult(items, good, bad, skip, error, item) { 67 | if (!item || !item.title) 68 | return; 69 | if (item.icon && item.icon == CHECK) { 70 | good.push(item); 71 | return; 72 | } 73 | if (item.icon && item.icon == CAUTION) { 74 | bad.push(item); 75 | return; 76 | } 77 | if (item.icon && item.icon == ALERT_ICON) { 78 | error.push(item); 79 | return; 80 | } 81 | if (item.icon && item.icon == SKIP) { 82 | skip.push(item); 83 | return; 84 | } 85 | items.push(item); 86 | } 87 | 88 | function checkAction(actionsDir, actionPackage) { 89 | LaunchBar.debugLog("Checking action " + actionPackage); 90 | var actionFile = actionsDir + "/" + actionPackage; 91 | if (!actionPackage 92 | || typeof(actionPackage) != "string" 93 | || actionPackage.indexOf(".lbaction") < 0) 94 | return; 95 | var plistFile = actionsDir + "/" + actionPackage + "/Contents/Info.plist"; 96 | if (!File.exists(plistFile)) { 97 | return {'title': actionPackage + ': Error local Info.plist does not exist ' + plistFile 98 | ,children: getActionChildren(actionFile, null, null) 99 | ,'icon':ALERT_ICON}; 100 | } 101 | if (!File.isReadable(plistFile)) { 102 | return {'title': actionPackage + ': Error local Info.plist not readable ' + plistFile 103 | ,children: getActionChildren(actionFile, null, null) 104 | ,'icon':ALERT_ICON}; 105 | } 106 | 107 | var plist; 108 | try { 109 | plist = File.readPlist(plistFile); 110 | } catch (exception) { 111 | LaunchBar.log('Error ' + actionPackage + ' reading plist -- ' + exception); 112 | return {'title': actionPackage + ': Error reading plist ' + plistFile 113 | ,children: getActionChildren(actionFile, null, null) 114 | ,'icon':ALERT_ICON}; 115 | } 116 | 117 | var updateURL = getUpdateURL(actionPackage, plist); 118 | if (updateURL == "SKIP") { 119 | return {'title': plist.CFBundleName + ': skipped' 120 | ,'icon':SKIP 121 | ,subtitle: 'Skipped via user preferences' 122 | ,children: getActionChildren(actionFile, plist, null)}; 123 | } 124 | if (!updateURL || updateURL.indexOf('http') != 0) { 125 | return {'title': plist.CFBundleName + ': updates not supported' 126 | ,subtitle: 'Missing LBUpdateURL key' 127 | ,'icon':SKIP 128 | ,children: getActionChildren(actionFile, plist, null)}; 129 | } 130 | 131 | if (updateURL.indexOf(" ") > 0) 132 | updateURL = encodeURI(updateURL); 133 | LaunchBar.debugLog(actionPackage + ' URL ' + updateURL); 134 | 135 | var result = {}; 136 | try { 137 | result = HTTP.getPlist(updateURL, TIMEOUT); 138 | } catch (exception) { 139 | LaunchBar.log('Error ' + actionPackage + ' -- ' + exception); 140 | return {'title':plist.CFBundleName + ': HTTP Error remote plist ' + exception + ' -- ' + updateURL 141 | ,'icon':ALERT_ICON 142 | ,children: getActionChildren(actionFile, plist, null)}; 143 | } 144 | 145 | if (!result) { 146 | return {'title': plist.CFBundleName + ': Error remote plist empty result -- ' + updateURL 147 | ,'icon':ALERT_ICON 148 | ,children: getActionChildren(actionFile, plist, null)}; 149 | } 150 | if (result.error) { 151 | return {'title': plist.CFBundleName + ': Error result remote plist ' + result.error 152 | + (result.response && result.response.status ? " -- " + result.response.status : "") 153 | + (result.response && result.response.localizedStatus ? " -- " + result.response.localizedStatus : "") 154 | ,'icon':ALERT_ICON 155 | ,children: getActionChildren(actionFile, plist, null)}; 156 | } 157 | if (!result.data || result.data.length < 1) { 158 | return {'title': plist.CFBundleName + ': Error remote plist empty data ' + updateURL 159 | ,'icon':ALERT_ICON 160 | ,children: getActionChildren(actionFile, plist, null)}; 161 | } 162 | 163 | if (upToDate(plist.CFBundleVersion, result.data.CFBundleVersion)) { 164 | return {'title': plist.CFBundleName + ': up to date' 165 | ,'badge' : plist.CFBundleVersion 166 | ,'icon': CHECK 167 | ,children: getActionChildren(actionFile, plist, result.data)}; 168 | } else { 169 | return {'title': plist.CFBundleName + ': Newer version available' 170 | + ' ' + plist.CFBundleVersion + ' ➔ ' + result.data.CFBundleVersion 171 | ,'icon':CAUTION 172 | ,children: getActionChildren(actionFile, plist, result.data)}; 173 | } 174 | 175 | return []; 176 | } 177 | 178 | function getActionChildren(actionFile, currPlist, plist) { 179 | var items = []; 180 | var w = getWebsite(currPlist); 181 | if (w) { 182 | items.push({'title': 'Open ' + currPlist.CFBundleName + ' web site' 183 | ,'subtitle': w 184 | ,'icon':'urlTemplate' 185 | ,'url': w}); 186 | } 187 | if (plist && plist.LBDescription && plist.LBDescription.LBChangelog && plist.LBDescription.LBChangelog.indexOf('http') == 0) { 188 | items.push({'title': 'Open version ' + plist.CFBundleVersion + ' change log' 189 | ,'subtitle':plist.LBDescription.LBChangelog 190 | ,'icon':'logTemplate' 191 | ,'url': plist.LBDescription.LBChangelog}); 192 | } 193 | if (plist && plist.LBDescription && plist.LBDescription.LBChangelog && plist.LBDescription.LBChangelog.indexOf('http') != 0) { 194 | var changes = [{title: plist.LBDescription.LBChangelog, icon:'Text.icns'}]; 195 | items.push({'title': 'Version ' + plist.CFBundleVersion + ' change log' 196 | ,'icon':'logTemplate' 197 | ,'children': changes}); 198 | } 199 | if (plist && plist.LBDescription && plist.LBDescription.LBDownloadURL) { 200 | items.push({'title': 'Download version ' + plist.CFBundleVersion 201 | ,'subtitle':plist.LBDescription.LBDownloadURL 202 | ,'icon':'downloadTemplate' 203 | ,'url': plist.LBDescription.LBDownloadURL}); 204 | } 205 | if (plist && plist.LBDescription && plist.LBDescription.LBDownload) { 206 | items.push({'title': 'Download version ' + plist.CFBundleVersion 207 | ,'subtitle':plist.LBDescription.LBDownload 208 | ,'icon':'downloadTemplate' 209 | ,'url': plist.LBDescription.LBDownload}); 210 | } 211 | if (currPlist && currPlist.LBDescription && currPlist.LBDescription.LBUpdateURL) { 212 | items.push({'title': 'Open remote Info.plist' 213 | ,'subtitle':currPlist.LBDescription.LBUpdateURL 214 | ,'icon':'urlTemplate' 215 | ,'url': currPlist.LBDescription.LBUpdateURL}); 216 | } 217 | if (currPlist && currPlist.LBDescription && currPlist.LBDescription.LBUpdate) { 218 | items.push({'title': 'Open remote Info.plist' 219 | ,'subtitle':currPlist.LBDescription.LBUpdate 220 | ,'icon':'urlTemplate' 221 | ,'url': currPlist.LBDescription.LBUpdate}); 222 | } 223 | items.push({'title': 'Installed action version ' + (currPlist ? currPlist.CFBundleVersion : "") 224 | ,'subtitle':actionFile 225 | ,'icon': 'actionTemplate' 226 | ,'path': actionFile}); 227 | 228 | if (currPlist) { 229 | if (Action.preferences 230 | && Action.preferences.LBUpdateURL 231 | && Action.preferences.LBUpdateURL[currPlist.CFBundleIdentifier] 232 | && Action.preferences.LBUpdateURL[currPlist.CFBundleIdentifier] == "SKIP") { 233 | items.push({'title': 'Resume checking this action for updates' 234 | ,'subtitle':currPlist.CFBundleIdentifier 235 | ,'icon':SKIP 236 | ,'action':'unskipper' 237 | ,'actionRunsInBackground':true 238 | ,'actionArgument': currPlist.CFBundleIdentifier}); 239 | } else { 240 | items.push({'title': 'Skip checking this action for updates' 241 | ,'subtitle':currPlist.CFBundleIdentifier 242 | ,'icon':SKIP 243 | ,'action':'skipper' 244 | ,'actionRunsInBackground':true 245 | ,'actionArgument': currPlist.CFBundleIdentifier}); 246 | } 247 | } 248 | return items; 249 | } 250 | 251 | function skipper(bundleId) { 252 | Action.preferences.LBUpdateURL[bundleId] = "SKIP"; 253 | } 254 | 255 | function unskipper(bundleId) { 256 | Action.preferences.LBUpdateURL[bundleId] = ""; 257 | } 258 | 259 | function getUpdateURL(actionPackage, plist) { 260 | if (Action.preferences 261 | && Action.preferences.LBUpdateURL 262 | && Action.preferences.LBUpdateURL[plist.CFBundleIdentifier] 263 | && Action.preferences.LBUpdateURL[plist.CFBundleIdentifier] == "SKIP") 264 | return "SKIP"; 265 | 266 | if (Action.preferences 267 | && Action.preferences.LBUpdateURL 268 | && Action.preferences.LBUpdateURL[plist.CFBundleIdentifier] 269 | && Action.preferences.LBUpdateURL[plist.CFBundleIdentifier].indexOf('http') == 0) 270 | return Action.preferences.LBUpdateURL[plist.CFBundleIdentifier]; 271 | 272 | if (plist.LBDescription 273 | && plist.LBDescription.LBUpdateURL 274 | && plist.LBDescription.LBUpdateURL.indexOf('http') == 0) 275 | return plist.LBDescription.LBUpdateURL; 276 | 277 | if (plist.LBDescription 278 | && plist.LBDescription.LBUpdate 279 | && plist.LBDescription.LBUpdate.indexOf('http') == 0) 280 | return plist.LBDescription.LBUpdate; 281 | 282 | return ""; 283 | } 284 | 285 | function getWebsite(plist) { 286 | if (!plist || !plist.LBDescription) 287 | return null; 288 | if (plist.LBDescription.LBWebsiteURL) 289 | return plist.LBDescription.LBWebsiteURL; 290 | if (plist.LBDescription.LBWebsite) 291 | return plist.LBDescription.LBWebsite; 292 | return null; 293 | } 294 | 295 | function checkLaunchBar() { 296 | try { 297 | var result = HTTP.getPlist(LB_INFO, TIMEOUT); 298 | if (!result) { 299 | return {'title':'Error checking LaunchBar version - empty result' 300 | ,'subtitle':'Empty Plist result from ' + LB_INFO 301 | ,'alwaysShowsSubtitle': true 302 | ,'icon':ALERT_ICON 303 | ,'url':LB_INFO}; 304 | } 305 | if (result.error) { 306 | return {'title':'Error checking LaunchBar version - ' + result.error 307 | ,'subtitle':result.error 308 | ,'alwaysShowsSubtitle': true 309 | ,'icon':ALERT_ICON 310 | ,'url':LB_INFO}; 311 | } 312 | if (!result.data || result.data.length < 1) { 313 | return {'title':'Error checking LaunchBar version - empty data' 314 | ,'subtitle':'Empty Plist result data from ' + LB_INFO 315 | ,'alwaysShowsSubtitle': true 316 | ,'icon':ALERT_ICON 317 | ,'url':LB_INFO}; 318 | } 319 | if (upToDate(LaunchBar.version, result.data[0].BundleVersion)) { 320 | return {'title':'LaunchBar: up to date' 321 | ,'badge': LaunchBar.shortVersion 322 | ,'icon':CHECK 323 | ,'url':LB_DOWNLOAD}; 324 | } else { 325 | return {'title':'LaunchBar: Newer version available ' 326 | + LaunchBar.shortVersion + ' ➔ ' + result.data[0].BundleShortVersionString 327 | ,'icon':CAUTION 328 | ,'url':LB_DOWNLOAD}; 329 | } 330 | } catch (exception) { 331 | LaunchBar.log('Error checkLaunchBar ' + exception); 332 | return {'title':'HTTP Error checking LaunchBar version' 333 | ,'subtitle':exception 334 | ,'alwaysShowsSubtitle': true 335 | ,'icon':ALERT_ICON 336 | ,'url':LB_INFO}; 337 | } 338 | return {}; 339 | } 340 | 341 | // compare two versions, return true if local is up to date, false otherwise 342 | // if both versions are in the form of major[.minor][.patch] then the comparison parses and compares as such 343 | // otherwise the versions are treated as strings and normal string compare is done 344 | var VPAT = /^\d+(\.\d+){0,2}$/; 345 | 346 | function upToDate(local, remote) { 347 | if (!local || !remote || local.length === 0 || remote.length === 0) 348 | return false; 349 | if (local == remote) 350 | return true; 351 | if (VPAT.test(local) && VPAT.test(remote)) { 352 | var lparts = local.split('.'); 353 | while(lparts.length < 3) 354 | lparts.push("0"); 355 | var rparts = remote.split('.'); 356 | while (rparts.length < 3) 357 | rparts.push("0"); 358 | for (var i=0; i<3; i++) { 359 | var l = parseInt(lparts[i], 10); 360 | var r = parseInt(rparts[i], 10); 361 | if (l === r) 362 | continue; 363 | return l > r; 364 | } 365 | return true; 366 | } else { 367 | return local >= remote; 368 | } 369 | } 370 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | Redirect to renaghan.com/launchbar/ 10 | 11 | 12 | If you are not redirected automatically, follow this 13 | link to renaghan.com/launchbar 14 | 15 | -------------------------------------------------------------------------------- /docs/updates.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | Redirect to renaghan.com/launchbar/action-updates 10 | 11 | 12 | If you are not redirected automatically, follow this 13 | link to renaghan.com/launchbar 14 | 15 | --------------------------------------------------------------------------------