LImgDLer

Auto-DL

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name     LImgDLer
// @version  1.11
// @grant    GM_download
// @grant    GM_addStyle
// @include  https://*.ladies.de/Sex-Anzeigen/*
// @include  https://ladies.de/Sex-Anzeigen/*
// @include  https://escorts24.de/*
// @connect video1.ladies.de
// @connect ladies.de
// @connect escorts24.de
// @namespace https://greasyfork.org/users/290665
// @description Auto-DL
// ==/UserScript==
/*jslint es6 */
"use strict";
let downloadFolder;
const SCRIPT_VERSION = (typeof GM_info !== 'undefined' && GM_info.script && GM_info.script.version)
    ? String(GM_info.script.version)
    : 'unknown';
let phonews;
let NAMERAW, PHONE;
if (typeof jQuery === 'undefined') {
    console.error('LImgDLer: jQuery not found. Aborting.');
} else {
    jQuery(function () {
        hideSpam();
        setupMutationObserver();
        if (jQuery('.kuenstlername').length) { // modern mode
            NAMERAW = jQuery('.kuenstlername').first().text();
            PHONE = jQuery('.contacts-data strong').first().text();
        } else if (jQuery('.auftrag-name h3').length) { // themenladies
            NAMERAW = jQuery('.auftrag-name h3').first().text();
            PHONE = jQuery('p.telefon strong').first().text();
        } else if (jQuery('div.full_pad.bigfont strong').length) { // classic
            NAMERAW = jQuery('div.full_pad.bigfont strong').text();
            PHONE = jQuery('.div_td_last.lalign.midfont.itxt_pad.icon_text').eq(1).text();
        }
        if (!PHONE || !NAMERAW) {
            console.warn('LImgDLer: some selectors not found; proceeding with defaults.');
        }
        // Coerce and sanitize values, use safe defaults when missing
        PHONE = PHONE ? String(PHONE).replace(/\s\/\s/g, '-') : '';
        let NAME = NAMERAW ? String(NAMERAW).replace(/[^\w-\(\)äöüß+ ]/ig, '') : '';
        downloadFolder = (NAME || PHONE) ? (NAME + ' - ' + PHONE) : 'unknown';
        phonews = PHONE.replace(/[ \-]+/g, '');
        // Build search term defensively (skip empty tokens)
        const _terms = [];
        if (NAME) _terms.push(`\"${NAME}\"`);
        if (PHONE) _terms.push(`\"${PHONE}\"`);
        if (phonews) _terms.push(phonews);
        let SEARCHTERM = _terms.length ? `(${_terms.join(' OR ')})` : '';

        let dlArea = jQuery('<div id="LImgDLer">'
            + '<div class="title"><span class="Llogo">LImgDLer<span class="Lversion">' + SCRIPT_VERSION + '</span></span> '
            + '<button id="dlbutton">📥 Download <b>' + downloadFolder + '</b></button>'
            + '<button id="lhbutton">🔍 search LH</button>'
            + '<button id="copybutton">📋 copy Text</button>'
            + '</div></div>');

        jQuery('body').on('click', '#lhbutton', function (event) {
            event.stopPropagation();
            event.preventDefault();

            let url = 'https://www.google.de/search?as_sitesearch=lusthaus.cc&q=';
            url += encodeURIComponent(SEARCHTERM);
            window.open(url);
        });

        jQuery(dlArea).find('button').data('name', downloadFolder);
        jQuery('body').on('click', '#dlbutton', function (event) {
            startDownloads(jQuery(event.currentTarget).data('name'));
        });
        jQuery('body').on('click', '#copybutton', function (event) {
            try {
                navigator.clipboard.writeText(adData()).catch(function (e) {
                    // older browsers might reject promises
                    throw e;
                });
            } catch (e) {
                // fallback: open a prompt so user can copy manually
                let text = adData();
                try { window.prompt('Copy ad data (Ctrl+C, Enter):', text); } catch (ee) { alert('Copy failed, please copy manually:\n' + text); }
            }
        });

        if (jQuery('.auftrag-title').length) { // modern mode
            addStyle();
            jQuery('.auftrag-title').first().append(dlArea);
        } else if (jQuery('div#content div.container').length) { // themenladies
            jQuery('div#content div.container').first().append(dlArea);
            addStyle("themen");
        } else if (jQuery('div.sitemenu').length) { // classic
            jQuery('div.sitemenu').append(dlArea);
            addStyle("classic");
        }
        jQuery('#lhbutton').attr('title', SEARCHTERM);
    });
}

function startDownloads(folderName) {
    let downloadlist = [];
    if (jQuery('.rsNav img').length) {
        jQuery('.rsNav img').each(function () {
            let link = jQuery(this).attr('src');
            if (link.match(/-FK\./)) {
                return; // skip videos
            }
            link = link.replace(/\?.*/, '');
            downloadlist.push(link.replace(/(\d+)-F(\d+)\./, '$1-A$2.'));
        });
        jQuery('a.gallery_video').each(function () { // get videos
            downloadlist.push(jQuery(this).attr('href'));
        });
        dlAll(folderName, downloadlist);
    } else {
        let images = jQuery('.photo-slide img');
        images.each(function () {
            let imgURL = jQuery(this).attr('src');
            downloadlist.push(imgURL);
        });
        let videos = jQuery('.video-slide lds-video-player');
        videos.each(function () {
            let videoOptions = jQuery(this).attr('options');
            if (videoOptions) {
                try {
                    let options = JSON.parse(videoOptions);
                    if (options && options.sources && options.sources.length) {
                        downloadlist.push(...options.sources);
                    }
                } catch (e) {
                    console.warn('Failed to parse video options:', videoOptions);
                }
            }
        });
        dlAll(folderName, downloadlist);
    }
}

function dlAll(path, downloadlist) {
    jQuery('#LImgDLerdialog').remove();
    const dialog = jQuery('<div id="LImgDLerdialog" title="Download to ' + path + ' ..."></div>').appendTo(jQuery('#LImgDLer'));

    for (let URL of downloadlist) {
        let originalName = (URL || '').replace(/.*\//, '').replace(/\?.*/g, '');
        let filename = originalName || 'file';
        let safeFolder = (path || '').replace(/[^\w\-\(\)\ ]/g, '_');
        // Try folder-style name first (safeFolder/filename) to preserve original behavior.
        // If the userscript manager doesn't support folders, retry with a flat fallback.
        let fileWithSlash = safeFolder ? (safeFolder + '/' + filename) : filename;
        let fallbackFile = safeFolder ? (safeFolder + ' - ' + filename) : filename;
        URL = (URL || '').replace(/^\/\//, 'https://');
        let line = jQuery('<div class="RLDL" data-name="' + filename + '">' + filename + '</div>\n').appendTo(dialog);

        (function (url, initialPath, fallbackPath, filenameLocal, linediv) {
            const attemptDownload = function (filepath, triedFallback) {
                GM_download({
                    url: url,
                    name: filepath,
                    saveAs: false,
                    onerror: function (err) {
                        let errText = 'Unknown error';
                        try {
                            if (!err) errText = 'no error object';
                            else if (err.error) errText = err.error;
                            else if (err.message) errText = err.message;
                            else errText = JSON.stringify(err);
                        } catch (ee) { errText = String(err); }
                        // If we tried the folder path and it failed, retry with fallback once.
                        if (!triedFallback && filepath === initialPath && fallbackPath !== initialPath) {
                            console.warn('LImgDLer: folder-style download failed, retrying flat filename.', err);
                            attemptDownload(fallbackPath, true);
                            return;
                        }
                        jQuery(linediv).append('<span class="download_error">ERROR: ' + errText + '</span>');
                        console.error('LImgDLer download error:', err);
                    },
                    onload: function () {
                        jQuery(linediv).append('<span class="download_ok">✓</span>');
                    }
                });
            };

            // Start with folder-style path, fallback will be attempted automatically on error.
            attemptDownload(fileWithSlash, false);
        })(URL, fileWithSlash, fallbackFile, filename, line);

    }
}
function adData() {
    // Defensive phone formatting (unique variants)
    const raw = (phonews || '').trim();
    const local = raw.replace(/\+49\(0\)/, '0');
    let dash = local.replace(/^(01[67].)/, '$1-');
    dash = dash.replace(/^(015510)/, '$1-');
    dash = dash.replace(/^(015..)(\d+)$/, '$1-$2');
    const intl = raw.replace(/^0([1-9])/, '+49$1');
    const phones = Array.from(new Set([dash, local, intl].filter(Boolean)));

    // Attributes
    const attrs = [];
    jQuery('.attribute-column > .row').each(function () {
        const key = jQuery(this).find('strong').first().text().trim();
        const vals = [];
        jQuery(this).find('p').each(function () { vals.push(jQuery(this).text().trim()); });
        const v = vals.join(', ');
        if (key && v) attrs.push(`${key}: [B]${v}[/B]`);
    });

    // Club and address
    const clubname = jQuery('[property=locationName]').first().text().trim();
    const clublink = jQuery('a[property="ladyLocation"]').attr('href') || '';
    const club = clubname ? `[URL=https://ladies.de${clublink}]${clubname}[/URL]` : '';
    const street = jQuery('[itemprop=address] [property=streetAddress]').first().text() || '';
    const postal = jQuery('[itemprop=address] [itemprop=postalCode]').first().attr('content') || '';
    const city = jQuery('[itemprop=address] [itemprop=addressLocality]').first().text() || jQuery('.contacts-data .address-details').first().text().replace(/[\s\n]+/g, ' ').trim() || '';
    const addressText = [street, postal, city].map(s => (s||'').trim()).filter(Boolean).join(' ');
    const addressBB = addressText ? `[URL=https://www.google.de/maps/place/${encodeURIComponent(addressText)}]${addressText}[/URL]` : '';

    // Description: concatenate multiple blocks if present, keep block separation
    let description = '';
    const descBlocks = [];
    const descContainer = jQuery('.einzelansicht-text div');
    if (descContainer.length) {
        // prefer direct child blocks (div, p, section)
        descContainer.children('div, p, section').each(function () {
            let html = jQuery(this).clone().find('span').remove().end().html() || '';
            // preserve <br> as newlines
            html = html.replace(/<br\s*\/?>/gi, '\n');
            // strip remaining tags
            let txt = html.replace(/<[^>]+>/g, '');
            // decode HTML entities
            try { txt = jQuery('<textarea/>').html(txt).text(); } catch (er) {}
            txt = txt.replace(/\n\s*\n+/g, '\n\n').trim();
            if (txt) descBlocks.push(txt);
        });
        // fallback: if no child blocks, use html content and split paragraphs
        if (!descBlocks.length) {
            const fullHtml = descContainer.html() || '';
            if (fullHtml) {
                let tmp = fullHtml.replace(/<br\s*\/?>/gi, '\n');
                tmp = tmp.replace(/<[^>]+>/g, '');
                try { tmp = jQuery('<textarea/>').html(tmp).text(); } catch (er) {}
                const parts = tmp.split(/\n\s*\n+/).map(s => s.trim()).filter(Boolean);
                if (parts.length) descBlocks.push(...parts);
            }
        }
        description = descBlocks.join('\n\n');
    }

    // Services
    const services = [];
    jQuery('.profil .column-group').each(function () {
        const key = jQuery(this).find('strong').first().text().trim();
        const vals = [];
        jQuery(this).find('.attribute-item').each(function () { vals.push(jQuery(this).text().trim()); });
        const v = vals.join(', ');
        if (key && v) services.push(`${key}: [B]${v}[/B]`);
    });

    const title = NAMERAW || downloadFolder || '';
    const pageURL = window.location.href || '';

    // Build BBCode with German headings, single-line attributes and size wrappers
    const parts = [];
    parts.push('Infoservice');
    parts.push('[QUOTE]');
    if (title) parts.push(`[URL=${pageURL}][B]${title}[/B][/URL]`);
    // size wrapper for compact content
    parts.push('[SIZE="1"]');
    if (description) {
        parts.push(description);
        parts.push('\n'); // ensure separation from next section
    }
    if (attrs.length) {
        parts.push(attrs.join('\n'));
    }
    if (services.length) {
        parts.push('\n[B]Service[/B]');
        parts.push(services.join('\n'));
    }
    if (phones.length) {
        parts.push('\n[B]Telefon[/B]');
        // large primary phone, others inline
        parts.push(`[B][SIZE=3]${phones[0]}[/SIZE][/B]` + (phones.length > 1 ? (', ' + phones.slice(1).join(' / ')) : ''));
    }
    if (club || addressBB) {
        parts.push('\n[B]Adresse[/B]');
        parts.push(((club ? club + ', ' : '') + (addressBB || '')).trim());
    }
    parts.push('[/SIZE]');
    parts.push('[/QUOTE]');

    const dataHTML = parts.join('\n').replace(/ {2,}/g, ' ').replace(/\n{3,}/g, '\n\n');
    return dataHTML;
}
function hideSpam() {
    jQuery('div.anzeige').has('span.webcam-markierung').remove();
    jQuery('div.anzeige').has('div.closed-info').css('opacity', '0.3');
    jQuery('section#page-top-sol').remove();
}
function setupMutationObserver() {
    const observer = new MutationObserver(hideSpam);
    let target = jQuery('ul.pagination');
    if (target.length) {
        observer.observe(jQuery(target).get(0), {
            attributes: true,
            childList: true,
            subtree: true
        });
    }
}

function addStyle(mode) {
    let styles = [];
    styles["classic"] = `
#LImgDLer {
    left: 174px;
    top: 109px;
    right: 202px;
    line-height: 10px;
    font-size:9px;
}
.Llogo {
    font-size: 16px;
    top: 2px;
}
#LImgDLer button {
    padding: 4px 12px;
    font-size:11px;
}
button:hover {
    cursor:pointer;
}
    `;
    styles["themen"] = `
div#content div.container {
  position:relative;
}
#LImgDLer {
    left: 16px;
    top: 50px;
    right: 15px;
    line-height: 10px;
}
.Llogo {
    font-size: 17px;
    top: 1px;
}
#LImgDLer button {
    padding: 4px 12px;
}

    `;

    GM_addStyle(`
.download_error {
    color: white;
    background-color: #880010;
    padding: 1px 4px;
    border-radius: 2px;
    margin: 0px 4px;
}

.download_ok {
    color: white;
    background-color: #10a020;
    padding: 1px 4px;
    border-radius: 2px;
    margin: 0px 4px;
}

#LImgDLer {
    max-height: 180px;
    font-size: 10px;
    position: absolute;
    left: 240px;
    top: 0;
    right: 30px;
}
#LImgDLer button {
    background-color: #e28c13;
    border: none;
    margin: 0px 4px;
    padding: 10px 12px;
    line-height: 15px;
    font-size: 15px;
    color: white;
}
#LImgDLer > div.title {
    background-color:#6f789f;
}

#LImgDLerdialog {
    background-color: rgba(255, 255, 255, .52);
    overflow: auto;
    column-width: 110px;
    padding:4px;
}
.Llogo {
    padding: 0 12px;
    color: #e69536;
    font-size: 24px;
    font-weight: 700;
    text-shadow: 0 0 1.5px black;
    font-style: italic;
    top: 3px;
    position: relative;
    line-height: 12px;
}
.Lversion {
    position: absolute;
    top: -6px;
    right: -6px;
    background: #9ba4cc;
    color: white;
    padding: 2px 6px;
    border-radius: 10px;
    font-size: 10px;
    font-weight: 700;
    line-height: 1;
    box-shadow: 1px 2px 6px #00000050;
}
`);
    if (mode && styles[mode]) {
        GM_addStyle(styles[mode]);
    }
}