Weiter zum Inhalt
  • Home
  • Aktuell
  • Tags
  • 0 Ungelesen 0
  • Kategorien
  • Unreplied
  • Beliebt
  • GitHub
  • Docu
  • Hilfe
Skins
  • Hell
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dunkel
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Standard: (Kein Skin)
  • Kein Skin
Einklappen
ioBroker Logo

Community Forum

donate donate
  1. ioBroker Community Home
  2. Deutsch
  3. Skripten / Logik
  4. JavaScript
  5. ioBroker Forum Widget – Forum-Daten direkt in Visu

NEWS

  • Neues YouTube-Video: Visualisierung im Devices-Adapter
    BluefoxB
    Bluefox
    11
    1
    458

  • Neuer ioBroker-Blog online: Monatsrückblick März/April 2026
    BluefoxB
    Bluefox
    8
    1
    1.9k

  • Verwendung von KI bitte immer deutlich kennzeichnen
    HomoranH
    Homoran
    11
    1
    831

ioBroker Forum Widget – Forum-Daten direkt in Visu

Geplant Angeheftet Gesperrt Verschoben JavaScript
18 Beiträge 6 Kommentatoren 298 Aufrufe 5 Beobachtet
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • David G.D Online
    David G.D Online
    David G.
    schrieb am zuletzt editiert von David G.
    #1

    Ich wollte euch kurz ein kleines JS vorstellen was ich gestern Abend mehr oder weniger aus Langeweile mit Claude geschrieben habe.

    Es ruft die "Wichtigsten" Infos aus diesem Forum ab und schreibt diese in DPs, zusätzliche wird noch ein konfigurierbares Widget erstellt.

    Das Widget zeigt:

    • Eigene Benachrichtigungen (ungelesen/gelesen)
    • Ungelesene Topics mit Kategorie, Postanzahl und Autor
    • Eigene letzten Topics & Posts der letzten X Tage
    • Neueste Forum-Topics
    • Forum-Stats (Online-User, Themen, Beiträge)

    Alles als einzelne DPs verfügbar – nicht nur das Widget
    1000070548.jpg

    1000070142.jpg

    // ═══════════════════════════════════════════════════════════════
    //  ioBroker Forum Widget
    //  Autor:   David G.
    //  Version: 2.3.0
    //  Forum:   https://forum.iobroker.net/topic/84513/iobroker-forum-widget-forum-daten-direkt-in-visu
    //
    //  Zeigt Forum-Daten als Datenpunkte und HTML-Widget an:
    //  Benachrichtigungen, ungelesene Topics, eigene Posts & Topics,
    //  neueste Topics und Forum-Stats.
    // ═══════════════════════════════════════════════════════════════
    
    // ── Zugangsdaten ──────────────────────────────────────────────
    const FORUM_URL          = 'https://forum.iobroker.net';
    const USERNAME           = 'deine@email.de';
    const PASSWORD           = 'deinPasswort';
    const SIMPLE_API         = 'http://192.168.99.33:8087';
    
    // ── Datenpunkte ───────────────────────────────────────────────
    const BASE_DP            = '0_userdata.0.forum.iobroker.';
    
    // ── Intervall & Limits ────────────────────────────────────────
    const INTERVAL_MIN       = 15;
    const OWN_POST_DAYS      = 7;
    const OWN_POSTS_MAX      = 10;
    const OWN_TOPICS_MAX     = 5;
    const LATEST_TOPICS_MAX  = 20;
    const UNREAD_MAX         = 20;
    const NOTIFICATIONS_MAX  = 10;
    const MARK_BATCH_SIZE    = 10;
    
    // ── Sektionen ein/ausblenden ──────────────────────────────────
    const SHOW_STATS         = true;
    const SHOW_NOTIFICATIONS = true;
    const SHOW_UNREAD        = true;
    const SHOW_OWN_TOPICS    = true;
    const SHOW_OWN_POSTS     = true;
    const SHOW_LATEST        = true;
    
    // ── Widget Design ─────────────────────────────────────────────
    const BG_COLOR           = '#1e1e2e';
    const BG_OPACITY         = 1.0;
    const TEXT_COLOR         = '#ccc';
    const LINK_COLOR         = '#cdd6f4';
    const ACCENT_COLOR       = '#89b4fa';
    const BADGE_BG           = '#f38ba8';
    const BADGE_TEXT         = '#1e1e2e';
    const STATS_COLOR        = '#a6e3a1';
    const MUTED_COLOR        = '#6c7086';
    const DIVIDER_COLOR      = '#313244';
    
    // ═══════════════════════════════════════════════════════════════
    
    const axios = require('axios');
    var cookieJar = '';
    var csrfToken = '';
    
    function extractCookies(arr) {
        if (!arr) return '';
        return arr.map(function(c) { return c.split(';')[0]; }).join('; ');
    }
    
    function stripHtml(str) {
        return (str || '').replace(/<[^>]*>/g, '')
            .replace(/&lt;/g,'<').replace(/&gt;/g,'>')
            .replace(/&amp;/g,'&').replace(/&#x27;/g,"'");
    }
    
    function dp(path) { return BASE_DP + path; }
    
    function getOpen(id) {
        try {
            var s = getState(dp('ui.' + id + '.open'));
            return s && s.val === true;
        } catch(e) { return false; }
    }
    
    function hexToRgba(hex, opacity) {
        var r = parseInt(hex.slice(1,3), 16);
        var g = parseInt(hex.slice(3,5), 16);
        var b = parseInt(hex.slice(5,7), 16);
        return 'rgba(' + r + ',' + g + ',' + b + ',' + opacity + ')';
    }
    
    function apiGet(path, callback, retry) {
        axios.get(FORUM_URL + path, {
            headers: { Cookie: cookieJar },
            validateStatus: function(s) { return s < 500; }
        })
        .then(function(res) {
            if (res.status === 401 && !retry) {
                log('Session abgelaufen – logge neu ein... (' + path + ')');
                login(function() { apiGet(path, callback, true); });
                return;
            }
            try { callback(res.data); }
            catch(e) { log('Callback Fehler ' + path + ': ' + e, 'error'); }
        })
        .catch(function(err) { log('GET Fehler ' + path + ': ' + err, 'error'); });
    }
    
    // ── Paginierte Fetch-Funktionen ───────────────────────────────
    
    function fetchLatestTopicsLimited(callback) {
        var allTopics   = [];
        var pagesNeeded = Math.ceil(LATEST_TOPICS_MAX / 20);
        function fetchPage(page) {
            apiGet('/api/recent?page=' + page, function(d) {
                allTopics = allTopics.concat(d.topics || []);
                var totalPages = d.pageCount || 1;
                if (allTopics.length < LATEST_TOPICS_MAX && page < totalPages && page < pagesNeeded) {
                    setTimeout(function() { fetchPage(page + 1); }, 300);
                } else { callback(allTopics.slice(0, LATEST_TOPICS_MAX)); }
            });
        }
        fetchPage(1);
    }
    
    function fetchNotifsLimited(callback) {
        var allNotifs   = [];
        var pagesNeeded = Math.ceil(NOTIFICATIONS_MAX / 20);
        function fetchPage(page) {
            apiGet('/api/notifications?page=' + page, function(d) {
                allNotifs = allNotifs.concat(d.notifications || []);
                var totalPages = d.pageCount || 1;
                if (allNotifs.length < NOTIFICATIONS_MAX && page < totalPages && page < pagesNeeded) {
                    setTimeout(function() { fetchPage(page + 1); }, 300);
                } else { callback(allNotifs.slice(0, NOTIFICATIONS_MAX)); }
            });
        }
        fetchPage(1);
    }
    
    function fetchUnreadLimited(callback) {
        var allTopics   = [];
        var pagesNeeded = Math.ceil(UNREAD_MAX / 20);
        var user        = null;
        var totalCount  = 0;
        function fetchPage(page) {
            apiGet('/api/unread?page=' + page, function(d) {
                allTopics  = allTopics.concat(d.topics || []);
                totalCount = d.topicCount || allTopics.length;
                if (page === 1 && d.loggedInUser) {
                    user = { uid: d.loggedInUser.uid, username: d.loggedInUser.username, userslug: d.loggedInUser.userslug };
                }
                var totalPages = d.pageCount || 1;
                if (allTopics.length < UNREAD_MAX && page < totalPages && page < pagesNeeded) {
                    setTimeout(function() { fetchPage(page + 1); }, 300);
                } else { callback(allTopics.slice(0, UNREAD_MAX), totalCount, user); }
            });
        }
        fetchPage(1);
    }
    
    function fetchOwnPostsLimited(userslug, since, callback) {
        var allPosts    = [];
        var pagesNeeded = Math.ceil(OWN_POSTS_MAX / 20);
        var reachedDate = false;
        function fetchPage(page) {
            apiGet('/api/user/' + userslug + '/posts?page=' + page, function(d) {
                (d.posts || []).forEach(function(p) {
                    if (p.timestamp >= since) { allPosts.push(p); } else { reachedDate = true; }
                });
                var totalPages = d.pageCount || 1;
                if (!reachedDate && allPosts.length < OWN_POSTS_MAX && page < totalPages && page < pagesNeeded) {
                    setTimeout(function() { fetchPage(page + 1); }, 300);
                } else { callback(allPosts.slice(0, OWN_POSTS_MAX)); }
            });
        }
        fetchPage(1);
    }
    
    function fetchOwnTopicsLimited(userslug, callback) {
        var allTopics   = [];
        var pagesNeeded = Math.ceil(OWN_TOPICS_MAX / 20);
        function fetchPage(page) {
            apiGet('/api/user/' + userslug + '/topics?page=' + page, function(d) {
                allTopics = allTopics.concat(d.topics || []);
                var totalPages = d.pageCount || 1;
                if (allTopics.length < OWN_TOPICS_MAX && page < totalPages && page < pagesNeeded) {
                    setTimeout(function() { fetchPage(page + 1); }, 300);
                } else { callback(allTopics.slice(0, OWN_TOPICS_MAX)); }
            });
        }
        fetchPage(1);
    }
    
    // ── Mark as Read ──────────────────────────────────────────────
    
    function runBatch(items, handler, callback) {
        if (items.length === 0) { if (callback) callback(); return; }
        var total = items.length, completed = 0, index = 0;
        function nextBatch() {
            if (index >= total) return;
            var batchItems = items.slice(index, index + MARK_BATCH_SIZE);
            index += MARK_BATCH_SIZE;
            var batchDone = 0;
            batchItems.forEach(function(item) {
                handler(item, function() {
                    completed++; batchDone++;
                    if (batchDone >= batchItems.length) {
                        log(completed + '/' + total + ' erledigt');
                        if (completed >= total) { log('Fertig'); if (callback) callback(); }
                        else { setTimeout(nextBatch, 200); }
                    }
                });
            });
        }
        nextBatch();
    }
    
    function fetchAllUnread(callback) {
        var allTopics = [];
        function fetchPage(page) {
            apiGet('/api/unread?page=' + page, function(d) {
                allTopics = allTopics.concat(d.topics || []);
                log('Unread Seite ' + page + '/' + (d.pageCount || 1) + ' – ' + allTopics.length + ' geladen');
                if (page < (d.pageCount || 1)) { setTimeout(function() { fetchPage(page + 1); }, 300); }
                else { callback(allTopics); }
            });
        }
        fetchPage(1);
    }
    
    function fetchAllNotifs(callback) {
        var allNotifs = [];
        function fetchPage(page) {
            apiGet('/api/notifications?page=' + page, function(d) {
                allNotifs = allNotifs.concat(d.notifications || []);
                log('Notifs Seite ' + page + '/' + (d.pageCount || 1) + ' – ' + allNotifs.length + ' geladen');
                if (page < (d.pageCount || 1)) { setTimeout(function() { fetchPage(page + 1); }, 300); }
                else { callback(allNotifs); }
            });
        }
        fetchPage(1);
    }
    
    function markNotifsRead(notifs, callback) {
        var unread = notifs.filter(function(n) { return !n.read && n.path; });
        log('Markiere ' + unread.length + ' Notifications als gelesen...');
        runBatch(unread, function(n, done) { apiGet(n.path, function() { done(); }); }, callback);
    }
    
    function markUnreadRead(topics, callback) {
        log('Markiere ' + topics.length + ' Topics als gelesen...');
        runBatch(topics, function(t, done) { apiGet('/api/topic/' + t.slug, function() { done(); }); }, callback);
    }
    
    // ── Stats ─────────────────────────────────────────────────────
    
    function extractStats(data) {
        var stats = { online: '?', topics: '?', posts: '?' };
        try {
            var footer = data.widgets.footer[0].html;
            var vals = footer.match(/title="([^"]+)">/g) || [];
            if (vals[0]) stats.online = vals[0].replace('title="','').replace('">','');
            if (vals[2]) stats.topics = vals[2].replace('title="','').replace('">','');
            if (vals[3]) stats.posts  = vals[3].replace('title="','').replace('">','');
        } catch(e) {}
        return stats;
    }
    
    // ── Datenpunkte anlegen ───────────────────────────────────────
    
    function createDPs(callback) {
        var dps = [
            { id: 'info.connected',            val: false,  type: 'boolean', role: 'indicator', write: false },
            { id: 'info.lastUpdate',           val: '',     type: 'string',  role: 'text',       write: false },
            { id: 'info.lastError',            val: '',     type: 'string',  role: 'text',       write: false },
            { id: 'info.username',             val: '',     type: 'string',  role: 'text',       write: false },
            { id: 'stats.online',              val: '?',    type: 'string',  role: 'text',       write: false },
            { id: 'stats.topics',              val: '?',    type: 'string',  role: 'text',       write: false },
            { id: 'stats.posts',               val: '?',    type: 'string',  role: 'text',       write: false },
            { id: 'topics.latest',             val: '[]',   type: 'string',  role: 'json',       write: false },
            { id: 'unread.count',              val: 0,      type: 'number',  role: 'value',      write: false },
            { id: 'unread.list',               val: '[]',   type: 'string',  role: 'json',       write: false },
            { id: 'notifications.unreadCount', val: 0,      type: 'number',  role: 'value',      write: false },
            { id: 'notifications.unreadList',  val: '[]',   type: 'string',  role: 'json',       write: false },
            { id: 'notifications.list',        val: '[]',   type: 'string',  role: 'json',       write: false },
            { id: 'ownPosts.list',             val: '[]',   type: 'string',  role: 'json',       write: false },
            { id: 'ownTopics.list',            val: '[]',   type: 'string',  role: 'json',       write: false },
            { id: 'cmd.reload',                val: false,  type: 'boolean', role: 'button',     write: true  },
            { id: 'cmd.markAllRead',           val: false,  type: 'boolean', role: 'button',     write: true  },
            { id: 'cmd.markUnreadRead',        val: false,  type: 'boolean', role: 'button',     write: true  },
            { id: 'ui.stats.open',             val: false,  type: 'boolean', role: 'indicator',  write: true  },
            { id: 'ui.notifs.open',            val: false,  type: 'boolean', role: 'indicator',  write: true  },
            { id: 'ui.unread.open',            val: false,  type: 'boolean', role: 'indicator',  write: true  },
            { id: 'ui.owntopics.open',         val: false,  type: 'boolean', role: 'indicator',  write: true  },
            { id: 'ui.ownposts.open',          val: false,  type: 'boolean', role: 'indicator',  write: true  },
            { id: 'ui.latest.open',            val: false,  type: 'boolean', role: 'indicator',  write: true  },
            { id: 'widget.html',               val: '',     type: 'string',  role: 'html',       write: false }
        ];
    
        var done = 0;
        dps.forEach(function(d) {
            createState(dp(d.id), d.val, {
                name: d.id, type: d.type, role: d.role, read: true, write: d.write
            }, function() {
                done++;
                if (done >= dps.length) callback();
            });
        });
    }
    
    // ── Widget HTML ───────────────────────────────────────────────
    
    function makeSection(id, title, content, apiBase) {
        var isOpen  = getOpen(id);
        var onclick = "this.parentElement.classList.toggle('open');"
            + "fetch('" + apiBase + "ui." + id + ".open?value="
            + "'+this.parentElement.classList.contains('open'),{mode:'no-cors'});";
    
        return '<div class="nb-sec' + (isOpen ? ' open' : '') + '">'
            + '<div class="sec-hd" onclick="' + onclick + '" style="cursor:pointer;user-select:none">'
            + '<span class="sec-t">' + title + '</span>'
            + '<span class="sec-arrow">▶</span>'
            + '</div>'
            + '<div class="sec-body">' + content + '</div>'
            + '</div>';
    }
    
    function buildHtml(topics, unread, unreadTotal, notifs, stats, ownPosts, ownTopics, user) {
        var unreadNotifs = notifs.filter(function(n) { return !n.read; }).slice(0, NOTIFICATIONS_MAX);
        var updated      = new Date().toLocaleString('de-DE');
        var apiBase      = SIMPLE_API + '/set/' + BASE_DP;
    
        var css = '<style>'
            + '.nb{font-family:sans-serif;font-size:13px;color:' + TEXT_COLOR + ';background:' + hexToRgba(BG_COLOR, BG_OPACITY) + ';border-radius:12px;padding:12px;box-sizing:border-box}'
            + '.nb .hd{display:flex;justify-content:space-between;align-items:center;margin-bottom:2px}'
            + '.nb h2{margin:0;font-size:15px;color:' + TEXT_COLOR + '}'
            + '.nb .hd-btns{display:flex;gap:6px}'
            + '.nb .btn{background:transparent;border:1px solid ' + DIVIDER_COLOR + ';color:' + MUTED_COLOR + ';border-radius:6px;padding:2px 8px;cursor:pointer;font-size:11px}'
            + '.nb .btn:hover{color:' + TEXT_COLOR + ';border-color:' + ACCENT_COLOR + '}'
            + '.nb .usr{font-size:11px;color:' + ACCENT_COLOR + ';margin-bottom:2px}'
            + '.nb .upd{font-size:11px;color:' + MUTED_COLOR + ';margin-bottom:10px}'
            + '.nb .nb-sec{margin-bottom:12px}'
            + '.nb .sec-hd{display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid ' + DIVIDER_COLOR + ';padding-bottom:3px;margin-bottom:5px}'
            + '.nb .sec-hd:hover .sec-t{color:' + TEXT_COLOR + '}'
            + '.nb .sec-t{font-size:11px;font-weight:bold;color:' + ACCENT_COLOR + ';text-transform:uppercase;letter-spacing:1px}'
            + '.nb .sec-arrow{display:inline-block;transition:transform 0.2s;font-size:10px;color:' + MUTED_COLOR + '}'
            + '.nb .sec-body{max-height:0;overflow:hidden;transition:max-height 0.3s ease}'
            + '.nb .nb-sec.open .sec-body{max-height:5000px}'
            + '.nb .nb-sec.open .sec-arrow{transform:rotate(90deg)}'
            + '.nb table{width:100%;border-collapse:collapse}'
            + '.nb td{padding:4px;border-bottom:1px solid ' + DIVIDER_COLOR + ';vertical-align:top;font-size:12px}'
            + '.nb .num{text-align:center;color:' + MUTED_COLOR + ';white-space:nowrap}'
            + '.nb .cat{font-size:10px;color:' + MUTED_COLOR + ';display:block}'
            + '.nb a{color:' + LINK_COLOR + ';text-decoration:none}'
            + '.nb .nf{padding:4px 0;border-bottom:1px solid ' + DIVIDER_COLOR + ';font-size:12px;color:' + TEXT_COLOR + '}'
            + '.nb .badge{background:' + BADGE_BG + ';color:' + BADGE_TEXT + ';font-size:10px;font-weight:bold;padding:1px 5px;border-radius:8px;margin-right:4px;white-space:nowrap}'
            + '.nb .stats{display:flex;gap:8px;flex-wrap:wrap}'
            + '.nb .stat{background:' + hexToRgba(DIVIDER_COLOR, 1) + ';border-radius:8px;padding:8px;flex:1;text-align:center}'
            + '.nb .sv{font-size:16px;font-weight:bold;color:' + STATS_COLOR + '}'
            + '.nb .sl{font-size:10px;color:' + MUTED_COLOR + '}'
            + '</style>';
    
        var statsContent = '<div class="stats">'
            + '<div class="stat"><div class="sv">' + stats.online + '</div><div class="sl">Online</div></div>'
            + '<div class="stat"><div class="sv">' + stats.topics + '</div><div class="sl">Themen</div></div>'
            + '<div class="stat"><div class="sv">' + stats.posts  + '</div><div class="sl">Beitraege</div></div>'
            + '<div class="stat"><div class="sv">' + unreadNotifs.length + '</div><div class="sl">Neue Notifs</div></div>'
            + '</div>';
    
        var notifContent = '';
        if (unreadNotifs.length > 0) {
            unreadNotifs.forEach(function(n) {
                notifContent += '<div class="nf"><span class="badge">NEU</span>' + stripHtml(n.bodyShort) + '</div>';
            });
        } else {
            notifContent = '<div style="font-size:12px;color:' + MUTED_COLOR + ';padding:4px 0;">Keine ungelesenen</div>';
        }
    
        var unreadContent = '<table><tr style="font-size:10px;color:' + MUTED_COLOR + '"><td>Titel</td><td class="num">Posts</td><td class="num">Von</td></tr>';
        unread.forEach(function(t) {
            unreadContent += '<tr><td>'
                + '<span class="cat">' + stripHtml(t.category.name) + '</span>'
                + '<a href="' + FORUM_URL + '/topic/' + t.slug + '" target="_blank">' + stripHtml(t.title) + '</a>'
                + '</td><td class="num">' + t.postcount
                + '</td><td class="num">' + stripHtml(t.user.username) + '</td></tr>';
        });
        unreadContent += '</table>';
    
        var ownTopicsContent = '';
        if (ownTopics.length > 0) {
            ownTopicsContent = '<table><tr style="font-size:10px;color:' + MUTED_COLOR + '"><td>Titel</td><td class="num">Posts</td></tr>';
            ownTopics.forEach(function(t) {
                ownTopicsContent += '<tr><td>'
                    + '<span class="cat">' + stripHtml(t.category ? t.category.name : '') + '</span>'
                    + '<a href="' + FORUM_URL + '/topic/' + t.slug + '" target="_blank">' + stripHtml(t.title) + '</a>'
                    + '</td><td class="num">' + t.postcount + '</td></tr>';
            });
            ownTopicsContent += '</table>';
        } else {
            ownTopicsContent = '<div style="font-size:12px;color:' + MUTED_COLOR + ';padding:4px 0;">Keine eigenen Topics</div>';
        }
    
        var ownPostsContent = '';
        if (ownPosts.length > 0) {
            ownPostsContent = '<table><tr style="font-size:10px;color:' + MUTED_COLOR + '"><td>Inhalt</td></tr>';
            ownPosts.forEach(function(p) {
                var date = new Date(p.timestamp).toLocaleString('de-DE');
                ownPostsContent += '<tr><td>'
                    + '<span class="cat">' + date + ' · ' + stripHtml(p.topic ? p.topic.title : '') + '</span>'
                    + '<a href="' + FORUM_URL + '/post/' + p.pid + '" target="_blank">' + stripHtml(p.content).substring(0, 80) + '...</a>'
                    + '</td></tr>';
            });
            ownPostsContent += '</table>';
        } else {
            ownPostsContent = '<div style="font-size:12px;color:' + MUTED_COLOR + ';padding:4px 0;">Keine eigenen Posts in diesem Zeitraum</div>';
        }
    
        var topicsContent = '<table><tr style="font-size:10px;color:' + MUTED_COLOR + '"><td>Titel</td><td class="num">Posts</td><td class="num">Von</td></tr>';
        topics.forEach(function(t) {
            topicsContent += '<tr><td>'
                + '<span class="cat">' + stripHtml(t.category.name) + '</span>'
                + '<a href="' + FORUM_URL + '/topic/' + t.slug + '" target="_blank">' + stripHtml(t.title) + '</a>'
                + '</td><td class="num">' + t.postcount
                + '</td><td class="num">' + stripHtml(t.user.username) + '</td></tr>';
        });
        topicsContent += '</table>';
    
        var userLine = user ? ('<div class="usr">Eingeloggt als ' + user.username + '</div>') : '';
    
        return css
            + '<div class="nb">'
            + '<div class="hd">'
            + '<h2>ioBroker Forum</h2>'
            + '<div class="hd-btns">'
            + '<button class="btn" title="Notifications als gelesen markieren" onclick="fetch(\'' + apiBase + 'cmd.markAllRead?value=true\',{mode:\'no-cors\'})">Notifs</button>'
            + '<button class="btn" title="Ungelesene Topics als gelesen markieren" onclick="fetch(\'' + apiBase + 'cmd.markUnreadRead?value=true\',{mode:\'no-cors\'})">Ungelesen</button>'
            + '<button class="btn" title="Daten neu laden" onclick="fetch(\'' + apiBase + 'cmd.reload?value=true\',{mode:\'no-cors\'})">Reload</button>'
            + '</div>'
            + '</div>'
            + userLine
            + '<div class="upd">Stand: ' + updated + ' · alle ' + INTERVAL_MIN + ' Min.</div>'
            + (SHOW_STATS         ? makeSection('stats',     'Stats',                                                                                                  statsContent,     apiBase) : '')
            + (SHOW_NOTIFICATIONS ? makeSection('notifs',    'Benachrichtigungen (' + unreadNotifs.length + ')',                                                       notifContent,     apiBase) : '')
            + (SHOW_UNREAD        ? makeSection('unread',    'Ungelesen (zeige ' + unread.length + ' von ' + unreadTotal + ')',                                        unreadContent,    apiBase) : '')
            + (SHOW_OWN_TOPICS    ? makeSection('owntopics', 'Meine letzten ' + OWN_TOPICS_MAX + ' Topics (' + ownTopics.length + ')',                                 ownTopicsContent, apiBase) : '')
            + (SHOW_OWN_POSTS     ? makeSection('ownposts',  'Meine letzten ' + OWN_POSTS_MAX + ' Posts - letzte ' + OWN_POST_DAYS + ' Tage (' + ownPosts.length + ')', ownPostsContent,  apiBase) : '')
            + (SHOW_LATEST        ? makeSection('latest',    'Neueste ' + LATEST_TOPICS_MAX + ' Topics',                                                               topicsContent,    apiBase) : '')
            + '</div>';
    }
    
    // ── DPs setzen ────────────────────────────────────────────────
    
    function setAllDPs(results) {
        var now          = new Date().toLocaleString('de-DE');
        var notifs       = results.notifs   || [];
        var unreadNotifs = notifs.filter(function(n) { return !n.read; });
        var stats        = results.stats    || {};
        var user         = results.user     || {};
    
        setState(dp('info.connected'),            true,                                    true);
        setState(dp('info.lastUpdate'),           now,                                     true);
        setState(dp('info.username'),             user.username || '',                     true);
        setState(dp('stats.online'),              stats.online  || '?',                   true);
        setState(dp('stats.topics'),              stats.topics  || '?',                   true);
        setState(dp('stats.posts'),               stats.posts   || '?',                   true);
        setState(dp('topics.latest'),             JSON.stringify(results.topics   || []),  true);
        setState(dp('unread.count'),              results.unreadTotal || 0,                true);
        setState(dp('unread.list'),               JSON.stringify(results.unread   || []),  true);
        setState(dp('notifications.unreadCount'), unreadNotifs.length,                     true);
        setState(dp('notifications.unreadList'),  JSON.stringify(unreadNotifs),            true);
        setState(dp('notifications.list'),        JSON.stringify(notifs),                  true);
        setState(dp('ownPosts.list'),             JSON.stringify(results.ownPosts  || []), true);
        setState(dp('ownTopics.list'),            JSON.stringify(results.ownTopics || []), true);
        setState(dp('widget.html'), buildHtml(
            results.topics    || [],
            results.unread    || [],
            results.unreadTotal || 0,
            notifs,
            stats,
            results.ownPosts  || [],
            results.ownTopics || [],
            user
        ), true);
    
        log('Alle DPs aktualisiert: ' + now);
    }
    
    // ── Daten laden ───────────────────────────────────────────────
    
    function loadData() {
        var results = {};
        var done    = 0;
        var total   = 6;
        var since   = Date.now() - (OWN_POST_DAYS * 24 * 60 * 60 * 1000);
    
        function check() {
            done++;
            if (done >= total) setAllDPs(results);
        }
    
        fetchLatestTopicsLimited(function(topics) { results.topics = topics; check(); });
        apiGet('/api/', function(d) { results.stats = extractStats(d); check(); });
        fetchNotifsLimited(function(notifs) { results.notifs = notifs; check(); });
    
        fetchUnreadLimited(function(topics, totalCount, user) {
            results.unread      = topics;
            results.unreadTotal = totalCount;
            results.user        = user || {};
    
            if (user && user.userslug) {
                fetchOwnPostsLimited(user.userslug, since, function(posts) {
                    results.ownPosts = posts;
                    check();
                });
                fetchOwnTopicsLimited(user.userslug, function(t) {
                    results.ownTopics = t;
                    check();
                });
            } else {
                results.ownPosts  = [];
                results.ownTopics = [];
                check();
                check();
            }
            check();
        });
    }
    
    // ── Login ─────────────────────────────────────────────────────
    
    function login(callback) {
        axios.get(FORUM_URL + '/api/config')
            .then(function(res) {
                csrfToken = res.data.csrf_token;
                cookieJar = extractCookies(res.headers['set-cookie']);
                return axios.post(FORUM_URL + '/login',
                    { username: USERNAME, password: PASSWORD },
                    { headers: {
                        'x-csrf-token': csrfToken,
                        'Content-Type': 'application/json',
                        'Cookie': cookieJar
                    }, maxRedirects: 0, validateStatus: function(s) { return s < 500; } }
                );
            })
            .then(function(res) {
                var newCookies = extractCookies(res.headers['set-cookie']);
                if (newCookies) cookieJar = newCookies;
                if (res.data && res.data.next) {
                    log('Eingeloggt');
                    if (callback) callback();
                } else {
                    log('Login fehlgeschlagen: ' + JSON.stringify(res.data), 'error');
                    setState(dp('info.connected'), false, true);
                    setState(dp('info.lastError'), 'Login fehlgeschlagen', true);
                }
            })
            .catch(function(err) {
                log('Login Fehler: ' + err, 'error');
                setState(dp('info.connected'), false, true);
                setState(dp('info.lastError'), err.toString(), true);
            });
    }
    
    // ── Init ──────────────────────────────────────────────────────
    
    createDPs(function() {
        on({id: dp('cmd.reload'), ack: false}, function(obj) {
            if (!obj.state.val) return;
            log('Manueller Reload...');
            setState(dp('cmd.reload'), false, true);
            loadData();
        });
    
        on({id: dp('cmd.markAllRead'), ack: false}, function(obj) {
            if (!obj.state.val) return;
            setState(dp('cmd.markAllRead'), false, true);
            fetchAllNotifs(function(notifs) {
                markNotifsRead(notifs, function() { loadData(); });
            });
        });
    
        on({id: dp('cmd.markUnreadRead'), ack: false}, function(obj) {
            if (!obj.state.val) return;
            setState(dp('cmd.markUnreadRead'), false, true);
            fetchAllUnread(function(topics) {
                markUnreadRead(topics, function() { loadData(); });
            });
        });
    
        login(function() {
            loadData();
            setInterval(function() { loadData(); }, INTERVAL_MIN * 60 * 1000);
        });
    });
    

    Die SimpleApi wird für den Button zum Aktualisieren des Widgets benötigt. Auf den Weg klappt es in jeder Visualisierung. Muss man nicjt korrekt ausfüllen, dann klappt nur der reload Button nicht.

    Falls jemand weiß, wie ich Daten senden kann, darf er das gerne schreiben. Mir scheint, als ob es über API und Socket.io deaktiviert ist. Im Forum.
    Wollte eigentlich noch machen, dass man die Benachrichtigungen löschen kann über das Widget.

    Zeigt eure Lovelace-Visualisierung klick
    (Auch ideal um sich Anregungen zu holen)

    Meine Tabellen für eure Visualisierung klick

    HomoranH 1 Antwort Letzte Antwort
    0
    • David G.D David G.

      Ich wollte euch kurz ein kleines JS vorstellen was ich gestern Abend mehr oder weniger aus Langeweile mit Claude geschrieben habe.

      Es ruft die "Wichtigsten" Infos aus diesem Forum ab und schreibt diese in DPs, zusätzliche wird noch ein konfigurierbares Widget erstellt.

      Das Widget zeigt:

      • Eigene Benachrichtigungen (ungelesen/gelesen)
      • Ungelesene Topics mit Kategorie, Postanzahl und Autor
      • Eigene letzten Topics & Posts der letzten X Tage
      • Neueste Forum-Topics
      • Forum-Stats (Online-User, Themen, Beiträge)

      Alles als einzelne DPs verfügbar – nicht nur das Widget
      1000070548.jpg

      1000070142.jpg

      // ═══════════════════════════════════════════════════════════════
      //  ioBroker Forum Widget
      //  Autor:   David G.
      //  Version: 2.3.0
      //  Forum:   https://forum.iobroker.net/topic/84513/iobroker-forum-widget-forum-daten-direkt-in-visu
      //
      //  Zeigt Forum-Daten als Datenpunkte und HTML-Widget an:
      //  Benachrichtigungen, ungelesene Topics, eigene Posts & Topics,
      //  neueste Topics und Forum-Stats.
      // ═══════════════════════════════════════════════════════════════
      
      // ── Zugangsdaten ──────────────────────────────────────────────
      const FORUM_URL          = 'https://forum.iobroker.net';
      const USERNAME           = 'deine@email.de';
      const PASSWORD           = 'deinPasswort';
      const SIMPLE_API         = 'http://192.168.99.33:8087';
      
      // ── Datenpunkte ───────────────────────────────────────────────
      const BASE_DP            = '0_userdata.0.forum.iobroker.';
      
      // ── Intervall & Limits ────────────────────────────────────────
      const INTERVAL_MIN       = 15;
      const OWN_POST_DAYS      = 7;
      const OWN_POSTS_MAX      = 10;
      const OWN_TOPICS_MAX     = 5;
      const LATEST_TOPICS_MAX  = 20;
      const UNREAD_MAX         = 20;
      const NOTIFICATIONS_MAX  = 10;
      const MARK_BATCH_SIZE    = 10;
      
      // ── Sektionen ein/ausblenden ──────────────────────────────────
      const SHOW_STATS         = true;
      const SHOW_NOTIFICATIONS = true;
      const SHOW_UNREAD        = true;
      const SHOW_OWN_TOPICS    = true;
      const SHOW_OWN_POSTS     = true;
      const SHOW_LATEST        = true;
      
      // ── Widget Design ─────────────────────────────────────────────
      const BG_COLOR           = '#1e1e2e';
      const BG_OPACITY         = 1.0;
      const TEXT_COLOR         = '#ccc';
      const LINK_COLOR         = '#cdd6f4';
      const ACCENT_COLOR       = '#89b4fa';
      const BADGE_BG           = '#f38ba8';
      const BADGE_TEXT         = '#1e1e2e';
      const STATS_COLOR        = '#a6e3a1';
      const MUTED_COLOR        = '#6c7086';
      const DIVIDER_COLOR      = '#313244';
      
      // ═══════════════════════════════════════════════════════════════
      
      const axios = require('axios');
      var cookieJar = '';
      var csrfToken = '';
      
      function extractCookies(arr) {
          if (!arr) return '';
          return arr.map(function(c) { return c.split(';')[0]; }).join('; ');
      }
      
      function stripHtml(str) {
          return (str || '').replace(/<[^>]*>/g, '')
              .replace(/&lt;/g,'<').replace(/&gt;/g,'>')
              .replace(/&amp;/g,'&').replace(/&#x27;/g,"'");
      }
      
      function dp(path) { return BASE_DP + path; }
      
      function getOpen(id) {
          try {
              var s = getState(dp('ui.' + id + '.open'));
              return s && s.val === true;
          } catch(e) { return false; }
      }
      
      function hexToRgba(hex, opacity) {
          var r = parseInt(hex.slice(1,3), 16);
          var g = parseInt(hex.slice(3,5), 16);
          var b = parseInt(hex.slice(5,7), 16);
          return 'rgba(' + r + ',' + g + ',' + b + ',' + opacity + ')';
      }
      
      function apiGet(path, callback, retry) {
          axios.get(FORUM_URL + path, {
              headers: { Cookie: cookieJar },
              validateStatus: function(s) { return s < 500; }
          })
          .then(function(res) {
              if (res.status === 401 && !retry) {
                  log('Session abgelaufen – logge neu ein... (' + path + ')');
                  login(function() { apiGet(path, callback, true); });
                  return;
              }
              try { callback(res.data); }
              catch(e) { log('Callback Fehler ' + path + ': ' + e, 'error'); }
          })
          .catch(function(err) { log('GET Fehler ' + path + ': ' + err, 'error'); });
      }
      
      // ── Paginierte Fetch-Funktionen ───────────────────────────────
      
      function fetchLatestTopicsLimited(callback) {
          var allTopics   = [];
          var pagesNeeded = Math.ceil(LATEST_TOPICS_MAX / 20);
          function fetchPage(page) {
              apiGet('/api/recent?page=' + page, function(d) {
                  allTopics = allTopics.concat(d.topics || []);
                  var totalPages = d.pageCount || 1;
                  if (allTopics.length < LATEST_TOPICS_MAX && page < totalPages && page < pagesNeeded) {
                      setTimeout(function() { fetchPage(page + 1); }, 300);
                  } else { callback(allTopics.slice(0, LATEST_TOPICS_MAX)); }
              });
          }
          fetchPage(1);
      }
      
      function fetchNotifsLimited(callback) {
          var allNotifs   = [];
          var pagesNeeded = Math.ceil(NOTIFICATIONS_MAX / 20);
          function fetchPage(page) {
              apiGet('/api/notifications?page=' + page, function(d) {
                  allNotifs = allNotifs.concat(d.notifications || []);
                  var totalPages = d.pageCount || 1;
                  if (allNotifs.length < NOTIFICATIONS_MAX && page < totalPages && page < pagesNeeded) {
                      setTimeout(function() { fetchPage(page + 1); }, 300);
                  } else { callback(allNotifs.slice(0, NOTIFICATIONS_MAX)); }
              });
          }
          fetchPage(1);
      }
      
      function fetchUnreadLimited(callback) {
          var allTopics   = [];
          var pagesNeeded = Math.ceil(UNREAD_MAX / 20);
          var user        = null;
          var totalCount  = 0;
          function fetchPage(page) {
              apiGet('/api/unread?page=' + page, function(d) {
                  allTopics  = allTopics.concat(d.topics || []);
                  totalCount = d.topicCount || allTopics.length;
                  if (page === 1 && d.loggedInUser) {
                      user = { uid: d.loggedInUser.uid, username: d.loggedInUser.username, userslug: d.loggedInUser.userslug };
                  }
                  var totalPages = d.pageCount || 1;
                  if (allTopics.length < UNREAD_MAX && page < totalPages && page < pagesNeeded) {
                      setTimeout(function() { fetchPage(page + 1); }, 300);
                  } else { callback(allTopics.slice(0, UNREAD_MAX), totalCount, user); }
              });
          }
          fetchPage(1);
      }
      
      function fetchOwnPostsLimited(userslug, since, callback) {
          var allPosts    = [];
          var pagesNeeded = Math.ceil(OWN_POSTS_MAX / 20);
          var reachedDate = false;
          function fetchPage(page) {
              apiGet('/api/user/' + userslug + '/posts?page=' + page, function(d) {
                  (d.posts || []).forEach(function(p) {
                      if (p.timestamp >= since) { allPosts.push(p); } else { reachedDate = true; }
                  });
                  var totalPages = d.pageCount || 1;
                  if (!reachedDate && allPosts.length < OWN_POSTS_MAX && page < totalPages && page < pagesNeeded) {
                      setTimeout(function() { fetchPage(page + 1); }, 300);
                  } else { callback(allPosts.slice(0, OWN_POSTS_MAX)); }
              });
          }
          fetchPage(1);
      }
      
      function fetchOwnTopicsLimited(userslug, callback) {
          var allTopics   = [];
          var pagesNeeded = Math.ceil(OWN_TOPICS_MAX / 20);
          function fetchPage(page) {
              apiGet('/api/user/' + userslug + '/topics?page=' + page, function(d) {
                  allTopics = allTopics.concat(d.topics || []);
                  var totalPages = d.pageCount || 1;
                  if (allTopics.length < OWN_TOPICS_MAX && page < totalPages && page < pagesNeeded) {
                      setTimeout(function() { fetchPage(page + 1); }, 300);
                  } else { callback(allTopics.slice(0, OWN_TOPICS_MAX)); }
              });
          }
          fetchPage(1);
      }
      
      // ── Mark as Read ──────────────────────────────────────────────
      
      function runBatch(items, handler, callback) {
          if (items.length === 0) { if (callback) callback(); return; }
          var total = items.length, completed = 0, index = 0;
          function nextBatch() {
              if (index >= total) return;
              var batchItems = items.slice(index, index + MARK_BATCH_SIZE);
              index += MARK_BATCH_SIZE;
              var batchDone = 0;
              batchItems.forEach(function(item) {
                  handler(item, function() {
                      completed++; batchDone++;
                      if (batchDone >= batchItems.length) {
                          log(completed + '/' + total + ' erledigt');
                          if (completed >= total) { log('Fertig'); if (callback) callback(); }
                          else { setTimeout(nextBatch, 200); }
                      }
                  });
              });
          }
          nextBatch();
      }
      
      function fetchAllUnread(callback) {
          var allTopics = [];
          function fetchPage(page) {
              apiGet('/api/unread?page=' + page, function(d) {
                  allTopics = allTopics.concat(d.topics || []);
                  log('Unread Seite ' + page + '/' + (d.pageCount || 1) + ' – ' + allTopics.length + ' geladen');
                  if (page < (d.pageCount || 1)) { setTimeout(function() { fetchPage(page + 1); }, 300); }
                  else { callback(allTopics); }
              });
          }
          fetchPage(1);
      }
      
      function fetchAllNotifs(callback) {
          var allNotifs = [];
          function fetchPage(page) {
              apiGet('/api/notifications?page=' + page, function(d) {
                  allNotifs = allNotifs.concat(d.notifications || []);
                  log('Notifs Seite ' + page + '/' + (d.pageCount || 1) + ' – ' + allNotifs.length + ' geladen');
                  if (page < (d.pageCount || 1)) { setTimeout(function() { fetchPage(page + 1); }, 300); }
                  else { callback(allNotifs); }
              });
          }
          fetchPage(1);
      }
      
      function markNotifsRead(notifs, callback) {
          var unread = notifs.filter(function(n) { return !n.read && n.path; });
          log('Markiere ' + unread.length + ' Notifications als gelesen...');
          runBatch(unread, function(n, done) { apiGet(n.path, function() { done(); }); }, callback);
      }
      
      function markUnreadRead(topics, callback) {
          log('Markiere ' + topics.length + ' Topics als gelesen...');
          runBatch(topics, function(t, done) { apiGet('/api/topic/' + t.slug, function() { done(); }); }, callback);
      }
      
      // ── Stats ─────────────────────────────────────────────────────
      
      function extractStats(data) {
          var stats = { online: '?', topics: '?', posts: '?' };
          try {
              var footer = data.widgets.footer[0].html;
              var vals = footer.match(/title="([^"]+)">/g) || [];
              if (vals[0]) stats.online = vals[0].replace('title="','').replace('">','');
              if (vals[2]) stats.topics = vals[2].replace('title="','').replace('">','');
              if (vals[3]) stats.posts  = vals[3].replace('title="','').replace('">','');
          } catch(e) {}
          return stats;
      }
      
      // ── Datenpunkte anlegen ───────────────────────────────────────
      
      function createDPs(callback) {
          var dps = [
              { id: 'info.connected',            val: false,  type: 'boolean', role: 'indicator', write: false },
              { id: 'info.lastUpdate',           val: '',     type: 'string',  role: 'text',       write: false },
              { id: 'info.lastError',            val: '',     type: 'string',  role: 'text',       write: false },
              { id: 'info.username',             val: '',     type: 'string',  role: 'text',       write: false },
              { id: 'stats.online',              val: '?',    type: 'string',  role: 'text',       write: false },
              { id: 'stats.topics',              val: '?',    type: 'string',  role: 'text',       write: false },
              { id: 'stats.posts',               val: '?',    type: 'string',  role: 'text',       write: false },
              { id: 'topics.latest',             val: '[]',   type: 'string',  role: 'json',       write: false },
              { id: 'unread.count',              val: 0,      type: 'number',  role: 'value',      write: false },
              { id: 'unread.list',               val: '[]',   type: 'string',  role: 'json',       write: false },
              { id: 'notifications.unreadCount', val: 0,      type: 'number',  role: 'value',      write: false },
              { id: 'notifications.unreadList',  val: '[]',   type: 'string',  role: 'json',       write: false },
              { id: 'notifications.list',        val: '[]',   type: 'string',  role: 'json',       write: false },
              { id: 'ownPosts.list',             val: '[]',   type: 'string',  role: 'json',       write: false },
              { id: 'ownTopics.list',            val: '[]',   type: 'string',  role: 'json',       write: false },
              { id: 'cmd.reload',                val: false,  type: 'boolean', role: 'button',     write: true  },
              { id: 'cmd.markAllRead',           val: false,  type: 'boolean', role: 'button',     write: true  },
              { id: 'cmd.markUnreadRead',        val: false,  type: 'boolean', role: 'button',     write: true  },
              { id: 'ui.stats.open',             val: false,  type: 'boolean', role: 'indicator',  write: true  },
              { id: 'ui.notifs.open',            val: false,  type: 'boolean', role: 'indicator',  write: true  },
              { id: 'ui.unread.open',            val: false,  type: 'boolean', role: 'indicator',  write: true  },
              { id: 'ui.owntopics.open',         val: false,  type: 'boolean', role: 'indicator',  write: true  },
              { id: 'ui.ownposts.open',          val: false,  type: 'boolean', role: 'indicator',  write: true  },
              { id: 'ui.latest.open',            val: false,  type: 'boolean', role: 'indicator',  write: true  },
              { id: 'widget.html',               val: '',     type: 'string',  role: 'html',       write: false }
          ];
      
          var done = 0;
          dps.forEach(function(d) {
              createState(dp(d.id), d.val, {
                  name: d.id, type: d.type, role: d.role, read: true, write: d.write
              }, function() {
                  done++;
                  if (done >= dps.length) callback();
              });
          });
      }
      
      // ── Widget HTML ───────────────────────────────────────────────
      
      function makeSection(id, title, content, apiBase) {
          var isOpen  = getOpen(id);
          var onclick = "this.parentElement.classList.toggle('open');"
              + "fetch('" + apiBase + "ui." + id + ".open?value="
              + "'+this.parentElement.classList.contains('open'),{mode:'no-cors'});";
      
          return '<div class="nb-sec' + (isOpen ? ' open' : '') + '">'
              + '<div class="sec-hd" onclick="' + onclick + '" style="cursor:pointer;user-select:none">'
              + '<span class="sec-t">' + title + '</span>'
              + '<span class="sec-arrow">▶</span>'
              + '</div>'
              + '<div class="sec-body">' + content + '</div>'
              + '</div>';
      }
      
      function buildHtml(topics, unread, unreadTotal, notifs, stats, ownPosts, ownTopics, user) {
          var unreadNotifs = notifs.filter(function(n) { return !n.read; }).slice(0, NOTIFICATIONS_MAX);
          var updated      = new Date().toLocaleString('de-DE');
          var apiBase      = SIMPLE_API + '/set/' + BASE_DP;
      
          var css = '<style>'
              + '.nb{font-family:sans-serif;font-size:13px;color:' + TEXT_COLOR + ';background:' + hexToRgba(BG_COLOR, BG_OPACITY) + ';border-radius:12px;padding:12px;box-sizing:border-box}'
              + '.nb .hd{display:flex;justify-content:space-between;align-items:center;margin-bottom:2px}'
              + '.nb h2{margin:0;font-size:15px;color:' + TEXT_COLOR + '}'
              + '.nb .hd-btns{display:flex;gap:6px}'
              + '.nb .btn{background:transparent;border:1px solid ' + DIVIDER_COLOR + ';color:' + MUTED_COLOR + ';border-radius:6px;padding:2px 8px;cursor:pointer;font-size:11px}'
              + '.nb .btn:hover{color:' + TEXT_COLOR + ';border-color:' + ACCENT_COLOR + '}'
              + '.nb .usr{font-size:11px;color:' + ACCENT_COLOR + ';margin-bottom:2px}'
              + '.nb .upd{font-size:11px;color:' + MUTED_COLOR + ';margin-bottom:10px}'
              + '.nb .nb-sec{margin-bottom:12px}'
              + '.nb .sec-hd{display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid ' + DIVIDER_COLOR + ';padding-bottom:3px;margin-bottom:5px}'
              + '.nb .sec-hd:hover .sec-t{color:' + TEXT_COLOR + '}'
              + '.nb .sec-t{font-size:11px;font-weight:bold;color:' + ACCENT_COLOR + ';text-transform:uppercase;letter-spacing:1px}'
              + '.nb .sec-arrow{display:inline-block;transition:transform 0.2s;font-size:10px;color:' + MUTED_COLOR + '}'
              + '.nb .sec-body{max-height:0;overflow:hidden;transition:max-height 0.3s ease}'
              + '.nb .nb-sec.open .sec-body{max-height:5000px}'
              + '.nb .nb-sec.open .sec-arrow{transform:rotate(90deg)}'
              + '.nb table{width:100%;border-collapse:collapse}'
              + '.nb td{padding:4px;border-bottom:1px solid ' + DIVIDER_COLOR + ';vertical-align:top;font-size:12px}'
              + '.nb .num{text-align:center;color:' + MUTED_COLOR + ';white-space:nowrap}'
              + '.nb .cat{font-size:10px;color:' + MUTED_COLOR + ';display:block}'
              + '.nb a{color:' + LINK_COLOR + ';text-decoration:none}'
              + '.nb .nf{padding:4px 0;border-bottom:1px solid ' + DIVIDER_COLOR + ';font-size:12px;color:' + TEXT_COLOR + '}'
              + '.nb .badge{background:' + BADGE_BG + ';color:' + BADGE_TEXT + ';font-size:10px;font-weight:bold;padding:1px 5px;border-radius:8px;margin-right:4px;white-space:nowrap}'
              + '.nb .stats{display:flex;gap:8px;flex-wrap:wrap}'
              + '.nb .stat{background:' + hexToRgba(DIVIDER_COLOR, 1) + ';border-radius:8px;padding:8px;flex:1;text-align:center}'
              + '.nb .sv{font-size:16px;font-weight:bold;color:' + STATS_COLOR + '}'
              + '.nb .sl{font-size:10px;color:' + MUTED_COLOR + '}'
              + '</style>';
      
          var statsContent = '<div class="stats">'
              + '<div class="stat"><div class="sv">' + stats.online + '</div><div class="sl">Online</div></div>'
              + '<div class="stat"><div class="sv">' + stats.topics + '</div><div class="sl">Themen</div></div>'
              + '<div class="stat"><div class="sv">' + stats.posts  + '</div><div class="sl">Beitraege</div></div>'
              + '<div class="stat"><div class="sv">' + unreadNotifs.length + '</div><div class="sl">Neue Notifs</div></div>'
              + '</div>';
      
          var notifContent = '';
          if (unreadNotifs.length > 0) {
              unreadNotifs.forEach(function(n) {
                  notifContent += '<div class="nf"><span class="badge">NEU</span>' + stripHtml(n.bodyShort) + '</div>';
              });
          } else {
              notifContent = '<div style="font-size:12px;color:' + MUTED_COLOR + ';padding:4px 0;">Keine ungelesenen</div>';
          }
      
          var unreadContent = '<table><tr style="font-size:10px;color:' + MUTED_COLOR + '"><td>Titel</td><td class="num">Posts</td><td class="num">Von</td></tr>';
          unread.forEach(function(t) {
              unreadContent += '<tr><td>'
                  + '<span class="cat">' + stripHtml(t.category.name) + '</span>'
                  + '<a href="' + FORUM_URL + '/topic/' + t.slug + '" target="_blank">' + stripHtml(t.title) + '</a>'
                  + '</td><td class="num">' + t.postcount
                  + '</td><td class="num">' + stripHtml(t.user.username) + '</td></tr>';
          });
          unreadContent += '</table>';
      
          var ownTopicsContent = '';
          if (ownTopics.length > 0) {
              ownTopicsContent = '<table><tr style="font-size:10px;color:' + MUTED_COLOR + '"><td>Titel</td><td class="num">Posts</td></tr>';
              ownTopics.forEach(function(t) {
                  ownTopicsContent += '<tr><td>'
                      + '<span class="cat">' + stripHtml(t.category ? t.category.name : '') + '</span>'
                      + '<a href="' + FORUM_URL + '/topic/' + t.slug + '" target="_blank">' + stripHtml(t.title) + '</a>'
                      + '</td><td class="num">' + t.postcount + '</td></tr>';
              });
              ownTopicsContent += '</table>';
          } else {
              ownTopicsContent = '<div style="font-size:12px;color:' + MUTED_COLOR + ';padding:4px 0;">Keine eigenen Topics</div>';
          }
      
          var ownPostsContent = '';
          if (ownPosts.length > 0) {
              ownPostsContent = '<table><tr style="font-size:10px;color:' + MUTED_COLOR + '"><td>Inhalt</td></tr>';
              ownPosts.forEach(function(p) {
                  var date = new Date(p.timestamp).toLocaleString('de-DE');
                  ownPostsContent += '<tr><td>'
                      + '<span class="cat">' + date + ' · ' + stripHtml(p.topic ? p.topic.title : '') + '</span>'
                      + '<a href="' + FORUM_URL + '/post/' + p.pid + '" target="_blank">' + stripHtml(p.content).substring(0, 80) + '...</a>'
                      + '</td></tr>';
              });
              ownPostsContent += '</table>';
          } else {
              ownPostsContent = '<div style="font-size:12px;color:' + MUTED_COLOR + ';padding:4px 0;">Keine eigenen Posts in diesem Zeitraum</div>';
          }
      
          var topicsContent = '<table><tr style="font-size:10px;color:' + MUTED_COLOR + '"><td>Titel</td><td class="num">Posts</td><td class="num">Von</td></tr>';
          topics.forEach(function(t) {
              topicsContent += '<tr><td>'
                  + '<span class="cat">' + stripHtml(t.category.name) + '</span>'
                  + '<a href="' + FORUM_URL + '/topic/' + t.slug + '" target="_blank">' + stripHtml(t.title) + '</a>'
                  + '</td><td class="num">' + t.postcount
                  + '</td><td class="num">' + stripHtml(t.user.username) + '</td></tr>';
          });
          topicsContent += '</table>';
      
          var userLine = user ? ('<div class="usr">Eingeloggt als ' + user.username + '</div>') : '';
      
          return css
              + '<div class="nb">'
              + '<div class="hd">'
              + '<h2>ioBroker Forum</h2>'
              + '<div class="hd-btns">'
              + '<button class="btn" title="Notifications als gelesen markieren" onclick="fetch(\'' + apiBase + 'cmd.markAllRead?value=true\',{mode:\'no-cors\'})">Notifs</button>'
              + '<button class="btn" title="Ungelesene Topics als gelesen markieren" onclick="fetch(\'' + apiBase + 'cmd.markUnreadRead?value=true\',{mode:\'no-cors\'})">Ungelesen</button>'
              + '<button class="btn" title="Daten neu laden" onclick="fetch(\'' + apiBase + 'cmd.reload?value=true\',{mode:\'no-cors\'})">Reload</button>'
              + '</div>'
              + '</div>'
              + userLine
              + '<div class="upd">Stand: ' + updated + ' · alle ' + INTERVAL_MIN + ' Min.</div>'
              + (SHOW_STATS         ? makeSection('stats',     'Stats',                                                                                                  statsContent,     apiBase) : '')
              + (SHOW_NOTIFICATIONS ? makeSection('notifs',    'Benachrichtigungen (' + unreadNotifs.length + ')',                                                       notifContent,     apiBase) : '')
              + (SHOW_UNREAD        ? makeSection('unread',    'Ungelesen (zeige ' + unread.length + ' von ' + unreadTotal + ')',                                        unreadContent,    apiBase) : '')
              + (SHOW_OWN_TOPICS    ? makeSection('owntopics', 'Meine letzten ' + OWN_TOPICS_MAX + ' Topics (' + ownTopics.length + ')',                                 ownTopicsContent, apiBase) : '')
              + (SHOW_OWN_POSTS     ? makeSection('ownposts',  'Meine letzten ' + OWN_POSTS_MAX + ' Posts - letzte ' + OWN_POST_DAYS + ' Tage (' + ownPosts.length + ')', ownPostsContent,  apiBase) : '')
              + (SHOW_LATEST        ? makeSection('latest',    'Neueste ' + LATEST_TOPICS_MAX + ' Topics',                                                               topicsContent,    apiBase) : '')
              + '</div>';
      }
      
      // ── DPs setzen ────────────────────────────────────────────────
      
      function setAllDPs(results) {
          var now          = new Date().toLocaleString('de-DE');
          var notifs       = results.notifs   || [];
          var unreadNotifs = notifs.filter(function(n) { return !n.read; });
          var stats        = results.stats    || {};
          var user         = results.user     || {};
      
          setState(dp('info.connected'),            true,                                    true);
          setState(dp('info.lastUpdate'),           now,                                     true);
          setState(dp('info.username'),             user.username || '',                     true);
          setState(dp('stats.online'),              stats.online  || '?',                   true);
          setState(dp('stats.topics'),              stats.topics  || '?',                   true);
          setState(dp('stats.posts'),               stats.posts   || '?',                   true);
          setState(dp('topics.latest'),             JSON.stringify(results.topics   || []),  true);
          setState(dp('unread.count'),              results.unreadTotal || 0,                true);
          setState(dp('unread.list'),               JSON.stringify(results.unread   || []),  true);
          setState(dp('notifications.unreadCount'), unreadNotifs.length,                     true);
          setState(dp('notifications.unreadList'),  JSON.stringify(unreadNotifs),            true);
          setState(dp('notifications.list'),        JSON.stringify(notifs),                  true);
          setState(dp('ownPosts.list'),             JSON.stringify(results.ownPosts  || []), true);
          setState(dp('ownTopics.list'),            JSON.stringify(results.ownTopics || []), true);
          setState(dp('widget.html'), buildHtml(
              results.topics    || [],
              results.unread    || [],
              results.unreadTotal || 0,
              notifs,
              stats,
              results.ownPosts  || [],
              results.ownTopics || [],
              user
          ), true);
      
          log('Alle DPs aktualisiert: ' + now);
      }
      
      // ── Daten laden ───────────────────────────────────────────────
      
      function loadData() {
          var results = {};
          var done    = 0;
          var total   = 6;
          var since   = Date.now() - (OWN_POST_DAYS * 24 * 60 * 60 * 1000);
      
          function check() {
              done++;
              if (done >= total) setAllDPs(results);
          }
      
          fetchLatestTopicsLimited(function(topics) { results.topics = topics; check(); });
          apiGet('/api/', function(d) { results.stats = extractStats(d); check(); });
          fetchNotifsLimited(function(notifs) { results.notifs = notifs; check(); });
      
          fetchUnreadLimited(function(topics, totalCount, user) {
              results.unread      = topics;
              results.unreadTotal = totalCount;
              results.user        = user || {};
      
              if (user && user.userslug) {
                  fetchOwnPostsLimited(user.userslug, since, function(posts) {
                      results.ownPosts = posts;
                      check();
                  });
                  fetchOwnTopicsLimited(user.userslug, function(t) {
                      results.ownTopics = t;
                      check();
                  });
              } else {
                  results.ownPosts  = [];
                  results.ownTopics = [];
                  check();
                  check();
              }
              check();
          });
      }
      
      // ── Login ─────────────────────────────────────────────────────
      
      function login(callback) {
          axios.get(FORUM_URL + '/api/config')
              .then(function(res) {
                  csrfToken = res.data.csrf_token;
                  cookieJar = extractCookies(res.headers['set-cookie']);
                  return axios.post(FORUM_URL + '/login',
                      { username: USERNAME, password: PASSWORD },
                      { headers: {
                          'x-csrf-token': csrfToken,
                          'Content-Type': 'application/json',
                          'Cookie': cookieJar
                      }, maxRedirects: 0, validateStatus: function(s) { return s < 500; } }
                  );
              })
              .then(function(res) {
                  var newCookies = extractCookies(res.headers['set-cookie']);
                  if (newCookies) cookieJar = newCookies;
                  if (res.data && res.data.next) {
                      log('Eingeloggt');
                      if (callback) callback();
                  } else {
                      log('Login fehlgeschlagen: ' + JSON.stringify(res.data), 'error');
                      setState(dp('info.connected'), false, true);
                      setState(dp('info.lastError'), 'Login fehlgeschlagen', true);
                  }
              })
              .catch(function(err) {
                  log('Login Fehler: ' + err, 'error');
                  setState(dp('info.connected'), false, true);
                  setState(dp('info.lastError'), err.toString(), true);
              });
      }
      
      // ── Init ──────────────────────────────────────────────────────
      
      createDPs(function() {
          on({id: dp('cmd.reload'), ack: false}, function(obj) {
              if (!obj.state.val) return;
              log('Manueller Reload...');
              setState(dp('cmd.reload'), false, true);
              loadData();
          });
      
          on({id: dp('cmd.markAllRead'), ack: false}, function(obj) {
              if (!obj.state.val) return;
              setState(dp('cmd.markAllRead'), false, true);
              fetchAllNotifs(function(notifs) {
                  markNotifsRead(notifs, function() { loadData(); });
              });
          });
      
          on({id: dp('cmd.markUnreadRead'), ack: false}, function(obj) {
              if (!obj.state.val) return;
              setState(dp('cmd.markUnreadRead'), false, true);
              fetchAllUnread(function(topics) {
                  markUnreadRead(topics, function() { loadData(); });
              });
          });
      
          login(function() {
              loadData();
              setInterval(function() { loadData(); }, INTERVAL_MIN * 60 * 1000);
          });
      });
      

      Die SimpleApi wird für den Button zum Aktualisieren des Widgets benötigt. Auf den Weg klappt es in jeder Visualisierung. Muss man nicjt korrekt ausfüllen, dann klappt nur der reload Button nicht.

      Falls jemand weiß, wie ich Daten senden kann, darf er das gerne schreiben. Mir scheint, als ob es über API und Socket.io deaktiviert ist. Im Forum.
      Wollte eigentlich noch machen, dass man die Benachrichtigungen löschen kann über das Widget.

      HomoranH Nicht stören
      HomoranH Nicht stören
      Homoran
      Global Moderator Administrators
      schrieb am zuletzt editiert von
      #2

      @David-G. sagte:

      wie ich Daten senden kann

      Das ist eine
      773.jpg

      kein Support per PN! - Fragen im Forum stellen -
      Benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.
      Das Forum freut sich über eine Spende. Benutzt dazu den Spendenbutton oben rechts. Danke!
      der Installationsfixer: curl -fsL https://iobroker.net/fix.sh | bash -

      David G.D 1 Antwort Letzte Antwort
      0
      • HomoranH Homoran

        @David-G. sagte:

        wie ich Daten senden kann

        Das ist eine
        773.jpg

        David G.D Online
        David G.D Online
        David G.
        schrieb am zuletzt editiert von
        #3

        @Homoran
        https://github.com/NodeBB/nodebb-plugin-write-api

        Zeigt eure Lovelace-Visualisierung klick
        (Auch ideal um sich Anregungen zu holen)

        Meine Tabellen für eure Visualisierung klick

        HomoranH 1 Antwort Letzte Antwort
        0
        • David G.D David G.

          @Homoran
          https://github.com/NodeBB/nodebb-plugin-write-api

          HomoranH Nicht stören
          HomoranH Nicht stören
          Homoran
          Global Moderator Administrators
          schrieb am zuletzt editiert von Homoran
          #4

          @David-G. Read more... ;-)

          As of NodeBB v1.15.0, this plugin is deprecated

          Ist aber auch eine Frage der Sicherheit.
          Wir haben genug mit den bisherigen ungewünschten Aktionen zu tun.

          kein Support per PN! - Fragen im Forum stellen -
          Benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.
          Das Forum freut sich über eine Spende. Benutzt dazu den Spendenbutton oben rechts. Danke!
          der Installationsfixer: curl -fsL https://iobroker.net/fix.sh | bash -

          1 Antwort Letzte Antwort
          1
          • mcm1957M Online
            mcm1957M Online
            mcm1957
            schrieb am zuletzt editiert von
            #5

            Außerdem schadet es nichts wenn Postings manuell getippt werden müssen. Claude und Co können und sollen einen Menschen als Frontend benutzen :-)

            Entwicklung u Betreuung: envertech-pv, hoymiles-ms, ns-client, pid, snmp Adapter;
            Support Repositoryverwaltung.

            Wer 'nen Kaffee spendieren will: https://paypal.me

            LESEN - gute Forenbeitrage

            1 Antwort Letzte Antwort
            2
            • David G.D Online
              David G.D Online
              David G.
              schrieb am zuletzt editiert von
              #6

              @homoran @mcm1957
              Da bin ich ganz bei euch beiden.
              Sicherheit geht vor, und KI soll hier definitiv nicht selber im Forum tippen.

              Hab es eben probiert weil es ein schönes "Upgrade" in Widget gewesen wäre ^^.

              Zeigt eure Lovelace-Visualisierung klick
              (Auch ideal um sich Anregungen zu holen)

              Meine Tabellen für eure Visualisierung klick

              1 Antwort Letzte Antwort
              0
              • ilovegymI Offline
                ilovegymI Offline
                ilovegym
                schrieb am zuletzt editiert von
                #7

                die Ki's sind eine Minderheit und sollten nicht diskriminiert werden 😀😎 - denkt an cyberdyne systems..

                1 Antwort Letzte Antwort
                0
                • David G.D Online
                  David G.D Online
                  David G.
                  schrieb am zuletzt editiert von
                  #8

                  Haltet mich für doof 🤣, aber hab mit dem Script noch ein wenig weiter gemacht.

                  Hab mich doch bei erwischt, wenn ich in meiner Visualisierung war, immer mal schnell einen Blick ins Forum zu werfen 😇.

                  Was ist neu:
                  Benachrichtigungen und Ungelesen können auf gelesen gesetzt werden und die Kategorien sind einklappbar.
                  Das als gelesen markieren benötigt die simpleApi, genau wie das speichern des Status ob eine Kategorie auf oder zugeklappt ist. Den Weg muss ich leider wegen lovelace gehen. Dort kann ich sonst nicht auf DPs zugreifen, localstorage unterstützt es auch nicht.

                  Aktualisierung im ersten Post.

                  Zeigt eure Lovelace-Visualisierung klick
                  (Auch ideal um sich Anregungen zu holen)

                  Meine Tabellen für eure Visualisierung klick

                  OliverIOO 1 Antwort Letzte Antwort
                  0
                  • David G.D David G.

                    Haltet mich für doof 🤣, aber hab mit dem Script noch ein wenig weiter gemacht.

                    Hab mich doch bei erwischt, wenn ich in meiner Visualisierung war, immer mal schnell einen Blick ins Forum zu werfen 😇.

                    Was ist neu:
                    Benachrichtigungen und Ungelesen können auf gelesen gesetzt werden und die Kategorien sind einklappbar.
                    Das als gelesen markieren benötigt die simpleApi, genau wie das speichern des Status ob eine Kategorie auf oder zugeklappt ist. Den Weg muss ich leider wegen lovelace gehen. Dort kann ich sonst nicht auf DPs zugreifen, localstorage unterstützt es auch nicht.

                    Aktualisierung im ersten Post.

                    OliverIOO Offline
                    OliverIOO Offline
                    OliverIO
                    schrieb am zuletzt editiert von
                    #9

                    @David-G.

                    Du könntest localstorage auch in Node verwenden (gibt es ja eigentlich nur im Browser)
                    https://www.npmjs.com/package/localStorage

                    Meine Adapter und Widgets
                    TVProgram, SqueezeboxRPC, OpenLiga, RSSFeed, MyTime,, pi-hole2, vis-json-template, skiinfo, vis-mapwidgets, vis-2-widgets-rssfeed
                    Links im Profil

                    David G.D 1 Antwort Letzte Antwort
                    0
                    • OliverIOO OliverIO

                      @David-G.

                      Du könntest localstorage auch in Node verwenden (gibt es ja eigentlich nur im Browser)
                      https://www.npmjs.com/package/localStorage

                      David G.D Online
                      David G.D Online
                      David G.
                      schrieb am zuletzt editiert von
                      #10

                      @OliverIO

                      Wie hilft mir das?
                      Wie ich es verstanden habe, blockiert lovelace die localstorage Funktion für die Website. In vis hatte mein test mit localstorage gut geklappt.

                      Oder hab ich einen Dreher?

                      Zeigt eure Lovelace-Visualisierung klick
                      (Auch ideal um sich Anregungen zu holen)

                      Meine Tabellen für eure Visualisierung klick

                      OliverIOO 1 Antwort Letzte Antwort
                      0
                      • David G.D David G.

                        @OliverIO

                        Wie hilft mir das?
                        Wie ich es verstanden habe, blockiert lovelace die localstorage Funktion für die Website. In vis hatte mein test mit localstorage gut geklappt.

                        Oder hab ich einen Dreher?

                        OliverIOO Offline
                        OliverIOO Offline
                        OliverIO
                        schrieb am zuletzt editiert von
                        #11

                        @David-G.

                        Mit der Bibliothek kannst du Local Storage auf die Serverseite verlegen.

                        Mit loverace kenne ich mich nicht aus, aber was wäre der Grund, warum man Local Storage blockieren sollte?

                        Meine Adapter und Widgets
                        TVProgram, SqueezeboxRPC, OpenLiga, RSSFeed, MyTime,, pi-hole2, vis-json-template, skiinfo, vis-mapwidgets, vis-2-widgets-rssfeed
                        Links im Profil

                        1 Antwort Letzte Antwort
                        0
                        • David G.D Online
                          David G.D Online
                          David G.
                          schrieb am zuletzt editiert von David G.
                          #12

                          Wer ist denn der Admin der meinen Account entsperren kann 🤣 ?

                          Hab scheinbar was übertrieben indem ich noch eine Topicvorschau mit einbauen wollte.... 😇

                          Diese Session hier läuft noch 👍

                          Edit
                          Oder wird das von selber aufgehoben?

                          Login fehlgeschlagen
                          Dein Konto wurde vorübergehend gesperrt.
                          

                          Zeigt eure Lovelace-Visualisierung klick
                          (Auch ideal um sich Anregungen zu holen)

                          Meine Tabellen für eure Visualisierung klick

                          HomoranH 1 Antwort Letzte Antwort
                          0
                          • David G.D David G.

                            Wer ist denn der Admin der meinen Account entsperren kann 🤣 ?

                            Hab scheinbar was übertrieben indem ich noch eine Topicvorschau mit einbauen wollte.... 😇

                            Diese Session hier läuft noch 👍

                            Edit
                            Oder wird das von selber aufgehoben?

                            Login fehlgeschlagen
                            Dein Konto wurde vorübergehend gesperrt.
                            
                            HomoranH Nicht stören
                            HomoranH Nicht stören
                            Homoran
                            Global Moderator Administrators
                            schrieb am zuletzt editiert von Homoran
                            #13

                            @David-G. sagte:

                            Diese Session hier läuft noch 👍

                            Habe dein Chrome auf Android und dein

                            axios 1.16.0 auf unknown

                            Widerrufen
                            Eine echte Sperre kann ich bei dir nicht finden

                            kein Support per PN! - Fragen im Forum stellen -
                            Benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.
                            Das Forum freut sich über eine Spende. Benutzt dazu den Spendenbutton oben rechts. Danke!
                            der Installationsfixer: curl -fsL https://iobroker.net/fix.sh | bash -

                            David G.D 1 Antwort Letzte Antwort
                            0
                            • HomoranH Homoran

                              @David-G. sagte:

                              Diese Session hier läuft noch 👍

                              Habe dein Chrome auf Android und dein

                              axios 1.16.0 auf unknown

                              Widerrufen
                              Eine echte Sperre kann ich bei dir nicht finden

                              David G.D Online
                              David G.D Online
                              David G.
                              schrieb am zuletzt editiert von
                              #14

                              @Homoran

                              Danke.
                              Dann lasse ich das Script jetzt bei den Basics.

                              Zeigt eure Lovelace-Visualisierung klick
                              (Auch ideal um sich Anregungen zu holen)

                              Meine Tabellen für eure Visualisierung klick

                              1 Antwort Letzte Antwort
                              0
                              • M Offline
                                M Offline
                                MCU
                                schrieb am zuletzt editiert von
                                #15

                                @homoran Gibt es eine Möglichkeit seine eigenen Beiträge als PDF / TXT mit Links zu den Bildern herunterzuladen?

                                NUC i7 64GB mit Proxmox ---- Jarvis Infos Aktualisierungen der Doku auf Instagram verfolgen -> mcuiobroker Instagram
                                Wenn Euch mein Vorschlag geholfen hat, bitte rechts "^" klicken.

                                HomoranH OliverIOO 2 Antworten Letzte Antwort
                                0
                                • M MCU

                                  @homoran Gibt es eine Möglichkeit seine eigenen Beiträge als PDF / TXT mit Links zu den Bildern herunterzuladen?

                                  HomoranH Nicht stören
                                  HomoranH Nicht stören
                                  Homoran
                                  Global Moderator Administrators
                                  schrieb am zuletzt editiert von
                                  #16

                                  @MCU puuh!
                                  Ist mir nicht bekannt

                                  kein Support per PN! - Fragen im Forum stellen -
                                  Benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.
                                  Das Forum freut sich über eine Spende. Benutzt dazu den Spendenbutton oben rechts. Danke!
                                  der Installationsfixer: curl -fsL https://iobroker.net/fix.sh | bash -

                                  1 Antwort Letzte Antwort
                                  0
                                  • M MCU

                                    @homoran Gibt es eine Möglichkeit seine eigenen Beiträge als PDF / TXT mit Links zu den Bildern herunterzuladen?

                                    OliverIOO Offline
                                    OliverIOO Offline
                                    OliverIO
                                    schrieb am zuletzt editiert von OliverIO
                                    #17

                                    @MCU

                                    stell doch mal eine anfrage gemäß dsgvo :)

                                    Alternativ kannst du dir über dein Profil und Klick auf Beiträge
                                    dir alle deine Beiträge anzeigen lassen.
                                    Wenn man dann auf Seitenweise Darstellung umstellt, kannst du dir die Seiten auch alle als PDF ausdrucken lassen. Aber alles vollautomatisch wird so nicht gehen

                                    Das habe ich noch gefunden, mittels einem Userscript und browser Extension
                                    https://community.nodebb.org/topic/18499/my-experiment-for-a-topic-export-save-to-pdf-html-feature

                                    Meine Adapter und Widgets
                                    TVProgram, SqueezeboxRPC, OpenLiga, RSSFeed, MyTime,, pi-hole2, vis-json-template, skiinfo, vis-mapwidgets, vis-2-widgets-rssfeed
                                    Links im Profil

                                    M 1 Antwort Letzte Antwort
                                    1
                                    • OliverIOO OliverIO

                                      @MCU

                                      stell doch mal eine anfrage gemäß dsgvo :)

                                      Alternativ kannst du dir über dein Profil und Klick auf Beiträge
                                      dir alle deine Beiträge anzeigen lassen.
                                      Wenn man dann auf Seitenweise Darstellung umstellt, kannst du dir die Seiten auch alle als PDF ausdrucken lassen. Aber alles vollautomatisch wird so nicht gehen

                                      Das habe ich noch gefunden, mittels einem Userscript und browser Extension
                                      https://community.nodebb.org/topic/18499/my-experiment-for-a-topic-export-save-to-pdf-html-feature

                                      M Offline
                                      M Offline
                                      MCU
                                      schrieb am zuletzt editiert von
                                      #18

                                      Es gibt eine Möglichkeit im Profil
                                      1565b73c-02ac-4b1c-aa71-82d8311947d5-image.jpeg

                                      NUC i7 64GB mit Proxmox ---- Jarvis Infos Aktualisierungen der Doku auf Instagram verfolgen -> mcuiobroker Instagram
                                      Wenn Euch mein Vorschlag geholfen hat, bitte rechts "^" klicken.

                                      1 Antwort Letzte Antwort
                                      2

                                      Hey! Du scheinst an dieser Unterhaltung interessiert zu sein, hast aber noch kein Konto.

                                      Hast du es satt, bei jedem Besuch durch die gleichen Beiträge zu scrollen? Wenn du dich für ein Konto anmeldest, kommst du immer genau dorthin zurück, wo du zuvor warst, und kannst dich über neue Antworten benachrichtigen lassen (entweder per E-Mail oder Push-Benachrichtigung). Du kannst auch Lesezeichen speichern und Beiträge positiv bewerten, um anderen Community-Mitgliedern deine Wertschätzung zu zeigen.

                                      Mit deinem Input könnte dieser Beitrag noch besser werden 💗

                                      Registrieren Anmelden
                                      Antworten
                                      • In einem neuen Thema antworten
                                      Anmelden zum Antworten
                                      • Älteste zuerst
                                      • Neuste zuerst
                                      • Meiste Stimmen


                                      Support us

                                      ioBroker
                                      Community Adapters
                                      Donate

                                      510

                                      Online

                                      32.9k

                                      Benutzer

                                      83.0k

                                      Themen

                                      1.3m

                                      Beiträge
                                      Community
                                      Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen | Einwilligungseinstellungen
                                      ioBroker Community 2014-2025
                                      logo
                                      • Anmelden

                                      • Du hast noch kein Konto? Registrieren

                                      • Anmelden oder registrieren, um zu suchen
                                      • Erster Beitrag
                                        Letzter Beitrag
                                      0
                                      • Home
                                      • Aktuell
                                      • Tags
                                      • Ungelesen 0
                                      • Kategorien
                                      • Unreplied
                                      • Beliebt
                                      • GitHub
                                      • Docu
                                      • Hilfe