mirror of https://github.com/NekoX-Dev/NekoX.git
Add relaybaton support
This commit is contained in:
parent
2e9c220b15
commit
9778ddc97f
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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.
|
@ -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.
|
@ -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"
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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\\$\\-\\_"
|
||||
|
|
|
@ -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") ||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)?) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()!!
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
/build
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -1,3 +1,4 @@
|
|||
include ':relaybaton'
|
||||
include ':TMessagesProj'
|
||||
include ':ss-rust'
|
||||
include ':ssr-libev'
|
Loading…
Reference in New Issue