const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); const { request } = require('undici'); const BASE_URL = 'https://vidmoly.me'; const USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'; const UPLOAD_TIMEOUT = 1800000; // 30 min const RESULT_POLL_ATTEMPTS = 10; const RESULT_POLL_DELAY_MS = 2000; /** * XFileSharing-based upload for Vidmoly (login + form upload) */ class VidmolyUploader { constructor() { this.cookies = new Map(); } _cookieHeader() { return Array.from(this.cookies.entries()) .map(([k, v]) => `${k}=${v}`) .join('; '); } _parseCookiesFromHeaders(headers) { // Handle both undici response headers and fetch Headers let setCookies; if (typeof headers.getSetCookie === 'function') { setCookies = headers.getSetCookie(); } else if (headers['set-cookie']) { setCookies = Array.isArray(headers['set-cookie']) ? headers['set-cookie'] : [headers['set-cookie']]; } else { return; } for (const raw of setCookies) { const pair = raw.split(';')[0]; const eq = pair.indexOf('='); if (eq > 0) { this.cookies.set(pair.substring(0, eq).trim(), pair.substring(eq + 1).trim()); } } } /** * Simple GET/POST using built-in fetch (handles redirects) */ async _fetch(url, opts = {}, _redirectCount = 0) { const MAX_REDIRECTS = 10; const headers = { 'User-Agent': USER_AGENT, ...(opts.headers || {}) }; if (this.cookies.size > 0) { headers['Cookie'] = this._cookieHeader(); } const res = await fetch(url, { ...opts, headers, redirect: 'manual' // handle manually to capture cookies from redirect responses }); this._parseCookiesFromHeaders(res.headers); // Follow redirects manually (to capture cookies at each hop) if ([301, 302, 303, 307, 308].includes(res.status)) { // Drain body to prevent connection leak try { await res.text(); } catch {} if (_redirectCount >= MAX_REDIRECTS) { throw new Error('Zu viele Redirects'); } const location = res.headers.get('location'); if (location) { const nextUrl = new URL(location, url).href; return this._fetch(nextUrl, { ...opts, method: 'GET', body: undefined }, _redirectCount + 1); } } return res; } /** * Login to Vidmoly */ async login(username, password) { // First GET the main page to get initial cookies const initRes = await this._fetch(BASE_URL); await initRes.text(); // POST login const loginData = new URLSearchParams({ op: 'login', login: username, password: password, redirect: '' }); const res = await this._fetch(BASE_URL, { method: 'POST', body: loginData.toString(), headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Referer': BASE_URL } }); const body = await res.text(); if (body.includes('Incorrect Login or Password')) { throw new Error('Vidmoly Login fehlgeschlagen: Falscher Username oder Passwort'); } // Check for login cookie const hasSession = this.cookies.has('login') || this.cookies.has('xfsts') || this.cookies.size > 1; if (!hasSession) { throw new Error('Vidmoly Login fehlgeschlagen: Keine Session erhalten'); } } /** * Get upload form parameters from the upload page */ async getUploadParams() { const res = await this._fetch(`${BASE_URL}/?op=upload`); const html = await res.text(); // Parse hidden form fields from XFS upload form const params = {}; const inputRegex = /]*type=["']hidden["'][^>]*>/gi; let match; while ((match = inputRegex.exec(html)) !== null) { const tag = match[0]; const nameMatch = tag.match(/name=["']([^"']+)["']/); const valueMatch = tag.match(/value=["']([^"']*?)["']/); if (nameMatch) { params[nameMatch[1]] = valueMatch ? valueMatch[1] : ''; } } // Extract form action const formMatch = html.match(/