From 31678e104e023f69c2f34deb48f4c27d9a5dd7ae Mon Sep 17 00:00:00 2001 From: Floens Date: Sun, 11 Feb 2018 12:53:30 +0100 Subject: [PATCH] add first version of lainchan --- .../org/floens/chan/core/site/SiteIcon.java | 26 +- .../floens/chan/core/site/SiteRegistry.java | 51 ++ .../floens/chan/core/site/SiteResolver.java | 2 +- .../floens/chan/core/site/SiteService.java | 6 +- .../java/org/floens/chan/core/site/Sites.java | 24 +- .../chan/core/site/common/CommonSite.java | 97 +++- .../core/site/common/MultipartHttpCall.java | 2 +- .../site/common/vichan/VichanAntispam.java | 106 ++++ .../core/site/common/vichan/VichanApi.java | 318 ++++++++++ .../floens/chan/core/site/http/HttpCall.java | 3 + .../chan/core/site/sites/chan4/Chan4.java | 4 +- .../chan/core/site/sites/chan8/Chan8.java | 205 +++++++ .../core/site/sites/lainchan/Lainchan.java | 204 +++++++ .../chan/core/site/sites/vichan/ViChan.java | 541 ------------------ 14 files changed, 1011 insertions(+), 578 deletions(-) create mode 100644 Clover/app/src/main/java/org/floens/chan/core/site/SiteRegistry.java create mode 100644 Clover/app/src/main/java/org/floens/chan/core/site/common/vichan/VichanAntispam.java create mode 100644 Clover/app/src/main/java/org/floens/chan/core/site/common/vichan/VichanApi.java create mode 100644 Clover/app/src/main/java/org/floens/chan/core/site/sites/chan8/Chan8.java create mode 100644 Clover/app/src/main/java/org/floens/chan/core/site/sites/lainchan/Lainchan.java delete mode 100644 Clover/app/src/main/java/org/floens/chan/core/site/sites/vichan/ViChan.java 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 clazz = Sites.SITE_CLASSES.get(classId); + Class 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