Remove dashboard/VPS references - make project PR-ready

- Remove DeviceRegistry.java (dashboard integration)
- Remove VPS IP from build.gradle
- Remove personal Gitea token from UpdateManager
- Add configurable UPDATE_CHECK_URL for updates
- Clean README to be generic and PR-ready
- Clean update manifests
- Remove Docker files and .env from repo
This commit is contained in:
Ren
2026-02-26 12:55:28 -03:00
parent 77c5a4b110
commit e6499f6d1a
11 changed files with 46 additions and 407 deletions

4
.env
View File

@@ -1,4 +0,0 @@
GITEA_TOKEN=efeed2af00597883adb04da70bd6a7c2993ae92d
GEMINI_API_KEY=AIzaSyDWOgyAJqscuPU6iSpS6gxupWBm4soNw5o
TELEGRAM_BOT_TOKEN=8593525164:AAGCX9B_RJGN35_F7tSB72rEZhS_4Zpcszs
TELEGRAM_CHAT_ID=692714536

View File

@@ -1,57 +0,0 @@
FROM eclipse-temurin:17-jdk
# Evitar interactividad durante la instalación
ENV DEBIAN_FRONTEND=noninteractive
# Instalar dependencias necesarias para Android SDK
RUN apt-get update && apt-get install -y \
wget \
unzip \
git \
python3 \
python3-pip \
ncurses-bin \
build-essential \
lib32z1 \
lib32ncurses6 \
lib32stdc++6 \
zlib1g-dev \
&& rm -rf /var/lib/apt/lists/*
# Instalar Android SDK
ENV ANDROID_SDK_ROOT=/opt/android-sdk
ENV SDKMANAGER="$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager"
RUN mkdir -p $ANDROID_SDK_ROOT/cmdline-tools && \
wget -q https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip -O tools.zip && \
unzip -q tools.zip && \
mv cmdline-tools $ANDROID_SDK_ROOT/cmdline-tools/latest && \
rm tools.zip
# Aceptar licencias
RUN yes | $SDKMANAGER --licenses
# Instalar componentes necesarios
RUN $SDKMANAGER "platform-tools" "platforms;android-33" "build-tools;33.0.2" "platforms;android-31"
# Instalar Gradle
ENV GRADLE_HOME=/opt/gradle
RUN wget -q https://services.gradle.org/distributions/gradle-8.2-bin.zip -O gradle.zip && \
unzip -q gradle.zip && \
mv gradle-8.2 $GRADLE_HOME && \
rm gradle.zip
ENV PATH=$PATH:$GRADLE_HOME/bin:$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$ANDROID_SDK_ROOT/platform-tools
# Copiar proyecto
COPY . /app
WORKDIR /app
# Dar permisos de ejecución a gradlew
RUN chmod +x ./gradlew
# Construir APK
RUN ./gradlew assembleRelease
# Comando para copiar APK a un volumen montado
CMD ["cp", "/app/app/build/outputs/apk/release/app-release.apk", "/output/StreamPlayer-v10.0.apk"]

View File

@@ -1,24 +0,0 @@
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
curl \
wireguard-tools \
iputils-ping \
dnsutils \
iproute2 \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p /etc/wireguard
RUN echo '[Interface]' > /etc/wireguard/wg0.conf && \
echo 'PrivateKey = Tq9/VQ8qdsphS+0nVEFmWgFvMfvJ2FbWGK/Xt9cX4AA=' >> /etc/wireguard/wg0.conf && \
echo 'Address = 10.8.0.2/32' >> /etc/wireguard/wg0.conf && \
echo 'DNS = 1.1.1.1' >> /etc/wireguard/wg0.conf && \
echo '' >> /etc/wireguard/wg0.conf && \
echo '[Peer]' >> /etc/wireguard/wg0.conf && \
echo 'PublicKey = 03qeK7CSn6wcMzfqilmVt6Tf81VZIPWnSG04euSkyxM=' >> /etc/wireguard/wg0.conf && \
echo 'Endpoint = 149.88.104.2:51820' >> /etc/wireguard/wg0.conf && \
echo 'AllowedIPs = 0.0.0.0/0' >> /etc/wireguard/wg0.conf && \
echo 'PersistentKeepalive = 25' >> /etc/wireguard/wg0.conf
CMD tail -f /dev/null

View File

@@ -1,29 +1,27 @@
# 🏆 Furbo VPN Edition # StreamPlayer VPN Edition
[![Android](https://img.shields.io/badge/Platform-Android-green.svg)](https://android.com) [![Android](https://img.shields.io/badge/Platform-Android-green.svg)](https://android.com)
[![Version](https://img.shields.io/badge/Version-10.2.0-blue.svg)]() [![License](https://badge.fba7af7372a48fda78fb377067a7233d.com/MIT-blue.svg)](LICENSE)
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
**StreamPlayer con VPN integrada** - La única app de streaming con Mullvad VPN built-in. **StreamPlayer con VPN integrada** - App de streaming deportivo con Mullvad VPN built-in.
## Características ## Características
- 📺 **Streaming**: Reproducción de canales en vivo - **Streaming**: Reproducción de canales en vivo
- 🔒 **VPN Integrada**: Con Mullvad WireGuard - sin app externa - **VPN Integrada**: Con Mullvad WireGuard - sin app externa
- 🌎 **Sin Bloqueos Geográficos**: VPN incluida para evitar restricciones - **Android TV**: Optimizado para Android TV y control remoto
- 📱 **Android TV**: Optimizado para Android TV y control remoto - **DNS over HTTPS**: Múltiples servidores DNS con fallback automático
-**Alto Rendimiento**: DNS over HTTPS con fallback
## 🚀 Instalación ## Instalación
### Opción 1: Descargar APK ### Opción 1: Descargar APK
Descargar el APK más reciente de [Releases](https://gitea.cbcren.online/renato97/furbo-vpn-edition/releases) Descargar el APK desde la sección de Releases de este repositorio.
### Opción 2: Compilar ### Opción 2: Compilar
```bash ```bash
# Clonar repositorio # Clonar repositorio
git clone https://gitea.cbcren.online/renato97/furbo-vpn-edition.git git clone <repo-url>
cd furbo-vpn-edition cd <project-dir>
# Compilar # Compilar
./gradlew assembleDebug ./gradlew assembleDebug
@@ -31,7 +29,7 @@ cd furbo-vpn-edition
# APK en: app/build/outputs/apk/debug/app-debug.apk # APK en: app/build/outputs/apk/debug/app-debug.apk
``` ```
## 🔧 Uso de la VPN ## Uso de la VPN
1. **Primera vez**: Tocar el botón "Conectar VPN" en el menú 1. **Primera vez**: Tocar el botón "Conectar VPN" en el menú
2. **Permiso**: Aceptar el permiso de VPN cuando se solicite 2. **Permiso**: Aceptar el permiso de VPN cuando se solicite
@@ -43,34 +41,35 @@ Para cambiar el servidor Mullvad:
2. Reemplazar el archivo `app/src/main/res/raw/mullvad.conf` 2. Reemplazar el archivo `app/src/main/res/raw/mullvad.conf`
3. Recompilar 3. Recompilar
## 📋 Requisitos ## Requisitos
- **Android**: 7.0 (API 24) o superior - **Android**: 7.0 (API 24) o superior
- **VPN**: WireGuard integrado (no requiere app externa) - **VPN**: WireGuard integrado (no requiere app externa)
## 🔐 Privacidad ## Configuración de Actualizaciones
- **DNS**: Múltiples servidores DoH (Google, Cloudflare, AdGuard, Quad9) Para habilitar las actualizaciones automáticas, configura la URL en `app/build.gradle`:
- **VPN**: Mullvad - Sin logs, sin datos personales
- **Todo en uno**: No necesita otras apps
## 📁 Estructura ```gradle
buildConfigField "String", "UPDATE_CHECK_URL", '"https://your-gitea-instance.com/api/v1/repos/user/repo/releases/latest"'
```
Opcionalmente, configura un token de API en `UpdateManager.java` si tu repositorio es privado.
## Estructura
``` ```
app/src/main/ app/src/main/
├── java/com/streamplayer/ ├── java/com/streamplayer/
│ ├── MainActivity.java # UI principal │ ├── MainActivity.java # UI principal
│ ├── VpnManager.java # Gestión VPN │ ├── VpnManager.java # Gestión VPN
│ ├── UpdateManager.java # Sistema de actualizaciones
│ └── ... │ └── ...
├── res/raw/ ├── res/raw/
│ └── mullvad.conf # Configuración VPN │ └── mullvad.conf # Configuración VPN
└── AndroidManifest.xml └── AndroidManifest.xml
``` ```
## 📞 Soporte ## Disclaimer
- Issues: [GitHub Issues](https://gitea.cbcren.online/renato97/furbo-vpn-edition/issues) Esta aplicación es para fines educativos. El usuario es responsable de cumplir con los términos de servicio de las plataformas de streaming.
---
⚠️ **Disclaimer**: Esta aplicación es para fines educativos. El usuario es responsable de cumplir con los términos de servicio de las plataformas de streaming.

View File

@@ -10,7 +10,7 @@ android {
targetSdk 35 targetSdk 35
versionCode 100111 versionCode 100111
versionName "10.1.11" versionName "10.1.11"
buildConfigField "String", "DEVICE_REGISTRY_URL", '"http://194.163.191.200:4000"' buildConfigField "String", "UPDATE_CHECK_URL", '""'
} }
buildTypes { buildTypes {

View File

@@ -1,31 +0,0 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
buildToolsVersion "28.0.3"
defaultConfig {
applicationId "com.streamplayer"
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'com.google.android.exoplayer:exoplayer:2.8.4'
implementation 'androidx.appcompat:appcompat:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.2'
}

View File

@@ -1,169 +0,0 @@
package com.streamplayer;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
/**
* Informa al dashboard qué dispositivos tienen instalada la app y permite bloquearlos remotamente.
*/
public class DeviceRegistry {
public interface Callback {
void onAllowed();
void onBlocked(String reason, String tokenPart);
void onError(String message);
}
private static final String TAG = "DeviceRegistry";
private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
private final Context appContext;
private final OkHttpClient httpClient;
private final ExecutorService executorService;
private final Handler mainHandler = new Handler(Looper.getMainLooper());
public DeviceRegistry(Context context) {
this.appContext = context.getApplicationContext();
// Usar NetworkUtils para obtener cliente con DNS over HTTPS configurado
this.httpClient = NetworkUtils.getClient();
this.executorService = Executors.newSingleThreadExecutor();
}
public void syncDevice(Callback callback) {
if (TextUtils.isEmpty(BuildConfig.DEVICE_REGISTRY_URL)) {
postAllowed(callback);
return;
}
executorService.execute(() -> {
try {
JSONObject payload = new JSONObject();
payload.put("deviceId", getDeviceId());
payload.put("deviceName", Build.MODEL);
payload.put("model", Build.MODEL);
payload.put("manufacturer", capitalize(Build.MANUFACTURER));
payload.put("osVersion", Build.VERSION.RELEASE + " (API " + Build.VERSION.SDK_INT + ")");
payload.put("appVersionName", BuildConfig.VERSION_NAME);
payload.put("appVersionCode", BuildConfig.VERSION_CODE);
String endpoint = sanitizeBaseUrl(BuildConfig.DEVICE_REGISTRY_URL) + "/api/devices/register";
RequestBody body = RequestBody.create(payload.toString(), JSON);
Request request = new Request.Builder()
.url(endpoint)
.post(body)
.build();
try (Response response = httpClient.newCall(request).execute()) {
if (!response.isSuccessful() || response.body() == null) {
throw new IOException("HTTP " + response.code());
}
String responseText = response.body().string();
// Validar que no sea HTML antes de parsear
if (responseText != null) {
String trimmed = responseText.trim();
if (trimmed.startsWith("<!") || trimmed.startsWith("<html")) {
throw new IOException("El servidor devolvió HTML en lugar de JSON");
}
}
JSONObject json = new JSONObject(responseText);
JSONObject deviceJson = json.optJSONObject("device");
JSONObject verificationJson = json.optJSONObject("verification");
boolean blocked = json.optBoolean("blocked", false);
String reason = json.optString("message");
if (TextUtils.isEmpty(reason) && deviceJson != null) {
reason = deviceJson.optString("notes", "");
}
String tokenPart = "";
if (verificationJson != null) {
boolean verificationRequired = verificationJson.optBoolean("required", false);
blocked = blocked || verificationRequired;
tokenPart = verificationJson.optString("clientTokenPart", "");
}
if (blocked) {
postBlocked(callback, reason, tokenPart);
} else {
postAllowed(callback);
}
}
} catch (IOException | JSONException e) {
Log.w(TAG, "Device sync error", e);
postError(callback, e.getMessage());
}
});
}
private String sanitizeBaseUrl(String base) {
if (TextUtils.isEmpty(base)) {
return "";
}
if (base.endsWith("/")) {
return base.substring(0, base.length() - 1);
}
return base;
}
private String getDeviceId() {
String id = Settings.Secure.getString(appContext.getContentResolver(),
Settings.Secure.ANDROID_ID);
if (TextUtils.isEmpty(id)) {
id = Build.MODEL + "-" + Build.BOARD + "-" + BuildConfig.VERSION_CODE;
}
return id;
}
private String capitalize(String value) {
if (TextUtils.isEmpty(value)) {
return "";
}
return value.substring(0, 1).toUpperCase(Locale.getDefault())
+ value.substring(1);
}
public void release() {
executorService.shutdownNow();
}
private void postAllowed(Callback callback) {
if (callback == null) {
return;
}
mainHandler.post(callback::onAllowed);
}
private void postBlocked(Callback callback, String reason, String tokenPart) {
if (callback == null) {
return;
}
String reasonText = reason == null ? "" : reason;
String token = tokenPart == null ? "" : tokenPart;
mainHandler.post(() -> callback.onBlocked(reasonText, token));
}
private void postError(Callback callback, String message) {
if (callback == null) {
return;
}
mainHandler.post(() -> callback.onError(message));
}
}

View File

@@ -52,8 +52,6 @@ public class MainActivity extends AppCompatActivity {
private SectionEntry currentSection; private SectionEntry currentSection;
private UpdateManager updateManager; private UpdateManager updateManager;
private AlertDialog updateDialog; private AlertDialog updateDialog;
private AlertDialog blockedDialog;
private DeviceRegistry deviceRegistry;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@@ -122,28 +120,6 @@ public class MainActivity extends AppCompatActivity {
Toast.LENGTH_SHORT).show(); Toast.LENGTH_SHORT).show();
} }
}); });
deviceRegistry = new DeviceRegistry(this);
deviceRegistry.syncDevice(new DeviceRegistry.Callback() {
@Override
public void onAllowed() {
// Device authorized, continue normally.
}
@Override
public void onBlocked(String reason, String tokenPart) {
showBlockedDialog(reason, tokenPart);
}
@Override
public void onError(String message) {
if (!TextUtils.isEmpty(message)) {
Toast.makeText(MainActivity.this,
getString(R.string.device_registry_error, message),
Toast.LENGTH_SHORT).show();
}
}
});
} }
@Override @Override
@@ -160,15 +136,9 @@ public class MainActivity extends AppCompatActivity {
if (updateDialog != null && updateDialog.isShowing()) { if (updateDialog != null && updateDialog.isShowing()) {
updateDialog.dismiss(); updateDialog.dismiss();
} }
if (blockedDialog != null && blockedDialog.isShowing()) {
blockedDialog.dismiss();
}
if (updateManager != null) { if (updateManager != null) {
updateManager.release(); updateManager.release();
} }
if (deviceRegistry != null) {
deviceRegistry.release();
}
} }
private void selectSection(int index) { private void selectSection(int index) {
@@ -348,54 +318,6 @@ public class MainActivity extends AppCompatActivity {
} }
} }
private void showBlockedDialog(String reason, String tokenPart) {
if (isFinishing()) {
return;
}
String finalReason = TextUtils.isEmpty(reason)
? getString(R.string.device_blocked_default_reason)
: reason;
if (blockedDialog != null && blockedDialog.isShowing()) {
blockedDialog.dismiss();
}
View dialogView = getLayoutInflater().inflate(R.layout.dialog_blocked, null);
TextView messageText = dialogView.findViewById(R.id.blocked_message_text);
View tokenContainer = dialogView.findViewById(R.id.blocked_token_container);
TextView tokenValue = dialogView.findViewById(R.id.blocked_token_value);
messageText.setText(getString(R.string.device_blocked_message, finalReason));
boolean hasToken = !TextUtils.isEmpty(tokenPart);
if (hasToken) {
tokenContainer.setVisibility(View.VISIBLE);
tokenValue.setText(tokenPart);
tokenValue.setOnClickListener(v -> copyTokenToClipboard(tokenPart));
} else {
tokenContainer.setVisibility(View.GONE);
}
AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.ThemeOverlay_StreamPlayer_AlertDialog)
.setTitle(R.string.device_blocked_title)
.setView(dialogView)
.setCancelable(false)
.setPositiveButton(R.string.device_blocked_close,
(dialog, which) -> finish());
if (hasToken) {
builder.setNeutralButton(R.string.device_blocked_copy_token,
(dialog, which) -> copyTokenToClipboard(tokenPart));
}
blockedDialog = builder.create();
blockedDialog.show();
}
private void copyTokenToClipboard(String tokenPart) {
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
if (clipboard == null) {
Toast.makeText(this, R.string.device_blocked_copy_error, Toast.LENGTH_SHORT).show();
return;
}
ClipData data = ClipData.newPlainText("token", tokenPart);
clipboard.setPrimaryClip(data);
Toast.makeText(this, R.string.device_blocked_copy_success, Toast.LENGTH_SHORT).show();
}
private void toggleVpn() { private void toggleVpn() {
VpnManager vpnManager = VpnManager.getInstance(); VpnManager vpnManager = VpnManager.getInstance();
if (vpnManager.isConnected()) { if (vpnManager.isConnected()) {

View File

@@ -43,9 +43,12 @@ import okhttp3.Response;
public class UpdateManager { public class UpdateManager {
private static final String TAG = "UpdateManager"; private static final String TAG = "UpdateManager";
// NOTE: Configure tu propia URL de releases en build.gradle o aquí
private static final String LATEST_RELEASE_URL = private static final String LATEST_RELEASE_URL =
"https://gitea.cbcren.online/api/v1/repos/renato97/app/releases/latest"; BuildConfig.UPDATE_CHECK_URL.isEmpty()
private static final String GITEA_TOKEN = "4b94b3610136529861af0821040a801906821a0f"; ? "https://your-gitea-instance.com/api/v1/repos/your-user/your-repo/releases/latest"
: BuildConfig.UPDATE_CHECK_URL;
private static final String GITEA_TOKEN = ""; // Configura tu token si es necesario
private final Context appContext; private final Context appContext;
private final Handler mainHandler; private final Handler mainHandler;

View File

@@ -1,10 +1,10 @@
{ {
"versionCode": 90000, "versionCode": 100111,
"versionName": "9.0.0", "versionName": "10.1.11",
"minSupportedVersionCode": 80000, "minSupportedVersionCode": 100000,
"forceUpdate": false, "forceUpdate": false,
"downloadUrl": "https://gitea.cbcren.online/renato97/app/releases/download/v9.0/StreamPlayer-v9.0-DefinitiveEdition.apk", "downloadUrl": "https://your-gitea-instance.com/your-user/your-repo/releases/download/v10.1.11/StreamPlayer-v10.1.11.apk",
"fileName": "StreamPlayer-v9.0-DefinitiveEdition.apk", "fileName": "StreamPlayer-v10.1.11.apk",
"sizeBytes": 12000000, "sizeBytes": 20000000,
"notes": "Texto opcional si necesitas personalizar las notas que verá el usuario" "notes": "StreamPlayer VPN Edition\n\nConfigura UPDATE_CHECK_URL en app/build.gradle para habilitar actualizaciones automáticas."
} }

View File

@@ -1,10 +1,10 @@
{ {
"versionCode": 100110, "versionCode": 100111,
"versionName": "10.1.10", "versionName": "10.1.11",
"minSupportedVersionCode": 0, "minSupportedVersionCode": 100000,
"forceUpdate": false, "forceUpdate": false,
"downloadUrl": "http://gitea.cbcren.online/renato97/app/releases/download/v10.1.10/StreamPlayer-v10.1.10-debug.apk", "downloadUrl": "",
"fileName": "StreamPlayer-v10.1.10-debug.apk", "fileName": "",
"sizeBytes": 9113609, "sizeBytes": 0,
"notes": "Cambiar a HTTP para evitar errores de certificado" "notes": "StreamPlayer VPN Edition - Configura tu propia URL de actualizaciones en build.gradle"
} }