Add relaybaton support

This commit is contained in:
世界 2020-06-01 14:08:46 +08:00
parent 2e9c220b15
commit 9778ddc97f
No known key found for this signature in database
GPG Key ID: CD109927C34A63C4
25 changed files with 1006 additions and 17 deletions

58
.github/scripts/build-relaybaton.sh vendored Normal file
View File

@ -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

View File

@ -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

View File

@ -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')
}

Binary file not shown.

View File

@ -205,6 +205,7 @@
<data android:scheme="vmess1" />
<data android:scheme="ss" />
<data android:scheme="ssr" />
<data android:scheme="rb" />
</intent-filter>
<intent-filter
android:icon="@mipmap/ic_launcher"

Binary file not shown.

View File

@ -16,6 +16,7 @@ object V2RayConfig {
const val VMESS1_PROTOCOL = "vmess1://"
const val SS_PROTOCOL: String = "ss://"
const val SSR_PROTOCOL: String = "ssr://"
const val RB_PROTOCOL: String = "rb://"
const val SOCKS_PROTOCOL: String = "socks://"
const val BROADCAST_ACTION_SERVICE = "com.v2ray.ang.action.service"

View File

@ -145,6 +145,7 @@ import tw.nekomimi.nekogram.utils.AlertUtil;
import tw.nekomimi.nekogram.utils.EnvUtil;
import tw.nekomimi.nekogram.utils.FileUtil;
import static com.v2ray.ang.V2RayConfig.RB_PROTOCOL;
import static com.v2ray.ang.V2RayConfig.SSR_PROTOCOL;
import static com.v2ray.ang.V2RayConfig.SS_PROTOCOL;
import static com.v2ray.ang.V2RayConfig.VMESS1_PROTOCOL;
@ -305,7 +306,7 @@ public class AndroidUtilities {
}
}
final ArrayList<LinkSpec> 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 {

View File

@ -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\\$\\-\\_"

View File

@ -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<ProxyInfo> proxyList = new LinkedList<>();
public static LinkedList<ProxyInfo> 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") ||

View File

@ -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 {

View File

@ -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)?) {

View File

@ -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

View File

@ -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<Int>()
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)
}
}
}

View File

@ -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<ThemeDescription> 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<ThemeDescription> 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;
}
}

View File

@ -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

View File

@ -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:

View File

@ -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:

View File

@ -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);

View File

@ -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()!!

View File

@ -138,6 +138,7 @@
<string name="AddProxyVmess">Add Vmess Proxy</string>
<string name="AddProxySS">Add Shadowsocks Proxy</string>
<string name="AddProxySSR">Add ShadowsocksR Proxy</string>
<string name="AddProxyRB">Add RelayBaton Proxy</string>
<string name="SSPluginConflictingName">Several plugins use this id: %s.</string>
<string name="EditProxy">Edit Proxy</string>
@ -166,6 +167,9 @@
<string name="SSRObfs">Obfs</string>
<string name="SSRObfsParam">Obfs Param</string>
<string name="ProxyInfoRB">RelayBaton Proxy Settings</string>
<string name="ESNI">Encrypted SNI</string>
<string name="ProxyRemarks">Remarks</string>
<string name="RetestPing">Retest ping for all servers</string>
<string name="ReorderByPing">Reorder servers by ping</string>

1
relaybaton/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

16
relaybaton/build.gradle Normal file
View File

@ -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"
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.github.nekohasekai.relaybaton">
</manifest>

View File

@ -1,3 +1,4 @@
include ':relaybaton'
include ':TMessagesProj'
include ':ss-rust'
include ':ssr-libev'