代理订阅

This commit is contained in:
世界 2020-04-22 16:55:25 +08:00
parent 3d6e117989
commit 8b78ea5a1c
No known key found for this signature in database
GPG Key ID: CD109927C34A63C4
33 changed files with 1402 additions and 493 deletions

View File

@ -12,15 +12,17 @@ jobs:
if: "!contains(github.event.head_commit.message, '[S]') && !contains(github.event.head_commit.message, '[RELEASE]')"
steps:
- uses: actions/checkout@v2
- uses: actions/cache@v1
with:
path: ~/.gradle
key: gradle-${{ hashFiles('**/*.gradle') }}
- uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Run Gradle Build
run: |
sudo bash <<EOF
export LOCAL_PROPERTIES="${{ secrets.LOCAL_PROPERTIES }}"
./gradlew assembleMinApi21Release
EOF
export LOCAL_PROPERTIES="${{ secrets.LOCAL_PROPERTIES }}"
./gradlew assembleMinApi21Release
echo ::set-env name=APK_FILE::$(find TMessagesProj/build/outputs/apk -name "*arm64-v8a*.apk")
# - uses: actions/upload-artifact@v1
# with:

View File

@ -12,6 +12,12 @@ jobs:
if: "contains(github.event.head_commit.message, '[RELEASE]')"
steps:
- uses: actions/checkout@v2
- uses: actions/cache@v1
with:
path: ~/.gradle
key: gradle-${{ hashFiles('**/*.gradle') }}
restore-keys: |
${{ runner.os }}-gradle-
- uses: actions/setup-java@v1
with:
java-version: 1.8

View File

@ -11,6 +11,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/cache@v1
with:
path: ~/.gradle
key: gradle-${{ hashFiles('**/*.gradle') }}
restore-keys: |
${{ runner.os }}-gradle-
- uses: actions/setup-java@v1
with:
java-version: 1.8

View File

@ -1,13 +1,14 @@
# NekoX
![Logo](https://raw.githubusercontent.com/NekoX-Dev/NekoX/master/TMessagesProj/src/main/res/mipmap-xxxhdpi/ic_launcher.png)
NekoX-Dev is an UNOFFICIAL app that uses Telegram's API.
NekoX is an open source third-party Telegram android app.
- Google play store: (https://play.google.com/store/apps/details?id=nekox.messenger)
- Update news : https://t.me/NekoX-Dev
- Feedback: https://t.me/NekoXChat
- Feedback (Persian) : https://t.me/NekogramX_Persian
- Feedback: https://github.com/NekoX-Dev/NekoX/issues
- FAQ: https://telegra.ph/NekoX-FAQ-03-31
- FAQ (Chinese): https://telegra.ph/NekoX-%E5%B8%B8%E8%A6%8B%E5%95%8F%E9%A1%8C-03-31
## API, Protocol documentation
@ -51,37 +52,24 @@ To compile the foss version, please refer to [this script](.github/workflows/fos
Available variant list:
`Afat`, `MinApi21`
`Afat`, ( android 4.1 + )
`MinApi21` ( android 5 + )
## Localization
NekoX is forked from Nekogram-FOSS, thus most locales follows the translations of Telegram for Android, checkout https://translations.telegram.org/en/android/.
As for the specialized strings, we use Crowdin to translate Nekogram. Join project at https://neko.crowdin.com/nekogram and https://nekox.crowdin.com/nekox. Help us bring Nekogram to the world!
Join project at https://neko.crowdin.com/nekogram and https://nekox.crowdin.com/nekox.
## Credits
<ul>
<li>Nekogram: <a href="https://github.com/Nekogram/Nekogram/blob/master/LICENSE">GPLv2</a></li>
<li>Telegram-FOSS: <a href="https://github.com/Telegram-FOSS-Team/Telegram-FOSS/blob/master/LICENSE">GPLv2</a></li>
<li>Nekogram: <a href="https://github.com/Nekogram/Nekogram/blob/master/LICENSE">GPLv2</a></li>
<li>v2rayNG: <a href="https://github.com/2dust/v2rayNG/blob/master/LICENSE">GPLv3</a></li>
<li>AndroidLibV2rayLite: <a href="https://github.com/2dust/AndroidLibV2rayLite/blob/master/LICENSE">LGPLv3</a></li>
<li>shadowsocks-libev: <a href="https://github.com/shadowsocks/shadowsocks-libev/blob/master/LICENSE">GPLv3</a></li>
<li>shadowsocksRb-android: <a href="https://github.com/shadowsocksRb/shadowsocksRb-android/blob/master/LICENSE">GPLv3</a></li>
</ul>
## Contributors
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
| [<img src="https://avatars3.githubusercontent.com/u/56506714?s=460&v=4" width="80px;"/><br /><sub>世界</sub>](https://github.com/nekohasekai)<br />[💻](https://github.com/NekoX-Dev/NekoX/commits?author=nekohasekai "Code") | [<img src="https://avatars2.githubusercontent.com/u/42698724?s=460&v=4" width="80px;"/><br /><sub>猫耳逆变器</sub>](https://github.com/NekoInverter)<br />[💻](https://github.com/NekoX-Dev/NekoX/commits?author=NekoInverter "Code") | [<img src="https://avatars1.githubusercontent.com/u/18373361?s=460&v=4" width="80px;"/><br /><sub>梨子</sub>](https://github.com/rikakomoe)<br />[💻](https://github.com/NekoX-Dev/NekoX/commits?author=rikakomoe "Code") | [<img src="https://i.loli.net/2020/01/17/e9Z5zkG7lNwUBPE.jpg" width="80px;"/><br /><sub>呆瓜</sub>](https://t.me/Duang)<br /> [🎨](#design-duang "Design") |
| :---: | :---: | :---: | :---: |
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
## :)
[关于 "NekoX 举报 Nekogram" 的声明](https://telegra.ph/page-04-12)

View File

@ -96,7 +96,7 @@ data class V2rayConfig(
data class HeadersBean(var Host: String = "")
}
data class HttpsettingsBean(var host: List<String> = ArrayList<String>(), var path: String = "")
data class HttpsettingsBean(var host: List<String> = ArrayList(), var path: String = "")
data class TlssettingsBean(var allowInsecure: Boolean = true,
var serverName: String = "")

View File

@ -62,7 +62,7 @@ object Utils {
*/
fun encode(text: String): String {
try {
return Base64.encodeToString(text.toByteArray(charset("UTF-8")), Base64.NO_WRAP or Base64.URL_SAFE)
return Base64.encodeToString(text.toByteArray(charset("UTF-8")), Base64.NO_WRAP)
} catch (e: Exception) {
e.printStackTrace()
return ""

View File

@ -36,6 +36,7 @@ import tw.nekomimi.nekogram.ExternalGcm;
import tw.nekomimi.nekogram.NekoConfig;
import tw.nekomimi.nekogram.utils.FileUtil;
import tw.nekomimi.nekogram.utils.ProxyUtil;
import tw.nekomimi.nekogram.utils.UIUtil;
public class ApplicationLoader extends Application {
@ -91,11 +92,15 @@ public class ApplicationLoader extends Application {
applicationInited = true;
try {
LocaleController.getInstance();
} catch (Exception e) {
e.printStackTrace();
}
UIUtil.runOnIoDispatcher(() -> {
try {
LocaleController.getInstance();
} catch (Exception e) {
e.printStackTrace();
}
});
try {
connectivityManager = (ConnectivityManager) ApplicationLoader.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE);

View File

@ -4362,7 +4362,7 @@ public class MessagesController extends BaseController implements NotificationCe
removeCurrent = 1;
}
lastCheckProxyId++;
if (!NekoConfig.hideProxySponsorChannel && (SharedConfig.currentProxy == null || !SharedConfig.currentProxy.isPublic) && enabled && !TextUtils.isEmpty(proxyAddress) && !TextUtils.isEmpty(proxySecret)) {
if (!NekoConfig.hideProxySponsorChannel && (SharedConfig.currentProxy == null || SharedConfig.currentProxy.subId != 1L) && enabled && !TextUtils.isEmpty(proxyAddress) && !TextUtils.isEmpty(proxySecret)) {
checkingProxyInfo = true;
int checkProxyId = lastCheckProxyId;
TLRPC.TL_help_getProxyData req = new TLRPC.TL_help_getProxyData();

View File

@ -25,6 +25,7 @@ import com.v2ray.ang.V2RayConfig;
import com.v2ray.ang.dto.AngConfig;
import com.v2ray.ang.util.Utils;
import org.dizitart.no2.objects.filters.ObjectFilters;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@ -42,8 +43,9 @@ import tw.nekomimi.nekogram.ProxyManager;
import tw.nekomimi.nekogram.ShadowsocksLoader;
import tw.nekomimi.nekogram.ShadowsocksRLoader;
import tw.nekomimi.nekogram.VmessLoader;
import tw.nekomimi.nekogram.sub.SubInfo;
import tw.nekomimi.nekogram.sub.SubManager;
import tw.nekomimi.nekogram.utils.FileUtil;
import tw.nekomimi.nekogram.utils.ProxyUtil;
import tw.nekomimi.nekogram.utils.UIUtil;
import static com.v2ray.ang.V2RayConfig.SSR_PROTOCOL;
@ -89,7 +91,7 @@ public class SharedConfig {
public static int passportConfigHash;
private static boolean configLoaded;
public static final Object sync = new Object();
private static final Object sync = new Object();
private static final Object localIdSync = new Object();
public static boolean saveToGallery;
@ -163,7 +165,7 @@ public class SharedConfig {
}
public boolean isPublic;
public long subId;
public ProxyInfo() {
address = "";
@ -192,7 +194,13 @@ public class SharedConfig {
}
}
private String getType() {
public String getAddress() {
return address + ":" + port;
}
public String getType() {
if (!StrUtil.isBlank(secret)) {
@ -208,20 +216,42 @@ public class SharedConfig {
public String getTitle() {
if (StrUtil.isBlank(remarks)) {
StringBuilder builder = new StringBuilder();
return (isPublic ? LocaleController.getString("PublicPrefix", R.string.PublicPrefix) : "[" + getType() + "]") + " " + address + ":" + port;
builder.append("[");
if (subId != 0L) {
builder.append(SubManager.getSubList().find(ObjectFilters.eq("id", subId)).firstOrDefault().displayName());
} else {
return (isPublic ? LocaleController.getString("PublicPrefix", R.string.PublicPrefix) : "[" + getType() + "]") + " " + remarks;
builder.append(getType());
}
builder.append("] ");
if (StrUtil.isBlank(getRemarks())) {
builder.append(getAddress());
} else {
builder.append(getRemarks());
}
return builder.toString();
}
private String remarks;
public String getRemarks() {
return remarks;
}
public void setRemarks(String remarks) {
@ -231,13 +261,11 @@ public class SharedConfig {
}
}
private String remarks;
public String toUrl() {
HttpUrl.Builder builder = HttpUrl.parse(StrUtil.isBlank(secret) ?
"https://t.me/socks" : "https://t.me/proxy").newBuilder()
.addQueryParameter("address", address)
.addQueryParameter("server", address)
.addQueryParameter("port", port + "");
if (!StrUtil.isBlank(secret)) {
@ -263,18 +291,28 @@ public class SharedConfig {
public static ProxyInfo fromUrl(String url) {
HttpUrl lnk = HttpUrl.parse(url);
Uri lnk = Uri.parse(url);
return new ProxyInfo(lnk.queryParameter("address"),
Utilities.parseInt(lnk.queryParameter("port")),
lnk.queryParameter("user"),
lnk.queryParameter("pass"),
lnk.queryParameter("secret"));
if (lnk == null) throw new IllegalArgumentException(url);
return new ProxyInfo(lnk.getQueryParameter("server"),
Utilities.parseInt(lnk.getQueryParameter("port")),
lnk.getQueryParameter("user"),
lnk.getQueryParameter("pass"),
lnk.getQueryParameter("secret"));
}
public JSONObject toJson() throws JSONException {
JSONObject object = toJsonInternal();
return object;
}
public JSONObject toJsonInternal() throws JSONException {
JSONObject obj = new JSONObject();
if (!StrUtil.isBlank(remarks)) {
@ -415,7 +453,7 @@ public class SharedConfig {
public abstract void stop();
@Override
public abstract String getTitle();
public abstract String getAddress();
@Override
public abstract String toUrl();
@ -427,7 +465,10 @@ public class SharedConfig {
public abstract void setRemarks(String remarks);
@Override
public abstract JSONObject toJson() throws JSONException;
public abstract String getType();
@Override
public abstract JSONObject toJsonInternal() throws JSONException;
}
@ -449,18 +490,8 @@ public class SharedConfig {
}
@Override
public String getTitle() {
if (StrUtil.isBlank(getRemarks())) {
return (isPublic ? LocaleController.getString("PublicPrefix", R.string.PublicPrefix) : "[Vmess]") + " " + bean.getAddress() + ":" + bean.getPort();
} else {
return (isPublic ? LocaleController.getString("PublicPrefix", R.string.PublicPrefix) : "[Vmess]") + " " + getRemarks();
}
public String getAddress() {
return bean.getAddress() + ":" + bean.getPort();
}
@Override
@ -516,7 +547,12 @@ public class SharedConfig {
}
@Override
public JSONObject toJson() throws JSONException {
public String getType() {
return "Vmess";
}
@Override
public JSONObject toJsonInternal() throws JSONException {
JSONObject obj = new JSONObject();
obj.put("type", "vmess");
@ -555,18 +591,8 @@ public class SharedConfig {
}
@Override
public String getTitle() {
if (StrUtil.isBlank(getRemarks())) {
return (isPublic ? LocaleController.getString("PublicPrefix", R.string.PublicPrefix) : "[SS]") + " " + bean.getHost() + ":" + bean.getRemotePort();
} else {
return (isPublic ? LocaleController.getString("PublicPrefix", R.string.PublicPrefix) : "[SS]") + " " + getRemarks();
}
public String getAddress() {
return bean.getHost() + ":" + bean.getRemotePort();
}
@Override
@ -623,7 +649,12 @@ public class SharedConfig {
}
@Override
public JSONObject toJson() throws JSONException {
public String getType() {
return "SS";
}
@Override
public JSONObject toJsonInternal() throws JSONException {
JSONObject obj = new JSONObject();
obj.put("type", "shadowsocks");
@ -664,18 +695,8 @@ public class SharedConfig {
}
@Override
public String getTitle() {
if (StrUtil.isBlank(getRemarks())) {
return (isPublic ? LocaleController.getString("PublicPrefix", R.string.PublicPrefix) : "[SSR]") + " " + bean.getHost() + ":" + bean.getRemotePort();
} else {
return (isPublic ? LocaleController.getString("PublicPrefix", R.string.PublicPrefix) : "[SSR]") + " " + getRemarks();
}
public String getAddress() {
return bean.getHost() + ":" + bean.getRemotePort();
}
@Override
@ -731,7 +752,12 @@ public class SharedConfig {
}
@Override
public JSONObject toJson() throws JSONException {
public String getType() {
return "SSR";
}
@Override
public JSONObject toJsonInternal() throws JSONException {
JSONObject obj = new JSONObject();
obj.put("type", "shadowsocksr");
@ -758,11 +784,7 @@ public class SharedConfig {
public static LinkedList<ProxyInfo> getProxyList() {
synchronized (sync) {
return new LinkedList<>(proxyList);
}
return new LinkedList<>(proxyList);
}
@ -1379,33 +1401,35 @@ public class SharedConfig {
public static void setProxyEnable(boolean enable) {
proxyEnabled = enable;
SharedPreferences preferences = MessagesController.getGlobalMainSettings();
preferences.edit().putBoolean("proxy_enabled", enable).apply();
ProxyInfo info = currentProxy;
if (info == null) {
info = new ProxyInfo();
}
ProxyInfo finalInfo = info;
UIUtil.runOnIoDispatcher(() -> {
proxyEnabled = enable;
if (enable && finalInfo instanceof ExternalSocks5Proxy) {
SharedPreferences preferences = MessagesController.getGlobalMainSettings();
((ExternalSocks5Proxy) finalInfo).start();
preferences.edit().putBoolean("proxy_enabled", enable).commit();
} else if (!enable && finalInfo instanceof ExternalSocks5Proxy) {
ProxyInfo info = currentProxy;
if (info == null) {
info = new ProxyInfo();
((ExternalSocks5Proxy) finalInfo).stop();
}
if (enable && info instanceof ExternalSocks5Proxy) {
((ExternalSocks5Proxy) info).start();
} else if (!enable && info instanceof ExternalSocks5Proxy) {
((ExternalSocks5Proxy) info).stop();
}
ConnectionsManager.setProxySettings(enable, info.address, info.port, info.username, info.password, info.secret);
ConnectionsManager.setProxySettings(enable, finalInfo.address, finalInfo.port, finalInfo.username, finalInfo.password, finalInfo.secret);
});
@ -1428,6 +1452,13 @@ public class SharedConfig {
public static void reloadProxyList() {
proxyListLoaded = false;
loadProxyList();
if (proxyEnabled && currentProxy == null) {
setProxyEnable(false);
}
}
public static void loadProxyList() {
@ -1435,98 +1466,47 @@ public class SharedConfig {
return;
}
for (ProxyInfo proxyInfo : proxyList) {
if (proxyInfo instanceof ExternalSocks5Proxy) {
((ExternalSocks5Proxy) proxyInfo).stop();
}
}
proxyListLoaded = true;
proxyList.clear();
currentProxy = null;
int current = MessagesController.getGlobalMainSettings().getInt("current_proxy", 0);
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
for (SubInfo subInfo : SubManager.getSubList().find()) {
boolean hidePublicProxy = preferences.getBoolean("hide_public_proxy", true);
if (!subInfo.enable) continue;
synchronized (sync) {
try {
if (!hidePublicProxy) {
VmessProxy publicProxy = new VmessProxy(VmessLoader.getPublic());
publicProxy.isPublic = true;
proxyList.add(publicProxy);
if (publicProxy.hashCode() == current) {
currentProxy = publicProxy;
publicProxy.start();
}
}
} catch (Exception e) {
FileLog.e(e);
}
File remoteProxyListFile = ProxyUtil.cacheFile;
if (remoteProxyListFile.isFile() && !hidePublicProxy) {
for (String proxy : subInfo.proxies) {
try {
JSONArray proxyArray = new JSONArray(FileUtil.readUtf8String(remoteProxyListFile));
ProxyInfo info = parseProxyInfo(proxy);
for (int a = 0; a < proxyArray.length(); a++) {
info.subId = subInfo.id;
JSONObject proxyObj = proxyArray.getJSONObject(a);
if (info.hashCode() == current) {
ProxyInfo info;
currentProxy = info;
try {
if (info instanceof ExternalSocks5Proxy) {
if (!proxyObj.isNull("proxy")) {
// old remote protocol
info = parseProxyInfo(proxyObj.getString("proxy"));
} else {
info = ProxyInfo.fromJson(proxyObj);
}
} catch (Exception ex) {
FileLog.e("load proxy failed", ex);
continue;
}
info.isPublic = true;
proxyList.add(info);
if (info.hashCode() == current) {
currentProxy = info;
if (info instanceof ExternalSocks5Proxy) {
((ExternalSocks5Proxy) info).start();
}
UIUtil.runOnIoDispatcher(((ExternalSocks5Proxy) info)::start);
}
}
} catch (Exception ex) {
proxyList.add(info);
FileLog.e("invalid proxy list json format", ex);
} catch (Exception e) {
FileLog.d("load sub proxy failed: " + e);
}
@ -1538,59 +1518,52 @@ public class SharedConfig {
boolean error = false;
synchronized (sync) {
if (proxyListFile.isFile()) {
if (proxyListFile.isFile()) {
try {
try {
JSONArray proxyArray = new JSONArray(FileUtil.readUtf8String(proxyListFile));
JSONArray proxyArray = new JSONArray(FileUtil.readUtf8String(proxyListFile));
for (int a = 0; a < proxyArray.length(); a++) {
for (int a = 0; a < proxyArray.length(); a++) {
JSONObject proxyObj = proxyArray.getJSONObject(a);
JSONObject proxyObj = proxyArray.getJSONObject(a);
ProxyInfo info;
ProxyInfo info;
try {
try {
info = ProxyInfo.fromJson(proxyObj);
info = ProxyInfo.fromJson(proxyObj);
} catch (Exception ex) {
} catch (Exception ex) {
FileLog.d("load proxy failed: " + ex);
FileLog.d("load proxy failed: " + ex);
error = true;
error = true;
continue;
continue;
}
}
proxyList.add(info);
if (!proxyObj.isNull("internal")) continue;
if (info.getTitle().toLowerCase().contains("nekox.me")) continue;
if (info.hashCode() == current) {
proxyList.add(info);
currentProxy = info;
if (info.hashCode() == current) {
if (info instanceof ExternalSocks5Proxy) {
currentProxy = info;
if (info instanceof ExternalSocks5Proxy) {
((ExternalSocks5Proxy) info).start();
}
UIUtil.runOnIoDispatcher(((ExternalSocks5Proxy) info)::start);
}
}
} catch (Exception ex) {
FileLog.d("invalid proxy list json format" + ex);
}
} catch (Exception ex) {
FileLog.d("invalid proxy list json format" + ex);
}
}
@ -1657,17 +1630,7 @@ public class SharedConfig {
url.startsWith("tg://socks") ||
url.startsWith("https://t.me/proxy") ||
url.startsWith("https://t.me/socks")) {
url = url
.replace("tg:proxy", "tg://telegram.org")
.replace("tg://proxy", "tg://telegram.org")
.replace("tg://socks", "tg://telegram.org")
.replace("tg:socks", "tg://telegram.org");
Uri data = Uri.parse(url);
return new ProxyInfo(data.getQueryParameter("server"),
Utilities.parseInt(data.getQueryParameter("port")),
data.getQueryParameter("user"),
data.getQueryParameter("pass"),
data.getQueryParameter("secret"));
return ProxyInfo.fromUrl(url);
}
throw new InvalidProxyException();
@ -1696,17 +1659,15 @@ public class SharedConfig {
JSONArray proxyArray = new JSONArray();
synchronized (sync) {
for (ProxyInfo info : new LinkedList<>(proxyList)) {
try {
JSONObject obj = info.toJson();
if (info.isPublic) {
continue;
}
proxyArray.put(obj);
} catch (JSONException e) {
FileLog.e(e);
for (ProxyInfo info : getProxyList()) {
try {
JSONObject obj = info.toJsonInternal();
if (info.subId != 0L) {
continue;
}
proxyArray.put(obj);
} catch (JSONException e) {
FileLog.e(e);
}
}
@ -1730,8 +1691,8 @@ public class SharedConfig {
return info;
}
}
proxyList.add(proxyInfo);
}
proxyList.add(proxyInfo);
saveProxyList();
return proxyInfo;
}
@ -1745,12 +1706,17 @@ public class SharedConfig {
}
}
proxyList.remove(proxyInfo);
if (proxyInfo.subId != 0) {
SubInfo sub = SubManager.getSubList().find(ObjectFilters.eq("id", proxyInfo.subId)).firstOrDefault();
sub.proxies.remove(proxyInfo.toUrl());
SubManager.getSubList().update(sub);
}
saveProxyList();
}
public static void deleteAllProxy() {
setProxyEnable(false);
setCurrentProxy(null);
proxyListLoaded = false;
@ -1764,7 +1730,7 @@ public class SharedConfig {
public static void checkSaveToGalleryFiles() {
try {
File telegramPath = ApplicationLoader.applicationContext.getExternalFilesDir("Telegram").getParentFile();
File telegramPath = ApplicationLoader.applicationContext.getExternalFilesDir(null);
File imagePath = new File(telegramPath, "images");
imagePath.mkdirs();
File videoPath = new File(telegramPath, "videos");

View File

@ -11,8 +11,6 @@ import android.util.Base64;
import com.v2ray.ang.util.Utils;
import org.json.JSONArray;
import org.json.JSONObject;
import org.telegram.messenger.AccountInstance;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ApplicationLoader;
@ -30,16 +28,12 @@ import org.telegram.messenger.StatsController;
import org.telegram.messenger.UserConfig;
import org.telegram.messenger.Utilities;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
@ -55,6 +49,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import tw.nekomimi.nekogram.NekoConfig;
import tw.nekomimi.nekogram.utils.DnsFactory;
import tw.nekomimi.nekogram.utils.UIUtil;
//import org.telegram.messenger.BuildConfig;
@ -249,9 +244,9 @@ public class ConnectionsManager extends BaseController {
public int sendRequest(final TLObject object, final RequestDelegate onComplete, final QuickAckDelegate onQuickAck, final WriteToSocketDelegate onWriteToSocket, final int flags, final int datacenterId, final int connetionType, final boolean immediate) {
final int requestToken = lastRequestToken.getAndIncrement();
Utilities.stageQueue.postRunnable(() -> {
UIUtil.runOnIoDispatcher(() -> {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("send request " + object + " with token = " + requestToken);
FileLog.d("send request " + object.getClass().getSimpleName() + " with token = " + requestToken);
}
try {
NativeByteBuffer buffer = new NativeByteBuffer(object.getObjectSize());
@ -271,14 +266,14 @@ public class ConnectionsManager extends BaseController {
error.code = errorCode;
error.text = errorText;
if (BuildVars.LOGS_ENABLED) {
FileLog.e(object + " got error " + error.code + " " + error.text);
FileLog.e(object.getClass().getSimpleName() + " got error " + error.code + " " + error.text + " with token = " + requestToken);
}
}
if (resp != null) {
resp.networkType = networkType;
}
if (BuildVars.LOGS_ENABLED) {
FileLog.d("java received " + resp + " error = " + error);
FileLog.d("java received " + resp + " error = " + (error == null ? "null" : (error.code + ": " + error.text)));
}
final TLObject finalResponse = resp;
final TLRPC.TL_error finalError = error;

View File

@ -64,6 +64,7 @@ public class TextCell extends FrameLayout {
addView(valueImageView);
setFocusable(true);
setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite));
}
public SimpleTextView getTextView() {

View File

@ -219,6 +219,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import cn.hutool.core.util.StrUtil;
import kotlin.Unit;
import tw.nekomimi.nekogram.MessageDetailsActivity;
import tw.nekomimi.nekogram.MessageHelper;
import tw.nekomimi.nekogram.NekoConfig;
@ -14982,8 +14983,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
File finalLocFile1 = locFile;
AlertUtil.showConfirm(getParentActivity(),
LocaleController.getString("ImportProxyList", R.string.ImportProxyList),
LocaleController.getString("ImportProxyListConfirm", R.string.ImportProxyListConfirm),
LocaleController.getString("OK", R.string.OK), false, (d, v) -> {
R.drawable.baseline_security_24, LocaleController.getString("Import", R.string.Import),
false, () -> {
String status = ProxyListActivity.processProxyListFile(getParentActivity(), finalLocFile1);
if (!StrUtil.isBlank(status)) {
presentFragment(new ProxyListActivity(status));
@ -14995,8 +14996,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
File finalLocFile = locFile;
AlertUtil.showConfirm(getParentActivity(),
LocaleController.getString("ImportStickersList", R.string.ImportStickersList),
LocaleController.getString("ImportStickersConfirm", R.string.ImportStickersConfirm),
LocaleController.getString("OK", R.string.OK), false, (d, v) -> {
R.drawable.deproko_baseline_stickers_filled_24, LocaleController.getString("Import", R.string.Import),
false, () -> {
presentFragment(new StickersActivity(finalLocFile));
});
@ -16844,8 +16845,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
File finalLocFile = locFile;
AlertUtil.showConfirm(getParentActivity(),
LocaleController.getString("ImportProxyList", R.string.ImportProxyList),
LocaleController.getString("ImportProxyListConfirm", R.string.ImportProxyListConfirm),
LocaleController.getString("OK", R.string.OK), false, (d, v) -> {
R.drawable.baseline_security_24,LocaleController.getString("Import", R.string.Import),
false, () -> {
String status = ProxyListActivity.processProxyListFile(getParentActivity(), finalLocFile);
if (!StrUtil.isBlank(status)) {
presentFragment(new ProxyListActivity(status));
@ -16857,8 +16858,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
File finalLocFile = locFile;
AlertUtil.showConfirm(getParentActivity(),
LocaleController.getString("ImportStickersList", R.string.ImportStickersList),
LocaleController.getString("ImportStickersConfirm", R.string.ImportStickersConfirm),
LocaleController.getString("OK", R.string.OK), false, (d, v) -> {
R.drawable.deproko_baseline_stickers_filled_24,LocaleController.getString("Import", R.string.Import), false, () -> {
presentFragment(new StickersActivity(finalLocFile));
});

View File

@ -55,6 +55,7 @@ import org.telegram.tgnet.ConnectionsManager;
import org.telegram.ui.ActionBar.ActionBar;
import org.telegram.ui.ActionBar.ActionBarMenu;
import org.telegram.ui.ActionBar.ActionBarMenuItem;
import org.telegram.ui.ActionBar.AlertDialog;
import org.telegram.ui.ActionBar.BaseFragment;
import org.telegram.ui.ActionBar.BottomSheet;
import org.telegram.ui.ActionBar.Theme;
@ -71,23 +72,32 @@ import org.telegram.ui.Components.URLSpanNoUnderline;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import kotlin.Unit;
import okhttp3.HttpUrl;
import tw.nekomimi.nekogram.NekoConfig;
import tw.nekomimi.nekogram.NekoXConfig;
import tw.nekomimi.nekogram.BottomBuilder;
import tw.nekomimi.nekogram.ShadowsocksRSettingsActivity;
import tw.nekomimi.nekogram.ShadowsocksSettingsActivity;
import tw.nekomimi.nekogram.SubSettingsActivity;
import tw.nekomimi.nekogram.VmessSettingsActivity;
import tw.nekomimi.nekogram.parts.ProxyChecksKt;
import tw.nekomimi.nekogram.sub.SubInfo;
import tw.nekomimi.nekogram.sub.SubManager;
import tw.nekomimi.nekogram.utils.AlertUtil;
import tw.nekomimi.nekogram.utils.FileUtil;
import tw.nekomimi.nekogram.utils.ProxyUtil;
import tw.nekomimi.nekogram.utils.ThreadUtil;
import tw.nekomimi.nekogram.utils.UIUtil;
public class ProxyListActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate {
@ -104,7 +114,6 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
private int rowCount;
private int useProxyRow;
private int hidePublicRow;
private int useProxyDetailRow;
private int connectionsHeaderRow;
private int proxyStartRow;
@ -279,6 +288,8 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
this.alert = alert;
}
private LinkedList<SharedConfig.ProxyInfo> proxyList = SharedConfig.getProxyList();
@Override
public boolean onFragmentCreate() {
super.onFragmentCreate();
@ -295,28 +306,6 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
updateRows(true);
UIUtil.runOnIoDispatcher(() -> {
int times = 0;
while (!ProxyUtil.reloadProxyList()) {
if (times > 5) return;
times++;
try {
Thread.sleep(10 * 1000L);
} catch (InterruptedException e) {
}
}
SharedConfig.reloadProxyList();
updateRows(true);
});
return true;
}
@ -326,6 +315,9 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.proxySettingsChanged);
NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.proxyCheckDone);
NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.didUpdateConnectionState);
if (currentCheck != null) currentCheck.shutdownNow();
}
private int menu_add = 1;
@ -343,8 +335,7 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
private int menu_import_json = 13;
private int menu_delete_all = 14;
private int menu_delete_unavailable = 15;
private int menu_dev_export_public = 30;
private int menu_sub = 16;
public void processProxyList(ArrayList<String> files) {
@ -485,7 +476,7 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
SharedConfig.proxyList = new LinkedList<>(new TreeSet<>(SharedConfig.getProxyList()));
SharedConfig.saveProxyList();
updateRows(true);
} else if (id == menu_export_json || id == menu_dev_export_public) {
} else if (id == menu_export_json) {
File cacheFile = new File(ApplicationLoader.applicationContext.getExternalCacheDir(), "Proxy-List-" + new Date().toLocaleString() + ".nekox.json");
try {
@ -498,11 +489,7 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
for (SharedConfig.ProxyInfo info : SharedConfig.getProxyList()) {
if (id == menu_dev_export_public) {
if (!info.isPublic) continue;
} else if (info.isPublic) {
if (info.subId <= 1) {
continue;
@ -568,20 +555,20 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
} else if (id == menu_delete_all) {
AlertUtil.showConfirm(getParentActivity(),
LocaleController.getString("DeleteAllServer", R.string.DeleteAllServer),
LocaleController.getString("DeleteAllServerConfirm", R.string.DeleteAllServerConfirm),
LocaleController.getString("Delete", R.string.Delete),
true, (d, v) -> {
R.drawable.baseline_delete_24, LocaleController.getString("Delete", R.string.Delete),
true, () -> {
SharedConfig.deleteAllProxy();
updateRows(true);
});
} else if (id == menu_delete_unavailable) {
AlertUtil.showConfirm(getParentActivity(),
LocaleController.getString("DeleteUnavailableServer", R.string.DeleteUnavailableServer),
LocaleController.getString("DeleteUnavailableServerConfirm", R.string.DeleteUnavailableServerConfirm),
LocaleController.getString("Delete", R.string.Delete),
true, (d, v) -> {
R.drawable.baseline_delete_24, LocaleController.getString("Delete", R.string.Delete),
true, () -> {
deleteUnavailableProxy();
});
} else if (id == menu_sub) {
showSubDialog();
}
}
});
@ -673,6 +660,8 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
}
menu.addItem(menu_sub, R.drawable.msg_list);
otherItem = menu.addItem(menu_other, R.drawable.ic_ab_other);
otherItem.setContentDescription(LocaleController.getString("AccDescrMoreOptions", R.string.AccDescrMoreOptions));
otherItem.addSubItem(menu_retest_ping, LocaleController.getString("RetestPing", R.string.RetestPing));
@ -682,12 +671,6 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
otherItem.addSubItem(menu_delete_all, LocaleController.getString("DeleteAllServer", R.string.DeleteAllServer));
otherItem.addSubItem(menu_delete_unavailable, LocaleController.getString("DeleteUnavailableServer", R.string.DeleteUnavailableServer));
if (NekoXConfig.developerMode) {
otherItem.addSubItem(menu_dev_export_public, "Export public servers");
}
listAdapter = new ListAdapter(context);
fragmentView = new FrameLayout(context);
@ -722,25 +705,6 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
updateRows(true);
} else if (position == hidePublicRow) {
if (NekoConfig.hidePublicProxy && SharedConfig.proxyEnabled && SharedConfig.currentProxy != null && SharedConfig.currentProxy.isPublic) {
NotificationCenter.getGlobalInstance().removeObserver(ProxyListActivity.this, NotificationCenter.proxySettingsChanged);
SharedConfig.setCurrentProxy(null);
NotificationCenter.getGlobalInstance().addObserver(ProxyListActivity.this, NotificationCenter.proxySettingsChanged);
}
NekoConfig.toggleHidePublicProxy();
TextCheckCell textCheckCell = (TextCheckCell) view;
textCheckCell.setChecked(NekoConfig.hidePublicProxy);
SharedConfig.reloadProxyList();
listView.getRecycledViewPool().clear();
updateRows(true);
} else if (position == callsRow) {
useProxyForCalls = !useProxyForCalls;
TextCheckCell textCheckCell = (TextCheckCell) view;
@ -768,11 +732,11 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
builder.setItems(new String[]{
info.isPublic ? null : LocaleController.getString("EditProxy", R.string.EditProxy),
info.isPublic ? null : LocaleController.getString("ShareProxy", R.string.ShareProxy),
info.isPublic ? null : LocaleController.getString("ShareQRCode", R.string.ShareQRCode),
info.isPublic ? null : LocaleController.getString("CopyLink", R.string.CopyLink),
info.isPublic ? null : LocaleController.getString("ProxyDelete", R.string.ProxyDelete),
info.subId == 1 ? null : LocaleController.getString("EditProxy", R.string.EditProxy),
info.subId == 1 ? null : LocaleController.getString("ShareProxy", R.string.ShareProxy),
info.subId == 1 ? null : LocaleController.getString("ShareQRCode", R.string.ShareQRCode),
info.subId == 1 ? null : LocaleController.getString("CopyLink", R.string.CopyLink),
LocaleController.getString("ProxyDelete", R.string.ProxyDelete),
LocaleController.getString("Cancel", R.string.Cancel)
}, new int[]{
@ -812,43 +776,28 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
} else if (i == 4) {
BottomSheet.Builder del = new BottomSheet.Builder(context);
AlertUtil.showConfirm(getParentActivity(),
LocaleController.getString("DeleteProxy", R.string.DeleteProxy),
R.drawable.baseline_delete_24, LocaleController.getString("Delete", R.string.Delete),
true, () -> {
del.setItems(new String[]{
LocaleController.getString("OK", R.string.OK),
LocaleController.getString("Cancel", R.string.Cancel)
}, new int[]{
R.drawable.baseline_delete_24,
R.drawable.msg_cancel
}, (dv, di) -> {
if (di == 0) {
SharedConfig.deleteProxy(info);
if (SharedConfig.currentProxy == null) {
SharedConfig.setProxyEnable(false);
}
NotificationCenter.getGlobalInstance().removeObserver(ProxyListActivity.this, NotificationCenter.proxySettingsChanged);
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.proxySettingsChanged);
NotificationCenter.getGlobalInstance().addObserver(ProxyListActivity.this, NotificationCenter.proxySettingsChanged);
updateRows(false);
if (listAdapter != null) {
listAdapter.notifyItemRemoved(position);
SharedConfig.deleteProxy(info);
if (SharedConfig.currentProxy == null) {
listAdapter.notifyItemChanged(useProxyRow, ListAdapter.PAYLOAD_CHECKED_CHANGED);
listAdapter.notifyItemChanged(callsRow, ListAdapter.PAYLOAD_CHECKED_CHANGED);
SharedConfig.setProxyEnable(false);
}
NotificationCenter.getGlobalInstance().removeObserver(ProxyListActivity.this, NotificationCenter.proxySettingsChanged);
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.proxySettingsChanged);
NotificationCenter.getGlobalInstance().addObserver(ProxyListActivity.this, NotificationCenter.proxySettingsChanged);
updateRows(false);
if (listAdapter != null) {
listAdapter.notifyItemRemoved(position);
if (SharedConfig.currentProxy == null) {
listAdapter.notifyItemChanged(useProxyRow, ListAdapter.PAYLOAD_CHECKED_CHANGED);
listAdapter.notifyItemChanged(callsRow, ListAdapter.PAYLOAD_CHECKED_CHANGED);
}
}
}
}
});
showDialog(del.create());
});
}
@ -866,6 +815,32 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
AlertUtil.showSimpleAlert(context, alert);
alert = null;
} else {
UIUtil.runOnIoDispatcher(() -> {
boolean updated = false;
for (SubInfo subInfo : SubManager.getSubList().find()) {
try {
subInfo.proxies = subInfo.reloadProxies();
subInfo.lastFetch = System.currentTimeMillis();
SubManager.getSubList().update(subInfo, true);
updated = subInfo.id == 1;
} catch (SubInfo.AllTriesFailed allTriesFailed) {
}
}
if (updated) updateRows(true);
});
}
return fragmentView;
@ -987,7 +962,6 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
private void updateRows(boolean notify) {
rowCount = 0;
useProxyRow = rowCount++;
hidePublicRow = rowCount++;
useProxyDetailRow = rowCount++;
connectionsHeaderRow = rowCount++;
if (!SharedConfig.proxyList.isEmpty()) {
@ -1034,21 +1008,25 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
}
}
private ExecutorService currentCheck;
private void checkProxyList(boolean force) {
for (int a = 0, count = SharedConfig.proxyList.size(); a < count; a++) {
final SharedConfig.ProxyInfo proxyInfo = SharedConfig.proxyList.get(a);
if (proxyInfo.checking || (SystemClock.elapsedRealtime() - proxyInfo.availableCheckTime < 2 * 60 * 1000L && !force)) {
continue;
}
checkSingleProxy(proxyInfo);
if (currentCheck == null) {
currentCheck = Executors.newFixedThreadPool(3);
}
ProxyChecksKt.checkProxyList(this, force, currentCheck);
}
private void deleteUnavailableProxy() {
for (SharedConfig.ProxyInfo info : SharedConfig.getProxyList()) {
if (info.isPublic) continue;
if (info.subId != 0) continue;
checkSingleProxy(info, 1, () -> {
@ -1072,52 +1050,195 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
}
private void checkSingleProxy(SharedConfig.ProxyInfo proxyInfo) {
checkSingleProxy(proxyInfo, proxyInfo instanceof SharedConfig.ExternalSocks5Proxy ? 3 : 0, () -> {
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.proxyCheckDone, proxyInfo);
});
}
private void checkSingleProxy(SharedConfig.ProxyInfo proxyInfo, int repeat, Runnable callback) {
public void checkSingleProxy(SharedConfig.ProxyInfo proxyInfo, int repeat, Runnable callback) {
proxyInfo.checking = true;
UIUtil.runOnUIThread(() -> NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.proxyCheckDone, proxyInfo));
if (proxyInfo instanceof SharedConfig.ExternalSocks5Proxy && !((SharedConfig.ExternalSocks5Proxy) proxyInfo).isStarted()) {
((SharedConfig.ExternalSocks5Proxy) proxyInfo).start();
}
UIUtil.runOnIoDispatcher(() -> {
proxyInfo.proxyCheckPingId = ConnectionsManager.getInstance(currentAccount).checkProxy(proxyInfo.address, proxyInfo.port, proxyInfo.username, proxyInfo.password, proxyInfo.secret, time -> AndroidUtilities.runOnUIThread(() -> {
proxyInfo.availableCheckTime = SystemClock.elapsedRealtime();
if (time == -1) {
if (repeat > 0) {
checkSingleProxy(proxyInfo, repeat - 1, callback);
if (proxyInfo instanceof SharedConfig.ExternalSocks5Proxy && !((SharedConfig.ExternalSocks5Proxy) proxyInfo).isStarted()) {
((SharedConfig.ExternalSocks5Proxy) proxyInfo).start();
}
proxyInfo.proxyCheckPingId = ConnectionsManager.getInstance(currentAccount).checkProxy(proxyInfo.address, proxyInfo.port, proxyInfo.username, proxyInfo.password, proxyInfo.secret, time -> AndroidUtilities.runOnUIThread(() -> {
proxyInfo.availableCheckTime = SystemClock.elapsedRealtime();
if (time == -1) {
if (repeat > 0) {
checkSingleProxy(proxyInfo, repeat - 1, callback);
} else {
proxyInfo.checking = false;
proxyInfo.available = false;
proxyInfo.ping = 0;
if (proxyInfo instanceof SharedConfig.ExternalSocks5Proxy && proxyInfo != SharedConfig.currentProxy) {
((SharedConfig.ExternalSocks5Proxy) proxyInfo).stop();
}
if (callback != null) {
UIUtil.runOnUIThread(callback);
}
}
} else {
proxyInfo.checking = false;
proxyInfo.available = false;
proxyInfo.ping = 0;
proxyInfo.ping = time;
proxyInfo.available = true;
if (proxyInfo instanceof SharedConfig.ExternalSocks5Proxy && proxyInfo != SharedConfig.currentProxy) {
((SharedConfig.ExternalSocks5Proxy) proxyInfo).stop();
}
if (callback != null) {
UIUtil.runOnUIThread(callback::run);
UIUtil.runOnUIThread(callback);
}
}
} else {
proxyInfo.checking = false;
proxyInfo.ping = time;
proxyInfo.available = true;
if (proxyInfo instanceof SharedConfig.ExternalSocks5Proxy && proxyInfo != SharedConfig.currentProxy) {
((SharedConfig.ExternalSocks5Proxy) proxyInfo).stop();
}));
});
}
private void showSubDialog() {
BottomBuilder builder = new BottomBuilder(getParentActivity());
builder.addTitle(LocaleController.getString("ProxySubscription", R.string.ProxySubscription));
HashMap<SubInfo, Boolean> toChange = new HashMap<>();
for (SubInfo sub : SubManager.getSubList().find()) {
TextCheckCell subItem = builder.addCheckItem(sub.name, sub.enable, true, (it) -> {
boolean curr = (toChange.containsKey(sub) ? toChange.get(sub) : sub.enable);
if (curr != sub.enable) {
toChange.remove(sub);
} else {
toChange.put(sub, !sub.enable);
}
if (callback != null) {
UIUtil.runOnUIThread(callback::run);
it.setChecked(!curr);
return Unit.INSTANCE;
});
subItem.setOnLongClickListener((it) -> {
if (sub.internal) return false;
builder.dismiss();
presentFragment(new SubSettingsActivity(sub));
return true;
});
}
builder.addButton(LocaleController.getString("Add", R.string.Add), false, true, (it) -> {
builder.dismiss();
presentFragment(new SubSettingsActivity());
return Unit.INSTANCE;
});
builder.addButton(LocaleController.getString("Update", R.string.Update), (it) -> {
AlertDialog pro = AlertUtil.showProgress(getParentActivity(), LocaleController.getString("SubscriptionUpdating", R.string.SubscriptionUpdating));
AtomicBoolean canceled = new AtomicBoolean();
pro.setOnCancelListener((__) -> {
canceled.set(true);
});
pro.show();
UIUtil.runOnIoDispatcher(() -> {
for (SubInfo subInfo : SubManager.getSubList().find()) {
try {
subInfo.proxies = subInfo.reloadProxies();
subInfo.lastFetch = System.currentTimeMillis();
} catch (SubInfo.AllTriesFailed allTriesFailed) {
if (canceled.get()) return;
AlertUtil.showSimpleAlert(getParentActivity(), "All tries failed: " + allTriesFailed.toString().trim());
continue;
}
SubManager.getSubList().update(subInfo, true);
if (canceled.get()) return;
}
SharedConfig.reloadProxyList();
updateRows(true);
UIUtil.runOnUIThread(() -> {
builder.dismiss();
pro.dismiss();
});
});
return Unit.INSTANCE;
});
builder.addButton(LocaleController.getString("OK", R.string.OK), (it) -> {
builder.dismiss();
if (!toChange.isEmpty()) {
AlertDialog pro = AlertUtil.showProgress(getParentActivity());
pro.setCanCacnel(false);
pro.show();
UIUtil.runOnIoDispatcher(() -> {
for (Map.Entry<SubInfo, Boolean> toChangeE : toChange.entrySet()) {
toChangeE.getKey().enable = toChangeE.getValue();
SubManager.getSubList().update(toChangeE.getKey(), true);
}
SharedConfig.reloadProxyList();
UIUtil.runOnUIThread(() -> updateRows(true));
ThreadUtil.sleep(233L);
UIUtil.runOnUIThread(pro::dismiss);
});
}
}));
return Unit.INSTANCE;
});
builder.show();
}
@ -1207,8 +1328,6 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
TextCheckCell checkCell = (TextCheckCell) holder.itemView;
if (position == useProxyRow) {
checkCell.setTextAndCheck(LocaleController.getString("UseProxySettings", R.string.UseProxySettings), useProxySettings, true);
} else if (position == hidePublicRow) {
checkCell.setTextAndCheck(LocaleController.getString("HidePublicProxyList", R.string.HidePublicProxyList), NekoConfig.hidePublicProxy, true);
} else if (position == callsRow) {
checkCell.setTextAndCheck(LocaleController.getString("UseProxyForCalls", R.string.UseProxyForCalls), useProxyForCalls, false);
}
@ -1238,8 +1357,6 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
TextCheckCell checkCell = (TextCheckCell) holder.itemView;
if (position == useProxyRow) {
checkCell.setChecked(useProxySettings);
} else if (position == hidePublicRow) {
checkCell.setChecked(NekoConfig.hidePublicProxy);
} else if (position == callsRow) {
checkCell.setChecked(useProxyForCalls);
}
@ -1256,8 +1373,6 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
int position = holder.getAdapterPosition();
if (position == useProxyRow) {
checkCell.setChecked(useProxySettings);
} else if (position == hidePublicRow) {
checkCell.setChecked(NekoConfig.hidePublicProxy);
} else if (position == callsRow) {
checkCell.setChecked(useProxyForCalls);
}
@ -1267,7 +1382,7 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
@Override
public boolean isEnabled(RecyclerView.ViewHolder holder) {
int position = holder.getAdapterPosition();
return position == useProxyRow || position == hidePublicRow || position == callsRow || position >= proxyStartRow && position < proxyEndRow;
return position == useProxyRow || position == callsRow || position >= proxyStartRow && position < proxyEndRow;
}
@Override
@ -1306,7 +1421,7 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
public int getItemViewType(int position) {
if (position == useProxyDetailRow || position == proxyDetailRow) {
return 0;
} else if (position == useProxyRow || position == hidePublicRow || position == callsRow) {
} else if (position == useProxyRow || position == callsRow) {
return 3;
} else if (position == connectionsHeaderRow) {
return 2;

View File

@ -12,10 +12,7 @@ import org.telegram.messenger.R
import org.telegram.ui.ActionBar.BottomSheet
import org.telegram.ui.ActionBar.BottomSheet.BottomSheetCell
import org.telegram.ui.ActionBar.Theme
import org.telegram.ui.Cells.HeaderCell
import org.telegram.ui.Cells.RadioButtonCell
import org.telegram.ui.Cells.ShadowSectionCell
import org.telegram.ui.Cells.TextCheckCell
import org.telegram.ui.Cells.*
import org.telegram.ui.Components.HintEditText
import org.telegram.ui.Components.LayoutHelper
import java.util.*
@ -210,6 +207,13 @@ class BottomBuilder(val ctx: Context) {
}
@JvmOverloads
fun addCancelItem() {
addItem(LocaleController.getString("Cancel", R.string.Cancel),R.drawable.baseline_cancel_24) { dismiss() }
}
@JvmOverloads
fun addCancelButton(left: Boolean = true) {
@ -238,12 +242,21 @@ class BottomBuilder(val ctx: Context) {
}
@JvmOverloads
fun addItem(text: String, icon: Int = 0, listener: (cell: BottomSheetCell) -> Unit): BottomSheetCell {
fun addItem(): TextCell {
return BottomSheetCell(ctx, 0).apply {
return TextCell(ctx).apply {
setTextAndIcon(text, icon)
this@BottomBuilder.rootView.addView(this, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT or Gravity.TOP))
}
}
fun addItem(text: String, icon: Int = 0,red: Boolean = false, listener: (cell: TextCell) -> Unit): TextCell {
return TextCell(ctx).apply {
setTextAndIcon(text, icon,true)
setOnClickListener {
@ -251,15 +264,21 @@ class BottomBuilder(val ctx: Context) {
}
this@BottomBuilder.rootView.addView(this, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 50, Gravity.LEFT or Gravity.TOP))
if (red) {
setColors("key_dialogTextRed2","key_dialogTextRed2")
}
this@BottomBuilder.rootView.addView(this, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT or Gravity.TOP))
}
}
fun addItems(text: Array<String>, icon: (Int) -> Int, listener: (index: Int, text: String, cell: BottomSheetCell) -> Unit): List<BottomSheetCell> {
fun addItems(text: Array<String>, icon: (Int) -> Int, listener: (index: Int, text: String, cell: TextCell) -> Unit): List<TextCell> {
val list = mutableListOf<BottomSheetCell>()
val list = mutableListOf<TextCell>()
text.forEachIndexed { index, textI ->

View File

@ -1,5 +1,6 @@
package tw.nekomimi.nekogram
import android.util.Base64
import com.v2ray.ang.V2RayConfig.SS_PROTOCOL
import com.v2ray.ang.util.Utils
import kotlinx.coroutines.runBlocking
@ -9,6 +10,7 @@ import org.telegram.messenger.ApplicationLoader
import org.telegram.messenger.FileLog
import tw.nekomimi.nekogram.utils.FileUtil
import java.io.File
import java.util.*
import kotlin.concurrent.thread
import kotlin.properties.Delegates
@ -174,7 +176,7 @@ class ShadowsocksLoader {
override fun toString(): String {
var url = "ss://" + Utils.encode("$method:$password") + "@$host:$remotePort"
var url = "ss://" + Base64.encode("$method:$password".toByteArray(),Base64.NO_WRAP or Base64.URL_SAFE) + "@$host:$remotePort"
if (remarks?.isNotBlank() == true) url += "#" + Utils.urlEncode(remarks!!)

View File

@ -0,0 +1,336 @@
/*
* 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.content.Context;
import android.os.Build;
import android.text.InputType;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.R;
import org.telegram.messenger.SharedConfig;
import org.telegram.ui.ActionBar.ActionBar;
import org.telegram.ui.ActionBar.ActionBarMenu;
import org.telegram.ui.ActionBar.AlertDialog;
import org.telegram.ui.ActionBar.BaseFragment;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.ActionBar.ThemeDescription;
import org.telegram.ui.Components.EditTextBoldCursor;
import org.telegram.ui.Components.LayoutHelper;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import cn.hutool.core.util.StrUtil;
import kotlin.collections.ArraysKt;
import kotlin.collections.CollectionsKt;
import tw.nekomimi.nekogram.sub.SubInfo;
import tw.nekomimi.nekogram.sub.SubManager;
import tw.nekomimi.nekogram.utils.AlertUtil;
import tw.nekomimi.nekogram.utils.UIUtil;
public class SubSettingsActivity extends BaseFragment {
private EditText[] inputFields;
private EditText urlsField;
private EditTextBoldCursor remarksField;
private ScrollView scrollView;
private LinearLayout linearLayout2;
private LinearLayout inputFieldsContainer;
private SubInfo subInfo;
private boolean ignoreOnTextChange;
private static int done_button = 1;
private static int menu_delete = 2;
public SubSettingsActivity() {
super();
subInfo = new SubInfo();
}
public SubSettingsActivity(SubInfo subInfo) {
super();
this.subInfo = subInfo;
}
@Override
public void onResume() {
super.onResume();
AndroidUtilities.requestAdjustResize(getParentActivity(), classGuid);
}
@Override
public View createView(Context context) {
actionBar.setTitle(LocaleController.getString("ProxySubDetails", R.string.ProxySubDetails));
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(urlsField.getText())) {
urlsField.requestFocus();
AndroidUtilities.showKeyboard(urlsField);
return;
}
if (StrUtil.isBlank(remarksField.getText())) {
remarksField.requestFocus();
AndroidUtilities.showKeyboard(remarksField);
return;
}
subInfo.urls = ArraysKt.toList(urlsField.getText().toString().split("\n"));
subInfo.name = remarksField.getText().toString();
doGetProxies();
} else if (id == menu_delete) {
AlertUtil.showConfirm(getParentActivity(),
LocaleController.getString("SubscriptionDelete", R.string.SubscriptionDelete),
R.drawable.baseline_delete_24, LocaleController.getString("Delete", R.string.Delete),
true, () -> {
AlertDialog pro = AlertUtil.showProgress(getParentActivity());
pro.show();
UIUtil.runOnIoDispatcher(() -> {
SubManager.getSubList().remove(subInfo);
SharedConfig.reloadProxyList();
UIUtil.runOnUIThread(() -> {
pro.dismiss();
finishFragment();
});
});
});
}
}
});
ActionBarMenu menu = actionBar.createMenu();
if (subInfo.id != 0) {
menu.addItem(menu_delete, R.drawable.baseline_delete_24, AndroidUtilities.dp(56)).setContentDescription(LocaleController.getString("Delete", R.string.Delete));
}
menu.addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56)).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 EditText[2];
urlsField = new EditText(context);
inputFields[0] = urlsField;
urlsField.setImeOptions(InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE);
urlsField.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
urlsField.setHint(LocaleController.getString("SubscriptionUrls", R.string.SubscriptionUrls));
urlsField.setText(CollectionsKt.joinToString(subInfo.urls, "\n", "", "", -1, "", null));
urlsField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
urlsField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText));
urlsField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText));
urlsField.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP);
urlsField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueHeader));
urlsField.setSingleLine(false);
urlsField.setMinLines(6);
inputFieldsContainer.addView(urlsField, LayoutHelper.createLinear(-1, -2, 17, 0, 17, 0));
FrameLayout container = new FrameLayout(context);
remarksField = mkCursor();
inputFields[1] = remarksField;
remarksField.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
remarksField.setHintText(LocaleController.getString("ProxyRemarks", R.string.ProxyRemarks));
remarksField.setText(subInfo.name);
remarksField.setSelection(remarksField.length());
container.addView(remarksField, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 17, 0, 17, 0));
inputFieldsContainer.addView((View) remarksField.getParent(), LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
return fragmentView;
}
EditTextBoldCursor mkCursor() {
EditTextBoldCursor cursor = new EditTextBoldCursor(getParentActivity());
cursor.setSingleLine(true);
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.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;
}
public void doGetProxies() {
AlertDialog pro = AlertUtil.showProgress(getParentActivity(), LocaleController.getString("SubscriptionUpdating", R.string.SubscriptionUpdating));
AtomicBoolean canceled = new AtomicBoolean();
pro.setOnCancelListener((it) -> {
canceled.set(true);
});
pro.show();
UIUtil.runOnIoDispatcher(() -> {
try {
subInfo.proxies = subInfo.reloadProxies();
subInfo.lastFetch = System.currentTimeMillis();
} catch (SubInfo.AllTriesFailed allTriesFailed) {
if (canceled.get()) return;
UIUtil.runOnUIThread(pro::dismiss);
AlertUtil.showSimpleAlert(getParentActivity(), "tries failed: " + allTriesFailed.toString().trim());
return;
}
SubManager.getSubList().update(subInfo, true);
SharedConfig.reloadProxyList();
UIUtil.runOnUIThread(() -> {
pro.dismiss();
finishFragment();
});
});
}
@Override
public ThemeDescription[] getThemeDescriptions() {
final ThemeDescription.ThemeDescriptionDelegate delegate = () -> {
if (inputFields != null) {
for (int i = 0; i < inputFields.length; i++) {
inputFields[i].setText(Theme.getColor(Theme.key_windowBackgroundWhiteInputField));
}
}
};
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));
}
return arrayList.toArray(new ThemeDescription[0]);
}
}

View File

@ -17,7 +17,6 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
import org.telegram.messenger.FileLog
import org.telegram.messenger.LocaleController
import org.telegram.messenger.R
import kotlin.concurrent.thread
import kotlin.random.Random
class VmessLoader {
@ -395,9 +394,9 @@ class VmessLoader {
return port
} catch (e: Exception) {
} catch (e: Throwable) {
retry --
retry--
if (retry <= 0) {
@ -415,13 +414,9 @@ class VmessLoader {
fun stop() {
thread {
runCatching {
runCatching {
point.stopLoop()
}
point.stopLoop()
}

View File

@ -0,0 +1,56 @@
package tw.nekomimi.nekogram.parts
import android.os.SystemClock
import cn.hutool.core.thread.ThreadUtil
import kotlinx.coroutines.*
import org.telegram.messenger.AndroidUtilities
import org.telegram.messenger.NotificationCenter
import org.telegram.messenger.SharedConfig
import org.telegram.messenger.SharedConfig.ExternalSocks5Proxy
import org.telegram.ui.ProxyListActivity
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicBoolean
@JvmOverloads
fun ProxyListActivity.checkProxyList(force: Boolean, context: ExecutorService) {
GlobalScope.launch(Dispatchers.IO) {
SharedConfig.proxyList.forEach {
if (it.checking || SystemClock.elapsedRealtime() - it.availableCheckTime < 2 * 60 * 1000L && !force) {
return@forEach
}
context.execute {
runCatching {
val lock = AtomicBoolean()
checkSingleProxy(it, if (it is ExternalSocks5Proxy) 1 else 0) {
AndroidUtilities.runOnUIThread {
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.proxyCheckDone, it)
}
lock.set(true)
}
while (!lock.get()) ThreadUtil.sleep(100L)
}
}
}
}
}

View File

@ -0,0 +1,176 @@
package tw.nekomimi.nekogram.sub;
import androidx.annotation.NonNull;
import org.dizitart.no2.Document;
import org.dizitart.no2.NitriteId;
import org.dizitart.no2.mapper.Mappable;
import org.dizitart.no2.mapper.NitriteMapper;
import org.dizitart.no2.objects.Id;
import org.dizitart.no2.objects.Index;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.R;
import org.telegram.messenger.SharedConfig;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.xml.transform.ErrorListener;
import cn.hutool.core.util.StrUtil;
import tw.nekomimi.nekogram.utils.HttpUtil;
import tw.nekomimi.nekogram.utils.ProxyUtil;
@Index("id")
@SuppressWarnings("unchecked")
public class SubInfo implements Mappable {
@Id
public long id;
public String name;
public List<String> urls = new LinkedList<>();
public List<String> proxies = new LinkedList<>();
public long lastFetch = -1L;
public boolean enable = true;
public boolean internal;
public String displayName() {
if (id == 1) return LocaleController.getString("PublicPrefix", R.string.PublicPrefix);
if (name.length() < 5) return name;
return name.substring(0,5) + "...";
}
public List<String> reloadProxies() throws AllTriesFailed {
HashMap<String,Exception> exceptions = new HashMap<>();
if (id == 1) {
try {
List<String> legacyList = ProxyUtil.downloadLegacyProxyList();
if (legacyList != null) {
exceptions.put("<Internal>", new IOException("Update Failed"));
return legacyList;
}
} catch (Exception e) {
exceptions.put("<Internal>", e);
}
}
for (String url : urls) {
try {
String source = HttpUtil.get(url);
return ProxyUtil.parseProxies(source);
} catch (Exception e) {
exceptions.put(url,e);
}
}
throw new AllTriesFailed(exceptions);
}
public static class AllTriesFailed extends IOException {
public AllTriesFailed(HashMap<String,Exception> exceptions) {
this.exceptions = exceptions;
}
public HashMap<String,Exception> exceptions;
@NonNull @Override public String toString() {
StringBuilder errors = new StringBuilder();
for (Map.Entry<String, Exception> e : exceptions.entrySet()) {
errors.append(e.getKey()).append(": ");
if (StrUtil.isBlank(e.getValue().getMessage())) {
errors.append(e.getValue().getMessage());
} else {
errors.append(e.getValue().getClass().getSimpleName());
}
errors.append("\n\n");
}
return errors.toString();
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SubInfo subInfo = (SubInfo) o;
return id == subInfo.id;
}
@Override
public Document write(NitriteMapper mapper) {
Document document = new Document();
if (id == 0) id = SubManager.getSubList().find().totalCount() + 10;
document.put("id", id);
document.put("name", name);
document.put("urls", urls);
document.put("proxies",proxies);
document.put("lastFetch", lastFetch);
document.put("enable", enable);
document.put("internal", internal);
return document;
}
@Override
public void read(NitriteMapper mapper, Document document) {
id = document.get("id", Long.class);
name = document.get("name", String.class);
urls = (List<String>) document.get("urls");
proxies = (List<String>) document.get("proxies");
lastFetch = document.get("lastFetch",Long.class);
enable = document.get("enable",Boolean.class);
internal = document.get("internal",Boolean.class);
}
}

View File

@ -0,0 +1,37 @@
package tw.nekomimi.nekogram.sub
import org.dizitart.no2.NitriteId
import org.dizitart.no2.objects.filters.ObjectFilters
import org.telegram.messenger.FileLog
import org.telegram.messenger.LocaleController
import org.telegram.messenger.R
import tw.nekomimi.nekogram.database.mkDatabase
object SubManager {
val database by lazy { mkDatabase("proxy_sub") }
@JvmStatic
val subList by lazy {
database.getRepository("proxy_sub", SubInfo::class.java).apply {
val public = find(ObjectFilters.eq("id",1L)).firstOrDefault()
update(SubInfo().apply {
name = LocaleController.getString("NekoXProxy", R.string.NekoXProxy)
enable = public?.enable ?: true
id = 1L
internal = true
proxies = public?.proxies ?: listOf()
}, true)
}
}
}

View File

@ -11,7 +11,10 @@ import org.telegram.messenger.LocaleController
import org.telegram.messenger.R
import org.telegram.ui.ActionBar.AlertDialog
import org.telegram.ui.ActionBar.Theme
import org.telegram.ui.Cells.TextCell
import org.telegram.ui.Components.EditTextBoldCursor
import org.telegram.ui.Components.NumberPicker
import tw.nekomimi.nekogram.BottomBuilder
import tw.nekomimi.nekogram.NekoConfig
import java.util.concurrent.atomic.AtomicReference
@ -36,7 +39,7 @@ object AlertUtil {
builder.setTitle(LocaleController.getString("NekoX", R.string.NekoX))
builder.setMessage(text)
builder.setPositiveButton(LocaleController.getString("OK", R.string.OK)) { _,_ ->
builder.setPositiveButton(LocaleController.getString("OK", R.string.OK)) { _, _ ->
listener?.invoke(builder)
@ -48,8 +51,37 @@ object AlertUtil {
})
@JvmOverloads
@JvmStatic
fun showConfirm(ctx: Context, title: String, text: String, button: String, red: Boolean = false, listener: DialogInterface.OnClickListener) = UIUtil.runOnUIThread(Runnable {
fun showProgress(ctx: Context,text: String = LocaleController.getString("",R.string.Loading)): AlertDialog {
return AlertDialog.Builder(ctx,1).apply {
setMessage(text)
}.create()
}
fun showInput(ctx: Context, title: String, hint: String, onInput: (AlertDialog.Builder, String) -> String) = UIUtil.runOnUIThread( Runnable {
val builder = AlertDialog.Builder(ctx)
builder.setTitle(title)
builder.setView(EditTextBoldCursor(ctx).apply {
setHintText(hint)
})
})
@JvmStatic
@JvmOverloads
fun showConfirm(ctx: Context, title: String, text: String? = null,icon: Int, button: String, red: Boolean, listener: Runnable) = UIUtil.runOnUIThread(Runnable {
/*
val builder = AlertDialog.Builder(ctx)
@ -67,6 +99,32 @@ object AlertUtil {
}
*/
val builder = BottomBuilder(ctx)
if (text != null) {
builder.addTitle(title, true, text)
} else {
builder.addTitle(title,false)
}
builder.addItem(button, icon,red) {
builder.dismiss()
listener.run()
}
builder.addCancelItem()
builder.show()
})
@JvmStatic

View File

@ -0,0 +1,129 @@
package tw.nekomimi.nekogram.utils
import cn.hutool.core.collection.CollUtil
import cn.hutool.core.util.ArrayUtil
import cn.hutool.core.util.StrUtil
import java.math.BigInteger
import java.util.*
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.atomic.AtomicReference
import kotlin.reflect.KMutableProperty0
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty0
/**
* 一些基于语言特性的全局函数
*/
fun <T> T.applyIf(boolean: Boolean, block: (T.() -> Unit)?): T {
if (boolean) block?.invoke(this)
return this
}
fun <T> T.applyIfNot(boolean: Boolean, block: (T.() -> Unit)?): T {
if (!boolean) block?.invoke(this)
return this
}
fun String.input(vararg params: Any): String {
return StrUtil.format(this, *params)
}
val Number.asByteArray get() = BigInteger.valueOf(toLong()).toByteArray()!!
val ByteArray.asLong get() = BigInteger(this).toLong()
val ByteArray.asInt get() = BigInteger(this).toInt()
fun <T> Array<T>.shift(): Array<T> {
return shift(1)
}
fun <T> Array<T>.shift(size: Int): Array<T> {
return ArrayUtil.sub(this, size, this.size)
}
fun <T> Collection<T>.shift() = shift(1)
fun <T> Collection<T>.shift(size: Int): Collection<T> {
return LinkedList(CollUtil.sub(this, size, this.size))
}
class WriteOnlyField<T>(val setter: (T) -> Unit) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): T = error("WriteOnlyField : ${property.name}")
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
setter.invoke(value)
}
}
class WeakField<T> {
private var value: T? = null
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
return value
?: throw IllegalStateException("Property ${property.name} should be initialized before get.")
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
this.value = value
}
}
fun <T, R> receive(initializer: T.() -> R) = LazyReceiver(initializer)
class LazyReceiver<T, R>(val initializer: T.() -> R) {
private var isInitialized by AtomicBoolean()
private var _impl: R? = null
@Suppress("UNCHECKED_CAST")
operator fun getValue(thisRef: Any?, property: KProperty<*>): R {
if (isInitialized) return _impl as R
synchronized(this) {
if (isInitialized) return _impl as R
_impl = initializer(thisRef as T)
isInitialized = true
return _impl as R
}
}
}
operator fun <F> KProperty0<F>.getValue(thisRef: Any?, property: KProperty<*>): F = get()
operator fun <F> KMutableProperty0<F>.setValue(thisRef: Any?, property: KProperty<*>, value: F) = set(value)
operator fun AtomicBoolean.getValue(thisRef: Any?, property: KProperty<*>): Boolean = get()
operator fun AtomicBoolean.setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) = set(value)
operator fun AtomicInteger.getValue(thisRef: Any?, property: KProperty<*>): Int = get()
operator fun AtomicInteger.setValue(thisRef: Any?, property: KProperty<*>, value: Int) = set(value)
operator fun AtomicLong.getValue(thisRef: Any?, property: KProperty<*>): Long = get()
operator fun AtomicLong.setValue(thisRef: Any?, property: KProperty<*>, value: Long) = set(value)
operator fun <T> AtomicReference<T>.getValue(thisRef: Any?, property: KProperty<*>): T = get()
operator fun <T> AtomicReference<T>.setValue(thisRef: Any?, property: KProperty<*>, value: T) = set(value)

View File

@ -10,10 +10,12 @@ import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.os.Build
import android.os.Environment
import android.util.Base64
import android.view.Gravity
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.Toast
import cn.hutool.core.util.StrUtil
import com.google.zxing.*
import com.google.zxing.common.GlobalHistogramBinarizer
import com.google.zxing.qrcode.QRCodeReader
@ -58,51 +60,25 @@ object ProxyUtil {
}
@JvmStatic
fun reloadProxyList(): Boolean {
cacheFile.parentFile?.mkdirs()
fun downloadLegacyProxyList(): List<String>? {
runCatching {
// 从 GITEE 主站 读取
val list = JSONArray(HttpUtil.get("https://gitee.com/nekoshizuku/AwesomeRepo/raw/master/proxy_list.json")).toString()
if (!cacheFile.isFile || list != cacheFile.readText()) {
cacheFile.writeText(list)
return true
}
JSONArray(HttpUtil.get("https://gitee.com/nekoshizuku/AwesomeRepo/raw/master/proxy_list.json"))
}.recoverCatching {
// 从 GITLAB 读取
val list = JSONArray(HttpUtil.get("https://gitlab.com/nekohasekai/nekox-proxy-list/-/raw/master/proxy_list.json")).toString()
if (!cacheFile.isFile || list != cacheFile.readText()) {
cacheFile.writeText(list)
return true
}
JSONArray(HttpUtil.get("https://gitlab.com/nekohasekai/nekox-proxy-list/-/raw/master/proxy_list.json"))
}.recoverCatching {
// 从 GITHUB PAGES 读取
val list = JSONArray(HttpUtil.get("https://nekox-dev.github.io/ProxyList/proxy_list.json")).toString()
if (!cacheFile.isFile || list != cacheFile.readText()) {
cacheFile.writeText(list)
return true
}
JSONArray(HttpUtil.get("https://nekox-dev.github.io/ProxyList/proxy_list.json"))
}.recoverCatching {
@ -110,19 +86,61 @@ object ProxyUtil {
val master = HttpUtil.getByteArray("https://github.com/NekoX-Dev/ProxyList/archive/master.zip")
val list = JSONArray(String(ZipUtil.read(ByteArrayInputStream(master), "ProxyList-master/proxy_list.json"))).toString()
JSONArray(String(ZipUtil.read(ByteArrayInputStream(master), "ProxyList-master/proxy_list.json")))
if (!cacheFile.isFile || list != cacheFile.readText()) {
}.getOrNull()?.also { arr ->
cacheFile.writeText(list)
return (0 until arr.length()).map {
return true
SharedConfig.parseProxyInfo(arr.getJSONObject(it).getString("proxy")).toUrl()
}
}
return false
return null
}
@JvmStatic
fun parseProxies(_text: String): MutableList<String> {
val text = runCatching {
StrUtil.utf8Str(Base64.decode(_text, Base64.DEFAULT))
}.recover {
_text
}.getOrThrow()
val proxies = mutableListOf<String>()
text.split('\n').map { it.split(" ") }.forEach {
it.forEach { line ->
if (line.startsWith("tg://proxy") ||
line.startsWith("tg://socks") ||
line.startsWith("https://t.me/proxy") ||
line.startsWith("https://t.me/socks") ||
line.startsWith(VMESS_PROTOCOL) ||
line.startsWith(VMESS1_PROTOCOL) ||
line.startsWith(SS_PROTOCOL) ||
line.startsWith(SSR_PROTOCOL)) {
runCatching { proxies.add(SharedConfig.parseProxyInfo(line).toUrl()) }
}
}
}
if (proxies.isEmpty()) error("no proxy link found")
return proxies
}

View File

@ -13,9 +13,6 @@
<string name="DoNotRemindAgain">لا تذكرني مرة أخرى</string>
<string name="RemoveTitleEmoji">إزالة الإيموجي من العناوين</string>
<string name="NekoXProxy">خادم NekoX العام</string>
<string name="PublicPrefix">[عام]</string>
<string name="NekoXProxyInfo">قد يستغرق الأمر عشر ثوان لتحميل وكيل NekoX العام المدمج :)</string>
<string name="HidePublicProxyList">إخفاء قائمة البروكسي العامة</string>
<string name="DisableChatAction">لا تقم بإرسال حالة الإدخال</string>
<string name="FakeScreenshot">لقطة شاشة مخرجة</string>
<string name="SaveCacheToSdcard">حفظ التخزين المؤقت على sdcard*</string>

View File

@ -1,5 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="CustomApi">API سفارشی</string>
<string name="UseCustomApiNotice">با استفاده از API سفارشی وارد شوید ، اگر قادر به ثبت نام یا ورود به سیستم نیستید ، این ممکن است کمک کند. توجه: اگر از نسخه نسخه استفاده می کنید ، fcm کار نخواهد کرد</string>
<string name="CustomApiNo">از API سفارشی استفاده نکنید</string>
<string name="CustomApiOffical">تلگرام اندروید</string>
<string name="CustomApiTGX">تلگرام اندروید X</string>
<string name="CustomApiInput">ورود دستی</string>
<string name="ChangeTranslateProvider">تغییر ارائه دهنده خدمات</string>
<string name="NekoXUpdatesChannel">کانال بروزرسانی NekoX</string>
<string name="ShowIdAndDc">نمایش آیدی کاربر در پروفایل</string>
@ -9,13 +15,11 @@
<string name="PrivacyNoticePhoneVisible">تشخیص داده شده است که شماره تلفن همراه شما برای همه قابل رویت است ، و این ممکن است باعث شود هکرهای تحت کنترل دولت هویت واقعی شما را پیدا کنند ، لطفا آن را خاموش کنید!</string>
<string name="PrivacyNoticeAddByPhone">تشخیص داده شده است که تنظیمات \"یافتن من از طریق شماره تلفن\" شما خاموش نیست، که ممکن است باعث شود یک هکر تحت کنترل دولت بتواند هویت واقعی شما را پیدا کند ، لطفاً آن را خاموش کنید!</string>
<string name="PrivacyNoticeP2p">تشخیص داده شده است که تنظیمات \"مجاز بودن تماس های نظیر به نظیر\" را خاموش نکرده اید ، که ممکن است باعث شود هکرهای تحت کنترل دولت هویت واقعی شما را پیدا کنند ، لطفاً آن را خاموش کنید!</string>
<string name="PrivacyNotice2fa">تشخیص داده می شود که رمز عبوری تنظیم نکرده اید ، و این ممکن است باعث شود هکرهای تحت کنترل دولت هویت واقعی شما را پیدا کنند ، لطفاً آنرا تعیین کنید</string>
<string name="ApplySuggestion">قبول،اعمال کن</string>
<string name="DoNotRemindAgain">دیگر یادآوری نشود</string>
<string name="RemoveTitleEmoji">حذف ایموجی کنار اسم Nekogram x</string>
<string name="NekoXProxy">پروکسی عمومی NekoX</string>
<string name="PublicPrefix">[عمومی]</string>
<string name="NekoXProxyInfo">ممکن است چند ثانیه برای بارگیری پروکسی داخلی NekoX طول بکشد :)</string>
<string name="HidePublicProxyList">مخفی کردن لیست پروکسی عمومی</string>
<string name="DisableChatAction">وضعیت مرا ارسال نکن</string>
<string name="FakeScreenshot">تصویر پیش‌نمایش</string>
<string name="SaveCacheToSdcard">ذخیره کش در حافظه sd card</string>

View File

@ -12,9 +12,7 @@
<string name="DoNotRemindAgain">今後表示しない</string>
<string name="RemoveTitleEmoji">タイトルの絵文字を削除</string>
<string name="NekoXProxy">NekoXパブリックプロキシ</string>
<string name="PublicPrefix">[ パブリック ]</string>
<string name="NekoXProxyInfo">NekoX パブリックビルトインプロキシの読み込みには数十秒かかる場合があります :)</string>
<string name="HidePublicProxyList">パブリックプロキシリストを非表示</string>
<string name="PublicPrefix">パブリック</string>
<string name="DisableChatAction">入力ステータスを送信しない</string>
<string name="SaveCacheToSdcard">キャッシュをSDカードに保存</string>
<string name="FilterNameUsers">ユーザー</string>

View File

@ -13,9 +13,7 @@
<string name="DoNotRemindAgain">Não lembre novamente</string>
<string name="RemoveTitleEmoji">Remover emoji no título</string>
<string name="NekoXProxy">Proxy Público do NekoX</string>
<string name="PublicPrefix">[ Público ]</string>
<string name="NekoXProxyInfo">Pode levar dezenas de segundos até que o proxy público nativo do NekoX seja carregado :)</string>
<string name="HidePublicProxyList">Ocultar lista de proxy público</string>
<string name="PublicPrefix">Público</string>
<string name="DisableChatAction">Não enviar meu status de entrada</string>
<string name="FakeScreenshot">Simular captura de tela</string>
<string name="SaveCacheToSdcard">Salvar cache no cartão SD*</string>

View File

@ -21,9 +21,7 @@
<string name="DoNotRemindAgain">不再提醒</string>
<string name="RemoveTitleEmoji">移除标题中的表情</string>
<string name="NekoXProxy">NekoX 公共代理</string>
<string name="PublicPrefix">[ 公共 ]</string>
<string name="NekoXProxyInfo">这可能需要几十秒的时间来加载 NekoX 公共代理 :)</string>
<string name="HidePublicProxyList">隐藏公共代理列表</string>
<string name="PublicPrefix">公共</string>
<string name="DisableChatAction">不要发送我的输入状态</string>
<string name="FakeScreenshot">假装截屏</string>
<string name="SaveCacheToSdcard">保存缓存到内部存储*</string>
@ -97,6 +95,11 @@
<string name="ImportProxies">从文件导入服务器列表</string>
<string name="ImportProxyList">导入代理服务器列表</string>
<string name="ImportProxyListConfirm">您确定要**导入代理服务器列表**吗?</string>
<string name="ProxySubDetails">订阅详情</string>
<string name="SubscriptionUrls">链接列表</string>
<string name="SubscriptionUpdating">正更新订阅</string>
<string name="SubscriptionDelete">删除订阅</string>
<string name="SubscriptionDeleteNotice">您确定要**删除订阅**吗?</string>
<string name="ExportStickers">导出贴纸集</string>
<string name="ImportStickers">从文件导入贴纸集</string>
<string name="ImportStickersList">导入贴纸集</string>

View File

@ -13,9 +13,7 @@
<string name="DoNotRemindAgain">不再提醒</string>
<string name="RemoveTitleEmoji">移除標題中的表情</string>
<string name="NekoXProxy">NekoX 公共代理</string>
<string name="PublicPrefix">[ 公共 ]</string>
<string name="NekoXProxyInfo">這可能需要幾十秒的時間來加載 NekoX 公共代理 :)</string>
<string name="HidePublicProxyList">隱藏公共代理列表</string>
<string name="PublicPrefix">公共</string>
<string name="DisableChatAction">不要發送我的輸入狀態</string>
<string name="FakeScreenshot">假裝截屏</string>
<string name="SaveCacheToSdcard">保存緩存到存儲卡*</string>

View File

@ -13,9 +13,7 @@
<string name="DoNotRemindAgain">不再提醒</string>
<string name="RemoveTitleEmoji">移除標題中的表情</string>
<string name="NekoXProxy">NekoX 公共代理</string>
<string name="PublicPrefix">[ 公共 ]</string>
<string name="NekoXProxyInfo">這可能需要幾十秒的時間來加載 NekoX 公共代理 :)</string>
<string name="HidePublicProxyList">隱藏公共代理列表</string>
<string name="PublicPrefix">公共</string>
<string name="DisableChatAction">不要發送我的輸入狀態</string>
<string name="FakeScreenshot">假裝截屏</string>
<string name="SaveCacheToSdcard">保存緩存到存儲卡*</string>

View File

@ -3,7 +3,7 @@
<string name="NekoX" translatable="false">Nekogram X</string>
<string name="CustomApi">Custom Api</string>
<string name="CustomApi">Custom API</string>
<string name="UseCustomApiNotice">Log in using the custom api, if you are unable to register or log in, this may help.\n\nNote: fcm will not work if you are using the release version.</string>
<string name="CustomApiNo">Don\'t use custom API</string>
<string name="CustomApiOffical">Telegram Android</string>
@ -15,7 +15,7 @@
<string name="ChangeTranslateProvider">Change Provider</string>
<string name="NekoXUpdatesChannel">NekoX Updates Channel</string>
<string name="ShowIdAndDc">Show id / dc in profile</string>
<string name="ShowIdAndDc">Show ID / DC in profile</string>
<string name="UseDefaultTheme">Use default theme *</string>
<string name="NightMode">Night Mode</string>
@ -31,15 +31,14 @@
<string name="RemoveTitleEmoji">Remove emoji in title</string>
<string name="NekoXProxy">NekoX Public Proxy</string>
<string name="PublicPrefix">[ Public ]</string>
<string name="NekoXProxyInfo">It may take tens of seconds to load the NekoX Public built-in proxy :)</string>
<string name="HidePublicProxyList">Hide public proxy list</string>
<string name="PublicPrefix">Public</string>
<string name="DisableChatAction">Don\'t send my input status</string>
<string name="FakeScreenshot">Pretend screenshot</string>
<string name="SaveCacheToSdcard">Save cache to sdcard*</string>
<string name="Import">Import</string>
<string name="FilterNameUsers">Users</string>
<string name="FilterNameUsersDescription">Only messages from private chats</string>
@ -130,12 +129,16 @@
<string name="ExportProxies">Export servers to file</string>
<string name="ImportProxies">Import servers from file</string>
<string name="ImportProxyList">Import proxy servers</string>
<string name="ImportProxyListConfirm">Are you sure you want to **import proxy servers**?</string>
<string name="ProxySubscription">Proxy Subscription</string>
<string name="ProxySubDetails">Subscription Details</string>
<string name="SubscriptionUrls">Urls</string>
<string name="SubscriptionUpdating">Updating Subscription</string>
<string name="SubscriptionDelete">Delete Subscription</string>
<string name="ExportStickers">Export Stickers</string>
<string name="ImportStickers">Import stickers from file</string>
<string name="ImportStickersList">Import stickers</string>
<string name="ImportStickersConfirm">Are you sure you want to **import stickers**?</string>
<string name="StickerSets">Sticker Sets</string>
<string name="InvalidStickersFile">Invalid stickers file: </string>

View File

@ -6,7 +6,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.3'
classpath 'com.android.tools.build:gradle:4.1.0-alpha06'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72"
classpath 'com.google.gms:google-services:4.3.3'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.0.0-beta04'

View File

@ -1,6 +1,6 @@
#Wed Mar 25 07:24:03 CST 2020
#Sun Apr 19 13:54:05 CST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.4-rc-1-all.zip