Share
Trang chủ / Tất cả hướng dẫn / Không phân loại / Chrome extension hỗ trợ tải Freepik

Chrome extension hỗ trợ tải Freepik

Hướng dẫn viết extension cho chrome hỗ trợ tải file từ freepik cực nhanh, tải kèm theo file ảnh preview.

Từ một nhu cầu rất thực tế là gần đây mình cần phải tải khá nhiều ảnh tại freepik. Trong quá trình làm mình cần thực hiện hai bước : Đầu tiên cần phải chọn lọc và tải nhiều file phù hợp, lưu vào các thư mục, sau khi số lượng file đã đủ, mình sẽ giải nén và sử dụng. Mình không sử dụng các file này ngay khi tải về, vì vậy đến lúc sử dụng, chỉ thấy toàn file nén, không biết file nào là file cần làm trước, file nào cần làm sau cả.

Từ đó mình nảy sinh một nhu cầu là tải kèm file ảnh preview về và đặt ngay bên cạnh file zip, như vậy khi sử dụng có thể nhanh chóng tìm được file cần dùng.

Theo như cách truyền thống, thì mình sẽ phải thực hiện những bước như sau :

  1. Truy cập vào trang category
  2. Bấm vào chi tiết file cần dùng
  3. Tải file ảnh, và nhớ tên file
  4. Click vào link download và lưu file lại với tên giống như file ảnh.

Làm như vậy mất khá nhiều lần click chuột, và mất thời gian chờ load trang và các tài nguyên không cần thiết, Mình nghĩ phải có cách nào đó để thao tác nhanh hơn chứ, và mình kiểm tra cấu trúc của link download rồi tìm ra cách.

Trong bài viết ngày hôm nay, mình sẽ nói về cách mà mình tìm giải pháp download file từ freepik nhanh hơn bằng cách viết một extension cho trình duyệt chrome.

Tính năng

Như đã nói, chức năng chính của extension này là hỗ trợ giải thiểu thời gian download file và hỗ trợ tải file ảnh preview tự động, dưới đây là một vài tính năng trong đó :

  • Tải file zip kèm theo file ảnh preview
  • Giảm bớt thao tác so với tải thông thường.
Nếu bạn chưa biết cách viết một extension, vui lòng tham khảo tại trang :
Building a Chrome Extension

Giải pháp  & Cấu trúc project

Giải pháp

Chúng sử dụng thư viện jquery, tìm đến các div nào chưa ảnh trong grid ảnh, và thêm vào đó một div và handle sự kiện của nó, để khi ta click vào, thì nó sẽ bắt đầu get link vào download file. Đồng thời ta cũng sẽ phân tích div đó để lấy các id, các tham số cần thiết để tạo link download.

Vì vậy ta cần sử dụng hai API là background script dùng để gọi download api và content script dùng để phân tích và tạo link.

Hạn chế

Có một hạn chế, là không phải mọi file đều được lưu trên freepik, có rất nhiều file nằm ở các trang khác nhau, freepik sẽ chuyển bạn tới trang khác khi bạn muốn download. Tuy nhiên hầu hết các file vector đều lưu trên server của freepik, vì vậy extension này chỉ hỗ trợ download trên server chính mà thôi, còn các link ngoài thì không hỗ trợ được.

Cấu trúc

Giống như bao project khác, chúng ta đặt các file ngôn ngữ trong thư mục _locales, ảnh trong image, và javascript trong thư mục scripts

Cấu trúc extension
Cấu trúc extension

Khai báo thông tin

Thông tin về extension được khai báo trong file manifest.json như sau :

{
  "background": {
    "scripts": ["javascript/background.js"]
  },
  "content_scripts": [
    {
      "matches": ["http://www.freepik.com/*"],
      "js": [
        "javascript/jquery-1.7.2.min.js",
        "javascript/contentscript.js"
      ]
    }
  ],
  "default_locale": "vi",
  "description": "__MSG_extDesc__",
  "icons": {
    "32": "images/icon_32.png"
  },
  "manifest_version": 2,
  "name": "__MSG_extName__",
  "permissions": [
    "tabs",
    "activeTab",
    "downloads",
    "http://*.freepik.com/*",
    "https://*.freepik.com/*",
    "storage"
  ],
  "version": "1.3.0"
}

Tất cả các tham số trên bạn đều có thể xem trong trang developer của google nhé. Không khó để hiểu đâu.

Ta muốn hỗ trợ đa ngôn ngữ cho extension, vì vậy ta không viết string vào trong file manifest.json mà ta viết vào một file messages.json, và đặt vào các thư mục con trong thư mục _locales. Ta tạo các thư mục con tương ứng với mã ngôn ngữ. Sau đó viết id của các string này vào manifest. Bạn xem thêm tại chrome.i18n

Content script – Get link

Để download được, ta cần phải có link, nên việc đầu tiên phải làm là phân tích code html của trang, cấu trúc link download và cuối cùng là tạo link download rồi gửi cho background script.

Cấu trúc link download, preview

Trước tiên bạn hãy truy cập vào trang này : Pirate ship vector art, và hãy copy link ảnh preview lớn đó, bạn sẽ có như sau :

http://cdns2.freepik.com/free-photo/pirate-ship-vector-art_23-2147494688.jpg

giờ hãy download file đó theo cách truyền thống, bạn sẽ có link download như dưới đấy :

http://static.freepik.com/download/23/2147495/2147494688.zip

Bây giờ bạn truy cập vào trang Vectors \ Water, Xem mã nguồn hoặc debug trang này, bạn hãy tìm đến div có class là slide, bạn sẽ thấy như sau :

<div class="slide " id="722072">
    <div class="img_box">
        <div class="img-holder">
            <a id="" href="http://www.freepik.com/free-vector/pirate-ship-vector-art_722072.htm" class="preview ">
                <span class="helper"></span>
                <img id="photoimg722072" class="preview" src="http://cdn4.freepik.com/image/th/23-2147494688.jpg"
                     border="0" alt="Pirate ship vector art" title="Pirate ship vector art">
            </a>
        </div
        <a class="favourite tip " title="Save in favorites" href="http://www.freepik.com/login"></a>
    </div>
    <span class="type type_vector ive tip" title="Vector Illustration" alt="Vector Illustration"></span>

    <div class="details">
        <span class="description">Pirate ship vector art</span>

        <span class="downloads" title="6.868 downloads" alt="6.868 downloads">Download</span>
        <span class="favourite-count"><i></i>39</span>
    </div>
</div>

Bạn đã thấy sự tương đồng rồi chứ, chúng ta sẽ từ đoạn code thứ 3 để sinh ra link ảnh preview và link download. Từ các link trên, ta có thể lấy được một vài tham số sau :

  • Tham số 1, tạm gọi là Name : pirate-ship-vector-art
  • Tham số 2, tạm gọi là arg2 : 23
  • Tham số 3, tạm gọi là arg3 : 2147494688
  • Tham soos 4, CDN Number : 2

Như vậy ta có cấu trúc các link như sau :

  • Link file ảnh preview : http://cdns{CDN_Number}.freepik.com/free-photo/{name}_{arg2}-{arg3}.jpg
  • Link download : http://static.freepik.com/download/{arg2}/{arg3.sub(7) + 1}/{arg3}.zip

Đối với link download thì mình chưa chắc lắm về tham số ở giữa, dù sao đi nữa, giải pháp này không phải là giải pháp toàn diện, nó chỉ giúp giải quyết vài vấn đề tạm thời mà thôi, vì vậy bạn cũng đừng quá tối ưu nó, vì suy cho dùng, freepik không phải website lớn, và họ cũng rất dễ thay đổi cấu trúc html của mình.

Vậy là ta đã có cấu trúc link, giờ sẽ phân tích đoạn code html kia để xác định giá trị các tham số.

Từ code của thẻ a như sau :

<a id=”” href=”http://www.freepik.com/free-vector/pirate-ship-vector-art_722072.htm” class=”preview”>

Ta lấy xây dựng được biểu thức regex như sau ( debug trực tiếp tại đây ):

http:\/\/www\.freepik\.com\/(free-vector)\/(.+)_(\d+)\.htm

Giá trị trả match được đối với ví dụ sẽ như sau :

  1. [38-49] `free-vector`
  2. [50-72] `pirate-ship-vector-art`
  3. [73-79] `722072`

Giờ đối với thẻ img :

<img id="photoimg722072" class="preview" src="http://cdn4.freepik.com/image/th/23-2147494688.jpg" border="0" alt="Pirate ship vector art" title="Pirate ship vector art">

Ta sẽ có biểu thức regex như sau ( xem online tại đây ) :

http:\/\/cdn(\d)\.freepik\.com\/(image|free-photo)\/th\/(\d+)-(\d+)\.(jpg|png)

Kết quả trong ví dụ sẽ là :

  1. [56-57] `4`
  2. [70-75] `image`
  3. [79-81] `23`
  4. [82-92] `2147494688`
  5. [93-96] `jpg`

Ta đã có tất cả, giờ việc cuối cùng là gộp tất cả lại với nhau thành file contentscript.js :

( function ($) {
    'use strict';
    $(document).ready(function () {
        var $downloadButton = $(".imageList > div > .slide > .details > .downloads");
        $downloadButton.each(function (index) {
            $(this).text("Download");
            $(this).click(function () {
                var $image = $(this).parent().parent().find("img.preview");
                var imgsrc = $image.attr("src");

                var match1 = imgsrc.match(/http:\/\/cdn(\d)\.freepik\.com\/(image|free-photo)\/th\/(\d+)-(\d+)\.(jpg|png)/);
                var downloadurl = "http://static.freepik.com/download/" + match1[3] + "/" + ( parseInt(match1[4].substr(0, 7)) + 1 ) + "/" + match1[4] + ".zip";

                var $imagelink = $(this).parent().parent().find("a.preview");
                var href = $imagelink.attr("href");
                var match2 = href.match(/http:\/\/www\.freepik\.com\/(free-vector)\/(.+)_(\d+)\.htm/);

                var bigImgSrc = "http://cdn" + match1[1] + ".freepik.com/" + match1[2] + "/" + match2[2] + "_" + match1[3] + "-" + match1[4] + "." + match1[5];

                chrome.runtime.sendMessage({from: "freepikdownloader-contentscript", imgurl : bigImgSrc, downloadurl : downloadurl}, function(response) {});
            });
        });
    })
}(jQuery) )

Send message đến backgroundscript.js

chrome.runtime.sendMessage(
    {
        from: "freepikdownloader-contentscript",
        imgurl: bigImgSrc,
        downloadurl: downloadurl
    }, function (response) {
        //...
    }
);

Bạn đọc thêm tài liệu tại : Message Passing . Tại file contentscript.js ta không nên tạo tab mới, thực ra ta có thể tạo bằng cách add vào một link có target là blank, tuy nhiên ta muốn quản lý việc download logic hơn, rằng sau khi download file zip xong thì mới bắt đầu download file ảnh, đồng thời cũng đặt file ảnh vào cùng thư mục, cũng như cùng tên với file zip, nên ta phải thực hiện việc download ở file background. Vì vậy ta cần send các dữ liệu link về cho background.js.

Background script – Xử lý download

Bạn có thể hình dung về file này như sau :

  1. Nhận thông tin về các link file, link preview image
  2. Bắt đầu download link file, lưu thông tin về link download preview image vào một danh sách
  3. Sau khi download file xong, sẽ download file preview và xóa khỏi danh sách đó.

Trước tiên ta cần một biến để lưu lại danh sách:

var queue = {};

Tiếp theo là add một handler để nhận message gửi từ content script :

chrome.runtime.onMessage.addListener(
function (request, sender, sendResponse) {
    if (request.from == "freepikdownloader-contentscript") {
        var filename = request.downloadurl.substring(request.downloadurl.lastIndexOf('/') + 1);
        chrome.downloads.download({url: request.downloadurl}, function (downloadId) {
            queue[downloadId] = {url: request.imgurl};
        });
    }
});

Chúng ta sẽ phải kiểm tra tham số from có khới hay không, vì có thể có nhiều extension khác cũng gửi message. Sau đó ta lưu url của file ảnh preview vào danh sách. Giờ bạn phải xem qua tài liệu về Download API tại trang của Google nhé. Đồng thời trong hàm callback sẽ cung cấp một id của download process. Ta lưu nó vào biến queue, với id đó đóng vai trò như một thuộc tính của queue.

Giờ để biết khi nào file được download xong, và tên file mới lưu là gì, ta add một handler nữa như sau :

chrome.downloads.onChanged.addListener(function (downloadDelta){
    console.log(downloadDelta);
    if(queue[downloadDelta.id]) {
        if (downloadDelta.hasOwnProperty("state")) {
            if (downloadDelta.state.current == "complete") {
                //Download success
                log("File name",queue[downloadDelta.id].filename);
                chrome.downloads.download({
                    url: queue[downloadDelta.id].url,
                    filename : queue[downloadDelta.id].filename
                }, function () {
                    delete queue[downloadDelta.id];
                    log("Queue", queue);
                });
            }else if(downloadDelta.state.current == "interrupted"){
                //Cancel
                delete queue[downloadDelta.id];
            }
        }
        if (!downloadDelta.hasOwnProperty("state")) {
            //Download started
            var filepathargs = downloadDelta.filename.current.replace("zip", "jpg").split("\\");
            var filepath = "freepik/" + filepathargs[filepathargs.length - 2] + "/" + filepathargs[filepathargs.length - 1];
            log("filepath", filepath);
            queue[downloadDelta.id].filename = filepath;
        }
    }
});

Trong thuộc tính state có các giá trị như sau :

  • in_progress : Khi đang nhận dữ liệu từ server
  • interrupted : Khi download không thành công, do người dùng cancel hoặc lỗi
  • complete : Khi download thành công

Trường hợp giá thuộc tính state không tồn tại, thì đó là lúc người dùng vừa mới chọn file, đặt tên file xong, trước bước in_process.

Cũng như hàm callback khi gọi phương thức download(), trong event này chrome cũng cho ta một id. Đầu tiên ta cần kiểm tra xem id này có nằm trong queue hay không, nếu không thì nó không phải file mà chúng ta vừa download, không quan tâm. Nếu nó tồn tại, ta sẽ kiểm tra state :

  • Nếu không có state, có nghĩa là người dùng vừa tắt hộp thoại chọn tên file, nơi lưu, khi này ta sẽ lấy tên file và lưu vào trong queue.
  • Nếu state là complete, có nghĩa là file zip đã được download xong, và ta sẽ bắt đầu download file preview

Để có thể đặt tên file preview giống với tên file zip, ta sẽ replace phần mở rộng trong tên file, từ zip sang jpg. Nhưng bạn phải chú ý rằng Download API chỉ cho phép ta đặt thư mục trong phần tên file là các thư mục con của thư mục download, không cho phép đặt vào các thư mục khác, việc sử dụng ký tự “..” cũng không được. Vì vậy mình sẽ tạo một thư mục freepik và đặt file vào đó cho chắc.

Giờ thì mọi thứ đã xong, bạn chỉ cần load extension vào google chrome mà thôi, bạn có thể đọc cách load plugin ở chế độ dành cho developer tại đây : Load extension in Developer mode

Kết luận

Như vậy ta đã có một plugin giúp tiết kiệm rất nhiều thời gian, thao tác, bạn thấy đó, chỉ cần bỏ một chút thời gian giải quyết các vấn để ở gốc, là ta có thể tiết kiệm được rất nhiều chi phí làm việc ở phần ngọn. Cũng khá hay và lợi thế cho những người học lập trình cho chúng ta nhỉ ?

Nhưng dù sao đi nữa, extension này không thích hợp để public lên Store, vì sự hỗ trợ của nó chưa toàn diện, chưa thích hợp với đa số người dùng, và hơn nữa freepik cũng sẽ có rất nhiều thay đổi trong tương lai, với mỗi thay đổi đó thì sẽ tạo ra rất nhiều khó khăn cho người dùng, và extension có thể vô dụng trong nay mai. Đây chỉ là một giải pháp để giải quyết vấn đề trước mắt.

Nhưng dù sao đi nữa, khi nó không hoạt động được nữa, bạn hay giúp mình comment tại đây để mình cập nhật lại bài viết nhé.