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)
[![Version](https://img.shields.io/badge/Version-10.2.0-blue.svg)]()
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![License](https://badge.fba7af7372a48fda78fb377067a7233d.com/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
- 🔒 **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
-**Alto Rendimiento**: DNS over HTTPS con fallback
- **Streaming**: Reproducción de canales en vivo
- **VPN Integrada**: Con Mullvad WireGuard - sin app externa
- **Android TV**: Optimizado para Android TV y control remoto
- **DNS over HTTPS**: Múltiples servidores DNS con fallback automático
## 🚀 Instalación
## Instalación
### 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
```bash
# Clonar repositorio
git clone https://gitea.cbcren.online/renato97/furbo-vpn-edition.git
cd furbo-vpn-edition
git clone <repo-url>
cd <project-dir>
# Compilar
./gradlew assembleDebug
@@ -31,7 +29,7 @@ cd furbo-vpn-edition
# 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ú
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`
3. Recompilar
## 📋 Requisitos
## Requisitos
- **Android**: 7.0 (API 24) o superior
- **VPN**: WireGuard integrado (no requiere app externa)
## 🔐 Privacidad
## Configuración de Actualizaciones
- **DNS**: Múltiples servidores DoH (Google, Cloudflare, AdGuard, Quad9)
- **VPN**: Mullvad - Sin logs, sin datos personales
- **Todo en uno**: No necesita otras apps
Para habilitar las actualizaciones automáticas, configura la URL en `app/build.gradle`:
## 📁 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/
├── java/com/streamplayer/
│ ├── MainActivity.java # UI principal
│ ├── VpnManager.java # Gestión VPN
│ ├── UpdateManager.java # Sistema de actualizaciones
│ └── ...
├── res/raw/
│ └── mullvad.conf # Configuración VPN
└── AndroidManifest.xml
```
## 📞 Soporte
## Disclaimer
- Issues: [GitHub Issues](https://gitea.cbcren.online/renato97/furbo-vpn-edition/issues)
---
⚠️ **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.
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
versionCode 100111
versionName "10.1.11"
buildConfigField "String", "DEVICE_REGISTRY_URL", '"http://194.163.191.200:4000"'
buildConfigField "String", "UPDATE_CHECK_URL", '""'
}
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 UpdateManager updateManager;
private AlertDialog updateDialog;
private AlertDialog blockedDialog;
private DeviceRegistry deviceRegistry;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -122,28 +120,6 @@ public class MainActivity extends AppCompatActivity {
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
@@ -160,15 +136,9 @@ public class MainActivity extends AppCompatActivity {
if (updateDialog != null && updateDialog.isShowing()) {
updateDialog.dismiss();
}
if (blockedDialog != null && blockedDialog.isShowing()) {
blockedDialog.dismiss();
}
if (updateManager != null) {
updateManager.release();
}
if (deviceRegistry != null) {
deviceRegistry.release();
}
}
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() {
VpnManager vpnManager = VpnManager.getInstance();
if (vpnManager.isConnected()) {

View File

@@ -43,9 +43,12 @@ import okhttp3.Response;
public class 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 =
"https://gitea.cbcren.online/api/v1/repos/renato97/app/releases/latest";
private static final String GITEA_TOKEN = "4b94b3610136529861af0821040a801906821a0f";
BuildConfig.UPDATE_CHECK_URL.isEmpty()
? "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 Handler mainHandler;

View File

@@ -1,10 +1,10 @@
{
"versionCode": 90000,
"versionName": "9.0.0",
"minSupportedVersionCode": 80000,
"versionCode": 100111,
"versionName": "10.1.11",
"minSupportedVersionCode": 100000,
"forceUpdate": false,
"downloadUrl": "https://gitea.cbcren.online/renato97/app/releases/download/v9.0/StreamPlayer-v9.0-DefinitiveEdition.apk",
"fileName": "StreamPlayer-v9.0-DefinitiveEdition.apk",
"sizeBytes": 12000000,
"notes": "Texto opcional si necesitas personalizar las notas que verá el usuario"
"downloadUrl": "https://your-gitea-instance.com/your-user/your-repo/releases/download/v10.1.11/StreamPlayer-v10.1.11.apk",
"fileName": "StreamPlayer-v10.1.11.apk",
"sizeBytes": 20000000,
"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,
"versionName": "10.1.10",
"minSupportedVersionCode": 0,
"versionCode": 100111,
"versionName": "10.1.11",
"minSupportedVersionCode": 100000,
"forceUpdate": false,
"downloadUrl": "http://gitea.cbcren.online/renato97/app/releases/download/v10.1.10/StreamPlayer-v10.1.10-debug.apk",
"fileName": "StreamPlayer-v10.1.10-debug.apk",
"sizeBytes": 9113609,
"notes": "Cambiar a HTTP para evitar errores de certificado"
"downloadUrl": "",
"fileName": "",
"sizeBytes": 0,
"notes": "StreamPlayer VPN Edition - Configura tu propia URL de actualizaciones en build.gradle"
}