diff --git a/Clover/app/libs/httpclientandroidlib-1.2.1.jar b/Clover/app/libs/httpclientandroidlib-1.2.1.jar
deleted file mode 100644
index f409b0b2..00000000
Binary files a/Clover/app/libs/httpclientandroidlib-1.2.1.jar and /dev/null differ
diff --git a/Clover/app/src/main/assets/captcha/captcha.html b/Clover/app/src/main/assets/captcha/captcha.html
new file mode 100644
index 00000000..ad2a4ee6
--- /dev/null
+++ b/Clover/app/src/main/assets/captcha/captcha.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+Loading captcha...
+
+
+
+
+
diff --git a/Clover/app/src/main/java/com/android/volley/toolbox/HurlStack.java b/Clover/app/src/main/java/com/android/volley/toolbox/HurlStack.java
index 31d57f0d..7bd5bf7a 100644
--- a/Clover/app/src/main/java/com/android/volley/toolbox/HurlStack.java
+++ b/Clover/app/src/main/java/com/android/volley/toolbox/HurlStack.java
@@ -49,6 +49,7 @@ import javax.net.ssl.SSLSocketFactory;
public class HurlStack implements HttpStack {
private static final String HEADER_CONTENT_TYPE = "Content-Type";
+ private final String mUserAgent;
/**
* An interface for transforming URLs before use.
@@ -64,22 +65,23 @@ public class HurlStack implements HttpStack {
private final UrlRewriter mUrlRewriter;
private final SSLSocketFactory mSslSocketFactory;
- public HurlStack() {
- this(null);
+ public HurlStack(String userAgent) {
+ this(userAgent, null);
}
/**
* @param urlRewriter Rewriter to use for request URLs
*/
- public HurlStack(UrlRewriter urlRewriter) {
- this(urlRewriter, null);
+ public HurlStack(String userAgent, UrlRewriter urlRewriter) {
+ this(userAgent, urlRewriter, null);
}
/**
* @param urlRewriter Rewriter to use for request URLs
* @param sslSocketFactory SSL factory to use for HTTPS connections
*/
- public HurlStack(UrlRewriter urlRewriter, SSLSocketFactory sslSocketFactory) {
+ public HurlStack(String userAgent, UrlRewriter urlRewriter, SSLSocketFactory sslSocketFactory) {
+ mUserAgent = userAgent;
mUrlRewriter = urlRewriter;
mSslSocketFactory = sslSocketFactory;
}
@@ -91,6 +93,7 @@ public class HurlStack implements HttpStack {
HashMap map = new HashMap();
map.putAll(request.getHeaders());
map.putAll(additionalHeaders);
+ map.put("User-Agent", mUserAgent);
if (mUrlRewriter != null) {
String rewritten = mUrlRewriter.rewriteUrl(url);
if (rewritten == null) {
diff --git a/Clover/app/src/main/java/com/android/volley/toolbox/Volley.java b/Clover/app/src/main/java/com/android/volley/toolbox/Volley.java
index 37c18a25..b840c6d1 100644
--- a/Clover/app/src/main/java/com/android/volley/toolbox/Volley.java
+++ b/Clover/app/src/main/java/com/android/volley/toolbox/Volley.java
@@ -17,8 +17,6 @@
package com.android.volley.toolbox;
import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.net.http.AndroidHttpClient;
import android.os.Build;
@@ -33,38 +31,14 @@ public class Volley {
/** Default on-disk cache directory. */
public static final String DEFAULT_CACHE_DIR = "volley";
-
- /**
- * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
- *
- * @param context A {@link Context} to use for creating the cache dir.
- * @param stack An {@link HttpStack} to use for the network, or null for default.
- * @return A started {@link RequestQueue} instance.
- */
- public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
- return newRequestQueue(context, stack, new File(context.getCacheDir(), DEFAULT_CACHE_DIR));
- }
-
- public static RequestQueue newRequestQueue(Context context, HttpStack stack, File cacheDir) {
- return newRequestQueue(context, stack, cacheDir, -1);
- }
-
- public static RequestQueue newRequestQueue(Context context, HttpStack stack, File cacheDir, int diskCacheSize) {
- String userAgent = "volley/0";
- try {
- String packageName = context.getPackageName();
- PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
- userAgent = packageName + "/" + info.versionCode;
- } catch (NameNotFoundException e) {
- }
-
+ public static RequestQueue newRequestQueue(Context context, String userAgent, HttpStack stack, File cacheDir, int diskCacheSize) {
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
// Use a socket factory that removes sslv3
- stack = new HurlStack(null, new NoSSLv3Compat.NoSSLv3Factory());
+ stack = new HurlStack(userAgent, null, new NoSSLv3Compat.NoSSLv3Factory());
} else {
- stack = new HurlStack();
+ stack = new HurlStack(userAgent);
}
} else {
// Prior to Gingerbread, HttpUrlConnection was unreliable.
@@ -81,14 +55,4 @@ public class Volley {
return queue;
}
-
- /**
- * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
- *
- * @param context A {@link Context} to use for creating the cache dir.
- * @return A started {@link RequestQueue} instance.
- */
- public static RequestQueue newRequestQueue(Context context) {
- return newRequestQueue(context, null);
- }
}
diff --git a/Clover/app/src/main/java/org/floens/chan/ChanApplication.java b/Clover/app/src/main/java/org/floens/chan/ChanApplication.java
index 295e1d2e..6f6a6879 100644
--- a/Clover/app/src/main/java/org/floens/chan/ChanApplication.java
+++ b/Clover/app/src/main/java/org/floens/chan/ChanApplication.java
@@ -19,6 +19,7 @@ package org.floens.chan;
import android.app.Application;
import android.content.SharedPreferences;
+import android.os.StrictMode;
import android.preference.PreferenceManager;
import android.view.ViewConfiguration;
@@ -119,8 +120,8 @@ public class ChanApplication extends Application {
}
if (ChanBuild.DEVELOPER_MODE) {
-// StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
-// StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
+ StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
+ StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
}
ChanUrls.loadScheme(ChanPreferences.getNetworkHttps());
@@ -129,7 +130,9 @@ public class ChanApplication extends Application {
File cacheDir = getExternalCacheDir() != null ? getExternalCacheDir() : getCacheDir();
- volleyRequestQueue = Volley.newRequestQueue(this, null, new File(cacheDir, Volley.DEFAULT_CACHE_DIR), VOLLEY_CACHE_SIZE);
+ replyManager = new ReplyManager(this);
+
+ volleyRequestQueue = Volley.newRequestQueue(this, replyManager.getUserAgent(), null, new File(cacheDir, Volley.DEFAULT_CACHE_DIR), VOLLEY_CACHE_SIZE);
imageLoader = new ImageLoader(volleyRequestQueue, new BitmapLruImageCache(VOLLEY_LRU_CACHE_SIZE));
fileCache = new FileCache(new File(cacheDir, FILE_CACHE_NAME), FILE_CACHE_DISK_SIZE);
@@ -137,7 +140,7 @@ public class ChanApplication extends Application {
databaseManager = new DatabaseManager(this);
boardManager = new BoardManager();
watchManager = new WatchManager(this);
- replyManager = new ReplyManager(this);
+
}
public void activityEnteredForeground() {
diff --git a/Clover/app/src/main/java/org/floens/chan/chan/ChanUrls.java b/Clover/app/src/main/java/org/floens/chan/chan/ChanUrls.java
index 2b640b7c..16e545d5 100644
--- a/Clover/app/src/main/java/org/floens/chan/chan/ChanUrls.java
+++ b/Clover/app/src/main/java/org/floens/chan/chan/ChanUrls.java
@@ -38,12 +38,8 @@ public class ChanUrls {
return scheme + "://a.4cdn.org/" + board + "/thread/" + no + ".json";
}
- public static String getCaptchaDomain() {
- return scheme + "://www.google.com/";
- }
-
- public static String getCaptchaFallback() {
- return scheme + "://www.google.com/recaptcha/api/fallback?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc";
+ public static String getCaptchaSiteKey() {
+ return "6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc";
}
public static String getImageUrl(String board, String code, String extension) {
diff --git a/Clover/app/src/main/java/org/floens/chan/core/manager/ReplyManager.java b/Clover/app/src/main/java/org/floens/chan/core/manager/ReplyManager.java
index a8ea252a..aa2c79ed 100644
--- a/Clover/app/src/main/java/org/floens/chan/core/manager/ReplyManager.java
+++ b/Clover/app/src/main/java/org/floens/chan/core/manager/ReplyManager.java
@@ -23,6 +23,7 @@ import android.content.pm.PackageManager;
import android.text.TextUtils;
import com.squareup.okhttp.Callback;
+import com.squareup.okhttp.FormEncodingBuilder;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.MultipartBuilder;
import com.squareup.okhttp.OkHttpClient;
@@ -33,58 +34,61 @@ import com.squareup.okhttp.Response;
import org.floens.chan.ChanApplication;
import org.floens.chan.R;
import org.floens.chan.chan.ChanUrls;
-import org.floens.chan.core.model.Pass;
import org.floens.chan.core.model.Reply;
import org.floens.chan.core.model.SavedReply;
import org.floens.chan.ui.activity.ImagePickActivity;
import org.floens.chan.utils.Logger;
import org.floens.chan.utils.Utils;
-import org.jsoup.Jsoup;
-import org.jsoup.nodes.Document;
-import org.jsoup.nodes.Element;
-import org.jsoup.select.Elements;
import java.io.File;
import java.io.IOException;
+import java.net.HttpCookie;
+import java.util.List;
import java.util.Locale;
import java.util.Random;
+import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import ch.boye.httpclientandroidlib.Consts;
-import ch.boye.httpclientandroidlib.Header;
-import ch.boye.httpclientandroidlib.HeaderElement;
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.client.HttpClient;
-import ch.boye.httpclientandroidlib.client.config.RequestConfig;
-import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse;
-import ch.boye.httpclientandroidlib.client.methods.HttpPost;
-import ch.boye.httpclientandroidlib.entity.ContentType;
-import ch.boye.httpclientandroidlib.entity.mime.MultipartEntityBuilder;
-import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient;
-import ch.boye.httpclientandroidlib.impl.client.HttpClientBuilder;
-import ch.boye.httpclientandroidlib.util.EntityUtils;
-
/**
* To send an reply to 4chan.
*/
public class ReplyManager {
private static final String TAG = "ReplyManager";
- private static final Pattern responsePattern = Pattern.compile("");
- private static final int POST_TIMEOUT = 10000;
-
- private static final ContentType TEXT_UTF_8 = ContentType.create(
- "text/plain", Consts.UTF_8);
+ private static final Pattern POST_THREAD_NO_PATTERN = Pattern.compile("");
+ private static final int TIMEOUT = 10000;
private final Context context;
private Reply draft;
private FileListener fileListener;
private final Random random = new Random();
+ private String userAgent;
+ OkHttpClient client;
public ReplyManager(Context context) {
this.context = context;
draft = new Reply();
+
+ client = new OkHttpClient();
+ client.setConnectTimeout(TIMEOUT, TimeUnit.MILLISECONDS);
+ client.setReadTimeout(TIMEOUT, TimeUnit.MILLISECONDS);
+ client.setWriteTimeout(TIMEOUT, TimeUnit.MILLISECONDS);
+
+ // User agent is /
+ String version = "";
+ try {
+ version = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName;
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ }
+
+ version = version.toLowerCase(Locale.ENGLISH).replace(" ", "_");
+ userAgent = context.getString(R.string.app_name) + "/" + version;
+ }
+
+ public String getUserAgent() {
+ return userAgent;
}
/**
@@ -180,75 +184,80 @@ public class ReplyManager {
public abstract void onFileLoading();
}
- public void sendPass(Pass pass, final PassListener listener) {
- Logger.i(TAG, "Sending pass login request");
-
- HttpPost httpPost = new HttpPost(ChanUrls.getPassUrl());
+ public void postPass(String token, String pin, final PassListener passListener) {
+ FormEncodingBuilder formBuilder = new FormEncodingBuilder();
- MultipartEntityBuilder entity = MultipartEntityBuilder.create();
+ formBuilder.add("act", "do_login");
- entity.addTextBody("act", "do_login");
+ formBuilder.add("id", token);
+ formBuilder.add("pin", pin);
- entity.addTextBody("id", pass.token);
- entity.addTextBody("pin", pass.pin);
+ Request.Builder request = new Request.Builder()
+ .url(ChanUrls.getPassUrl())
+ .post(formBuilder.build());
- // entity.addPart("pwd", new StringBody(reply.password));
-
- httpPost.setEntity(entity.build());
-
- sendHttpPost(httpPost, new HttpPostSendListener() {
+ makeOkHttpCall(request, new Callback() {
@Override
- public void onResponse(String responseString, HttpClient client, HttpResponse response) {
- PassResponse e = new PassResponse();
+ public void onFailure(Request request, IOException e) {
+ final PassResponse res = new PassResponse();
+ res.isError = true;
+ res.message = context.getString(R.string.pass_error);
+ runUI(new Runnable() {
+ public void run() {
+ passListener.onResponse(res);
+ }
+ });
+ }
- if (responseString == null || response == null) {
- e.isError = true;
- e.message = context.getString(R.string.pass_error);
- } else {
- e.responseData = responseString;
-
- if (responseString.contains("Your device is now authorized")) {
- e.message = "Success! Your device is now authorized.";
-
- String passId = null;
-
- Header[] cookieHeaders = response.getHeaders("Set-Cookie");
- if (cookieHeaders != null) {
- for (Header cookieHeader : cookieHeaders) {
- HeaderElement[] elements = cookieHeader.getElements();
- if (elements != null) {
- for (HeaderElement el : elements) {
- if (el != null) {
- if (el.getName().equals("pass_id")) {
- passId = el.getValue();
- }
- }
- }
+ @Override
+ public void onResponse(Response response) throws IOException {
+ if (!response.isSuccessful()) {
+ onFailure(response.request(), null);
+ return;
+ }
+ String responseString = response.body().string();
+ response.body().close();
+
+ final PassResponse res = new PassResponse();
+ if (responseString.contains("Your device is now authorized")) {
+ List cookies = response.headers("Set-Cookie");
+ String passId = null;
+ for (String cookie : cookies) {
+ try {
+ List parsedList = HttpCookie.parse(cookie);
+ for (HttpCookie parsed : parsedList) {
+ if (parsed.getName().equals("pass_id") && !parsed.getValue().equals("0")) {
+ passId = parsed.getValue();
}
}
+ } catch (IllegalArgumentException ignored) {
}
-
- if (passId != null) {
- e.passId = passId;
- } else {
- e.isError = true;
- e.message = "Could not get pass id";
- }
+ }
+ if (passId != null) {
+ res.passId = passId;
+ res.message = "Success! Your device is now authorized.";
} else {
- e.isError = true;
- if (responseString.contains("Your Token must be exactly 10 characters")) {
- e.message = "Incorrect token";
- } else if (responseString.contains("You have left one or more fields blank")) {
- e.message = "You have left one or more fields blank";
- } else if (responseString.contains("Incorrect Token or PIN")) {
- e.message = "Incorrect Token or PIN";
- } else {
- e.unknownError = true;
- }
+ res.isError = true;
+ res.message = "Could not get pass id";
+ }
+ } else {
+ res.isError = true;
+ if (responseString.contains("Your Token must be exactly 10 characters")) {
+ res.message = "Incorrect token";
+ } else if (responseString.contains("You have left one or more fields blank")) {
+ res.message = "You have left one or more fields blank";
+ } else if (responseString.contains("Incorrect Token or PIN")) {
+ res.message = "Incorrect Token or PIN";
+ } else {
+ res.unknownError = true;
}
}
- listener.onResponse(e);
+ runUI(new Runnable() {
+ public void run() {
+ passListener.onResponse(res);
+ }
+ });
}
});
}
@@ -265,58 +274,68 @@ public class ReplyManager {
public String passId;
}
- public void sendDelete(final SavedReply reply, boolean onlyImageDelete, final DeleteListener listener) {
- Logger.i(TAG, "Sending delete request: " + reply.board + ", " + reply.no);
-
- HttpPost httpPost = new HttpPost(ChanUrls.getDeleteUrl(reply.board));
-
- MultipartEntityBuilder entity = MultipartEntityBuilder.create();
-
-
- entity.addTextBody(Integer.toString(reply.no), "delete");
-
+ public void postDelete(final SavedReply reply, boolean onlyImageDelete, final DeleteListener listener) {
+ FormEncodingBuilder formBuilder = new FormEncodingBuilder();
+ formBuilder.add(Integer.toString(reply.no), "delete");
if (onlyImageDelete) {
- entity.addTextBody("onlyimgdel", "on");
+ formBuilder.add("onlyimgdel", "on");
}
+ formBuilder.add("mode", "usrdel");
+ formBuilder.add("pwd", reply.password);
- // res not necessary
+ Request.Builder request = new Request.Builder()
+ .url(ChanUrls.getDeleteUrl(reply.board))
+ .post(formBuilder.build());
- entity.addTextBody("mode", "usrdel");
- entity.addTextBody("pwd", reply.password);
-
-
- httpPost.setEntity(entity.build());
-
- sendHttpPost(httpPost, new HttpPostSendListener() {
+ makeOkHttpCall(request, new Callback() {
@Override
- public void onResponse(String responseString, HttpClient client, HttpResponse response) {
- DeleteResponse e = new DeleteResponse();
-
- if (responseString == null) {
- e.isNetworkError = true;
- } else {
- e.responseData = responseString;
-
- if (responseString.contains("You must wait longer before deleting this post")) {
- e.isUserError = true;
- e.isTooSoonError = true;
- } else if (responseString.contains("Password incorrect")) {
- e.isUserError = true;
- e.isInvalidPassword = true;
- } else if (responseString.contains("You cannot delete a post this old")) {
- e.isUserError = true;
- e.isTooOldError = true;
- } else if (responseString.contains("Updating index")) {
- e.isSuccessful = true;
+ public void onFailure(Request request, IOException e) {
+ final DeleteResponse res = new DeleteResponse();
+ res.isNetworkError = true;
+ runUI(new Runnable() {
+ @Override
+ public void run() {
+ listener.onResponse(res);
}
+ });
+ }
+
+ @Override
+ public void onResponse(Response response) throws IOException {
+ if (!response.isSuccessful()) {
+ onFailure(response.request(), null);
+ return;
+ }
+ String responseString = response.body().string();
+ response.body().close();
+
+ final DeleteResponse res = new DeleteResponse();
+ res.responseData = responseString;
+
+ if (responseString.contains("You must wait longer before deleting this post")) {
+ res.isUserError = true;
+ res.isTooSoonError = true;
+ } else if (responseString.contains("Password incorrect")) {
+ res.isUserError = true;
+ res.isInvalidPassword = true;
+ } else if (responseString.contains("You cannot delete a post this old")) {
+ res.isUserError = true;
+ res.isTooOldError = true;
+ } else if (responseString.contains("Updating index")) {
+ res.isSuccessful = true;
}
- listener.onResponse(e);
+ runUI(new Runnable() {
+ @Override
+ public void run() {
+ listener.onResponse(res);
+ }
+ });
}
});
}
- public static interface DeleteListener {
+ public interface DeleteListener {
public void onResponse(DeleteResponse response);
}
@@ -330,244 +349,139 @@ public class ReplyManager {
public String responseData = "";
}
- public void getCaptchaChallenge(final CaptchaChallengeListener listener) {
- HttpPost httpPost = new HttpPost(ChanUrls.getCaptchaFallback());
- httpPost.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36");
- httpPost.addHeader("Referer", "https://boards.4chan.org/");
- httpPost.addHeader("Cookie", "NID=67");
-
- HttpPostSendListener postListener = new HttpPostSendListener() {
- @Override
- public void onResponse(String responseString, HttpClient client, HttpResponse response) {
- if (responseString != null) {
- Document document = Jsoup.parseBodyFragment(responseString, ChanUrls.getCaptchaDomain());
- Elements images = document.select("div.fbc-challenge img");
- String imageUrl = images.first() == null ? "" : images.first().absUrl("src");
-
- Elements inputs = document.select("div.fbc-challenge input");
- String challenge = "";
- for (Element input : inputs) {
- if (input.attr("name").equals("c")) {
- challenge = input.attr("value");
- break;
- }
- }
-
- if (!TextUtils.isEmpty(imageUrl) && !TextUtils.isEmpty(challenge)) {
- listener.onChallenge(imageUrl, challenge);
- return;
- }
- }
- listener.onError();
- }
- };
-
- sendHttpPost(httpPost, postListener);
+ public void postReply(Reply reply, ReplyListener replyListener) {
+ if (reply.usePass) {
+ postReplyInternal(reply, replyListener, null);
+ } else {
+ postReplyInternal(reply, replyListener, reply.captchaResponse);
+ }
}
- public interface CaptchaChallengeListener {
- public void onChallenge(String imageUrl, String challenge);
+ private void postReplyInternal(final Reply reply, final ReplyListener replyListener, String captchaHash) {
+ reply.password = Long.toHexString(random.nextLong());
- public void onError();
- }
+ MultipartBuilder formBuilder = new MultipartBuilder();
+ formBuilder.type(MultipartBuilder.FORM);
- private void getCaptchaHash(final CaptchaHashListener listener, String challenge, String response) {
- HttpPost httpPost = new HttpPost(ChanUrls.getCaptchaFallback());
+ formBuilder.addFormDataPart("mode", "regist");
+ formBuilder.addFormDataPart("pwd", reply.password);
- MultipartEntityBuilder entity = MultipartEntityBuilder.create();
+ if (reply.resto >= 0) {
+ formBuilder.addFormDataPart("resto", String.valueOf(reply.resto));
+ }
- entity.addTextBody("c", challenge, TEXT_UTF_8);
- entity.addTextBody("response", response, TEXT_UTF_8);
+ formBuilder.addFormDataPart("name", reply.name);
+ formBuilder.addFormDataPart("email", reply.email);
- httpPost.setEntity(entity.build());
+ if (!TextUtils.isEmpty(reply.subject)) {
+ formBuilder.addFormDataPart("sub", reply.subject);
+ }
- sendHttpPost(httpPost, new HttpPostSendListener() {
- @Override
- public void onResponse(String responseString, HttpClient client, HttpResponse response) {
- if (responseString != null) {
- Document document = Jsoup.parseBodyFragment(responseString);
- Elements verificationToken = document.select("div.fbc-verification-token textarea");
- String hash = verificationToken.text();
- if (hash.length() > 0) {
- listener.onHash(hash);
- return;
- }
- }
- listener.onHash(null);
- }
- });
- }
+ formBuilder.addFormDataPart("com", reply.comment);
- private interface CaptchaHashListener {
- public void onHash(String hash);
- }
-
- public String getUserAgent() {
- String version = "";
- try {
- version = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName;
- } catch (PackageManager.NameNotFoundException e) {
- e.printStackTrace();
+ if (captchaHash != null) {
+ formBuilder.addFormDataPart("g-recaptcha-response", captchaHash);
}
- version = version.toLowerCase(Locale.ENGLISH).replace(" ", "_");
- return "Clover/" + version;
- }
- public void postReply(Reply reply) {
- OkHttpClient client = new OkHttpClient();
-
- MultipartBuilder formBuilder = new MultipartBuilder();
- formBuilder.type(MultipartBuilder.FORM);
- formBuilder.addFormDataPart("foo", "bar");
if (reply.file != null) {
- formBuilder.addFormDataPart("file", "filename", RequestBody.create(
+ formBuilder.addFormDataPart("upfile", reply.fileName, RequestBody.create(
MediaType.parse("application/octet-stream"), reply.file
));
}
- RequestBody form = formBuilder.build();
- Request request = new Request.Builder()
- .header("User-Agent", getUserAgent())
- .url("")
- .post(form)
- .build();
+ if (reply.spoilerImage) {
+ formBuilder.addFormDataPart("spoiler", "on");
+ }
+
+ Request.Builder request = new Request.Builder()
+ .url(ChanUrls.getReplyUrl(reply.board))
+ .post(formBuilder.build());
+
+ if (reply.usePass) {
+ request.addHeader("Cookie", "pass_id=" + reply.passId);
+ }
- client.newCall(request).enqueue(new Callback() {
+ makeOkHttpCall(request, new Callback() {
@Override
public void onFailure(Request request, IOException e) {
- e.printStackTrace();
+ final ReplyResponse res = new ReplyResponse();
+ res.isNetworkError = true;
+
+ runUI(new Runnable() {
+ public void run() {
+ replyListener.onResponse(res);
+ }
+ });
}
@Override
public void onResponse(Response response) throws IOException {
+ final ReplyResponse res = new ReplyResponse();
if (response.isSuccessful()) {
- Logger.test("Output = " + response.body().string());
+ onReplyPosted(response.body().string(), reply, res);
+ response.body().close();
+ } else {
+ res.isNetworkError = true;
}
+
+ runUI(new Runnable() {
+ public void run() {
+ replyListener.onResponse(res);
+ }
+ });
}
});
}
- /**
- * Send an reply off to the server.
- *
- * @param reply The reply object with all data needed, like captcha and the
- * file.
- * @param listener The listener, after server response.
- */
- public void sendReply(final Reply reply, final ReplyListener listener) {
- Logger.i(TAG, "Sending reply request: " + reply.board + ", " + reply.resto);
-
- CaptchaHashListener captchaHashListener = new CaptchaHashListener() {
- @Override
- public void onHash(String captchaHash) {
- if (captchaHash == null && !reply.usePass) {
- // Could not find a hash in the response html
- ReplyResponse e = new ReplyResponse();
- e.isUserError = true;
- e.isCaptchaError = true;
- listener.onResponse(e);
- return;
- }
-
- HttpPost httpPost = new HttpPost(ChanUrls.getReplyUrl(reply.board));
-
- MultipartEntityBuilder entity = MultipartEntityBuilder.create();
-
- reply.password = Long.toHexString(random.nextLong());
-
- entity.addTextBody("name", reply.name, TEXT_UTF_8);
- entity.addTextBody("email", reply.email, TEXT_UTF_8);
-
- entity.addTextBody("sub", reply.subject, TEXT_UTF_8);
- entity.addTextBody("com", reply.comment, TEXT_UTF_8);
-
- if (reply.resto >= 0) {
- entity.addTextBody("resto", Integer.toString(reply.resto));
- }
-
- if (reply.spoilerImage) {
- entity.addTextBody("spoiler", "on");
- }
-
- if (!reply.usePass) {
- entity.addTextBody("g-recaptcha-response", captchaHash, TEXT_UTF_8);
- }
-
- entity.addTextBody("mode", "regist");
- entity.addTextBody("pwd", reply.password);
-
- if (reply.usePass) {
- httpPost.addHeader("Cookie", "pass_id=" + reply.passId);
- }
-
- if (reply.file != null) {
- entity.addBinaryBody("upfile", reply.file, ContentType.APPLICATION_OCTET_STREAM, reply.fileName);
+ private ReplyResponse onReplyPosted(String responseString, Reply reply, ReplyResponse res) {
+ res.responseData = responseString;
+
+ if (res.responseData.contains("No file selected")) {
+ res.isUserError = true;
+ res.isFileError = true;
+ } else if (res.responseData.contains("You forgot to solve the CAPTCHA") || res.responseData.contains("You seem to have mistyped the CAPTCHA")) {
+ res.isUserError = true;
+ res.isCaptchaError = true;
+ } else if (res.responseData.toLowerCase(Locale.ENGLISH).contains("post successful")) {
+ res.isSuccessful = true;
+
+ Matcher matcher = POST_THREAD_NO_PATTERN.matcher(res.responseData);
+ int threadNo = -1;
+ int no = -1;
+ if (matcher.find()) {
+ try {
+ threadNo = Integer.parseInt(matcher.group(1));
+ no = Integer.parseInt(matcher.group(2));
+ } catch (NumberFormatException err) {
+ err.printStackTrace();
}
+ }
- httpPost.setEntity(entity.build());
-
- sendHttpPost(httpPost, new HttpPostSendListener() {
- @Override
- public void onResponse(String responseString, HttpClient client, HttpResponse response) {
- ReplyResponse e = new ReplyResponse();
-
- if (responseString == null) {
- e.isNetworkError = true;
- } else {
- e.responseData = responseString;
-
- if (responseString.contains("No file selected")) {
- e.isUserError = true;
- e.isFileError = true;
- } else if (responseString.contains("You forgot to solve the CAPTCHA")
- || responseString.contains("You seem to have mistyped the CAPTCHA")) {
- e.isUserError = true;
- e.isCaptchaError = true;
- } else if (responseString.toLowerCase(Locale.ENGLISH).contains("post successful")) {
- e.isSuccessful = true;
- }
- }
-
- if (e.isSuccessful) {
- Matcher matcher = responsePattern.matcher(e.responseData);
-
- int threadNo = -1;
- int no = -1;
- if (matcher.find()) {
- try {
- threadNo = Integer.parseInt(matcher.group(1));
- no = Integer.parseInt(matcher.group(2));
- } catch (NumberFormatException err) {
- err.printStackTrace();
- }
- }
-
- if (threadNo >= 0 && no >= 0) {
- SavedReply savedReply = new SavedReply();
- savedReply.board = reply.board;
- savedReply.no = no;
- savedReply.password = reply.password;
-
- ChanApplication.getDatabaseManager().saveReply(savedReply);
+ if (threadNo >= 0 && no >= 0) {
+ SavedReply savedReply = new SavedReply();
+ savedReply.board = reply.board;
+ savedReply.no = no;
+ savedReply.password = reply.password;
- e.threadNo = threadNo;
- e.no = no;
- } else {
- Logger.w(TAG, "No thread & no in the response");
- }
- }
+ ChanApplication.getDatabaseManager().saveReply(savedReply);
- listener.onResponse(e);
- }
- });
+ res.threadNo = threadNo;
+ res.no = no;
+ } else {
+ Logger.w(TAG, "No thread & no in the response");
}
- };
-
- if (reply.usePass) {
- captchaHashListener.onHash(null);
- } else {
- getCaptchaHash(captchaHashListener, reply.captchaChallenge, reply.captchaResponse);
}
+ return res;
+ }
+
+ private void makeOkHttpCall(Request.Builder requestBuilder, Callback callback) {
+ requestBuilder.header("User-Agent", getUserAgent());
+ Request request = requestBuilder.build();
+ client.newCall(request).enqueue(callback);
+ }
+
+ private void runUI(Runnable runnable) {
+ Utils.runOnUiThread(runnable);
}
public static interface ReplyListener {
@@ -616,55 +530,4 @@ public class ReplyManager {
*/
public int threadNo = -1;
}
-
- /**
- * Async task to send an reply to the server. Uses HttpClient. Since Android
- * 4.4 there is an updated version of HttpClient, 4.2, given with Android.
- * However, that version causes problems with file uploading. Version 4.3 of
- * HttpClient has been given with a library, that has another namespace:
- * ch.boye.httpclientandroidlib This lib also has some fixes/improvements of
- * HttpClient for Android.
- */
- private void sendHttpPost(final HttpPost post, final HttpPostSendListener listener) {
- new Thread(new Runnable() {
- @Override
- public void run() {
- RequestConfig.Builder requestBuilder = RequestConfig.custom();
- requestBuilder = requestBuilder.setConnectTimeout(POST_TIMEOUT);
- requestBuilder = requestBuilder.setConnectionRequestTimeout(POST_TIMEOUT);
-
- HttpClientBuilder httpBuilder = HttpClientBuilder.create();
- httpBuilder.setDefaultRequestConfig(requestBuilder.build());
- final CloseableHttpClient client = httpBuilder.build();
- try {
- final CloseableHttpResponse response = client.execute(post);
- final String responseString = EntityUtils.toString(response.getEntity(), "UTF-8");
- Utils.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- listener.onResponse(responseString, client, response);
- }
- });
- } catch (IOException e) {
- e.printStackTrace();
- Utils.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- listener.onResponse(null, client, null);
- }
- });
- } finally {
- try {
- client.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }).start();
- }
-
- private static interface HttpPostSendListener {
- public void onResponse(String responseString, HttpClient client, HttpResponse response);
- }
}
diff --git a/Clover/app/src/main/java/org/floens/chan/core/manager/ThreadManager.java b/Clover/app/src/main/java/org/floens/chan/core/manager/ThreadManager.java
index e6cd20b0..eb165c5a 100644
--- a/Clover/app/src/main/java/org/floens/chan/core/manager/ThreadManager.java
+++ b/Clover/app/src/main/java/org/floens/chan/core/manager/ThreadManager.java
@@ -547,7 +547,7 @@ public class ThreadManager implements Loader.LoaderListener {
final ProgressDialog dialog = ProgressDialog.show(activity, null, activity.getString(R.string.delete_wait));
- ChanApplication.getReplyManager().sendDelete(reply, onlyImageDelete, new DeleteListener() {
+ ChanApplication.getReplyManager().postDelete(reply, onlyImageDelete, new DeleteListener() {
@Override
public void onResponse(DeleteResponse response) {
dialog.dismiss();
diff --git a/Clover/app/src/main/java/org/floens/chan/core/model/Pass.java b/Clover/app/src/main/java/org/floens/chan/core/model/Pass.java
deleted file mode 100644
index 5f5317dd..00000000
--- a/Clover/app/src/main/java/org/floens/chan/core/model/Pass.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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.model;
-
-public class Pass {
- public String token = "";
- public String pin = "";
-
- public Pass(String token, String pin) {
- this.token = token;
- this.pin = pin;
- }
-}
diff --git a/Clover/app/src/main/java/org/floens/chan/core/model/Reply.java b/Clover/app/src/main/java/org/floens/chan/core/model/Reply.java
index e2781b56..4f8c3aee 100644
--- a/Clover/app/src/main/java/org/floens/chan/core/model/Reply.java
+++ b/Clover/app/src/main/java/org/floens/chan/core/model/Reply.java
@@ -31,7 +31,6 @@ public class Reply {
public int resto = 0;
public File file;
public String fileName = "";
- public String captchaChallenge = "";
public String captchaResponse = "";
public String password = "";
public boolean usePass = false;
diff --git a/Clover/app/src/main/java/org/floens/chan/ui/activity/PassSettingsActivity.java b/Clover/app/src/main/java/org/floens/chan/ui/activity/PassSettingsActivity.java
index 37bd50fa..c64b6ad6 100644
--- a/Clover/app/src/main/java/org/floens/chan/ui/activity/PassSettingsActivity.java
+++ b/Clover/app/src/main/java/org/floens/chan/ui/activity/PassSettingsActivity.java
@@ -42,7 +42,6 @@ import org.floens.chan.R;
import org.floens.chan.core.ChanPreferences;
import org.floens.chan.core.manager.ReplyManager;
import org.floens.chan.core.manager.ReplyManager.PassResponse;
-import org.floens.chan.core.model.Pass;
import org.floens.chan.utils.ThemeHelper;
import org.floens.chan.utils.Utils;
@@ -145,8 +144,7 @@ public class PassSettingsActivity extends Activity implements OnCheckedChangeLis
@Override
public boolean onPreferenceClick(Preference preference) {
if (PassSettingsActivity.instance != null) {
- Pass pass = new Pass(ChanPreferences.getPassToken(), ChanPreferences.getPassPin());
- onLoginClick(pass);
+ onLoginClick(ChanPreferences.getPassToken(), ChanPreferences.getPassPin());
}
return true;
}
@@ -159,12 +157,12 @@ public class PassSettingsActivity extends Activity implements OnCheckedChangeLis
findPreference("preference_pass_login").setTitle(TextUtils.isEmpty(ChanPreferences.getPassId()) ? R.string.pass_login : R.string.pass_logout);
}
- private void onLoginClick(Pass pass) {
+ private void onLoginClick(String token, String pin) {
if (TextUtils.isEmpty(ChanPreferences.getPassId())) {
// Login
final ProgressDialog dialog = ProgressDialog.show(getActivity(), null, "Logging in");
- ChanApplication.getReplyManager().sendPass(pass, new ReplyManager.PassListener() {
+ ChanApplication.getReplyManager().postPass(token, pin, new ReplyManager.PassListener() {
@Override
public void onResponse(PassResponse response) {
dialog.dismiss();
diff --git a/Clover/app/src/main/java/org/floens/chan/ui/fragment/ReplyFragment.java b/Clover/app/src/main/java/org/floens/chan/ui/fragment/ReplyFragment.java
index 7caee9a8..ab778e15 100644
--- a/Clover/app/src/main/java/org/floens/chan/ui/fragment/ReplyFragment.java
+++ b/Clover/app/src/main/java/org/floens/chan/ui/fragment/ReplyFragment.java
@@ -44,10 +44,9 @@ import android.widget.TextView;
import android.widget.Toast;
import android.widget.ViewFlipper;
-import com.android.volley.toolbox.NetworkImageView;
-
import org.floens.chan.ChanApplication;
import org.floens.chan.R;
+import org.floens.chan.chan.ChanUrls;
import org.floens.chan.core.ChanPreferences;
import org.floens.chan.core.manager.ReplyManager;
import org.floens.chan.core.manager.ReplyManager.ReplyResponse;
@@ -55,6 +54,7 @@ import org.floens.chan.core.model.Board;
import org.floens.chan.core.model.Loadable;
import org.floens.chan.core.model.Reply;
import org.floens.chan.ui.ViewFlipperAnimations;
+import org.floens.chan.ui.layout.CaptchaLayout;
import org.floens.chan.ui.view.LoadView;
import org.floens.chan.utils.ImageDecoder;
import org.floens.chan.utils.Logger;
@@ -63,7 +63,7 @@ import org.floens.chan.utils.Utils;
import java.io.File;
-public class ReplyFragment extends DialogFragment {
+public class ReplyFragment extends DialogFragment implements CaptchaLayout.CaptchaCallback {
private static final String TAG = "ReplyFragment";
private int page = 0;
@@ -74,9 +74,7 @@ public class ReplyFragment extends DialogFragment {
private final Reply draft = new Reply();
private boolean shouldSaveDraft = true;
- private boolean gotInitialCaptcha = false;
- private boolean gettingCaptcha = false;
- private String captchaChallenge = "";
+ private String captchaResponse;
private int defaultTextColor;
private int maxCommentCount;
@@ -94,8 +92,7 @@ public class ReplyFragment extends DialogFragment {
private EditText fileNameView;
private CheckBox spoilerImageView;
private LoadView imageViewContainer;
- private LoadView captchaContainer;
- private TextView captchaInput;
+ private CaptchaLayout captchaLayout;
private LoadView responseContainer;
private Button insertSpoiler;
private Button insertCode;
@@ -204,6 +201,9 @@ public class ReplyFragment extends DialogFragment {
}
});
showCommentCount();
+
+ String baseUrl = loadable.isThreadMode() ? ChanUrls.getThreadUrlDesktop(loadable.board, loadable.no) : ChanUrls.getBoardUrlDesktop(loadable.board);
+ captchaLayout.initCaptcha(baseUrl, ChanUrls.getCaptchaSiteKey(), ThemeHelper.getInstance().getTheme().isLightTheme, this);
} else {
Logger.e(TAG, "Loadable in ReplyFragment was null");
closeReply();
@@ -258,14 +258,7 @@ public class ReplyFragment extends DialogFragment {
imageViewContainer = (LoadView) container.findViewById(R.id.reply_image);
responseContainer = (LoadView) container.findViewById(R.id.reply_response);
- captchaContainer = (LoadView) container.findViewById(R.id.reply_captcha_container);
- captchaContainer.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- getCaptcha();
- }
- });
- captchaInput = (TextView) container.findViewById(R.id.reply_captcha);
+ captchaLayout = (CaptchaLayout) container.findViewById(R.id.captcha_layout);
cancelButton = (Button) container.findViewById(R.id.reply_cancel);
cancelButton.setOnClickListener(new OnClickListener() {
@@ -347,6 +340,19 @@ public class ReplyFragment extends DialogFragment {
}
}
+ @Override
+ public void captchaLoaded(CaptchaLayout captchaLayout) {
+ }
+
+ @Override
+ public void captchaEntered(CaptchaLayout captchaLayout, String response) {
+ captchaResponse = response;
+ if (page == 1) {
+ flipPage(2);
+ submit();
+ }
+ }
+
private void insertAtCursor(String before, String after) {
int pos = commentView.getSelectionStart();
String text = commentView.getText().toString();
@@ -412,9 +418,11 @@ public class ReplyFragment extends DialogFragment {
cancelButton.setText(R.string.close);
}
- if (page == 1 && !gotInitialCaptcha) {
- gotInitialCaptcha = true;
- getCaptcha();
+ if (page == 1) {
+ captchaLayout.load();
+ submitButton.setEnabled(captchaResponse != null);
+ } else if (page == 0) {
+ submitButton.setEnabled(true);
}
}
@@ -511,42 +519,6 @@ public class ReplyFragment extends DialogFragment {
loadView.setView(text);
}
- private void getCaptcha() {
- if (gettingCaptcha)
- return;
- gettingCaptcha = true;
-
- captchaContainer.setView(null);
- captchaInput.setText("");
-
- ChanApplication.getReplyManager().getCaptchaChallenge(new ReplyManager.CaptchaChallengeListener() {
- @Override
- public void onChallenge(String imageUrl, String challenge) {
- gettingCaptcha = false;
-
- if (context != null) {
- captchaChallenge = challenge;
-
- NetworkImageView captchaImage = new NetworkImageView(context);
- captchaImage.setImageUrl(imageUrl, ChanApplication.getVolleyImageLoader());
- captchaContainer.setView(captchaImage);
- }
- }
-
- @Override
- public void onError() {
- gettingCaptcha = false;
-
- if (context != null) {
- TextView text = new TextView(context);
- text.setGravity(Gravity.CENTER);
- text.setText(R.string.reply_captcha_load_error);
- captchaContainer.setView(text);
- }
- }
- });
- }
-
/**
* Submit button clicked at page 1
*/
@@ -561,8 +533,7 @@ public class ReplyFragment extends DialogFragment {
draft.email = emailView.getText().toString();
draft.subject = subjectView.getText().toString();
draft.comment = commentView.getText().toString();
- draft.captchaChallenge = captchaChallenge;
- draft.captchaResponse = captchaInput.getText().toString();
+ draft.captchaResponse = captchaResponse;
draft.fileName = "image";
String n = fileNameView.getText().toString();
@@ -581,13 +552,12 @@ public class ReplyFragment extends DialogFragment {
Board b = ChanApplication.getBoardManager().getBoardByValue(loadable.board);
draft.spoilerImage = b != null && b.spoilers && spoilerImageView.isChecked();
- /*ChanApplication.getReplyManager().sendReply(draft, new ReplyManager.ReplyListener() {
+ ChanApplication.getReplyManager().postReply(draft, new ReplyManager.ReplyListener() {
@Override
public void onResponse(ReplyResponse response) {
handleSubmitResponse(response);
}
- });*/
- ChanApplication.getReplyManager().postReply(draft);
+ });
}
/**
@@ -606,13 +576,13 @@ public class ReplyFragment extends DialogFragment {
submitButton.setEnabled(true);
cancelButton.setEnabled(true);
setClosable(true);
+ captchaResponse = null;
+ captchaLayout.reset();
if (ChanPreferences.getPassEnabled()) {
flipPage(0);
} else {
flipPage(1);
}
- getCaptcha();
- captchaInput.setText("");
} else if (response.isSuccessful) {
shouldSaveDraft = false;
Toast.makeText(context, R.string.reply_success, Toast.LENGTH_SHORT).show();
diff --git a/Clover/app/src/main/java/org/floens/chan/ui/layout/CaptchaLayout.java b/Clover/app/src/main/java/org/floens/chan/ui/layout/CaptchaLayout.java
new file mode 100644
index 00000000..95a8e03f
--- /dev/null
+++ b/Clover/app/src/main/java/org/floens/chan/ui/layout/CaptchaLayout.java
@@ -0,0 +1,130 @@
+/*
+ * 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.ui.layout;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.webkit.JavascriptInterface;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+
+import org.floens.chan.ChanApplication;
+import org.floens.chan.utils.IOUtils;
+import org.floens.chan.utils.Utils;
+
+public class CaptchaLayout extends WebView {
+ private CaptchaCallback callback;
+ private boolean loaded = false;
+ private String baseUrl;
+ private String siteKey;
+ private boolean lightTheme;
+
+ public CaptchaLayout(Context context) {
+ super(context);
+ }
+
+ public CaptchaLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CaptchaLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @SuppressLint("SetJavaScriptEnabled")
+ public void initCaptcha(String baseUrl, String siteKey, boolean lightTheme, CaptchaCallback callback) {
+ this.callback = callback;
+ this.baseUrl = baseUrl;
+ this.siteKey = siteKey;
+ this.lightTheme = lightTheme;
+
+ WebSettings settings = getSettings();
+ settings.setJavaScriptEnabled(true);
+ settings.setUserAgentString(ChanApplication.getReplyManager().getUserAgent());
+
+ addJavascriptInterface(new CaptchaInterface(this), "CaptchaCallback");
+ }
+
+ public void load() {
+ if (!loaded) {
+ loaded = true;
+
+ String html = IOUtils.assetAsString(getContext(), "captcha/captcha.html");
+ html = html.replace("__site_key__", siteKey);
+ html = html.replace("__theme__", lightTheme ? "light" : "dark");
+
+ loadDataWithBaseURL(baseUrl, html, "text/html", "UTF-8", null);
+ }
+ }
+
+ public void reset() {
+ if (loaded) {
+ loadUrl("javascript:grecaptcha.reset()");
+ } else {
+ load();
+ }
+ }
+
+ private void onCaptchaLoaded() {
+ callback.captchaLoaded(this);
+ }
+
+ private void onCaptchaEntered(String response) {
+ if (TextUtils.isEmpty(response)) {
+ reset();
+ } else {
+ callback.captchaEntered(this, response);
+ }
+ }
+
+ public interface CaptchaCallback {
+ public void captchaLoaded(CaptchaLayout captchaLayout);
+
+ public void captchaEntered(CaptchaLayout captchaLayout, String response);
+ }
+
+ public static class CaptchaInterface {
+ private final CaptchaLayout layout;
+
+ public CaptchaInterface(CaptchaLayout layout) {
+ this.layout = layout;
+ }
+
+ @JavascriptInterface
+ public void onCaptchaLoaded() {
+ Utils.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ layout.onCaptchaLoaded();
+ }
+ });
+ }
+
+ @JavascriptInterface
+ public void onCaptchaEntered(final String response) {
+ Utils.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ layout.onCaptchaEntered(response);
+ }
+ });
+ }
+ }
+}
diff --git a/Clover/app/src/main/java/org/floens/chan/utils/FileCache.java b/Clover/app/src/main/java/org/floens/chan/utils/FileCache.java
index 7e4c00be..8b4cb7fd 100644
--- a/Clover/app/src/main/java/org/floens/chan/utils/FileCache.java
+++ b/Clover/app/src/main/java/org/floens/chan/utils/FileCache.java
@@ -9,6 +9,8 @@ import com.squareup.okhttp.Response;
import com.squareup.okhttp.ResponseBody;
import com.squareup.okhttp.internal.Util;
+import org.floens.chan.ChanApplication;
+
import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.File;
@@ -25,8 +27,8 @@ public class FileCache {
private static final String TAG = "FileCache";
private static final ExecutorService executor = Executors.newFixedThreadPool(2);
-
private OkHttpClient httpClient;
+ private static String userAgent;
private final File directory;
private final long maxSize;
@@ -38,6 +40,7 @@ public class FileCache {
this.maxSize = maxSize;
httpClient = new OkHttpClient();
+ userAgent = ChanApplication.getReplyManager().getUserAgent();
makeDir();
calculateSize();
@@ -271,7 +274,10 @@ public class FileCache {
}
private void execute() throws Exception {
- Request request = new Request.Builder().url(url).build();
+ Request request = new Request.Builder()
+ .url(url)
+ .header("User-Agent", FileCache.userAgent)
+ .build();
call = fileCache.httpClient.newCall(request);
Response response = call.execute();
diff --git a/Clover/app/src/main/java/org/floens/chan/utils/IOUtils.java b/Clover/app/src/main/java/org/floens/chan/utils/IOUtils.java
index 1e7ff38d..074d729c 100644
--- a/Clover/app/src/main/java/org/floens/chan/utils/IOUtils.java
+++ b/Clover/app/src/main/java/org/floens/chan/utils/IOUtils.java
@@ -17,6 +17,9 @@
*/
package org.floens.chan.utils;
+import android.content.Context;
+
+import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -26,20 +29,40 @@ import java.io.StringWriter;
import java.io.Writer;
public class IOUtils {
+ public static String assetAsString(Context context, String assetName) {
+ String res = null;
+ try {
+ res = IOUtils.readString(context.getResources().getAssets().open(assetName));
+ } catch (IOException ignored) {
+ }
+ return res;
+ }
+
public static String readString(InputStream is) {
- StringWriter sw = new StringWriter();
+ Reader sr = new InputStreamReader(is);
+ Writer sw = new StringWriter();
try {
- copy(new InputStreamReader(is), sw);
- is.close();
- sw.close();
+ copy(sr, sw);
} catch (IOException e) {
e.printStackTrace();
+ } finally {
+ IOUtils.closeQuietly(sr);
+ IOUtils.closeQuietly(sw);
}
return sw.toString();
}
+ public static void closeQuietly(Closeable closeable) {
+ if (closeable != null) {
+ try {
+ closeable.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+
/**
* Copies the inputstream to the outputstream and closes both streams.
*
diff --git a/Clover/app/src/main/res/layout/reply_captcha.xml b/Clover/app/src/main/res/layout/reply_captcha.xml
deleted file mode 100644
index 776f9d81..00000000
--- a/Clover/app/src/main/res/layout/reply_captcha.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/Clover/app/src/main/res/layout/reply_view.xml b/Clover/app/src/main/res/layout/reply_view.xml
index 52ba6f36..561e8795 100644
--- a/Clover/app/src/main/res/layout/reply_view.xml
+++ b/Clover/app/src/main/res/layout/reply_view.xml
@@ -44,12 +44,10 @@ along with this program. If not, see .
-
-
-
-
+ android:layout_height="match_parent" />
diff --git a/Clover/app/src/main/res/values/strings.xml b/Clover/app/src/main/res/values/strings.xml
index 3f2d2d6a..0dcab0b7 100644
--- a/Clover/app/src/main/res/values/strings.xml
+++ b/Clover/app/src/main/res/values/strings.xml
@@ -133,7 +133,7 @@ along with this program. If not, see .
Submit
Enter the text
Error sending reply
- Wrong captcha
+ Invalid captcha
No file selected
Post Successful
Failed to load captcha