add captcha v2 nojs

add a setting to the site for it.
refactor-toolbar
Floens 8 years ago
parent b0271587d7
commit 9ffe4ec060
  1. 0
      Clover/app/src/main/assets/captcha/captcha2.html
  2. 3
      Clover/app/src/main/java/org/floens/chan/Chan.java
  3. 8
      Clover/app/src/main/java/org/floens/chan/core/model/orm/SiteModel.java
  4. 10
      Clover/app/src/main/java/org/floens/chan/core/presenter/SiteSetupPresenter.java
  5. 14
      Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java
  6. 2
      Clover/app/src/main/java/org/floens/chan/core/settings/OptionSettingItem.java
  7. 21
      Clover/app/src/main/java/org/floens/chan/core/settings/OptionsSetting.java
  8. 8
      Clover/app/src/main/java/org/floens/chan/core/site/Authentication.java
  9. 7
      Clover/app/src/main/java/org/floens/chan/core/site/Site.java
  10. 13
      Clover/app/src/main/java/org/floens/chan/core/site/SiteBase.java
  11. 49
      Clover/app/src/main/java/org/floens/chan/core/site/sites/chan4/Chan4.java
  12. 7
      Clover/app/src/main/java/org/floens/chan/ui/captcha/CaptchaLayout.java
  13. 191
      Clover/app/src/main/java/org/floens/chan/ui/captcha/CaptchaNojsLayout.java
  14. 6
      Clover/app/src/main/java/org/floens/chan/ui/controller/MediaSettingsController.java
  15. 30
      Clover/app/src/main/java/org/floens/chan/ui/controller/SiteSetupController.java
  16. 4
      Clover/app/src/main/java/org/floens/chan/ui/layout/ReplyLayout.java

@ -25,6 +25,7 @@ import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.StrictMode;
import android.webkit.WebView;
import org.codejargon.feather.Feather;
import org.floens.chan.core.database.DatabaseManager;
@ -115,7 +116,7 @@ public class Chan extends Application implements UserAgentProvider, Application.
//noinspection PointlessBooleanExpression
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// WebView.setWebContentsDebuggingEnabled(true);
WebView.setWebContentsDebuggingEnabled(true);
}
}
}

@ -24,14 +24,15 @@ import com.google.gson.GsonBuilder;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import org.floens.chan.core.model.json.site.SiteConfig;
import org.floens.chan.core.settings.json.BooleanJsonSetting;
import org.floens.chan.core.settings.json.LongJsonSetting;
import org.floens.chan.core.settings.json.RuntimeTypeAdapterFactory;
import org.floens.chan.core.settings.json.IntegerJsonSetting;
import org.floens.chan.core.model.json.site.SiteConfig;
import org.floens.chan.core.settings.json.JsonSetting;
import org.floens.chan.core.settings.json.JsonSettings;
import org.floens.chan.core.settings.json.LongJsonSetting;
import org.floens.chan.core.settings.json.RuntimeTypeAdapterFactory;
import org.floens.chan.core.settings.json.StringJsonSetting;
import org.floens.chan.utils.Logger;
@DatabaseTable(tableName = "site")
public class SiteModel {
@ -68,6 +69,7 @@ public class SiteModel {
public void storeUserSettings(JsonSettings userSettings) {
this.userSettings = gson.toJson(userSettings);
Logger.test("userSettings = " + this.userSettings);
}
public Pair<SiteConfig, JsonSettings> loadConfigFields() {

@ -1,8 +1,11 @@
package org.floens.chan.core.presenter;
import org.floens.chan.core.database.DatabaseManager;
import org.floens.chan.core.settings.Setting;
import org.floens.chan.core.site.Site;
import java.util.List;
import javax.inject.Inject;
public class SiteSetupPresenter {
@ -25,6 +28,11 @@ public class SiteSetupPresenter {
if (hasLogin) {
callback.showLogin();
}
List<Setting<?>> settings = site.settings();
if (!settings.isEmpty()) {
callback.showSettings(settings);
}
}
public void show() {
@ -48,5 +56,7 @@ public class SiteSetupPresenter {
void showLogin();
void setIsLoggedIn(boolean isLoggedIn);
void showSettings(List<Setting<?>> settings);
}
}

@ -49,7 +49,7 @@ public class ChanSettings {
}
@Override
public String getName() {
public String getKey() {
return name;
}
}
@ -65,7 +65,7 @@ public class ChanSettings {
}
@Override
public String getName() {
public String getKey() {
return name;
}
}
@ -83,7 +83,7 @@ public class ChanSettings {
}
@Override
public String getName() {
public String getKey() {
return name;
}
}
@ -162,7 +162,7 @@ public class ChanSettings {
theme = new StringSetting(p, "preference_theme", "yotsuba");
layoutMode = new OptionsSetting<>(p, "preference_layout_mode", LayoutMode.values(), LayoutMode.AUTO);
layoutMode = new OptionsSetting<>(p, "preference_layout_mode", LayoutMode.class, LayoutMode.AUTO);
boolean tablet = AndroidUtils.getRes().getBoolean(R.bool.is_tablet);
@ -171,12 +171,12 @@ public class ChanSettings {
openLinkConfirmation = new BooleanSetting(p, "preference_open_link_confirmation", false);
autoRefreshThread = new BooleanSetting(p, "preference_auto_refresh_thread", true);
// imageAutoLoad = new BooleanSetting(p, "preference_image_auto_load", true);
imageAutoLoadNetwork = new OptionsSetting<>(p, "preference_image_auto_load_network", MediaAutoLoadMode.values(), MediaAutoLoadMode.WIFI);
videoAutoLoadNetwork = new OptionsSetting<>(p, "preference_video_auto_load_network", MediaAutoLoadMode.values(), MediaAutoLoadMode.WIFI);
imageAutoLoadNetwork = new OptionsSetting<>(p, "preference_image_auto_load_network", MediaAutoLoadMode.class, MediaAutoLoadMode.WIFI);
videoAutoLoadNetwork = new OptionsSetting<>(p, "preference_video_auto_load_network", MediaAutoLoadMode.class, MediaAutoLoadMode.WIFI);
videoOpenExternal = new BooleanSetting(p, "preference_video_external", false);
textOnly = new BooleanSetting(p, "preference_text_only", false);
videoErrorIgnore = new BooleanSetting(p, "preference_video_error_ignore", false);
boardViewMode = new OptionsSetting<>(p, "preference_board_view_mode", PostViewMode.values(), PostViewMode.LIST);
boardViewMode = new OptionsSetting<>(p, "preference_board_view_mode", PostViewMode.class, PostViewMode.LIST);
boardGridSpanCount = new IntegerSetting(p, "preference_board_grid_span_count", 0);
boardOrder = new StringSetting(p, "preference_board_order", PostsFilter.Order.BUMP.name);

@ -18,5 +18,5 @@
package org.floens.chan.core.settings;
public interface OptionSettingItem {
String getName();
String getKey();
}

@ -17,14 +17,23 @@
*/
package org.floens.chan.core.settings;
public class OptionsSetting<T extends OptionSettingItem> extends Setting<T> {
public class OptionsSetting<T extends Enum & OptionSettingItem> extends Setting<T> {
private boolean hasCached = false;
private Class<T> clazz;
private T cached;
private T[] items;
public OptionsSetting(SettingProvider settingProvider, String key, T[] items, T def) {
public OptionsSetting(SettingProvider settingProvider, String key, Class<T> clazz, T def) {
super(settingProvider, key, def);
this.items = items;
this.clazz = clazz;
this.items = clazz.getEnumConstants();
}
public T[] getItems() {
return items;
}
@Override
@ -32,10 +41,10 @@ public class OptionsSetting<T extends OptionSettingItem> extends Setting<T> {
if (hasCached) {
return cached;
} else {
String itemName = settingProvider.getString(key, def.getName());
String itemName = settingProvider.getString(key, def.getKey());
T selectedItem = null;
for (T item : items) {
if (item.getName().equals(itemName)) {
if (item.getKey().equals(itemName)) {
selectedItem = item;
}
}
@ -52,7 +61,7 @@ public class OptionsSetting<T extends OptionSettingItem> extends Setting<T> {
@Override
public void set(T value) {
if (!value.equals(get())) {
settingProvider.putString(key, value.getName());
settingProvider.putString(key, value.getKey());
cached = value;
onValueChanged();
}

@ -22,6 +22,7 @@ public class Authentication {
NONE,
CAPTCHA1,
CAPTCHA2,
CAPTCHA2_NOJS,
GENERIC_WEBVIEW
}
@ -43,6 +44,13 @@ public class Authentication {
return a;
}
public static Authentication fromCaptcha2nojs(String siteKey, String baseUrl) {
Authentication a = new Authentication(Type.CAPTCHA2_NOJS);
a.siteKey = siteKey;
a.baseUrl = baseUrl;
return a;
}
public static Authentication fromUrl(String url, String retryText, String successText) {
Authentication a = new Authentication(Type.GENERIC_WEBVIEW);
a.url = url;

@ -21,9 +21,10 @@ import android.support.annotation.Nullable;
import org.floens.chan.core.model.Post;
import org.floens.chan.core.model.json.site.SiteConfig;
import org.floens.chan.core.settings.json.JsonSettings;
import org.floens.chan.core.model.orm.Board;
import org.floens.chan.core.model.orm.Loadable;
import org.floens.chan.core.settings.Setting;
import org.floens.chan.core.settings.json.JsonSettings;
import org.floens.chan.core.site.common.ChanReader;
import org.floens.chan.core.site.http.DeleteRequest;
import org.floens.chan.core.site.http.DeleteResponse;
@ -33,6 +34,8 @@ import org.floens.chan.core.site.http.LoginResponse;
import org.floens.chan.core.site.http.Reply;
import org.floens.chan.core.site.http.ReplyResponse;
import java.util.List;
import okhttp3.HttpUrl;
public interface Site {
@ -139,6 +142,8 @@ public interface Site {
boolean boardFeature(BoardFeature boardFeature, Board board);
List<Setting<?>> settings();
SiteEndpoints endpoints();
SiteRequestModifier requestModifier();

@ -25,12 +25,15 @@ import org.floens.chan.core.database.LoadableProvider;
import org.floens.chan.core.manager.BoardManager;
import org.floens.chan.core.model.json.site.SiteConfig;
import org.floens.chan.core.model.orm.Board;
import org.floens.chan.core.settings.Setting;
import org.floens.chan.core.settings.SettingProvider;
import org.floens.chan.core.settings.json.JsonSettings;
import org.floens.chan.core.settings.json.JsonSettingsProvider;
import org.floens.chan.core.site.http.HttpCallManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.floens.chan.Chan.injector;
@ -66,6 +69,8 @@ public abstract class SiteBase implements Site {
siteManager.updateUserSettings(this, userSettings);
});
initializeSettings();
if (boardsType() == BoardsType.DYNAMIC) {
boards(boards -> boardManager.createAll(boards.boards));
}
@ -81,6 +86,14 @@ public abstract class SiteBase implements Site {
return boardManager.getBoard(this, code);
}
@Override
public List<Setting<?>> settings() {
return new ArrayList<>();
}
public void initializeSettings() {
}
@Override
public Board createBoard(String name, String code) {
Board existing = board(code);

@ -24,7 +24,9 @@ import android.webkit.WebView;
import org.floens.chan.core.model.Post;
import org.floens.chan.core.model.orm.Board;
import org.floens.chan.core.model.orm.Loadable;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.core.settings.OptionSettingItem;
import org.floens.chan.core.settings.OptionsSetting;
import org.floens.chan.core.settings.Setting;
import org.floens.chan.core.settings.SettingProvider;
import org.floens.chan.core.settings.SharedPreferencesSettingProvider;
import org.floens.chan.core.settings.StringSetting;
@ -48,6 +50,7 @@ import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.utils.Logger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
@ -253,6 +256,24 @@ public class Chan4 extends SiteBase {
private final StringSetting passPass;
private final StringSetting passToken;
public enum CaptchaType implements OptionSettingItem {
V2JS("v2js"),
V2NOJS("v2nojs");
String name;
CaptchaType(String name) {
this.name = name;
}
@Override
public String getKey() {
return name;
}
}
private OptionsSetting<CaptchaType> captchaType;
public Chan4() {
// we used these before multisite, and lets keep using them.
SettingProvider p = new SharedPreferencesSettingProvider(AndroidUtils.getPreferences());
@ -263,6 +284,21 @@ public class Chan4 extends SiteBase {
passToken = new StringSetting(p, "preference_pass_id", "");
}
@Override
public void initializeSettings() {
super.initializeSettings();
captchaType = new OptionsSetting<>(settingsProvider, "preference_captcha_type",
CaptchaType.class, CaptchaType.V2NOJS);
}
@Override
public List<Setting<?>> settings() {
return Arrays.asList(
captchaType
);
}
@Override
public String name() {
return "4chan";
@ -442,10 +478,13 @@ public class Chan4 extends SiteBase {
if (isLoggedIn()) {
return Authentication.fromNone();
} else {
if (ChanSettings.postNewCaptcha.get()) {
return Authentication.fromCaptcha2(CAPTCHA_KEY, "https://boards.4chan.org");
} else {
return Authentication.fromCaptcha1(CAPTCHA_KEY, "https://boards.4chan.org");
switch (captchaType.get()) {
case V2JS:
return Authentication.fromCaptcha2(CAPTCHA_KEY, "https://boards.4chan.org");
case V2NOJS:
return Authentication.fromCaptcha2nojs(CAPTCHA_KEY, "https://boards.4chan.org");
default:
throw new IllegalArgumentException();
}
}
}

@ -23,7 +23,6 @@ import android.net.Uri;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.webkit.ConsoleMessage;
import android.webkit.JavascriptInterface;
import android.webkit.WebChromeClient;
@ -35,6 +34,7 @@ import org.floens.chan.core.site.Authentication;
import org.floens.chan.core.site.Site;
import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.utils.IOUtils;
import org.floens.chan.utils.Logger;
import static org.floens.chan.ui.theme.ThemeHelper.theme;
@ -80,7 +80,8 @@ public class CaptchaLayout extends WebView implements AuthenticationLayoutInterf
setWebChromeClient(new WebChromeClient() {
@Override
public boolean onConsoleMessage(@NonNull ConsoleMessage consoleMessage) {
Log.i(TAG, consoleMessage.lineNumber() + ":" + consoleMessage.message() + " " + consoleMessage.sourceId());
Logger.i(TAG, consoleMessage.lineNumber() + ":" + consoleMessage.message()
+ " " + consoleMessage.sourceId());
return true;
}
});
@ -113,7 +114,7 @@ public class CaptchaLayout extends WebView implements AuthenticationLayoutInterf
public void hardReset() {
loaded = true;
String html = IOUtils.assetAsString(getContext(), "captcha/captcha.html");
String html = IOUtils.assetAsString(getContext(), "captcha/captcha2.html");
html = html.replace("__site_key__", siteKey);
html = html.replace("__theme__", lightTheme ? "light" : "dark");

@ -0,0 +1,191 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.captcha;
import android.annotation.SuppressLint;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.webkit.ConsoleMessage;
import android.webkit.JavascriptInterface;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import org.floens.chan.core.site.Authentication;
import org.floens.chan.core.site.Site;
import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.utils.Logger;
import java.io.IOException;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
/**
* It directly loads the captcha2 fallback url into a webview, and on each requests it executes
* some javascript that will tell the callback if the token is there.
*/
public class CaptchaNojsLayout extends WebView implements AuthenticationLayoutInterface {
private static final String TAG = "CaptchaNojsLayout";
private AuthenticationLayoutCallback callback;
private String baseUrl;
private String siteKey;
private OkHttpClient okHttpClient = new OkHttpClient();
private String webviewUserAgent;
public CaptchaNojsLayout(Context context) {
super(context);
}
public CaptchaNojsLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CaptchaNojsLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@SuppressLint({"SetJavaScriptEnabled", "AddJavascriptInterface"})
@Override
public void initialize(Site site, AuthenticationLayoutCallback callback) {
this.callback = callback;
Authentication authentication = site.postAuthenticate();
this.siteKey = authentication.siteKey;
this.baseUrl = authentication.baseUrl;
requestDisallowInterceptTouchEvent(true);
WebSettings settings = getSettings();
settings.setJavaScriptEnabled(true);
webviewUserAgent = settings.getUserAgentString();
setWebChromeClient(new WebChromeClient() {
@Override
public boolean onConsoleMessage(@NonNull ConsoleMessage consoleMessage) {
Logger.i(TAG, consoleMessage.lineNumber() + ":" + consoleMessage.message()
+ " " + consoleMessage.sourceId());
return true;
}
});
setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
// Fails if there is no token yet, which is ok.
final String setResponseJavascript = "CaptchaCallback.onCaptchaEntered(" +
"document.querySelector('.fbc-verification-token textarea').value);";
view.loadUrl("javascript:" + setResponseJavascript);
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
String host = Uri.parse(url).getHost();
if (host == null) {
return false;
}
if (host.equals(Uri.parse(CaptchaNojsLayout.this.baseUrl).getHost())) {
return false;
} else {
AndroidUtils.openLink(url);
return true;
}
}
});
setBackgroundColor(0x00000000);
addJavascriptInterface(new CaptchaInterface(this), "CaptchaCallback");
}
public void reset() {
hardReset();
}
@Override
public void hardReset() {
loadRecaptchaAndSetWebViewData();
}
private void loadRecaptchaAndSetWebViewData() {
final String recaptchaUrl = "https://www.google.com/recaptcha/api/fallback?k=" + siteKey;
Request request = new Request.Builder()
.url(recaptchaUrl)
.header("User-Agent", webviewUserAgent)
.header("Referer", baseUrl)
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
ResponseBody body = response.body();
if (body == null) throw new IOException();
String responseHtml = body.string();
post(() -> {
loadDataWithBaseURL(recaptchaUrl,
responseHtml, "text/html", "UTF-8", null);
});
}
});
}
private void onCaptchaEntered(String response) {
if (TextUtils.isEmpty(response)) {
reset();
} else {
callback.onAuthenticationComplete(this, null, response);
}
}
public static class CaptchaInterface {
private final CaptchaNojsLayout layout;
public CaptchaInterface(CaptchaNojsLayout layout) {
this.layout = layout;
}
@JavascriptInterface
public void onCaptchaEntered(final String response) {
AndroidUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
layout.onCaptchaEntered(response);
}
});
}
}
}

@ -165,7 +165,7 @@ public class MediaSettingsController extends SettingsController {
boolean enabled = false;
boolean resetVideoMode = false;
for (int i = 0; i < modes.length; i++) {
if (modes[i].getName().equals(currentImageLoadMode.getName())) {
if (modes[i].getKey().equals(currentImageLoadMode.getKey())) {
enabled = true;
if (resetVideoMode) {
ChanSettings.videoAutoLoadNetwork.set(modes[i]);
@ -174,8 +174,8 @@ public class MediaSettingsController extends SettingsController {
}
}
videoAutoLoadView.items.get(i).enabled = enabled;
if (!enabled && ChanSettings.videoAutoLoadNetwork.get().getName()
.equals(modes[i].getName())) {
if (!enabled && ChanSettings.videoAutoLoadNetwork.get().getKey()
.equals(modes[i].getKey())) {
resetVideoMode = true;
}
}

@ -21,11 +21,17 @@ import android.content.Context;
import org.floens.chan.R;
import org.floens.chan.core.presenter.SiteSetupPresenter;
import org.floens.chan.core.settings.OptionsSetting;
import org.floens.chan.core.settings.Setting;
import org.floens.chan.core.site.Site;
import org.floens.chan.ui.settings.LinkSettingView;
import org.floens.chan.ui.settings.ListSettingView;
import org.floens.chan.ui.settings.SettingsController;
import org.floens.chan.ui.settings.SettingsGroup;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import static org.floens.chan.Chan.inject;
@ -91,6 +97,30 @@ public class SiteSetupController extends SettingsController implements SiteSetup
loginLink.setDescription(text);
}
@Override
public void showSettings(List<Setting<?>> settings) {
SettingsGroup group = new SettingsGroup("Additional settings");
for (Setting<?> setting : settings) {
if (setting instanceof OptionsSetting) {
OptionsSetting optionsSetting = (OptionsSetting) setting;
List<ListSettingView.Item<Enum>> items = new ArrayList<>();
for (Enum anEnum : optionsSetting.getItems()) {
items.add(new ListSettingView.Item<>(anEnum.name(), anEnum));
}
String name = optionsSetting.getItems()[0].getDeclaringClass().getSimpleName();
ListSettingView<?> v = new ListSettingView(this,
optionsSetting, name, items);
group.add(v);
}
}
groups.add(group);
}
@Override
public void showLogin() {
SettingsGroup login = new SettingsGroup(R.string.setup_site_group_login);

@ -49,6 +49,7 @@ import org.floens.chan.ui.activity.StartActivity;
import org.floens.chan.ui.captcha.AuthenticationLayoutCallback;
import org.floens.chan.ui.captcha.AuthenticationLayoutInterface;
import org.floens.chan.ui.captcha.CaptchaLayout;
import org.floens.chan.ui.captcha.CaptchaNojsLayout;
import org.floens.chan.ui.captcha.GenericWebViewAuthenticationLayout;
import org.floens.chan.ui.captcha.LegacyCaptchaLayout;
import org.floens.chan.ui.drawable.DropdownArrowDrawable;
@ -267,6 +268,9 @@ public class ReplyLayout extends LoadView implements View.OnClickListener, Reply
authenticationLayout = new CaptchaLayout(getContext());
break;
}
case CAPTCHA2_NOJS:
authenticationLayout = new CaptchaNojsLayout(getContext());
break;
case GENERIC_WEBVIEW: {
GenericWebViewAuthenticationLayout view = new GenericWebViewAuthenticationLayout(getContext());

Loading…
Cancel
Save