Showing posts with label blogger. Show all posts
Showing posts with label blogger. Show all posts

Saturday, 14 March 2020

Blogger Go Cli

Blogger Go Cli

Blogger Go Cli

2020-03-14T21:16:00Z



Introduction

We will be creating a commandLine utility in Go to upload a post in Google blogger.

Account Preparation

  • You need to have a google account (gmail) (Paid account NOT required).
  • Once you have a google account, Login to https://console.developers.google.com/apis/credentials and create a new Project here.
  • Click on Dashboard on left hand column, and click on `+ ENABLE APIS AND SERVICES`.
    • Type in blogger in search bar, and select Blogger API v3 and then click ENABLE.`
  • Click on OAuth consent screen on left hand column, and select User Type to External and click CREATE.
    • On next screen type in Application Name as Blogger CLI (It can be any string).
    • Click on Add Scope and select both the Blogger API v3 options and click ADD and click Save.
  • Click on Credentials on the left hand side and you will be presented with following two options of creating credentials for Blogger API.
    • API Keys
    • OAuth 2.0 Client IDs
    • Service Accounts
    • Click on `+ CREATE CREDENTAILSon the top and selectOAuth Client ID` from drop down menu.
    • Select Application Type to other.
    • Type in Name to CLI Utility (it can be any string) and click create.
    • You will see an entry with name CLI Utility under OAuth 2.0 Client IDs.
    • Download the credential files by clicking on a down arrow button.
    • This will be a json file and we need it to authenticate it with google OAuth2.0 services.
  • Login to blogger site https://www.blogger.com
    • Sign-in to blogger.
    • Enter a display name you like.
    • Create a NEW BLOG and give it a name you like in Ttile
    • Type in address. It has to be a unique e.g somethingrandom23455.blogspot.com and click create blog
    • On next screen, look at the address bar of browser, it will be like https://www.blogger.com/blogger.g?blogID=3342324243435#allposts
    • Note down blog ID (a string after blogID=, excluding #allposts) in above URL. We need this ID to put in Go script.

Go development environment creation

I am using following Go version.

$ go version
go version go1.13.8 darwin/amd64

Setup following Go development directory structure.

mkdir -p BloggerCli/go-cli
touch BloggerCli/go-cli/createAndActivateGoWorkSpace.sh

Populate BloggerCli/go-cli/createAndActivateGoWorkSpace.sh with following contents

#! /bin/bash
# current directory is workspace
WORKSPACE=$(pwd)
cd "$WORKSPACE"
mkdir -p src bin pkg
export GOPATH=$WORKSPACE
export GOBIN=$WORKSPACE/bin

Create directory structure

cd BloggerCli/go-cli
source ./createAndActivateGoWorkSpace.sh
mkdir src/blogger

Install required libraries.

go get -v google.golang.org/api/blogger/v3
go get -v golang.org/x/oauth2/google

Copy credential file.

Copy above downloaded credential json file in BloggerCli/go-cli/src/blogger folder and rename it to OAuth2.0_secret.json.

Note: You can rename it any name

Create Go file BloggerCli/go-cli/src/blogger/bloggerCli.go file with following contents.

package main

/*
https://pkg.go.dev/google.golang.org/api/blogger/v3?tab=doc
https://godoc.org/google.golang.org/api/option
https://pkg.go.dev/golang.org/x/oauth2/google?tab=doc
https://pkg.go.dev/golang.org/x/oauth2?tab=doc#Config.AuthCodeURL
*/

import (
    "context"
    "flag"
    "fmt"
    "io/ioutil"
    "log"
    "os"
    "strings"

    "golang.org/x/oauth2"
    "golang.org/x/oauth2/google"

    "google.golang.org/api/blogger/v3"
    "google.golang.org/api/option"
)

func checkErrorAndExit(err error) {
    if err != nil {
        log.Fatal(err)
    }
}

func printNoOfPosts(postsService *blogger.PostsService, BlogID string) {
    postListCall := postsService.List(BlogID).MaxResults(500)
    postList, err := postListCall.Do()
    checkErrorAndExit(err)
    fmt.Printf("Total posts in this blog: %d\n", len(postList.Items))
}

func getBloggerClient() *blogger.Service {
    ctx := context.Background()

    contents, err := ioutil.ReadFile("OAuth2.0_secret.json")
    checkErrorAndExit(err)

    conf, err := google.ConfigFromJSON(contents, blogger.BloggerScope)
    checkErrorAndExit(err)

    url := conf.AuthCodeURL("state")
    fmt.Println("Visit the following URL for the auth dialog:")
    fmt.Printf("\n%v\n", url)

    var code string
    fmt.Printf("\nEnter the code obtained from above here: ")
    fmt.Scanln(&code)

    token, err := conf.Exchange(oauth2.NoContext, code)
    checkErrorAndExit(err)

    bloggerService, err := blogger.NewService(ctx, option.WithTokenSource(conf.TokenSource(ctx, token)))
    checkErrorAndExit(err)

    return bloggerService
}

func checkMandatoryArguments(args []*string) {

    allArgsExists := true
    for _, val := range args {
        if len(*val) == 0 {
            allArgsExists = allArgsExists && false
        } else {
            allArgsExists = allArgsExists && true
        }
    }

    if !allArgsExists {
        flag.PrintDefaults()
        os.Exit(2)
    }
}

func createNewPost(title *string, labels *string, fileToUpload *string) *blogger.Post {

    labelsSlice := strings.Split(*labels, ",")
    contents, err := ioutil.ReadFile(*fileToUpload)
    checkErrorAndExit(err)

    newPost := blogger.Post{}

    if len(*labels) == 0 {
        newPost = blogger.Post{
            Content: string(contents),
            Title:   *title,
        }
    } else {
        newPost = blogger.Post{
            Content: string(contents),
            Labels:  labelsSlice,
            Title:   *title,
        }
    }
    return &newPost
}

func insertAPost(title *string, labels *string, fileToUpload *string, BlogID string, postsService *blogger.PostsService) {
    fmt.Println("============= Inserting a new Post ===============")

    newPost := createNewPost(title, labels, fileToUpload)

    postInsertCall := postsService.Insert(BlogID, newPost)
    post, err := postInsertCall.Do()
    checkErrorAndExit(err)
    fmt.Println("A new post added to Blog with following details")
    fmt.Printf("Title: %s\nPostID: %s\n", post.Title, post.Id)
    fmt.Println("=================================================")
    printNoOfPosts(postsService, BlogID)
}

func updateAPost(title *string, labels *string, fileToUpload *string, BlogID string, postsService *blogger.PostsService, postID string) {
    fmt.Println("============= updating a Post ===============")

    // Search post from Title if postID not specified.
    if len(postID) == 0 {
        postsSearchCall := postsService.Search(BlogID, *title)
        postsList, err := postsSearchCall.Do()
        checkErrorAndExit(err)
        if len(postsList.Items) > 1 {
            fmt.Printf("Following multiple posts found for title = %s\n", *title)
            for _, post := range postsList.Items {
                fmt.Println(post.Title)
            }
            log.Fatal("\n\nERROR: Not updating post. Title must identify a single post only. Try specifying PostID")
        } else {
            postID = postsList.Items[0].Id
        }
    }

    newPost := createNewPost(title, labels, fileToUpload)
    postsPatchCall := postsService.Patch(BlogID, postID, newPost)
    post, err := postsPatchCall.Do()
    checkErrorAndExit(err)
    fmt.Println("Following post has been updated in Blog with following details")
    fmt.Printf("Title: %s\nPostID: %s\n", post.Title, post.Id)
    fmt.Println("=================================================")
    printNoOfPosts(postsService, BlogID)
}

func main() {

    fileToUpload := flag.String("f", "", "html file to upload (mandatory)")
    title := flag.String("t", "", "Post title (mandatory)")
    blogid := flag.String("b", "", "Blod ID (mandatory)")
    labels := flag.String("l", "", "comma separated list of labels (optional)")
    modify := flag.Bool("m", false, "Modify a post contents (optional: modifies post in mentioned in -t )")
    postID := flag.String("p", "", "post ID (optional: To be specified when updating a post)")
    flag.Parse()

    if flag.NFlag() == 0 {
        flag.PrintDefaults()
        os.Exit(1)
    }

    mandatoryArgs := []*string{fileToUpload, title, blogid}
    checkMandatoryArguments(mandatoryArgs)

    BlogID := *blogid
    PostID := *postID

    bloggerService := getBloggerClient()
    postsService := bloggerService.Posts

    printNoOfPosts(postsService, BlogID)

    if !*modify {
        insertAPost(title, labels, fileToUpload, BlogID, postsService)
    } else {
        updateAPost(title, labels, fileToUpload, BlogID, postsService, PostID)
    }
}

Create a test file to be uploaded.

echo 'This is a first post' > sometext.txt

Note: You can create an HTML file as well.

Run the script

Create a New Post

$ cd BloggerCli/go-cli/src/blogger

$ go run bloggerCli.go -b 232343232322 -f sometext.txt -t "A new Post" -l "label1,label2"
Visit the URL for the auth dialog: https://accounts.google.com/o/oauth2/auth?client_id=99999999-ccc55566666iiioo999ddd.apps.googleusercontent.com&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fblogger&state=state
Enter the code obtained from above here: 9/rf7hM-sds98JnHgsdK90OiYhsss790NHdsaDs
Total posts in this blog: 51
============= Inserting a new Post ===============
A new post added to Blog with following details
Title: A new Post
PostID: 2344555555555999
=================================================
Total posts in this blog: 52

Update a Post

$ cd BloggerCli/go-cli/src/blogger

$ go run bloggerCli.go -b 232343232322 -f sometext.txt -t "Multiple Vms Vagrantfile" -m
Visit the URL for the auth dialog: https://accounts.google.com/o/oauth2/auth?client_id=99999999-ccc55566666iiioo999ddd.apps.googleusercontent.com&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fblogger&state=state
Enter the code obtained from above here: 9/rf7hM-sds98JnHgsdK90OiYhsss790NHdsaDs
Total posts in this blog: 51
============= updating a Post ===============
Following post has been updated in Blog with following details
Title: Multiple Vms Vagrantfile
PostID: 334556669992323
=================================================
Total posts in this blog: 51

Note: You need to open the URL mentioned in the above output in a browser and authorize the request. This will return a code that need to enter above.

References used

  • https://pkg.go.dev/google.golang.org/api/blogger/v3?tab=doc
  • https://godoc.org/google.golang.org/api/option
  • https://pkg.go.dev/golang.org/x/oauth2/google?tab=doc
  • https://pkg.go.dev/golang.org/x/oauth2?tab=doc#Config.AuthCodeURL

Tuesday, 10 March 2020

Blogger python cli

Blogger python cli

Blogger python cli

2020-03-09T18:46:41Z



Introduction

We will be creating a commandLine utility in python to upload a post in Google blogger.

Account Preparation

  • You need to have a google account (gmail) (Paid account NOT required).
  • Once you have a google account, Login to https://console.developers.google.com/apis/credentials and create a new Project here.
  • Click on Dashboard on left hand column, and click on `+ ENABLE APIS AND SERVICES`.
    • Type in blogger in search bar, and select Blogger API v3 and then click ENABLE.`
  • Click on OAuth consent screen on left hand column, and select User Type to External and click CREATE.
    • On next screen type in Application Name as Blogger CLI (It can be any string).
    • Click on Add Scope and select both the Blogger API v3 options and click ADD and click Save.
  • Click on Credentials on the left hand side and you will be presented with following two options of creating credentials for Blogger API.
    • API Keys
    • OAuth 2.0 Client IDs
    • Service Accounts
    • Click on `+ CREATE CREDENTAILSon the top and selectOAuth Client ID` from drop down menu.
    • Select Application Type to other.
    • Type in Name to Python Blogger CLI (it can be any string) and click create.
    • You will see an entry with name Python Blogger CLI under OAuth 2.0 Client IDs.
    • Download the credential files by clicking on a down arrow button.
    • This will be a json file and we need it to authenticate it with google OAuth2.0 services.
  • Login to blogger site https://www.blogger.com
    • Sign-in to blogger.
    • Enter a display name you like.
    • Create a NEW BLOG and give it a name you like in Ttile
    • Type in address. It has to be a unique e.g somethingrandom23455.blogspot.com and click create blog
    • On next screen, look at the address bar of browser, it will be like https://www.blogger.com/blogger.g?blogID=3342324243435#allposts
    • Note down blog ID (a string after blogID=, excluding #allposts) in above URL. We need this ID to put in python script.

Python environment creation

I am using following python version.

$ python --version
Python 3.7.0

We will be creating a python virtual environment.

mkdir BloggerCli
cd BloggerCli
python -m venv python-2.7.0
source python-2.7.0/bin/activate

Install required libraries.

pip install google-api-python-client
pip install --upgrade google-auth-oauthlib

Copy credential script.

mkdir python-cli

Copy above downloaded credential json file in python-cli folder and rename it to OAuth2.0_secret.json.

Note: You can rename it any name.

Create Python script bloggerCli.py

cd python-cli

Create following python script in python-cli folder.

#! /Users/<username>/BloggerCli/python-2.7.0/bin/python

# https://github.com/googleapis/google-api-python-client/blob/master/docs/README.md
# https://github.com/googleapis/google-api-python-client/blob/master/docs/oauth-server.md
# https://github.com/googleapis/google-api-python-client/blob/master/docs/oauth-installed.md
# http://googleapis.github.io/google-api-python-client/docs/dyn/blogger_v3.html
# http://googleapis.github.io/google-api-python-client/docs/dyn/blogger_v3.posts.html
# https://developers.google.com/identity/protocols/googlescopes
# https://console.developers.google.com/apis/credentials
# pip install --upgrade google-auth-oauthlib

# Note: This script does NOT check the duplicate title of posts while inserting the post in Blog.

from google.oauth2 import service_account
import googleapiclient.discovery
from google_auth_oauthlib.flow import InstalledAppFlow
import os
import sys
import argparse


usage = f"{os.path.basename(__file__)} -f <html file> -t <post title> -l <label_string1> -l <label_string2> -l .. -l .."

parser = argparse.ArgumentParser(prog=os.path.basename(__file__), usage=usage, description='Upload a post to Blogger')
parser.add_argument('-f', '--uploadfile', action='store', dest='fileToPost', help='html file to post', required=True)
parser.add_argument('-l', '--labels', action='append', dest='labels', default=[], help='-l <label1> -l <label2>')
parser.add_argument('-t', '--title', action='store', dest='title', help='-t <Post Title string>', required=True)
parser.add_argument('-b', '--blogid', action='store', dest='blogid', help='-b <blogid string>', required=True)
arguments = parser.parse_args()

if len(sys.argv) == 1:
    parser.print_help()
    parser.error("You must specify command line flags as mentioned above.")


FILE_TO_POST = arguments.fileToPost
LABELS_FOR_POST = arguments.labels
POST_TITLE = arguments.title
BLOG_ID = arguments.blogid

SCOPES = ['https://www.googleapis.com/auth/blogger']
SERVICE_ACCOUNT_FILE = 'blogger-api-credentials.json'
OAUTH2_ACCOUNT_FILE = 'OAuth2.0_secret.json'
FLOW_SERVER_PORT = 9090
API_SERVICE_NAME = 'blogger'
API_SERVICE_VERSION = 'v3'

# create a API client from service account
def create_client_from_serviceAccount():
    credentials = service_account.Credentials.from_service_account_file( SERVICE_ACCOUNT_FILE, scopes=SCOPES)

    blogger = googleapiclient.discovery.build(API_SERVICE_NAME, API_SERVICE_VERSION, credentials=credentials)
    return blogger

# create a client from an AOUTH2.0 account.
def create_client_from_outhAccount():
    flow = InstalledAppFlow.from_client_secrets_file(OAUTH2_ACCOUNT_FILE, scopes=SCOPES)
    credentials = flow.run_local_server(host='localhost', port=FLOW_SERVER_PORT, authorization_prompt_message='Please visit this URL: {url}', 
                        success_message='The auth flow is complete; you may close this window.', open_browser=True)
    blogger = googleapiclient.discovery.build(API_SERVICE_NAME, API_SERVICE_VERSION, credentials=credentials)
    return blogger

def print_number_of_posts(postList):
    if not 'items' in postList:
        print("No of posts in blog = 0")
    else:    
        print("No of posts in blog = {}".format(len(postList['items'])))

# From a service account you cannot create a new post in blogger. It might need some special permissions that I am not aware of.
# However, Service account can read posts and fetch them.
# blogger = create_client_from_serviceAccount()

# Below will open a browser window to authenticate yourself.
blogger = create_client_from_outhAccount()

postList = blogger.posts().list(blogId=BLOG_ID).execute()
print_number_of_posts(postList)

print("====== Inserting a new post =======")

with open(FILE_TO_POST) as f:
    contents = f.read()

blogBody = {
    "content": contents,
    "kind": "blogger#post",
    "title": POST_TITLE,
    "labels": LABELS_FOR_POST,
        }

result = blogger.posts().insert(blogId=BLOG_ID, body=blogBody).execute()
postList = blogger.posts().list(blogId=BLOG_ID).execute()
print_number_of_posts(postList)

Create a test file to be uploaded.

echo 'This is a first post' > sometext.txt

Note: You can create an HTML file as well.

Run the script.

$ ./bloggerCli.py -f sometext.txt -t "My First Post" -b 34456769666334 -l label1 -l label2 -l label3
Please visit this URL: https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=26652434353-e9u77isb2sdsd4343sdsd343434.apps.googleusercontent.com&redirect_uri=http%3A%2F%2Flocalhost%3A9090%2F&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fblogger&state=BkaXVyiW15sdsdsdADH7sadsH8hS&access_type=offline
No of posts in blog = 0
====== Inserting a new post =======
No of posts in blog = 1

Note-1: While running above script, this will open a browser and wil ask you to login to your google account.

Note-2: You may need to change the FLOW_SERVER_PORT in above script.

References used:

  • https://github.com/googleapis/google-api-python-client/blob/master/docs/README.md
  • https://github.com/googleapis/google-api-python-client/blob/master/docs/oauth-server.md
  • https://github.com/googleapis/google-api-python-client/blob/master/docs/oauth-installed.md
  • http://googleapis.github.io/google-api-python-client/docs/dyn/blogger_v3.html
  • http://googleapis.github.io/google-api-python-client/docs/dyn/blogger_v3.posts.html
  • https://developers.google.com/identity/protocols/googlescopes
  • https://console.developers.google.com/apis/credentials

Convert markup to blogger compatible html

Convert markup to blogger compatible html

Convert markup to blogger compatible html

2020-03-10T11:12:00Z



Introduction

We will be converting a markup file to a blogger compatible html.

Install pandoc and GitHUB html5 Template

Install pandoc using URL https://pandoc.org/installing.html . Create a workspace and clone following git repos in that workspace area.

mkdir $HOME/panDocs
cd $HOME/panDocs
git clone https://github.com/tajmone/pandoc-goodies.git
cd pandoc-goodies/templates/html5/github/src

Modify above GitHUB html5 template source code for blogger.

Make the padding and max-width changes in _github-markdown.scss file as follows:

$ git diff templates/html5/github/src/_github-markdown.scss
diff --git a/templates/html5/github/src/_github-markdown.scss b/templates/html5/github/src/_github-markdown.scss
index 479b5fc..54c9abb 100644
--- a/templates/html5/github/src/_github-markdown.scss
+++ b/templates/html5/github/src/_github-markdown.scss
@@ -48,9 +48,9 @@
   word-wrap: break-word;
   box-sizing: border-box;
   min-width: 200px;
-  max-width: 980px;
+  max-width: 100%;
   margin: 0 auto;
-  padding: 45px;
+  padding: 45px 5px;

   a {
     color: #0366d6;

Change the css source file name in src/css-injector.js as follows:

$ git diff css-injector.js
diff --git a/templates/html5/github/src/css-injector.js b/templates/html5/github/src/css-injector.js
index 861494d..14b5597 100644
--- a/templates/html5/github/src/css-injector.js
+++ b/templates/html5/github/src/css-injector.js
@@ -16,7 +16,7 @@ This script will:
 */

 templateFile  = "GitHub_source.html5" // Template with CSS-injection placeholder
-cssFile       = "GitHub.min.css"      // CSS source to inject into placeholder
+cssFile       = "GitHub.css"      // CSS source to inject into placeholder
 outFile       = "../GitHub.html5"     // Final template output file
 placeHolder   = "{{CSS-INJECT}}"      // Placeholder string for CSS-injection

Compile scss file.

Note: You need to install node on your system in order to compile.

cd pandoc-goodies/templates/html5/github/src
npm init -y
npm install node-sass
./node_modules/node-sass/bin/node-sass --source-map true --output-style=expanded --indent-type space GitHub.scss > GitHub.css
node css-injector.js

Note: You can clone pre-compiled code from https://github.com/spareslant/markup_to_GitHUB_HTML5_and_PDF.git as well.

  • pandoc-goodies is to generate html files.

Create a sample markup file.

Create a markup file in $HOME/panDocs say markupContents.md with following contents.

    # A top Level Heading
    This is a heading.

    ## Second top level heading
    Another heading

    ### Following is a Bash code.
    ```bash
    $ ls -l $HOME
    ```
    ### Following is just a text file.
    ```
    This is a plain text
    or it can be any kind of text.
    ```

    ### Some highligts in `heading`.
    ```
    This Text is colorized.
    This Text is not colorized.
    ```

Create a conversion script.

Create a shell script say generate_html_for_blogger.sh with following contents in $HOME/panDocs folder.

#! /bin/bash

[[ $1 == "" ]] && echo "pass a html filename as first argument" && exit 2

#output_file="output.html"
input_file="$1"
output_file="$(basename $input_file .md).html"
output_file_code_tag_removed="$(basename $input_file .md).code_tag_removed.html"

pandoc "$input_file" -f markdown --template pandoc-goodies/templates/html5/github/GitHub.html5 --self-contained --toc --toc-depth=6 -o $output_file

cat "$output_file" | perl -wpl -e 's!\<code\>(.+?)\</code\>!\<span class=\"code"\>$1\</span\>!g' > "$output_file_code_tag_removed"
echo "file geneated: $output_file_code_tag_removed"

Run the script.

$ ./generate_html_for_blogger.sh markupContent.md
[WARNING] This document format requires a nonempty <title> element.
  Defaulting to 'markupContent' as the title.
  To specify a title, use 'title' in metadata or --metadata title="...".
file geneated: markupContent.code_tag_removed.html

html content for blogger is generated in markupContent.code_tag_removed.html file. You can copy/paste the contents in blogger post without any modification.

Note: Blogger does NOT recognize <code> tag. Therefore we had to replace <code> tag with <span>. This also gives us the flexiblity to add custom colors in final HTML. You cannot colorize text in markup though.

Colorizing text for blogger in Markup.

In order to colorize the texts in blogger, add CSS styles as following in the markup file. We also added <pre> tag to colorize the text we want. Following style will also colorize text in single backquotes in markup.

    <style>
    /* To highlight text in Green in pre tag */
    .hl {color: #008A00;}
    /* To highlight text in Bold Green in pre tag */
    .hlb {color: #008A00; font-weight: bold;}
    /* To highlight text in Bold Red in pre tag */
    .hlbr {color:#e90001; font-weight: bold;}
    /* <code> tag does not work in blogger. Use following class with span tag */
    .code {
        color:#7e168d; 
        background: #f0f0f0; 
        padding: 0.1em 0.4em;
        font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace;
    }
    </style> 
    # A top Level Heading
    This is a heading.

    ## Second top level heading
    Another heading

    ### Following is a Bash code.
    ```bash
    $ ls -l $HOME
    ```
    ### Following is just a text file.
    ```
    This is a plain text
    or it can be any kind of text.
    ```

    ### Some highligts in `heading`.
    <pre>
    This Text is not colorized.
    <span class="hlb">This Text is colorized.</span>
    </pre>

Run the script.

$ ./generate_html_for_blogger.sh markupContent.md
[WARNING] This document format requires a nonempty <title> element.
  Defaulting to 'markupContent' as the title.
  To specify a title, use 'title' in metadata or --metadata title="...".
file geneated: markupContent.code_tag_removed.html

markupContent.code_tag_removed.html will have contents with CSS styles applied.