Switch update checker to GitHub Releases
- UpdateHelper now fetches latest release from GitHub API instead of helper bot - Compares dotted version names, prefers arm64 APK asset, falls back to release page - Keeps TL_help_appUpdate output contract so existing update UI works unchanged - Decouples ConfigHelper from update check
This commit is contained in:
parent
42a780f702
commit
12792f77f3
1 changed files with 300 additions and 159 deletions
|
|
@ -1,27 +1,42 @@
|
|||
package tw.nekomimi.nekogram.helpers.remote;
|
||||
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import org.telegram.messenger.AndroidUtilities;
|
||||
import org.telegram.messenger.ApplicationLoader;
|
||||
import org.telegram.messenger.BuildConfig;
|
||||
import org.telegram.messenger.FileLog;
|
||||
import org.telegram.messenger.LocaleController;
|
||||
import org.telegram.messenger.R;
|
||||
import org.telegram.messenger.Utilities;
|
||||
import org.telegram.tgnet.TLRPC;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class UpdateHelper extends BaseRemoteHelper {
|
||||
public static final String UPDATE_METHOD = "check_for_updates";
|
||||
|
||||
// GitHub repository that hosts FoxiGram releases.
|
||||
private static final String GITHUB_REPO = "instant992/FoxiGram";
|
||||
private static final String RELEASES_API = "https://api.github.com/repos/" + GITHUB_REPO + "/releases/latest";
|
||||
|
||||
private static final Pattern VERSION_PART = Pattern.compile("\\d+");
|
||||
|
||||
private volatile boolean checking;
|
||||
|
||||
/**
|
||||
* @param date {long} - date in milliseconds
|
||||
*/
|
||||
|
|
@ -80,7 +95,9 @@ public class UpdateHelper extends BaseRemoteHelper {
|
|||
|
||||
@Override
|
||||
protected void onError(String text, Delegate delegate) {
|
||||
delegate.onTLResponse(null, text);
|
||||
if (delegate != null) {
|
||||
delegate.onTLResponse(null, text);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -90,70 +107,194 @@ public class UpdateHelper extends BaseRemoteHelper {
|
|||
|
||||
@Override
|
||||
protected String getRequestParams() {
|
||||
return " " + TextUtils.join(",", Build.SUPPORTED_ABIS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLoadSuccess(ArrayList<TLRPC.BotInlineResult> results, Delegate delegate) {
|
||||
var map = results.stream()
|
||||
.collect(Collectors.toMap(result -> result.id, result -> result));
|
||||
var update_info = map.get("update_info");
|
||||
if (update_info == null) {
|
||||
delegate.onTLResponse(null, null);
|
||||
return;
|
||||
}
|
||||
var update = new TLRPC.TL_help_appUpdate();
|
||||
var json = GSON.fromJson(getTextFromInlineResult(update_info), Update.class);
|
||||
if (json == null || json.versionCode <= BuildConfig.VERSION_CODE) {
|
||||
delegate.onTLResponse(null, null);
|
||||
return;
|
||||
}
|
||||
update.version = json.version;
|
||||
update.can_not_skip = json.canNotSkip;
|
||||
if (json.url != null) {
|
||||
update.url = json.url;
|
||||
update.flags |= 4;
|
||||
}
|
||||
var document = map.get("document");
|
||||
if (document != null && document.document != null) {
|
||||
update.document = document.document;
|
||||
update.flags |= 2;
|
||||
}
|
||||
var message = map.get("message");
|
||||
if (message != null && message.send_message != null) {
|
||||
update.text = message.send_message.message;
|
||||
update.entities = message.send_message.entities;
|
||||
var entities = map.get("entities");
|
||||
if (entities != null) {
|
||||
var entities_json = GSON.fromJson(getTextFromInlineResult(entities), MessageEntity[].class);
|
||||
update.entities.addAll(parseBotAPIEntities(entities_json, true));
|
||||
}
|
||||
}
|
||||
var sticker = map.get("sticker");
|
||||
if (sticker != null && sticker.document != null) {
|
||||
update.sticker = sticker.document;
|
||||
update.flags |= 8;
|
||||
}
|
||||
delegate.onTLResponse(update, null);
|
||||
return "";
|
||||
}
|
||||
|
||||
public void checkNewVersionAvailable(Delegate delegate) {
|
||||
load(delegate);
|
||||
ConfigHelper.getInstance().load();
|
||||
if (checking) {
|
||||
return;
|
||||
}
|
||||
checking = true;
|
||||
Utilities.globalQueue.postRunnable(() -> fetchLatestRelease(delegate));
|
||||
}
|
||||
|
||||
public static class Update {
|
||||
@SerializedName("can_not_skip")
|
||||
@Expose
|
||||
public Boolean canNotSkip;
|
||||
@SerializedName("version")
|
||||
@Expose
|
||||
public String version;
|
||||
@SerializedName("version_code")
|
||||
@Expose
|
||||
public Integer versionCode;
|
||||
@SerializedName("url")
|
||||
@Expose
|
||||
public String url;
|
||||
private void fetchLatestRelease(Delegate delegate) {
|
||||
HttpURLConnection connection = null;
|
||||
try {
|
||||
URL url = new URL(RELEASES_API);
|
||||
connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("GET");
|
||||
connection.setRequestProperty("Accept", "application/vnd.github+json");
|
||||
connection.setRequestProperty("User-Agent", ApplicationLoader.getApplicationId());
|
||||
connection.setConnectTimeout(15000);
|
||||
connection.setReadTimeout(15000);
|
||||
|
||||
int code = connection.getResponseCode();
|
||||
if (code != HttpURLConnection.HTTP_OK) {
|
||||
deliverError(delegate, "HTTP " + code);
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line);
|
||||
}
|
||||
}
|
||||
|
||||
GithubRelease release = new Gson().fromJson(sb.toString(), GithubRelease.class);
|
||||
TLRPC.TL_help_appUpdate update = buildUpdate(release);
|
||||
deliverSuccess(delegate, update);
|
||||
} catch (Exception e) {
|
||||
FileLog.e(e);
|
||||
deliverError(delegate, e.getLocalizedMessage());
|
||||
} finally {
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
}
|
||||
checking = false;
|
||||
}
|
||||
}
|
||||
|
||||
private TLRPC.TL_help_appUpdate buildUpdate(GithubRelease release) {
|
||||
if (release == null || release.draft || release.prerelease || TextUtils.isEmpty(release.tagName)) {
|
||||
return null;
|
||||
}
|
||||
if (!isNewerVersion(release.tagName, BuildConfig.VERSION_NAME)) {
|
||||
return null;
|
||||
}
|
||||
String apkUrl = selectApkAsset(release.assets);
|
||||
if (apkUrl == null) {
|
||||
// No installable asset attached; fall back to the release page.
|
||||
apkUrl = release.htmlUrl;
|
||||
}
|
||||
if (TextUtils.isEmpty(apkUrl)) {
|
||||
return null;
|
||||
}
|
||||
TLRPC.TL_help_appUpdate update = new TLRPC.TL_help_appUpdate();
|
||||
update.version = stripVersionPrefix(release.tagName);
|
||||
update.can_not_skip = false;
|
||||
update.url = apkUrl;
|
||||
update.flags |= 4;
|
||||
String body = TextUtils.isEmpty(release.body) ? "" : release.body.trim();
|
||||
String title = TextUtils.isEmpty(release.name) ? update.version : release.name.trim();
|
||||
update.text = TextUtils.isEmpty(body) ? title : title + "\n\n" + body;
|
||||
update.entities = new ArrayList<>();
|
||||
return update;
|
||||
}
|
||||
|
||||
private static String selectApkAsset(List<GithubAsset> assets) {
|
||||
if (assets == null || assets.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String anyApk = null;
|
||||
for (GithubAsset asset : assets) {
|
||||
if (asset == null || TextUtils.isEmpty(asset.name) || TextUtils.isEmpty(asset.downloadUrl)) {
|
||||
continue;
|
||||
}
|
||||
String name = asset.name.toLowerCase();
|
||||
if (!name.endsWith(".apk")) {
|
||||
continue;
|
||||
}
|
||||
// The build only targets arm64-v8a, prefer a matching asset.
|
||||
if (name.contains("arm64")) {
|
||||
return asset.downloadUrl;
|
||||
}
|
||||
if (anyApk == null) {
|
||||
anyApk = asset.downloadUrl;
|
||||
}
|
||||
}
|
||||
return anyApk;
|
||||
}
|
||||
|
||||
private static String stripVersionPrefix(String tag) {
|
||||
if (tag == null) {
|
||||
return "";
|
||||
}
|
||||
String t = tag.trim();
|
||||
if (t.startsWith("v") || t.startsWith("V")) {
|
||||
t = t.substring(1);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
// Compares dotted numeric versions (e.g. "12.7.4" > "12.7.3"). Non-numeric
|
||||
// suffixes are ignored. Returns true when remote is strictly newer.
|
||||
private static boolean isNewerVersion(String remoteTag, String currentName) {
|
||||
int[] remote = parseVersion(stripVersionPrefix(remoteTag));
|
||||
int[] current = parseVersion(currentName);
|
||||
int len = Math.max(remote.length, current.length);
|
||||
for (int i = 0; i < len; i++) {
|
||||
int r = i < remote.length ? remote[i] : 0;
|
||||
int c = i < current.length ? current[i] : 0;
|
||||
if (r != c) {
|
||||
return r > c;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int[] parseVersion(String version) {
|
||||
if (TextUtils.isEmpty(version)) {
|
||||
return new int[0];
|
||||
}
|
||||
String[] rawParts = version.split("\\.");
|
||||
ArrayList<Integer> parts = new ArrayList<>();
|
||||
for (String part : rawParts) {
|
||||
var matcher = VERSION_PART.matcher(part);
|
||||
if (matcher.find()) {
|
||||
try {
|
||||
parts.add(Integer.parseInt(matcher.group()));
|
||||
} catch (NumberFormatException ignored) {
|
||||
parts.add(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
int[] result = new int[parts.size()];
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = parts.get(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void deliverSuccess(Delegate delegate, TLRPC.TL_help_appUpdate update) {
|
||||
if (delegate == null) {
|
||||
return;
|
||||
}
|
||||
AndroidUtilities.runOnUIThread(() -> delegate.onTLResponse(update, null));
|
||||
}
|
||||
|
||||
private void deliverError(Delegate delegate, String error) {
|
||||
if (delegate == null) {
|
||||
return;
|
||||
}
|
||||
AndroidUtilities.runOnUIThread(() -> delegate.onTLResponse(null, error));
|
||||
}
|
||||
|
||||
private static class GithubRelease {
|
||||
@SerializedName("tag_name")
|
||||
String tagName;
|
||||
@SerializedName("name")
|
||||
String name;
|
||||
@SerializedName("body")
|
||||
String body;
|
||||
@SerializedName("html_url")
|
||||
String htmlUrl;
|
||||
@SerializedName("draft")
|
||||
boolean draft;
|
||||
@SerializedName("prerelease")
|
||||
boolean prerelease;
|
||||
@SerializedName("assets")
|
||||
List<GithubAsset> assets;
|
||||
}
|
||||
|
||||
private static class GithubAsset {
|
||||
@SerializedName("name")
|
||||
String name;
|
||||
@SerializedName("browser_download_url")
|
||||
String downloadUrl;
|
||||
@SerializedName("size")
|
||||
long size;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue