diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/SiteIcon.java b/Clover/app/src/main/java/org/floens/chan/core/site/SiteIcon.java
index f375aa25..6494d449 100644
--- a/Clover/app/src/main/java/org/floens/chan/core/site/SiteIcon.java
+++ b/Clover/app/src/main/java/org/floens/chan/core/site/SiteIcon.java
@@ -23,14 +23,23 @@ import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
+import com.android.volley.VolleyError;
+import com.android.volley.toolbox.ImageLoader;
+
+import org.floens.chan.utils.Logger;
+
import java.io.IOException;
import okhttp3.HttpUrl;
+import static org.floens.chan.Chan.injector;
import static org.floens.chan.utils.AndroidUtils.getAppContext;
import static org.floens.chan.utils.AndroidUtils.getRes;
public class SiteIcon {
+ private static final String TAG = "SiteIcon";
+ private static final int FAVICON_SIZE = 64;
+
private String assetPath;
private HttpUrl url;
@@ -40,7 +49,7 @@ public class SiteIcon {
return siteIcon;
}
- public static SiteIcon fromUrl(HttpUrl url) {
+ public static SiteIcon fromFavicon(HttpUrl url) {
SiteIcon siteIcon = new SiteIcon();
siteIcon.url = url;
return siteIcon;
@@ -65,7 +74,20 @@ public class SiteIcon {
drawable.getPaint().setFilterBitmap(false);
result.onSiteIcon(this, drawable);
} else if (url != null) {
- // TODO
+ injector().instance(ImageLoader.class).get(url.toString(), new ImageLoader.ImageListener() {
+ @Override
+ public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) {
+ if (response.getBitmap() != null) {
+ Drawable drawable = new BitmapDrawable(response.getBitmap());
+ result.onSiteIcon(SiteIcon.this, drawable);
+ }
+ }
+
+ @Override
+ public void onErrorResponse(VolleyError error) {
+ Logger.e(TAG, "Error loading favicon", error);
+ }
+ }, FAVICON_SIZE, FAVICON_SIZE);
}
}
diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/SiteRegistry.java b/Clover/app/src/main/java/org/floens/chan/core/site/SiteRegistry.java
new file mode 100644
index 00000000..93caa5d3
--- /dev/null
+++ b/Clover/app/src/main/java/org/floens/chan/core/site/SiteRegistry.java
@@ -0,0 +1,51 @@
+/*
+ * Clover - 4chan browser https://github.com/Floens/Clover/
+ * Copyright (C) 2014 Floens
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.floens.chan.core.site;
+
+import android.util.SparseArray;
+
+import org.floens.chan.core.site.sites.chan4.Chan4;
+import org.floens.chan.core.site.sites.lainchan.Lainchan;
+import org.floens.chan.core.site.sites.vichan.ViChan;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Registry of all sites and url handler we have.
+ */
+public class SiteRegistry {
+ public static final List URL_HANDLERS = new ArrayList<>();
+ public static final SparseArray> SITE_CLASSES = new SparseArray<>();
+
+ static {
+ URL_HANDLERS.add(Chan4.URL_HANDLER);
+ URL_HANDLERS.add(ViChan.URL_HANDLER);
+ URL_HANDLERS.add(Lainchan.URL_HANDLER);
+ }
+
+ static {
+ // This id-siteclass mapping is used to look up the correct site class at deserialization.
+ // This differs from the Site.id() id, that id is used for site instance linking, this is just to
+ // find the correct class to use.
+ SITE_CLASSES.put(0, Chan4.class);
+
+ SITE_CLASSES.put(1, ViChan.class);
+ SITE_CLASSES.put(2, Lainchan.class);
+ }
+}
diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/SiteResolver.java b/Clover/app/src/main/java/org/floens/chan/core/site/SiteResolver.java
index 9959e9b2..cac65f3d 100644
--- a/Clover/app/src/main/java/org/floens/chan/core/site/SiteResolver.java
+++ b/Clover/app/src/main/java/org/floens/chan/core/site/SiteResolver.java
@@ -67,7 +67,7 @@ public class SiteResolver {
}
public SiteResolverResult resolveSiteForUrl(String url) {
- List siteUrlHandlers = Sites.URL_HANDLERS;
+ List siteUrlHandlers = SiteRegistry.URL_HANDLERS;
HttpUrl httpUrl = sanitizeUrl(url);
diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/SiteService.java b/Clover/app/src/main/java/org/floens/chan/core/site/SiteService.java
index 605db46d..3e5eec8e 100644
--- a/Clover/app/src/main/java/org/floens/chan/core/site/SiteService.java
+++ b/Clover/app/src/main/java/org/floens/chan/core/site/SiteService.java
@@ -104,7 +104,7 @@ public class SiteService {
Site site = new Chan4();
SiteConfig config = new SiteConfig();
- config.classId = Sites.SITE_CLASSES.indexOfValue(site.getClass());
+ config.classId = SiteRegistry.SITE_CLASSES.indexOfValue(site.getClass());
config.external = false;
SiteModel model = siteRepository.create(config, new JsonSettings());
@@ -138,7 +138,7 @@ public class SiteService {
SiteConfig config = new SiteConfig();
JsonSettings settings = new JsonSettings();
- config.classId = Sites.SITE_CLASSES.indexOfValue(site.getClass());
+ config.classId = SiteRegistry.SITE_CLASSES.indexOfValue(site.getClass());
config.external = false;
siteRepository.create(config, settings);
@@ -155,7 +155,7 @@ public class SiteService {
}
private Site instantiateSiteClass(int classId) {
- Class extends Site> clazz = Sites.SITE_CLASSES.get(classId);
+ Class extends Site> clazz = SiteRegistry.SITE_CLASSES.get(classId);
if (clazz == null) {
throw new IllegalArgumentException("Unknown class id");
}
diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/Sites.java b/Clover/app/src/main/java/org/floens/chan/core/site/Sites.java
index c6c8232e..e4545e33 100644
--- a/Clover/app/src/main/java/org/floens/chan/core/site/Sites.java
+++ b/Clover/app/src/main/java/org/floens/chan/core/site/Sites.java
@@ -1,39 +1,17 @@
package org.floens.chan.core.site;
-import android.util.SparseArray;
-
-import org.floens.chan.core.site.sites.vichan.ViChan;
-import org.floens.chan.core.site.sites.chan4.Chan4;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Sites {
- public static final SparseArray> SITE_CLASSES = new SparseArray<>();
-
- static {
- // This id-siteclass mapping is used to look up the correct site class at deserialization.
- // This differs from the Site.id() id, that id is used for site instance linking, this is just to
- // find the correct class to use.
- SITE_CLASSES.put(0, Chan4.class);
-
- SITE_CLASSES.put(1, ViChan.class);
- }
-
- public static final List URL_HANDLERS = new ArrayList<>();
-
- static {
- URL_HANDLERS.add(Chan4.SITE_URL_HANDLER);
- URL_HANDLERS.add(ViChan.RESOLVABLE);
- }
-
private static List ALL_SITES = Collections.unmodifiableList(new ArrayList());
/**
* Return all sites known in the system.
*
This list is immutable. Changes to the known sites cause this function to return a new immutable list
* with the site changes.
+ *
* @return list of sites known in the system.
*/
public static List allSites() {
diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/common/CommonSite.java b/Clover/app/src/main/java/org/floens/chan/core/site/common/CommonSite.java
index c9f23110..6368ce41 100644
--- a/Clover/app/src/main/java/org/floens/chan/core/site/common/CommonSite.java
+++ b/Clover/app/src/main/java/org/floens/chan/core/site/common/CommonSite.java
@@ -17,6 +17,8 @@
*/
package org.floens.chan.core.site.common;
+import android.os.Handler;
+import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.webkit.WebView;
@@ -47,11 +49,15 @@ import java.io.IOException;
import java.security.SecureRandom;
import java.util.Map;
import java.util.Random;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import okhttp3.HttpUrl;
import okhttp3.Request;
import okhttp3.Response;
+import static android.text.TextUtils.isEmpty;
+
public abstract class CommonSite extends SiteBase {
private final Random secureRandom = new SecureRandom();
@@ -222,25 +228,79 @@ public abstract class CommonSite extends SiteBase {
}
public static abstract class CommonSiteUrlHandler implements SiteUrlHandler {
+ public abstract HttpUrl getUrl();
+
+ public abstract String[] getNames();
+
@Override
public boolean matchesName(String value) {
+ for (String s : getNames()) {
+ if (value.equals(s)) {
+ return true;
+ }
+ }
+
return false;
}
@Override
public boolean respondsTo(HttpUrl url) {
- return false;
+ return getUrl().host().equals(url.host());
}
@Override
public String desktopUrl(Loadable loadable, @Nullable Post post) {
- return null;
+ if (loadable.isCatalogMode()) {
+ return getUrl().newBuilder().addPathSegment(loadable.boardCode).toString();
+ } else if (loadable.isThreadMode()) {
+ return getUrl().newBuilder()
+ .addPathSegment(loadable.boardCode).addPathSegment("res")
+ .addPathSegment(String.valueOf(loadable.no))
+ .toString();
+ } else {
+ return getUrl().toString();
+ }
}
@Override
public Loadable resolveLoadable(Site site, HttpUrl url) {
+ Matcher board = boardPattern().matcher(url.encodedPath());
+ Matcher thread = threadPattern().matcher(url.encodedPath());
+
+ try {
+ if (thread.find()) {
+ Board b = site.board(thread.group(1));
+ if (b == null) {
+ return null;
+ }
+ Loadable l = Loadable.forThread(site, b, Integer.parseInt(thread.group(3)));
+
+ if (isEmpty(url.fragment())) {
+ l.markedNo = Integer.parseInt(url.fragment());
+ }
+
+ return l;
+ } else if (board.find()) {
+ Board b = site.board(board.group(1));
+ if (b == null) {
+ return null;
+ }
+
+ return Loadable.forCatalog(b);
+ }
+ } catch (NumberFormatException ignored) {
+ }
+
return null;
}
+
+ public Pattern boardPattern() {
+ return Pattern.compile("/(\\w+)");
+ }
+
+ public Pattern threadPattern() {
+ return Pattern.compile("/(\\w+)/\\w+/(\\d+).*");
+ }
}
public abstract class CommonEndpoints implements SiteEndpoints {
@@ -357,8 +417,22 @@ public abstract class CommonSite extends SiteBase {
call.url(endpoints().reply(reply.loadable));
- setupPost(reply, call);
+ if (requirePrepare()) {
+ Handler handler = new Handler(Looper.getMainLooper());
+ new Thread(() -> {
+ prepare(call, reply, replyResponse);
+ handler.post(() -> {
+ setupPost(reply, call);
+ makePostCall(call, replyResponse, postListener);
+ });
+ }).start();
+ } else {
+ setupPost(reply, call);
+ makePostCall(call, replyResponse, postListener);
+ }
+ }
+ private void makePostCall(HttpCall call, ReplyResponse replyResponse, PostListener postListener) {
httpCallManager.makeHttpCall(call, new HttpCall.HttpCallback() {
@Override
public void onHttpSuccess(HttpCall httpCall) {
@@ -372,6 +446,13 @@ public abstract class CommonSite extends SiteBase {
});
}
+ public boolean requirePrepare() {
+ return false;
+ }
+
+ public void prepare(MultipartHttpCall call, Reply reply, ReplyResponse replyResponse) {
+ }
+
@Override
public boolean postRequiresAuthentication() {
return false;
@@ -408,10 +489,16 @@ public abstract class CommonSite extends SiteBase {
}
}
- public abstract class CommonApi implements ChanReader {
+ public static abstract class CommonApi implements ChanReader {
+ private CommonSite commonSite;
+
+ public CommonApi(CommonSite commonSite) {
+ this.commonSite = commonSite;
+ }
+
@Override
public PostParser getParser() {
- return postParser;
+ return commonSite.postParser;
}
}
diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/common/MultipartHttpCall.java b/Clover/app/src/main/java/org/floens/chan/core/site/common/MultipartHttpCall.java
index 4c243290..75044987 100644
--- a/Clover/app/src/main/java/org/floens/chan/core/site/common/MultipartHttpCall.java
+++ b/Clover/app/src/main/java/org/floens/chan/core/site/common/MultipartHttpCall.java
@@ -60,7 +60,7 @@ public abstract class MultipartHttpCall extends HttpCall {
@Override
public void setup(Request.Builder requestBuilder) {
requestBuilder.url(url);
- requestBuilder.addHeader("Referer", url.toString());
+ requestBuilder.addHeader("Referer", url.scheme() + "://" + url.host());
requestBuilder.post(formBuilder.build());
}
}
diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/common/vichan/VichanAntispam.java b/Clover/app/src/main/java/org/floens/chan/core/site/common/vichan/VichanAntispam.java
new file mode 100644
index 00000000..ad1aaa16
--- /dev/null
+++ b/Clover/app/src/main/java/org/floens/chan/core/site/common/vichan/VichanAntispam.java
@@ -0,0 +1,106 @@
+/*
+ * Clover - 4chan browser https://github.com/Floens/Clover/
+ * Copyright (C) 2014 Floens
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.floens.chan.core.site.common.vichan;
+
+import org.floens.chan.utils.Logger;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import okhttp3.HttpUrl;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+
+/**
+ * Vichan applies garbage looking fields to the post form, to combat bots.
+ * Load up the normal html, parse the form, and get these fields for our post.
+ *
+ * {@link #get()} blocks, run it off the main thread.
+ */
+public class VichanAntispam {
+ private static final String TAG = "Antispam";
+ private HttpUrl url;
+
+ private OkHttpClient okHttpClient = new OkHttpClient();
+
+ private List fieldsToIgnore = new ArrayList<>();
+
+ public VichanAntispam(HttpUrl url) {
+ this.url = url;
+ }
+
+ public void addDefaultIgnoreFields() {
+ fieldsToIgnore.addAll(Arrays.asList("board", "thread", "name", "email",
+ "subject", "body", "password", "file", "spoiler", "json_response",
+ "file_url1", "file_url2", "file_url3"));
+ }
+
+ public void ignoreField(String name) {
+ fieldsToIgnore.add(name);
+ }
+
+ public Map get() {
+ Map res = new HashMap<>();
+
+ Request request = new Request.Builder()
+ .url(url)
+ .build();
+ try {
+ Response response = okHttpClient.newCall(request).execute();
+ ResponseBody body = response.body();
+ if (body != null) {
+ Document document = Jsoup.parse(body.string());
+ Elements form = document.body().getElementsByTag("form");
+ for (Element element : form) {
+ if (element.attr("name").equals("post")) {
+ // Add all and