/*
* Copyright 2016 Google Inc.
*
* 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 io.plaidapp.data.api.dribbble;
import android.text.TextUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import io.plaidapp.data.api.dribbble.model.Images;
import io.plaidapp.data.api.dribbble.model.Shot;
import io.plaidapp.data.api.dribbble.model.User;
import okhttp3.ResponseBody;
import retrofit2.Converter;
import retrofit2.Retrofit;
/**
* Dribbble API does not have a search endpoint so we have to do gross things :(
*/
public class DribbbleSearchConverter implements Converter<ResponseBody, List<Shot>> {
/** Factory for creating converter. We only care about decoding responses. **/
public static final class Factory extends Converter.Factory {
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type,
Annotation[] annotations,
Retrofit retrofit) {
return INSTANCE;
}
}
private DribbbleSearchConverter() { }
static final DribbbleSearchConverter INSTANCE = new DribbbleSearchConverter();
private static final String HOST = "https://dribbble.com";
private static final Pattern PATTERN_PLAYER_ID =
Pattern.compile("users/(\\d+?)/", Pattern.DOTALL);
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("MMMM d, yyyy");
@Override
public List<Shot> convert(ResponseBody value) throws IOException {
final Elements shotElements =
Jsoup.parse(value.string(), HOST).select("li[id^=screenshot]");
final List<Shot> shots = new ArrayList<>(shotElements.size());
for (Element element : shotElements) {
final Shot shot = parseShot(element, DATE_FORMAT);
if (shot != null) {
shots.add(shot);
}
}
return shots;
}
private static Shot parseShot(Element element, SimpleDateFormat dateFormat) {
final Element descriptionBlock = element.select("a.dribbble-over").first();
// API responses wrap description in a <p> tag. Do the same for consistent display.
String description = descriptionBlock.select("span.comment").text().trim();
if (!TextUtils.isEmpty(description)) {
description = "<p>" + description + "</p>";
}
String imgUrl = element.select("img").first().attr("src");
if (imgUrl.contains("_teaser.")) {
imgUrl = imgUrl.replace("_teaser.", ".");
}
Date createdAt = null;
try {
createdAt = dateFormat.parse(descriptionBlock.select("em.timestamp").first().text());
} catch (ParseException e) { }
return new Shot.Builder()
.setId(Long.parseLong(element.id().replace("screenshot-", "")))
.setHtmlUrl(HOST + element.select("a.dribbble-link").first().attr("href"))
.setTitle(descriptionBlock.select("strong").first().text())
.setDescription(description)
.setImages(new Images(null, imgUrl, null))
.setAnimated(element.select("div.gif-indicator").first() != null)
.setCreatedAt(createdAt)
.setLikesCount(Long.parseLong(element.select("li.fav").first().child(0).text()
.replaceAll(",", "")))
.setCommentsCount(Long.parseLong(element.select("li.cmnt").first().child(0).text
().replaceAll(",", "")))
.setViewsCount(Long.parseLong(element.select("li.views").first().child(0)
.text().replaceAll(",", "")))
.setUser(parsePlayer(element.select("h2").first()))
.build();
}
private static User parsePlayer(Element element) {
final Element userBlock = element.select("a.url").first();
String avatarUrl = userBlock.select("img.photo").first().attr("src");
if (avatarUrl.contains("/mini/")) {
avatarUrl = avatarUrl.replace("/mini/", "/normal/");
}
final Matcher matchId = PATTERN_PLAYER_ID.matcher(avatarUrl);
Long id = -1l;
if (matchId.find() && matchId.groupCount() == 1) {
id = Long.parseLong(matchId.group(1));
}
final String slashUsername = userBlock.attr("href");
final String username =
TextUtils.isEmpty(slashUsername) ? null : slashUsername.substring(1);
return new User.Builder()
.setId(id)
.setName(userBlock.text())
.setUsername(username)
.setHtmlUrl(HOST + slashUsername)
.setAvatarUrl(avatarUrl)
.setPro(element.select("span.badge-pro").size() > 0)
.build();
}
}