Grab Packt With Racket

Automatically claim Packt's free e-books with a Racket program.

Introduction

Packt Publishing has launched a new programme called "free learning offer"; each day they put an e-book for free on their website to download: one needs to login to Packt's website and click on the "claim your free book" link.
A nice fellow, wrote some Javascript code (nodejs) to be able to automatically claim a book, for example by calling the code from a cron job. He named the code "grab_packt" and has made it available on github.
To me it is not fair to have only a Javascript version! So I decided to give it a go with different languages. This time I'll present the version written with Racket.

The Code

The code is in public domain. You can get it directly from is.gd/iRF96N or from my github gist.
For this to work you need to replace YOUR-EMAIL-ADDRESS (line 24) and YOUR-PASSWORD (line 25) with reasonable values. If you wish to run this from shell/cron remember to add #! /usr/bin/env racket to the top of the file.
#lang racket
;;;
;;; Author: Bahman Movaqar 
;;;
(require net/url)
(require sxml)
(require net/uri-codec)
(require net/http-client)
(require (planet neil/html-parsing))
(require net/cookies)

;;;
(define LOGIN-ERR-MSG
  "you entered an invalid email address and password combination")
(define XPATH-LOGIN-ERR "//div[contains(@class, 'error')][contains(., '~a')]")
(define XPATH-FORM-BUILD-ID
  "//input[@type='hidden'][substring(@id, 1, 4)='form'][substring(@value, 1, 4)='form']//@value")
(define XPATH-BOOK-URL "//a[contains(@class, 'twelve-days-claim')]//@href")
(current-cookie-jar (new list-cookie-jar%))

;;;
(define (login-params form-build-id)
  (alist->form-urlencoded
   (list (cons 'email "YOUR-EMAIL-ADDRESS")
         (cons 'password "YOUR-PASSWORD")
         (cons 'op "Login")
         (cons 'form_id "packt_user_login_form")
         (cons 'form_build_id form-build-id))))

;;;
(define (url->xexp a-url)
  (html->xexp (call/input-url
               a-url
               get-pure-port
               (λ (in-port) (port->string in-port)))))

;;;
(define (req-with-cookies conn uri method data headers url)
  (http-conn-send! conn uri #:method method #:data data #:headers headers)
  (let-values ([(status-line header-list inport) (http-conn-recv! conn)])
    (extract-and-save-cookies! header-list url)
    inport))

;;;
(define (login-to-packt form-build-id fl-url)
  (let* [(in-port (req-with-cookies
                   (http-conn-open "www.packtpub.com" #:ssl? #t)
                   "/packt/offers/free-learning"
                   #"POST"
                   (login-params form-build-id)
                   `("Content-Type: application/x-www-form-urlencoded")
                   fl-url))
         (html-doc (html->xexp (port->string in-port)))
         (error-xpath (format XPATH-LOGIN-ERR LOGIN-ERR-MSG))
         (login-success? (equal? '() ((txpath error-xpath) html-doc)))]
    (if login-success? #t (error "Login failed"))))

;;;
(define (claim-the-book book-uri fl-url)
  (req-with-cookies (http-conn-open "www.packtpub.com" #:ssl? #t)
                    book-uri
                    #"GET"
                    #""
                    `(,(format "Cookie: ~a" (cookie-header fl-url)))
                    fl-url))

;;;
(let* [(fl-url (string->url "https://www.packtpub.com/packt/offers/free-learning"))
       (html-doc (url->xexp fl-url))
       (form-build-id (second (car ((txpath XPATH-FORM-BUILD-ID) html-doc))))
       (book-uri (second (car ((txpath XPATH-BOOK-URL) html-doc))))]
  (login-to-packt form-build-id fl-url)
  (claim-the-book book-uri fl-url)
  (println "Book is claimed."))

How To Run

Provided that you have already downloaded and installed Racket and have the installation's bin/ directory on your PATH, run this in a terminal to install dependencies:
$ raco pkg install sxml
$ raco pkg install net-cookies
Get the code (either by copy-pasting or directly from is.gd/iRF96N), and simply run it:
$ racket grab-packt.rkt

Comments

Popular posts from this blog

Variables in GNU Make: Simple and Recursive

Checkmate on Your Terms: A Personal Journey with Correspondence Chess

Firefox profiles: Quickly replicate your settings to any machine