Monday, January 2, 2017

Ad lister app with Meteor and CasperJS

The Problem

Recently I listed bunch of stuff on a local web listing site. Unfortunately they keep the ads for 30 days. After this period the adds are gone. I got frustrated by the fact I have to spend time to re-list all the stuff again. It gets worse if I wanted to list the stuff in other sites as well.
What if I can do it with one click...

The Solution

Here comes the little meteor app I came up with to do exactly this. All I have to do is to upload the photos, add the text and press the button. The way it works is simple - the pictures are stored on AWS S3 with metadata on MongoDB collection, the ad titles, text and other related information is stored in another MongoDB collection. Then for each 'submitter' I have a separate meteor package, which posts the ad.

Images orientation 

The html img tag shows the photo as it is taken, keeping the original orientation, but when the photo is opened by the browser itself, or by image viewer software it is rotated according the EXIF orientation flag. When the image is shown, the user can change its orientation from the web interface. It's done just by adding one of the classes and some CSS:
CSS:
div.show-image img.img90 {
    -ms-transform: rotate(90deg); /* IE 9 */
    -webkit-transform: rotate(90deg); /* Chrome, Safari, Opera */
    transform: rotate(90deg);
}

div.show-image img.img180 {
    -ms-transform: rotate(180deg); /* IE 9 */
    -webkit-transform: rotate(180deg); /* Chrome, Safari, Opera */
    transform: rotate(180deg);
}

div.show-image img.img270 {
    -ms-transform: rotate(270deg); /* IE 9 */
    -webkit-transform: rotate(270deg); /* Chrome, Safari, Opera */
    transform: rotate(270deg);
}
The image orientation selected by the user is also stored in the Files MongoBD collection along with the image other metadata. In order to get the right user selected orientation I use ImageMagic to rotate the image and strip all additional metadata from the image itself (like EXIF in JPEGs) as soon as the image is downloaded. I use Fibers/Future block the execution until all the images have been downloaded and rotated.

futures = _.map(files, function(file) {
    // Set up a future for the current job
    var future = new Future();
    // A callback so the job can signal completion
    var onComplete = future.resolver();
    /// Make async http call
    Meteor.call("downloadImage", file.url, dirPath, null, function(x){
        var imgPath = dirPath+"/"+x;
        s.paths.photos.push(imgPath);
        var rot = 0;
        if(file.rot) rot = file.rot;
        console.log("-rotate "  + rot);
        if(rot > 0){
            im.convert([imgPath, '-strip', '-rotate', rot, imgPath], onComplete);
        } else {
            // Inform the future that we're done with it
            onComplete(null, "");
        }
    }, function(e, r){});
    // Return the future
    return future;
});
Future.wait(futures);
console.log(_.invoke(futures, 'get'));

Once this is done, then all the submitters are executed. Each submitter has a collection, where a site relevant information is stored, like username, categories, price, etc. 
Currently I have only one submit package, for DoneDeal.ie site. It is even not fully complete as it adds only free listings and doesn't handle most of the site features.

The source code is published to https://bitbucket.org/nchokoev/adapp under the MIT license.