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