Wednesday, October 04, 2017

One Less Reason to Curse YouTube -- Managing Subscriptions from the Command Line

Every time I look over at the channels I'm subscribed to in YouTube I have this internal discussion:

  1. My gosh, how did that list of subscriptions get so big? I sure wish I had a way to trim it down without going through the tedious process of manually unusbscribing.
  2. I know, I'll write a command line script to manage my YouTube subscriptions!
  3. But that will take time ... let me just muscle through and manually unsubscribe from channels I don't want to follow any longer.
  4. (Goes through the process of unsubscribing from two channels)
  5. Ugh! This is painful. I give up.
  6. And repeat...

Today, however, I finally broke the cycle! I decided to write a tool that will let me list and delete subscriptions from the command line. This wasn't really a stretch, I've written code that works with the Blogger Data API, so I figured working with the YouTube Data API wouldn't be any more difficult.

Step 1, I created a small command line utility to grant me OAuth access to my YouTube account:

#!/bin/bash

##
## Authenticate with YouTube Data API
##
USAGE="`basename $0` {auth|refresh|token} ctx"
CTX_DIR=$HOME/.youtube_auth
CLIENT_ID=703261074341-e1vpmqq8kh2v9ke0mveve7dakpbobkft.apps.googleusercontent.com 
CLIENT_SECRET=uRhAcxywxYZF2bhbx7zVqeML

ctx=default

function usage {
  echo "Usage: `basename $0` [-h] [-c context] {init|token}"
  exit
}

function age {
  modified=`stat -c %X $1`
  now=`date +%s`
  expr $now - $modified
}

function refresh {
  refresh_token=`cat $CTX_DIR/$ctx.refresh_token`
  curl -si \
       -d client_id=$CLIENT_ID \
       -d client_secret=$CLIENT_SECRET \
       -d refresh_token=$refresh_token \
       -d grant_type=refresh_token \
       https://accounts.google.com/o/oauth2/token > $CTX_DIR/$ctx.refresh
  grep access_token $CTX_DIR/$ctx.refresh | sed -e 's/.*: "//' -e 's/",//' > $CTX_DIR/$ctx.access_token
}

while getopts :hc: opt ; do
  case $opt in
    c) ctx=$OPTARG ;;
    h) usage ;;
  esac
done
shift $(($OPTIND - 1))

cmd=$1 ; shift

mkdir -p $CTX_DIR
case $cmd in
  init)
    url=`curl -gsi \
         -d scope=https://www.googleapis.com/auth/youtube \
         -d redirect_uri=urn:ietf:wg:oauth:2.0:oob \
         -d response_type=code \
         -d access_type=offline \
         -d client_id=$CLIENT_ID \
         https://accounts.google.com/o/oauth2/v2/auth | \
      grep -i Location: | \
      sed 's/[lL]ocation: //'`
    echo $url
    echo -n "Code? "
    read code
    curl -s \
         -d client_id=$CLIENT_ID \
         -d client_secret=$CLIENT_SECRET \
         -d code=$code \
         -d grant_type=authorization_code \
         -d redirect_uri=urn:ietf:wg:oauth:2.0:oob \
         https://www.googleapis.com/oauth2/v4/token > $CTX_DIR/$ctx.init
    grep access_token $CTX_DIR/$ctx.init | sed -e 's/.*: "//' -e 's/",//' > $CTX_DIR/$ctx.access_token
    grep refresh_token $CTX_DIR/$ctx.init | sed -e 's/.*: "//' -e 's/"//' > $CTX_DIR/$ctx.refresh_token
    echo "Done"
    ;;
  token)
    if [ ! -f $CTX_DIR/$ctx.access_token ] ; then
      echo "Unknown context: $ctx. Try initing first."
      exit
    fi
    age=`age $CTX_DIR/$ctx.access_token`
    if [ $age -gt 3600 ] ; then
      refresh
    fi
    cat $CTX_DIR/$ctx.access_token
    ;;
  *)
    usage
esac

Surprisingly, I needed to use different API end points than I used for other curl based Google OAuth apps. I needed to generate the authentication code at https://accounts.google.com/o/oauth2/v2/auth and generate the auth token at https://www.googleapis.com/oauth2/v4/token.

Once I had an authentication token, life was good. I used the usual suspects to power this script. Mainly, curl to make the appropriate web requests and jq to format the resulting JSON in an intelligent way. You'll notice from the script below that if you pass -v to the script, you get the complete JSON output. Man those tools are awesome; if you don't know them well you're missing out.

Here's a sample run of my shiny new script:

$ youtube_tools  -a subscriptions|grep -i megan
ffVDX-wHSD6NaM3-kGgVug8jUOQIbwozBmWubSPnjtU:Megan Davies

# Nothing personal Megan, but we're breaking up:
$ youtube_tools -a subscription-delete \
 -i `youtube_tools  -a subscriptions |grep Megan | cut -d ':' -f 1`

Now that's how you manage YouTube subscriptions!

I'm not quite sure what else I'm going to do with this tool, but given how much I use YouTube for, I won't be surprised if I add to it.

Here's the code for the script, enjoy!

#!/bin/bash

##
## command line tools for working with YouTube.
## See: https://developers.google.com/youtube/v3/docs/
##
API_BASE=https://www.googleapis.com/youtube/v3
AUTH_TOKEN=`youtube_auth token`

if [ -z "$AUTH_TOKEN" ] ; then
  echo "`basename $0`: authentication not setup. Run: 'youtube_auth init'"
  exit 1
fi

function usage {
  echo "Usage: `basename $0` -a {subscriptions|subscription-delete} [-i ID] -v"
  exit
}

function filter {
  if [ -z "$VERBOSE" ] ; then
    jq "$@"
  else
    cat
  fi
}

while getopts ":a:i:v" opt ; do
  case $opt in
    a) ACTION=$OPTARG ;;
    v) VERBOSE=yes    ;;
    i) ID=$OPTARG     ;;
    \?) usage         ;;
  esac
done

function invoke {
  curl -s -H "Authorization: Bearer $AUTH_TOKEN" "$@"
}

case $ACTION in
  subscriptions)
    invoke -G $API_BASE/subscriptions \
           -d mine=true \
           -d part=snippet \
           -d maxResults=50 | 
      filter -r ' .items[] |  .id + ":" + .snippet.title'
    ;;
  subscription-delete)
    if [ -z "$ID" ] ; then
      echo "Deleting a subscription requires an ID (-i) value"
      usage
    fi
    invoke -X DELETE "$API_BASE/subscriptions?id=$ID"
    ;;
  *)
    usage
    ;;
esac

No comments:

Post a Comment