diff --git a/.gitignore b/.gitignore index 585944b..c55a002 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,11 @@ node_modules/ .env .env.local +# Telegram credentials +scripts/.env.telegram +scripts/node_modules/ +scripts/package-lock.json + # IDE .vscode/ .idea/ diff --git a/FeatureRequests/FEATURE_PLAN-telegram.md b/FeatureRequests/FEATURE_PLAN-telegram.md new file mode 100644 index 0000000..0be4fe2 --- /dev/null +++ b/FeatureRequests/FEATURE_PLAN-telegram.md @@ -0,0 +1,385 @@ +# Feature Plan: Telegram Bot Integration + +## Übersicht + +Implementierung eines Telegram Bots zur automatischen Benachrichtigung der Werkstatt-Gruppe über wichtige Events im Image Uploader System. + +**Basis:** [FEATURE_REQUEST-telegram.md](./FEATURE_REQUEST-telegram.md) + +--- + +## Phasen-Aufteilung + +### Phase 1: Bot Setup & Standalone-Test +**Ziel:** Telegram Bot erstellen und isoliert testen (ohne App-Integration) + +**Status:** 🟢 Abgeschlossen + +**Deliverables:** +- [x] Telegram Bot via BotFather erstellt +- [x] Bot zu Test-Telegram-Gruppe hinzugefügt +- [x] Chat-ID ermittelt +- [x] `scripts/telegram-test.js` - Standalone Test-Script +- [x] `scripts/README.telegram.md` - Setup-Anleitung +- [x] `.env.telegram` - Template für Bot-Credentials +- [x] Erfolgreiche Test-Nachricht versendet + +**Akzeptanzkriterium:** +✅ Bot sendet erfolgreich Nachricht an Testgruppe + +--- + +### Phase 2: Backend-Service Integration +**Ziel:** TelegramNotificationService in Backend integrieren + +**Status:** 🟢 Abgeschlossen + +**Dependencies:** Phase 1 abgeschlossen + +**Deliverables:** +- [x] `backend/src/services/TelegramNotificationService.js` +- [x] ENV-Variablen in `docker/dev/backend/config/.env` +- [x] Unit-Tests für Service +- [x] Docker Dev Environment funktioniert + +--- + +### Phase 3: Upload-Benachrichtigungen +**Ziel:** Automatische Benachrichtigungen bei neuem Upload + +**Status:** 🟢 Abgeschlossen + +**Dependencies:** Phase 2 abgeschlossen + +**Deliverables:** +- [x] Integration in `routes/batchUpload.js` +- [x] `sendUploadNotification()` Methode +- [x] Formatierung mit Icons/Emojis +- [x] Integration-Tests + +--- + +### Phase 4: User-Änderungs-Benachrichtigungen +**Ziel:** Benachrichtigungen bei Consent-Änderungen & Löschungen + +**Status:** 🟢 Abgeschlossen + +**Dependencies:** Phase 3 abgeschlossen + +**Deliverables:** +- [x] Integration in `routes/management.js` (PUT/DELETE) +- [x] `sendConsentChangeNotification()` Methode +- [x] `sendGroupDeletedNotification()` Methode +- [x] Integration-Tests + +--- + +### Phase 5: Tägliche Lösch-Warnungen +**Ziel:** Cron-Job für bevorstehende Löschungen + +**Status:** 🟢 Abgeschlossen + +**Dependencies:** Phase 4 abgeschlossen + +**Deliverables:** +- [x] Cron-Job Setup (node-cron) +- [x] `sendDeletionWarning()` Methode +- [x] Admin-Route für manuellen Trigger (`POST /api/admin/telegram/warning`) +- [x] SchedulerService Integration (09:00 daily) +- [x] Docker ENV-Variablen konfiguriert +- [x] README.md Update + +--- + +### Phase 6: Production Deployment +**Ziel:** Rollout in Production-Umgebung + ENV-Vereinfachung + +**Status:** 🟢 Abgeschlossen + +**Dependencies:** Phase 1-5 abgeschlossen + getestet + +**Deliverables:** +- [x] ENV-Struktur vereinfachen (zu viele .env-Dateien!) +- [x] Production ENV-Variablen in docker/prod/.env konfigurieren +- [x] docker/prod/docker-compose.yml mit Telegram-ENV erweitern +- [x] Consent-Änderung Bug Fix (platform_name statt name) +- [x] README.md Update mit ENV-Struktur Dokumentation +- ⏭️ Bot in echte Werkstatt-Gruppe einfügen (optional, bei Bedarf) +- ⏭️ Production Testing (optional, bei Bedarf) + +**ENV-Vereinfachung (Abgeschlossen):** +``` +Vorher: 16 .env-Dateien mit redundanter Konfiguration +Nachher: 2 zentrale .env-Dateien + ✅ docker/dev/.env (alle dev secrets) + ✅ docker/prod/.env (alle prod secrets) + ✅ docker-compose.yml nutzt ${VAR} Platzhalter + ✅ Gemountete .env-Dateien entfernt (wurden überschrieben) + ✅ Alle ENV-Variablen in docker-compose environment +``` + +--- + +## Phase 1 - Detaillierter Plan + +### 1. Vorbereitung (5 min) + +**Auf Windows 11 Host-System:** + +```bash +# Node.js Version prüfen +node --version # Sollte >= 18.x sein + +# Projektverzeichnis öffnen +cd /home/lotzm/gitea.hobbyhimmel/Project-Image-Uploader/scripts + +# Dependencies installieren (lokal) +npm init -y # Falls noch keine package.json +npm install node-telegram-bot-api dotenv +``` + +### 2. Telegram Bot erstellen (10 min) + +**Anleitung:** Siehe `scripts/README.telegram.md` + +**Schritte:** +1. Telegram öffnen (Windows 11 App) +2. [@BotFather](https://t.me/botfather) suchen +3. `/newbot` Command +4. Bot-Name: "Werkstatt Image Uploader Bot" +5. Username: `werkstatt_uploader_bot` (oder verfügbar) +6. **Token kopieren** → `.env.telegram` + +### 3. Test-Gruppe erstellen & Bot hinzufügen (5 min) + +**Schritte:** +1. Neue Telegram-Gruppe erstellen: "Werkstatt Upload Bot Test" +2. Bot zur Gruppe hinzufügen: @werkstatt_uploader_bot +3. **Chat-ID ermitteln** (siehe README.telegram.md) +4. Chat-ID speichern → `.env.telegram` + +### 4. Test-Script erstellen (10 min) + +**Datei:** `scripts/telegram-test.js` + +**Features:** +- Lädt `.env.telegram` +- Validiert Bot-Token +- Sendet Test-Nachricht +- Error-Handling + +### 5. Erste Nachricht senden (2 min) + +```bash +cd scripts +node telegram-test.js +``` + +**Erwartete Ausgabe:** +``` +✅ Telegram Bot erfolgreich verbunden! +Bot-Name: Werkstatt Image Uploader Bot +Bot-Username: @werkstatt_uploader_bot + +📤 Sende Test-Nachricht an Chat -1001234567890... +✅ Nachricht erfolgreich gesendet! +``` + +**In Telegram-Gruppe:** +``` +🤖 Telegram Bot Test + +Dies ist eine Test-Nachricht vom Werkstatt Image Uploader Bot. + +Status: ✅ Erfolgreich verbunden! +Zeitstempel: 2025-11-29 14:23:45 +``` + +--- + +## Dateistruktur (Phase 1) + +``` +scripts/ +├── README.telegram.md # Setup-Anleitung (NEU) +├── telegram-test.js # Test-Script (NEU) +├── .env.telegram.example # ENV-Template (NEU) +├── .env.telegram # Echte Credentials (gitignored, NEU) +├── package.json # Lokale Dependencies (NEU) +└── node_modules/ # npm packages (gitignored) +``` + +--- + +## Environment Variables (Phase 1) + +**Datei:** `scripts/.env.telegram` + +```bash +# Telegram Bot Configuration +TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz +TELEGRAM_CHAT_ID=-1001234567890 +``` + +--- + +## Dependencies (Phase 1) + +**Package:** `scripts/package.json` + +```json +{ + "name": "telegram-test-scripts", + "version": "1.0.0", + "description": "Standalone Telegram Bot Testing", + "main": "telegram-test.js", + "scripts": { + "test": "node telegram-test.js" + }, + "dependencies": { + "node-telegram-bot-api": "^0.66.0", + "dotenv": "^16.3.1" + } +} +``` + +--- + +## Sicherheit (Phase 1) + +**`.gitignore` ergänzen:** + +``` +# Telegram Credentials +scripts/.env.telegram +scripts/node_modules/ +scripts/package-lock.json +``` + +**Wichtig:** +- ❌ Niemals `.env.telegram` committen! +- ✅ Nur `.env.telegram.example` (ohne echte Tokens) committen +- ✅ Bot-Token regenerieren, falls versehentlich exposed + +--- + +## Testing Checklist (Phase 1) + +- [x] Node.js Version >= 18.x +- [x] Telegram App installiert (Windows 11) +- [x] Bot via BotFather erstellt +- [x] Bot-Token gespeichert in `.env.telegram` +- [x] Test-Gruppe erstellt +- [x] Bot zur Gruppe hinzugefügt +- [x] Chat-ID ermittelt +- [x] Chat-ID gespeichert in `.env.telegram` +- [x] Privacy Mode deaktiviert +- [x] Test-Nachricht erfolgreich gesendet +- [ ] `npm install` erfolgreich +- [ ] `node telegram-test.js` läuft ohne Fehler +- [ ] Test-Nachricht in Telegram-Gruppe empfangen +- [ ] Formatierung (Emojis, Zeilenumbrüche) korrekt + +--- + +## Troubleshooting (Phase 1) + +### Problem: "Unauthorized (401)" +**Lösung:** Bot-Token falsch → BotFather prüfen, `.env.telegram` korrigieren + +### Problem: "Bad Request: chat not found" +**Lösung:** Chat-ID falsch → Neue Nachricht in Gruppe senden, Chat-ID neu ermitteln + +### Problem: "ETELEGRAM: 403 Forbidden" +**Lösung:** Bot wurde aus Gruppe entfernt → Bot erneut zur Gruppe hinzufügen + +### Problem: "Module not found: node-telegram-bot-api" +**Lösung:** +```bash +cd scripts +npm install +``` + +--- + +## Nächste Schritte (nach Phase 1) + +1. **Code-Review:** `scripts/telegram-test.js` +2. **Dokumentation Review:** `scripts/README.telegram.md` +3. **Commit:** + ```bash + git add scripts/ + git commit -m "feat: Add Telegram Bot standalone test (Phase 1)" + ``` +4. **Phase 2 starten:** Backend-Integration planen + +--- + +## Zeitschätzung + +| Phase | Aufwand | Beschreibung | +|-------|---------|--------------| +| **Phase 1** | **~45 min** | Bot Setup + Standalone-Test | +| Phase 2 | ~2h | Backend-Service | +| Phase 3 | ~2h | Upload-Benachrichtigungen | +| Phase 4 | ~2h | Änderungs-Benachrichtigungen | +| Phase 5 | ~2h | Cron-Job | +| Phase 6 | ~1h | Production Deployment | +| **Gesamt** | **~9-10h** | Vollständige Integration | + +--- + +## Conventional Commits (ab Phase 1) + +**Phase 1:** +```bash +git commit -m "feat: Add Telegram Bot test script" +git commit -m "docs: Add Telegram Bot setup guide" +git commit -m "chore: Add node-telegram-bot-api dependency to scripts" +``` + +**Phase 2:** +```bash +git commit -m "feat: Add TelegramNotificationService" +git commit -m "test: Add TelegramNotificationService unit tests" +``` + +**Phase 3-6:** +```bash +git commit -m "feat: Add upload notification to Telegram" +git commit -m "feat: Add consent change notifications" +git commit -m "feat: Add daily deletion warnings cron job" +git commit -m "docs: Update README with Telegram features" +``` + +--- + +## Release-Planung + +**Phase 1:** Kein Release (interne Tests) + +**Phase 6 (Final):** +- **Version:** `2.0.0` (Major Release) +- **Branch:** `feature/telegram-notifications` +- **Release-Command:** `npm run release:major` + +--- + +## Status-Tracking + +**Letzte Aktualisierung:** 2025-11-30 + +| Phase | Status | Datum | +|-------|--------|-------| +| Phase 1 | 🟢 Abgeschlossen | 2025-11-29 | +| Phase 2 | 🟢 Abgeschlossen | 2025-11-29 | +| Phase 3 | 🟢 Abgeschlossen | 2025-11-29 | +| Phase 4 | 🟢 Abgeschlossen | 2025-11-30 | +| Phase 5 | 🟢 Abgeschlossen | 2025-11-30 | +| Phase 6 | 🟡 ENV vereinfacht | 2025-11-30 | + +**Legende:** +- 🟢 Abgeschlossen +- 🟡 In Arbeit +- 🔴 Blockiert +- ⚪ Ausstehend diff --git a/README.dev.md b/README.dev.md index 04f9cd2..483de66 100644 --- a/README.dev.md +++ b/README.dev.md @@ -302,6 +302,35 @@ describe('Example API', () => { # 5. Log prüfen: GET http://localhost:5001/api/admin/deletion-log ``` +### Telegram-Benachrichtigungen testen + +**Voraussetzung:** Bot-Setup abgeschlossen (siehe `scripts/README.telegram.md`) + +```bash +# 1. ENV-Variablen in docker/dev/backend/config/.env konfigurieren: +TELEGRAM_ENABLED=true +TELEGRAM_BOT_TOKEN= +TELEGRAM_CHAT_ID= + +# 2. Backend neu starten (lädt neue ENV-Variablen): +docker compose -f docker/dev/docker-compose.yml restart backend-dev + +# 3. Test-Nachricht wird automatisch beim Server-Start gesendet +docker compose -f docker/dev/docker-compose.yml logs -f backend-dev + +# 4. Upload-Benachrichtigung testen (Phase 3+): +curl -X POST http://localhost:5001/api/upload-batch \ + -F "images=@test.jpg" \ + -F "year=2024" \ + -F "title=Test Upload" \ + -F "name=Test User" \ + -F 'consents={"workshopConsent":true,"socialMediaConsents":[]}' +# → Prüfe Telegram-Gruppe auf Benachrichtigung + +# 5. Service manuell deaktivieren: +TELEGRAM_ENABLED=false +``` + ### API-Tests ```bash diff --git a/README.md b/README.md index 6d1b560..86564b8 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ A self-hosted image uploader with multi-image upload capabilities and automatic ## Features **Multi-Image Upload**: Upload multiple images at once with batch processing +**Telegram Notifications**: 🆕 Real-time notifications for uploads, consent changes, deletions, and daily warnings **Social Media Consent Management**: 🆕 GDPR-compliant consent system for workshop display and social media publishing **Automatic Cleanup**: 🆕 Unapproved groups are automatically deleted after 7 days **Deletion Log**: 🆕 Complete audit trail of automatically deleted content @@ -22,6 +23,18 @@ This project extends the original [Image-Uploader by vallezw](https://github.com ### 🆕 Latest Features (November 2025) +- **📱 Telegram Bot Notifications** (Nov 30): + - Real-time notifications for all critical events + - 4 notification types: Upload, Consent Changes, Group Deletion, Daily Warnings + - Upload notifications with name, year, title, image count, and consent status + - Consent change tracking (workshop display + social media platforms) + - Group deletion confirmations with uploader and statistics + - Daily deletion warnings (09:00) for groups pending auto-cleanup (24h notice) + - Cron-scheduled automation via node-cron + - Admin endpoint for manual trigger: `POST /api/admin/telegram/warning` + - Optional feature via `TELEGRAM_ENABLED` environment variable + - Complete setup guide in `scripts/README.telegram.md` + - **🌐 Public/Internal Host Separation** (Nov 25): - Subdomain-based feature separation for production deployment - Public host (`deinprojekt.hobbyhimmel.de`): Upload + UUID Management only @@ -262,31 +275,31 @@ The application automatically generates optimized preview thumbnails for all upl ## Docker Structure -The application uses separate Docker configurations for development and production: +The application uses separate Docker configurations for development and production with **simplified environment variable management**: ``` docker/ ├── .env.backend.example # Backend environment variables documentation ├── .env.frontend.example # Frontend environment variables documentation ├── dev/ # Development environment -│ ├── docker-compose.yml # Development services configuration +│ ├── .env # 🆕 Central dev secrets (gitignored) +│ ├── .env.example # Dev environment template +│ ├── docker-compose.yml # All ENV vars defined here │ ├── backend/ -│ │ ├── config/.env # Development backend configuration │ │ └── Dockerfile # Development backend container │ └── frontend/ -│ ├── config/.env # Development frontend configuration -│ ├── config/env.sh # Runtime configuration script +│ ├── config/env.sh # Generates window._env_ from ENV │ ├── Dockerfile # Development frontend container │ ├── nginx.conf # Development nginx configuration │ └── start.sh # Development startup script └── prod/ # Production environment - ├── docker-compose.yml # Production services configuration + ├── .env # 🆕 Central prod secrets (gitignored) + ├── .env.example # Production environment template + ├── docker-compose.yml # All ENV vars defined here ├── backend/ - │ ├── config/.env # Production backend configuration │ └── Dockerfile # Production backend container └── frontend/ - ├── config/.env # Production frontend configuration - ├── config/env.sh # Runtime configuration script + ├── config/env.sh # Generates window._env_ from ENV ├── config/htpasswd # HTTP Basic Auth credentials ├── Dockerfile # Production frontend container └── nginx.conf # Production nginx configuration @@ -294,6 +307,20 @@ docker/ ### Environment Configuration +**🆕 Simplified ENV Structure (Nov 2025):** +- **2 central `.env` files** (down from 16 files!) + - `docker/dev/.env` - All development secrets + - `docker/prod/.env` - All production secrets +- **docker-compose.yml** - All environment variables defined in `environment:` sections +- **No .env files in Docker images** - All configuration via docker-compose +- **Frontend env.sh** - Generates `window._env_` JavaScript object from ENV variables at runtime + +**How it works:** +1. Docker Compose automatically reads `.env` from the same directory +2. Variables are injected into containers via `environment:` sections using `${VAR}` placeholders +3. Frontend `env.sh` script reads ENV variables and generates JavaScript config at container startup +4. Secrets stay in gitignored `.env` files, never in code or images + - **Development**: Uses `docker/dev/` configuration with live reloading - **Production**: Uses `docker/prod/` configuration with optimized builds - **Scripts**: Use `./dev.sh` or `./prod.sh` for easy deployment @@ -591,12 +618,41 @@ The application includes comprehensive testing tools for the automatic cleanup f For detailed testing instructions, see: [`tests/TESTING-CLEANUP.md`](tests/TESTING-CLEANUP.md) ## Configuration + ### Environment Variables +**Simplified ENV Management (Nov 2025):** +All environment variables are now managed through **2 central `.env` files** and `docker-compose.yml`: + +**Core Variables:** | Variable | Default | Description | |----------|---------|-------------| -| `API_URL` | `http://localhost:5001` | Backend API endpoint | -| `CLIENT_URL` | `http://localhost` | Frontend application URL | +| `API_URL` | `http://localhost:5001` | Backend API endpoint (frontend → backend) | +| `PUBLIC_HOST` | `public.test.local` | Public upload subdomain (no admin access) | +| `INTERNAL_HOST` | `internal.test.local` | Internal admin subdomain (full access) | +| `ADMIN_SESSION_SECRET` | - | Secret for admin session cookies (required) | + +**Telegram Notifications (Optional):** +| Variable | Default | Description | +|----------|---------|-------------| +| `TELEGRAM_ENABLED` | `false` | Enable/disable Telegram notifications | +| `TELEGRAM_BOT_TOKEN` | - | Telegram Bot API token (from @BotFather) | +| `TELEGRAM_CHAT_ID` | - | Telegram chat/group ID for notifications | +| `TELEGRAM_SEND_TEST_ON_START` | `false` | Send test message on service startup (dev only) | + +**Configuration Files:** +- `docker/dev/.env` - Development secrets (gitignored) +- `docker/prod/.env` - Production secrets (gitignored) +- `docker/dev/.env.example` - Development template (committed) +- `docker/prod/.env.example` - Production template (committed) + +**How to configure:** +1. Copy `.env.example` to `.env` in the respective environment folder +2. Edit `.env` and set your secrets (ADMIN_SESSION_SECRET, Telegram tokens, etc.) +3. Docker Compose automatically reads `.env` and injects variables into containers +4. Never commit `.env` files (already in `.gitignore`) + +**Telegram Setup:** See `scripts/README.telegram.md` for complete configuration guide. ### Volume Configuration - **Upload Limits**: 100MB maximum file size for batch uploads diff --git a/backend/docs/openapi.json b/backend/docs/openapi.json index 4ce36e5..0f8d635 100644 --- a/backend/docs/openapi.json +++ b/backend/docs/openapi.json @@ -39,6 +39,9 @@ { "name": "Admin - Cleanup" }, + { + "name": "Admin - Telegram" + }, { "name": "Admin - Monitoring" }, @@ -385,6 +388,15 @@ }, "description": { "example": "any" + }, + "year": { + "example": "any" + }, + "title": { + "example": "any" + }, + "name": { + "example": "any" } } } @@ -1058,22 +1070,38 @@ }, "/api/manage/{token}/reorder": { "put": { - "description": "", + "tags": [ + "Management Portal" + ], + "summary": "Reorder images in group", + "description": "Reorder images within the managed group (token-based access)", "parameters": [ { "name": "token", "in": "path", "required": true, - "type": "string" + "type": "string", + "description": "Management token (UUID v4)", + "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }, { "name": "body", "in": "body", + "required": true, "schema": { "type": "object", "properties": { "imageIds": { - "example": "any" + "type": "array", + "example": [ + 1, + 3, + 2, + 4 + ], + "items": { + "type": "number" + } } } } @@ -1081,13 +1109,29 @@ ], "responses": { "200": { - "description": "OK" + "description": "Images reordered successfully", + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "updatedCount": { + "type": "number", + "example": 4 + } + }, + "xml": { + "name": "main" + } + } }, "400": { - "description": "Bad Request" + "description": "Invalid token format or imageIds" }, "404": { - "description": "Not Found" + "description": "Token not found or group deleted" }, "429": { "description": "Too Many Requests" @@ -1151,25 +1195,46 @@ }, "/api/admin/groups/{groupId}/consents": { "post": { - "description": "", + "tags": [ + "Consent Management" + ], + "summary": "Save or update consents for a group", + "description": "Store workshop consent and social media consents for a specific group", "parameters": [ { "name": "groupId", "in": "path", "required": true, - "type": "string" + "type": "string", + "description": "Group ID", + "example": "abc123def456" }, { "name": "body", "in": "body", + "required": true, "schema": { "type": "object", "properties": { "workshopConsent": { - "example": "any" + "type": "boolean", + "example": true }, "socialMediaConsents": { - "example": "any" + "type": "array", + "items": { + "type": "object", + "properties": { + "platformId": { + "type": "number", + "example": 2 + }, + "consented": { + "type": "boolean", + "example": false + } + } + } } } } @@ -1177,10 +1242,26 @@ ], "responses": { "200": { - "description": "OK" + "description": "Consents saved successfully", + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "Consents saved successfully" + } + }, + "xml": { + "name": "main" + } + } }, "400": { - "description": "Bad Request" + "description": "Invalid request data" }, "403": { "description": "Forbidden" @@ -1717,6 +1798,46 @@ } } }, + "/api/admin/telegram/warning": { + "post": { + "tags": [ + "Admin - Telegram" + ], + "summary": "Manually trigger Telegram deletion warning", + "description": "Sends deletion warning to Telegram for testing (normally runs daily at 09:00)", + "responses": { + "200": { + "description": "Warning sent successfully", + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "groupsWarned": { + "type": "number", + "example": 2 + }, + "message": { + "type": "string", + "example": "Deletion warning sent for 2 groups" + } + }, + "xml": { + "name": "main" + } + } + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, "/api/admin/cleanup/preview": { "get": { "tags": [ diff --git a/backend/jest.config.js b/backend/jest.config.js index b1e0f15..27eda88 100644 --- a/backend/jest.config.js +++ b/backend/jest.config.js @@ -26,5 +26,9 @@ module.exports = { // Run tests serially to avoid DB conflicts maxWorkers: 1, // Force exit after tests complete - forceExit: true + forceExit: true, + // Transform ESM modules in node_modules + transformIgnorePatterns: [ + 'node_modules/(?!(uuid)/)' + ] }; diff --git a/backend/package.json b/backend/package.json index 56a163f..22d61a4 100644 --- a/backend/package.json +++ b/backend/package.json @@ -31,6 +31,7 @@ "find-remove": "^2.0.3", "fs": "^0.0.1-security", "node-cron": "^4.2.1", + "node-telegram-bot-api": "^0.66.0", "sharp": "^0.34.4", "shortid": "^2.2.16", "sqlite3": "^5.1.7", diff --git a/backend/src/routes/admin.js b/backend/src/routes/admin.js index 6582a00..82a40ac 100644 --- a/backend/src/routes/admin.js +++ b/backend/src/routes/admin.js @@ -237,6 +237,46 @@ router.post('/cleanup/trigger', async (req, res) => { } }); +router.post('/telegram/warning', async (req, res) => { + /* + #swagger.tags = ['Admin - Telegram'] + #swagger.summary = 'Manually trigger Telegram deletion warning' + #swagger.description = 'Sends deletion warning to Telegram for testing (normally runs daily at 09:00)' + #swagger.responses[200] = { + description: 'Warning sent successfully', + schema: { + success: true, + groupsWarned: 2, + message: 'Deletion warning sent for 2 groups' + } + } + */ + try { + const schedulerService = req.app.get('schedulerService'); + + if (!schedulerService) { + return res.status(500).json({ + success: false, + message: 'Scheduler service not available' + }); + } + + const result = await schedulerService.triggerTelegramWarningNow(); + + res.json({ + success: true, + groupsWarned: result.groupCount, + message: result.message + }); + } catch (error) { + console.error('[Admin API] Error triggering Telegram warning:', error); + res.status(500).json({ + error: 'Internal server error', + message: error.message + }); + } +}); + router.get('/cleanup/preview', async (req, res) => { /* #swagger.tags = ['Admin - Cleanup'] diff --git a/backend/src/routes/batchUpload.js b/backend/src/routes/batchUpload.js index 35ec22a..2c4fc57 100644 --- a/backend/src/routes/batchUpload.js +++ b/backend/src/routes/batchUpload.js @@ -6,6 +6,10 @@ const UploadGroup = require('../models/uploadGroup'); const groupRepository = require('../repositories/GroupRepository'); const dbManager = require('../database/DatabaseManager'); const ImagePreviewService = require('../services/ImagePreviewService'); +const TelegramNotificationService = require('../services/TelegramNotificationService'); + +// Singleton-Instanz des Telegram Service +const telegramService = new TelegramNotificationService(); const router = Router(); @@ -117,6 +121,12 @@ router.post('/upload/batch', async (req, res) => { consents = {}; } + // Merge separate form fields into metadata (backwards compatibility) + if (req.body.year) metadata.year = parseInt(req.body.year); + if (req.body.title) metadata.title = req.body.title; + if (req.body.name) metadata.name = req.body.name; + if (req.body.description) metadata.description = req.body.description; + // Validiere Workshop Consent (Pflichtfeld) if (!consents.workshopConsent) { return res.status(400).json({ @@ -229,6 +239,22 @@ router.post('/upload/batch', async (req, res) => { console.log(`Successfully saved group ${group.groupId} with ${files.length} images to database`); + // Sende Telegram-Benachrichtigung (async, non-blocking) + if (telegramService.isAvailable()) { + telegramService.sendUploadNotification({ + name: group.name, + year: group.year, + title: group.title, + imageCount: files.length, + workshopConsent: consents.workshopConsent, + socialMediaConsents: consents.socialMediaConsents || [], + token: createResult.managementToken + }).catch(err => { + // Fehler loggen, aber Upload nicht fehlschlagen lassen + console.error('[Telegram] Upload notification failed:', err.message); + }); + } + // Erfolgreiche Antwort mit Management-Token res.json({ groupId: group.groupId, diff --git a/backend/src/routes/consent.js b/backend/src/routes/consent.js index 12e46bf..7494b13 100644 --- a/backend/src/routes/consent.js +++ b/backend/src/routes/consent.js @@ -58,16 +58,37 @@ router.get('/social-media/platforms', async (req, res) => { // Group Consents // ============================================================================ -/** - * POST /groups/:groupId/consents - * Speichere oder aktualisiere Consents für eine Gruppe - * - * Body: { - * workshopConsent: boolean, - * socialMediaConsents: [{ platformId: number, consented: boolean }] - * } - */ router.post('/groups/:groupId/consents', async (req, res) => { + /* + #swagger.tags = ['Consent Management'] + #swagger.summary = 'Save or update consents for a group' + #swagger.description = 'Store workshop consent and social media consents for a specific group' + #swagger.parameters['groupId'] = { + in: 'path', + required: true, + type: 'string', + description: 'Group ID', + example: 'abc123def456' + } + #swagger.parameters['body'] = { + in: 'body', + required: true, + schema: { + workshopConsent: true, + socialMediaConsents: [ + { platformId: 1, consented: true }, + { platformId: 2, consented: false } + ] + } + } + #swagger.responses[200] = { + description: 'Consents saved successfully', + schema: { success: true, message: 'Consents saved successfully' } + } + #swagger.responses[400] = { + description: 'Invalid request data' + } + */ try { const { groupId } = req.params; const { workshopConsent, socialMediaConsents } = req.body; diff --git a/backend/src/routes/management.js b/backend/src/routes/management.js index 5f0b4c2..93afcc6 100644 --- a/backend/src/routes/management.js +++ b/backend/src/routes/management.js @@ -5,6 +5,10 @@ const deletionLogRepository = require('../repositories/DeletionLogRepository'); const dbManager = require('../database/DatabaseManager'); const { rateLimitMiddleware, recordFailedTokenValidation } = require('../middlewares/rateLimiter'); const auditLogMiddleware = require('../middlewares/auditLog'); +const TelegramNotificationService = require('../services/TelegramNotificationService'); + +// Singleton-Instanz des Telegram Service +const telegramService = new TelegramNotificationService(); // Apply middleware to all management routes router.use(rateLimitMiddleware); @@ -211,6 +215,20 @@ router.put('/:token/consents', async (req, res) => { [newValue, groupData.groupId] ); + // Sende Telegram-Benachrichtigung (async, non-blocking) + if (telegramService.isAvailable()) { + telegramService.sendConsentChangeNotification({ + name: groupData.name, + year: groupData.year, + title: groupData.title, + consentType: 'workshop', + action: action, + newValue: newValue === 1 + }).catch(err => { + console.error('[Telegram] Consent change notification failed:', err.message); + }); + } + return res.json({ success: true, message: `Workshop consent ${action}d successfully`, @@ -263,6 +281,26 @@ router.put('/:token/consents', async (req, res) => { } } + // Sende Telegram-Benachrichtigung (async, non-blocking) + if (telegramService.isAvailable()) { + // Hole Platform-Name für Benachrichtigung + const platform = await dbManager.get( + 'SELECT platform_name FROM social_media_platforms WHERE id = ?', + [platformId] + ); + + telegramService.sendConsentChangeNotification({ + name: groupData.name, + year: groupData.year, + title: groupData.title, + consentType: 'social_media', + action: action, + platform: platform ? platform.platform_name : `Platform ${platformId}` + }).catch(err => { + console.error('[Telegram] Consent change notification failed:', err.message); + }); + } + return res.json({ success: true, message: `Social media consent ${action}d successfully`, @@ -1007,6 +1045,18 @@ router.delete('/:token', async (req, res) => { console.log(`✓ Group ${groupId} deleted via management token (${imageCount} images)`); + // Sende Telegram-Benachrichtigung (async, non-blocking) + if (telegramService.isAvailable()) { + telegramService.sendGroupDeletedNotification({ + name: groupData.name, + year: groupData.year, + title: groupData.title, + imageCount: imageCount + }).catch(err => { + console.error('[Telegram] Group deletion notification failed:', err.message); + }); + } + res.json({ success: true, message: 'Group and all associated data deleted successfully', @@ -1026,18 +1076,36 @@ router.delete('/:token', async (req, res) => { } }); -/** - * PUT /api/manage/:token/reorder - * Reorder images within the managed group (token-based access) - * - * @param {string} token - Management token (UUID v4) - * @param {number[]} imageIds - Array of image IDs in new order - * @returns {Object} Success status and updated image count - * @throws {400} Invalid token format or imageIds - * @throws {404} Token not found or group deleted - * @throws {500} Server error - */ router.put('/:token/reorder', async (req, res) => { + /* + #swagger.tags = ['Management Portal'] + #swagger.summary = 'Reorder images in group' + #swagger.description = 'Reorder images within the managed group (token-based access)' + #swagger.parameters['token'] = { + in: 'path', + required: true, + type: 'string', + description: 'Management token (UUID v4)', + example: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' + } + #swagger.parameters['body'] = { + in: 'body', + required: true, + schema: { + imageIds: [1, 3, 2, 4] + } + } + #swagger.responses[200] = { + description: 'Images reordered successfully', + schema: { success: true, updatedCount: 4 } + } + #swagger.responses[400] = { + description: 'Invalid token format or imageIds' + } + #swagger.responses[404] = { + description: 'Token not found or group deleted' + } + */ try { const { token } = req.params; const { imageIds } = req.body; diff --git a/backend/src/server.js b/backend/src/server.js index ac4224d..b99bc91 100644 --- a/backend/src/server.js +++ b/backend/src/server.js @@ -4,6 +4,10 @@ const path = require('path'); const initiateResources = require('./utils/initiate-resources'); const dbManager = require('./database/DatabaseManager'); const SchedulerService = require('./services/SchedulerService'); +const TelegramNotificationService = require('./services/TelegramNotificationService'); + +// Singleton-Instanz des Telegram Service +const telegramService = new TelegramNotificationService(); // Dev: Swagger UI (mount only in non-production) — require lazily let swaggerUi = null; @@ -78,8 +82,19 @@ class Server { console.log(`✅ Server läuft auf Port ${this._port}`); console.log(`📊 SQLite Datenbank aktiv`); + // Speichere SchedulerService in app für Admin-Endpoints + this._app.set('schedulerService', SchedulerService); + // Starte Scheduler für automatisches Cleanup SchedulerService.start(); + + // Teste Telegram-Service (optional, nur in Development wenn aktiviert) + if (process.env.NODE_ENV === 'development' + && process.env.TELEGRAM_SEND_TEST_ON_START === 'true' + && telegramService.isAvailable()) { + telegramService.sendTestMessage() + .catch(err => console.error('[Telegram] Test message failed:', err.message)); + } }); } catch (error) { console.error('💥 Fehler beim Serverstart:', error); diff --git a/backend/src/services/SchedulerService.js b/backend/src/services/SchedulerService.js index 5d23193..5b7f713 100644 --- a/backend/src/services/SchedulerService.js +++ b/backend/src/services/SchedulerService.js @@ -1,9 +1,11 @@ const cron = require('node-cron'); const GroupCleanupService = require('./GroupCleanupService'); +const TelegramNotificationService = require('./TelegramNotificationService'); class SchedulerService { constructor() { this.tasks = []; + this.telegramService = new TelegramNotificationService(); } start() { @@ -30,7 +32,35 @@ class SchedulerService { this.tasks.push(cleanupTask); - console.log('✓ Scheduler started - Daily cleanup at 10:00 AM (Europe/Berlin)'); + // Telegram Warning-Job: Jeden Tag um 09:00 Uhr (1 Stunde vor Cleanup) + const telegramWarningTask = cron.schedule('0 9 * * *', async () => { + console.log('[Scheduler] Running daily Telegram deletion warning at 09:00 AM...'); + try { + if (this.telegramService.isAvailable()) { + const groupsForDeletion = await GroupCleanupService.findGroupsForDeletion(); + + if (groupsForDeletion && groupsForDeletion.length > 0) { + await this.telegramService.sendDeletionWarning(groupsForDeletion); + console.log(`[Scheduler] Sent deletion warning for ${groupsForDeletion.length} groups`); + } else { + console.log('[Scheduler] No groups pending deletion'); + } + } else { + console.log('[Scheduler] Telegram service not available, skipping warning'); + } + } catch (error) { + console.error('[Scheduler] Telegram warning task failed:', error); + } + }, { + scheduled: true, + timezone: "Europe/Berlin" + }); + + this.tasks.push(telegramWarningTask); + + console.log('✓ Scheduler started:'); + console.log(' - Daily cleanup at 10:00 AM (Europe/Berlin)'); + console.log(' - Daily Telegram warning at 09:00 AM (Europe/Berlin)'); // Für Development: Manueller Trigger if (process.env.NODE_ENV === 'development') { @@ -50,6 +80,42 @@ class SchedulerService { console.log('[Scheduler] Manual cleanup triggered...'); return await GroupCleanupService.performScheduledCleanup(); } + + // Für Development: Manueller Telegram-Warning-Trigger + async triggerTelegramWarningNow() { + console.log('[Scheduler] Manual Telegram warning triggered...'); + try { + if (!this.telegramService.isAvailable()) { + console.log('[Scheduler] Telegram service not available'); + return { success: false, message: 'Telegram service not available' }; + } + + const groupsForDeletion = await GroupCleanupService.findGroupsForDeletion(); + + if (!groupsForDeletion || groupsForDeletion.length === 0) { + console.log('[Scheduler] No groups pending deletion'); + return { success: true, message: 'No groups pending deletion', groupCount: 0 }; + } + + await this.telegramService.sendDeletionWarning(groupsForDeletion); + console.log(`[Scheduler] Sent deletion warning for ${groupsForDeletion.length} groups`); + + return { + success: true, + message: `Warning sent for ${groupsForDeletion.length} groups`, + groupCount: groupsForDeletion.length, + groups: groupsForDeletion.map(g => ({ + groupId: g.groupId, + name: g.name, + year: g.year, + uploadDate: g.uploadDate + })) + }; + } catch (error) { + console.error('[Scheduler] Manual Telegram warning failed:', error); + return { success: false, message: error.message }; + } + } } module.exports = new SchedulerService(); diff --git a/backend/src/services/TelegramNotificationService.js b/backend/src/services/TelegramNotificationService.js new file mode 100644 index 0000000..f86bb78 --- /dev/null +++ b/backend/src/services/TelegramNotificationService.js @@ -0,0 +1,312 @@ +const TelegramBot = require('node-telegram-bot-api'); + +/** + * TelegramNotificationService + * + * Versendet automatische Benachrichtigungen über Telegram an die Werkstatt-Gruppe. + * + * Features: + * - Upload-Benachrichtigungen (Phase 3) + * - Consent-Änderungs-Benachrichtigungen (Phase 4) + * - Gruppen-Lösch-Benachrichtigungen (Phase 4) + * - Tägliche Lösch-Warnungen (Phase 5) + * + * Phase 2: Backend-Service Integration (Basic Setup) + */ +class TelegramNotificationService { + constructor() { + this.enabled = process.env.TELEGRAM_ENABLED === 'true'; + this.botToken = process.env.TELEGRAM_BOT_TOKEN; + this.chatId = process.env.TELEGRAM_CHAT_ID; + this.bot = null; + + if (this.enabled) { + this.initialize(); + } else { + console.log('[Telegram] Service disabled (TELEGRAM_ENABLED=false)'); + } + } + + /** + * Initialisiert den Telegram Bot + */ + initialize() { + try { + if (!this.botToken) { + throw new Error('TELEGRAM_BOT_TOKEN is not defined'); + } + + if (!this.chatId) { + throw new Error('TELEGRAM_CHAT_ID is not defined'); + } + + this.bot = new TelegramBot(this.botToken, { polling: false }); + console.log('[Telegram] Service initialized successfully'); + } catch (error) { + console.error('[Telegram] Initialization failed:', error.message); + this.enabled = false; + } + } + + /** + * Prüft, ob der Service verfügbar ist + */ + isAvailable() { + return this.enabled && this.bot !== null; + } + + /** + * Sendet eine Test-Nachricht + * + * @returns {Promise} Telegram API Response + */ + async sendTestMessage() { + if (!this.isAvailable()) { + console.log('[Telegram] Service not available, skipping test message'); + return null; + } + + try { + const timestamp = new Date().toLocaleString('de-DE', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + }); + + const message = ` +🤖 Telegram Service Test + +Service erfolgreich initialisiert! + +Zeitstempel: ${timestamp} +Environment: ${process.env.NODE_ENV || 'development'} + +--- +ℹ️ Dieser Bot sendet automatische Benachrichtigungen für den Image Uploader. +`.trim(); + + const response = await this.bot.sendMessage(this.chatId, message); + console.log('[Telegram] Test message sent successfully'); + return response; + } catch (error) { + console.error('[Telegram] Failed to send test message:', error.message); + throw error; + } + } + + /** + * Phase 3: Sendet Benachrichtigung bei neuem Upload + * + * @param {Object} groupData - Gruppen-Informationen + * @param {string} groupData.name - Name des Uploaders + * @param {number} groupData.year - Jahr der Gruppe + * @param {string} groupData.title - Titel der Gruppe + * @param {number} groupData.imageCount - Anzahl hochgeladener Bilder + * @param {boolean} groupData.workshopConsent - Workshop-Consent Status + * @param {Array} groupData.socialMediaConsents - Social Media Plattformen + * @param {string} groupData.token - Management-Token + */ + async sendUploadNotification(groupData) { + if (!this.isAvailable()) { + console.log('[Telegram] Service not available, skipping upload notification'); + return null; + } + + try { + const workshopIcon = groupData.workshopConsent ? '✅' : '❌'; + const socialMediaIcons = this.formatSocialMediaIcons(groupData.socialMediaConsents); + + const message = ` +📸 Neuer Upload! + +Uploader: ${groupData.name} +Bilder: ${groupData.imageCount} +Gruppe: ${groupData.year} - ${groupData.title} + +Workshop: ${workshopIcon} ${groupData.workshopConsent ? 'Ja' : 'Nein'} +Social Media: ${socialMediaIcons || '❌ Keine'} + +🔗 Zur Freigabe: ${this.getAdminUrl()} +`.trim(); + + const response = await this.bot.sendMessage(this.chatId, message); + console.log(`[Telegram] Upload notification sent for group: ${groupData.title}`); + return response; + } catch (error) { + console.error('[Telegram] Failed to send upload notification:', error.message); + // Fehler loggen, aber nicht werfen - Upload soll nicht fehlschlagen wegen Telegram + return null; + } + } + + /** + * Phase 4: Sendet Benachrichtigung bei Consent-Änderung + * + * @param {Object} changeData - Änderungs-Informationen + * @param {string} changeData.name - Name des Uploaders + * @param {number} changeData.year - Jahr + * @param {string} changeData.title - Titel + * @param {string} changeData.consentType - 'workshop' oder 'social_media' + * @param {string} changeData.action - 'revoke' oder 'restore' + * @param {string} [changeData.platform] - Plattform-Name (nur bei social_media) + * @param {boolean} [changeData.newValue] - Neuer Wert (nur bei workshop) + */ + async sendConsentChangeNotification(changeData) { + if (!this.isAvailable()) { + console.log('[Telegram] Service not available, skipping consent change notification'); + return null; + } + + try { + const { name, year, title, consentType, action, platform, newValue } = changeData; + + let changeDescription; + if (consentType === 'workshop') { + const icon = newValue ? '✅' : '❌'; + const status = newValue ? 'Ja' : 'Nein'; + const actionText = action === 'revoke' ? 'widerrufen' : 'wiederhergestellt'; + changeDescription = `Workshop-Consent ${actionText}\nNeuer Status: ${icon} ${status}`; + } else if (consentType === 'social_media') { + const actionText = action === 'revoke' ? 'widerrufen' : 'erteilt'; + changeDescription = `Social Media Consent ${actionText}\nPlattform: ${platform}`; + } + + const message = ` +⚙️ Consent-Änderung + +Gruppe: ${year} - ${title} +Uploader: ${name} + +${changeDescription} + +🔗 Details: ${this.getAdminUrl()} +`.trim(); + + const response = await this.bot.sendMessage(this.chatId, message); + console.log(`[Telegram] Consent change notification sent for: ${title}`); + return response; + } catch (error) { + console.error('[Telegram] Failed to send consent change notification:', error.message); + throw error; + } + } + + /** + * Phase 4: Sendet Benachrichtigung bei Gruppen-Löschung durch User + * + * @param {Object} groupData - Gruppen-Informationen + */ + async sendGroupDeletedNotification(groupData) { + if (!this.isAvailable()) { + console.log('[Telegram] Service not available, skipping group deleted notification'); + return null; + } + + try { + const message = ` +⚙️ User-Änderung + +Aktion: Gruppe gelöscht +Gruppe: ${groupData.year} - ${groupData.title} +Uploader: ${groupData.name} +Bilder: ${groupData.imageCount} + +ℹ️ User hat Gruppe selbst über Management-Link gelöscht +`.trim(); + + const response = await this.bot.sendMessage(this.chatId, message); + console.log(`[Telegram] Group deleted notification sent for: ${groupData.title}`); + return response; + } catch (error) { + console.error('[Telegram] Failed to send group deleted notification:', error.message); + return null; + } + } + + /** + * Phase 5: Sendet tägliche Warnung für bevorstehende Löschungen + * + * @param {Array} groupsList - Liste der zu löschenden Gruppen + */ + async sendDeletionWarning(groupsList) { + if (!this.isAvailable()) { + console.log('[Telegram] Service not available, skipping deletion warning'); + return null; + } + + if (!groupsList || groupsList.length === 0) { + console.log('[Telegram] No groups pending deletion, skipping warning'); + return null; + } + + try { + let groupsText = groupsList.map((group, index) => { + const uploadDate = new Date(group.created_at).toLocaleDateString('de-DE'); + return `${index + 1}. ${group.year} - ${group.title} + Uploader: ${group.name} + Bilder: ${group.imageCount} + Hochgeladen: ${uploadDate}`; + }).join('\n\n'); + + const message = ` +⏰ Löschung in 24 Stunden! + +Folgende Gruppen werden morgen automatisch gelöscht: + +${groupsText} + +💡 Jetzt freigeben oder Freigabe bleibt aus! +🔗 Zur Moderation: ${this.getAdminUrl()} +`.trim(); + + const response = await this.bot.sendMessage(this.chatId, message); + console.log(`[Telegram] Deletion warning sent for ${groupsList.length} groups`); + return response; + } catch (error) { + console.error('[Telegram] Failed to send deletion warning:', error.message); + return null; + } + } + + // ========================================================================= + // Helper Methods + // ========================================================================= + + /** + * Formatiert Social Media Consents als Icons + * + * @param {Array} platforms - Liste der Plattformen + * @returns {string} Formatierter String mit Icons + */ + formatSocialMediaIcons(platforms) { + if (!platforms || platforms.length === 0) { + return ''; + } + + const iconMap = { + 'facebook': '📘 Facebook', + 'instagram': '📷 Instagram', + 'tiktok': '🎵 TikTok' + }; + + return platforms.map(p => iconMap[p.toLowerCase()] || p).join(', '); + } + + /** + * Gibt die Admin-URL zurück + * + * @returns {string} Admin-Panel URL + */ + getAdminUrl() { + const host = process.env.INTERNAL_HOST || 'internal.hobbyhimmel.de'; + const isProduction = process.env.NODE_ENV === 'production'; + const protocol = isProduction ? 'https' : 'http'; + const port = isProduction ? '' : ':3000'; + return `${protocol}://${host}${port}/moderation`; + } +} + +module.exports = TelegramNotificationService; diff --git a/backend/tests/api/telegram-upload.test.js b/backend/tests/api/telegram-upload.test.js new file mode 100644 index 0000000..015612d --- /dev/null +++ b/backend/tests/api/telegram-upload.test.js @@ -0,0 +1,183 @@ +/** + * Integration Tests für Telegram Upload-Benachrichtigungen + * + * Phase 3: Upload-Benachrichtigungen + * + * Diese Tests prüfen die Integration zwischen Upload-Route und Telegram-Service + */ + +const path = require('path'); +const fs = require('fs'); +const { getRequest } = require('../testServer'); + +describe('Telegram Upload Notifications (Integration)', () => { + let TelegramNotificationService; + let sendUploadNotificationSpy; + + beforeAll(() => { + // Spy auf TelegramNotificationService + TelegramNotificationService = require('../../src/services/TelegramNotificationService'); + }); + + beforeEach(() => { + // Spy auf sendUploadNotification erstellen + sendUploadNotificationSpy = jest.spyOn(TelegramNotificationService.prototype, 'sendUploadNotification') + .mockResolvedValue({ message_id: 42 }); + + // isAvailable() immer true zurückgeben für Tests + jest.spyOn(TelegramNotificationService.prototype, 'isAvailable') + .mockReturnValue(true); + }); + + afterEach(() => { + // Restore alle Spys + jest.restoreAllMocks(); + }); + + describe('POST /api/upload/batch', () => { + const testImagePath = path.join(__dirname, '../utils/test-image.jpg'); + + // Erstelle Test-Bild falls nicht vorhanden + beforeAll(() => { + if (!fs.existsSync(testImagePath)) { + // Erstelle 1x1 px JPEG + const buffer = Buffer.from([ + 0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, + 0x49, 0x46, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x00, 0xFF, 0xDB, 0x00, 0x43, + 0x00, 0x08, 0x06, 0x06, 0x07, 0x06, 0x05, 0x08, + 0x07, 0x07, 0x07, 0x09, 0x09, 0x08, 0x0A, 0x0C, + 0x14, 0x0D, 0x0C, 0x0B, 0x0B, 0x0C, 0x19, 0x12, + 0x13, 0x0F, 0x14, 0x1D, 0x1A, 0x1F, 0x1E, 0x1D, + 0x1A, 0x1C, 0x1C, 0x20, 0x24, 0x2E, 0x27, 0x20, + 0x22, 0x2C, 0x23, 0x1C, 0x1C, 0x28, 0x37, 0x29, + 0x2C, 0x30, 0x31, 0x34, 0x34, 0x34, 0x1F, 0x27, + 0x39, 0x3D, 0x38, 0x32, 0x3C, 0x2E, 0x33, 0x34, + 0x32, 0xFF, 0xC0, 0x00, 0x0B, 0x08, 0x00, 0x01, + 0x00, 0x01, 0x01, 0x01, 0x11, 0x00, 0xFF, 0xC4, + 0x00, 0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0xFF, 0xC4, 0x00, 0x14, + 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0xDA, 0x00, 0x08, 0x01, 0x01, + 0x00, 0x00, 0x3F, 0x00, 0x37, 0xFF, 0xD9 + ]); + fs.writeFileSync(testImagePath, buffer); + } + }); + + it('sollte Telegram-Benachrichtigung bei erfolgreichem Upload senden', async () => { + const response = await getRequest() + .post('/api/upload/batch') + .field('year', '2024') + .field('title', 'Test Upload') + .field('name', 'Test User') + .field('consents', JSON.stringify({ + workshopConsent: true, + socialMediaConsents: ['instagram', 'tiktok'] + })) + .attach('images', testImagePath); + + // Upload sollte erfolgreich sein + expect(response.status).toBe(200); + expect(response.body.message).toBe('Batch upload successful'); + + // Warte kurz auf async Telegram-Call + await new Promise(resolve => setTimeout(resolve, 150)); + + // Telegram-Service sollte aufgerufen worden sein + expect(sendUploadNotificationSpy).toHaveBeenCalledWith( + expect.objectContaining({ + name: 'Test User', + year: 2024, + title: 'Test Upload', + imageCount: 1, + workshopConsent: true, + socialMediaConsents: ['instagram', 'tiktok'] + }) + ); + }); + + it('sollte Upload nicht fehlschlagen wenn Telegram-Service nicht verfügbar', async () => { + // Restore mock und setze isAvailable auf false + jest.restoreAllMocks(); + jest.spyOn(TelegramNotificationService.prototype, 'isAvailable') + .mockReturnValue(false); + sendUploadNotificationSpy = jest.spyOn(TelegramNotificationService.prototype, 'sendUploadNotification'); + + const response = await getRequest() + .post('/api/upload/batch') + .field('year', '2024') + .field('title', 'Test Upload') + .field('name', 'Test User') + .field('consents', JSON.stringify({ + workshopConsent: false, + socialMediaConsents: [] + })) + .attach('images', testImagePath); + + // Upload sollte trotzdem erfolgreich sein + expect(response.status).toBe(200); + expect(response.body.message).toBe('Batch upload successful'); + + // Telegram sollte nicht aufgerufen worden sein + expect(sendUploadNotificationSpy).not.toHaveBeenCalled(); + }); + + it('sollte Upload nicht fehlschlagen wenn Telegram-Benachrichtigung fehlschlägt', async () => { + sendUploadNotificationSpy.mockRejectedValueOnce( + new Error('Telegram API Error') + ); + + const response = await getRequest() + .post('/api/upload/batch') + .field('year', '2024') + .field('title', 'Test Upload') + .field('name', 'Test User') + .field('consents', JSON.stringify({ + workshopConsent: true, + socialMediaConsents: [] + })) + .attach('images', testImagePath); + + // Upload sollte trotzdem erfolgreich sein + expect(response.status).toBe(200); + expect(response.body.message).toBe('Batch upload successful'); + + // Warte auf async error handling + await new Promise(resolve => setTimeout(resolve, 150)); + + // Telegram wurde versucht aufzurufen + expect(sendUploadNotificationSpy).toHaveBeenCalled(); + }); + + it('sollte korrekte Daten an Telegram-Service übergeben', async () => { + const response = await getRequest() + .post('/api/upload/batch') + .field('year', '2025') + .field('title', 'Schweißkurs November') + .field('name', 'Max Mustermann') + .field('consents', JSON.stringify({ + workshopConsent: true, + socialMediaConsents: ['facebook', 'instagram'] + })) + .attach('images', testImagePath) + .attach('images', testImagePath); + + expect(response.status).toBe(200); + + await new Promise(resolve => setTimeout(resolve, 150)); + + expect(sendUploadNotificationSpy).toHaveBeenCalledWith({ + name: 'Max Mustermann', + year: 2025, + title: 'Schweißkurs November', + imageCount: 2, + workshopConsent: true, + socialMediaConsents: ['facebook', 'instagram'], + token: expect.any(String) + }); + }); + }); +}); diff --git a/backend/tests/unit/TelegramNotificationService.test.js b/backend/tests/unit/TelegramNotificationService.test.js new file mode 100644 index 0000000..b1e20dc --- /dev/null +++ b/backend/tests/unit/TelegramNotificationService.test.js @@ -0,0 +1,216 @@ +/** + * Unit Tests für TelegramNotificationService + * + * Phase 2: Basic Service Tests + */ + +const TelegramNotificationService = require('../../src/services/TelegramNotificationService'); + +// Mock node-telegram-bot-api komplett +jest.mock('node-telegram-bot-api'); + +describe('TelegramNotificationService', () => { + let originalEnv; + let TelegramBot; + let mockBotInstance; + + beforeAll(() => { + TelegramBot = require('node-telegram-bot-api'); + }); + + beforeEach(() => { + // Speichere originale ENV-Variablen + originalEnv = { ...process.env }; + + // Setze Test-ENV + process.env.TELEGRAM_ENABLED = 'true'; + process.env.TELEGRAM_BOT_TOKEN = 'test-bot-token-123'; + process.env.TELEGRAM_CHAT_ID = '-1001234567890'; + + // Erstelle Mock-Bot-Instanz + mockBotInstance = { + sendMessage: jest.fn().mockResolvedValue({ + message_id: 42, + chat: { id: -1001234567890 }, + text: 'Test' + }), + getMe: jest.fn().mockResolvedValue({ + id: 123456, + first_name: 'Test Bot', + username: 'test_bot' + }) + }; + + // Mock TelegramBot constructor + TelegramBot.mockImplementation(() => mockBotInstance); + }); + + afterEach(() => { + // Restore original ENV + process.env = originalEnv; + }); + + describe('Initialization', () => { + it('sollte erfolgreich initialisieren wenn TELEGRAM_ENABLED=true', () => { + const service = new TelegramNotificationService(); + + expect(service.isAvailable()).toBe(true); + expect(TelegramBot).toHaveBeenCalledWith('test-bot-token-123', { polling: false }); + }); + + it('sollte nicht initialisieren wenn TELEGRAM_ENABLED=false', () => { + process.env.TELEGRAM_ENABLED = 'false'; + + const service = new TelegramNotificationService(); + + expect(service.isAvailable()).toBe(false); + }); + + it('sollte fehlschlagen wenn TELEGRAM_BOT_TOKEN fehlt', () => { + delete process.env.TELEGRAM_BOT_TOKEN; + + const service = new TelegramNotificationService(); + + expect(service.isAvailable()).toBe(false); + }); + + it('sollte fehlschlagen wenn TELEGRAM_CHAT_ID fehlt', () => { + delete process.env.TELEGRAM_CHAT_ID; + + const service = new TelegramNotificationService(); + + expect(service.isAvailable()).toBe(false); + }); + }); + + describe('sendTestMessage', () => { + it('sollte Test-Nachricht erfolgreich senden', async () => { + const service = new TelegramNotificationService(); + + const result = await service.sendTestMessage(); + + expect(result).toBeDefined(); + expect(result.message_id).toBe(42); + expect(mockBotInstance.sendMessage).toHaveBeenCalledWith( + '-1001234567890', + expect.stringContaining('Telegram Service Test') + ); + }); + + it('sollte null zurückgeben wenn Service nicht verfügbar', async () => { + process.env.TELEGRAM_ENABLED = 'false'; + + const service = new TelegramNotificationService(); + + const result = await service.sendTestMessage(); + + expect(result).toBeNull(); + }); + + it('sollte Fehler werfen bei Telegram-API-Fehler', async () => { + const service = new TelegramNotificationService(); + + mockBotInstance.sendMessage.mockRejectedValueOnce(new Error('API Error')); + + await expect(service.sendTestMessage()).rejects.toThrow('API Error'); + }); + }); + + describe('formatSocialMediaIcons', () => { + it('sollte Social Media Plattformen korrekt formatieren', () => { + const service = new TelegramNotificationService(); + + const result = service.formatSocialMediaIcons(['facebook', 'instagram', 'tiktok']); + + expect(result).toBe('📘 Facebook, 📷 Instagram, 🎵 TikTok'); + }); + + it('sollte leeren String bei leerer Liste zurückgeben', () => { + const service = new TelegramNotificationService(); + + const result = service.formatSocialMediaIcons([]); + + expect(result).toBe(''); + }); + + it('sollte case-insensitive sein', () => { + const service = new TelegramNotificationService(); + + const result = service.formatSocialMediaIcons(['FACEBOOK', 'Instagram', 'TikTok']); + + expect(result).toBe('📘 Facebook, 📷 Instagram, 🎵 TikTok'); + }); + }); + + describe('getAdminUrl', () => { + it('sollte Admin-URL mit PUBLIC_URL generieren', () => { + process.env.PUBLIC_URL = 'https://test.example.com'; + + const service = new TelegramNotificationService(); + + const url = service.getAdminUrl(); + + expect(url).toBe('https://test.example.com/moderation'); + }); + + it('sollte Default-URL verwenden wenn PUBLIC_URL nicht gesetzt', () => { + delete process.env.PUBLIC_URL; + + const service = new TelegramNotificationService(); + + const url = service.getAdminUrl(); + + expect(url).toBe('https://internal.hobbyhimmel.de/moderation'); + }); + }); + + describe('sendUploadNotification (Phase 3)', () => { + it('sollte Upload-Benachrichtigung mit korrekten Daten senden', async () => { + const service = new TelegramNotificationService(); + + const groupData = { + name: 'Max Mustermann', + year: 2024, + title: 'Schweißkurs November', + imageCount: 12, + workshopConsent: true, + socialMediaConsents: ['instagram', 'tiktok'], + token: 'test-token-123' + }; + + await service.sendUploadNotification(groupData); + + expect(mockBotInstance.sendMessage).toHaveBeenCalledWith( + '-1001234567890', + expect.stringContaining('📸 Neuer Upload!') + ); + expect(mockBotInstance.sendMessage).toHaveBeenCalledWith( + '-1001234567890', + expect.stringContaining('Max Mustermann') + ); + expect(mockBotInstance.sendMessage).toHaveBeenCalledWith( + '-1001234567890', + expect.stringContaining('Bilder: 12') + ); + }); + + it('sollte null zurückgeben und nicht werfen bei Fehler', async () => { + const service = new TelegramNotificationService(); + + mockBotInstance.sendMessage.mockRejectedValueOnce(new Error('Network error')); + + const groupData = { + name: 'Test User', + year: 2024, + title: 'Test', + imageCount: 5, + workshopConsent: false, + socialMediaConsents: [] + }; + + const result = await service.sendUploadNotification(groupData); + + expect(result).toBeNull(); + }); + }); +}); diff --git a/backend/tests/utils/test-image.jpg b/backend/tests/utils/test-image.jpg new file mode 100644 index 0000000..0e00faa Binary files /dev/null and b/backend/tests/utils/test-image.jpg differ diff --git a/docker/.env.backend.example b/docker/.env.backend.example index ddad885..e0830c8 100644 --- a/docker/.env.backend.example +++ b/docker/.env.backend.example @@ -10,6 +10,22 @@ NODE_ENV=development # Port for the backend server PORT=5000 +# Admin Session Secret (IMPORTANT: Change in production!) +# Generate with: openssl rand -base64 32 +ADMIN_SESSION_SECRET=change-me-in-production + +# Telegram Bot Configuration (optional) +TELEGRAM_ENABLED=false +# Send test message on server start (development only) +TELEGRAM_SEND_TEST_ON_START=false +# Bot-Token from @BotFather +# Example: 123456789:ABCdefGHIjklMNOpqrsTUVwxyz1234567890 +TELEGRAM_BOT_TOKEN=your-bot-token-here +# Chat-ID of the Telegram group (negative for groups!) +# Get via: https://api.telegram.org/bot/getUpdates +# Example: -1001234567890 +TELEGRAM_CHAT_ID=your-chat-id-here + # Database settings (if needed in future) # DB_HOST=localhost # DB_PORT=3306 \ No newline at end of file diff --git a/docker/.env.frontend.example b/docker/.env.frontend.example index 57f1b3e..f761544 100644 --- a/docker/.env.frontend.example +++ b/docker/.env.frontend.example @@ -6,7 +6,4 @@ # Production: http://backend:5000 (container-to-container) API_URL=http://backend:5000 -# Client URL - the URL where users access the frontend -# Development: http://localhost:3000 (dev server) -# Production: http://localhost (nginx on port 80) -CLIENT_URL=http://localhost \ No newline at end of file +# Public/Internal host separation (optional) \ No newline at end of file diff --git a/docker/dev/.env.example b/docker/dev/.env.example new file mode 100644 index 0000000..eed5c26 --- /dev/null +++ b/docker/dev/.env.example @@ -0,0 +1,16 @@ +# Docker Compose Environment Variables for Development +# Copy this file to .env and adjust values + +# Admin Session Secret (optional, has default: dev-session-secret-change-me) +#ADMIN_SESSION_SECRET=your-secret-here + +# Telegram Bot Configuration (optional) +TELEGRAM_ENABLED=false +TELEGRAM_SEND_TEST_ON_START=false +# Bot-Token from @BotFather +# Example: 123456789:ABCdefGHIjklMNOpqrsTUVwxyz1234567890 +TELEGRAM_BOT_TOKEN=your-bot-token-here +# Chat-ID of the Telegram group (negative for groups!) +# Get via: https://api.telegram.org/bot/getUpdates +# Example: -1001234567890 +TELEGRAM_CHAT_ID=your-chat-id-here diff --git a/docker/dev/backend/Dockerfile b/docker/dev/backend/Dockerfile index dd0f13e..0e7bb40 100644 --- a/docker/dev/backend/Dockerfile +++ b/docker/dev/backend/Dockerfile @@ -12,8 +12,8 @@ RUN npm install # Copy backend source code COPY backend/ . -# Copy development environment configuration -COPY docker/dev/backend/config/.env ./.env +# Note: Environment variables are set via docker-compose.yml +# No .env file needed in the image # Expose port EXPOSE 5000 diff --git a/docker/dev/docker-compose.yml b/docker/dev/docker-compose.yml index cf8d18c..897ccd2 100644 --- a/docker/dev/docker-compose.yml +++ b/docker/dev/docker-compose.yml @@ -15,7 +15,6 @@ services: volumes: - ../../frontend:/app:cached - dev_frontend_node_modules:/app/node_modules - - ./frontend/config/.env:/app/.env:ro environment: - CHOKIDAR_USEPOLLING=true - API_URL=http://localhost:5001 @@ -38,14 +37,20 @@ services: volumes: - ../../backend:/usr/src/app:cached - dev_backend_node_modules:/usr/src/app/node_modules - - ./backend/config/.env:/usr/src/app/.env:ro environment: - NODE_ENV=development + - PORT=5000 + - REMOVE_IMAGES=false + - ADMIN_SESSION_SECRET=${ADMIN_SESSION_SECRET:-dev-session-secret-change-me} - PUBLIC_HOST=public.test.local - INTERNAL_HOST=internal.test.local - ENABLE_HOST_RESTRICTION=true - TRUST_PROXY_HOPS=0 - PUBLIC_UPLOAD_RATE_LIMIT=20 + - TELEGRAM_ENABLED=${TELEGRAM_ENABLED:-false} + - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN} + - TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID} + - TELEGRAM_SEND_TEST_ON_START=${TELEGRAM_SEND_TEST_ON_START:-false} networks: - dev-internal command: [ "npm", "run", "server" ] diff --git a/docker/dev/frontend/Dockerfile b/docker/dev/frontend/Dockerfile index d306915..e52ece0 100644 --- a/docker/dev/frontend/Dockerfile +++ b/docker/dev/frontend/Dockerfile @@ -13,9 +13,9 @@ WORKDIR /app # Copy package files first to leverage Docker cache for npm install COPY frontend/package*.json ./ -# Copy environment configuration +# Copy environment shell script (generates env-config.js from ENV at runtime) COPY docker/dev/frontend/config/env.sh ./env.sh -COPY docker/dev/frontend/config/.env ./.env +# Note: ENV variables are set via docker-compose.yml, not from .env file # Make env.sh executable RUN chmod +x ./env.sh diff --git a/docker/dev/frontend/config/env.sh b/docker/dev/frontend/config/env.sh index e8ce862..d4e7d12 100755 --- a/docker/dev/frontend/config/env.sh +++ b/docker/dev/frontend/config/env.sh @@ -7,23 +7,18 @@ touch ./env-config.js # Add assignment echo "window._env_ = {" >> ./env-config.js -# Read each line in .env file -# Each line represents key=value pairs -while read -r line || [[ -n "$line" ]]; -do - # Split env variables by character `=` - if printf '%s\n' "$line" | grep -q -e '='; then - varname=$(printf '%s\n' "$line" | sed -e 's/=.*//') - varvalue=$(printf '%s\n' "$line" | sed -e 's/^[^=]*=//') - fi +# List of environment variables to export (add more as needed) +ENV_VARS="API_URL PUBLIC_HOST INTERNAL_HOST" - # Read value of current variable if exists as Environment variable - value=$(printf '%s\n' "${!varname}") - # Otherwise use value from .env file - [[ -z $value ]] && value=${varvalue} +# Read each environment variable and add to config +for varname in $ENV_VARS; do + # Get value from environment using indirect expansion + value="${!varname}" - # Append configuration property to JS file - echo " $varname: \"$value\"," >> ./env-config.js -done < .env + # Only add if value exists + if [ -n "$value" ]; then + echo " $varname: \"$value\"," >> ./env-config.js + fi +done echo "}" >> ./env-config.js diff --git a/docker/prod/.env.example b/docker/prod/.env.example new file mode 100644 index 0000000..4e1aa67 --- /dev/null +++ b/docker/prod/.env.example @@ -0,0 +1,18 @@ +# Docker Compose Environment Variables for Production +# Copy this file to .env and adjust values +# IMPORTANT: Keep this file secure and never commit .env to git! + +# Admin Session Secret (REQUIRED: Generate new secret!) +# Generate with: openssl rand -base64 32 +ADMIN_SESSION_SECRET=CHANGE-ME-IN-PRODUCTION + +# Telegram Bot Configuration (optional) +# Set to true to enable Telegram notifications in production +TELEGRAM_ENABLED=false +# Bot-Token from @BotFather (production bot) +# Example: 123456789:ABCdefGHIjklMNOpqrsTUVwxyz1234567890 +TELEGRAM_BOT_TOKEN=your-production-bot-token-here +# Chat-ID of the production Telegram group (negative for groups!) +# Get via: https://api.telegram.org/bot/getUpdates +# Example: -1001234567890 +TELEGRAM_CHAT_ID=your-production-chat-id-here diff --git a/docker/prod/docker-compose.yml b/docker/prod/docker-compose.yml index d510ec1..2ba26f9 100644 --- a/docker/prod/docker-compose.yml +++ b/docker/prod/docker-compose.yml @@ -15,8 +15,8 @@ services: - backend environment: - API_URL=http://backend:5000 - - PUBLIC_HOST=deinprojekt.hobbyhimmel.de - - INTERNAL_HOST=deinprojekt.lan.hobbyhimmel.de + - PUBLIC_HOST=public.test.local + - INTERNAL_HOST=internal.test.local networks: - npm-nw @@ -42,13 +42,18 @@ services: # ⚠️ Für HTTP-only Labs per Override auf "false" setzen (nicht im Repo committen) - ADMIN_SESSION_COOKIE_SECURE=true # Host Configuration (Public/Internal Separation) - - PUBLIC_HOST=deinprojekt.hobbyhimmel.de - - INTERNAL_HOST=deinprojekt.lan.hobbyhimmel.de + - PUBLIC_HOST=public.test.local + - INTERNAL_HOST=internal.test.local - ENABLE_HOST_RESTRICTION=true - PUBLIC_UPLOAD_RATE_LIMIT=20 - PUBLIC_UPLOAD_RATE_WINDOW=3600000 # Trust nginx-proxy-manager (1 hop) - TRUST_PROXY_HOPS=1 + # Telegram Bot Configuration (optional) + - TELEGRAM_ENABLED=${TELEGRAM_ENABLED:-false} + - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN} + - TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID} + - TELEGRAM_SEND_TEST_ON_START=false diff --git a/docker/prod/frontend/Dockerfile b/docker/prod/frontend/Dockerfile index bf024fe..717bfb4 100644 --- a/docker/prod/frontend/Dockerfile +++ b/docker/prod/frontend/Dockerfile @@ -20,10 +20,10 @@ COPY --from=build /app/build /usr/share/nginx/html # Default port exposure EXPOSE 80 -# Copy .env file and shell script to container +# Copy .env shell script to container (generates env-config.js from ENV at runtime) WORKDIR /usr/share/nginx/html COPY docker/prod/frontend/config/env.sh ./env.sh -COPY docker/prod/frontend/config/.env ./.env +# Note: ENV variables are set via docker-compose.yml, not from .env file # Add bash RUN apk add --no-cache bash diff --git a/docker/prod/frontend/config/env.sh b/docker/prod/frontend/config/env.sh index e8ce862..d4e7d12 100755 --- a/docker/prod/frontend/config/env.sh +++ b/docker/prod/frontend/config/env.sh @@ -7,23 +7,18 @@ touch ./env-config.js # Add assignment echo "window._env_ = {" >> ./env-config.js -# Read each line in .env file -# Each line represents key=value pairs -while read -r line || [[ -n "$line" ]]; -do - # Split env variables by character `=` - if printf '%s\n' "$line" | grep -q -e '='; then - varname=$(printf '%s\n' "$line" | sed -e 's/=.*//') - varvalue=$(printf '%s\n' "$line" | sed -e 's/^[^=]*=//') - fi +# List of environment variables to export (add more as needed) +ENV_VARS="API_URL PUBLIC_HOST INTERNAL_HOST" - # Read value of current variable if exists as Environment variable - value=$(printf '%s\n' "${!varname}") - # Otherwise use value from .env file - [[ -z $value ]] && value=${varvalue} +# Read each environment variable and add to config +for varname in $ENV_VARS; do + # Get value from environment using indirect expansion + value="${!varname}" - # Append configuration property to JS file - echo " $varname: \"$value\"," >> ./env-config.js -done < .env + # Only add if value exists + if [ -n "$value" ]; then + echo " $varname: \"$value\"," >> ./env-config.js + fi +done echo "}" >> ./env-config.js diff --git a/frontend/src/Components/Pages/MultiUploadPage.js b/frontend/src/Components/Pages/MultiUploadPage.js index 1fcbdc7..723993d 100644 --- a/frontend/src/Components/Pages/MultiUploadPage.js +++ b/frontend/src/Components/Pages/MultiUploadPage.js @@ -318,10 +318,10 @@ function MultiUploadPage() {

- ⚠️ Wichtig: Bewahren Sie diesen Link sicher auf! Jeder mit diesem Link kann Ihren Upload verwalten. + ⚠️ Wichtig: Bewahre diesen Link sicher auf! Jeder mit diesem Link kann Deinen Upload verwalten.

- ℹ️ Hinweis: Über diesen Link können Sie nur die Bilder in der Werkstatt verwalten. Bereits auf Social Media Plattformen veröffentlichte Bilder müssen separat dort gelöscht werden. + ℹ️ Hinweis: Über diesen Link kannst Du nur die Bilder in der Werkstatt verwalten. Bereits auf Social Media Plattformen veröffentlichte Bilder müssen separat dort gelöscht werden.

)} diff --git a/package-lock.json b/package-lock.json index b97d8cf..74fc360 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "find-remove": "^2.0.3", "fs": "^0.0.1-security", "node-cron": "^4.2.1", + "node-telegram-bot-api": "^0.66.0", "sharp": "^0.34.4", "shortid": "^2.2.16", "sqlite3": "^5.1.7", @@ -2779,11 +2780,6 @@ "dev": true, "license": "MIT" }, - "backend/node_modules/asynckit": { - "version": "0.4.0", - "dev": true, - "license": "MIT" - }, "backend/node_modules/atomic-sleep": { "version": "1.0.0", "dev": true, @@ -2964,11 +2960,6 @@ "version": "2.0.4", "license": "ISC" }, - "backend/node_modules/bluebird": { - "version": "3.7.2", - "dev": true, - "license": "MIT" - }, "backend/node_modules/body-parser": { "version": "1.19.0", "license": "MIT", @@ -3209,33 +3200,6 @@ "node": ">=8" } }, - "backend/node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "backend/node_modules/call-bound": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "backend/node_modules/call-me-maybe": { "version": "1.0.2", "dev": true, @@ -3284,11 +3248,6 @@ ], "license": "CC-BY-4.0" }, - "backend/node_modules/caseless": { - "version": "0.12.0", - "dev": true, - "license": "Apache-2.0" - }, "backend/node_modules/chalk": { "version": "4.1.2", "dev": true, @@ -3464,17 +3423,6 @@ "dev": true, "license": "MIT" }, - "backend/node_modules/combined-stream": { - "version": "1.0.8", - "dev": true, - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "backend/node_modules/component-emitter": { "version": "1.3.1", "dev": true, @@ -3726,14 +3674,6 @@ "dev": true, "license": "MIT" }, - "backend/node_modules/delayed-stream": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "backend/node_modules/delegates": { "version": "1.0.0", "license": "MIT", @@ -3809,19 +3749,6 @@ "node": ">=8" } }, - "backend/node_modules/dunder-proto": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "backend/node_modules/duplexer3": { "version": "0.1.4", "dev": true, @@ -3883,13 +3810,6 @@ "node": ">=0.10.0" } }, - "backend/node_modules/end-of-stream": { - "version": "1.4.4", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, "backend/node_modules/env-paths": { "version": "2.2.1", "license": "MIT", @@ -3911,47 +3831,6 @@ "is-arrayish": "^0.2.1" } }, - "backend/node_modules/es-define-property": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "backend/node_modules/es-errors": { - "version": "1.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "backend/node_modules/es-object-atoms": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "backend/node_modules/es-set-tostringtag": { - "version": "2.1.0", - "dev": true, - "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" - } - }, "backend/node_modules/es6-promise": { "version": "3.3.1", "dev": true, @@ -4183,16 +4062,6 @@ ], "license": "MIT" }, - "backend/node_modules/fast-deep-equal": { - "version": "3.1.3", - "dev": true, - "license": "MIT" - }, - "backend/node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "dev": true, - "license": "MIT" - }, "backend/node_modules/fast-redact": { "version": "3.5.0", "dev": true, @@ -4262,14 +4131,6 @@ "node": ">=4" } }, - "backend/node_modules/file-type": { - "version": "3.9.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "backend/node_modules/file-uri-to-path": { "version": "1.0.0", "license": "MIT" @@ -4371,21 +4232,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "backend/node_modules/form-data": { - "version": "4.0.4", - "dev": true, - "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" - } - }, "backend/node_modules/format-util": { "version": "1.0.5", "dev": true, @@ -4448,14 +4294,6 @@ "version": "1.0.0", "license": "ISC" }, - "backend/node_modules/function-bind": { - "version": "1.1.2", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "backend/node_modules/gauge": { "version": "4.0.4", "license": "ISC", @@ -4490,29 +4328,6 @@ "node": "6.* || 8.* || >= 10.*" } }, - "backend/node_modules/get-intrinsic": { - "version": "1.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "backend/node_modules/get-package-type": { "version": "0.1.0", "dev": true, @@ -4521,18 +4336,6 @@ "node": ">=8.0.0" } }, - "backend/node_modules/get-proto": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "backend/node_modules/get-stream": { "version": "4.1.0", "dev": true, @@ -4591,17 +4394,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "backend/node_modules/gopd": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "backend/node_modules/got": { "version": "9.6.0", "dev": true, @@ -4672,31 +4464,6 @@ "node": ">=8" } }, - "backend/node_modules/has-symbols": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "backend/node_modules/has-tostringtag": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "backend/node_modules/has-unicode": { "version": "2.0.1", "license": "ISC", @@ -4710,17 +4477,6 @@ "node": ">=8" } }, - "backend/node_modules/hasown": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "backend/node_modules/hosted-git-info": { "version": "2.8.8", "dev": true, @@ -5098,11 +4854,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "backend/node_modules/is-typedarray": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, "backend/node_modules/is-yarn-global": { "version": "0.3.0", "dev": true, @@ -6350,11 +6101,6 @@ "node": ">=4" } }, - "backend/node_modules/lodash": { - "version": "4.17.21", - "dev": true, - "license": "MIT" - }, "backend/node_modules/lodash.camelcase": { "version": "4.3.0", "dev": true, @@ -6472,14 +6218,6 @@ "node": ">= 12" } }, - "backend/node_modules/math-intrinsics": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, "backend/node_modules/media-typer": { "version": "0.3.0", "license": "MIT", @@ -6526,23 +6264,6 @@ "node": ">=8.6" } }, - "backend/node_modules/mime": { - "version": "1.6.0", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "backend/node_modules/mime-db": { - "version": "1.52.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "backend/node_modules/mime-format": { "version": "2.0.1", "dev": true, @@ -6551,16 +6272,6 @@ "charset": "^1.0.0" } }, - "backend/node_modules/mime-types": { - "version": "2.1.35", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "backend/node_modules/mimic-fn": { "version": "2.1.0", "dev": true, @@ -7152,17 +6863,6 @@ "node": ">=0.10.0" } }, - "backend/node_modules/object-inspect": { - "version": "1.13.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "backend/node_modules/on-finished": { "version": "2.3.0", "license": "MIT", @@ -7180,13 +6880,6 @@ "node": ">= 0.8" } }, - "backend/node_modules/once": { - "version": "1.4.0", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, "backend/node_modules/onetime": { "version": "5.1.2", "dev": true, @@ -7850,14 +7543,6 @@ "once": "^1.3.1" } }, - "backend/node_modules/punycode": { - "version": "2.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "backend/node_modules/pupa": { "version": "2.1.1", "dev": true, @@ -8302,10 +7987,6 @@ "dev": true, "license": "MIT" }, - "backend/node_modules/safer-buffer": { - "version": "2.1.2", - "license": "MIT" - }, "backend/node_modules/scheduler": { "version": "0.27.0", "dev": true, @@ -8525,74 +8206,6 @@ "dev": true, "license": "MIT" }, - "backend/node_modules/side-channel": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "backend/node_modules/side-channel-list": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "backend/node_modules/side-channel-map": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "backend/node_modules/side-channel-weakmap": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "backend/node_modules/signal-exit": { "version": "3.0.7", "devOptional": true, @@ -9498,16 +9111,6 @@ "dev": true, "license": "0BSD" }, - "backend/node_modules/tunnel-agent": { - "version": "0.6.0", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, "backend/node_modules/type-detect": { "version": "4.0.8", "dev": true, @@ -9770,10 +9373,6 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "backend/node_modules/util-deprecate": { - "version": "1.0.2", - "license": "MIT" - }, "backend/node_modules/utility-types": { "version": "3.11.0", "dev": true, @@ -9963,10 +9562,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "backend/node_modules/wrappy": { - "version": "1.0.2", - "license": "ISC" - }, "backend/node_modules/write-file-atomic": { "version": "3.0.3", "dev": true, @@ -15173,20 +14768,6 @@ "node": ">= 6.0.0" } }, - "frontend/node_modules/ajv": { - "version": "6.12.6", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "frontend/node_modules/ajv-formats": { "version": "2.1.1", "license": "MIT", @@ -15324,20 +14905,6 @@ "node": ">=6.0" } }, - "frontend/node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/array-flatten": { "version": "1.1.1", "license": "MIT" @@ -15452,25 +15019,6 @@ "node": ">= 0.4" } }, - "frontend/node_modules/arraybuffer.prototype.slice": { - "version": "1.0.4", - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/asap": { "version": "2.0.6", "license": "MIT" @@ -15483,17 +15031,6 @@ "version": "3.2.6", "license": "MIT" }, - "frontend/node_modules/async-function": { - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "frontend/node_modules/asynckit": { - "version": "0.4.0", - "license": "MIT" - }, "frontend/node_modules/at-least-node": { "version": "1.0.0", "license": "ISC", @@ -15553,19 +15090,6 @@ "postcss": "^8.1.0" } }, - "frontend/node_modules/available-typed-arrays": { - "version": "1.0.7", - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/axe-core": { "version": "4.11.0", "license": "MPL-2.0", @@ -15925,10 +15449,6 @@ "node": ">=8" } }, - "frontend/node_modules/bluebird": { - "version": "3.7.2", - "license": "MIT" - }, "frontend/node_modules/body-parser": { "version": "1.20.3", "license": "MIT", @@ -16065,47 +15585,6 @@ "node": ">= 0.8" } }, - "frontend/node_modules/call-bind": { - "version": "1.0.8", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "frontend/node_modules/call-bound": { - "version": "1.0.4", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/callsites": { "version": "3.1.0", "license": "MIT", @@ -16352,16 +15831,6 @@ "version": "2.0.20", "license": "MIT" }, - "frontend/node_modules/combined-stream": { - "version": "1.0.8", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "frontend/node_modules/comma-separated-tokens": { "version": "1.0.8", "license": "MIT", @@ -16501,10 +15970,6 @@ "url": "https://opencollective.com/core-js" } }, - "frontend/node_modules/core-util-is": { - "version": "1.0.3", - "license": "MIT" - }, "frontend/node_modules/cosmiconfig": { "version": "7.0.0", "license": "MIT", @@ -16895,51 +16360,6 @@ "node": ">=10" } }, - "frontend/node_modules/data-view-buffer": { - "version": "1.0.2", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/data-view-byte-length": { - "version": "1.0.2", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/inspect-js" - } - }, - "frontend/node_modules/data-view-byte-offset": { - "version": "1.0.1", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/debug": { "version": "4.4.3", "license": "MIT", @@ -16991,21 +16411,6 @@ "node": ">= 10" } }, - "frontend/node_modules/define-data-property": { - "version": "1.1.4", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/define-lazy-prop": { "version": "2.0.0", "license": "MIT", @@ -17013,28 +16418,6 @@ "node": ">=8" } }, - "frontend/node_modules/define-properties": { - "version": "1.2.1", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/delayed-stream": { - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "frontend/node_modules/delegate": { "version": "3.2.0", "license": "MIT", @@ -17245,18 +16628,6 @@ "version": "5.1.0", "license": "BSD-2-Clause" }, - "frontend/node_modules/dunder-proto": { - "version": "1.0.1", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "frontend/node_modules/duplexer": { "version": "0.1.2", "license": "MIT" @@ -17346,86 +16717,6 @@ "stackframe": "^1.3.4" } }, - "frontend/node_modules/es-abstract": { - "version": "1.24.0", - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.2", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "data-view-buffer": "^1.0.2", - "data-view-byte-length": "^1.0.2", - "data-view-byte-offset": "^1.0.1", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-set-tostringtag": "^2.1.0", - "es-to-primitive": "^1.3.0", - "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.3.0", - "get-proto": "^1.0.1", - "get-symbol-description": "^1.1.0", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "internal-slot": "^1.1.0", - "is-array-buffer": "^3.0.5", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.2", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.2.1", - "is-set": "^2.0.3", - "is-shared-array-buffer": "^1.0.4", - "is-string": "^1.1.1", - "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.1", - "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.4", - "object-keys": "^1.1.1", - "object.assign": "^4.1.7", - "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.4", - "safe-array-concat": "^1.1.3", - "safe-push-apply": "^1.0.0", - "safe-regex-test": "^1.1.0", - "set-proto": "^1.0.0", - "stop-iteration-iterator": "^1.1.0", - "string.prototype.trim": "^1.2.10", - "string.prototype.trimend": "^1.0.9", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-length": "^1.0.3", - "typed-array-byte-offset": "^1.0.4", - "typed-array-length": "^1.0.7", - "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.19" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/es-define-property": { - "version": "1.0.1", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "frontend/node_modules/es-errors": { - "version": "1.3.0", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, "frontend/node_modules/es-iterator-helpers": { "version": "1.2.1", "license": "MIT", @@ -17455,54 +16746,6 @@ "version": "1.7.0", "license": "MIT" }, - "frontend/node_modules/es-object-atoms": { - "version": "1.1.1", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "frontend/node_modules/es-set-tostringtag": { - "version": "2.1.0", - "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" - } - }, - "frontend/node_modules/es-shim-unscopables": { - "version": "1.1.0", - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "frontend/node_modules/es-to-primitive": { - "version": "1.3.0", - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/escalade": { "version": "3.2.0", "license": "MIT", @@ -18364,10 +17607,6 @@ "version": "2.0.0", "license": "MIT" }, - "frontend/node_modules/fast-deep-equal": { - "version": "3.1.3", - "license": "MIT" - }, "frontend/node_modules/fast-glob": { "version": "3.3.3", "license": "MIT", @@ -18382,10 +17621,6 @@ "node": ">=8.6.0" } }, - "frontend/node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "license": "MIT" - }, "frontend/node_modules/fast-levenshtein": { "version": "2.0.6", "license": "MIT" @@ -18625,19 +17860,6 @@ } } }, - "frontend/node_modules/for-each": { - "version": "0.3.5", - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/foreground-child": { "version": "3.3.1", "license": "ISC", @@ -18872,45 +18094,6 @@ "version": "1.0.0", "license": "ISC" }, - "frontend/node_modules/function-bind": { - "version": "1.1.2", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/function.prototype.name": { - "version": "1.1.8", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/functions-have-names": { - "version": "1.2.3", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/generator-function": { - "version": "2.0.1", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, "frontend/node_modules/gensync": { "version": "1.0.0-beta.2", "license": "MIT", @@ -18925,28 +18108,6 @@ "node": "6.* || 8.* || >= 10.*" } }, - "frontend/node_modules/get-intrinsic": { - "version": "1.3.0", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/get-own-enumerable-property-symbols": { "version": "3.0.2", "license": "ISC" @@ -18958,17 +18119,6 @@ "node": ">=8.0.0" } }, - "frontend/node_modules/get-proto": { - "version": "1.0.1", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "frontend/node_modules/get-stream": { "version": "6.0.1", "license": "MIT", @@ -18979,21 +18129,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "frontend/node_modules/get-symbol-description": { - "version": "1.1.0", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/glob": { "version": "7.2.3", "license": "ISC", @@ -19071,20 +18206,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "frontend/node_modules/globalthis": { - "version": "1.0.4", - "license": "MIT", - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/globby": { "version": "11.1.0", "license": "MIT", @@ -19111,16 +18232,6 @@ "delegate": "^3.1.2" } }, - "frontend/node_modules/gopd": { - "version": "1.2.0", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/graceful-fs": { "version": "4.2.11", "license": "ISC" @@ -19150,16 +18261,6 @@ "version": "1.6.1", "license": "(Apache-2.0 OR MPL-1.1)" }, - "frontend/node_modules/has-bigints": { - "version": "1.1.0", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/has-flag": { "version": "3.0.0", "license": "MIT", @@ -19167,62 +18268,6 @@ "node": ">=4" } }, - "frontend/node_modules/has-property-descriptors": { - "version": "1.0.2", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/has-proto": { - "version": "1.2.0", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/has-symbols": { - "version": "1.1.0", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/has-tostringtag": { - "version": "1.0.2", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/hasown": { - "version": "2.0.2", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "frontend/node_modules/hast-util-parse-selector": { "version": "2.2.5", "license": "MIT", @@ -19287,10 +18332,6 @@ "wbuf": "^1.1.0" } }, - "frontend/node_modules/hpack.js/node_modules/isarray": { - "version": "1.0.0", - "license": "MIT" - }, "frontend/node_modules/hpack.js/node_modules/readable-stream": { "version": "2.3.8", "license": "MIT", @@ -19638,26 +18679,10 @@ "wrappy": "1" } }, - "frontend/node_modules/inherits": { - "version": "2.0.4", - "license": "ISC" - }, "frontend/node_modules/ini": { "version": "1.3.8", "license": "ISC" }, - "frontend/node_modules/internal-slot": { - "version": "1.1.0", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "frontend/node_modules/ipaddr.js": { "version": "2.2.0", "license": "MIT", @@ -19685,55 +18710,10 @@ "url": "https://github.com/sponsors/wooorm" } }, - "frontend/node_modules/is-array-buffer": { - "version": "3.0.5", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/is-arrayish": { "version": "0.2.1", "license": "MIT" }, - "frontend/node_modules/is-async-function": { - "version": "2.1.1", - "license": "MIT", - "dependencies": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/is-bigint": { - "version": "1.1.0", - "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/is-binary-path": { "version": "2.1.0", "license": "MIT", @@ -19744,30 +18724,6 @@ "node": ">=8" } }, - "frontend/node_modules/is-boolean-object": { - "version": "1.2.2", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/is-callable": { - "version": "1.2.7", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/is-core-module": { "version": "2.16.1", "license": "MIT", @@ -19781,35 +18737,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "frontend/node_modules/is-data-view": { - "version": "1.0.2", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/is-date-object": { - "version": "1.1.0", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/is-decimal": { "version": "1.0.4", "license": "MIT", @@ -19838,19 +18765,6 @@ "node": ">=0.10.0" } }, - "frontend/node_modules/is-finalizationregistry": { - "version": "1.1.1", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "license": "MIT", @@ -19865,23 +18779,6 @@ "node": ">=6" } }, - "frontend/node_modules/is-generator-function": { - "version": "1.1.2", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.4", - "generator-function": "^2.0.0", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/is-glob": { "version": "4.0.3", "license": "MIT", @@ -19900,30 +18797,10 @@ "url": "https://github.com/sponsors/wooorm" } }, - "frontend/node_modules/is-map": { - "version": "2.0.3", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/is-module": { "version": "1.0.0", "license": "MIT" }, - "frontend/node_modules/is-negative-zero": { - "version": "2.0.3", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/is-number": { "version": "7.0.0", "license": "MIT", @@ -19931,20 +18808,6 @@ "node": ">=0.12.0" } }, - "frontend/node_modules/is-number-object": { - "version": "1.1.1", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/is-obj": { "version": "1.0.1", "license": "MIT", @@ -19973,22 +18836,6 @@ "version": "1.0.1", "license": "MIT" }, - "frontend/node_modules/is-regex": { - "version": "1.2.1", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/is-regexp": { "version": "1.0.0", "license": "MIT", @@ -20003,29 +18850,6 @@ "node": ">=6" } }, - "frontend/node_modules/is-set": { - "version": "2.0.3", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/is-shared-array-buffer": { - "version": "1.0.4", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/is-stream": { "version": "2.0.1", "license": "MIT", @@ -20036,89 +18860,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "frontend/node_modules/is-string": { - "version": "1.1.1", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/is-symbol": { - "version": "1.1.1", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/is-typed-array": { - "version": "1.1.15", - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/is-typedarray": { - "version": "1.0.0", - "license": "MIT" - }, - "frontend/node_modules/is-weakmap": { - "version": "2.0.2", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/is-weakref": { - "version": "1.1.1", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/is-weakset": { - "version": "2.0.4", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/is-wsl": { "version": "2.2.0", "license": "MIT", @@ -20129,10 +18870,6 @@ "node": ">=8" } }, - "frontend/node_modules/isarray": { - "version": "2.0.5", - "license": "MIT" - }, "frontend/node_modules/isexe": { "version": "2.0.0", "license": "ISC" @@ -23189,14 +21926,6 @@ "version": "2.3.1", "license": "MIT" }, - "frontend/node_modules/json-schema": { - "version": "0.4.0", - "license": "(AFL-2.1 OR BSD-3-Clause)" - }, - "frontend/node_modules/json-schema-traverse": { - "version": "0.4.1", - "license": "MIT" - }, "frontend/node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "license": "MIT" @@ -23353,10 +22082,6 @@ "node": ">=8" } }, - "frontend/node_modules/lodash": { - "version": "4.17.21", - "license": "MIT" - }, "frontend/node_modules/lodash.debounce": { "version": "4.0.8", "license": "MIT" @@ -23465,13 +22190,6 @@ "tmpl": "1.0.5" } }, - "frontend/node_modules/math-intrinsics": { - "version": "1.1.0", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, "frontend/node_modules/mdn-data": { "version": "2.0.4", "license": "CC0-1.0" @@ -23529,33 +22247,6 @@ "node": ">=8.6" } }, - "frontend/node_modules/mime": { - "version": "1.6.0", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "frontend/node_modules/mime-db": { - "version": "1.52.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "frontend/node_modules/mime-types": { - "version": "2.1.35", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "frontend/node_modules/mimic-fn": { "version": "2.1.0", "license": "MIT", @@ -23626,10 +22317,6 @@ "mkdirp": "bin/cmd.js" } }, - "frontend/node_modules/ms": { - "version": "2.1.3", - "license": "MIT" - }, "frontend/node_modules/multicast-dns": { "version": "7.2.5", "license": "MIT", @@ -23767,41 +22454,6 @@ "node": ">= 6" } }, - "frontend/node_modules/object-inspect": { - "version": "1.13.4", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/object-keys": { - "version": "1.1.1", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "frontend/node_modules/object.assign": { - "version": "4.1.7", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/object.entries": { "version": "1.1.9", "license": "MIT", @@ -23895,13 +22547,6 @@ "node": ">= 0.8" } }, - "frontend/node_modules/once": { - "version": "1.4.0", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, "frontend/node_modules/onetime": { "version": "5.1.2", "license": "MIT", @@ -23945,21 +22590,6 @@ "node": ">= 0.8.0" } }, - "frontend/node_modules/own-keys": { - "version": "1.0.1", - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.6", - "object-keys": "^1.1.1", - "safe-push-apply": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/p-limit": { "version": "2.3.0", "license": "MIT", @@ -24124,10 +22754,6 @@ "node": ">=8" } }, - "frontend/node_modules/performance-now": { - "version": "2.1.0", - "license": "MIT" - }, "frontend/node_modules/picocolors": { "version": "1.1.1", "license": "ISC" @@ -24214,13 +22840,6 @@ "node": ">=4" } }, - "frontend/node_modules/possible-typed-array-names": { - "version": "1.1.0", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, "frontend/node_modules/postcss": { "version": "8.5.6", "funding": [ @@ -25463,10 +24082,6 @@ "clipboard": "^2.0.0" } }, - "frontend/node_modules/process-nextick-args": { - "version": "2.0.1", - "license": "MIT" - }, "frontend/node_modules/promise": { "version": "8.3.0", "license": "MIT", @@ -25527,23 +24142,6 @@ "node": ">= 0.10" } }, - "frontend/node_modules/psl": { - "version": "1.15.0", - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "funding": { - "url": "https://github.com/sponsors/lupomontero" - } - }, - "frontend/node_modules/punycode": { - "version": "2.3.1", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "frontend/node_modules/q": { "version": "1.5.1", "license": "MIT", @@ -25565,10 +24163,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "frontend/node_modules/querystringify": { - "version": "2.2.0", - "license": "MIT" - }, "frontend/node_modules/queue-microtask": { "version": "1.2.3", "funding": [ @@ -26118,26 +24712,6 @@ "node": ">=8" } }, - "frontend/node_modules/reflect.getprototypeof": { - "version": "1.0.10", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.1", - "which-builtin-type": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/refractor": { "version": "2.10.1", "license": "MIT", @@ -26180,24 +24754,6 @@ "version": "2.3.1", "license": "MIT" }, - "frontend/node_modules/regexp.prototype.flags": { - "version": "1.5.4", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/regexpu-core": { "version": "6.4.0", "license": "MIT", @@ -26327,10 +24883,6 @@ "node": ">=0.10.0" } }, - "frontend/node_modules/requires-port": { - "version": "1.0.0", - "license": "MIT" - }, "frontend/node_modules/resolve": { "version": "1.22.11", "license": "MIT", @@ -26540,74 +25092,6 @@ "queue-microtask": "^1.2.2" } }, - "frontend/node_modules/safe-array-concat": { - "version": "1.1.3", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "has-symbols": "^1.1.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/safe-buffer": { - "version": "5.2.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "frontend/node_modules/safe-push-apply": { - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/safe-regex-test": { - "version": "1.1.0", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/safer-buffer": { - "version": "2.1.2", - "license": "MIT" - }, "frontend/node_modules/sanitize.css": { "version": "13.0.0", "license": "CC0-1.0" @@ -26879,46 +25363,6 @@ "node": ">= 0.8.0" } }, - "frontend/node_modules/set-function-length": { - "version": "1.2.2", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "frontend/node_modules/set-function-name": { - "version": "2.0.2", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "frontend/node_modules/set-proto": { - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "frontend/node_modules/setprototypeof": { "version": "1.2.0", "license": "ISC" @@ -26954,70 +25398,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "frontend/node_modules/side-channel": { - "version": "1.1.0", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/side-channel-list": { - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/side-channel-map": { - "version": "1.0.1", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/side-channel-weakmap": { - "version": "1.0.2", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/signal-exit": { "version": "3.0.7", "license": "ISC" @@ -27168,17 +25548,6 @@ "node": ">= 0.8" } }, - "frontend/node_modules/stop-iteration-iterator": { - "version": "1.1.0", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "internal-slot": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "frontend/node_modules/string_decoder": { "version": "1.3.0", "license": "MIT", @@ -27279,56 +25648,6 @@ "es-abstract": "^1.17.5" } }, - "frontend/node_modules/string.prototype.trim": { - "version": "1.2.10", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-data-property": "^1.1.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/string.prototype.trimend": { - "version": "1.0.9", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/stringify-object": { "version": "3.3.0", "license": "BSD-2-Clause", @@ -28010,72 +26329,6 @@ "node": ">= 0.6" } }, - "frontend/node_modules/typed-array-buffer": { - "version": "1.0.3", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - } - }, - "frontend/node_modules/typed-array-byte-length": { - "version": "1.0.3", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/typed-array-byte-offset": { - "version": "1.0.4", - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/typed-array-length": { - "version": "1.0.7", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/typedarray-to-buffer": { "version": "3.1.5", "license": "MIT", @@ -28102,22 +26355,6 @@ "typescript": "^2.5.2 || ^3.0 || ^4.0" } }, - "frontend/node_modules/unbox-primitive": { - "version": "1.1.0", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.1", "license": "MIT", @@ -28214,25 +26451,6 @@ "browserslist": ">= 4.21.0" } }, - "frontend/node_modules/uri-js": { - "version": "4.4.1", - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "frontend/node_modules/url-parse": { - "version": "1.5.10", - "license": "MIT", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "frontend/node_modules/util-deprecate": { - "version": "1.0.2", - "license": "MIT" - }, "frontend/node_modules/util.promisify": { "version": "1.0.0", "license": "MIT", @@ -28252,13 +26470,6 @@ "node": ">= 0.4.0" } }, - "frontend/node_modules/uuid": { - "version": "8.3.2", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "frontend/node_modules/v8-to-istanbul": { "version": "8.1.1", "license": "ISC", @@ -28604,83 +26815,6 @@ "node": ">= 8" } }, - "frontend/node_modules/which-boxed-primitive": { - "version": "1.1.1", - "license": "MIT", - "dependencies": { - "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.1", - "is-number-object": "^1.1.1", - "is-string": "^1.1.1", - "is-symbol": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/which-builtin-type": { - "version": "1.2.1", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "function.prototype.name": "^1.1.6", - "has-tostringtag": "^1.0.2", - "is-async-function": "^2.0.0", - "is-date-object": "^1.1.0", - "is-finalizationregistry": "^1.1.0", - "is-generator-function": "^1.0.10", - "is-regex": "^1.2.1", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.1.0", - "which-collection": "^1.0.2", - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/which-collection": { - "version": "1.0.2", - "license": "MIT", - "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "frontend/node_modules/which-typed-array": { - "version": "1.1.19", - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "frontend/node_modules/word-wrap": { "version": "1.2.5", "license": "MIT", @@ -29040,10 +27174,6 @@ "version": "1.1.4", "license": "MIT" }, - "frontend/node_modules/wrappy": { - "version": "1.0.2", - "license": "ISC" - }, "frontend/node_modules/write-file-atomic": { "version": "3.0.3", "license": "ISC", @@ -29139,13 +27269,2200 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@cypress/request": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.9.tgz", + "integrity": "sha512-I3l7FdGRXluAS44/0NguwWlO83J18p0vlr2FYHrJkWdNYhgVoiYo61IXPqaOsL+vNxU1ZqMACzItGK3/KKDsdw==", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~4.0.4", + "http-signature": "~1.4.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "performance-now": "^2.1.0", + "qs": "6.14.0", + "safe-buffer": "^5.1.2", + "tough-cookie": "^5.0.0", + "tunnel-agent": "^0.6.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@cypress/request-promise": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@cypress/request-promise/-/request-promise-5.0.0.tgz", + "integrity": "sha512-eKdYVpa9cBEw2kTBlHeu1PP16Blwtum6QHg/u9s/MoHkZfuo1pRGka1VlUHXF5kdew82BvOJVVGk0x8X0nbp+w==", + "dependencies": { + "bluebird": "^3.5.0", + "request-promise-core": "1.1.3", + "stealthy-require": "^1.1.1", + "tough-cookie": "^4.1.3" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "@cypress/request": "^3.0.0" + } + }, + "node_modules/@cypress/request-promise/node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findindex": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.findindex/-/array.prototype.findindex-2.2.4.tgz", + "integrity": "sha512-LLm4mhxa9v8j0A/RPnpQAP4svXToJFh+Hp1pNYl5ZD5qpB4zdx/D4YjpVcETkhFbUKWO3iGMVLvrOnnmkAJT6A==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==" + }, "node_modules/backend": { "resolved": "backend", "link": true }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" + }, + "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==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es-abstract": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "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==", + "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/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eventemitter3": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "engines": { + "node": "*" + } + }, + "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==", + "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/frontend": { "resolved": "frontend", "link": true + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "peer": true, + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "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==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-signature": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", + "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^2.0.2", + "sshpk": "^1.18.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, + "node_modules/jsprim": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", + "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/node-telegram-bot-api": { + "version": "0.66.0", + "resolved": "https://registry.npmjs.org/node-telegram-bot-api/-/node-telegram-bot-api-0.66.0.tgz", + "integrity": "sha512-s4Hrg5q+VPl4/tJVG++pImxF6eb8tNJNj4KnDqAOKL6zGU34lo9RXmyAN158njwGN+v8hdNf8s9fWIYW9hPb5A==", + "dependencies": { + "@cypress/request": "^3.0.1", + "@cypress/request-promise": "^5.0.0", + "array.prototype.findindex": "^2.0.2", + "bl": "^1.2.3", + "debug": "^3.2.7", + "eventemitter3": "^3.0.0", + "file-type": "^3.9.0", + "mime": "^1.6.0", + "pump": "^2.0.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "peer": true, + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request-promise-core": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", + "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", + "dependencies": { + "lodash": "^4.17.15" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "request": "^2.34" + } + }, + "node_modules/request/node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "peer": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/request/node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "peer": true, + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/request/node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "peer": true, + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/request/node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "peer": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/request/node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "peer": true, + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/request/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "peer": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==" + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/verror/node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==" } } } diff --git a/scripts/.env.telegram.example b/scripts/.env.telegram.example new file mode 100644 index 0000000..e2251f3 --- /dev/null +++ b/scripts/.env.telegram.example @@ -0,0 +1,15 @@ +# Telegram Bot Configuration Template +# +# Kopiere diese Datei zu .env.telegram und trage deine echten Werte ein: +# cp .env.telegram.example .env.telegram +# +# WICHTIG: .env.telegram NIEMALS committen! (ist in .gitignore) + +# Bot-Token von @BotFather +# Beispiel: 123456789:ABCdefGHIjklMNOpqrsTUVwxyz1234567890 +TELEGRAM_BOT_TOKEN=YOUR_BOT_TOKEN_HERE + +# Chat-ID der Telegram-Gruppe (negativ für Gruppen!) +# Ermitteln via: https://api.telegram.org/bot/getUpdates +# Beispiel: -1001234567890 +TELEGRAM_CHAT_ID=YOUR_CHAT_ID_HERE diff --git a/scripts/README.telegram.md b/scripts/README.telegram.md new file mode 100644 index 0000000..dc5bab8 --- /dev/null +++ b/scripts/README.telegram.md @@ -0,0 +1,506 @@ +# Telegram Bot Setup & Testing Guide + +## Übersicht + +Diese Anleitung beschreibt Schritt-für-Schritt, wie du den Telegram Bot für den Image Uploader erstellst und testest. + +**Phase 1:** Standalone-Test (ohne Backend-Integration) + +--- + +## Voraussetzungen + +- ✅ Windows 11 mit installiertem Telegram Desktop +- ✅ Telegram Account +- ✅ Node.js >= 18.x installiert +- ✅ Zugriff auf dieses Git-Repository + +--- + +## 1. Telegram Bot erstellen + +### 1.1 BotFather öffnen + +1. **Telegram Desktop** öffnen +2. In der Suche eingeben: `@BotFather` +3. Chat mit **BotFather** öffnen (offizieller Bot mit blauem Haken ✓) + +### 1.2 Neuen Bot erstellen + +**Commands im BotFather-Chat eingeben:** + +``` +/newbot +``` + +**BotFather fragt nach Namen:** +``` +Alright, a new bot. How are we going to call it? +Please choose a name for your bot. +``` + +**Antworten mit:** +``` +Werkstatt Image Uploader Bot +``` + +**BotFather fragt nach Username:** +``` +Good. Now let's choose a username for your bot. +It must end in `bot`. Like this, for example: TetrisBot or tetris_bot. +``` + +**Antworten mit (muss auf `bot` enden):** +``` +werkstatt_uploader_bot +``` + +⚠️ **Falls Username vergeben:** Anderen Namen wählen (z.B. `werkstatt_upload_bot`, `hobbyhimmel_uploader_bot`) + +### 1.3 Bot-Token speichern + +**BotFather antwortet mit:** +``` +Done! Congratulations on your new bot. +You will find it at t.me/werkstatt_uploader_bot. +You can now add a description, about section and profile picture for your bot. + +Use this token to access the HTTP API: +123456789:ABCdefGHIjklMNOpqrsTUVwxyz1234567890 + +Keep your token secure and store it safely, it can be used by anyone to control your bot. +``` + +**Token kopieren** (z.B. `123456789:ABCdefGHIjklMNOpqrsTUVwxyz1234567890`) + +➡️ **Diesen Token brauchst du gleich für `.env.telegram`!** + +--- + +## 2. Test-Telegram-Gruppe erstellen + +### 2.1 Neue Gruppe erstellen + +1. In Telegram: **Neuer Chat** → **Neue Gruppe** +2. Gruppennamen eingeben: `Werkstatt Upload Bot Test` +3. **Weiter** klicken +4. (Optional) Weitere Mitglieder hinzufügen (oder nur dich selbst) +5. **Erstellen** klicken + +### 2.2 Bot zur Gruppe hinzufügen + +1. Test-Gruppe öffnen +2. Auf Gruppennamen (oben) klicken → **Mitglieder hinzufügen** +3. Suche nach: `@werkstatt_uploader_bot` (dein Bot-Username) +4. Bot auswählen → **Hinzufügen** + +**Telegram zeigt:** +``` +werkstatt_uploader_bot wurde zur Gruppe hinzugefügt +``` + +### 2.3 Privacy Mode deaktivieren (WICHTIG!) + +⚠️ **Ohne diesen Schritt sieht der Bot keine Gruppennachrichten!** + +1. **BotFather** öffnen +2. Command eingeben: `/mybots` +3. Deinen Bot auswählen: `@werkstatt_uploader_bot` +4. **Bot Settings** klicken +5. **Group Privacy** klicken +6. **Turn off** klicken + +**BotFather bestätigt:** +``` +Privacy mode is disabled for . +All messages will now be sent to the bot. +``` + +### 2.4 Bot als Admin hinzufügen (optional, aber empfohlen) + +1. Gruppe öffnen → Gruppennamen klicken +2. **Administratoren** → **Administrator hinzufügen** +3. `@werkstatt_uploader_bot` auswählen +4. **Berechtigungen:** + - ✅ Nachrichten senden + - ❌ Alle anderen optional (nicht nötig) +5. **Speichern** + +--- + +## 3. Chat-ID ermitteln + +Die Chat-ID wird benötigt, um Nachrichten an die richtige Gruppe zu senden. + +### Methode 1: Via Telegram API (empfohlen) + +**Schritt 1:** Nachricht in Test-Gruppe senden + +- Öffne die Test-Gruppe +- Sende eine beliebige Nachricht (z.B. "Test") + +**Schritt 2:** Browser öffnen und folgende URL aufrufen: + +``` +https://api.telegram.org/bot/getUpdates +``` + +**Ersetze ``** mit deinem echten Token! + +**Beispiel:** +``` +https://api.telegram.org/bot123456789:ABCdefGHIjklMNOpqrsTUVwxyz/getUpdates +``` + +⚠️ **Wenn du `{"ok":true,"result":[]}` siehst (leeres result-Array):** + +Das bedeutet, der Bot hat noch keine Nachrichten empfangen. **Checkliste:** + +1. ✅ Hast du den Bot zur Gruppe hinzugefügt? (Schritt 2.2) +2. ✅ Hast du **NACH** dem Hinzufügen eine Nachricht gesendet? (Schritt 1) +3. ✅ War die Nachricht in der **richtigen Gruppe** (nicht im Bot-Direct-Chat)? + +**Lösung - Mach jetzt folgendes:** +- Telegram öffnen +- **Test-Gruppe** öffnen (nicht den Bot direkt!) +- Prüfe, ob der Bot als Mitglied angezeigt wird +- Sende eine neue Nachricht: "Test" +- **Sofort** zurück zum Browser → Seite neu laden (F5) + +**Schritt 3:** Im JSON-Response nach `chat` suchen: + +```json +{ + "ok": true, + "result": [ + { + "update_id": 123456789, + "message": { + "message_id": 1, + "from": { ... }, + "chat": { + "id": -1001234567890, // ← DAS IST DEINE CHAT-ID! + "title": "Werkstatt Upload Bot Test", + "type": "supergroup" + }, + "text": "Test" + } + } + ] +} +``` + +**Chat-ID kopieren** (z.B. `-1001234567890`) + +⚠️ **Wichtig:** Gruppen-Chat-IDs sind **negativ** und beginnen meist mit `-100`! + +### Methode 2: Via curl (Linux/WSL) + +```bash +curl "https://api.telegram.org/bot/getUpdates" | jq '.result[0].message.chat.id' +``` + +--- + +## 4. Environment-Datei erstellen + +### 4.1 Template kopieren + +```bash +cd scripts +cp .env.telegram.example .env.telegram +``` + +### 4.2 `.env.telegram` bearbeiten + +**Datei öffnen:** `scripts/.env.telegram` + +```bash +# Telegram Bot Configuration +TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz +TELEGRAM_CHAT_ID=-1001234567890 +``` + +**Deine echten Werte eintragen:** +- `TELEGRAM_BOT_TOKEN` → Token von BotFather (Schritt 1.3) +- `TELEGRAM_CHAT_ID` → Chat-ID aus Schritt 3 (negativ!) + +**Speichern** und schließen. + +--- + +## 5. Dependencies installieren + +```bash +cd scripts + +# package.json initialisieren (falls nicht vorhanden) +npm init -y + +# Telegram Bot API installieren +npm install node-telegram-bot-api dotenv +``` + +**Erwartete Ausgabe:** +``` +added 15 packages, and audited 16 packages in 2s +``` + +--- + +## 6. Test-Script ausführen + +### 6.1 Script starten + +```bash +node telegram-test.js +``` + +### 6.2 Erwartete Ausgabe (Erfolg) + +``` +🔧 Lade Telegram-Konfiguration... +✅ Konfiguration geladen! + +🤖 Verbinde mit Telegram Bot... +✅ Telegram Bot erfolgreich verbunden! + +Bot-Details: + Name: Werkstatt Image Uploader Bot + Username: @werkstatt_uploader_bot + ID: 1234567890 + +📤 Sende Test-Nachricht an Chat -1001234567890... +✅ Nachricht erfolgreich gesendet! + +Message-ID: 42 +``` + +### 6.3 Telegram-Gruppe prüfen + +**In der Test-Gruppe sollte jetzt erscheinen:** + +``` +🤖 Telegram Bot Test + +Dies ist eine Test-Nachricht vom Werkstatt Image Uploader Bot. + +Status: ✅ Erfolgreich verbunden! +Zeitstempel: 2025-11-29 14:23:45 + +--- +ℹ️ Dieser Bot sendet automatische Benachrichtigungen für den Image Uploader. +``` + +--- + +## 7. Troubleshooting + +### ❌ Fehler: "Error: Unauthorized (401)" + +**Ursache:** Bot-Token ist falsch oder ungültig + +**Lösung:** +1. BotFather öffnen +2. `/token` eingeben +3. Deinen Bot auswählen +4. Neuen Token kopieren +5. `.env.telegram` aktualisieren +6. Script erneut starten + +--- + +### ❌ Fehler: "Bad Request: chat not found" + +**Ursache:** Chat-ID ist falsch + +**Lösung:** +1. Test-Gruppe öffnen +2. Neue Nachricht senden +3. Chat-ID erneut ermitteln (Schritt 3) +4. `.env.telegram` aktualisieren +5. Script erneut starten + +--- + +### ❌ Fehler: "Error: ETELEGRAM: 403 Forbidden" + +**Ursache:** Bot wurde aus der Gruppe entfernt oder kann nicht posten + +**Lösung:** +1. Test-Gruppe öffnen +2. Prüfen, ob Bot noch Mitglied ist +3. Falls nicht: Bot erneut hinzufügen (Schritt 2.2) +4. Falls ja: Bot als Admin hinzufügen (Schritt 2.3) +5. Script erneut starten + +--- + +### ❌ Fehler: "Cannot find module 'node-telegram-bot-api'" + +**Ursache:** Dependencies nicht installiert + +**Lösung:** +```bash +cd scripts +npm install +node telegram-test.js +``` + +--- + +### ❌ Fehler: "TELEGRAM_BOT_TOKEN is not defined" + +**Ursache:** `.env.telegram` fehlt oder nicht korrekt + +**Lösung:** +1. Prüfen, ob `.env.telegram` existiert: `ls -la scripts/.env.telegram` +2. Falls nicht: Template kopieren (Schritt 4.1) +3. Werte eintragen (Schritt 4.2) +4. Script erneut starten + +--- + +## 8. Erweiterte Tests + +### Test 1: Formatierung mit Emojis + +**Script anpassen:** `telegram-test.js` + +```javascript +const message = ` +📸 Neuer Upload! + +Uploader: Max Mustermann +Bilder: 12 +Gruppe: 2024 - Schweißkurs November + +Workshop: ✅ Ja +Social Media: 📘 Instagram, 🎵 TikTok + +🔗 Zur Freigabe: https://internal.hobbyhimmel.de/moderation +`; + +bot.sendMessage(chatId, message); +``` + +**Ausführen:** +```bash +node telegram-test.js +``` + +**Telegram prüfen:** Emojis sollten korrekt angezeigt werden + +--- + +### Test 2: HTML-Formatierung + +**Script anpassen:** + +```javascript +const message = ` +🤖 Telegram Bot Test + +HTML-Formatierung funktioniert! + +Status: ✅ Erfolgreich + +Link zur Website +`; + +bot.sendMessage(chatId, message, { parse_mode: 'HTML' }); +``` + +**Ausführen & prüfen:** Fetter Text, kursiver Text, Code, Link + +--- + +### Test 3: Markdown-Formatierung + +**Script anpassen:** + +```javascript +const message = ` +*🤖 Telegram Bot Test* + +_Markdown-Formatierung funktioniert!_ + +\`Status: ✅ Erfolgreich\` + +[Link zur Website](https://hobbyhimmel.de) +`; + +bot.sendMessage(chatId, message, { parse_mode: 'Markdown' }); +``` + +--- + +## 9. Sicherheit + +### ⚠️ Wichtig! + +- ❌ **NIEMALS** `.env.telegram` committen! +- ❌ **NIEMALS** Bot-Token öffentlich teilen! +- ✅ `.env.telegram` ist in `.gitignore` eingetragen +- ✅ Nur `.env.telegram.example` (ohne echte Tokens) committen + +### Bot-Token kompromittiert? + +**Falls Token versehentlich exposed:** + +1. BotFather öffnen +2. `/revoke` eingeben +3. Deinen Bot auswählen +4. **Neuen Token** kopieren +5. `.env.telegram` aktualisieren +6. Alle Services neu starten + +--- + +## 10. Nächste Schritte + +### ✅ Phase 1 abgeschlossen? + +Checklist: +- [x] Bot erstellt +- [x] Test-Gruppe erstellt +- [x] Bot zur Gruppe hinzugefügt +- [x] Chat-ID ermittelt +- [x] `.env.telegram` konfiguriert +- [x] `npm install` erfolgreich +- [x] `node telegram-test.js` läuft ohne Fehler +- [x] Test-Nachricht in Telegram empfangen + +### ➡️ Weiter zu Phase 2 + +**Backend-Integration:** +1. `TelegramNotificationService.js` erstellen +2. Service in Docker Dev Environment integrieren +3. ENV-Variablen in Backend übertragen +4. Unit-Tests schreiben + +**Siehe:** `FeatureRequests/FEATURE_PLAN-telegram.md` + +--- + +## Referenzen + +- [Telegram Bot API Dokumentation](https://core.telegram.org/bots/api) +- [node-telegram-bot-api (npm)](https://www.npmjs.com/package/node-telegram-bot-api) +- [BotFather Commands](https://core.telegram.org/bots#botfather) + +--- + +## Support + +**Bei Problemen:** +1. Troubleshooting-Sektion durchlesen (Schritt 7) +2. Telegram Bot API Logs prüfen +3. BotFather `/mybots` → Bot auswählen → API Token prüfen +4. Chat-ID erneut ermitteln + +**Erfolgreicher Test? 🎉** +```bash +git add scripts/ +git commit -m "feat: Add Telegram Bot standalone test (Phase 1)" +``` diff --git a/scripts/git-hooks/pre-commit b/scripts/git-hooks/pre-commit index 5cb1636..b514df8 100755 --- a/scripts/git-hooks/pre-commit +++ b/scripts/git-hooks/pre-commit @@ -39,6 +39,8 @@ changed = False cookie_pattern = re.compile(r'(\-\s*ADMIN_SESSION_COOKIE_SECURE\s*=\s*)([^\n\r]+)') secret_pattern = re.compile(r'(\-\s*ADMIN_SESSION_SECRET\s*=\s*)([^\n\r]+)') +telegram_token_pattern = re.compile(r'(\-\s*TELEGRAM_BOT_TOKEN\s*=\s*)([^\n\r${}]+)') +telegram_chat_pattern = re.compile(r'(\-\s*TELEGRAM_CHAT_ID\s*=\s*)(-?\d{10,})') def ensure_entry(text, *, pattern, value, anchor_line, expected_line, label): match = pattern.search(text) @@ -80,6 +82,18 @@ if secret_expected not in new_text: print('ERROR: Failed to ensure ADMIN_SESSION_SECRET uses environment variable in docker-compose.yml', file=sys.stderr) sys.exit(4) +telegram_token_match = telegram_token_pattern.search(new_text) +if telegram_token_match and telegram_token_match.group(2).strip() not in ['${TELEGRAM_BOT_TOKEN}', '']: + print(f'ERROR: TELEGRAM_BOT_TOKEN contains hardcoded secret: {telegram_token_match.group(2)[:20]}...', file=sys.stderr) + print(' Use ${TELEGRAM_BOT_TOKEN} placeholder instead!', file=sys.stderr) + sys.exit(5) + +telegram_chat_match = telegram_chat_pattern.search(new_text) +if telegram_chat_match: + print(f'ERROR: TELEGRAM_CHAT_ID contains hardcoded value: {telegram_chat_match.group(2)}', file=sys.stderr) + print(' Use ${TELEGRAM_CHAT_ID} placeholder instead!', file=sys.stderr) + sys.exit(6) + if changed: path.write_text(new_text) print('UPDATED') diff --git a/scripts/package.json b/scripts/package.json new file mode 100644 index 0000000..a10136e --- /dev/null +++ b/scripts/package.json @@ -0,0 +1,20 @@ +{ + "name": "telegram-test-scripts", + "version": "1.0.0", + "description": "Standalone Telegram Bot Testing Scripts for Image Uploader", + "main": "telegram-test.js", + "scripts": { + "test": "node telegram-test.js" + }, + "keywords": [ + "telegram", + "bot", + "testing" + ], + "author": "Werkstatt Hobbyhimmel", + "license": "ISC", + "dependencies": { + "dotenv": "^17.2.3", + "node-telegram-bot-api": "^0.66.0" + } +} diff --git a/scripts/telegram-test.js b/scripts/telegram-test.js new file mode 100644 index 0000000..71613c8 --- /dev/null +++ b/scripts/telegram-test.js @@ -0,0 +1,166 @@ +#!/usr/bin/env node + +/** + * Telegram Bot Standalone Test Script + * + * Testet die grundlegende Funktionalität des Telegram Bots + * ohne Integration in das Backend. + * + * Phase 1: Bot Setup & Standalone-Test + * + * Usage: + * node telegram-test.js + * + * Prerequisites: + * - .env.telegram mit TELEGRAM_BOT_TOKEN und TELEGRAM_CHAT_ID + * - npm install node-telegram-bot-api dotenv + */ + +require('dotenv').config({ path: '.env.telegram' }); +const TelegramBot = require('node-telegram-bot-api'); + +// ============================================================================= +// Configuration +// ============================================================================= + +const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN; +const CHAT_ID = process.env.TELEGRAM_CHAT_ID; + +// ============================================================================= +// Validation +// ============================================================================= + +console.log('🔧 Lade Telegram-Konfiguration...'); + +if (!BOT_TOKEN) { + console.error('❌ FEHLER: TELEGRAM_BOT_TOKEN ist nicht definiert!'); + console.error(''); + console.error('Bitte .env.telegram erstellen und Bot-Token eintragen:'); + console.error(''); + console.error(' TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz'); + console.error(' TELEGRAM_CHAT_ID=-1001234567890'); + console.error(''); + console.error('Siehe: scripts/README.telegram.md'); + process.exit(1); +} + +if (!CHAT_ID) { + console.error('❌ FEHLER: TELEGRAM_CHAT_ID ist nicht definiert!'); + console.error(''); + console.error('Bitte .env.telegram erstellen und Chat-ID eintragen:'); + console.error(''); + console.error(' TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz'); + console.error(' TELEGRAM_CHAT_ID=-1001234567890'); + console.error(''); + console.error('Chat-ID ermitteln: https://api.telegram.org/bot/getUpdates'); + console.error('Siehe: scripts/README.telegram.md'); + process.exit(1); +} + +console.log('✅ Konfiguration geladen!'); +console.log(''); + +// ============================================================================= +// Bot Initialization +// ============================================================================= + +console.log('🤖 Verbinde mit Telegram Bot...'); + +// Create bot instance (polling disabled for one-off script) +const bot = new TelegramBot(BOT_TOKEN, { polling: false }); + +// ============================================================================= +// Main Test Function +// ============================================================================= + +async function runTest() { + try { + // Step 1: Verify bot connection + const botInfo = await bot.getMe(); + + console.log('✅ Telegram Bot erfolgreich verbunden!'); + console.log(''); + console.log('Bot-Details:'); + console.log(` Name: ${botInfo.first_name}`); + console.log(` Username: @${botInfo.username}`); + console.log(` ID: ${botInfo.id}`); + console.log(''); + + // Step 2: Prepare test message + const timestamp = new Date().toLocaleString('de-DE', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + }); + + const message = ` +🤖 Telegram Bot Test + +Dies ist eine Test-Nachricht vom Werkstatt Image Uploader Bot. + +Status: ✅ Erfolgreich verbunden! +Zeitstempel: ${timestamp} + +--- +ℹ️ Dieser Bot sendet automatische Benachrichtigungen für den Image Uploader. +`.trim(); + + // Step 3: Send test message + console.log(`📤 Sende Test-Nachricht an Chat ${CHAT_ID}...`); + + const sentMessage = await bot.sendMessage(CHAT_ID, message); + + console.log('✅ Nachricht erfolgreich gesendet!'); + console.log(''); + console.log(`Message-ID: ${sentMessage.message_id}`); + console.log(''); + console.log('🎉 Test erfolgreich abgeschlossen!'); + console.log(''); + console.log('➡️ Nächste Schritte:'); + console.log(' 1. Telegram-Gruppe öffnen und Nachricht prüfen'); + console.log(' 2. Verschiedene Nachrichtenformate testen (siehe README.telegram.md)'); + console.log(' 3. Phase 2 starten: Backend-Integration'); + + } catch (error) { + console.error('❌ FEHLER beim Senden der Nachricht:'); + console.error(''); + + if (error.response && error.response.body) { + const errorData = error.response.body; + console.error(`Error Code: ${errorData.error_code}`); + console.error(`Description: ${errorData.description}`); + console.error(''); + + // Helpful error messages + if (errorData.error_code === 401) { + console.error('💡 Lösung: Bot-Token ist ungültig oder falsch.'); + console.error(' → BotFather öffnen und Token prüfen'); + console.error(' → .env.telegram aktualisieren'); + } else if (errorData.error_code === 400 && errorData.description.includes('chat not found')) { + console.error('💡 Lösung: Chat-ID ist falsch oder ungültig.'); + console.error(' → Chat-ID erneut ermitteln: https://api.telegram.org/bot/getUpdates'); + console.error(' → Neue Nachricht in Gruppe senden, dann getUpdates aufrufen'); + console.error(' → .env.telegram aktualisieren'); + } else if (errorData.error_code === 403) { + console.error('💡 Lösung: Bot hat keine Berechtigung, in diese Gruppe zu schreiben.'); + console.error(' → Bot zur Gruppe hinzufügen'); + console.error(' → Bot als Admin hinzufügen (empfohlen)'); + } + } else { + console.error(error.message); + } + + console.error(''); + console.error('📖 Siehe Troubleshooting: scripts/README.telegram.md'); + process.exit(1); + } +} + +// ============================================================================= +// Execute Test +// ============================================================================= + +runTest();