From 0f855c5ac2dccbc1c1df99b9d5ee17d6293d82df Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 27 Apr 2022 15:20:42 +0200 Subject: first commit --- mytransl/.gitignore | 1 + mytransl/build.gradle | 34 ++ mytransl/src/main/AndroidManifest.xml | 5 + .../java/com/github/stom79/mytransl/MyTransL.java | 150 ++++++++ .../github/stom79/mytransl/async/TransAsync.java | 181 ++++++++++ .../com/github/stom79/mytransl/client/Client.java | 171 +++++++++ .../mytransl/client/HttpsConnectionException.java | 52 +++ .../com/github/stom79/mytransl/client/Results.java | 29 ++ .../stom79/mytransl/client/TLSSocketFactory.java | 91 +++++ .../github/stom79/mytransl/translate/Helper.java | 129 +++++++ .../github/stom79/mytransl/translate/Params.java | 104 ++++++ .../stom79/mytransl/translate/Translate.java | 388 +++++++++++++++++++++ 12 files changed, 1335 insertions(+) create mode 100644 mytransl/.gitignore create mode 100644 mytransl/build.gradle create mode 100644 mytransl/src/main/AndroidManifest.xml create mode 100644 mytransl/src/main/java/com/github/stom79/mytransl/MyTransL.java create mode 100644 mytransl/src/main/java/com/github/stom79/mytransl/async/TransAsync.java create mode 100644 mytransl/src/main/java/com/github/stom79/mytransl/client/Client.java create mode 100644 mytransl/src/main/java/com/github/stom79/mytransl/client/HttpsConnectionException.java create mode 100644 mytransl/src/main/java/com/github/stom79/mytransl/client/Results.java create mode 100644 mytransl/src/main/java/com/github/stom79/mytransl/client/TLSSocketFactory.java create mode 100644 mytransl/src/main/java/com/github/stom79/mytransl/translate/Helper.java create mode 100644 mytransl/src/main/java/com/github/stom79/mytransl/translate/Params.java create mode 100644 mytransl/src/main/java/com/github/stom79/mytransl/translate/Translate.java (limited to 'mytransl') diff --git a/mytransl/.gitignore b/mytransl/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/mytransl/.gitignore @@ -0,0 +1 @@ +/build diff --git a/mytransl/build.gradle b/mytransl/build.gradle new file mode 100644 index 000000000..6cd6f921a --- /dev/null +++ b/mytransl/build.gradle @@ -0,0 +1,34 @@ +apply plugin: 'com.android.library' + +group = 'com.github.stom79' +android { + compileSdkVersion 31 + + + defaultConfig { + minSdkVersion 15 + targetSdkVersion 31 + versionCode 7 + versionName "3.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + buildConfigField("String","VERSION_NAME","\"${defaultConfig.versionName}\"") + } + debug{ + buildConfigField("String","VERSION_NAME","\"${defaultConfig.versionName}\"") + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation "com.google.code.gson:gson:2.8.6" +} diff --git a/mytransl/src/main/AndroidManifest.xml b/mytransl/src/main/AndroidManifest.xml new file mode 100644 index 000000000..c3342ad18 --- /dev/null +++ b/mytransl/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + diff --git a/mytransl/src/main/java/com/github/stom79/mytransl/MyTransL.java b/mytransl/src/main/java/com/github/stom79/mytransl/MyTransL.java new file mode 100644 index 000000000..4f96bc5c0 --- /dev/null +++ b/mytransl/src/main/java/com/github/stom79/mytransl/MyTransL.java @@ -0,0 +1,150 @@ +package com.github.stom79.mytransl; +/* Copyright 2017 Thomas Schneider + * + * This file is a part of MyTransL + * + * 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. + * + * MyTransL 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 MyTransL; if not, + * see . */ + + +import com.github.stom79.mytransl.async.TransAsync; +import com.github.stom79.mytransl.client.Results; +import com.github.stom79.mytransl.translate.Params; + +import java.util.Locale; + + +@SuppressWarnings({"unused", "RedundantSuppression"}) +public class MyTransL { + + public static String TAG = "MyTrans_TAG"; + private static MyTransL myTransL; + private static String libretranslateDomain; + private final translatorEngine te; + private String yandexAPIKey, deeplAPIKey, systranAPIKey, libreTranslateAPIKey; + private int timeout = 30; + private boolean obfuscation = false; + + private MyTransL(translatorEngine te) { + this.te = te; + } + + public static synchronized MyTransL getInstance(translatorEngine te) { + if (myTransL == null) + myTransL = new MyTransL(te); + return myTransL; + } + + /** + * Allows to get the current locale of the device + * + * @return locale String + */ + public static String getLibreTranslateUrl() { + return "https://" + libretranslateDomain + "/translate?"; + } + + /** + * Allows to get the current locale of the device + * + * @return locale String + */ + public static String getLocale() { + return Locale.getDefault().getLanguage(); + } + + /** + * Timeout in seconds + * + * @param timeout - int + */ + public void setTimeout(int timeout) { + this.timeout = timeout; + } + + public void setObfuscation(boolean obfuscation) { + this.obfuscation = obfuscation; + } + + public boolean isObfuscated() { + return this.obfuscation; + } + + public String getDeeplAPIKey() { + return deeplAPIKey; + } + + public void setDeeplAPIKey(String deeplAPIKey) { + this.deeplAPIKey = deeplAPIKey; + } + + public String getYandexAPIKey() { + return this.yandexAPIKey; + } + + public void setYandexAPIKey(String key) { + this.yandexAPIKey = key; + } + + public String getSystranAPIKey() { + return this.systranAPIKey; + } + + public void setSystranAPIKey(String key) { + this.systranAPIKey = key; + } + + public String getLibretranslateDomain() { + return libretranslateDomain; + } + + public void setLibretranslateDomain(String libretranslateDomain) { + MyTransL.libretranslateDomain = libretranslateDomain; + } + + public String getLibreTranslateAPIKey() { + return libreTranslateAPIKey; + } + + public void setLibreTranslateAPIKey(String libreTranslateAPIKey) { + this.libreTranslateAPIKey = libreTranslateAPIKey; + } + + /** + * Asynchronous call for the translation + * + * @param content String - Content to translate + * @param toLanguage - String the targeted language + * @param listener - Callback for the asynchronous call + */ + public void translate(final String content, final String toLanguage, Params params, final Results listener) { + new TransAsync(te, content, toLanguage, params, timeout, obfuscation, listener); + } + + /** + * Asynchronous call for the translation + * + * @param content String - Content to translate + * @param toLanguage - String the targeted language + * @param listener - Callback for the asynchronous call + */ + public void translate(final String content, Params.fType format, final String toLanguage, final Results listener) { + new TransAsync(te, content, format, toLanguage, timeout, obfuscation, listener); + } + + public enum translatorEngine { + YANDEX, + DEEPL, + SYSTRAN, + LIBRETRANSLATE + } + +} diff --git a/mytransl/src/main/java/com/github/stom79/mytransl/async/TransAsync.java b/mytransl/src/main/java/com/github/stom79/mytransl/async/TransAsync.java new file mode 100644 index 000000000..7c9052199 --- /dev/null +++ b/mytransl/src/main/java/com/github/stom79/mytransl/async/TransAsync.java @@ -0,0 +1,181 @@ +package com.github.stom79.mytransl.async; +/* Copyright 2017 Thomas Schneider + * + * This file is a part of MyTransL + * + * 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. + * + * MyTransL 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 MyTransL; if not, + * see . */ + +import android.os.Handler; +import android.os.Looper; + +import com.github.stom79.mytransl.MyTransL; +import com.github.stom79.mytransl.client.Client; +import com.github.stom79.mytransl.client.HttpsConnectionException; +import com.github.stom79.mytransl.client.Results; +import com.github.stom79.mytransl.translate.Helper; +import com.github.stom79.mytransl.translate.Params; +import com.github.stom79.mytransl.translate.Translate; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; + + +/** + * Created by @stom79 on 27/11/2017. + * Asynchronous task to get the translation + * Changed 10/01/2021 + */ + +public class TransAsync { + + private final Results listener; + private final MyTransL.translatorEngine te; + private final int timeout; + private final Translate translate; + private final boolean obfuscation; + private final String contentToSend; + private final String toLanguage; + private final Params params; + private Params.fType format; + private HttpsConnectionException e; + + public TransAsync(MyTransL.translatorEngine te, String content, Params.fType format, String toLanguage, int timeout, boolean obfuscation, Results results) { + this.listener = results; + this.te = te; + this.timeout = timeout; + this.obfuscation = obfuscation; + //An instance of the Translate class will be hydrated depending of the translator engine + translate = new Translate(); + translate.setTranslatorEngine(te); + translate.setInitialContent(content); + translate.setTargetedLanguage(toLanguage); + translate.setFormat(format); + //Obfuscation if asked + if (obfuscation) + translate.obfuscate(); + if (obfuscation) { + contentToSend = translate.getObfuscateContent(); + } else { + contentToSend = translate.getInitialContent(); + } + this.toLanguage = toLanguage; + this.params = new Params(); + + new Thread(() -> { + String response = doInBackground(); + Handler mainHandler = new Handler(Looper.getMainLooper()); + Runnable myRunnable = () -> onPostExecute(response); + mainHandler.post(myRunnable); + }).start(); + } + + public TransAsync(MyTransL.translatorEngine te, String content, String toLanguage, Params params, int timeout, boolean obfuscation, Results results) { + this.listener = results; + this.te = te; + this.timeout = timeout; + this.obfuscation = obfuscation; + //An instance of the Translate class will be hydrated depending of the translator engine + translate = new Translate(); + translate.setTranslatorEngine(te); + translate.setInitialContent(content); + translate.setTargetedLanguage(toLanguage); + //Obfuscation if asked + if (obfuscation) { + translate.obfuscate(); + } + if (obfuscation) { + contentToSend = translate.getObfuscateContent(); + } else { + contentToSend = translate.getInitialContent(); + } + this.toLanguage = toLanguage; + this.params = params; + new Thread(() -> { + String response = doInBackground(); + Handler mainHandler = new Handler(Looper.getMainLooper()); + MyTransL.getLocale(); + Runnable myRunnable = () -> onPostExecute(response); + mainHandler.post(myRunnable); + }).start(); + } + + + protected String doInBackground() { + String str_response = null; + //Some parameters + try { + String url; + + if (te == MyTransL.translatorEngine.YANDEX) { + String key = MyTransL.getInstance(te).getYandexAPIKey(); + url = Helper.getYandexAbsoluteUrl(contentToSend, key, toLanguage); + str_response = new Client().get(url, this.timeout); + } else if (te == MyTransL.translatorEngine.DEEPL) { + String key = MyTransL.getInstance(te).getDeeplAPIKey(); + url = Helper.getDeeplAbsoluteUrl(contentToSend, toLanguage, params, key); + str_response = new Client().get(url, this.timeout); + } else if (te == MyTransL.translatorEngine.SYSTRAN) { + String key = MyTransL.getInstance(te).getSystranAPIKey(); + url = Helper.getSystranAbsoluteUrl(contentToSend, key, toLanguage); + str_response = new Client().get(url, this.timeout); + } else if (te == MyTransL.translatorEngine.LIBRETRANSLATE) { + String key = MyTransL.getInstance(te).getLibreTranslateAPIKey(); + JSONObject params = new JSONObject(); + try { + params.put("source", this.params.getSource_lang()); + params.put("target", toLanguage); + params.put("q", contentToSend); + params.put("format", format); + if (key != null) { + params.put("key", key); + } + } catch (JSONException e) { + e.printStackTrace(); + } + str_response = new Client().post(MyTransL.getLibreTranslateUrl(), this.timeout, params); + } + } catch (IOException | NoSuchAlgorithmException | KeyManagementException err) { + this.e = new HttpsConnectionException(-1, err.getMessage()); + err.printStackTrace(); + } catch (HttpsConnectionException e) { + this.e = e; + } + return str_response; + } + + protected void onPostExecute(String result) { + if (this.e == null) { + //Yandex response + if (this.te == MyTransL.translatorEngine.YANDEX) { + translate.parseYandexResult(result, listener); + } else if (this.te == MyTransL.translatorEngine.DEEPL) { + translate.parseDeeplResult(result, listener); + } else if (this.te == MyTransL.translatorEngine.SYSTRAN) { + translate.parseSystranlResult(result, listener); + } else if (this.te == MyTransL.translatorEngine.LIBRETRANSLATE) { + translate.parseLibreTranslateResult(result, listener); + } + //Obfuscation if asked + if (obfuscation) { + translate.deobfuscate(); + } + listener.onSuccess(translate); + } else { + listener.onFail(this.e); + } + } + +} diff --git a/mytransl/src/main/java/com/github/stom79/mytransl/client/Client.java b/mytransl/src/main/java/com/github/stom79/mytransl/client/Client.java new file mode 100644 index 000000000..26826622d --- /dev/null +++ b/mytransl/src/main/java/com/github/stom79/mytransl/client/Client.java @@ -0,0 +1,171 @@ +package com.github.stom79.mytransl.client; +/* Copyright 2017 Thomas Schneider + * + * This file is a part of MyTransL + * + * 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. + * + * MyTransL 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 MyTransL; if not, + * see . */ + +import android.os.Build; + +import com.github.stom79.mytransl.BuildConfig; + +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; + +import javax.net.ssl.HttpsURLConnection; + +/** + * Created by @stom79 on 27/11/2017. + * Manages GET and POST calls + * Changed 10/01/2021 + */ + +public class Client { + + + private static final String USER_AGENT = "MyTransL/" + BuildConfig.VERSION_NAME + " Android/" + Build.VERSION.RELEASE; + + public Client() { + } + + + /*** + * Get call to the translator API + * @param urlConnection - String url to query + * @param timeout - int a timeout + * @return response - String + * @throws IOException - Exception + * @throws NoSuchAlgorithmException - Exception + * @throws KeyManagementException - Exception + * @throws HttpsConnectionException - Exception + */ + @SuppressWarnings({"SameParameterValue"}) + public String get(String urlConnection, int timeout) throws IOException, NoSuchAlgorithmException, KeyManagementException, HttpsConnectionException { + URL url = new URL(urlConnection); + HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url.openConnection(); + httpsURLConnection.setConnectTimeout(timeout * 1000); + httpsURLConnection.setRequestProperty("http.keepAlive", "false"); + httpsURLConnection.setRequestProperty("User-Agent", USER_AGENT); + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) + httpsURLConnection.setSSLSocketFactory(new TLSSocketFactory()); + httpsURLConnection.setRequestMethod("GET"); + //Read the reply + if (httpsURLConnection.getResponseCode() >= 200 && httpsURLConnection.getResponseCode() < 400) { + Reader in; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { + in = new BufferedReader(new InputStreamReader(httpsURLConnection.getInputStream(), StandardCharsets.UTF_8)); + } else { + //noinspection CharsetObjectCanBeUsed + in = new BufferedReader(new InputStreamReader(httpsURLConnection.getInputStream(), "UTF-8")); + } + StringBuilder sb = new StringBuilder(); + for (int c; (c = in.read()) >= 0; ) + sb.append((char) c); + httpsURLConnection.disconnect(); + in.close(); + return sb.toString(); + } else { + Reader in; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { + in = new BufferedReader(new InputStreamReader(httpsURLConnection.getErrorStream(), StandardCharsets.UTF_8)); + } else { + //noinspection CharsetObjectCanBeUsed + in = new BufferedReader(new InputStreamReader(httpsURLConnection.getErrorStream(), "UTF-8")); + } + StringBuilder sb = new StringBuilder();// TODO Auto-generated catch block + for (int c; (c = in.read()) >= 0; ) + sb.append((char) c); + httpsURLConnection.disconnect(); + throw new HttpsConnectionException(httpsURLConnection.getResponseCode(), sb.toString()); + } + } + + + /*** + * POST call to the translator API + * @param urlConnection - String url to query + * @param timeout - int a timeout + * @param jsonObject - parameters to send (JSON) + * @return response - String + * @throws IOException - Exception + * @throws NoSuchAlgorithmException - Exception + * @throws KeyManagementException - Exception + * @throws HttpsConnectionException - Exception + */ + @SuppressWarnings({"SameParameterValue", "unused", "RedundantSuppression"}) + public String post(String urlConnection, int timeout, JSONObject jsonObject) throws IOException, NoSuchAlgorithmException, KeyManagementException, HttpsConnectionException { + URL url = new URL(urlConnection); + HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url.openConnection(); + byte[] postDataBytes; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + postDataBytes = jsonObject.toString().getBytes(StandardCharsets.UTF_8); + } else { + //noinspection CharsetObjectCanBeUsed + postDataBytes = jsonObject.toString().getBytes("utf-8"); + } + httpsURLConnection.setRequestProperty("User-Agent", USER_AGENT); + httpsURLConnection.setConnectTimeout(timeout * 1000); + httpsURLConnection.setDoInput(true); + httpsURLConnection.setDoOutput(true); + httpsURLConnection.setUseCaches(false); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) + httpsURLConnection.setSSLSocketFactory(new TLSSocketFactory()); + httpsURLConnection.setRequestMethod("POST"); + httpsURLConnection.setRequestProperty("Content-Type", "application/json"); + httpsURLConnection.setRequestProperty("Accept", "application/json"); + httpsURLConnection.setRequestProperty("Content-Length", String.valueOf(postDataBytes.length)); + // Send POST output + DataOutputStream printout = new DataOutputStream(httpsURLConnection.getOutputStream()); + httpsURLConnection.getOutputStream().write(postDataBytes); + printout.flush(); + printout.close(); + //Read the reply + if (httpsURLConnection.getResponseCode() >= 200 && httpsURLConnection.getResponseCode() < 400) { + Reader in; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + in = new BufferedReader(new InputStreamReader(httpsURLConnection.getInputStream(), StandardCharsets.UTF_8)); + } else { + //noinspection CharsetObjectCanBeUsed + in = new BufferedReader(new InputStreamReader(httpsURLConnection.getInputStream(), "UTF-8")); + } + StringBuilder sb = new StringBuilder(); + for (int c; (c = in.read()) >= 0; ) + sb.append((char) c); + httpsURLConnection.disconnect(); + in.close(); + return sb.toString(); + } else { + Reader in; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + in = new BufferedReader(new InputStreamReader(httpsURLConnection.getErrorStream(), StandardCharsets.UTF_8)); + } else { + //noinspection CharsetObjectCanBeUsed + in = new BufferedReader(new InputStreamReader(httpsURLConnection.getErrorStream(), "UTF-8")); + } + StringBuilder sb = new StringBuilder(); + for (int c; (c = in.read()) >= 0; ) + sb.append((char) c); + httpsURLConnection.disconnect(); + throw new HttpsConnectionException(httpsURLConnection.getResponseCode(), sb.toString()); + } + } + +} diff --git a/mytransl/src/main/java/com/github/stom79/mytransl/client/HttpsConnectionException.java b/mytransl/src/main/java/com/github/stom79/mytransl/client/HttpsConnectionException.java new file mode 100644 index 000000000..fd4adebfe --- /dev/null +++ b/mytransl/src/main/java/com/github/stom79/mytransl/client/HttpsConnectionException.java @@ -0,0 +1,52 @@ +package com.github.stom79.mytransl.client; +/* Copyright 2017 Thomas Schneider + * + * This file is a part of MyTransL + * + * 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. + * + * MyTransL 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 MyTransL; if not, + * see . */ + +import android.os.Build; +import android.text.Html; +import android.text.SpannableString; + +/** + * Created by @stom79 on 28/11/2017. + * Manage custom Exception + * Changed 10/01/2021 + */ + +@SuppressWarnings({"unused", "RedundantSuppression"}) +public class HttpsConnectionException extends Exception { + + private final int statusCode; + private final String message; + + public HttpsConnectionException(int statusCode, String message) { + this.statusCode = statusCode; + SpannableString spannableString; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + spannableString = new SpannableString(Html.fromHtml(message, Html.FROM_HTML_MODE_LEGACY)); + } else { + spannableString = new SpannableString(Html.fromHtml(message)); + } + this.message = spannableString.toString(); + } + + public int getStatusCode() { + return statusCode; + } + + @Override + public String getMessage() { + return message; + } +} diff --git a/mytransl/src/main/java/com/github/stom79/mytransl/client/Results.java b/mytransl/src/main/java/com/github/stom79/mytransl/client/Results.java new file mode 100644 index 000000000..28cfce2b9 --- /dev/null +++ b/mytransl/src/main/java/com/github/stom79/mytransl/client/Results.java @@ -0,0 +1,29 @@ +package com.github.stom79.mytransl.client; +/* Copyright 2017 Thomas Schneider + * + * This file is a part of MyTransL + * + * 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. + * + * MyTransL 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 MyTransL; if not, + * see . */ + + +import com.github.stom79.mytransl.translate.Translate; + +/** + * Created by @stom79 on 27/11/2017. + * Handler for the results of the translation + */ + +public interface Results { + void onSuccess(Translate translate); + + void onFail(HttpsConnectionException httpsConnectionException); +} diff --git a/mytransl/src/main/java/com/github/stom79/mytransl/client/TLSSocketFactory.java b/mytransl/src/main/java/com/github/stom79/mytransl/client/TLSSocketFactory.java new file mode 100644 index 000000000..8b9f665a3 --- /dev/null +++ b/mytransl/src/main/java/com/github/stom79/mytransl/client/TLSSocketFactory.java @@ -0,0 +1,91 @@ +package com.github.stom79.mytransl.client; +/* Copyright 2017 Thomas Schneider + * + * This file is a part of MyTransL + * + * 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. + * + * MyTransL 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 MyTransL; if not, + * see . */ + + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; + +/** + * Created by @stom79 on 27/11/2017. + * Enable TLS 1.1 & 1.2 on older devices + * Changed 10/01/2021 + */ + +public class TLSSocketFactory extends SSLSocketFactory { + + private final SSLSocketFactory sSLSocketFactory; + + TLSSocketFactory() throws KeyManagementException, NoSuchAlgorithmException { + + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, null, null); + sSLSocketFactory = context.getSocketFactory(); + } + + @Override + public String[] getDefaultCipherSuites() { + return sSLSocketFactory.getDefaultCipherSuites(); + } + + @Override + public String[] getSupportedCipherSuites() { + return sSLSocketFactory.getSupportedCipherSuites(); + } + + @Override + public Socket createSocket() throws IOException { + return enableTLSOnSocket(sSLSocketFactory.createSocket()); + } + + @Override + public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { + return enableTLSOnSocket(sSLSocketFactory.createSocket(s, host, port, autoClose)); + } + + @Override + public Socket createSocket(String host, int port) throws IOException { + return enableTLSOnSocket(sSLSocketFactory.createSocket(host, port)); + } + + @Override + public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { + return enableTLSOnSocket(sSLSocketFactory.createSocket(host, port, localHost, localPort)); + } + + @Override + public Socket createSocket(InetAddress host, int port) throws IOException { + return enableTLSOnSocket(sSLSocketFactory.createSocket(host, port)); + } + + @Override + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { + return enableTLSOnSocket(sSLSocketFactory.createSocket(address, port, localAddress, localPort)); + } + + private Socket enableTLSOnSocket(Socket socket) { + if ((socket instanceof SSLSocket)) { + ((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1.1", "TLSv1.2",}); + } + return socket; + } +} diff --git a/mytransl/src/main/java/com/github/stom79/mytransl/translate/Helper.java b/mytransl/src/main/java/com/github/stom79/mytransl/translate/Helper.java new file mode 100644 index 000000000..86aaac0f6 --- /dev/null +++ b/mytransl/src/main/java/com/github/stom79/mytransl/translate/Helper.java @@ -0,0 +1,129 @@ +package com.github.stom79.mytransl.translate; +/* Copyright 2017 Thomas Schneider + * + * This file is a part of MyTransL + * + * 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. + * + * MyTransL 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 MyTransL; if not, + * see . */ + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Arrays; + +/** + * Created by Thomas on 28/11/2017. + * Some static references + * Changed 10/01/2021 + */ + +public class Helper { + + + private static final String YANDEX_BASE_URL = "https://translate.yandex.net/api/v1.5/tr.json/translate?"; + private static final String DEEPL_BASE_URL = "https://api.deepl.com/v1/translate?"; + private static final String SYSTRAN_BASE_URL = "https://api-platform.systran.net/translation/text/translate?"; + private static final String[] deeplAvailableLang = {"EN", "DE", "FR", "ES", "IT", "NL", "PL"}; + + + /*** + * Returns the URL for Yandex + * @param content String - Content to translate + * @param apikey String - The Yandex API Key + * @param toLanguage String - The targeted locale + * @return String - absolute URL for Yandex + */ + public static String getYandexAbsoluteUrl(String content, String apikey, String toLanguage) { + String key = "key=" + apikey + "&"; + toLanguage = toLanguage.replace("null", ""); + String lang = "lang=" + toLanguage + "&"; + String text; + try { + text = "text=" + URLEncoder.encode(content, "utf-8") + "&"; + } catch (UnsupportedEncodingException e) { + text = "text=" + content + "&"; + e.printStackTrace(); + } + String format = "format=html&"; + return Helper.YANDEX_BASE_URL + key + lang + format + text; + } + + + /*** + * Returns the URL for Deepl + * @param content String - Content to translate + * @param toLanguage String - The targeted locale + * @param deepLParams DeepLParams - The deepl paramaters see: https://www.deepl.com/api.html#api_reference_article + * @param apikey String - The Deepl API Key + * @return String - absolute URL for Deepl + */ + public static String getDeeplAbsoluteUrl(String content, String toLanguage, Params deepLParams, String apikey) { + String key = "&auth_key=" + apikey; + toLanguage = toLanguage.replace("null", ""); + String lang = "target_lang=" + toLanguage.toUpperCase(); + String text; + try { + text = "text=" + URLEncoder.encode(content, "utf-8") + "&"; + } catch (UnsupportedEncodingException e) { + text = "text=" + content + "&"; + e.printStackTrace(); + } + String params = ""; + if (deepLParams.isPreserve_formatting()) + params += "&preserve_formatting=1"; + else + params += "&preserve_formatting=0"; + + if (deepLParams.isSplit_sentences()) + params += "&split_sentences=1"; + else + params += "&split_sentences=0"; + + if (deepLParams.getSource_lang() != null && Arrays.asList(deeplAvailableLang).contains(deepLParams.getSource_lang().toUpperCase())) + params += "&split_sentences=" + deepLParams.getSource_lang(); + + if (deepLParams.getIgnore_tags() != null) + params += "&ignore_tags=" + deepLParams.getIgnore_tags(); + + if (deepLParams.getTag_handling() != null) + params += "&tag_handling=" + deepLParams.getTag_handling(); + + if (deepLParams.getNon_splitting_tags() != null) + params += "&tag_handling=" + deepLParams.getNon_splitting_tags(); + + + return Helper.DEEPL_BASE_URL + text + lang + params + key; + } + + + /*** + * Returns the URL for Systran + * @param content String - Content to translate + * @param toLanguage String - The targeted locale + * @param apikey String - The Systran API Key + * @return String - absolute URL for Systran + */ + public static String getSystranAbsoluteUrl(String content, String apikey, String toLanguage) { + String key = "key=" + apikey + "&"; + String from = "source=auto&"; + toLanguage = toLanguage.replace("null", ""); + String lang = "target=" + toLanguage + "&"; + String text; + try { + text = "input=" + URLEncoder.encode(content, "utf-8") + "&"; + } catch (UnsupportedEncodingException e) { + text = "input=" + content + "&"; + e.printStackTrace(); + } + String encoding = "encoding=utf-8&"; + return Helper.SYSTRAN_BASE_URL + key + from + lang + encoding + text; + } + +} diff --git a/mytransl/src/main/java/com/github/stom79/mytransl/translate/Params.java b/mytransl/src/main/java/com/github/stom79/mytransl/translate/Params.java new file mode 100644 index 000000000..fba8eec63 --- /dev/null +++ b/mytransl/src/main/java/com/github/stom79/mytransl/translate/Params.java @@ -0,0 +1,104 @@ +package com.github.stom79.mytransl.translate; +/* Copyright 2018 Thomas Schneider + * + * This file is a part of MyTransL + * + * 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. + * + * MyTransL 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 MyTransL; if not, + * see . */ + +import com.google.gson.annotations.SerializedName; + +@SuppressWarnings({"unused", "RedundantSuppression"}) +public class Params { + + private String source_lang; + private String tag_handling; + private String non_splitting_tags; + private String ignore_tags; + private boolean split_sentences = true; + private boolean preserve_formatting = false; + private Params.fType format; + + public Params.fType getFormat() { + if (format == null) { + format = Params.fType.TEXT; + } + return format; + } + + public void setFormat(Params.fType format) { + this.format = format; + } + + public String getSource_lang() { + return source_lang; + } + + public void setSource_lang(String source_lang) { + this.source_lang = source_lang; + } + + public String getTag_handling() { + return tag_handling; + } + + public void setTag_handling(String tag_handling) { + this.tag_handling = tag_handling; + } + + public String getNon_splitting_tags() { + return non_splitting_tags; + } + + public void setNon_splitting_tags(String non_splitting_tags) { + this.non_splitting_tags = non_splitting_tags; + } + + public String getIgnore_tags() { + return ignore_tags; + } + + public void setIgnore_tags(String ignore_tags) { + this.ignore_tags = ignore_tags; + } + + public boolean isSplit_sentences() { + return split_sentences; + } + + public void setSplit_sentences(boolean split_sentences) { + this.split_sentences = split_sentences; + } + + public boolean isPreserve_formatting() { + return preserve_formatting; + } + + public void setPreserve_formatting(boolean preserve_formatting) { + this.preserve_formatting = preserve_formatting; + } + + public enum fType { + @SerializedName("TEXT") + TEXT("text"), + @SerializedName("HTML") + HTML("html"); + private final String value; + + fType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } +} diff --git a/mytransl/src/main/java/com/github/stom79/mytransl/translate/Translate.java b/mytransl/src/main/java/com/github/stom79/mytransl/translate/Translate.java new file mode 100644 index 000000000..6bccabff0 --- /dev/null +++ b/mytransl/src/main/java/com/github/stom79/mytransl/translate/Translate.java @@ -0,0 +1,388 @@ +package com.github.stom79.mytransl.translate; +/* Copyright 2017 Thomas Schneider + * + * This file is a part of MyTransL + * + * 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. + * + * MyTransL 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 MyTransL; if not, + * see . */ + + +import android.os.Build; +import android.text.Html; +import android.text.SpannableString; +import android.util.Patterns; + +import com.github.stom79.mytransl.MyTransL; +import com.github.stom79.mytransl.client.HttpsConnectionException; +import com.github.stom79.mytransl.client.Results; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +/** + * Created by @stom79 on 28/11/2017. + * The class which manages the replies + * Changed 10/01/2021 + */ + +@SuppressWarnings({"unused", "RedundantSuppression"}) +public class Translate { + + private static final Pattern hashtagPattern = Pattern.compile("(#[\\w_À-ú-]+)"); + private static final Pattern mentionPattern = Pattern.compile("(@[\\w]+)"); + private static final Pattern mentionOtherInstancePattern = Pattern.compile("(@[\\w]*@[\\w.-]+)"); + private final Translate translate; + private String targetedLanguage; + private String initialLanguage; + private MyTransL.translatorEngine translatorEngine; + private String initialContent; + private String obfuscateContent; + private String translatedContent; + private Params.fType format; + private HashMap tagConversion, mentionConversion, urlConversion, mailConversion; + + public Translate() { + this.translate = this; + } + + private static String replacer(StringBuffer outBuffer) throws UnsupportedEncodingException { + String data = outBuffer.toString(); + data = data.replaceAll("%(?![0-9a-fA-F]{2})", "%25"); + data = data.replaceAll("\\+", "%2B"); + data = URLDecoder.decode(data, "utf-8"); + return data; + } + + public Params.fType getFormat() { + return format; + } + + public void setFormat(Params.fType format) { + this.format = format; + } + + public String getTargetedLanguage() { + return targetedLanguage; + } + + public void setTargetedLanguage(String targetedLanguage) { + this.targetedLanguage = targetedLanguage; + } + + public String getInitialLanguage() { + return initialLanguage; + } + + private void setInitialLanguage(String initialLanguage) { + this.initialLanguage = initialLanguage; + } + + public String getInitialContent() { + return initialContent; + } + + public void setInitialContent(String initialContent) { + this.initialContent = initialContent; + } + + public String getTranslatedContent() { + return translatedContent; + } + + private void setTranslatedContent(String translatedContent) { + this.translatedContent = translatedContent; + } + + public MyTransL.translatorEngine getTranslatorEngine() { + return translatorEngine; + } + + public void setTranslatorEngine(MyTransL.translatorEngine translatorEngine) { + this.translatorEngine = translatorEngine; + } + + public HashMap getTagConversion() { + return this.tagConversion; + } + + public HashMap getMentionConversion() { + return this.mentionConversion; + } + + public HashMap getUrlConversion() { + return this.urlConversion; + } + + public HashMap getMailConversion() { + return this.mailConversion; + } + + public String getObfuscateContent() { + return this.obfuscateContent; + } + + public void obfuscate() { + + this.tagConversion = new HashMap<>(); + this.mentionConversion = new HashMap<>(); + this.urlConversion = new HashMap<>(); + this.mailConversion = new HashMap<>(); + SpannableString spannableString; + String content = this.translate.getInitialContent(); + content = content.replaceAll("\n", "
"); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + spannableString = new SpannableString(Html.fromHtml(content, Html.FROM_HTML_MODE_LEGACY)); + } else { + spannableString = new SpannableString(Html.fromHtml(content)); + } + String text = spannableString.toString(); + Matcher matcher; + + //Mentions with instances (@name@domain) will be replaced by __o0__, __o1__, etc. + int i = 0; + matcher = mentionOtherInstancePattern.matcher(text); + while (matcher.find()) { + String key = "$o" + i; + String value = matcher.group(0); + if (value != null) { + this.mentionConversion.put(key, value); + text = text.replace(value, key); + } + i++; + } + //Extracts Emails + matcher = Patterns.EMAIL_ADDRESS.matcher(text); + i = 0; + //replaces them by a kind of variable which shouldn't be translated ie: __e0__, __e1__, etc. + while (matcher.find()) { + String key = "$e" + i; + String value = matcher.group(0); + if (value != null) { + this.mailConversion.put(key, value); + text = text.replace(value, key); + } + i++; + } + + //Same for mentions with __m0__, __m1__, etc. + i = 0; + matcher = mentionPattern.matcher(text); + while (matcher.find()) { + String key = "$m" + i; + String value = matcher.group(0); + if (value != null) { + this.mentionConversion.put(key, value); + text = text.replace(value, key); + } + i++; + } + + //Extracts urls + matcher = Patterns.WEB_URL.matcher(text); + i = 0; + //replaces them by a kind of variable which shouldn't be translated ie: __u0__, __u1__, etc. + while (matcher.find()) { + String key = "$u" + i; + String value = matcher.group(0); + int end = matcher.end(); + if (spannableString.length() > end && spannableString.charAt(end) == '/') { + text = spannableString.toString().substring(0, end). + concat(spannableString.toString().substring(end + 1, spannableString.length())); + } + if (value != null) { + this.urlConversion.put(key, value); + text = text.replace(value, key); + } + i++; + } + i = 0; + //Same for tags with __t0__, __t1__, etc. + matcher = hashtagPattern.matcher(text); + while (matcher.find()) { + String key = "$t" + i; + String value = matcher.group(0); + if (value != null) { + this.tagConversion.put(key, value); + text = text.replace(value, key); + } + i++; + } + this.obfuscateContent = text; + } + + public void deobfuscate() { + String aJsonString = null; + try { + if (translatorEngine == MyTransL.translatorEngine.YANDEX) + aJsonString = yandexTranslateToText(translatedContent); + else + aJsonString = translatedContent; + if (aJsonString != null) { + if (this.urlConversion != null) { + Iterator> itU = this.urlConversion.entrySet().iterator(); + while (itU.hasNext()) { + Map.Entry pair = itU.next(); + aJsonString = aJsonString.replace(pair.getKey(), pair.getValue()); + itU.remove(); + } + } + if (this.tagConversion != null) { + Iterator> itT = this.tagConversion.entrySet().iterator(); + while (itT.hasNext()) { + Map.Entry pair = itT.next(); + aJsonString = aJsonString.replace(pair.getKey(), pair.getValue()); + itT.remove(); + } + } + if (this.mentionConversion != null) { + Iterator> itM = this.mentionConversion.entrySet().iterator(); + while (itM.hasNext()) { + Map.Entry pair = itM.next(); + aJsonString = aJsonString.replace(pair.getKey(), pair.getValue()); + itM.remove(); + } + } + if (this.mailConversion != null) { + Iterator> itE = this.mailConversion.entrySet().iterator(); + while (itE.hasNext()) { + Map.Entry pair = itE.next(); + aJsonString = aJsonString.replace(pair.getKey(), pair.getValue()); + itE.remove(); + } + } + } + } catch (UnsupportedEncodingException | IllegalArgumentException e) { + e.printStackTrace(); + } + if (aJsonString != null) + translatedContent = aJsonString; + } + + private String yandexTranslateToText(String text) throws UnsupportedEncodingException { + if (text == null) + return null; + /* The one instance where I've seen this happen, + the special tag was originally a hashtag ("__t1__"), + that Yandex decided to change to a "__q1 - __". + */ + text = text.replaceAll("__q(\\d+) - __", "\\$t$1"); + // Noticed this in the very same toot + text = text.replace("&", "&"); + text = replacer(new StringBuffer(text)); + return text; + } + + /*** + * Method to parse result coming from the Yandex translator + * More about Yandex translate API - https://tech.yandex.com/translate/ + * @param response String - Response of the engine translator + * @param listener - Results Listener + */ + public void parseYandexResult(String response, Results listener) { + translate.setTranslatorEngine(MyTransL.translatorEngine.YANDEX); + try { + JSONObject translationJson = new JSONObject(response); + //Retrieves the translated content + JSONArray aJsonArray = translationJson.getJSONArray("text"); + String aJsonString = aJsonArray.get(0).toString(); + aJsonString = aJsonString.replace("&", "&"); + aJsonString = replacer(new StringBuffer(aJsonString)); + translate.setTranslatedContent(aJsonString); + //Retrieves the translation direction + String translationDirection = translationJson.get("lang").toString(); + String[] td = translationDirection.split("-"); + translate.setInitialLanguage(td[0]); + translate.setTargetedLanguage(td[1]); + } catch (JSONException | UnsupportedEncodingException e1) { + HttpsConnectionException httpsConnectionException = new HttpsConnectionException(-1, e1.getMessage()); + listener.onFail(httpsConnectionException); + } + } + + /*** + * Method to parse result coming from the Deepl translator + * More about Deepl translate API - https://www.deepl.com/api-reference.html + * @param response String - Response of the engine translator + * @param listener - Results Listener + */ + public void parseLibreTranslateResult(String response, Results listener) { + translate.setTranslatorEngine(MyTransL.translatorEngine.DEEPL); + try { + JSONObject translationJson = new JSONObject(response); + //Retrieves the translated content + translate.setTranslatedContent(translationJson.getString("translatedText")); + //Retrieves the initial language + translate.setInitialLanguage(initialLanguage); + } catch (JSONException e1) { + e1.printStackTrace(); + HttpsConnectionException httpsConnectionException = new HttpsConnectionException(-1, e1.getMessage()); + listener.onFail(httpsConnectionException); + } + } + + + /*** + * Method to parse result coming from the Deepl translator + * More about Deepl translate API - https://www.deepl.com/api-reference.html + * @param response String - Response of the engine translator + * @param listener - Results Listener + */ + public void parseDeeplResult(String response, Results listener) { + translate.setTranslatorEngine(MyTransL.translatorEngine.DEEPL); + try { + JSONObject translationJson = new JSONObject(response); + //Retrieves the translated content + JSONArray aJsonArray = translationJson.getJSONArray("translations"); + JSONObject aJsonString = aJsonArray.getJSONObject(0); + translate.setTranslatedContent(aJsonString.getString("text")); + //Retrieves the initial language + translate.setInitialLanguage(initialLanguage); + } catch (JSONException e1) { + e1.printStackTrace(); + HttpsConnectionException httpsConnectionException = new HttpsConnectionException(-1, e1.getMessage()); + listener.onFail(httpsConnectionException); + } + } + + + /*** + * Method to parse result coming from the Systrans translator + * More about Systran translate API - https://platform.systran.net/reference/translation + * @param response String - Response of the engine translator + * @param listener - Results Listener + */ + public void parseSystranlResult(String response, Results listener) { + translate.setTranslatorEngine(MyTransL.translatorEngine.SYSTRAN); + try { + JSONObject translationJson = new JSONObject(response); + //Retrieves the translated content + JSONArray aJsonArray = translationJson.getJSONArray("outputs"); + JSONObject aJsonString = aJsonArray.getJSONObject(0); + translate.setTranslatedContent(aJsonString.getString("output")); + //Retrieves the initial language + translate.setInitialLanguage(initialLanguage); + } catch (JSONException e1) { + e1.printStackTrace(); + HttpsConnectionException httpsConnectionException = new HttpsConnectionException(-1, e1.getMessage()); + listener.onFail(httpsConnectionException); + } + } +} -- cgit v1.2.3