Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 77c417117a | |||
|
|
2c65578bdd | ||
|
|
6aef195f30 | ||
|
|
cc4696dec2 | ||
|
|
73a4f81341 | ||
|
|
adadb89232 |
3
.env
3
.env
@@ -1,3 +1,4 @@
|
|||||||
GITEA_TOKEN=7921aa22187b39125d29399d26f527ba26a2fb5b
|
GITEA_TOKEN=7921aa22187b39125d29399d26f527ba26a2fb5b
|
||||||
|
|
||||||
GEMINI_API_KEY=AIzaSyDWOgyAJqscuPU6iSpS6gxupWBm4soNw5o
|
GEMINI_API_KEY=AIzaSyDWOgyAJqscuPU6iSpS6gxupWBm4soNw5o
|
||||||
|
TELEGRAM_BOT_TOKEN=8593525164:AAGCX9B_RJGN35_F7tSB72rEZhS_4Zpcszs
|
||||||
|
TELEGRAM_CHAT_ID=692714536
|
||||||
|
|||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -129,3 +129,8 @@ lint/tmp/
|
|||||||
app/release/
|
app/release/
|
||||||
app/debug/
|
app/debug/
|
||||||
*.apk
|
*.apk
|
||||||
|
|
||||||
|
# Dashboard local files
|
||||||
|
dashboard/node_modules/
|
||||||
|
dashboard/server.log
|
||||||
|
dashboard/config.json
|
||||||
|
|||||||
45
README.md
45
README.md
@@ -113,13 +113,6 @@ StreamPlayer ahora consulta automáticamente las releases públicas del reposito
|
|||||||
|
|
||||||
Si por algún motivo olvidas subir el manifiesto, la app igualmente tomará el primer asset `.apk` de la release, pero no podrá forzar versiones mínimas.
|
Si por algún motivo olvidas subir el manifiesto, la app igualmente tomará el primer asset `.apk` de la release, pero no podrá forzar versiones mínimas.
|
||||||
|
|
||||||
### Flujo dentro de la app
|
|
||||||
|
|
||||||
- Cada vez que se abre `MainActivity` se consulta `https://gitea.cbcren.online/api/v1/repos/renato97/app/releases/latest`.
|
|
||||||
- Si `versionCode` del servidor es mayor al instalado se muestra un diálogo para actualizar; el usuario puede abrir la release o descargarla directamente.
|
|
||||||
- Si `minSupportedVersionCode` es mayor al instalado la app bloqueará el uso hasta actualizar, cumpliendo con el requerimiento de controlar instalaciones.
|
|
||||||
- La descarga se gestiona con `DownloadManager` y, una vez completada, se lanza el instalador usando FileProvider.
|
|
||||||
|
|
||||||
### Dashboard de Dispositivos y Bloqueo Remoto
|
### Dashboard de Dispositivos y Bloqueo Remoto
|
||||||
|
|
||||||
Para saber en qué equipo está instalada la app y bloquear el acceso cuando lo necesites, se incluye un dashboard liviano en `dashboard/`:
|
Para saber en qué equipo está instalada la app y bloquear el acceso cuando lo necesites, se incluye un dashboard liviano en `dashboard/`:
|
||||||
@@ -132,19 +125,49 @@ npm install
|
|||||||
npm start # escucha en http://localhost:4000
|
npm start # escucha en http://localhost:4000
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Ajusta `DEVICE_REGISTRY_URL` en `app/build.gradle` para apuntar al dominio/puerto donde despliegues el servidor (por defecto `http://localhost:4000`).
|
2. Copia `dashboard/config.example.json` a `dashboard/config.json` y completa `telegramBotToken` + `telegramChatId` (o usa variables de entorno `TELEGRAM_BOT_TOKEN` / `TELEGRAM_CHAT_ID`).
|
||||||
3. Distribuye el APK; cada vez que se abra la app enviará un registro a `POST /api/devices/register` con su `ANDROID_ID`, modelo y versión.
|
3. Ajusta `DEVICE_REGISTRY_URL` en `app/build.gradle` para apuntar al dominio/puerto donde despliegues el servidor (ya configurado como `http://194.163.191.200:4000`).
|
||||||
4. Entra a `http://TU_HOST:4000/` para ver el listado, asignar alias o bloquear/desbloquear dispositivos.
|
4. Distribuye el APK; cada instalación reportará `ANDROID_ID`, modelo, IP pública y país.
|
||||||
|
5. Entra a `http://TU_HOST:4000/` para ver el listado, asignar alias, bloquear/desbloquear o validar tokens.
|
||||||
|
|
||||||
El servidor guarda los datos en `dashboard/data/devices.json`, por lo que puedes versionarlo o respaldarlo fácilmente. Cada registro almacena:
|
El servidor guarda los datos en `dashboard/data/devices.json`, por lo que puedes versionarlo o respaldarlo fácilmente. Cada registro almacena:
|
||||||
|
|
||||||
- `deviceId`: `Settings.Secure.ANDROID_ID` del equipo
|
- `deviceId`: `Settings.Secure.ANDROID_ID` del equipo
|
||||||
- `deviceName`, `manufacturer`, `model`, `osVersion`
|
- `deviceName`, `manufacturer`, `model`, `osVersion`
|
||||||
- `appVersionName`/`Code`
|
- `appVersionName`/`Code`
|
||||||
- `firstSeen`, `lastSeen`, `blocked`, `notes`
|
- `ip`, `country` detectados automáticamente
|
||||||
|
- `firstSeen`, `lastSeen`, `blocked`, `notes`, `verification.status`
|
||||||
|
|
||||||
Cuando presionas “Bloquear”, la app recibe la respuesta `{"blocked": true}` y muestra un diálogo irreversible hasta que lo habilites. Esto añade una capa adicional de control aparte del sistema de actualizaciones.
|
Cuando presionas “Bloquear”, la app recibe la respuesta `{"blocked": true}` y muestra un diálogo irreversible hasta que lo habilites. Esto añade una capa adicional de control aparte del sistema de actualizaciones.
|
||||||
|
|
||||||
|
### Flujo dentro de la app y tokens divididos
|
||||||
|
|
||||||
|
- Cada vez que se abre `MainActivity` se consulta `https://gitea.cbcren.online/api/v1/repos/renato97/app/releases/latest`.
|
||||||
|
- Si `versionCode` del servidor es mayor al instalado se muestra un diálogo para actualizar; el usuario puede abrir la release o descargarla directamente.
|
||||||
|
- Si `minSupportedVersionCode` es mayor al instalado la app bloqueará el uso hasta actualizar, cumpliendo con el requerimiento de controlar instalaciones.
|
||||||
|
- La descarga se gestiona con `DownloadManager` y, una vez completada, se lanza el instalador usando FileProvider.
|
||||||
|
- Mientras el dashboard mantenga un dispositivo "Pendiente" o "Bloqueado", la app muestra un diálogo con el motivo y la mitad del token que debe compartir la persona.
|
||||||
|
|
||||||
|
Cada instalación genera un token interno dividido en dos:
|
||||||
|
|
||||||
|
1. **Parte cliente**: se muestra en el diálogo del dispositivo bloqueado para que el usuario pueda copiarla.
|
||||||
|
2. **Parte admin**: llega al bot de Telegram configurado junto con la IP, país y datos del dispositivo.
|
||||||
|
|
||||||
|
Para autorizar un dispositivo pendiente:
|
||||||
|
|
||||||
|
1. Obtén la parte cliente desde el usuario (visible en pantalla).
|
||||||
|
2. Copia la parte admin del mensaje de Telegram.
|
||||||
|
3. En el dashboard presiona “Verificar token” e introduce ambas mitades. Si coinciden, el estado pasa a "Verificado" y la app se desbloquea automáticamente.
|
||||||
|
4. A partir de allí puedes bloquear/desbloquear manualmente cuando quieras.
|
||||||
|
|
||||||
|
También puedes gestionar todo desde Telegram:
|
||||||
|
|
||||||
|
- `/allow <deviceId> <token_cliente>` autoriza el dispositivo (verifica el token y lo desbloquea).
|
||||||
|
- `/deny <deviceId> <token_cliente> [motivo]` lo bloquea con un motivo opcional.
|
||||||
|
- `/pending` lista los registros que aún esperan un token válido.
|
||||||
|
|
||||||
|
Cada nuevo registro dispara una notificación de Telegram con la parte admin del token y recordatorios de esos comandos.
|
||||||
|
|
||||||
## 📱 Estructura del Proyecto
|
## 📱 Estructura del Proyecto
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ android {
|
|||||||
applicationId "com.streamplayer"
|
applicationId "com.streamplayer"
|
||||||
minSdk 21
|
minSdk 21
|
||||||
targetSdk 33
|
targetSdk 33
|
||||||
versionCode 92000
|
versionCode 94300
|
||||||
versionName "9.2.0"
|
versionName "9.4.3"
|
||||||
buildConfigField "String", "DEVICE_REGISTRY_URL", '"http://194.163.191.200:4000"'
|
buildConfigField "String", "DEVICE_REGISTRY_URL", '"http://194.163.191.200:4000"'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ public class DeviceRegistry {
|
|||||||
public interface Callback {
|
public interface Callback {
|
||||||
void onAllowed();
|
void onAllowed();
|
||||||
|
|
||||||
void onBlocked(String reason);
|
void onBlocked(String reason, String tokenPart);
|
||||||
|
|
||||||
void onError(String message);
|
void onError(String message);
|
||||||
}
|
}
|
||||||
@@ -82,14 +82,21 @@ public class DeviceRegistry {
|
|||||||
}
|
}
|
||||||
String responseText = response.body().string();
|
String responseText = response.body().string();
|
||||||
JSONObject json = new JSONObject(responseText);
|
JSONObject json = new JSONObject(responseText);
|
||||||
boolean blocked = json.optBoolean("blocked", false);
|
|
||||||
JSONObject deviceJson = json.optJSONObject("device");
|
JSONObject deviceJson = json.optJSONObject("device");
|
||||||
|
JSONObject verificationJson = json.optJSONObject("verification");
|
||||||
|
boolean blocked = json.optBoolean("blocked", false);
|
||||||
String reason = json.optString("message");
|
String reason = json.optString("message");
|
||||||
if (TextUtils.isEmpty(reason) && deviceJson != null) {
|
if (TextUtils.isEmpty(reason) && deviceJson != null) {
|
||||||
reason = deviceJson.optString("notes", "");
|
reason = deviceJson.optString("notes", "");
|
||||||
}
|
}
|
||||||
|
String tokenPart = "";
|
||||||
|
if (verificationJson != null) {
|
||||||
|
boolean verificationRequired = verificationJson.optBoolean("required", false);
|
||||||
|
blocked = blocked || verificationRequired;
|
||||||
|
tokenPart = verificationJson.optString("clientTokenPart", "");
|
||||||
|
}
|
||||||
if (blocked) {
|
if (blocked) {
|
||||||
postBlocked(callback, reason);
|
postBlocked(callback, reason, tokenPart);
|
||||||
} else {
|
} else {
|
||||||
postAllowed(callback);
|
postAllowed(callback);
|
||||||
}
|
}
|
||||||
@@ -139,11 +146,13 @@ public class DeviceRegistry {
|
|||||||
mainHandler.post(callback::onAllowed);
|
mainHandler.post(callback::onAllowed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void postBlocked(Callback callback, String reason) {
|
private void postBlocked(Callback callback, String reason, String tokenPart) {
|
||||||
if (callback == null) {
|
if (callback == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mainHandler.post(() -> callback.onBlocked(reason));
|
String reasonText = reason == null ? "" : reason;
|
||||||
|
String token = tokenPart == null ? "" : tokenPart;
|
||||||
|
mainHandler.post(() -> callback.onBlocked(reasonText, token));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void postError(Callback callback, String message) {
|
private void postError(Callback callback, String message) {
|
||||||
|
|||||||
@@ -73,6 +73,21 @@ public class EventRepository {
|
|||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void prefetchEvents(Context context) {
|
||||||
|
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
String json = downloadJson();
|
||||||
|
parseEvents(json);
|
||||||
|
prefs.edit()
|
||||||
|
.putString(KEY_JSON, json)
|
||||||
|
.putLong(KEY_TIMESTAMP, System.currentTimeMillis())
|
||||||
|
.apply();
|
||||||
|
} catch (IOException | JSONException ignored) {
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
private String downloadJson() throws IOException {
|
private String downloadJson() throws IOException {
|
||||||
URL url = new URL(EVENTS_URL);
|
URL url = new URL(EVENTS_URL);
|
||||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
package com.streamplayer;
|
package com.streamplayer;
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
|
import android.content.ClipData;
|
||||||
|
import android.content.ClipboardManager;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.widget.Button;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.recyclerview.widget.GridLayoutManager;
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
@@ -21,14 +27,18 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
private static final long EVENT_PREFETCH_INTERVAL_MS = TimeUnit.HOURS.toMillis(1);
|
||||||
|
|
||||||
private RecyclerView sectionList;
|
private RecyclerView sectionList;
|
||||||
private RecyclerView contentList;
|
private RecyclerView contentList;
|
||||||
private ProgressBar loadingIndicator;
|
private ProgressBar loadingIndicator;
|
||||||
private TextView messageView;
|
private TextView messageView;
|
||||||
private TextView contentTitle;
|
private TextView contentTitle;
|
||||||
|
private Button eventsRefreshButton;
|
||||||
|
|
||||||
private ChannelAdapter channelAdapter;
|
private ChannelAdapter channelAdapter;
|
||||||
private EventAdapter eventAdapter;
|
private EventAdapter eventAdapter;
|
||||||
@@ -43,6 +53,9 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
private AlertDialog updateDialog;
|
private AlertDialog updateDialog;
|
||||||
private AlertDialog blockedDialog;
|
private AlertDialog blockedDialog;
|
||||||
private DeviceRegistry deviceRegistry;
|
private DeviceRegistry deviceRegistry;
|
||||||
|
private Handler eventPrefetchHandler;
|
||||||
|
private Runnable eventPrefetchRunnable;
|
||||||
|
private boolean isEventsRefreshing;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
@@ -54,6 +67,8 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
loadingIndicator = findViewById(R.id.loading_indicator);
|
loadingIndicator = findViewById(R.id.loading_indicator);
|
||||||
messageView = findViewById(R.id.message_view);
|
messageView = findViewById(R.id.message_view);
|
||||||
contentTitle = findViewById(R.id.content_title);
|
contentTitle = findViewById(R.id.content_title);
|
||||||
|
eventsRefreshButton = findViewById(R.id.events_refresh_button);
|
||||||
|
eventsRefreshButton.setOnClickListener(v -> manualRefreshEvents());
|
||||||
|
|
||||||
channelAdapter = new ChannelAdapter(
|
channelAdapter = new ChannelAdapter(
|
||||||
channel -> openPlayer(channel.getName(), channel.getPageUrl()));
|
channel -> openPlayer(channel.getName(), channel.getPageUrl()));
|
||||||
@@ -97,8 +112,8 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBlocked(String reason) {
|
public void onBlocked(String reason, String tokenPart) {
|
||||||
showBlockedDialog(reason);
|
showBlockedDialog(reason, tokenPart);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -110,6 +125,8 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
startEventPrefetchScheduler();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -135,6 +152,12 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
if (deviceRegistry != null) {
|
if (deviceRegistry != null) {
|
||||||
deviceRegistry.release();
|
deviceRegistry.release();
|
||||||
}
|
}
|
||||||
|
stopEventPrefetchScheduler();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
closeAppCompletely();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void selectSection(int index) {
|
private void selectSection(int index) {
|
||||||
@@ -154,6 +177,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showChannels(SectionEntry section) {
|
private void showChannels(SectionEntry section) {
|
||||||
|
updateEventsRefreshVisibility(false);
|
||||||
contentTitle.setText(section.title);
|
contentTitle.setText(section.title);
|
||||||
contentList.setLayoutManager(channelLayoutManager);
|
contentList.setLayoutManager(channelLayoutManager);
|
||||||
contentList.setAdapter(channelAdapter);
|
contentList.setAdapter(channelAdapter);
|
||||||
@@ -169,6 +193,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showEvents() {
|
private void showEvents() {
|
||||||
|
updateEventsRefreshVisibility(true);
|
||||||
contentTitle.setText(currentSection != null ? currentSection.title : getString(R.string.section_events));
|
contentTitle.setText(currentSection != null ? currentSection.title : getString(R.string.section_events));
|
||||||
contentList.setLayoutManager(eventLayoutManager);
|
contentList.setLayoutManager(eventLayoutManager);
|
||||||
contentList.setAdapter(eventAdapter);
|
contentList.setAdapter(eventAdapter);
|
||||||
@@ -180,6 +205,9 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void loadEvents(boolean forceRefresh) {
|
private void loadEvents(boolean forceRefresh) {
|
||||||
|
if (currentSection != null && currentSection.type == SectionEntry.Type.EVENTS) {
|
||||||
|
setEventsRefreshing(true);
|
||||||
|
}
|
||||||
loadingIndicator.setVisibility(View.VISIBLE);
|
loadingIndicator.setVisibility(View.VISIBLE);
|
||||||
messageView.setVisibility(View.GONE);
|
messageView.setVisibility(View.GONE);
|
||||||
eventRepository.loadEvents(this, forceRefresh, new EventRepository.Callback() {
|
eventRepository.loadEvents(this, forceRefresh, new EventRepository.Callback() {
|
||||||
@@ -193,6 +221,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
} else {
|
} else {
|
||||||
loadingIndicator.setVisibility(View.GONE);
|
loadingIndicator.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
setEventsRefreshing(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,11 +231,48 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
loadingIndicator.setVisibility(View.GONE);
|
loadingIndicator.setVisibility(View.GONE);
|
||||||
messageView.setVisibility(View.VISIBLE);
|
messageView.setVisibility(View.VISIBLE);
|
||||||
messageView.setText(getString(R.string.message_events_error, message));
|
messageView.setText(getString(R.string.message_events_error, message));
|
||||||
|
setEventsRefreshing(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void manualRefreshEvents() {
|
||||||
|
if (isEventsRefreshing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (currentSection == null || currentSection.type != SectionEntry.Type.EVENTS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loadEvents(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateEventsRefreshVisibility(boolean visible) {
|
||||||
|
if (eventsRefreshButton == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
eventsRefreshButton.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||||
|
if (visible) {
|
||||||
|
eventsRefreshButton.setEnabled(!isEventsRefreshing);
|
||||||
|
eventsRefreshButton.setText(isEventsRefreshing
|
||||||
|
? getString(R.string.events_refreshing)
|
||||||
|
: getString(R.string.events_refresh_action));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setEventsRefreshing(boolean refreshing) {
|
||||||
|
isEventsRefreshing = refreshing;
|
||||||
|
if (eventsRefreshButton == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (eventsRefreshButton.getVisibility() == View.VISIBLE) {
|
||||||
|
eventsRefreshButton.setEnabled(!refreshing);
|
||||||
|
eventsRefreshButton.setText(refreshing
|
||||||
|
? getString(R.string.events_refreshing)
|
||||||
|
: getString(R.string.events_refresh_action));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void displayEvents() {
|
private void displayEvents() {
|
||||||
loadingIndicator.setVisibility(View.GONE);
|
loadingIndicator.setVisibility(View.GONE);
|
||||||
if (cachedEvents.isEmpty()) {
|
if (cachedEvents.isEmpty()) {
|
||||||
@@ -241,7 +307,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
if (updateDialog != null && updateDialog.isShowing()) {
|
if (updateDialog != null && updateDialog.isShowing()) {
|
||||||
updateDialog.dismiss();
|
updateDialog.dismiss();
|
||||||
}
|
}
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(this)
|
AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.ThemeOverlay_StreamPlayer_AlertDialog)
|
||||||
.setTitle(mandatory ? R.string.update_required_title : R.string.update_available_title)
|
.setTitle(mandatory ? R.string.update_required_title : R.string.update_available_title)
|
||||||
.setMessage(buildUpdateMessage(info))
|
.setMessage(buildUpdateMessage(info))
|
||||||
.setPositiveButton(R.string.update_action_download,
|
.setPositiveButton(R.string.update_action_download,
|
||||||
@@ -251,7 +317,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
builder.setCancelable(false);
|
builder.setCancelable(false);
|
||||||
builder.setNegativeButton(R.string.update_action_close_app,
|
builder.setNegativeButton(R.string.update_action_close_app,
|
||||||
(dialog, which) -> finish());
|
(dialog, which) -> closeAppCompletely());
|
||||||
} else {
|
} else {
|
||||||
builder.setNegativeButton(R.string.update_action_later, null);
|
builder.setNegativeButton(R.string.update_action_later, null);
|
||||||
}
|
}
|
||||||
@@ -307,7 +373,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showBlockedDialog(String reason) {
|
private void showBlockedDialog(String reason, String tokenPart) {
|
||||||
if (isFinishing()) {
|
if (isFinishing()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -317,13 +383,76 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
if (blockedDialog != null && blockedDialog.isShowing()) {
|
if (blockedDialog != null && blockedDialog.isShowing()) {
|
||||||
blockedDialog.dismiss();
|
blockedDialog.dismiss();
|
||||||
}
|
}
|
||||||
blockedDialog = new AlertDialog.Builder(this)
|
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)
|
.setTitle(R.string.device_blocked_title)
|
||||||
.setMessage(getString(R.string.device_blocked_message, finalReason))
|
.setView(dialogView)
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
.setPositiveButton(R.string.device_blocked_close,
|
.setPositiveButton(R.string.device_blocked_close,
|
||||||
(dialog, which) -> finish())
|
(dialog, which) -> closeAppCompletely());
|
||||||
.show();
|
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 startEventPrefetchScheduler() {
|
||||||
|
if (eventPrefetchHandler == null) {
|
||||||
|
eventPrefetchHandler = new Handler(Looper.getMainLooper());
|
||||||
|
}
|
||||||
|
if (eventPrefetchRunnable == null) {
|
||||||
|
eventPrefetchRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (eventRepository != null) {
|
||||||
|
eventRepository.prefetchEvents(getApplicationContext());
|
||||||
|
}
|
||||||
|
if (eventPrefetchHandler != null) {
|
||||||
|
eventPrefetchHandler.postDelayed(this, EVENT_PREFETCH_INTERVAL_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
eventPrefetchHandler.removeCallbacks(eventPrefetchRunnable);
|
||||||
|
}
|
||||||
|
eventPrefetchHandler.post(eventPrefetchRunnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopEventPrefetchScheduler() {
|
||||||
|
if (eventPrefetchHandler != null && eventPrefetchRunnable != null) {
|
||||||
|
eventPrefetchHandler.removeCallbacks(eventPrefetchRunnable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeAppCompletely() {
|
||||||
|
finishAffinity();
|
||||||
|
android.os.Process.killProcess(android.os.Process.myPid());
|
||||||
|
System.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getSpanCount() {
|
private int getSpanCount() {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.content.Intent;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.StrictMode;
|
import android.os.StrictMode;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.WindowManager;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
@@ -58,6 +59,7 @@ public class PlayerActivity extends AppCompatActivity {
|
|||||||
);
|
);
|
||||||
|
|
||||||
setContentView(R.layout.activity_player);
|
setContentView(R.layout.activity_player);
|
||||||
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
|
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
if (intent == null) {
|
if (intent == null) {
|
||||||
@@ -254,6 +256,7 @@ public class PlayerActivity extends AppCompatActivity {
|
|||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
releasePlayer();
|
releasePlayer();
|
||||||
|
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void toggleOverlay() {
|
private void toggleOverlay() {
|
||||||
|
|||||||
@@ -72,14 +72,32 @@
|
|||||||
app:layout_constraintStart_toEndOf="@id/divider"
|
app:layout_constraintStart_toEndOf="@id/divider"
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:id="@+id/content_title"
|
android:id="@+id/content_header"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textColor="@color/white"
|
android:gravity="center_vertical"
|
||||||
android:textSize="18sp"
|
android:orientation="horizontal">
|
||||||
android:textStyle="bold"
|
|
||||||
tools:text="Canales" />
|
<TextView
|
||||||
|
android:id="@+id/content_title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="Canales" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/events_refresh_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:text="@string/events_refresh_action"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
android:id="@+id/loading_indicator"
|
android:id="@+id/loading_indicator"
|
||||||
|
|||||||
48
app/src/main/res/layout/dialog_blocked.xml
Normal file
48
app/src/main/res/layout/dialog_blocked.xml
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/blocked_message_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/blocked_token_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/blocked_token_label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/device_blocked_token_label"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/blocked_token_value"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textIsSelectable="true"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
@@ -39,6 +39,13 @@
|
|||||||
<string name="device_blocked_title">Dispositivo bloqueado</string>
|
<string name="device_blocked_title">Dispositivo bloqueado</string>
|
||||||
<string name="device_blocked_message">Este dispositivo fue bloqueado desde el panel de control. Motivo: %1$s</string>
|
<string name="device_blocked_message">Este dispositivo fue bloqueado desde el panel de control. Motivo: %1$s</string>
|
||||||
<string name="device_blocked_default_reason">Sin motivo especificado.</string>
|
<string name="device_blocked_default_reason">Sin motivo especificado.</string>
|
||||||
|
<string name="device_blocked_token_hint">Comparte este código con el administrador para solicitar acceso: %1$s</string>
|
||||||
|
<string name="device_blocked_token_label">Código de verificación</string>
|
||||||
<string name="device_blocked_close">Salir</string>
|
<string name="device_blocked_close">Salir</string>
|
||||||
|
<string name="device_blocked_copy_token">Copiar código</string>
|
||||||
|
<string name="device_blocked_copy_success">Código copiado al portapapeles</string>
|
||||||
|
<string name="device_blocked_copy_error">No se pudo copiar el código</string>
|
||||||
<string name="device_registry_error">No se pudo registrar el dispositivo (%1$s)</string>
|
<string name="device_registry_error">No se pudo registrar el dispositivo (%1$s)</string>
|
||||||
|
<string name="events_refresh_action">Actualizar ahora</string>
|
||||||
|
<string name="events_refreshing">Actualizando...</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -6,4 +6,10 @@
|
|||||||
<item name="android:statusBarColor">@color/black</item>
|
<item name="android:statusBarColor">@color/black</item>
|
||||||
<item name="android:navigationBarColor">@color/black</item>
|
<item name="android:navigationBarColor">@color/black</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="ThemeOverlay.StreamPlayer.AlertDialog" parent="ThemeOverlay.AppCompat.Dialog.Alert">
|
||||||
|
<item name="android:textColorPrimary">@color/white</item>
|
||||||
|
<item name="android:textColorSecondary">@color/white</item>
|
||||||
|
<item name="colorAccent">@color/white</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
4
dashboard/config.example.json
Normal file
4
dashboard/config.example.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"telegramBotToken": "123456:ABCDEF-TOKEN",
|
||||||
|
"telegramChatId": "123456789"
|
||||||
|
}
|
||||||
@@ -1 +1,26 @@
|
|||||||
[]
|
[
|
||||||
|
{
|
||||||
|
"deviceId": "f91f2668e8dfb2a7",
|
||||||
|
"alias": "",
|
||||||
|
"deviceName": "SM-S928B",
|
||||||
|
"model": "SM-S928B",
|
||||||
|
"manufacturer": "Samsung",
|
||||||
|
"osVersion": "16 (API 36)",
|
||||||
|
"appVersionName": "9.4.3",
|
||||||
|
"appVersionCode": 94300,
|
||||||
|
"firstSeen": "2025-11-23T22:31:13.359Z",
|
||||||
|
"lastSeen": "2025-11-23T23:11:07.215Z",
|
||||||
|
"blocked": false,
|
||||||
|
"notes": "",
|
||||||
|
"installs": 7,
|
||||||
|
"ip": "181.23.253.20",
|
||||||
|
"country": "AR",
|
||||||
|
"verification": {
|
||||||
|
"clientPart": "1714c2bb93670c3f",
|
||||||
|
"adminPart": "9924c7049211c58c",
|
||||||
|
"status": "verified",
|
||||||
|
"createdAt": "2025-11-23T22:31:13.359Z",
|
||||||
|
"verifiedAt": "2025-11-23T22:33:11.942Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|||||||
379
dashboard/node_modules/.package-lock.json
generated
vendored
379
dashboard/node_modules/.package-lock.json
generated
vendored
@@ -17,6 +17,21 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ansi-styles": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"color-convert": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/anymatch": {
|
"node_modules/anymatch": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||||
@@ -37,11 +52,36 @@
|
|||||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/async": {
|
||||||
|
"version": "2.6.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
|
||||||
|
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"lodash": "^4.17.14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "1.13.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
|
||||||
|
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.15.6",
|
||||||
|
"form-data": "^4.0.4",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/basic-auth": {
|
"node_modules/basic-auth": {
|
||||||
@@ -103,7 +143,6 @@
|
|||||||
"version": "1.1.12",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
@@ -123,6 +162,15 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/buffer-crc32": {
|
||||||
|
"version": "0.2.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
|
||||||
|
"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/bytes": {
|
"node_modules/bytes": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||||
@@ -161,6 +209,43 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/chalk": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": "^4.1.0",
|
||||||
|
"supports-color": "^7.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/chalk/node_modules/has-flag": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/chalk/node_modules/supports-color": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"has-flag": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||||
@@ -186,11 +271,40 @@
|
|||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/color-convert": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"color-name": "~1.1.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/color-name": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/concat-map": {
|
"node_modules/concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/content-disposition": {
|
"node_modules/content-disposition": {
|
||||||
@@ -251,6 +365,15 @@
|
|||||||
"ms": "2.0.0"
|
"ms": "2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/depd": {
|
"node_modules/depd": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||||
@@ -270,6 +393,18 @@
|
|||||||
"npm": "1.2.8000 || >= 1.4.16"
|
"npm": "1.2.8000 || >= 1.4.16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dotenv": {
|
||||||
|
"version": "16.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
|
||||||
|
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://dotenvx.com"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dunder-proto": {
|
"node_modules/dunder-proto": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
@@ -329,6 +464,21 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/es-set-tostringtag": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"get-intrinsic": "^1.2.6",
|
||||||
|
"has-tostringtag": "^1.0.2",
|
||||||
|
"hasown": "^2.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/escape-html": {
|
"node_modules/escape-html": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||||
@@ -390,6 +540,15 @@
|
|||||||
"url": "https://opencollective.com/express"
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fd-slicer": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"pend": "~1.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fill-range": {
|
"node_modules/fill-range": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||||
@@ -421,6 +580,42 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.15.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||||
|
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"debug": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/form-data": {
|
||||||
|
"version": "4.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||||
|
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"es-set-tostringtag": "^2.1.0",
|
||||||
|
"hasown": "^2.0.2",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/forwarded": {
|
"node_modules/forwarded": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||||
@@ -439,6 +634,12 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fs.realpath": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/function-bind": {
|
"node_modules/function-bind": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
@@ -448,6 +649,24 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/geoip-lite": {
|
||||||
|
"version": "1.4.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/geoip-lite/-/geoip-lite-1.4.10.tgz",
|
||||||
|
"integrity": "sha512-4N69uhpS3KFd97m00wiFEefwa+L+HT5xZbzPhwu+sDawStg6UN/dPwWtUfkQuZkGIY1Cj7wDVp80IsqNtGMi2w==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"async": "2.1 - 2.6.4",
|
||||||
|
"chalk": "4.1 - 4.1.2",
|
||||||
|
"iconv-lite": "0.4.13 - 0.6.3",
|
||||||
|
"ip-address": "5.8.9 - 5.9.4",
|
||||||
|
"lazy": "1.0.11",
|
||||||
|
"rimraf": "2.5.2 - 2.7.1",
|
||||||
|
"yauzl": "2.9.2 - 2.10.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/get-intrinsic": {
|
"node_modules/get-intrinsic": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||||
@@ -485,6 +704,27 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/glob": {
|
||||||
|
"version": "7.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||||
|
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||||
|
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"fs.realpath": "^1.0.0",
|
||||||
|
"inflight": "^1.0.4",
|
||||||
|
"inherits": "2",
|
||||||
|
"minimatch": "^3.1.1",
|
||||||
|
"once": "^1.3.0",
|
||||||
|
"path-is-absolute": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/glob-parent": {
|
"node_modules/glob-parent": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||||
@@ -532,6 +772,21 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/has-tostringtag": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"has-symbols": "^1.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hasown": {
|
"node_modules/hasown": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
@@ -579,12 +834,37 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/inflight": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||||
|
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"once": "^1.3.0",
|
||||||
|
"wrappy": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/inherits": {
|
"node_modules/inherits": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/ip-address": {
|
||||||
|
"version": "5.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-5.9.4.tgz",
|
||||||
|
"integrity": "sha512-dHkI3/YNJq4b/qQaz+c8LuarD3pY24JqZWfjB8aZx1gtpc2MDILu9L9jpZe1sHpzo/yWFweQVn+U//FhazUxmw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"jsbn": "1.1.0",
|
||||||
|
"lodash": "^4.17.15",
|
||||||
|
"sprintf-js": "1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ipaddr.js": {
|
"node_modules/ipaddr.js": {
|
||||||
"version": "1.9.1",
|
"version": "1.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||||
@@ -640,6 +920,27 @@
|
|||||||
"node": ">=0.12.0"
|
"node": ">=0.12.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jsbn": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lazy": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/lazy/-/lazy-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-Y+CjUfLmIpoUCCRl0ub4smrYtGGr5AOa2AKOaWelGHOGz33X/Y/KizefGqbkwfz44+cnq/+9habclf8vOmu2LA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/lodash": {
|
||||||
|
"version": "4.17.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/math-intrinsics": {
|
"node_modules/math-intrinsics": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
@@ -713,7 +1014,6 @@
|
|||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
@@ -871,6 +1171,15 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/once": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"wrappy": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/parseurl": {
|
"node_modules/parseurl": {
|
||||||
"version": "1.3.3",
|
"version": "1.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||||
@@ -880,12 +1189,27 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/path-is-absolute": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/path-to-regexp": {
|
"node_modules/path-to-regexp": {
|
||||||
"version": "0.1.12",
|
"version": "0.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
|
||||||
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
|
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/pend": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
@@ -912,6 +1236,12 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/proxy-from-env": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/pstree.remy": {
|
"node_modules/pstree.remy": {
|
||||||
"version": "1.1.8",
|
"version": "1.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
|
||||||
@@ -971,6 +1301,25 @@
|
|||||||
"node": ">=8.10.0"
|
"node": ">=8.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/request-ip": {
|
||||||
|
"version": "3.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/request-ip/-/request-ip-3.3.0.tgz",
|
||||||
|
"integrity": "sha512-cA6Xh6e0fDBBBwH77SLJaJPBmD3nWVAcF9/XAcsrIHdjhFzFiB5aNQFytdjCGPezU3ROwrR11IddKAM08vohxA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/rimraf": {
|
||||||
|
"version": "2.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||||
|
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||||
|
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"glob": "^7.1.3"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"rimraf": "bin.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/safe-buffer": {
|
"node_modules/safe-buffer": {
|
||||||
"version": "5.2.1",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||||
@@ -1155,6 +1504,12 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sprintf-js": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
"node_modules/statuses": {
|
"node_modules/statuses": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||||
@@ -1255,6 +1610,22 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/wrappy": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/yauzl": {
|
||||||
|
"version": "2.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
|
||||||
|
"integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"buffer-crc32": "~0.2.3",
|
||||||
|
"fd-slicer": "~1.1.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
385
dashboard/package-lock.json
generated
385
dashboard/package-lock.json
generated
@@ -9,9 +9,13 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^1.6.7",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"morgan": "^1.10.0"
|
"geoip-lite": "^1.4.6",
|
||||||
|
"morgan": "^1.10.0",
|
||||||
|
"request-ip": "^3.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^3.0.1"
|
"nodemon": "^3.0.1"
|
||||||
@@ -30,6 +34,21 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ansi-styles": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"color-convert": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/anymatch": {
|
"node_modules/anymatch": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||||
@@ -50,11 +69,36 @@
|
|||||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/async": {
|
||||||
|
"version": "2.6.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
|
||||||
|
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"lodash": "^4.17.14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "1.13.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
|
||||||
|
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.15.6",
|
||||||
|
"form-data": "^4.0.4",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/basic-auth": {
|
"node_modules/basic-auth": {
|
||||||
@@ -116,7 +160,6 @@
|
|||||||
"version": "1.1.12",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
@@ -136,6 +179,15 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/buffer-crc32": {
|
||||||
|
"version": "0.2.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
|
||||||
|
"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/bytes": {
|
"node_modules/bytes": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||||
@@ -174,6 +226,43 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/chalk": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": "^4.1.0",
|
||||||
|
"supports-color": "^7.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/chalk/node_modules/has-flag": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/chalk/node_modules/supports-color": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"has-flag": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||||
@@ -199,11 +288,40 @@
|
|||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/color-convert": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"color-name": "~1.1.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/color-name": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/concat-map": {
|
"node_modules/concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/content-disposition": {
|
"node_modules/content-disposition": {
|
||||||
@@ -264,6 +382,15 @@
|
|||||||
"ms": "2.0.0"
|
"ms": "2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/depd": {
|
"node_modules/depd": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||||
@@ -283,6 +410,18 @@
|
|||||||
"npm": "1.2.8000 || >= 1.4.16"
|
"npm": "1.2.8000 || >= 1.4.16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dotenv": {
|
||||||
|
"version": "16.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
|
||||||
|
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://dotenvx.com"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dunder-proto": {
|
"node_modules/dunder-proto": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
@@ -342,6 +481,21 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/es-set-tostringtag": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"get-intrinsic": "^1.2.6",
|
||||||
|
"has-tostringtag": "^1.0.2",
|
||||||
|
"hasown": "^2.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/escape-html": {
|
"node_modules/escape-html": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||||
@@ -403,6 +557,15 @@
|
|||||||
"url": "https://opencollective.com/express"
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fd-slicer": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"pend": "~1.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fill-range": {
|
"node_modules/fill-range": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||||
@@ -434,6 +597,42 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.15.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||||
|
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"debug": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/form-data": {
|
||||||
|
"version": "4.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||||
|
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"es-set-tostringtag": "^2.1.0",
|
||||||
|
"hasown": "^2.0.2",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/forwarded": {
|
"node_modules/forwarded": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||||
@@ -452,6 +651,12 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fs.realpath": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
@@ -476,6 +681,24 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/geoip-lite": {
|
||||||
|
"version": "1.4.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/geoip-lite/-/geoip-lite-1.4.10.tgz",
|
||||||
|
"integrity": "sha512-4N69uhpS3KFd97m00wiFEefwa+L+HT5xZbzPhwu+sDawStg6UN/dPwWtUfkQuZkGIY1Cj7wDVp80IsqNtGMi2w==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"async": "2.1 - 2.6.4",
|
||||||
|
"chalk": "4.1 - 4.1.2",
|
||||||
|
"iconv-lite": "0.4.13 - 0.6.3",
|
||||||
|
"ip-address": "5.8.9 - 5.9.4",
|
||||||
|
"lazy": "1.0.11",
|
||||||
|
"rimraf": "2.5.2 - 2.7.1",
|
||||||
|
"yauzl": "2.9.2 - 2.10.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/get-intrinsic": {
|
"node_modules/get-intrinsic": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||||
@@ -513,6 +736,27 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/glob": {
|
||||||
|
"version": "7.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||||
|
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||||
|
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"fs.realpath": "^1.0.0",
|
||||||
|
"inflight": "^1.0.4",
|
||||||
|
"inherits": "2",
|
||||||
|
"minimatch": "^3.1.1",
|
||||||
|
"once": "^1.3.0",
|
||||||
|
"path-is-absolute": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/glob-parent": {
|
"node_modules/glob-parent": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||||
@@ -560,6 +804,21 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/has-tostringtag": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"has-symbols": "^1.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hasown": {
|
"node_modules/hasown": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
@@ -607,12 +866,37 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/inflight": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||||
|
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"once": "^1.3.0",
|
||||||
|
"wrappy": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/inherits": {
|
"node_modules/inherits": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/ip-address": {
|
||||||
|
"version": "5.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-5.9.4.tgz",
|
||||||
|
"integrity": "sha512-dHkI3/YNJq4b/qQaz+c8LuarD3pY24JqZWfjB8aZx1gtpc2MDILu9L9jpZe1sHpzo/yWFweQVn+U//FhazUxmw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"jsbn": "1.1.0",
|
||||||
|
"lodash": "^4.17.15",
|
||||||
|
"sprintf-js": "1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ipaddr.js": {
|
"node_modules/ipaddr.js": {
|
||||||
"version": "1.9.1",
|
"version": "1.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||||
@@ -668,6 +952,27 @@
|
|||||||
"node": ">=0.12.0"
|
"node": ">=0.12.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jsbn": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lazy": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/lazy/-/lazy-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-Y+CjUfLmIpoUCCRl0ub4smrYtGGr5AOa2AKOaWelGHOGz33X/Y/KizefGqbkwfz44+cnq/+9habclf8vOmu2LA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/lodash": {
|
||||||
|
"version": "4.17.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/math-intrinsics": {
|
"node_modules/math-intrinsics": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
@@ -741,7 +1046,6 @@
|
|||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
@@ -899,6 +1203,15 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/once": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"wrappy": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/parseurl": {
|
"node_modules/parseurl": {
|
||||||
"version": "1.3.3",
|
"version": "1.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||||
@@ -908,12 +1221,27 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/path-is-absolute": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/path-to-regexp": {
|
"node_modules/path-to-regexp": {
|
||||||
"version": "0.1.12",
|
"version": "0.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
|
||||||
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
|
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/pend": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
@@ -940,6 +1268,12 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/proxy-from-env": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/pstree.remy": {
|
"node_modules/pstree.remy": {
|
||||||
"version": "1.1.8",
|
"version": "1.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
|
||||||
@@ -999,6 +1333,25 @@
|
|||||||
"node": ">=8.10.0"
|
"node": ">=8.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/request-ip": {
|
||||||
|
"version": "3.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/request-ip/-/request-ip-3.3.0.tgz",
|
||||||
|
"integrity": "sha512-cA6Xh6e0fDBBBwH77SLJaJPBmD3nWVAcF9/XAcsrIHdjhFzFiB5aNQFytdjCGPezU3ROwrR11IddKAM08vohxA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/rimraf": {
|
||||||
|
"version": "2.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||||
|
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||||
|
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"glob": "^7.1.3"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"rimraf": "bin.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/safe-buffer": {
|
"node_modules/safe-buffer": {
|
||||||
"version": "5.2.1",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||||
@@ -1183,6 +1536,12 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sprintf-js": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
"node_modules/statuses": {
|
"node_modules/statuses": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||||
@@ -1283,6 +1642,22 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/wrappy": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/yauzl": {
|
||||||
|
"version": "2.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
|
||||||
|
"integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"buffer-crc32": "~0.2.3",
|
||||||
|
"fd-slicer": "~1.1.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,13 @@
|
|||||||
"author": "StreamPlayer",
|
"author": "StreamPlayer",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^1.6.7",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"morgan": "^1.10.0"
|
"geoip-lite": "^1.4.6",
|
||||||
|
"morgan": "^1.10.0",
|
||||||
|
"request-ip": "^3.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^3.0.1"
|
"nodemon": "^3.0.1"
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ async function fetchDevices() {
|
|||||||
function renderTable(devices) {
|
function renderTable(devices) {
|
||||||
tableBody.innerHTML = '';
|
tableBody.innerHTML = '';
|
||||||
if (!devices.length) {
|
if (!devices.length) {
|
||||||
tableBody.innerHTML = '<tr><td colspan="7" class="empty">Sin registros</td></tr>';
|
tableBody.innerHTML = '<tr><td colspan="10" class="empty">Sin registros</td></tr>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
devices.sort((a, b) => (a.lastSeen || '').localeCompare(b.lastSeen || '') * -1);
|
devices.sort((a, b) => (a.lastSeen || '').localeCompare(b.lastSeen || '') * -1);
|
||||||
@@ -31,6 +31,22 @@ function renderTable(devices) {
|
|||||||
tr.classList.add('blocked');
|
tr.classList.add('blocked');
|
||||||
}
|
}
|
||||||
const alias = device.alias && device.alias.trim().length ? device.alias : 'Sin alias';
|
const alias = device.alias && device.alias.trim().length ? device.alias : 'Sin alias';
|
||||||
|
const verificationStatus = device.verification && device.verification.status ? device.verification.status : 'pending';
|
||||||
|
const needsVerification = verificationStatus !== 'verified';
|
||||||
|
const verificationText = needsVerification
|
||||||
|
? `Pendiente - Token cliente: ${device.verification && device.verification.clientPart ? device.verification.clientPart : 'N/A'}`
|
||||||
|
: `Verificado ${device.verification.verifiedAt ? `(${formatDate(device.verification.verifiedAt)})` : ''}`;
|
||||||
|
const statusLabel = device.blocked
|
||||||
|
? 'Bloqueado'
|
||||||
|
: needsVerification ? 'Pendiente token' : 'Activo';
|
||||||
|
|
||||||
|
const actions = [`<button data-action="alias">Alias</button>`];
|
||||||
|
if (needsVerification) {
|
||||||
|
actions.push('<button data-action="verify" class="primary">Verificar token</button>');
|
||||||
|
}
|
||||||
|
actions.push(device.blocked ? '<button data-action="unblock" class="primary">Desbloquear</button>' : '<button data-action="block" class="danger">Bloquear</button>');
|
||||||
|
actions.push('<button data-action="delete" class="danger ghost">Borrar</button>');
|
||||||
|
|
||||||
tr.innerHTML = `
|
tr.innerHTML = `
|
||||||
<td>
|
<td>
|
||||||
<div class="alias">${alias}</div>
|
<div class="alias">${alias}</div>
|
||||||
@@ -39,11 +55,13 @@ function renderTable(devices) {
|
|||||||
<td>${device.deviceId}</td>
|
<td>${device.deviceId}</td>
|
||||||
<td>${[device.manufacturer, device.model].filter(Boolean).join(' ')}</td>
|
<td>${[device.manufacturer, device.model].filter(Boolean).join(' ')}</td>
|
||||||
<td>${device.appVersionName || ''} (${device.appVersionCode || ''})</td>
|
<td>${device.appVersionName || ''} (${device.appVersionCode || ''})</td>
|
||||||
|
<td>${device.ip || '-'}</td>
|
||||||
|
<td>${formatCountry(device.country)}</td>
|
||||||
|
<td>${verificationText}</td>
|
||||||
<td>${formatDate(device.lastSeen)}</td>
|
<td>${formatDate(device.lastSeen)}</td>
|
||||||
<td>${device.blocked ? 'Bloqueado' : 'Activo'}</td>
|
<td>${statusLabel}</td>
|
||||||
<td class="actions-cell">
|
<td class="actions-cell">
|
||||||
<button data-action="alias">Alias</button>
|
${actions.join(' ')}
|
||||||
${device.blocked ? '<button data-action="unblock" class="primary">Desbloquear</button>' : '<button data-action="block" class="danger">Bloquear</button>'}
|
|
||||||
</td>
|
</td>
|
||||||
`;
|
`;
|
||||||
tr.dataset.deviceId = device.deviceId;
|
tr.dataset.deviceId = device.deviceId;
|
||||||
@@ -58,6 +76,13 @@ function formatDate(value) {
|
|||||||
return date.toLocaleString();
|
return date.toLocaleString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatCountry(value) {
|
||||||
|
if (!value || value === 'N/A') {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
async function blockDevice(deviceId) {
|
async function blockDevice(deviceId) {
|
||||||
const reason = prompt('Motivo del bloqueo (opcional):');
|
const reason = prompt('Motivo del bloqueo (opcional):');
|
||||||
await fetch(`/api/devices/${encodeURIComponent(deviceId)}/block`, {
|
await fetch(`/api/devices/${encodeURIComponent(deviceId)}/block`, {
|
||||||
@@ -73,6 +98,41 @@ async function unblockDevice(deviceId) {
|
|||||||
await fetchDevices();
|
await fetchDevices();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function deleteDevice(deviceId) {
|
||||||
|
const confirmation = confirm('¿Seguro que quieres borrar este dispositivo? Generará un nuevo token cuando se registre de nuevo.');
|
||||||
|
if (!confirmation) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const response = await fetch(`/api/devices/${encodeURIComponent(deviceId)}`, { method: 'DELETE' });
|
||||||
|
if (!response.ok) {
|
||||||
|
alert('No se pudo borrar el dispositivo');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await fetchDevices();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verifyDevice(deviceId) {
|
||||||
|
const clientTokenPart = prompt('Introduce el token que aparece en el dispositivo:');
|
||||||
|
if (clientTokenPart === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const adminTokenPart = prompt('Introduce el token recibido en Telegram:');
|
||||||
|
if (adminTokenPart === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const response = await fetch(`/api/devices/${encodeURIComponent(deviceId)}/verify`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ clientTokenPart, adminTokenPart })
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
const payload = await response.json().catch(() => ({}));
|
||||||
|
alert(payload.error || 'No se pudo verificar el token');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await fetchDevices();
|
||||||
|
}
|
||||||
|
|
||||||
async function updateAlias(deviceId) {
|
async function updateAlias(deviceId) {
|
||||||
const alias = prompt('Nuevo alias para el dispositivo:');
|
const alias = prompt('Nuevo alias para el dispositivo:');
|
||||||
if (alias === null) {
|
if (alias === null) {
|
||||||
@@ -106,6 +166,10 @@ tableBody.addEventListener('click', async (event) => {
|
|||||||
await unblockDevice(deviceId);
|
await unblockDevice(deviceId);
|
||||||
} else if (action === 'alias') {
|
} else if (action === 'alias') {
|
||||||
await updateAlias(deviceId);
|
await updateAlias(deviceId);
|
||||||
|
} else if (action === 'verify') {
|
||||||
|
await verifyDevice(deviceId);
|
||||||
|
} else if (action === 'delete') {
|
||||||
|
await deleteDevice(deviceId);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|||||||
@@ -22,6 +22,9 @@
|
|||||||
<th>Device ID</th>
|
<th>Device ID</th>
|
||||||
<th>Modelo</th>
|
<th>Modelo</th>
|
||||||
<th>Versión app</th>
|
<th>Versión app</th>
|
||||||
|
<th>IP Pública</th>
|
||||||
|
<th>País</th>
|
||||||
|
<th>Verificación</th>
|
||||||
<th>Última vez visto</th>
|
<th>Última vez visto</th>
|
||||||
<th>Estado</th>
|
<th>Estado</th>
|
||||||
<th>Acciones</th>
|
<th>Acciones</th>
|
||||||
|
|||||||
@@ -3,10 +3,29 @@ const cors = require('cors');
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const morgan = require('morgan');
|
const morgan = require('morgan');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
const axios = require('axios');
|
||||||
|
const requestIp = require('request-ip');
|
||||||
|
const geoip = require('geoip-lite');
|
||||||
|
const dotenv = require('dotenv');
|
||||||
|
|
||||||
|
const envPath = path.resolve(__dirname, '..', '.env');
|
||||||
|
if (fs.existsSync(envPath)) {
|
||||||
|
dotenv.config({ path: envPath });
|
||||||
|
} else {
|
||||||
|
dotenv.config();
|
||||||
|
}
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 4000;
|
const PORT = process.env.PORT || 4000;
|
||||||
const DATA_PATH = path.join(__dirname, 'data', 'devices.json');
|
const DATA_PATH = path.join(__dirname, 'data', 'devices.json');
|
||||||
|
const CONFIG_PATH = path.join(__dirname, 'config.json');
|
||||||
|
|
||||||
|
const config = loadConfig();
|
||||||
|
const TELEGRAM_BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN || config.telegramBotToken || '';
|
||||||
|
const TELEGRAM_CHAT_ID = (process.env.TELEGRAM_CHAT_ID || config.telegramChatId || '').toString();
|
||||||
|
let telegramOffset = 0;
|
||||||
|
let telegramPollingStarted = false;
|
||||||
|
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
@@ -20,17 +39,29 @@ app.use((req, res, next) => {
|
|||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function loadConfig() {
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(CONFIG_PATH)) {
|
||||||
|
const raw = fs.readFileSync(CONFIG_PATH, 'utf-8');
|
||||||
|
return JSON.parse(raw);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Could not parse config.json', error);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
function ensureDataFile() {
|
function ensureDataFile() {
|
||||||
if (!fs.existsSync(DATA_PATH)) {
|
if (!fs.existsSync(DATA_PATH)) {
|
||||||
fs.mkdirSync(path.dirname(DATA_PATH), { recursive: true });
|
fs.mkdirSync(path.dirname(DATA_PATH), { recursive: true });
|
||||||
fs.writeFileSync(DATA_PATH, JSON.stringify([] , null, 2));
|
fs.writeFileSync(DATA_PATH, JSON.stringify([], null, 2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function readDevices() {
|
function readDevices() {
|
||||||
ensureDataFile();
|
ensureDataFile();
|
||||||
const raw = fs.readFileSync(DATA_PATH, 'utf-8');
|
|
||||||
try {
|
try {
|
||||||
|
const raw = fs.readFileSync(DATA_PATH, 'utf-8');
|
||||||
const devices = JSON.parse(raw);
|
const devices = JSON.parse(raw);
|
||||||
return Array.isArray(devices) ? devices : [];
|
return Array.isArray(devices) ? devices : [];
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -48,6 +79,211 @@ function sanitizeId(input) {
|
|||||||
return String(input || '').trim();
|
return String(input || '').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sanitizeIp(ip) {
|
||||||
|
if (!ip) return '';
|
||||||
|
if (ip.startsWith('::ffff:')) {
|
||||||
|
return ip.replace('::ffff:', '');
|
||||||
|
}
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
function lookupLocation(ip) {
|
||||||
|
if (!ip) {
|
||||||
|
return { country: 'N/A', region: '', city: '' };
|
||||||
|
}
|
||||||
|
const geo = geoip.lookup(ip);
|
||||||
|
if (!geo) {
|
||||||
|
return { country: 'N/A', region: '', city: '' };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
country: geo.country || 'N/A',
|
||||||
|
region: geo.region || '',
|
||||||
|
city: geo.city || ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateTokenParts() {
|
||||||
|
const fullToken = crypto.randomBytes(16).toString('hex');
|
||||||
|
const mid = Math.floor(fullToken.length / 2);
|
||||||
|
return {
|
||||||
|
clientPart: fullToken.slice(0, mid),
|
||||||
|
adminPart: fullToken.slice(mid)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendTelegramMessage(message, options = {}) {
|
||||||
|
if (!TELEGRAM_BOT_TOKEN || !TELEGRAM_CHAT_ID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const url = `https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage`;
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
chat_id: TELEGRAM_CHAT_ID,
|
||||||
|
text: message,
|
||||||
|
disable_web_page_preview: true
|
||||||
|
};
|
||||||
|
if (options.parseMode) {
|
||||||
|
payload.parse_mode = options.parseMode;
|
||||||
|
}
|
||||||
|
await axios.post(url, payload);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to send Telegram message', error.response ? error.response.data : error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendTelegramNotification(message) {
|
||||||
|
await sendTelegramMessage(message, { parseMode: 'Markdown' });
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTelegramMessage(device, verificationRequired) {
|
||||||
|
const lines = [
|
||||||
|
'*Nuevo registro de dispositivo*',
|
||||||
|
`ID: ${device.deviceId}`,
|
||||||
|
`Alias: ${device.alias || '-'}`,
|
||||||
|
`Modelo: ${device.manufacturer} ${device.model}`,
|
||||||
|
`Versión app: ${device.appVersionName} (${device.appVersionCode})`,
|
||||||
|
`IP: ${device.ip || '-'} / País: ${device.country || '-'}`,
|
||||||
|
`Última vez visto: ${device.lastSeen}`
|
||||||
|
];
|
||||||
|
if (verificationRequired && device.verification) {
|
||||||
|
lines.push('`Token Admin` (guárdalo): `' + device.verification.adminPart + '`');
|
||||||
|
lines.push('Autorizar: `/allow ' + device.deviceId + ' TOKEN_CLIENTE`');
|
||||||
|
lines.push('Rechazar: `/deny ' + device.deviceId + ' TOKEN_CLIENTE [motivo]`');
|
||||||
|
}
|
||||||
|
return lines.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initializeTelegramOffset() {
|
||||||
|
if (!TELEGRAM_BOT_TOKEN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const url = `https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getUpdates`;
|
||||||
|
try {
|
||||||
|
const { data } = await axios.get(url, { params: { timeout: 1 } });
|
||||||
|
if (data.ok && Array.isArray(data.result) && data.result.length > 0) {
|
||||||
|
telegramOffset = data.result[data.result.length - 1].update_id + 1;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to initialize Telegram offset', error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pollTelegramUpdates() {
|
||||||
|
if (!TELEGRAM_BOT_TOKEN || !TELEGRAM_CHAT_ID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const url = `https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getUpdates`;
|
||||||
|
const params = {
|
||||||
|
timeout: 25
|
||||||
|
};
|
||||||
|
if (telegramOffset) {
|
||||||
|
params.offset = telegramOffset;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const { data } = await axios.get(url, { params });
|
||||||
|
if (data.ok && Array.isArray(data.result)) {
|
||||||
|
for (const update of data.result) {
|
||||||
|
telegramOffset = update.update_id + 1;
|
||||||
|
if (update.message) {
|
||||||
|
processTelegramMessage(update.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Telegram polling error', error.message);
|
||||||
|
} finally {
|
||||||
|
setTimeout(pollTelegramUpdates, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startTelegramPolling() {
|
||||||
|
if (telegramPollingStarted || !TELEGRAM_BOT_TOKEN || !TELEGRAM_CHAT_ID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
telegramPollingStarted = true;
|
||||||
|
await initializeTelegramOffset();
|
||||||
|
pollTelegramUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
|
function processTelegramMessage(message) {
|
||||||
|
const chatId = message.chat && message.chat.id ? message.chat.id.toString() : '';
|
||||||
|
if (chatId && TELEGRAM_CHAT_ID && chatId !== TELEGRAM_CHAT_ID.toString()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const text = (message.text || '').trim();
|
||||||
|
if (!text.startsWith('/')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const parts = text.split(/\s+/);
|
||||||
|
const command = parts[0].toLowerCase();
|
||||||
|
if (command === '/allow' && parts.length >= 3) {
|
||||||
|
handleTelegramAllow(parts[1], parts[2]);
|
||||||
|
} else if (command === '/deny' && parts.length >= 3) {
|
||||||
|
const reason = parts.slice(3).join(' ') || 'Bloqueado desde Telegram';
|
||||||
|
handleTelegramDeny(parts[1], parts[2], reason);
|
||||||
|
} else if (command === '/pending') {
|
||||||
|
handleTelegramPending();
|
||||||
|
} else {
|
||||||
|
sendTelegramMessage('Comandos disponibles:\n/allow <deviceId> <token_cliente>\n/deny <deviceId> <token_cliente> [motivo]\n/pending');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleTelegramAllow(deviceId, clientToken) {
|
||||||
|
const devices = readDevices();
|
||||||
|
const device = devices.find(d => d.deviceId === deviceId);
|
||||||
|
if (!device || !device.verification) {
|
||||||
|
await sendTelegramMessage(`❌ Dispositivo ${deviceId} no encontrado.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (device.verification.status === 'verified') {
|
||||||
|
await sendTelegramMessage(`ℹ️ El dispositivo ${deviceId} ya está verificado.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (device.verification.clientPart !== clientToken.trim()) {
|
||||||
|
await sendTelegramMessage('❌ Token del cliente inválido.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
device.verification.status = 'verified';
|
||||||
|
device.verification.verifiedAt = new Date().toISOString();
|
||||||
|
device.blocked = false;
|
||||||
|
device.notes = '';
|
||||||
|
writeDevices(devices);
|
||||||
|
await sendTelegramMessage(`✅ Dispositivo ${deviceId} autorizado correctamente.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleTelegramDeny(deviceId, clientToken, reason) {
|
||||||
|
const devices = readDevices();
|
||||||
|
const device = devices.find(d => d.deviceId === deviceId);
|
||||||
|
if (!device || !device.verification) {
|
||||||
|
await sendTelegramMessage(`❌ Dispositivo ${deviceId} no encontrado.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (device.verification.clientPart !== clientToken.trim()) {
|
||||||
|
await sendTelegramMessage('❌ Token del cliente inválido.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
device.verification.status = 'denied';
|
||||||
|
device.verification.deniedAt = new Date().toISOString();
|
||||||
|
device.blocked = true;
|
||||||
|
device.notes = reason;
|
||||||
|
writeDevices(devices);
|
||||||
|
await sendTelegramMessage(`🚫 Dispositivo ${deviceId} bloqueado. Motivo: ${reason}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleTelegramPending() {
|
||||||
|
const devices = readDevices();
|
||||||
|
const pending = devices.filter(d => !d.verification || d.verification.status !== 'verified');
|
||||||
|
if (!pending.length) {
|
||||||
|
await sendTelegramMessage('No hay dispositivos pendientes.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const lines = pending.slice(0, 10).map(device => {
|
||||||
|
const token = device.verification ? device.verification.clientPart : 'N/A';
|
||||||
|
return `${device.deviceId} - Token cliente: ${token}`;
|
||||||
|
});
|
||||||
|
await sendTelegramMessage('Pendientes:\n' + lines.join('\n'));
|
||||||
|
}
|
||||||
|
|
||||||
app.get('/api/health', (req, res) => {
|
app.get('/api/health', (req, res) => {
|
||||||
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
||||||
});
|
});
|
||||||
@@ -75,8 +311,13 @@ app.post('/api/devices/register', (req, res) => {
|
|||||||
|
|
||||||
const devices = readDevices();
|
const devices = readDevices();
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
|
const clientIp = sanitizeIp(requestIp.getClientIp(req) || req.ip || '');
|
||||||
|
const location = lookupLocation(clientIp);
|
||||||
|
|
||||||
let existing = devices.find(d => d.deviceId === trimmedId);
|
let existing = devices.find(d => d.deviceId === trimmedId);
|
||||||
|
let isNew = false;
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
|
const tokenParts = generateTokenParts();
|
||||||
existing = {
|
existing = {
|
||||||
deviceId: trimmedId,
|
deviceId: trimmedId,
|
||||||
alias: '',
|
alias: '',
|
||||||
@@ -90,9 +331,18 @@ app.post('/api/devices/register', (req, res) => {
|
|||||||
lastSeen: now,
|
lastSeen: now,
|
||||||
blocked: false,
|
blocked: false,
|
||||||
notes: '',
|
notes: '',
|
||||||
installs: 1
|
installs: 1,
|
||||||
|
ip: clientIp,
|
||||||
|
country: location.country,
|
||||||
|
verification: {
|
||||||
|
clientPart: tokenParts.clientPart,
|
||||||
|
adminPart: tokenParts.adminPart,
|
||||||
|
status: 'pending',
|
||||||
|
createdAt: now
|
||||||
|
}
|
||||||
};
|
};
|
||||||
devices.push(existing);
|
devices.push(existing);
|
||||||
|
isNew = true;
|
||||||
} else {
|
} else {
|
||||||
existing.deviceName = deviceName || existing.deviceName;
|
existing.deviceName = deviceName || existing.deviceName;
|
||||||
existing.model = model || existing.model;
|
existing.model = model || existing.model;
|
||||||
@@ -102,10 +352,34 @@ app.post('/api/devices/register', (req, res) => {
|
|||||||
existing.appVersionCode = appVersionCode || existing.appVersionCode;
|
existing.appVersionCode = appVersionCode || existing.appVersionCode;
|
||||||
existing.lastSeen = now;
|
existing.lastSeen = now;
|
||||||
existing.installs = (existing.installs || 1) + 1;
|
existing.installs = (existing.installs || 1) + 1;
|
||||||
|
existing.ip = clientIp || existing.ip;
|
||||||
|
const hasValidCountry = location.country && location.country !== 'N/A';
|
||||||
|
existing.country = hasValidCountry ? location.country : (existing.country || 'N/A');
|
||||||
|
existing.verification = existing.verification || {
|
||||||
|
...generateTokenParts(),
|
||||||
|
status: 'pending',
|
||||||
|
createdAt: now
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const verificationRequired = !existing.verification || existing.verification.status !== 'verified';
|
||||||
|
const blocked = existing.blocked || verificationRequired;
|
||||||
|
|
||||||
writeDevices(devices);
|
writeDevices(devices);
|
||||||
res.json({ blocked: existing.blocked, device: existing });
|
|
||||||
|
if (isNew || verificationRequired) {
|
||||||
|
sendTelegramNotification(formatTelegramMessage(existing, verificationRequired));
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
blocked,
|
||||||
|
device: existing,
|
||||||
|
message: verificationRequired ? 'Instalación pendiente de verificación.' : (existing.notes || ''),
|
||||||
|
verification: {
|
||||||
|
required: verificationRequired,
|
||||||
|
clientTokenPart: verificationRequired ? existing.verification.clientPart : ''
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/api/devices/:deviceId/block', (req, res) => {
|
app.post('/api/devices/:deviceId/block', (req, res) => {
|
||||||
@@ -135,6 +409,30 @@ app.post('/api/devices/:deviceId/unblock', (req, res) => {
|
|||||||
res.json({ blocked: false, device: existing });
|
res.json({ blocked: false, device: existing });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.post('/api/devices/:deviceId/verify', (req, res) => {
|
||||||
|
const { deviceId } = req.params;
|
||||||
|
const { clientTokenPart, adminTokenPart } = req.body || {};
|
||||||
|
const devices = readDevices();
|
||||||
|
const existing = devices.find(d => d.deviceId === deviceId);
|
||||||
|
if (!existing || !existing.verification) {
|
||||||
|
return res.status(404).json({ error: 'Device not found' });
|
||||||
|
}
|
||||||
|
if (!clientTokenPart || !adminTokenPart) {
|
||||||
|
return res.status(400).json({ error: 'Both token parts are required' });
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
existing.verification.clientPart !== clientTokenPart.trim() ||
|
||||||
|
existing.verification.adminPart !== adminTokenPart.trim()
|
||||||
|
) {
|
||||||
|
return res.status(400).json({ error: 'Invalid token parts' });
|
||||||
|
}
|
||||||
|
existing.verification.status = 'verified';
|
||||||
|
existing.verification.verifiedAt = new Date().toISOString();
|
||||||
|
existing.blocked = false;
|
||||||
|
writeDevices(devices);
|
||||||
|
res.json({ verified: true, device: existing });
|
||||||
|
});
|
||||||
|
|
||||||
app.put('/api/devices/:deviceId/alias', (req, res) => {
|
app.put('/api/devices/:deviceId/alias', (req, res) => {
|
||||||
const { deviceId } = req.params;
|
const { deviceId } = req.params;
|
||||||
const { alias } = req.body || {};
|
const { alias } = req.body || {};
|
||||||
@@ -162,4 +460,5 @@ app.delete('/api/devices/:deviceId', (req, res) => {
|
|||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
ensureDataFile();
|
ensureDataFile();
|
||||||
console.log(`StreamPlayer dashboard server listening on port ${PORT}`);
|
console.log(`StreamPlayer dashboard server listening on port ${PORT}`);
|
||||||
|
startTelegramPolling();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"versionCode": 91010,
|
"versionCode": 94300,
|
||||||
"versionName": "9.1.1",
|
"versionName": "9.4.3",
|
||||||
"minSupportedVersionCode": 91000,
|
"minSupportedVersionCode": 91000,
|
||||||
"forceUpdate": false,
|
"forceUpdate": false,
|
||||||
"downloadUrl": "https://gitea.cbcren.online/renato97/app/releases/download/v9.1.1/StreamPlayer-v9.1.1.apk",
|
"downloadUrl": "https://gitea.cbcren.online/renato97/app/releases/download/v9.4.3/StreamPlayer-v9.4.3.apk",
|
||||||
"fileName": "StreamPlayer-v9.1.1.apk",
|
"fileName": "StreamPlayer-v9.4.3.apk",
|
||||||
"sizeBytes": 5940765,
|
"sizeBytes": 5947283,
|
||||||
"notes": "StreamPlayer v9.1.1 - Device Registry and Remote Blocking\n\nNovedades principales:\n- Device Registry para gestión remota de dispositivos\n- Dashboard web para monitoreo y bloqueo de dispositivos\n- Bloqueo remoto con control administrativo\n- Sistema de alias y notas para dispositivos\n- Mejoras en seguridad y control de acceso\n- Panel de control en tiempo real\n\nEsta versión incluye importantes mejoras de seguridad y permite un control centralizado sobre los dispositivos donde está instalada la aplicación."
|
"notes": "StreamPlayer v9.4.3\n\nNovedades destacadas:\n\n- Botón \"Actualizar ahora\" en la sección de Eventos para refrescar manualmente la grilla.\n- Sincronización silenciosa cada 60 minutos para precargar nuevos eventos sin interrumpir la reproducción.\n- Cache persiste y se aplica automáticamente al reiniciar la app para asegurar datos frescos.\n- Cierre completo al salir para garantizar que cada inicio recargue con la última información disponible.\n- Correcciones y mejoras generales de estabilidad en la sección de eventos."
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user