diff --git a/.github/scripts/build-relaybaton.sh b/.github/scripts/build-relaybaton.sh
new file mode 100644
index 000000000..f85ea3037
--- /dev/null
+++ b/.github/scripts/build-relaybaton.sh
@@ -0,0 +1,58 @@
+#!/usr/bin/env bash
+
+DEPS=$ANDROID_HOME/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/bin
+
+ANDROID_ARM_CC=$DEPS/armv7a-linux-androideabi16-clang
+ANDROID_ARM_STRIP=$DEPS/arm-linux-androideabi-strip
+
+ANDROID_ARM64_CC=$DEPS/aarch64-linux-android21-clang
+ANDROID_ARM64_STRIP=$DEPS/aarch64-linux-android-strip
+
+ANDROID_X86_CC=$DEPS/i686-linux-android16-clang
+ANDROID_X86_STRIP=$DEPS/i686-linux-android-strip
+
+ANDROID_X86_64_CC=$DEPS/x86_64-linux-android21-clang
+ANDROID_X86_64_STRIP=$DEPS/x86_64-linux-android-strip
+
+git clone https://github.com/cloudflare/tls-tris -b pwu/esni tls
+
+BASEDIR=$(realpath tls/_dev)
+
+GOENV="$(go env GOHOSTOS)_$(go env GOHOSTARCH)"
+
+BUILD_DIR=${BASEDIR}/GOROOT make -f $BASEDIR/Makefile >&2
+
+export GOROOT="$BASEDIR/GOROOT/$GOENV"
+
+export GO111MOD=on
+export CGO_ENABLED=1
+export GOOS=android
+
+PKG="github.com/iyouport-org/relaybaton/main"
+OUTPUT="relaybaton"
+LIB_OUTPUT="lib$OUTPUT.so"
+AAR_OUTPUT="$OUTPUT.aar"
+
+go get -v $PKG
+
+ROOT="relaybaton/src/main/jniLibs"
+
+DIR="$ROOT/armeabi-v7a"
+mkdir -p $DIR
+env CC=$ANDROID_ARM_CC GOARCH=arm GOARM=7 go build -o $DIR/$LIB_OUTPUT -v $PKG
+$ANDROID_ARM_STRIP $DIR/$LIB_OUTPUT
+
+DIR="$ROOT/arm64-v8a"
+mkdir -p $DIR
+env CC=$ANDROID_ARM64_CC GOARCH=arm64 go build -o $DIR/$LIB_OUTPUT -v $PKG
+$ANDROID_ARM64_STRIP $DIR/$LIB_OUTPUT
+
+DIR="$ROOT/x86"
+mkdir -p $DIR
+env CC=$ANDROID_X86_CC GOARCH=386 go build -o $DIR/$LIB_OUTPUT -v $PKG
+$ANDROID_X86_STRIP $DIR/$LIB_OUTPUT
+
+DIR="$ROOT/x86_64"
+mkdir -p $DIR
+env CC=$ANDROID_X86_64_CC GOARCH=amd64 go build -o $DIR/$LIB_OUTPUT -v $PKG
+$ANDROID_X86_64_STRIP $DIR/$LIB_OUTPUT
\ No newline at end of file
diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml
index 448f116a6..033b1a0be 100644
--- a/.github/workflows/dev.yml
+++ b/.github/workflows/dev.yml
@@ -122,11 +122,33 @@ jobs:
gomobile init
- env GO111MODULE=off gomobile bind -v -ldflags='-s -w' github.com/2dust/AndroidLibV2rayLite
+ env GO111MODULE=off gomobile bind -v -ldflags='-s -w' github.com/2dust/AndroidLibV2rayLite
- uses: actions/upload-artifact@master
with:
name: libv2ray
path: libv2ray.aar
+ rbBuild:
+ name: RelayBaton Build
+ runs-on: ubuntu-latest
+ if: "contains(github.event.head_commit.message, '[rb]')"
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-go@v1
+ with:
+ go-version: 1.14
+ - uses: actions/setup-java@v1
+ with:
+ java-version: 1.8
+ - name: Build RelayBaton
+ run: bash .github/scripts/build-relaybaton.sh
+ - name: package
+ run: |
+ ./gradlew relaybaton:assembleRelease
+ ls relaybaton/build/outputs/aar
+ - uses: actions/upload-artifact@master
+ with:
+ name: relaybaton
+ path: relaybaton/build/outputs/aar
ssBuild:
name: SS-Rust Build
runs-on: ubuntu-latest
diff --git a/TMessagesProj/build.gradle b/TMessagesProj/build.gradle
index 1689a85a7..40703acfe 100644
--- a/TMessagesProj/build.gradle
+++ b/TMessagesProj/build.gradle
@@ -306,6 +306,7 @@ android {
sourceSets.full {
dependencies {
implementation files('libs/libv2ray.aar')
+ implementation files('libs/relaybaton-release.aar')
implementation files('libs/ss-rust-release.aar')
implementation files('libs/ssr-libev-release.aar')
}
diff --git a/TMessagesProj/libs/relaybaton-release.aar b/TMessagesProj/libs/relaybaton-release.aar
new file mode 100644
index 000000000..3ab042095
Binary files /dev/null and b/TMessagesProj/libs/relaybaton-release.aar differ
diff --git a/TMessagesProj/src/main/AndroidManifest.xml b/TMessagesProj/src/main/AndroidManifest.xml
index 2d8749ab2..0a5cb0416 100644
--- a/TMessagesProj/src/main/AndroidManifest.xml
+++ b/TMessagesProj/src/main/AndroidManifest.xml
@@ -205,6 +205,7 @@
+
links = new ArrayList<>();
- gatherLinks(links, text, LinkifyPort.PROXY_PATTERN, new String[]{VMESS_PROTOCOL, VMESS1_PROTOCOL, SS_PROTOCOL, SSR_PROTOCOL}, sUrlMatchFilter);
+ gatherLinks(links, text, LinkifyPort.PROXY_PATTERN, new String[]{VMESS_PROTOCOL, VMESS1_PROTOCOL, SS_PROTOCOL, SSR_PROTOCOL, RB_PROTOCOL}, sUrlMatchFilter);
pruneOverlaps(links);
if (links.size() == 0) {
return false;
@@ -3079,7 +3080,7 @@ public static class LinkMovementMethodMy extends LinkMovementMethod {
detail = LocaleController.getString("UseProxyPort", R.string.UseProxyPort);
} else if (a == 2) {
text = info.bean.getPassword();
- detail = LocaleController.getString("SSPassword", R.string.SSPassword);
+ detail = LocaleController.getString("UseProxyPassword", R.string.UseProxyPassword);
} else if (a == 3) {
text = info.bean.getMethod();
detail = LocaleController.getString("SSMethod", R.string.SSMethod);
@@ -3275,6 +3276,108 @@ public static class LinkMovementMethodMy extends LinkMovementMethod {
builder.show();
}
+ public static void showRelayBatonAlert(Context activity, final SharedConfig.RelayBatonProxy info) {
+ BottomSheet.Builder builder = new BottomSheet.Builder(activity);
+ final Runnable dismissRunnable = builder.getDismissRunnable();
+
+ builder.setApplyTopPadding(false);
+ builder.setApplyBottomPadding(false);
+ LinearLayout linearLayout = new LinearLayout(activity);
+ builder.setCustomView(linearLayout);
+ linearLayout.setOrientation(LinearLayout.VERTICAL);
+ for (int a = 0; a < 5; a++) {
+ String text = null;
+ String detail = null;
+ if (a == 0) {
+ text = info.bean.getServer();
+ detail = LocaleController.getString("UseProxyAddress", R.string.UseProxyAddress);
+ } else if (a == 1) {
+ text = "" + info.bean.getUsername();
+ detail = LocaleController.getString("UseProxyPort", R.string.UseProxyUsername);
+ } else if (a == 2) {
+ text = info.bean.getPassword();
+ detail = LocaleController.getString("UseProxyPassword", R.string.UseProxyPassword);
+ } else if (a == 3) {
+ text = info.bean.getEsni() ? "Y" : "N";
+ detail = LocaleController.getString("ESNI", R.string.ESNI);
+ } else {
+ text = LocaleController.getString("Checking", R.string.Checking);
+ detail = LocaleController.getString("Checking", R.string.Checking);
+ }
+ if (TextUtils.isEmpty(text)) {
+ continue;
+ }
+ TextDetailSettingsCell cell = new TextDetailSettingsCell(activity);
+ cell.setTextAndValue(text, detail, true);
+ cell.getTextView().setTextColor(Theme.getColor(Theme.key_dialogTextBlack));
+ cell.getValueTextView().setTextColor(Theme.getColor(Theme.key_dialogTextGray3));
+ linearLayout.addView(cell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
+ AtomicInteger count = new AtomicInteger();
+ if (a == 4) {
+ info.start();
+ RequestTimeDelegate callback = new RequestTimeDelegate() {
+ @Override
+ public void run(long time) {
+ int c = count.getAndIncrement();
+ String colorKey;
+ if (time != -1) {
+ info.stop();
+ cell.setTextAndValue(LocaleController.getString("Available", R.string.Available), LocaleController.formatString("Ping", R.string.Ping, time), true);
+ colorKey = Theme.key_windowBackgroundWhiteGreenText;
+ } else if (c < 3) {
+ ConnectionsManager.getInstance(UserConfig.selectedAccount).checkProxy(info.address, info.port, "", "", "", t -> AndroidUtilities.runOnUIThread(() -> run(t), 500));
+ colorKey = Theme.key_windowBackgroundWhiteGreenText;
+ } else {
+ info.stop();
+ cell.setTextAndValue(LocaleController.getString("Unavailable", R.string.Unavailable), LocaleController.getString("Unavailable", R.string.Unavailable), true);
+ colorKey = Theme.key_windowBackgroundWhiteRedText4;
+ }
+ cell.getValueTextView().setTextColor(Theme.getColor(colorKey));
+ }
+
+ };
+
+ ConnectionsManager.getInstance(UserConfig.selectedAccount).checkProxy(info.address, info.port, "", "", "", time -> AndroidUtilities.runOnUIThread(() -> callback.run(time)));
+
+ }
+ }
+
+ PickerBottomLayout pickerBottomLayout = new PickerBottomLayout(activity, false);
+ pickerBottomLayout.setBackgroundColor(Theme.getColor(Theme.key_dialogBackground));
+ linearLayout.addView(pickerBottomLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT | Gravity.BOTTOM));
+ pickerBottomLayout.cancelButton.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0);
+ pickerBottomLayout.cancelButton.setTextColor(Theme.getColor(Theme.key_dialogTextBlue2));
+ pickerBottomLayout.cancelButton.setText(LocaleController.getString("Cancel", R.string.Cancel).toUpperCase());
+ pickerBottomLayout.cancelButton.setOnClickListener(view -> {
+ info.stop();
+ dismissRunnable.run();
+ });
+ pickerBottomLayout.doneButtonTextView.setTextColor(Theme.getColor(Theme.key_dialogTextBlue2));
+ pickerBottomLayout.doneButton.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0);
+ pickerBottomLayout.doneButtonBadgeTextView.setVisibility(View.GONE);
+ pickerBottomLayout.middleButtonTextView.setText(LocaleController.getString("Save", R.string.Save).toUpperCase());
+ pickerBottomLayout.middleButton.setVisibility(View.VISIBLE);
+ pickerBottomLayout.middleButton.setOnClickListener((it) -> {
+ SharedConfig.addProxy(info);
+
+ NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.proxySettingsChanged);
+
+ dismissRunnable.run();
+
+ });
+ pickerBottomLayout.doneButtonTextView.setText(LocaleController.getString("ConnectingConnectProxy", R.string.ConnectingConnectProxy).toUpperCase());
+ pickerBottomLayout.doneButton.setOnClickListener(v -> {
+
+ SharedConfig.setCurrentProxy(SharedConfig.addProxy(info));
+
+ NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.proxySettingsChanged);
+
+ dismissRunnable.run();
+
+ });
+ builder.show();
+ }
+
@SuppressLint("PrivateApi")
public static String getSystemProperty(String key) {
try {
diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/LinkifyPort.java b/TMessagesProj/src/main/java/org/telegram/messenger/LinkifyPort.java
index 80b015fb5..05cb53903 100644
--- a/TMessagesProj/src/main/java/org/telegram/messenger/LinkifyPort.java
+++ b/TMessagesProj/src/main/java/org/telegram/messenger/LinkifyPort.java
@@ -156,7 +156,7 @@ public class LinkifyPort {
private static final String DOMAIN_NAME_STR = "(" + HOST_NAME + "|" + IP_ADDRESS_STRING + ")";
private static final Pattern DOMAIN_NAME = Pattern.compile(DOMAIN_NAME_STR);
private static final String PROTOCOL = "(?i:http|https|ton|tg)://";
- private static final String PROXY_PROTOCOL = "(?i:vmess|vmess1|ss|ssr)://";
+ private static final String PROXY_PROTOCOL = "(?i:vmess|vmess1|ss|ssr|rb)://";
private static final String WORD_BOUNDARY = "(?:\\b|$|^)";
private static final String USER_INFO = "(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)"
+ "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_"
diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/SharedConfig.java b/TMessagesProj/src/main/java/org/telegram/messenger/SharedConfig.java
index 52203cc3f..26e03a2fe 100644
--- a/TMessagesProj/src/main/java/org/telegram/messenger/SharedConfig.java
+++ b/TMessagesProj/src/main/java/org/telegram/messenger/SharedConfig.java
@@ -20,6 +20,7 @@ import android.util.Base64;
import android.util.SparseArray;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import com.v2ray.ang.V2RayConfig;
import com.v2ray.ang.dto.AngConfig;
@@ -41,6 +42,7 @@ import java.util.LinkedList;
import cn.hutool.core.util.StrUtil;
import okhttp3.HttpUrl;
import tw.nekomimi.nekogram.ProxyManager;
+import tw.nekomimi.nekogram.RelayBatonLoader;
import tw.nekomimi.nekogram.ShadowsocksLoader;
import tw.nekomimi.nekogram.ShadowsocksRLoader;
import tw.nekomimi.nekogram.VmessLoader;
@@ -51,6 +53,7 @@ import tw.nekomimi.nekogram.utils.FileUtil;
import tw.nekomimi.nekogram.utils.ThreadUtil;
import tw.nekomimi.nekogram.utils.UIUtil;
+import static com.v2ray.ang.V2RayConfig.RB_PROTOCOL;
import static com.v2ray.ang.V2RayConfig.SSR_PROTOCOL;
import static com.v2ray.ang.V2RayConfig.SS_PROTOCOL;
@@ -637,7 +640,7 @@ public class SharedConfig {
}
- @Override
+ @Override
public void start() {
if (loader != null) return;
@@ -841,6 +844,121 @@ public class SharedConfig {
}
+ public static class RelayBatonProxy extends ExternalSocks5Proxy {
+
+ public RelayBatonLoader.Bean bean;
+ public RelayBatonLoader loader;
+
+ public RelayBatonProxy(String rbLink) {
+
+ this(RelayBatonLoader.Bean.Companion.parse(rbLink));
+
+ }
+
+ public RelayBatonProxy(RelayBatonLoader.Bean bean) {
+
+ this.bean = bean;
+
+ if (BuildVars.isMini) {
+
+ throw new RuntimeException(LocaleController.getString("MiniVersionAlert", R.string.MiniVersionAlert));
+
+ }
+
+ }
+
+ @Override
+ public String getAddress() {
+ return bean.getServer();
+ }
+
+ @Override
+ public boolean isStarted() {
+
+ return loader != null;
+
+ }
+
+ @Override
+ public void start() {
+
+ if (loader != null) return;
+
+ port = ProxyManager.getPortForBean(bean);
+ RelayBatonLoader loader = new RelayBatonLoader();
+ loader.initConfig(bean, port);
+
+ loader.start();
+
+ this.loader = loader;
+
+ if (SharedConfig.proxyEnabled && SharedConfig.currentProxy == this) {
+
+ ConnectionsManager.setProxySettings(true, address, port, username, password, secret);
+
+ }
+
+ }
+
+ @Override
+ public void stop() {
+
+ if (loader != null) {
+
+ RelayBatonLoader loader = this.loader;
+
+ this.loader = null;
+
+ loader.stop();
+
+ }
+
+ }
+
+ @Override
+ public String toUrl() {
+ return bean.toString();
+ }
+
+ @Override
+ public String getRemarks() {
+ return bean.getRemarks();
+ }
+
+ @Override
+ public void setRemarks(String remarks) {
+ bean.setRemarks(remarks);
+ }
+
+ @Override
+ public String getType() {
+ return "RB";
+ }
+
+ @Override
+ public JSONObject toJsonInternal() throws JSONException {
+
+ JSONObject obj = new JSONObject();
+ obj.put("type", "shadowsocksr");
+ obj.put("link", toUrl());
+ return obj;
+
+ }
+
+ @Override
+ public int hashCode() {
+
+ return bean.hashCode();
+
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ return super.equals(obj) || (obj instanceof RelayBatonProxy && bean.equals(((RelayBatonProxy) obj).bean));
+ }
+
+ }
+
public static LinkedList proxyList = new LinkedList<>();
public static LinkedList getProxyList() {
@@ -1553,6 +1671,23 @@ public class SharedConfig {
if (!subInfo.enable) continue;
+ if (subInfo.id == 1L) {
+
+ try {
+ RelayBatonProxy publicProxy = (RelayBatonProxy) parseProxyInfo(RelayBatonLoader.publicServer);
+ publicProxy.setRemarks(LocaleController.getString("NekoXProxy",R.string.NekoXProxy));
+ publicProxy.subId = subInfo.id;
+ proxyList.add(publicProxy);
+ if (publicProxy.hashCode() == current) {
+ currentProxy = publicProxy;
+ UIUtil.runOnIoDispatcher(publicProxy::start);
+ }
+ } catch (InvalidProxyException e) {
+ e.printStackTrace();
+ }
+
+ }
+
for (String proxy : subInfo.proxies) {
try {
@@ -1693,6 +1828,18 @@ public class SharedConfig {
}
+ } else if (url.startsWith(RB_PROTOCOL)) {
+
+ try {
+
+ return new RelayBatonProxy(url);
+
+ } catch (Exception ex) {
+
+ throw new InvalidProxyException(ex);
+
+ }
+
}
if (url.startsWith("tg:proxy") ||
diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ProxyListActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ProxyListActivity.java
index faedf9d91..fe32e8fa0 100644
--- a/TMessagesProj/src/main/java/org/telegram/ui/ProxyListActivity.java
+++ b/TMessagesProj/src/main/java/org/telegram/ui/ProxyListActivity.java
@@ -88,6 +88,7 @@ import java.util.regex.Pattern;
import kotlin.Unit;
import okhttp3.HttpUrl;
import tw.nekomimi.nekogram.BottomBuilder;
+import tw.nekomimi.nekogram.RelayBatonSettingsActivity;
import tw.nekomimi.nekogram.ShadowsocksRSettingsActivity;
import tw.nekomimi.nekogram.ShadowsocksSettingsActivity;
import tw.nekomimi.nekogram.SubSettingsActivity;
@@ -327,6 +328,8 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
private int menu_add_input_vmess = 4;
private int menu_add_input_ss = 7;
private int menu_add_input_ssr = 8;
+ private int menu_add_input_rb = 17;
+
private int menu_add_import_from_clipboard = 5;
private int menu_add_scan_qr = 6;
private int menu_other = 9;
@@ -656,8 +659,11 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
if (!BuildVars.isMini) {
addItem.addSubItem(menu_add_input_vmess, LocaleController.getString("AddProxyVmess", R.string.AddProxyVmess)).setOnClickListener((v) -> presentFragment(new VmessSettingsActivity()));
- addItem.addSubItem(menu_add_input_ss, LocaleController.getString("AddProxySS", R.string.AddProxySS)).setOnClickListener((v) -> presentFragment(new ShadowsocksSettingsActivity()));
- addItem.addSubItem(menu_add_input_ssr, LocaleController.getString("AddProxySSR", R.string.AddProxySSR)).setOnClickListener((v) -> presentFragment(new ShadowsocksRSettingsActivity()));
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ addItem.addSubItem(menu_add_input_ss, LocaleController.getString("AddProxySS", R.string.AddProxySS)).setOnClickListener((v) -> presentFragment(new ShadowsocksSettingsActivity()));
+ addItem.addSubItem(menu_add_input_ssr, LocaleController.getString("AddProxySSR", R.string.AddProxySSR)).setOnClickListener((v) -> presentFragment(new ShadowsocksRSettingsActivity()));
+ }
+ addItem.addSubItem(menu_add_input_rb, LocaleController.getString("AddProxyRB", R.string.AddProxyRB)).setOnClickListener((v) -> presentFragment(new RelayBatonSettingsActivity()));
}
@@ -756,9 +762,15 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
if (info instanceof SharedConfig.VmessProxy) {
presentFragment(new VmessSettingsActivity((SharedConfig.VmessProxy) info));
} else if (info instanceof SharedConfig.ShadowsocksProxy) {
- presentFragment(new ShadowsocksSettingsActivity((SharedConfig.ShadowsocksProxy) info));
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ presentFragment(new ShadowsocksSettingsActivity((SharedConfig.ShadowsocksProxy) info));
+ }
} else if (info instanceof SharedConfig.ShadowsocksRProxy) {
- presentFragment(new ShadowsocksRSettingsActivity((SharedConfig.ShadowsocksRProxy) info));
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ presentFragment(new ShadowsocksRSettingsActivity((SharedConfig.ShadowsocksRProxy) info));
+ }
+ } else if (info instanceof SharedConfig.RelayBatonProxy) {
+ presentFragment(new RelayBatonSettingsActivity((SharedConfig.RelayBatonProxy) info));
} else {
presentFragment(new ProxySettingsActivity(info));
}
@@ -850,7 +862,7 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
}
- private void addProxy() {
+ @SuppressLint("NewApi") private void addProxy() {
BottomSheet.Builder builder = new BottomSheet.Builder(getParentActivity());
@@ -861,7 +873,7 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
LocaleController.getString("AddProxyVmess", R.string.AddProxyVmess),
Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ? null : LocaleController.getString("AddProxySS", R.string.AddProxySS),
Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ? null : LocaleController.getString("AddProxySSR", R.string.AddProxySSR),
-
+ LocaleController.getString("AddProxyRB", R.string.AddProxyRB),
LocaleController.getString("ImportProxyFromClipboard", R.string.ImportProxyFromClipboard),
LocaleController.getString("ScanQRCode", R.string.ScanQRCode)
@@ -889,6 +901,10 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
} else if (i == 5) {
+ presentFragment(new RelayBatonSettingsActivity());
+
+ } else if (i == 6) {
+
ProxyUtil.importFromClipboard(getParentActivity());
} else {
diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/GuardedProcessPool.kt b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/GuardedProcessPool.kt
index 0dd19e7dc..ecc11cc45 100644
--- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/GuardedProcessPool.kt
+++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/GuardedProcessPool.kt
@@ -49,7 +49,7 @@ class GuardedProcessPool(private val onFatal: suspend (IOException) -> Unit) : C
} catch (_: IOException) { } // ignore
fun start() {
- process = ProcessBuilder(cmd).directory(ApplicationLoader.applicationContext.filesDir).start()
+ process = ProcessBuilder(cmd).directory(ApplicationLoader.applicationContext.cacheDir).start()
}
suspend fun looper(onRestartCallback: (suspend () -> Unit)?) {
diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/ProxyManager.kt b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/ProxyManager.kt
index 6784d53fb..d6b489cad 100644
--- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/ProxyManager.kt
+++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/ProxyManager.kt
@@ -68,6 +68,27 @@ object ProxyManager {
}
+
+ @JvmStatic
+ fun getPortForBean(bean: RelayBatonLoader.Bean): Int {
+
+ val hash = bean.hash.toString()
+
+ var port = pref.getInt(hash, -1)
+
+ if (!isProxyAvailable(port)) {
+
+ port = mkNewPort()
+
+ pref.edit().putInt(hash, port).apply()
+
+ }
+
+ return port
+
+ }
+
+
private fun mkNewPort() = Random.nextInt(2048, 32768)
@JvmStatic
diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/RelayBatonLoader.kt b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/RelayBatonLoader.kt
new file mode 100644
index 000000000..80dbd01c9
--- /dev/null
+++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/RelayBatonLoader.kt
@@ -0,0 +1,191 @@
+package tw.nekomimi.nekogram
+
+import android.annotation.SuppressLint
+import com.v2ray.ang.V2RayConfig
+import kotlinx.coroutines.runBlocking
+import okhttp3.HttpUrl
+import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
+import org.telegram.messenger.ApplicationLoader
+import org.telegram.messenger.FileLog
+import tw.nekomimi.nekogram.utils.FileUtil
+import java.io.File
+import kotlin.concurrent.thread
+import kotlin.properties.Delegates
+
+class RelayBatonLoader {
+
+ companion object {
+
+ @SuppressLint("AuthLeak")
+ const val publicServer = "rb://anonymous:nya@net.neko.services"
+
+ }
+
+ lateinit var bean: Bean
+ var port by Delegates.notNull()
+ var rbProcess: GuardedProcessPool? = null
+
+ fun initConfig(bean: Bean, port: Int) {
+
+ this.bean = bean
+ this.port = port
+
+ }
+
+ fun start() {
+
+ stop()
+
+ val cacheCfg = File(ApplicationLoader.applicationContext.cacheDir, "config.toml")
+
+ cacheCfg.writeText(bean.toToml(port))
+
+ val geoip = File(ApplicationLoader.applicationContext.cacheDir, "GeoLite2-Country.mmdb")
+
+ if (!geoip.isFile) {
+
+ geoip.createNewFile()
+
+ geoip.outputStream().use { out ->
+
+ ApplicationLoader.applicationContext.assets.open("GeoLite2-Country.mmdb").use {
+
+ it.copyTo(out)
+
+ }
+
+ }
+
+ }
+
+ rbProcess = GuardedProcessPool {
+
+ FileLog.e(it)
+
+ }.apply {
+
+ runCatching {
+
+ start(listOf(FileUtil.extLib("relaybaton").path, "client")) {
+
+ cacheCfg.delete()
+
+ }
+
+ }.onFailure {
+
+ cacheCfg.delete()
+
+ FileLog.e(it)
+
+ }
+
+ }
+
+ }
+
+ fun stop() {
+
+ if (rbProcess != null) {
+
+ val proc = rbProcess!!
+
+ thread {
+
+ runCatching {
+
+ runBlocking { proc.close(this) }
+
+ }
+
+ }
+
+ rbProcess = null
+
+ }
+
+ }
+
+ data class Bean(
+ var server: String = "",
+ var username: String = "",
+ var password: String = "",
+ var esni: Boolean = true,
+ var remarks: String? = null
+ ) {
+
+ override fun equals(other: Any?): Boolean {
+ return super.equals(other) || (other is Bean && hash == other.hash)
+ }
+
+ val hash = (server + username + password).hashCode()
+
+ fun toToml(port: Int) = """
+[log]
+file="./log.txt"
+level="error"
+
+[dns]
+type="dot"
+server="cloudflare-dns.com"
+addr="1.0.0.1:853"
+local_resolve=false
+
+[clients]
+port=$port
+ [[clients.client]]
+ id="1"
+ server="$server"
+ username="$username"
+ password="$password"
+ esni=$esni
+ timeout=15
+
+[routes]
+geoip_file="GeoLite2-Country.mmdb"
+ [[routes.route]]
+ type="default"
+ cond=""
+ target="1"
+"""
+
+ companion object {
+
+ fun parse(url: String): Bean {
+
+ // ss-android style
+
+ val link = url.replace(V2RayConfig.RB_PROTOCOL, "https://").toHttpUrlOrNull() ?: error("invalid relaybaton link $url")
+
+ return Bean(
+ link.host,
+ link.username,
+ link.password,
+ link.queryParameter("esni").takeIf { it in arrayOf("true", "false") }?.toBoolean() ?: true,
+ link.fragment
+ )
+
+ }
+
+ }
+
+ override fun toString(): String {
+
+ val url = HttpUrl.Builder()
+ .scheme("https")
+ .username(username)
+ .password(password)
+ .host(server)
+
+ if (!remarks.isNullOrBlank()) url.fragment(remarks)
+
+ if (!esni) url.addQueryParameter("esni","false")
+
+ return url.build().toString().replace("https://", V2RayConfig.RB_PROTOCOL)
+
+ }
+
+ }
+
+
+}
\ No newline at end of file
diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/RelayBatonSettingsActivity.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/RelayBatonSettingsActivity.java
new file mode 100644
index 000000000..73b71ac39
--- /dev/null
+++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/RelayBatonSettingsActivity.java
@@ -0,0 +1,389 @@
+/*
+ * This is the source code of Telegram for Android v. 5.x.x.
+ * It is licensed under GNU GPL v. 2 or later.
+ * You should have received a copy of the license in this archive (see LICENSE).
+ *
+ * Copyright Nikolai Kudashov, 2013-2018.
+ */
+
+package tw.nekomimi.nekogram;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Canvas;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.os.Build;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import com.github.shadowsocks.plugin.PluginConfiguration;
+import com.github.shadowsocks.plugin.PluginContract;
+import com.github.shadowsocks.plugin.PluginList;
+import com.github.shadowsocks.plugin.PluginManager;
+import com.github.shadowsocks.plugin.PluginOptions;
+
+import org.telegram.messenger.AndroidUtilities;
+import org.telegram.messenger.LocaleController;
+import org.telegram.messenger.R;
+import org.telegram.messenger.SharedConfig;
+import org.telegram.messenger.Utilities;
+import org.telegram.ui.ActionBar.ActionBar;
+import org.telegram.ui.ActionBar.ActionBarMenuItem;
+import org.telegram.ui.ActionBar.BaseFragment;
+import org.telegram.ui.ActionBar.Theme;
+import org.telegram.ui.ActionBar.ThemeDescription;
+import org.telegram.ui.Cells.TextCheckCell;
+import org.telegram.ui.Cells.TextInfoPrivacyCell;
+import org.telegram.ui.Cells.TextSettingsCell;
+import org.telegram.ui.Components.EditTextBoldCursor;
+import org.telegram.ui.Components.LayoutHelper;
+
+import java.util.ArrayList;
+
+import cn.hutool.core.util.StrUtil;
+import kotlin.Unit;
+import tw.nekomimi.nekogram.utils.AlertUtil;
+import tw.nekomimi.nekogram.utils.PopupBuilder;
+
+public class RelayBatonSettingsActivity extends BaseFragment {
+
+ private EditTextBoldCursor[] inputFields;
+
+ private EditTextBoldCursor serverField;
+ private EditTextBoldCursor usernameField;
+ private EditTextBoldCursor passwordField;
+ private TextCheckCell esniField;
+ private EditTextBoldCursor remarksField;
+
+ private ScrollView scrollView;
+ private LinearLayout linearLayout2;
+ private LinearLayout inputFieldsContainer;
+
+ private TextInfoPrivacyCell bottomCell;
+
+ private SharedConfig.RelayBatonProxy currentProxyInfo;
+ private RelayBatonLoader.Bean currentBean;
+
+ private boolean ignoreOnTextChange;
+
+ private static final int done_button = 1;
+
+ public class TypeCell extends FrameLayout {
+
+ private TextView textView;
+ private ImageView checkImage;
+ private boolean needDivider;
+
+ public TypeCell(Context context) {
+ super(context);
+
+ setWillNotDraw(false);
+
+ textView = new TextView(context);
+ textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText));
+ textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
+ textView.setLines(1);
+ textView.setMaxLines(1);
+ textView.setSingleLine(true);
+ textView.setEllipsize(TextUtils.TruncateAt.END);
+ textView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL);
+ addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 23 + 48 : 21, 0, LocaleController.isRTL ? 21 : 23, 0));
+
+ checkImage = new ImageView(context);
+ checkImage.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_featuredStickers_addedIcon), PorterDuff.Mode.SRC_IN));
+ checkImage.setImageResource(R.drawable.sticker_added);
+ addView(checkImage, LayoutHelper.createFrame(19, 14, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.CENTER_VERTICAL, 21, 0, 21, 0));
+
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(50) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY));
+ }
+
+ public void setValue(String name, boolean checked, boolean divider) {
+ textView.setText(name);
+ checkImage.setVisibility(checked ? VISIBLE : INVISIBLE);
+ needDivider = divider;
+ }
+
+ public void setTypeChecked(boolean value) {
+ checkImage.setVisibility(value ? VISIBLE : INVISIBLE);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (needDivider) {
+ canvas.drawLine(LocaleController.isRTL ? 0 : AndroidUtilities.dp(20), getMeasuredHeight() - 1, getMeasuredWidth() - (LocaleController.isRTL ? AndroidUtilities.dp(20) : 0), getMeasuredHeight() - 1, Theme.dividerPaint);
+ }
+ }
+ }
+
+ public RelayBatonSettingsActivity() {
+ super();
+ currentBean = new RelayBatonLoader.Bean();
+ }
+
+ public RelayBatonSettingsActivity(SharedConfig.RelayBatonProxy proxyInfo) {
+ super();
+ currentProxyInfo = proxyInfo;
+ currentBean = proxyInfo.bean;
+ }
+
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ AndroidUtilities.requestAdjustResize(getParentActivity(), classGuid);
+ }
+
+ @Override
+ public View createView(Context context) {
+ actionBar.setTitle(LocaleController.getString("ProxyDetails", R.string.ProxyDetails));
+ actionBar.setBackButtonImage(R.drawable.ic_ab_back);
+ actionBar.setAllowOverlayTitle(false);
+ if (AndroidUtilities.isTablet()) {
+ actionBar.setOccupyStatusBar(false);
+ }
+
+ actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() {
+ @Override
+ public void onItemClick(int id) {
+ if (id == -1) {
+ finishFragment();
+ } else if (id == done_button) {
+
+ if (getParentActivity() == null) {
+ return;
+ }
+
+ if (StrUtil.isBlank(serverField.getText())) {
+
+ serverField.requestFocus();
+ AndroidUtilities.showKeyboard(serverField);
+
+ return;
+
+ }
+
+ if (StrUtil.isBlank(usernameField.getText())) {
+
+ usernameField.requestFocus();
+ AndroidUtilities.showKeyboard(usernameField);
+
+ return;
+
+ }
+
+ if (StrUtil.isBlank(passwordField.getText())) {
+
+ passwordField.requestFocus();
+ AndroidUtilities.showKeyboard(passwordField);
+
+ return;
+
+ }
+
+
+ currentBean.setServer(serverField.getText().toString());
+ currentBean.setUsername(usernameField.getText().toString());
+ currentBean.setPassword(passwordField.getText().toString());
+ currentBean.setEsni(esniField.isChecked());
+ currentBean.setRemarks(remarksField.getText().toString());
+
+ if (currentProxyInfo == null) {
+ currentProxyInfo = new SharedConfig.RelayBatonProxy(currentBean);
+ SharedConfig.addProxy(currentProxyInfo);
+ SharedConfig.setCurrentProxy(currentProxyInfo);
+ } else {
+ currentProxyInfo.proxyCheckPingId = 0;
+ currentProxyInfo.availableCheckTime = 0;
+ currentProxyInfo.ping = 0;
+ SharedConfig.saveProxyList();
+ SharedConfig.setProxyEnable(false);
+ }
+
+ finishFragment();
+
+ }
+ }
+ });
+
+ ActionBarMenuItem doneItem = actionBar.createMenu().addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56));
+ doneItem.setContentDescription(LocaleController.getString("Done", R.string.Done));
+
+ fragmentView = new FrameLayout(context);
+ FrameLayout frameLayout = (FrameLayout) fragmentView;
+ fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray));
+
+ scrollView = new ScrollView(context);
+ scrollView.setFillViewport(true);
+ AndroidUtilities.setScrollViewEdgeEffectColor(scrollView, Theme.getColor(Theme.key_actionBarDefault));
+ frameLayout.addView(scrollView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT));
+
+ linearLayout2 = new LinearLayout(context);
+ linearLayout2.setOrientation(LinearLayout.VERTICAL);
+ scrollView.addView(linearLayout2, new ScrollView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+
+ inputFieldsContainer = new LinearLayout(context);
+ inputFieldsContainer.setOrientation(LinearLayout.VERTICAL);
+ inputFieldsContainer.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite));
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ // bring to front for transitions
+ inputFieldsContainer.setElevation(AndroidUtilities.dp(1f));
+ inputFieldsContainer.setOutlineProvider(null);
+ }
+ linearLayout2.addView(inputFieldsContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
+
+ inputFields = new EditTextBoldCursor[4];
+
+ for (int a = 0; a < 4; a++) {
+ FrameLayout container = new FrameLayout(context);
+ EditTextBoldCursor cursor = mkCursor();
+ inputFields[a] = cursor;
+ cursor.setImeOptions(EditorInfo.IME_ACTION_NEXT | EditorInfo.IME_FLAG_NO_EXTRACT_UI);
+ switch (a) {
+ case 0:
+ serverField = cursor;
+ cursor.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
+ cursor.setHintText(LocaleController.getString("UseProxyAddress", R.string.UseProxyAddress));
+ cursor.setText(currentBean.getServer());
+ break;
+ case 1:
+ usernameField = cursor;
+ cursor.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
+ cursor.setHintText(LocaleController.getString("UseProxyUsername", R.string.UseProxyUsername));
+ cursor.setText(currentBean.getUsername());
+ break;
+ case 2:
+ passwordField = cursor;
+ cursor.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
+ cursor.setHintText(LocaleController.getString("UseProxyPassword", R.string.UseProxyPassword));
+ cursor.setText(currentBean.getPassword());
+ break;
+ case 3:
+ remarksField = cursor;
+ cursor.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
+ cursor.setHintText(LocaleController.getString("ProxyRemarks", R.string.ProxyRemarks));
+ cursor.setText(currentBean.getRemarks());
+ break;
+ }
+ cursor.setSelection(cursor.length());
+
+ cursor.setPadding(0, 0, 0, 0);
+ container.addView(cursor, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 17, a == 0 ? 12 : 0, 17, 0));
+
+ }
+
+ inputFieldsContainer.addView((View) serverField.getParent(), LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
+ inputFieldsContainer.addView((View) usernameField.getParent(), LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
+ inputFieldsContainer.addView((View) passwordField.getParent(), LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
+
+ FrameLayout container = new FrameLayout(context);
+ esniField = new TextCheckCell(context);
+ esniField.setBackground(Theme.getSelectorDrawable(false));
+ esniField.setTextAndCheck(LocaleController.getString("ESNI", R.string.ESNI), currentBean.getEsni(), false);
+ esniField.setOnClickListener((v) -> esniField.setChecked(!esniField.isChecked()));
+ container.addView(esniField, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 0, 0, 0, 0));
+ inputFieldsContainer.addView(container, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
+
+ inputFieldsContainer.addView((View) remarksField.getParent(), LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
+
+ bottomCell = new TextInfoPrivacyCell(context);
+ bottomCell.setBackground(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow));
+ bottomCell.setText(LocaleController.getString("ProxyInfoSS", R.string.ProxyInfoSS));
+ linearLayout2.addView(bottomCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
+
+ return fragmentView;
+
+ }
+
+ EditTextBoldCursor mkCursor() {
+
+ EditTextBoldCursor cursor = new EditTextBoldCursor(getParentActivity());
+ cursor.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
+ cursor.setHintColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText));
+ cursor.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText));
+ cursor.setBackground(null);
+ cursor.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText));
+ cursor.setCursorSize(AndroidUtilities.dp(20));
+ cursor.setCursorWidth(1.5f);
+ cursor.setSingleLine(true);
+ cursor.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL);
+ cursor.setHeaderHintColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueHeader));
+ cursor.setTransformHintToHeader(true);
+ cursor.setLineColors(Theme.getColor(Theme.key_windowBackgroundWhiteInputField), Theme.getColor(Theme.key_windowBackgroundWhiteInputFieldActivated), Theme.getColor(Theme.key_windowBackgroundWhiteRedText3));
+ return cursor;
+
+ }
+
+ @Override
+ protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) {
+ if (isOpen && !backward && currentProxyInfo == null) {
+ serverField.requestFocus();
+ AndroidUtilities.showKeyboard(serverField);
+ }
+ }
+
+ @Override
+ public ArrayList getThemeDescriptions() {
+ final ThemeDescription.ThemeDescriptionDelegate delegate = () -> {
+ if (inputFields != null) {
+ for (int i = 0; i < inputFields.length; i++) {
+ inputFields[i].setLineColors(Theme.getColor(Theme.key_windowBackgroundWhiteInputField),
+ Theme.getColor(Theme.key_windowBackgroundWhiteInputFieldActivated),
+ Theme.getColor(Theme.key_windowBackgroundWhiteRedText3));
+ }
+ }
+ };
+ ArrayList arrayList = new ArrayList<>();
+ arrayList.add(new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray));
+ arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault));
+ arrayList.add(new ThemeDescription(scrollView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault));
+ arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon));
+ arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle));
+ arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector));
+ arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCH, null, null, null, null, Theme.key_actionBarDefaultSearch));
+ arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCHPLACEHOLDER, null, null, null, null, Theme.key_actionBarDefaultSearchPlaceholder));
+ arrayList.add(new ThemeDescription(inputFieldsContainer, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite));
+ arrayList.add(new ThemeDescription(linearLayout2, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider));
+
+ arrayList.add(new ThemeDescription(null, 0, null, null, null, null, delegate, Theme.key_windowBackgroundWhiteBlueText4));
+ arrayList.add(new ThemeDescription(null, 0, null, null, null, null, delegate, Theme.key_windowBackgroundWhiteGrayText2));
+
+
+ if (inputFields != null) {
+ for (int a = 0; a < inputFields.length; a++) {
+ arrayList.add(new ThemeDescription(inputFields[a], ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText));
+ arrayList.add(new ThemeDescription(inputFields[a], ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText));
+ arrayList.add(new ThemeDescription(inputFields[a], ThemeDescription.FLAG_HINTTEXTCOLOR | ThemeDescription.FLAG_PROGRESSBAR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueHeader));
+ arrayList.add(new ThemeDescription(inputFields[a], ThemeDescription.FLAG_CURSORCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText));
+ arrayList.add(new ThemeDescription(null, 0, null, null, null, delegate, Theme.key_windowBackgroundWhiteInputField));
+ arrayList.add(new ThemeDescription(null, 0, null, null, null, delegate, Theme.key_windowBackgroundWhiteInputFieldActivated));
+ arrayList.add(new ThemeDescription(null, 0, null, null, null, delegate, Theme.key_windowBackgroundWhiteRedText3));
+ }
+ } else {
+ arrayList.add(new ThemeDescription(null, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText));
+ arrayList.add(new ThemeDescription(null, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText));
+ }
+
+ arrayList.add(new ThemeDescription(bottomCell, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow));
+ arrayList.add(new ThemeDescription(bottomCell, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4));
+ arrayList.add(new ThemeDescription(bottomCell, ThemeDescription.FLAG_LINKCOLOR, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteLinkText));
+
+ return arrayList;
+ }
+}
diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/ShadowsocksLoader.kt b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/ShadowsocksLoader.kt
index 951e3cbf9..0850db79a 100644
--- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/ShadowsocksLoader.kt
+++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/ShadowsocksLoader.kt
@@ -1,5 +1,8 @@
package tw.nekomimi.nekogram
+import android.annotation.SuppressLint
+import android.os.Build
+import androidx.annotation.RequiresApi
import cn.hutool.core.codec.Base64
import com.github.shadowsocks.plugin.PluginConfiguration
import com.github.shadowsocks.plugin.PluginManager
@@ -15,6 +18,7 @@ import java.io.File
import kotlin.concurrent.thread
import kotlin.properties.Delegates
+@SuppressLint("NewApi")
class ShadowsocksLoader {
lateinit var bean: Bean
diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/ShadowsocksRSettingsActivity.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/ShadowsocksRSettingsActivity.java
index 9903f2447..2d94e36a3 100644
--- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/ShadowsocksRSettingsActivity.java
+++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/ShadowsocksRSettingsActivity.java
@@ -270,7 +270,7 @@ public class ShadowsocksRSettingsActivity extends BaseFragment {
case 2:
passwordField = cursor;
cursor.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
- cursor.setHintText(LocaleController.getString("SSPassword", R.string.SSPassword));
+ cursor.setHintText(LocaleController.getString("UseProxyPassword", R.string.UseProxyPassword));
cursor.setText(currentBean.getPassword());
break;
case 3:
diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/ShadowsocksSettingsActivity.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/ShadowsocksSettingsActivity.java
index c99619d0e..f6a5c64e7 100644
--- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/ShadowsocksSettingsActivity.java
+++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/ShadowsocksSettingsActivity.java
@@ -278,7 +278,7 @@ public class ShadowsocksSettingsActivity extends BaseFragment {
case 2:
passwordField = cursor;
cursor.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
- cursor.setHintText(LocaleController.getString("SSPassword", R.string.SSPassword));
+ cursor.setHintText(LocaleController.getString("UseProxyPassword", R.string.UseProxyPassword));
cursor.setText(currentBean.getPassword());
break;
case 3:
diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/sub/SubInfo.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/sub/SubInfo.java
index 264e3a99e..31c4c27f3 100644
--- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/sub/SubInfo.java
+++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/sub/SubInfo.java
@@ -23,6 +23,7 @@ import java.util.Objects;
import javax.xml.transform.ErrorListener;
import cn.hutool.core.util.StrUtil;
+import tw.nekomimi.nekogram.RelayBatonLoader;
import tw.nekomimi.nekogram.utils.HttpUtil;
import tw.nekomimi.nekogram.utils.ProxyUtil;
@@ -138,6 +139,7 @@ public class SubInfo implements Mappable {
SubInfo subInfo = (SubInfo) o;
return id == subInfo.id;
}
+
@Override
public Document write(NitriteMapper mapper) {
@@ -147,7 +149,6 @@ public class SubInfo implements Mappable {
document.put("name", name);
document.put("urls", urls);
document.put("proxies",proxies);
-
document.put("lastFetch", lastFetch);
document.put("enable", enable);
document.put("internal", internal);
diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/utils/ProxyUtil.kt b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/utils/ProxyUtil.kt
index 8c6c3a1e1..70286e65d 100644
--- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/utils/ProxyUtil.kt
+++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/utils/ProxyUtil.kt
@@ -20,6 +20,7 @@ import com.google.zxing.*
import com.google.zxing.common.GlobalHistogramBinarizer
import com.google.zxing.qrcode.QRCodeReader
import com.google.zxing.qrcode.QRCodeWriter
+import com.v2ray.ang.V2RayConfig.RB_PROTOCOL
import com.v2ray.ang.V2RayConfig.SSR_PROTOCOL
import com.v2ray.ang.V2RayConfig.SS_PROTOCOL
import com.v2ray.ang.V2RayConfig.VMESS1_PROTOCOL
@@ -128,7 +129,8 @@ object ProxyUtil {
line.startsWith(VMESS_PROTOCOL) ||
line.startsWith(VMESS1_PROTOCOL) ||
line.startsWith(SS_PROTOCOL) ||
- line.startsWith(SSR_PROTOCOL)) {
+ line.startsWith(SSR_PROTOCOL) ||
+ line.startsWith(RB_PROTOCOL)) {
runCatching { proxies.add(SharedConfig.parseProxyInfo(line).toUrl()) }
@@ -162,7 +164,8 @@ object ProxyUtil {
line.startsWith(VMESS_PROTOCOL) ||
line.startsWith(VMESS1_PROTOCOL) ||
line.startsWith(SS_PROTOCOL) ||
- line.startsWith(SSR_PROTOCOL)) {
+ line.startsWith(SSR_PROTOCOL) ||
+ line.startsWith(RB_PROTOCOL)) {
exists = true
@@ -198,6 +201,10 @@ object ProxyUtil {
AndroidUtilities.showShadowsocksRAlert(ctx, SharedConfig.ShadowsocksRProxy(link))
+ } else if (link.startsWith(RB_PROTOCOL)) {
+
+ AndroidUtilities.showRelayBatonAlert(ctx, SharedConfig.RelayBatonProxy(link))
+
} else {
val url = link.toHttpUrlOrNull()!!
diff --git a/TMessagesProj/src/main/res/values/strings_nekox.xml b/TMessagesProj/src/main/res/values/strings_nekox.xml
index 4473058e1..39b0583a2 100644
--- a/TMessagesProj/src/main/res/values/strings_nekox.xml
+++ b/TMessagesProj/src/main/res/values/strings_nekox.xml
@@ -138,6 +138,7 @@
Add Vmess Proxy
Add Shadowsocks Proxy
Add ShadowsocksR Proxy
+ Add RelayBaton Proxy
Several plugins use this id: %s.
Edit Proxy
@@ -166,6 +167,9 @@
Obfs
Obfs Param
+ RelayBaton Proxy Settings
+ Encrypted SNI
+
Remarks
Retest ping for all servers
Reorder servers by ping
diff --git a/relaybaton/.gitignore b/relaybaton/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/relaybaton/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/relaybaton/build.gradle b/relaybaton/build.gradle
new file mode 100644
index 000000000..ad3f426b0
--- /dev/null
+++ b/relaybaton/build.gradle
@@ -0,0 +1,16 @@
+plugins {
+ id 'com.android.library'
+}
+
+android {
+ compileSdkVersion 29
+ buildToolsVersion "29.0.3"
+ ndkVersion "21.1.6352462"
+
+ defaultConfig {
+ minSdkVersion 16
+ targetSdkVersion 29
+ versionCode 1
+ versionName "1.0"
+ }
+}
\ No newline at end of file
diff --git a/relaybaton/src/main/AndroidManifest.xml b/relaybaton/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..ed92616fc
--- /dev/null
+++ b/relaybaton/src/main/AndroidManifest.xml
@@ -0,0 +1,5 @@
+
+
+
+
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index c13438d40..35fbb9d70 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,3 +1,4 @@
+include ':relaybaton'
include ':TMessagesProj'
include ':ss-rust'
include ':ssr-libev'
\ No newline at end of file