Download Liked Posts from Tumblr.com

After using tumblr.com for years, a lot of posts have been liked. It would be easier to look up if the contents of these posts are stored to local. Fortunately, tumblr has provided a set of APIs to make this easy, and even an API console to play with these APIs. The API client is also provided in:

which are all open source.

Create application and install dependent modules

Here JavaScript would a nice option, since Microsoft has released a Node.js Tools for Visual Studio 2015. So, first, create a Node.js application:

image

Then, install npm modules. Instead of installing the tumblr module directly, here tumblr-auto-auth is installed. Because tumblr APIs uses OAuth 1.0a, and this tumblr-auto-auth module handles the authentication/authorization. It also installs the tumblr module as its dependency.

image

Also, Q can be installed for promise-based asynchronous programming.

Create utility functions

The plan is to download the pictures/video in each post, so a download utility function is needed. Node.js does not have such a built-in API, but it is easy to create one in a common module (common.js):

var http = require("http"),
    fs = require("fs"),
    Q = require("q"),

    download = function (url, path) {
        var deferred = Q.defer(),
            file = fs.createWriteStream(path);
        console.log("Downloading " + url + "to " + path);
        http.get(url, function (response) {
            response.pipe(file);
            file.on("finish", function () {
                file.close(deferred.resolve);
            });
        }).on("error", function (error) {
            fs.unlink(path);
            deferred.reject(error);
        });
        return deferred.promise;
    },

It simply downloads the specified URL to the specified file system path. And Q is used to convert the callback style asynchronous programming model into promise style. This is very useful to stay away from callback hell.

To save the downloaded file to local, a file name is needed. The easiest way is to directly use the file name from the URL. But it would be nice if the file name can have more semantics, which will be very helpful for search. So the post id and the post summary text can be used as the local file name. Notice not all the characters in the text can be used in file names. So another utility function is needed to remove those reversed and disallowed characters from text:

    removeReservedCharactersFromFileName = function (fileName) {
        // https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
        // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx#file_and_directory_names
        return fileName.replace(/[<>:"/\\|?*\x00-\x1F\r\n\t]/g, "");
    },

Then export these 2 functions:

module.exports = {
    download: download,
    removeReservedCharactersFromFileName: removeReservedCharactersFromFileName,
    exists: exists
};

Download post’s pictures/video files then unlike post

Now a tumblr module (tumblr.js) can be created. The first step is to create a tumblr client, with tumblr-auto-auth, this is extremely simple:

var path = require("path"),
    util = require("util"),
    Q = require("q"),
    tumblr = require("tumblr-auto-auth"),
    common = require("./common"),

    getClient = function (options) {
        var deferred = Q.defer();
        tumblr.getAuthorizedClient({
            userEmail: options.userEmail,
            userPassword: options.userPassword,
            appConsumerKey: options.appConsumerKey,
            appSecretKey: options.appSecretKey,
            debug: options.debug
        }, function (error, client) {
            if (error) {
                deferred.reject(error);
            } else {
                options.client = client;
                deferred.resolve(options);
            }
        });
        return deferred.promise;
    },

Q is consistently used for asynchrony.

Then the created client can be used to pull the liked posts of the specified user from tumblr. Just call client.likes:

    getLikes = function (options) {
        var deferred = Q.defer();
        options.client.likes(options, function (error, data) {
            if (error) {
                deferred.reject(error);
            } else {
                console.log("Likes: " + data.liked_count);
                options.posts = data.liked_posts;
                options.likesCount = data.liked_count;
                deferred.resolve(options);
            }
        });
        return deferred.promise;
    },

It will send a GET request to tumblr. The response will be a JSON data. data.liked_posts is an array of post objects, and data.liked_count is the total count of liked posts all time.

Now it is time to download the contents of each post. A post can have:

  • one or more pictures
  • one video file.

All files will be downloaded by calling common.download, which was defined a moment ago:

    downloadPost = function (post, directory, getFileName) {
        var downloads = [];
        console.log("Processing " + post.post_url);
        if (post.photos) { // Post has pictures.
            post.photos.forEach(function (photo, index) {
                var url = photo.original_size.url;
                var file = path.join(directory, getFileName(post, url, index));
                downloads.push(common.download(url, file).thenResolve({
                    post: post, 
                    url: url, 
                    file: file,
                    type: "photo"
                }));
            });
        }
        if (post.video_url) { // Post has videos.
            var url = post.video_url;
            var file = path.join(directory, getFileName(post, url));
            downloads.push(common.download(url, file).thenResolve({
                post: post, 
                url: url, 
                file: file,
                type: "video"
            }));
        }
        return Q.all(downloads);
    },

Since common.download returns a promise object, all these promise objects can be pushed to a promise array, then Q.all can be called to composite them to a single promise object. Q.all is similar to Task.WaitAll in .NET.

Also a getFileName function is used to generate file name with post id and file URL (either URL of a picture, or URL of a video):

    getFileName = function (post, url, index) {
        var summary = post.summary ? common.removeReservedCharactersFromFileName(post.summary).trim() : "",
            extension = url.split(".").pop();
        summary = summary ? " " + summary.substring(0, 30) : "";
        index = index ? index : 0;
        // return `${post.id} ${index}${summary}.${extension}`;
        return post.id + " " + index + summary + "." + extension;
    },

Unfortunately, Node.js Tools for Visual Studio does not support ECMAScript 2015/ES 6, even though Node.js already support it. So here the old string concatenation syntax is applied instead of the new template string syntax.

After finishing downloading all files in a post, this post can be removed from liked posts list. Tumblr client has a unlike API for this:

    unlikePost = function (options) {
        var deferred = Q.defer();
        console.log("Unliking post " + options.post.post_url);
        options.client.unlike(options.post.id, options.post.reblog_key, function (error, data) {
            if (error) {
                deferred.reject(error);
            } else {
                deferred.resolve(options);
            }
        });
        return deferred.promise;
    },

Now it is time to composite these steps together. They all return a promise, so it is quite easy and straightforword:

    downloadAllAndUnlike = function (options) {
        getClient(options) // Get tumblr client.
            .then(getLikes) // Get tumblr liked post.
            .then(function (options) {
                if (options.likesCount > 0 && options.posts && options.posts.length > 0) {
                    // If there is any liked post.  
                    Q.all(options.posts.map(function (post) { // Download each liked post.
                        return downloadPost(post, options.directory, getFileName).then(function (download) {
                            return unlikePost({ // After downloading all files of the tumblr post, unlike it
                                client: options.client,
                                post: post
                            }).thenResolve(download);
                        });
                    })).then(function (posts) { // After downloading and unliking all tumblr post, log them.
                        if (util.isArray(posts)) {
                            posts.forEach(console.log);
                        } else {
                            console.log(posts);
                        }
                    }, function (errors) { // If there is error, log it.
                        if (util.isArray(errors)) {
                            errors.forEach(console.error);
                        } else {
                            console.error(errors);
                        }
                    }).then(function() {
                        downloadAllAndUnlike(options); // Download gain, recursively.
                    });
                }
                // If there is not any liked post, stop. Recursion terminates.
            });
    };

When calling tumblr API to get liked posts, the API returns 50 posts even when there is more. So, above downloadAllAndUnlike is a recursive function:

  • When there is any liked post to download, it calls itself recursively to try to download again.
  • When there is nothing to download, the recursion terminates.

And finally, export downloadAllAndUnlike function:

module.exports = {
    downloadAllAndUnlike: downloadAllAndUnlike
};

Start working

To start downloading, specify a startup file for this Node.js application:

image

In the startup file (main.js), just call downloadAllAndUnlike function of the tumblr module:

var tumblr = require("./tumblr");

tumblr.downloadAllAndUnlike({
    userEmail: "dixinyan@live.com",
    userPassword: "...",
    appConsumerKey: "...",
    appSecretKey: "...",
    offset: 5,
    limit: 51,
    directory: "D:\\Downloads\\Tumblr",
    after: 1
});

The user email, user password, application consumer key, and application secrete key are required. To get the application consumer/secrete key, just register an application in tumblr.

35 Comments

  • great post excellent

  • Cool, thx bro!

  • Hi,

    I've gotten your example code to build without errors but it hangs on the SlimerJS window and doesn't appear to be doing anything.

    Furthermore, in your example code, the require section of var path in "tumblr.js" that needs the common module should be written as common = require("common"),

    Also, it wasn't clear A) how to install new packages using Visual Studio, and B) that you required the "common" and "q" npm modules. After I actually read the code it made more sense.

    Yeah, it's been 5 minutes now and the SlimerJS window is just hanging there without it appearing to do anything. The directory I specified hasn't been created yet.

    I love the post, but the code doesn't appear to actually do what was intended as of the moment I'm writing this comment. I thank you for the work so far!

    Best,
    Chris

  • The complete project can be found https://github.com/Dixin/CodeSnippets/tree/master/Dixin.Nodejs

    After opening the .njsproj in Visual Studio, there will be a "npm" node under the project. Modules can be added by right clicking "npm", or by editing the package.json file.

    Not sure about the SlimerJS problem. It works on my 3 laptops. Can you press F5 to debug from Visual Studio?


  • Dixin,

    This is a great post. I'm new to front-end coding (I've been a SQL Server developer/administrator for 23 years). I want to build an application around this by storing the image/video tags in a SQL Server db and providing an interface to allow a user to search the tags and retrieve the appropriate items. Does the Tumblr API allow for retrieving the tags? Also, I've seen a number of Tumblr posts where there are multiple images in a single post. Is there additional code required to separate these?

    Thanks,

    Doug

  • Thanks again for the article post.Really looking forward to read more. Awesome.

  • داشتن ظاهری زیباتر و جوانتر هم مو جب خرسندی و رضایت خود فرد می شود و هم باعث موفقیت او در امور شخصی، خانوادگی و اجتماعی نیز می شود. مر کز زیبایی دکتر مهدیانی فوق تخصص جراحی پلاستیک و زیبایی به عنوان بهترین مراکز جراحی پلاستیک و زیبایی در تهران کلیه خدمات زیبایی و جوانسازی را با بهترین کیفیت و مناسب ترین قیمت به شما عزیزان ارائه می کند.
    https://drmahdiani.com/

  • شرکت بازرگانی بارمان با بهره گیری از پرسنل ، کارشناسان و کادر فنی متخصص با تجربه ای بالغ بر 10 سال سابقه در زمینه توزیع ماشین آلات و قطعات یدکی ، تعمیرگاهی و تامین تجهیزات تست عملکرد خودرو در صنایع خودرو سازی کشور ، مفتخر است به عنوان یکی از طراحان شبکه تخصصی فروش ماشین آلات و تجهیزات تعمیرگاهی در ایران بستری ایمن ، تخصصی و مناسب جهت معرفی محصولات تعمیرگاهی با شرایط مناسب برای کلیه شرکت ها ، اشخاص حقیقی و حقوقی که در زمینه ارائه سرویس و خدمات تعمیرگاهی فعالیت دارند را عرضه می نماید .

  • https://ma-study.blogspot.com/

  • I came to this site with the introduction of a friend around me and I was very impressed when I found your writing. I'll come back often after bookmarking! <a href="http://clients1.google.com.br/url?sa=t&url=https%3A%2F%2Foncasino.io">safetoto</a>

  • I came to this site with the introduction of a friend around me and I was very impressed when I found your writing. I'll come back often after bookmarking! ..

  • I am very impressed with your writing I couldn't think of this, but it's amazing! I wrote several posts similar to this one, but please come and see!
    ..

  • Your writing is perfect and complete. <a href="http://cse.google.de/url?sa=t&url=https%3A%2F%2Foncasino.io">baccaratsite</a> However, I think it will be more wonderful if your post includes additional topics that I am thinking of. I have a lot of posts on my site similar to your topic. Would you like to visit once? ?

  • Your explanation is organized very easy to understand!!! I understood at once. Could you please post about ?? Please!!

  • It's the same topic , but I was quite surprised to see the opinions I didn't think of. My blog also has articles on these topics, so I look forward to your visit.

  • New Hope Physio a <a href="https://www.newhopephysio.com/massage-therapy"><strong>Best Massage Therapist in Brampton</strong></a>. We provide complete relief from sports injuries, stress, anxiety and more.

  • Finding <strong>Best Physiotherapy Clinic in Brampton</strong>, On (905) 846-4000. So New Hope <a href="https://www.newhopephysio.com/"><strong>Physiotherapy Brampton</strong></a> offers Expert physiotherapist and massage therapy with reasonable prices. Book Appointment Today!

  • On the hunt for a <a href="https://www.newhopephysio.com/pelvic-floor-physiotherapy"><strong>Pelvic Floor Physiotherapy Brampton</strong></a>, Orangeville, Malton? So New Hope Physiotherapy can provide relief from muscles, joints, and nerves of the pelvis. Book Appointment Today!

  • On the hunt for a Best Shockwave Therapy Brampton, Orangeville, Malton? So New Hope Physiotherapy can provide relief from a range of conditions affecting joints, tendons and bones.

  • On the hunt for a <a href="https://www.newhopephysio.com/cupping-therapy"><strong>Best Cupping Therapy Brampton</strong></a>, Orangeville, Malton? So New Hope Physiotherapy is proud to offer an array of massage services, including cupping massage.

  • The content you create has always caught my attention. you are really good. Your article is one of my favorites to read. you write very well I understand what you mean.

  • New hope Physiotherapy & Rehab Center specializes in providing professional <a href="https://www.newhopephysio.com/chiropractic-brampton"><strong>Chiropractor Brampton</strong></a> and supportive environment to the patients suffering from neuro muscular disorders. Our team of professionals practitioners offer superior quality care focused on treating the causes of the pain and not just the symptoms.

  • Great post. I was checking continuously this blog and I’m impressed! .

  • I came to this site with the introduction of a friend around me and I was very impressed when I found your writing. I'll come back often after bookmarking! <a href="https://google.ae/url?sa=t&url=https%3A%2F%2Fwww.mtclean.blog/">baccarat online</a>

  • Why couldn't I have the same or similar opinions as you? T^T I hope you also visit my blog and give us a good opinion. <a href="https://google.co.bw/url?sa=t&url=https%3A%2F%2Fwww.mtclean.blog/">totosite</a>

  • Your article has answered the question I was wondering about! I would like to write a thesis on this subject, but I would like you to give your opinion once :D <a href="https://google.co.ao/url?sa=t&url=https%3A%2F%2Fwww.mtclean.blog/">baccarat online</a>

  • I came to this site with the introduction of a friend around me and I was very impressed when I found your writing. I'll come back often after bookmarking! <a href="https://google.cm/url?sa=t&url=https%3A%2F%2Fwww.mtclean.blog/">casinosite</a>

  • I know you better than this through your article. You are so charming when writing articles I can feel it.

  • I read your post very well.I also have a website, can I comment on it? Thank you friend.

  • I admire your ability to distill complex ideas into clear and concise prose.

  • I admire your ability to tackle such complex topics with precision and grace.

  • You're so good at writing so many articles. There's a lot of substance to everything you write.

  • I've read your article, it's really good, you have a really creative idea. It's all interesting.

  • I found here very Good Information thanks so much for sharing.Thank you for sharing excellent information.

  • I appreciate the thoughtfulness and care with which you approached your subject matter.

Add a Comment

As it will appear on the website

Not displayed

Your website