Skip to content

Instantly share code, notes, and snippets.

@criztovyl
Last active March 13, 2016 10:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save criztovyl/788d80624e6c0641d465 to your computer and use it in GitHub Desktop.
Save criztovyl/788d80624e6c0641d465 to your computer and use it in GitHub Desktop.
Sets up Gists
#!/bin/bash
# A script for initializing gists
# Copyright (C) 2015 Christoph "criztovyl" Schulz
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
if [ "$1" ] && ( [ "$1" == "help" ] || [ "$1" == "-h" ] || [ "$1" == "--help" ] ); then
echo "Usage: gist-init [GitHubUsername]"
fi
# Args: GitHubUsername
username=$1
method="ssh" # change it to http(s) if you want
[ -z "$USER" ] && USER=$USERNAME; # local user not GitHub
##
# Constants
##
config=~/.gist-init
api_accept="application/vnd.github.v3+json"
XGH="X-GitHub-OTP: required; " # Git Hub OTP Header
AUTH_DATA="{\"scopes\": [\"gist\"], \"note\": \"gist-init.sh $USER@$HOSTNAME\"}"
TRUE=0
FALSE=1
OPT_KEYRING_ID="oauth-keyring-id"
OPT_USERNAME="username"
CONTINUE_LATER='To continue later, you have to `git commit`, then you can publish the gist via `'$0'`.'
GIST_STUB_INFO="The Gist itself is already created, so it will show up in your gist.github.com/$username list as a stub."
EXIT_NO_CREATE_REPO=5
EXIT_MISSING_TOKEN=6
EXIT_BAD_CREDENTIALS=7
EXIT_TOKEN_ALREADY_OPTAINED=8
EXIT_UNKNOWN_HTTP_ERR=9
EXIT_WRONG_OTP=10
EXIT_DESCRIPTION_EMPTY=11
EXIT_SIGINT=12
EXIT_GIT_ERROR=13
EXIT_ERROR_GIST_CREATE=14
EXIT_SHELL_CANCEL=15
EXIT_COMMIT_ABORTED=16
# CTRL-C
trap "echo >&2; exit $EXIT_SIGINT" SIGINT
##
# Functions
##
# Checks wether OTP is required
# Args: filteredResponse resultVar
otp_required()
{
local filteredResponse=$1
local resultVar=$2
local _otp=`echo $filteredResponse | grep "$XGH" | wc -l`
[ $_otp -eq 1 ] && eval $resultVar=$TRUE || eval $resultVar=$FALSE
}
# Determines the OTP type (app or sms)
# Args: filteredResponse resultVar
otp_type()
{
local filteredResponse=$1
local resultVar=$2
local _type=`echo $filteredResponse | grep "$XGH" | sed "s/.\+$XGH\(\w\+\).\+/\1/"`
eval $resultVar=$_type
}
# Set an option
# Args: optName optValue
set_opt()
{
local name=$1
local value=$2
[ `grep "$name" $config | wc -l` -gt 0 ] && sed "s/$name .\+/$name $value/" $config -i || echo "$name $value" >> $config
}
# Read an option
# Args: optName
read_opt()
{
local name=$1
eval 'read_opt_callback(){
local regex="'$name' (.+)"
[[ "$2" =~ $regex ]] && echo ${BASH_REMATCH[1]}
}'
echo `exec 3<$config; mapfile -u 3 -C read_opt_callback -c 1; exec 3<&-`
}
# Construct POST data
# Args: resultVar
gist_post()
{
data='{"description": "'$description'", "public": true, "files": {"initializing.txt": {"content": "Initializing..."}}}'
eval $1="'"$data"'"
}
# Git Error quit
# Args: info
git_error()
{
info=$1
echo "Git Error." >&2
[ "$info" ] && echo $info >&2
echo $GIST_STUB_INFO >&2
exit $EXIT_GIT_ERROR
}
##
# Script
##
[ ! -f $config ] && > $config
username=`read_opt $OPT_USERNAME`
# Try to obtain OAuth token from GNOME keyring
keyring_item_id=`read_opt $OPT_KEYRING_ID`
if [ "$keyring_item_id" ]; then
token=`echo -e "import gnomekeyring as gk; print(gk.item_get_info_sync(None, $keyring_item_id).get_secret())" | python 2>/dev/null`
[ "$token" ] || echo "Token missing in keyring" >&2
else
echo "No keyring item ID in config." >&2
fi
# If can't obtain from keyring, ask GitHub and save into keyring
if [ ! "$token" ]; then
if [ -z $username ]; then
read -p "GitHub username: " username
set_opt username $username
else
echo "Username: $username" >&2
fi
read -sp "GitHub password: " password && echo
# Authenticate with GitHub
# Pick only data we need with grep and RegEx
response=`\
curl -is https://api.github.com/authorizations -X POST -u "$username:$password" \
-H "Accept: $api_accept" -H "application/json" -d "$AUTH_DATA" \
| grep 'Status: [45][0-9]\{2\}\|X-GitHub-OTP: required; .\+\|message\|"code": "already_exists"\|"token": "[[:alnum:]]"' | tr -d "\r"`
# X-GitHub-OTP contains OTP type (if required); code: already_exists appears if we already registered a token.
# Last one picks the token we require. Also remove carriage return which leads to data errors.
#Errors
[ `echo "$response" | grep 'Status: 401\|Bad credentials' | wc -l` -eq 2 ] && { echo "Wrong password." >&2; exit $EXIT_BAD_CREDENTIALS; }
[ `echo "$response" | grep 'Status: 422\|Unprocessable Entity\|"code": "already_exists"' | wc -l` -eq 2 ] && { echo "Already optained OAuth token." >&2; exit $EXIT_TOKEN_ALREADY_OPTAINED; }
#[ `echo "$response" | grep 'Status: 4[0-9]\{2\}' | wc -l` -eq 1 ] && echo -e "Unknown error.\n$response"; exit 1;
# Check whether we need to provide an one-time-password
otp_required "$response" otp
if [ "$otp" == "$TRUE" ]; then
otp_type "$response" "type" # app or sms
read -p "Enter your OTP code (check your $type): " code
# Retry request with OTP
response=`\
curl -is https://api.github.com/authorizations -X POST -u "$username:$password" \
-H "Accept: $api_accept" -H "X-GitHub-OTP: $code" -H "application/json" -d "$AUTH_DATA" \
| grep 'Status: [45][0-9]\{2\}\|X-GitHub-OTP: required; .\+\|message\|"code": "already_exists"\|"token": "[[:alnum:]]\+"' | tr -d "\r"`
[ `echo "$response" | grep 'Status: 422\|Unprocessable Entity\|"code": "already_exists"' | wc -l` -eq 2 ] && { echo "Already optained OAuth token." >&2; exit $EXIT_TOKEN_ALREADY_OPTAINED; }
[ `echo "$response" | grep 'Status: 4[0-9]\{2\}' | wc -l` -eq 1 ] && echo -e "Unknown error.\n$response" >&2; exit $EXIT_UNKNOWN_HTTP_ERR;
otp_required "$response" otp; [ "$otp" == "$TRUE" ] && { echo "Wrong OTP." >&2; exit $EXIT_WRONG_OTP; }
fi
# Extract OAuth token
regex='"token": "([[:alnum:]]+)",'
[[ "$response" =~ $regex ]] && echo "Found token" >&2 || echo "No token found!" >&2
token=${BASH_REMATCH[1]}
# Store token into GNOME keyring, and remember item id
if [ "$token" ]; then
keyring_item_id=`echo -e "import gnomekeyring as gk; print(gk.item_create_sync(None, 0, \"gist-init.sh GitHub OAuth\", {}, \"$token\", False))" | python 2>/dev/null`
set_opt $OPT_KEYRING_ID $keyring_item_id
fi
fi
# Exit if token is still missing
[ -z "$token" ] && { echo "Missing token!" >&2; exit $EXIT_MISSING_TOKEN; }
# Check if is already an repo, if not ask whether to create one
if [ ! -d .git ]; then
read -p "Initialise repository? (Y/n)"
if [[ "$REPLY" =~ ^[yY]$ ]] || [ -z "$REPLY" ]; then
git init
git add .
git status
read -p "Open a shell? (y/N)"
if [[ "$REPLY" =~ ^[yY]$ ]]; then
echo "If you're ready, quit shell with \"exit\". Cancel with non-zero exit code (i.e \"exit 1\")." >&2
if ! bash; then
echo 'You cancelled. '$CONTINUE_LATER >&2
exit $EXIT_SHELL_CANCEL
fi
fi
if ! git commit -a; then
echo "You aborted the commit, we'll stop here." >&2
echo $CONTINUE_LATER >&2
exit $EXIT_COMMIT_ABORTED
fi
else
echo "Cancelling." >&2
exit $EXIT_NO_CREATE_REPO
fi
fi
read -p "Description (one line): " description
[ "$description" ] || { echo "Description cannot be empty!" >&2; exit $EXIT_DESCRIPTION_EMPTY; }
gist_post data
# Finally create gist
response=`curl -s https://api.github.com/gists -u $user:$token -d "$data"`
# Extract Gist ID
id=`echo $response | grep -o '"id": "[[:alnum:]]\+"'`
regex='"id": "([[:alnum:]]+)"'
if ! [[ "$id" =~ $regex ]]; then
echo "Error Creating Gist: Cannot match Gist ID." >&2
echo $GIST_STUB_INFO >&2
exit $EXIT_ERROR_GIST_CREATE
fi
id=${BASH_REMATCH[1]}
[ "$method" == "https" ] && remote="https://gist.github.com/"$id".git"
# Default is ssh, so if no remote is set, use ssh. (or if ssh is set, isn't handled special)
[ -z "$remote" ] && remote="git@github.com:/"$id".git"
# Add remote
git remote add origin $remote || git_error
# force update, set upstream
git push -fu origin master || git_error
# Fetch origin for that nice "Your branch is up-to-date with 'origin/master'".
git fetch origin || git_error
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment