ru.redcraft.pinterest4j.core.api.PinAPI.java Source code

Java tutorial

Introduction

Here is the source code for ru.redcraft.pinterest4j.core.api.PinAPI.java

Source

/**
 * Copyright (C) 2012 Maxim Gurkin <redmax3d@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package ru.redcraft.pinterest4j.core.api;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;

import javax.ws.rs.core.MediaType;

import org.apache.log4j.Logger;
import org.json.JSONException;
import org.json.JSONObject;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import ru.redcraft.pinterest4j.Board;
import ru.redcraft.pinterest4j.Comment;
import ru.redcraft.pinterest4j.NewPin;
import ru.redcraft.pinterest4j.Pin;
import ru.redcraft.pinterest4j.User;
import ru.redcraft.pinterest4j.exceptions.PinMessageSizeException;
import ru.redcraft.pinterest4j.exceptions.PinterestPinNotFoundException;
import ru.redcraft.pinterest4j.exceptions.PinterestRuntimeException;

import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.ClientResponse.Status;
import com.sun.jersey.api.client.async.TypeListener;
import com.sun.jersey.api.representation.Form;
import com.sun.jersey.multipart.FormDataBodyPart;
import com.sun.jersey.multipart.FormDataMultiPart;

public class PinAPI extends CoreAPI {

    private static final int MAX_PIN_DESCRIPTION_LENGTH = 500;

    private static final String PIN_DESCRIPTION_PROP_NAME = "og:description";
    private static final String PIN_IMAGE_PROP_NAME = "og:image";
    private static final String PIN_PINBOARD_PROP_NAME = "pinterestapp:pinboard";
    private static final String PIN_LINK_PROP_NAME = "pinterestapp:source";
    private static final String PIN_PRICE_PROP_NAME = "pinterestapp:price";
    private static final String PIN_PINNER_PROP_NAME = "pinterestapp:pinner";
    private static final String PIN_LIKES_COUNT_PROP_NAME = "pinterestapp:likes";
    private static final String PIN_REPINS_COUNT_PROP_NAME = "pinterestapp:repins";
    private static final String PIN_COMMENTS_COUNT_PROP_NAME = "pinterestapp:comments";
    private static final String PIN_URL = "og:url";
    private static final String PIN_ID_ATTR = "data-id";

    private static final String PIN_API_ERROR = "PIN API ERROR: ";
    private static final String COLLECTED_PINS = "Collected pins: ";

    private static final Logger LOG = Logger.getLogger(PinAPI.class);

    static class NoMorePinsException extends PinterestRuntimeException {

        private static final long serialVersionUID = -8944899106991139578L;

        public NoMorePinsException() {
            super("No more pins");
        }

    }

    PinAPI(PinterestAccessToken accessToken, InternalAPIManager apiManager) {
        super(accessToken, apiManager);
    }

    private void chechPinDescriptionLength(String description) {
        int descLength = description.length();
        if (descLength == 0) {
            throw new PinMessageSizeException(PinMessageSizeException.MSG_ZERO_SIZE);
        }
        if (descLength > MAX_PIN_DESCRIPTION_LENGTH) {
            throw new PinMessageSizeException(PinMessageSizeException.MSG_TOO_LONG);
        }
    }

    private FormDataMultiPart createPinAddForm(long boardID, NewPin newPin) {
        return createPinAddForm(boardID, newPin.getDescription(), newPin.getPrice(), newPin.getLink(),
                newPin.getImageURL(), newPin.getImageFile());
    }

    private FormDataMultiPart createPinAddForm(long boardID, String description, double price, String link,
            String imageUrl, File imageFile) {
        FormDataMultiPart multipartForm = new FormDataMultiPart();
        multipartForm.bodyPart(new FormDataBodyPart("board", Long.toString(boardID)));
        multipartForm.bodyPart(new FormDataBodyPart("details", description));
        multipartForm.bodyPart(new FormDataBodyPart("link", link));
        multipartForm
                .bodyPart(new FormDataBodyPart("csrfmiddlewaretoken", getAccessToken().getCsrfToken().getValue()));
        multipartForm.bodyPart(new FormDataBodyPart("buyable", ""));
        multipartForm.bodyPart(new FormDataBodyPart("tags", ""));
        multipartForm.bodyPart(new FormDataBodyPart("replies", ""));
        if (price != 0) {
            multipartForm.bodyPart(new FormDataBodyPart("buyable", "$" + Double.toString(price)));
        }
        if (imageUrl != null) {
            multipartForm.bodyPart(new FormDataBodyPart("img_url", imageUrl));
        } else {
            multipartForm.bodyPart(createImageBodyPart(imageFile));
        }
        return multipartForm;
    }

    public Pin addPinToBoard(Board board, NewPin newPin) {
        LOG.debug("Creating new pin on board " + board + " with new pin: " + newPin);
        chechPinDescriptionLength(newPin.getDescription());
        board.getPinsCount(); // Magic. Pinterest boards are lazy initiated on creation
        Map<String, String> responseMap = new APIRequestBuilder("pin/create/")
                .setMediaType(MediaType.MULTIPART_FORM_DATA_TYPE)
                .setMethod(Method.POST, createPinAddForm(board.getId(), newPin)).setErrorMessage(PIN_API_ERROR)
                .build().parseResponse();
        LazyPin createdPin = new LazyPin(responseMap.get("url"), getApiManager());
        LOG.debug("Pin created " + createdPin);
        return createdPin;
    }

    private APIRequestBuilder getPinInfoRequestBuilder(long id) {
        return new APIRequestBuilder("pin/" + Long.toString(id) + "/").setAjaxUsage(false)
                .addExceptionMapping(Status.NOT_FOUND, new PinterestPinNotFoundException(id))
                .setErrorMessage(PIN_API_ERROR);
    }

    public PinBuilder getCompletePin(long id) {
        LOG.debug("Getting all info for pin with id " + id);
        return parsePinInfo(getPinInfoRequestBuilder(id).build().getDocument());
    }

    private PinBuilder parsePinInfo(Document doc) {
        PinBuilder builder = new PinBuilder();
        Map<String, String> metaMap = new HashMap<String, String>();
        for (Element meta : doc.select("meta")) {
            metaMap.put(meta.attr("property"), meta.attr("content"));
        }
        builder.setId(Long.valueOf(metaMap.get(PIN_URL).replace("http://pinterest.com/pin/", "").replace("/", "")));
        builder.setDescription(metaMap.get(PIN_DESCRIPTION_PROP_NAME));
        builder.setImageURL(metaMap.get(PIN_IMAGE_PROP_NAME));
        builder.setLink(metaMap.get(PIN_LINK_PROP_NAME));
        builder.setPrice(
                metaMap.get(PIN_PRICE_PROP_NAME) != null ? Double.valueOf(metaMap.get(PIN_PRICE_PROP_NAME)) : 0);
        builder.setLikesCount(Integer.valueOf(metaMap.get(PIN_LIKES_COUNT_PROP_NAME)));
        builder.setRepinsCount(Integer.valueOf(metaMap.get(PIN_REPINS_COUNT_PROP_NAME)));
        builder.setCommentsCount(Integer.valueOf(metaMap.get(PIN_COMMENTS_COUNT_PROP_NAME)));
        builder.setBoard(
                new LazyBoard(metaMap.get(PIN_PINBOARD_PROP_NAME).replace(PINTEREST_URL, ""), getApiManager()));
        builder.setPinner(new LazyUser(
                metaMap.get(PIN_PINNER_PROP_NAME).replace(PINTEREST_URL, "").replace("/", ""), getApiManager()));

        Elements pinners = doc.select("p#PinnerName").first().getElementsByTag("a");
        if (pinners.size() > 1) {
            builder.setOriginalPinner(new LazyUser(pinners.get(1).attr("href").replace("/", ""), getApiManager()));
            builder.setRepined(true);
        } else {
            builder.setOriginalPinner(builder.getPinner());
            builder.setRepined(false);
        }
        return builder;
    }

    private List<Pin> parsePinsResponse(Document doc) {
        List<Pin> pinList = new ArrayList<Pin>();
        Elements htmlPins = doc.select("div.pin");
        for (Element htmlPin : htmlPins) {
            if (htmlPin.hasAttr(PinAPI.PIN_ID_ATTR)) {
                long pinID = Long.valueOf(htmlPin.attr(PinAPI.PIN_ID_ATTR));
                pinList.add(new LazyPin(pinID, getApiManager()));
            }
        }
        LOG.debug(COLLECTED_PINS + pinList.size());
        return pinList;
    }

    private List<Pin> processPinsPage(APIRequestBuilder requestBuilder) {
        List<Pin> response = null;
        try {
            Document doc = requestBuilder.addExceptionMapping(Status.NOT_FOUND, new NoMorePinsException())
                    .setErrorMessage(PIN_API_ERROR).build().getDocument();
            response = parsePinsResponse(doc);
        } catch (NoMorePinsException e) {
            response = new ArrayList<Pin>();
        }
        return response;
    }

    public List<Pin> getPins(User user, int page, boolean likes) {
        LOG.debug("Getting pin list for user " + user + " on page " + page + "with filter likes " + likes);
        String filter = likes ? "&filter=likes" : "";
        return processPinsPage(new APIRequestBuilder(user.getUserName() + "/pins/?page=" + page + filter));
    }

    public List<Pin> getPins(Board board, int page) {
        LOG.debug("Getting pin list for board " + board + " on page " + page);
        return processPinsPage(new APIRequestBuilder(board.getURL() + "?page=" + page));
    }

    public void deletePin(Pin pin) {
        LOG.debug("Deleting pin " + pin);
        new APIRequestBuilder(pin.getURL() + "delete/").setMethod(Method.POST, "{}").setErrorMessage(PIN_API_ERROR)
                .build();
        LOG.debug("Pin deleted");
    }

    public Pin updatePin(Pin pin, String description, double price, String link, Board board) {
        LOG.debug(String.format("Updating pin=%s with desc=%s, price=%f, link = %s, board=%s", pin, description,
                price, link, board));
        chechPinDescriptionLength(description);

        FormDataMultiPart multipartForm = createPinAddForm(board.getId(), description, price, link, "", null);
        new APIRequestBuilder(pin.getURL() + "edit/").setMethod(Method.POST, multipartForm)
                .setErrorMessage(PIN_API_ERROR).setMediaType(MediaType.MULTIPART_FORM_DATA_TYPE).build();

        Pin updatedPin = new LazyPin(pin.getId(), getApiManager());
        LOG.debug("Pin updated");
        return updatedPin;
    }

    public Pin getPinByID(long id) {
        return new LazyPin(getCompletePin(id), getApiManager());
    }

    class PinListener extends TypeListener<ClientResponse> {

        private final long id;
        private final AsyncPinPayload payload;

        public PinListener(long id, AsyncPinPayload payload) {
            super(ClientResponse.class);
            this.id = id;
            this.payload = payload;
        }

        @Override
        public void onComplete(Future<ClientResponse> f) throws InterruptedException {
            try {
                ClientResponse response = f.get();
                if (response.getStatus() == Status.OK.getStatusCode()) {
                    Document doc = Jsoup.parse(response.getEntity(String.class));
                    Pin pin = new LazyPin(parsePinInfo(doc), getApiManager());
                    payload.processPin(pin);
                } else {
                    LOG.error("Async pin processing error");
                    response.close();
                    f.cancel(true);
                    payload.processException(id, new PinterestRuntimeException(response, "Bad async response"));
                }
            } catch (Exception e) {
                LOG.error("Async pin processing error", e);
                f.cancel(true);
                payload.processException(id, e);
            }
        }
    }

    public void getAsyncPinByID(final long id, final AsyncPinPayload payload) {
        getPinInfoRequestBuilder(id).buildAsync(new PinListener(id, payload));
    }

    private Form createRepinForm(Pin pin, Board board, String description) {
        Form form = new Form();
        form.add("board", board.getId());
        form.add("id", pin.getId());
        form.add("details", description);
        form.add("csrfmiddlewaretoken", getAccessToken().getCsrfToken().getValue());
        return form;
    }

    public Pin repin(Pin pin, Board board, String description) {
        String newDescription = (description != null) ? description : pin.getDescription();
        LOG.debug(String.format("Repining pin = %s on board = %s with descr = %s", pin, board, newDescription));
        Form repinForm = createRepinForm(pin, board, newDescription);
        String repinURL = new APIRequestBuilder(pin.getURL() + "repin/").setMethod(Method.POST, repinForm)
                .setErrorMessage(PIN_API_ERROR).build().parseResponse().get("repin_url");
        Pin repinedPin = new LazyPin(repinURL, getApiManager());
        LOG.debug("Created repined pin=" + repinedPin);
        return repinedPin;
    }

    public void like(Pin pin, boolean like) {
        LOG.debug(String.format("Setting like of pin = %s to = %s", pin, like));
        new APIRequestBuilder(pin.getURL() + "like/").setMethod(Method.POST, getSwitchForm("unlike", like))
                .setErrorMessage(PIN_API_ERROR).build().parseResponse();
        LOG.debug("Pin like mark set to " + like);
    }

    private Form createCommentForm(Pin pin, String comment) {
        Form form = new Form();
        form.add("text", comment);
        form.add("path", "/" + pin.getURL());
        form.add("replies", "");
        return form;
    }

    public Comment addCommentToPin(Pin pin, String comment, User user) {
        LOG.debug(String.format("Adding comment to pin = %s with text = '%s' by user = %s", pin, comment, user));
        Map<String, String> responseMap = new APIRequestBuilder(pin.getURL() + "comment/")
                .setMethod(Method.POST, createCommentForm(pin, comment)).setErrorMessage(PIN_API_ERROR).build()
                .parseResponse();
        Comment newComment = new CommentImpl(Long.valueOf(responseMap.get("id")), comment, user,
                new LazyPin(pin.getId(), getApiManager()));
        LOG.debug("Comment created: " + newComment);
        return newComment;
    }

    public void deleteComment(Comment comment) {
        LOG.debug(String.format("Deleting comment = %s", comment));
        Form commentForm = new Form();
        commentForm.add("comment", comment.getId());
        new APIRequestBuilder(comment.getPin().getURL() + "deletecomment/").setMethod(Method.POST, commentForm)
                .setErrorMessage(PIN_API_ERROR).build().parseResponse();
        LOG.debug("Comment deleted");
    }

    public List<Comment> getComments(Pin pin) {
        LOG.debug("Getting comments for pin = " + pin);
        List<Comment> comments = new ArrayList<Comment>();
        Document doc = null;
        String axajResponse = null;
        try {
            axajResponse = new APIRequestBuilder(pin.getURL()).setErrorMessage(PIN_API_ERROR).build().getResponse()
                    .getEntity(String.class);
            doc = Jsoup.parse(new JSONObject(axajResponse).getString("footer"));
        } catch (JSONException e) {
            throw new PinterestRuntimeException(PIN_API_ERROR + axajResponse, e);
        }
        for (Element comment : doc.select("div.comment")) {
            long id = Long.valueOf(comment.getElementsByClass("DeleteComment").first().attr("data"));
            Element contentMeta = comment.getElementsByClass("CommenterMeta").first();
            User user = new LazyUser(contentMeta.getElementsByTag("a").first().attr("href").replace("/", ""),
                    getApiManager());
            contentMeta.getElementsByTag("a").remove();
            String text = contentMeta.text();
            comments.add(new CommentImpl(id, text, user, pin));
        }
        LOG.debug("Comments extracted: " + comments);
        return comments;
    }

    public boolean isLiked(Pin pin) {
        LOG.debug("Checking liked status for pin=" + pin);
        boolean liked = false;
        if (getPinInfoRequestBuilder(pin.getId()).build().getDocument().select("li.unlike-button").size() == 1) {
            liked = true;
        }
        LOG.debug("Liked state is " + liked);
        return liked;
    }

    public List<Pin> getPinsFromThread(String url, int page, long marker) {
        LOG.debug(
                String.format("Loading pins thread for url = %s on page = %d with marker = %d", url, page, marker));
        Document doc = new APIRequestBuilder(url + "&page=" + page + "&marker=" + marker)
                .setErrorMessage(PIN_API_ERROR).build().getDocument();
        return parsePinsResponse(doc);
    }

}