chezmoi init

This commit is contained in:
Cy Pokhrel 2024-10-22 11:11:45 -04:00
commit 530d6d7195
No known key found for this signature in database
GPG key ID: 1200FBE36C2ADE2E
1176 changed files with 111325 additions and 0 deletions

View file

@ -0,0 +1,69 @@
# MacOS plugin
This plugin provides a few utilities to make it more enjoyable on macOS (previously named OSX).
To start using it, add the `macos` plugin to your plugins array in `~/.zshrc`:
```zsh
plugins=(... macos)
```
## Supported Terminals
- [iTerm](https://iterm.sourceforge.net/)
- [iTerm2](https://iterm2.com/)
- [Hyper](https://hyper.is/)
- [Tabby](https://tabby.sh/)
## Commands
| Command | Description |
| :------------ | :------------------------------------------------------- |
| `tab` | Open the current directory in a new tab |
| `split_tab` | Split the current terminal tab horizontally |
| `vsplit_tab` | Split the current terminal tab vertically |
| `ofd` | Open passed directories (or $PWD by default) in Finder |
| `pfd` | Return the path of the frontmost Finder window |
| `pfs` | Return the current Finder selection |
| `cdf` | `cd` to the current Finder directory |
| `pushdf` | `pushd` to the current Finder directory |
| `pxd` | Return the current Xcode project directory |
| `cdx` | `cd` to the current Xcode project directory |
| `quick-look` | Quick-Look a specified file |
| `man-preview` | Open man pages in Preview app |
| `showfiles` | Show hidden files in Finder |
| `hidefiles` | Hide the hidden files in Finder |
| `itunes` | _DEPRECATED_. Use `music` from macOS Catalina on |
| `music` | Control Apple Music. Use `music -h` for usage details |
| `spotify` | Control Spotify and search by artist, album, track… |
| `rmdsstore` | Remove .DS_Store files recursively in a directory |
| `btrestart` | Restart the Bluetooth daemon |
| `freespace` | Erases purgeable disk space with 0s on the selected disk |
## Acknowledgements
Original author: [Sorin Ionescu](https://github.com/sorin-ionescu)
This application makes use of the following third-party scripts:
[shpotify](https://github.com/hnarayanan/shpotify)
Copyright (c) 2012–2019 [Harish Narayanan](https://harishnarayanan.org/).
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,90 @@
#compdef security
local -a _1st_arguments
_1st_arguments=(
'help:Show all commands, or show usage for a command'
'list-keychains:Display or manipulate the keychain search list'
'default-keychain:Display or set the default keychain'
'login-keychain:Display or set the login keychain'
'create-keychain:Create keychains and add them to the search list'
'delete-keychain:Delete keychains and remove them from the search list'
'lock-keychain:Lock the specified keychain'
'lock-keychain:Unlock the specified keychain'
'set-keychain-settings:Set settings for a keychain'
'set-keychain-password:Set password for a keychain'
'show-keychain-info:Show the settings for keychain'
'dump-keychain:Dump the contents of one or more keychains'
'create-keypair:Create an asymmetric key pair'
'add-generic-password:Add a generic password item'
'add-internet-password:Add an internet password item'
'add-certificates:Add certificates to a keychain'
'find-generic-password:Find a generic password item'
'delete-generic-password:Delete a generic password item'
'find-internet-password:Find an internet password item'
'delete-internet-password:Delete an internet password item'
'find-certificate:Find a certificate item'
'find-identity:Find an identity certificate + private key'
'delete-certificate:Delete a certificate from a keychain'
'set-identity-preference:Set the preferred identity to use for a service'
'get-identity-preference:Get the preferred identity to use for a service'
'create-db:Create a db using the DL'
'export:Export items from a keychain'
'import:Import items into a keychain'
'cms:Encode or decode CMS messages'
'install-mds:MDS database'
'add-trusted-cert:Add trusted certificates:'
'remove-trusted-cert:Remove trusted certificates:'
'dump-trust-settings:Display contents of trust settings'
'user-trust-settings-enable:Display or manipulate user-level trust settings'
'trust-settings-export:Export trust settings'
'trust-settings-import:Import trust settings'
'verify-cert:Verify certificates:'
'authorize:Perform authorization operations'
'authorizationdb:Make changes to the authorization policy database'
'execute-with-privileges:Execute tool with privileges'
'leaks:Run /usr/bin/leaks on this process'
'error:Display a descriptive message for the given error codes:'
'create-filevaultmaster-keychain:"Create a keychain containing a key pair for FileVault recovery use'
)
_arguments '*:: :->command'
if (( CURRENT == 1 )); then
_describe -t commands "security command" _1st_arguments
return
fi
case "$words[1]" in
find-(generic|internet)-password)
_values \
'Usage: find-[internet/generic]-password [-a account] [-s server] [options...] [-g] [keychain...]' \
'-a[Match "account" string]' \
'-c[Match "creator" (four-character code)]' \
'-C[Match "type" (four-character code)]' \
'-D[Match "kind" string]' \
'-G[Match "value" string (generic attribute)]' \
'-j[Match "comment" string]' \
'-l[Match "label" string]' \
'-s[Match "service" string]' \
'-g[Display the password for the item found]' \
'-w[Display only the password on stdout]' ;;
add-(generic|internet)-password)
_values \
'Usage: add-[internet/generic]-password [-a account] [-s server] [-w password] [options...] [-A|-T appPath] [keychain]]' \
'-a[Specify account name (required)]' \
'-c[Specify item creator (optional four-character code)]' \
'-C[Specify item type (optional four-character code)]' \
'-d[Specify security domain string (optional)]' \
'-D[Specify kind (default is "Internet password")]' \
'-j[Specify comment string (optional)]' \
'-l[Specify label (if omitted, server name is used as default label)]' \
'-p[Specify path string (optional)]' \
'-P[Specify port number (optional)]' \
'-r[Specify protocol (optional four-character SecProtocolType, e.g. "http", "ftp ")]' \
'-s[Specify server name (required)]' \
'-t[Specify authentication type (as a four-character SecAuthenticationType, default is "dflt")]' \
'-w[Specify password to be added]' \
'-A[Allow any application to access this item without warning (insecure, not recommended!)]' \
'-T[Specify an application which may access this item (multiple -T options are allowed)]' \
'-U[Update item if it already exists (if omitted, the item cannot already exist) ]' \
'utils)]' ;;
esac

View file

@ -0,0 +1,298 @@
# Handle $0 according to the standard:
# https://zdharma-continuum.github.io/Zsh-100-Commits-Club/Zsh-Plugin-Standard.html
0="${${ZERO:-${0:#$ZSH_ARGZERO}}:-${(%):-%N}}"
0="${${(M)0:#/*}:-$PWD/$0}"
# Open in Finder the directories passed as arguments, or the current directory if
# no directories are passed
function ofd {
if (( ! $# )); then
open_command $PWD
else
open_command $@
fi
}
# Show/hide hidden files in the Finder
alias showfiles="defaults write com.apple.finder AppleShowAllFiles -bool true && killall Finder"
alias hidefiles="defaults write com.apple.finder AppleShowAllFiles -bool false && killall Finder"
# Bluetooth restart
function btrestart() {
sudo kextunload -b com.apple.iokit.BroadcomBluetoothHostControllerUSBTransport
sudo kextload -b com.apple.iokit.BroadcomBluetoothHostControllerUSBTransport
}
function _omz_macos_get_frontmost_app() {
osascript 2>/dev/null <<EOF
tell application "System Events"
name of first item of (every process whose frontmost is true)
end tell
EOF
}
function tab() {
# Must not have trailing semicolon, for iTerm compatibility
local command="cd \\\"$PWD\\\"; clear"
(( $# > 0 )) && command="${command}; $*"
local the_app=$(_omz_macos_get_frontmost_app)
if [[ "$the_app" == 'Terminal' ]]; then
# Discarding stdout to quash "tab N of window id XXX" output
osascript >/dev/null <<EOF
tell application "System Events"
tell process "Terminal" to keystroke "t" using command down
end tell
tell application "Terminal" to do script "${command}" in front window
EOF
elif [[ "$the_app" == 'iTerm' ]]; then
osascript <<EOF
tell application "iTerm"
set current_terminal to current terminal
tell current_terminal
launch session "Default Session"
set current_session to current session
tell current_session
write text "${command}"
end tell
end tell
end tell
EOF
elif [[ "$the_app" == 'iTerm2' ]]; then
osascript <<EOF
tell application "iTerm2"
tell current window
create tab with default profile
tell current session to write text "${command}"
end tell
end tell
EOF
elif [[ "$the_app" == 'Hyper' ]]; then
osascript >/dev/null <<EOF
tell application "System Events"
tell process "Hyper" to keystroke "t" using command down
end tell
delay 1
tell application "System Events"
keystroke "${command}"
key code 36 #(presses enter)
end tell
EOF
elif [[ "$the_app" == 'Tabby' ]]; then
osascript >/dev/null <<EOF
tell application "System Events"
tell process "Tabby" to keystroke "t" using command down
end tell
EOF
else
echo "$0: unsupported terminal app: $the_app" >&2
return 1
fi
}
function vsplit_tab() {
local command="cd \\\"$PWD\\\"; clear"
(( $# > 0 )) && command="${command}; $*"
local the_app=$(_omz_macos_get_frontmost_app)
if [[ "$the_app" == 'iTerm' ]]; then
osascript <<EOF
-- tell application "iTerm" to activate
tell application "System Events"
tell process "iTerm"
tell menu item "Split Vertically With Current Profile" of menu "Shell" of menu bar item "Shell" of menu bar 1
click
end tell
end tell
keystroke "${command} \n"
end tell
EOF
elif [[ "$the_app" == 'iTerm2' ]]; then
osascript <<EOF
tell application "iTerm2"
tell current session of first window
set newSession to (split vertically with same profile)
tell newSession
write text "${command}"
select
end tell
end tell
end tell
EOF
elif [[ "$the_app" == 'Hyper' ]]; then
osascript >/dev/null <<EOF
tell application "System Events"
tell process "Hyper"
tell menu item "Split Vertically" of menu "Shell" of menu bar 1
click
end tell
end tell
delay 1
keystroke "${command} \n"
end tell
EOF
elif [[ "$the_app" == 'Tabby' ]]; then
osascript >/dev/null <<EOF
tell application "System Events"
tell process "Tabby" to keystroke "D" using command down
end tell
EOF
else
echo "$0: unsupported terminal app: $the_app" >&2
return 1
fi
}
function split_tab() {
local command="cd \\\"$PWD\\\"; clear"
(( $# > 0 )) && command="${command}; $*"
local the_app=$(_omz_macos_get_frontmost_app)
if [[ "$the_app" == 'iTerm' ]]; then
osascript 2>/dev/null <<EOF
tell application "iTerm" to activate
tell application "System Events"
tell process "iTerm"
tell menu item "Split Horizontally With Current Profile" of menu "Shell" of menu bar item "Shell" of menu bar 1
click
end tell
end tell
keystroke "${command} \n"
end tell
EOF
elif [[ "$the_app" == 'iTerm2' ]]; then
osascript <<EOF
tell application "iTerm2"
tell current session of first window
set newSession to (split horizontally with same profile)
tell newSession
write text "${command}"
select
end tell
end tell
end tell
EOF
elif [[ "$the_app" == 'Hyper' ]]; then
osascript >/dev/null <<EOF
tell application "System Events"
tell process "Hyper"
tell menu item "Split Horizontally" of menu "Shell" of menu bar 1
click
end tell
end tell
delay 1
keystroke "${command} \n"
end tell
EOF
elif [[ "$the_app" == 'Tabby' ]]; then
osascript >/dev/null <<EOF
tell application "System Events"
tell process "Tabby" to keystroke "d" using command down
end tell
EOF
else
echo "$0: unsupported terminal app: $the_app" >&2
return 1
fi
}
function pfd() {
osascript 2>/dev/null <<EOF
tell application "Finder"
return POSIX path of (insertion location as alias)
end tell
EOF
}
function pfs() {
osascript 2>/dev/null <<EOF
set output to ""
tell application "Finder" to set the_selection to selection
set item_count to count the_selection
repeat with item_index from 1 to count the_selection
if item_index is less than item_count then set the_delimiter to "\n"
if item_index is item_count then set the_delimiter to ""
set output to output & ((item item_index of the_selection as alias)'s POSIX path) & the_delimiter
end repeat
EOF
}
function cdf() {
cd "$(pfd)"
}
function pushdf() {
pushd "$(pfd)"
}
function pxd() {
dirname $(osascript 2>/dev/null <<EOF
if application "Xcode" is running then
tell application "Xcode"
return path of active workspace document
end tell
end if
EOF
)
}
function cdx() {
cd "$(pxd)"
}
function quick-look() {
(( $# > 0 )) && qlmanage -p $* &>/dev/null &
}
function man-preview() {
[[ $# -eq 0 ]] && >&2 echo "Usage: $0 command1 [command2 ...]" && return 1
local page
for page in "${(@f)"$(man -w $@)"}"; do
command mandoc -Tpdf $page | open -f -a Preview
done
}
compdef _man man-preview
function vncviewer() {
open vnc://$@
}
# Remove .DS_Store files recursively in a directory, default .
function rmdsstore() {
find "${@:-.}" -type f -name .DS_Store -delete
}
# Erases purgeable disk space with 0s on the selected disk
function freespace(){
if [[ -z "$1" ]]; then
echo "Usage: $0 <disk>"
echo "Example: $0 /dev/disk1s1"
echo
echo "Possible disks:"
df -h | awk 'NR == 1 || /^\/dev\/disk/'
return 1
fi
echo "Cleaning purgeable files from disk: $1 ...."
diskutil secureErase freespace 0 $1
}
_freespace() {
local -a disks
disks=("${(@f)"$(df | awk '/^\/dev\/disk/{ printf $1 ":"; for (i=9; i<=NF; i++) printf $i FS; print "" }')"}")
_describe disks disks
}
compdef _freespace freespace
# Music / iTunes control function
source "${0:h:A}/music"
# Spotify control function
source "${0:h:A}/spotify"

View file

@ -0,0 +1,170 @@
#!/usr/bin/env zsh
function music itunes() {
local APP_NAME=Music sw_vers=$(sw_vers -productVersion 2>/dev/null)
autoload is-at-least
if [[ -z "$sw_vers" ]] || is-at-least 10.15 $sw_vers; then
if [[ $0 = itunes ]]; then
echo >&2 The itunes function name is deprecated. Use \'music\' instead.
return 1
fi
else
APP_NAME=iTunes
fi
local opt=$1 playlist=$2
(( $# > 0 )) && shift
case "$opt" in
launch|play|pause|stop|rewind|resume|quit)
;;
mute)
opt="set mute to true"
;;
unmute)
opt="set mute to false"
;;
next|previous)
opt="$opt track"
;;
vol)
local new_volume volume=$(osascript -e "tell application \"$APP_NAME\" to get sound volume")
if [[ $# -eq 0 ]]; then
echo "Current volume is ${volume}."
return 0
fi
case $1 in
up) new_volume=$((volume + 10 < 100 ? volume + 10 : 100)) ;;
down) new_volume=$((volume - 10 > 0 ? volume - 10 : 0)) ;;
<0-100>) new_volume=$1 ;;
*) echo "'$1' is not valid. Expected <0-100>, up or down."
return 1 ;;
esac
opt="set sound volume to ${new_volume}"
;;
playlist)
# Inspired by: https://gist.github.com/nakajijapan/ac8b45371064ae98ea7f
if [[ -n "$playlist" ]]; then
osascript 2>/dev/null <<EOF
tell application "$APP_NAME"
set new_playlist to "$playlist" as string
play playlist new_playlist
end tell
EOF
if [[ $? -eq 0 ]]; then
opt="play"
else
opt="stop"
fi
else
opt="set allPlaylists to (get name of every playlist)"
fi
;;
playing|status)
local currenttrack currentartist state=$(osascript -e "tell application \"$APP_NAME\" to player state as string")
if [[ "$state" = "playing" ]]; then
currenttrack=$(osascript -e "tell application \"$APP_NAME\" to name of current track as string")
currentartist=$(osascript -e "tell application \"$APP_NAME\" to artist of current track as string")
echo -E "Listening to ${fg[yellow]}${currenttrack}${reset_color} by ${fg[yellow]}${currentartist}${reset_color}"
else
echo "$APP_NAME is $state"
fi
return 0
;;
shuf|shuff|shuffle)
# The shuffle property of current playlist can't be changed in iTunes 12,
# so this workaround uses AppleScript to simulate user input instead.
# Defaults to toggling when no options are given.
# The toggle option depends on the shuffle button being visible in the Now playing area.
# On and off use the menu bar items.
local state=$1
if [[ -n "$state" && "$state" != (on|off|toggle) ]]; then
print "Usage: $0 shuffle [on|off|toggle]. Invalid option."
return 1
fi
case "$state" in
on|off)
# Inspired by: https://stackoverflow.com/a/14675583
osascript >/dev/null 2>&1 <<EOF
tell application "System Events" to perform action "AXPress" of (menu item "${state}" of menu "Shuffle" of menu item "Shuffle" of menu "Controls" of menu bar item "Controls" of menu bar 1 of application process "iTunes" )
EOF
return 0
;;
toggle|*)
osascript >/dev/null 2>&1 <<EOF
tell application "System Events" to perform action "AXPress" of (button 2 of process "iTunes"'s window "iTunes"'s scroll area 1)
EOF
return 0
;;
esac
;;
""|-h|--help)
echo "Usage: $0 <option>"
echo "option:"
echo "\t-h|--help\tShow this message and exit"
echo "\tlaunch|play|pause|stop|rewind|resume|quit"
echo "\tmute|unmute\tMute or unmute $APP_NAME"
echo "\tnext|previous\tPlay next or previous track"
echo "\tshuf|shuffle [on|off|toggle]\tSet shuffled playback. Default: toggle. Note: toggle doesn't support the MiniPlayer."
echo "\tvol [0-100|up|down]\tGet or set the volume. 0 to 100 sets the volume. 'up' / 'down' increases / decreases by 10 points. No argument displays current volume."
echo "\tplaying|status\tShow what song is currently playing in Music."
echo "\tplaylist [playlist name]\t Play specific playlist"
return 0
;;
*)
print "Unknown option: $opt"
return 1
;;
esac
osascript -e "tell application \"$APP_NAME\" to $opt"
}
function _music() {
local app_name
case "$words[1]" in
itunes) app_name="iTunes" ;;
music|*) app_name="Music" ;;
esac
local -a cmds subcmds
cmds=(
"launch:Launch the ${app_name} app"
"play:Play ${app_name}"
"pause:Pause ${app_name}"
"stop:Stop ${app_name}"
"rewind:Rewind ${app_name}"
"resume:Resume ${app_name}"
"quit:Quit ${app_name}"
"mute:Mute the ${app_name} app"
"unmute:Unmute the ${app_name} app"
"next:Skip to the next song"
"previous:Skip to the previous song"
"vol:Change the volume"
"playlist:Play a specific playlist"
{playing,status}":Show what song is currently playing"
{shuf,shuff,shuffle}":Set shuffle mode"
{-h,--help}":Show usage"
)
if (( CURRENT == 2 )); then
_describe 'command' cmds
elif (( CURRENT == 3 )); then
case "$words[2]" in
vol) subcmds=( 'up:Raise the volume' 'down:Lower the volume' )
_describe 'command' subcmds ;;
shuf|shuff|shuffle) subcmds=('on:Switch on shuffle mode' 'off:Switch off shuffle mode' 'toggle:Toggle shuffle mode (default)')
_describe 'command' subcmds ;;
esac
elif (( CURRENT == 4 )); then
case "$words[2]" in
playlist) subcmds=('play:Play the playlist (default)' 'stop:Stop the playlist')
_describe 'command' subcmds ;;
esac
fi
return 0
}
compdef _music music itunes

View file

@ -0,0 +1,479 @@
#!/usr/bin/env bash
function spotify() {
# Copyright (c) 2012--2023 Harish Narayanan <mail@harishnarayanan.org>
#
# Contains numerous helpful contributions from Jorge Colindres, Thomas
# Pritchard, iLan Epstein, Gabriele Bonetti, Sean Heller, Eric Martin
# and Peter Fonseca.
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
USER_CONFIG_DEFAULTS="CLIENT_ID=\"\"\nCLIENT_SECRET=\"\"";
USER_CONFIG_FILE="${HOME}/.shpotify.cfg";
if ! [[ -f "${USER_CONFIG_FILE}" ]]; then
touch "${USER_CONFIG_FILE}";
echo -e "${USER_CONFIG_DEFAULTS}" > "${USER_CONFIG_FILE}";
fi
source "${USER_CONFIG_FILE}";
# Set the percent change in volume for vol up and vol down
VOL_INCREMENT=10
showAPIHelp() {
echo;
echo "Connecting to Spotify's API:";
echo;
echo " This command line application needs to connect to Spotify's API in order to";
echo " find music by name. It is very likely you want this feature!";
echo;
echo " To get this to work, you need to sign up (or in) and create an 'Application' at:";
echo " https://developer.spotify.com/dashboard/create";
echo;
echo " Once you've created an application, find the 'Client ID' and 'Client Secret'";
echo " values, and enter them into your shpotify config file at '${USER_CONFIG_FILE}'";
echo;
echo " Be sure to quote your values and don't add any extra spaces!";
echo " When done, it should look like this (but with your own values):";
echo ' CLIENT_ID="abc01de2fghijk345lmnop"';
echo ' CLIENT_SECRET="qr6stu789vwxyz"';
}
showHelp () {
echo "Usage:";
echo;
echo " `basename $0` <command>";
echo;
echo "Commands:";
echo;
echo " play # Resumes playback where Spotify last left off.";
echo " play <song name> # Finds a song by name and plays it.";
echo " play album <album name> # Finds an album by name and plays it.";
echo " play artist <artist name> # Finds an artist by name and plays it.";
echo " play list <playlist name> # Finds a playlist by name and plays it.";
echo " play uri <uri> # Play songs from specific uri.";
echo;
echo " next # Skips to the next song in a playlist.";
echo " prev # Returns to the previous song in a playlist.";
echo " replay # Replays the current track from the beginning.";
echo " pos <time> # Jumps to a time (in secs) in the current song.";
echo " pause # Pauses (or resumes) Spotify playback.";
echo " stop # Stops playback.";
echo " quit # Stops playback and quits Spotify.";
echo;
echo " vol up # Increases the volume by 10%.";
echo " vol down # Decreases the volume by 10%.";
echo " vol <amount> # Sets the volume to an amount between 0 and 100.";
echo " vol [show] # Shows the current Spotify volume.";
echo;
echo " status # Shows the current player status.";
echo " status artist # Shows the currently playing artist.";
echo " status album # Shows the currently playing album.";
echo " status track # Shows the currently playing track.";
echo;
echo " share # Displays the current song's Spotify URL and URI."
echo " share url # Displays the current song's Spotify URL and copies it to the clipboard."
echo " share uri # Displays the current song's Spotify URI and copies it to the clipboard."
echo;
echo " toggle shuffle # Toggles shuffle playback mode.";
echo " toggle repeat # Toggles repeat playback mode.";
showAPIHelp
}
cecho(){
bold=$(tput bold);
green=$(tput setaf 2);
reset=$(tput sgr0);
echo $bold$green"$1"$reset;
}
showArtist() {
echo `osascript -e 'tell application "Spotify" to artist of current track as string'`;
}
showAlbum() {
echo `osascript -e 'tell application "Spotify" to album of current track as string'`;
}
showTrack() {
echo `osascript -e 'tell application "Spotify" to name of current track as string'`;
}
showStatus () {
state=`osascript -e 'tell application "Spotify" to player state as string'`;
cecho "Spotify is currently $state.";
duration=`osascript -e 'tell application "Spotify"
set durSec to (duration of current track / 1000) as text
set tM to (round (durSec / 60) rounding down) as text
if length of ((durSec mod 60 div 1) as text) is greater than 1 then
set tS to (durSec mod 60 div 1) as text
else
set tS to ("0" & (durSec mod 60 div 1)) as text
end if
set myTime to tM as text & ":" & tS as text
end tell
return myTime'`;
position=`osascript -e 'tell application "Spotify"
set pos to player position
set nM to (round (pos / 60) rounding down) as text
if length of ((round (pos mod 60) rounding down) as text) is greater than 1 then
set nS to (round (pos mod 60) rounding down) as text
else
set nS to ("0" & (round (pos mod 60) rounding down)) as text
end if
set nowAt to nM as text & ":" & nS as text
end tell
return nowAt'`;
echo -e $reset"Artist: $(showArtist)\nAlbum: $(showAlbum)\nTrack: $(showTrack) \nPosition: $position / $duration";
}
if [ $# = 0 ]; then
showHelp;
else
if [ ! -d /Applications/Spotify.app ] && [ ! -d $HOME/Applications/Spotify.app ]; then
echo "The Spotify application must be installed."
return 1
fi
if [ $(osascript -e 'application "Spotify" is running') = "false" ]; then
osascript -e 'tell application "Spotify" to activate' || return 1
sleep 2
fi
fi
while [ $# -gt 0 ]; do
arg=$1;
case $arg in
"play" )
if [ $# != 1 ]; then
# There are additional arguments, so find out how many
array=( $@ );
len=${#array[@]};
SPOTIFY_SEARCH_API="https://api.spotify.com/v1/search";
SPOTIFY_TOKEN_URI="https://accounts.spotify.com/api/token";
if [ -z "${CLIENT_ID}" ]; then
cecho "Invalid Client ID, please update ${USER_CONFIG_FILE}";
showAPIHelp;
return 1;
fi
if [ -z "${CLIENT_SECRET}" ]; then
cecho "Invalid Client Secret, please update ${USER_CONFIG_FILE}";
showAPIHelp;
return 1;
fi
SHPOTIFY_CREDENTIALS=$(printf "${CLIENT_ID}:${CLIENT_SECRET}" | base64 | tr -d "\n"|tr -d '\r');
SPOTIFY_PLAY_URI="";
getAccessToken() {
cecho "Connecting to Spotify's API";
SPOTIFY_TOKEN_RESPONSE_DATA=$( \
curl "${SPOTIFY_TOKEN_URI}" \
--silent \
-X "POST" \
-H "Authorization: Basic ${SHPOTIFY_CREDENTIALS}" \
-d "grant_type=client_credentials" \
)
if ! [[ "${SPOTIFY_TOKEN_RESPONSE_DATA}" =~ "access_token" ]]; then
cecho "Authorization failed, please check ${USER_CONFG_FILE}"
cecho "${SPOTIFY_TOKEN_RESPONSE_DATA}"
showAPIHelp
return 1
fi
SPOTIFY_ACCESS_TOKEN=$( \
printf "${SPOTIFY_TOKEN_RESPONSE_DATA}" \
| command grep -E -o '"access_token":".*",' \
| sed 's/"access_token"://g' \
| sed 's/"//g' \
| sed 's/,.*//g' \
)
}
searchAndPlay() {
type="$1"
Q="$2"
getAccessToken;
cecho "Searching ${type}s for: $Q";
SPOTIFY_PLAY_URI=$( \
curl -s -G $SPOTIFY_SEARCH_API \
-H "Authorization: Bearer ${SPOTIFY_ACCESS_TOKEN}" \
-H "Accept: application/json" \
--data-urlencode "q=$Q" \
-d "type=$type&limit=1&offset=0" \
| command grep -E -o "spotify:$type:[a-zA-Z0-9]+" -m 1
)
}
case $2 in
"list" )
_args=${array[@]:2:$len};
Q=$_args;
getAccessToken;
cecho "Searching playlists for: $Q";
results=$( \
curl -s -G $SPOTIFY_SEARCH_API --data-urlencode "q=$Q" -d "type=playlist&limit=10&offset=0" -H "Accept: application/json" -H "Authorization: Bearer ${SPOTIFY_ACCESS_TOKEN}" \
| command grep -E -o "spotify:playlist:[a-zA-Z0-9]+" -m 10 \
)
count=$( \
echo "$results" | command grep -c "spotify:playlist" \
)
if [ "$count" -gt 0 ]; then
random=$(( $RANDOM % $count));
SPOTIFY_PLAY_URI=$( \
echo "$results" | awk -v random="$random" '/spotify:playlist:[a-zA-Z0-9]+/{i++}i==random{print; exit}' \
)
fi;;
"album" | "artist" | "track" )
_args=${array[@]:2:$len};
searchAndPlay $2 "$_args";;
"uri" )
SPOTIFY_PLAY_URI=${array[@]:2:$len};;
* )
_args=${array[@]:1:$len};
searchAndPlay track "$_args";;
esac
if [ "$SPOTIFY_PLAY_URI" != "" ]; then
if [ "$2" = "uri" ]; then
cecho "Playing Spotify URI: $SPOTIFY_PLAY_URI";
else
cecho "Playing ($Q Search) -> Spotify URI: $SPOTIFY_PLAY_URI";
fi
osascript -e "tell application \"Spotify\" to play track \"$SPOTIFY_PLAY_URI\"";
else
cecho "No results when searching for $Q";
fi
else
# play is the only param
cecho "Playing Spotify.";
osascript -e 'tell application "Spotify" to play';
fi
break ;;
"pause" )
state=`osascript -e 'tell application "Spotify" to player state as string'`;
if [ $state = "playing" ]; then
cecho "Pausing Spotify.";
else
cecho "Playing Spotify.";
fi
osascript -e 'tell application "Spotify" to playpause';
break ;;
"stop" )
state=`osascript -e 'tell application "Spotify" to player state as string'`;
if [ $state = "playing" ]; then
cecho "Pausing Spotify.";
osascript -e 'tell application "Spotify" to playpause';
else
cecho "Spotify is already stopped."
fi
break ;;
"quit" ) cecho "Quitting Spotify.";
osascript -e 'tell application "Spotify" to quit';
break ;;
"next" ) cecho "Going to next track." ;
osascript -e 'tell application "Spotify" to next track';
showStatus;
break ;;
"prev" ) cecho "Going to previous track.";
osascript -e '
tell application "Spotify"
set player position to 0
previous track
end tell';
showStatus;
break ;;
"replay" ) cecho "Replaying current track.";
osascript -e 'tell application "Spotify" to set player position to 0'
break ;;
"vol" )
vol=`osascript -e 'tell application "Spotify" to sound volume as integer'`;
if [[ $2 = "" || $2 = "show" ]]; then
cecho "Current Spotify volume level is $vol.";
break ;
elif [ "$2" = "up" ]; then
if [ $vol -le $(( 100-$VOL_INCREMENT )) ]; then
newvol=$(( vol+$VOL_INCREMENT ));
cecho "Increasing Spotify volume to $newvol.";
else
newvol=100;
cecho "Spotify volume level is at max.";
fi
elif [ "$2" = "down" ]; then
if [ $vol -ge $(( $VOL_INCREMENT )) ]; then
newvol=$(( vol-$VOL_INCREMENT ));
cecho "Reducing Spotify volume to $newvol.";
else
newvol=0;
cecho "Spotify volume level is at min.";
fi
elif [[ $2 =~ ^[0-9]+$ ]] && [[ $2 -ge 0 && $2 -le 100 ]]; then
newvol=$2;
cecho "Setting Spotify volume level to $newvol";
else
echo "Improper use of 'vol' command"
echo "The 'vol' command should be used as follows:"
echo " vol up # Increases the volume by $VOL_INCREMENT%.";
echo " vol down # Decreases the volume by $VOL_INCREMENT%.";
echo " vol [amount] # Sets the volume to an amount between 0 and 100.";
echo " vol # Shows the current Spotify volume.";
return 1;
fi
osascript -e "tell application \"Spotify\" to set sound volume to $newvol";
break ;;
"toggle" )
if [ "$2" = "shuffle" ]; then
osascript -e 'tell application "Spotify" to set shuffling to not shuffling';
curr=`osascript -e 'tell application "Spotify" to shuffling'`;
cecho "Spotify shuffling set to $curr";
elif [ "$2" = "repeat" ]; then
osascript -e 'tell application "Spotify" to set repeating to not repeating';
curr=`osascript -e 'tell application "Spotify" to repeating'`;
cecho "Spotify repeating set to $curr";
fi
break ;;
"status" )
if [ $# != 1 ]; then
# There are additional arguments, a status subcommand
case $2 in
"artist" )
showArtist;
break ;;
"album" )
showAlbum;
break ;;
"track" )
showTrack;
break ;;
esac
else
# status is the only param
showStatus;
fi
break ;;
"info" )
info=`osascript -e 'tell application "Spotify"
set durSec to (duration of current track / 1000)
set tM to (round (durSec / 60) rounding down) as text
if length of ((durSec mod 60 div 1) as text) is greater than 1 then
set tS to (durSec mod 60 div 1) as text
else
set tS to ("0" & (durSec mod 60 div 1)) as text
end if
set myTime to tM as text & "min " & tS as text & "s"
set pos to player position
set nM to (round (pos / 60) rounding down) as text
if length of ((round (pos mod 60) rounding down) as text) is greater than 1 then
set nS to (round (pos mod 60) rounding down) as text
else
set nS to ("0" & (round (pos mod 60) rounding down)) as text
end if
set nowAt to nM as text & "min " & nS as text & "s"
set info to "" & "\nArtist: " & artist of current track
set info to info & "\nTrack: " & name of current track
set info to info & "\nAlbum Artist: " & album artist of current track
set info to info & "\nAlbum: " & album of current track
set info to info & "\nSeconds: " & durSec
set info to info & "\nSeconds played: " & pos
set info to info & "\nDuration: " & mytime
set info to info & "\nNow at: " & nowAt
set info to info & "\nPlayed Count: " & played count of current track
set info to info & "\nTrack Number: " & track number of current track
set info to info & "\nPopularity: " & popularity of current track
set info to info & "\nId: " & id of current track
set info to info & "\nSpotify URL: " & spotify url of current track
set info to info & "\nArtwork: " & artwork url of current track
set info to info & "\nPlayer: " & player state
set info to info & "\nVolume: " & sound volume
set info to info & "\nShuffle: " & shuffling
set info to info & "\nRepeating: " & repeating
end tell
return info'`
cecho "$info";
break ;;
"share" )
uri=`osascript -e 'tell application "Spotify" to spotify url of current track'`;
remove='spotify:track:'
url=${uri#$remove}
url="https://open.spotify.com/track/$url"
if [ "$2" = "" ]; then
cecho "Spotify URL: $url"
cecho "Spotify URI: $uri"
echo "To copy the URL or URI to your clipboard, use:"
echo "\`spotify share url\` or"
echo "\`spotify share uri\` respectively."
elif [ "$2" = "url" ]; then
cecho "Spotify URL: $url";
echo -n $url | pbcopy
elif [ "$2" = "uri" ]; then
cecho "Spotify URI: $uri";
echo -n $uri | pbcopy
fi
break ;;
"pos" )
cecho "Adjusting Spotify play position."
osascript -e "tell application \"Spotify\" to set player position to $2";
break ;;
"help" )
showHelp;
break ;;
* )
showHelp;
return 1;
esac
done
}