You are here: Home / Blogs / Grab Packt With Racket

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 publicly 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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#lang racket
;;;
;;; Author: Bahman Movaqar <Bahman AT BahmanM.com>
;;;
(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
Related content
Grab Packt With Groovy
Colin says:
Jun 23, 2015 06:08 AM

So is it on github too?
Do you have instructions for me to run this? Assuming I install racket.

Bahman Movaqar says:
Jun 23, 2015 08:41 AM

Colin, I have updated the post with simple steps to run it.
And yes, it is available as a github gist (https://gist.github.com/bahmanm/c890592935f9ae1f4a16)

Colin says:
Jun 23, 2015 02:59 PM

Nice one.
When I viewed the page just now I saw the "how to run" section I thought I must have been asleep this morning to miss it... then I saw your update! :)
Thanks for the howto

Colin

Bahman Movaqar says:
Jun 23, 2015 03:11 PM

Glad you found it useful. Stay tuned for the Groovy version to come any minute now...that may be more suited to your liking I guess :-)

Jony says:
Mar 24, 2016 10:22 AM

I have found this useful, python version: https://github.com/igbt6/Packt-Publishing-Free-Learning

josh says:
Aug 17, 2017 12:15 PM

I get an error when I try the code something about a contract violation in this area of code. (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."))

Add comment

You can add a comment by filling out the form below. Plain text formatting.

Question: What is 8 + 3 - 1 ?
Your answer: