// ==UserScript==
// @name Twitter/X Timeline Sync
// @description Tracks and syncs your last reading position on Twitter/X, with manual and automatic options. Ideal for keeping track of new posts without losing your place.
// @description:de Verfolgt und synchronisiert Ihre letzte Leseposition auf Twitter/X, mit manuellen und automatischen Optionen. Perfekt, um neue Beiträge im Blick zu behalten, ohne die aktuelle Position zu verlieren.
// @description:es Rastrea y sincroniza tu última posición de lectura en Twitter/X, con opciones manuales y automáticas. Ideal para mantener el seguimiento de las publicaciones nuevas sin perder tu posición.
// @description:fr Suit et synchronise votre dernière position de lecture sur Twitter/X, avec des options manuelles et automatiques. Idéal pour suivre les nouveaux posts sans perdre votre place actuelle.
// @description:zh-CN 跟踪并同步您在 Twitter/X 上的最后阅读位置,提供手动和自动选项。完美解决在查看新帖子时不丢失当前位置的问题。
// @description:ru Отслеживает и синхронизирует вашу последнюю позицию чтения на Twitter/X с ручными и автоматическими опциями. Идеально подходит для просмотра новых постов без потери текущей позиции.
// @description:ja Twitter/X での最後の読み取り位置を追跡して同期します。手動および自動オプションを提供します。新しい投稿を見逃さずに現在の位置を維持するのに最適です。
// @description:pt-BR Rastrea e sincroniza sua última posição de leitura no Twitter/X, com opções manuais e automáticas. Perfeito para acompanhar novos posts sem perder sua posição atual.
// @description:hi Twitter/X पर आपकी अंतिम पठन स्थिति को ट्रैक और सिंक करता है, मैनुअल और स्वचालित विकल्पों के साथ। नई पोस्ट देखते समय अपनी वर्तमान स्थिति को खोए बिना इसे ट्रैक करें।
// @description:ar يتتبع ويزامن آخر موضع قراءة لك على Twitter/X، مع خيارات يدوية وتلقائية. مثالي لتتبع المشاركات الجديدة دون فقدان موضعك الحالي.
// @description:it Traccia e sincronizza la tua ultima posizione di lettura su Twitter/X, con opzioni manuali e automatiche. Ideale per tenere traccia dei nuovi post senza perdere la posizione attuale.
// @description:ko Twitter/X에서 마지막 읽기 위치를 추적하고 동기화합니다. 수동 및 자동 옵션 포함. 새로운 게시물을 확인하면서 현재 위치를 잃지 않도록 이상적입니다。
// @icon https://x.com/favicon.ico
// @namespace http://tampermonkey.net/
// @version 2025-06-01-4
// @author Copiis
// @license MIT
// @match https://x.com/home
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// ==/UserScript==
// If you find this script useful and would like to support my work, consider making a small donation!
// Bitcoin (BTC): bc1quc5mkudlwwkktzhvzw5u2nruxyepef957p68r7
// PayPal: https://www.paypal.com/paypalme/Coopiis?country.x=DE&locale.x=de_DE
(function () {
let lastReadPost = null;
let isAutoScrolling = false;
let isSearching = false;
let isTabFocused = true;
let isScriptActivated = false;
// Initialize download enabled state
let isDownloadEnabled = GM_getValue("isDownloadEnabled", true); // Default to enabled
// Register ViolentMonkey menu commands to toggle download function
GM_registerMenuCommand("Enable Download", () => {
isDownloadEnabled = true;
GM_setValue("isDownloadEnabled", true);
showPopup("✅ Download function enabled.", 3000);
console.log("✅ Download function enabled.");
});
GM_registerMenuCommand("Disable Download", () => {
isDownloadEnabled = false;
GM_setValue("isDownloadEnabled", false);
showPopup("⏹️ Download function disabled.", 3000);
console.log("⏹️ Download function disabled.");
});
// Speichert die Lesestelle mit GM_setValue
function saveLastReadPost(data) {
let attempts = 0;
const maxAttempts = 3;
function trySave() {
try {
const bookmarkData = JSON.stringify(data);
GM_setValue("lastReadPost", bookmarkData);
console.log("💾 Leseposition erfolgreich mit GM_setValue gespeichert:", bookmarkData);
localStorage.setItem("lastReadPost", bookmarkData); // Optionaler Fallback
console.log("💾 Fallback in localStorage gespeichert:", localStorage.getItem("lastReadPost"));
} catch (err) {
attempts++;
console.error(`❌ Fehler beim Speichern der Leseposition (Versuch ${attempts}/${maxAttempts}):`, err);
if (attempts < maxAttempts) {
console.log("🔄 Wiederhole Speicherversuch...");
setTimeout(trySave, 1000);
} else {
console.error("❌ Maximale Speicherversuche erreicht. Fallback auf localStorage.");
localStorage.setItem("lastReadPost", JSON.stringify(data));
promptManualFallback(data);
}
}
}
trySave();
}
// Speichert die heruntergeladene Lesestelle mit GM_setValue
function saveLastDownloadedPost(data) {
try {
const bookmarkData = JSON.stringify(data);
GM_setValue("lastDownloadedPost", bookmarkData);
console.log("💾 Zuletzt heruntergeladene Leseposition gespeichert:", bookmarkData);
} catch (err) {
console.error("❌ Fehler beim Speichern der heruntergeladenen Leseposition:", err);
}
}
// Lädt die Lesestelle mit GM_getValue
function loadLastReadPost(callback) {
try {
const storedData = GM_getValue("lastReadPost", null);
if (storedData) {
const data = JSON.parse(storedData);
console.log("✅ Leseposition geladen:", data);
callback(data);
} else {
console.warn("⚠️ Keine gespeicherte Leseposition gefunden.");
callback(null);
}
} catch (err) {
console.error("❌ Fehler beim Laden der Leseposition:", err);
const storedPost = JSON.parse(localStorage.getItem("lastReadPost") || "{}");
callback(storedPost);
}
}
// Lädt die zuletzt heruntergeladene Lesestelle
function loadLastDownloadedPost(callback) {
try {
const storedData = GM_getValue("lastDownloadedPost", null);
if (storedData) {
const data = JSON.parse(storedData);
console.log("✅ Zuletzt heruntergeladene Leseposition geladen:", data);
callback(data);
} else {
console.warn("⚠️ Keine heruntergeladene Leseposition gefunden.");
callback(null);
}
} catch (err) {
console.error("❌ Fehler beim Laden der heruntergeladenen Leseposition:", err);
callback(null);
}
}
// Fallback: Manuelle Benachrichtigung
function promptManualFallback(data) {
const content = JSON.stringify(data);
const message = `📝 Neue Leseposition: ${content}\nBitte speichere dies manuell, da der Speichervorgang fehlschlug.`;
showPopup(message, 10000);
console.log("📝 Bitte manuell speichern:", content);
}
// Initialisierung
function initializeWhenDOMReady() {
if (!window.location.href.includes("/home")) {
console.log("🚫 Skript deaktiviert: Nicht auf der Home-Seite.");
return;
}
console.log("🚀 Initialisiere Skript...");
const observer = new MutationObserver((mutations, obs) => {
if (document.body) {
obs.disconnect();
initializeScript().then(() => {
createButtons();
startPeriodicSave();
}).catch(err => {
console.error("❌ Fehler bei der Initialisierung:", err);
showPopup("❌ Fehler beim Laden des Skripts.");
});
}
});
observer.observe(document.documentElement, { childList: true, subtree: true });
}
window.addEventListener("load", initializeWhenDOMReady);
// Event-Listener für Fokusverlust
window.addEventListener("blur", () => {
isTabFocused = false;
console.log("🔴 Tab hat Fokus verloren.");
saveLastReadPostToFile();
});
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "hidden") {
isTabFocused = false;
console.log("🔴 Tab ist unsichtbar.");
saveLastReadPostToFile();
} else {
isTabFocused = true;
console.log("🟢 Tab wieder sichtbar.");
}
});
window.addEventListener("focus", () => {
isTabFocused = true;
console.log("🟢 Tab wieder fokussiert.");
});
function startPeriodicSave() {
setInterval(() => {
if (isTabFocused && lastReadPost && isScriptActivated) {
loadLastReadPost(existingPost => {
if (!existingPost || new Date(lastReadPost.timestamp) > new Date(existingPost.timestamp) ||
(lastReadPost.timestamp === existingPost.timestamp && lastReadPost.authorHandler !== existingPost.authorHandler)) {
saveLastReadPost(lastReadPost); // Nur speichern, wenn neuer oder anderer Autor
console.log("💾 Periodische Speicherung: Neue Leseposition gespeichert:", lastReadPost);
} else {
console.log("⏹️ Periodische Speicherung übersprungen: Leseposition nicht neuer oder identisch.");
}
});
}
}, 30000);
}
// Funktion zum Speichern der Lesestelle als Datei im Download-Ordner
function saveLastReadPostToFile() {
if (!isDownloadEnabled) {
console.log("⏹️ Download übersprungen: Download-Funktion deaktiviert.");
return;
}
try {
if (!lastReadPost || !lastReadPost.timestamp || !lastReadPost.authorHandler) {
console.warn("⚠️ Keine gültige Leseposition zum Speichern:", lastReadPost);
return;
}
// Prüfen, ob die Lesestelle neuer ist als die zuletzt heruntergeladene
loadLastDownloadedPost(lastDownloadedPost => {
console.log("🛠️ DEBUG: Vergleich - lastReadPost:", lastReadPost, "lastDownloadedPost:", lastDownloadedPost);
if (lastDownloadedPost &&
new Date(lastReadPost.timestamp) <= new Date(lastDownloadedPost.timestamp) &&
lastReadPost.authorHandler === lastDownloadedPost.authorHandler) {
console.log("⏹️ Datei-Download übersprungen: Lesestelle nicht neuer oder identisch.");
return;
}
// Datum und Uhrzeit aus dem Timestamp extrahieren
const date = new Date(lastReadPost.timestamp);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
const hour = String(date.getHours()).padStart(2, "0");
const minute = String(date.getMinutes()).padStart(2, "0");
const second = String(date.getSeconds()).padStart(2, "0");
// Dateinamen im gewünschten Format erstellen
const fileName = `${year}${month}${day}_${hour}${minute}${second}-${lastReadPost.authorHandler}.json`;
// Dateiinhalt erstellen (JSON-Format)
const fileContent = JSON.stringify(lastReadPost, null, 2);
// Blob erstellen und Download auslösen
const blob = new Blob([fileContent], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
// Zuletzt heruntergeladene Lesestelle speichern
saveLastDownloadedPost(lastReadPost);
console.log(`💾 Leseposition als Datei gespeichert: ${fileName}`);
});
} catch (err) {
console.error("❌ Fehler beim Speichern der Datei:", err);
promptManualFallback(lastReadPost);
}
}
// Funktion zum Laden der Lesestelle aus einer Datei
function loadLastReadPostFromFile() {
try {
const input = document.createElement("input");
input.type = "file";
input.accept = ".json";
input.style.display = "none";
document.body.appendChild(input);
input.addEventListener("change", (event) => {
const file = event.target.files[0];
if (!file) {
console.warn("⚠️ Keine Datei ausgewählt.");
showPopup("❌ Bitte wähle eine JSON-Datei aus.", 5000);
document.body.removeChild(input);
return;
}
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = JSON.parse(e.target.result);
if (data.timestamp && data.authorHandler) {
lastReadPost = data;
saveLastReadPost(data); // Speichert in GM_setValue und localStorage
console.log("✅ Leseposition aus Datei geladen:", lastReadPost);
showPopup("✅ Leseposition erfolgreich geladen!", 3000);
// Optional: Automatische Suche nach der geladenen Position
startRefinedSearchForLastReadPost();
} else {
console.warn("⚠️ Ungültige Leseposition in der Datei:", data);
showPopup("❌ Ungültige Leseposition in der Datei.", 5000);
}
} catch (err) {
console.error("❌ Fehler beim Lesen der Datei:", err);
showPopup("❌ Fehler beim Lesen der Datei.", 5000);
}
document.body.removeChild(input);
};
reader.readAsText(file);
});
input.click();
} catch (err) {
console.error("❌ Fehler beim Öffnen des Datei-Dialogs:", err);
showPopup("❌ Fehler beim Öffnen des Datei-Dialogs.", 5000);
}
}
function loadNewestLastReadPost() {
return new Promise(resolve => {
loadLastReadPost(storedPost => {
if (storedPost && storedPost.timestamp && storedPost.authorHandler) {
lastReadPost = storedPost;
console.log("✅ Leseposition geladen:", lastReadPost);
} else {
const localPost = JSON.parse(localStorage.getItem("lastReadPost") || "{}");
if (localPost && localPost.timestamp && localPost.authorHandler) {
lastReadPost = localPost;
console.log("✅ Leseposition aus localStorage:", lastReadPost);
} else {
console.warn("⚠️ Keine Leseposition gefunden.");
showPopup("ℹ️ Scrolle, um eine Leseposition zu setzen.");
}
}
resolve();
});
});
}
async function initializeScript() {
console.log("🔧 Lade Leseposition...");
await loadNewestLastReadPost();
window.addEventListener("scroll", () => {
if (!isScriptActivated) {
isScriptActivated = true;
console.log("🛠️ DEBUG: Skript durch Scrollen aktiviert.");
observeForNewPosts();
}
if (isAutoScrolling || isSearching) {
console.log("⏹️ Scroll-Ereignis ignoriert.");
return;
}
markTopVisiblePost(true);
}, { passive: true });
}
function markTopVisiblePost(save = true) {
const topPost = getTopVisiblePost();
if (!topPost) {
console.log("❌ Kein sichtbarer Beitrag.");
return;
}
// Nur speichern, wenn der Post ein Thread-Starter ist
if (!isThreadStarter(topPost)) {
console.log("⏹️ Post ist keine Thread-Starter, wird nicht gespeichert.");
return;
}
const postTimestamp = getPostTimestamp(topPost);
const postAuthorHandler = getPostAuthorHandler(topPost);
if (postTimestamp && postAuthorHandler) {
const newPost = { timestamp: postTimestamp, authorHandler: postAuthorHandler };
if (save && isScriptActivated) {
loadLastReadPost(existingPost => {
console.log("🛠️ DEBUG: markTopVisiblePost - newPost:", newPost, "existingPost:", existingPost);
if (!existingPost ||
new Date(postTimestamp) > new Date(existingPost.timestamp) ||
(postTimestamp === existingPost.timestamp && postAuthorHandler !== existingPost.authorHandler)) {
lastReadPost = newPost;
console.log("💾 Neue Leseposition gesetzt (Thread-Starter):", lastReadPost);
saveLastReadPost(lastReadPost); // Immer speichern, wenn neuer oder anderer Autor
} else {
console.log("⏹️ Interne Speicherung übersprungen: Leseposition nicht neuer oder identisch.");
}
});
}
}
}
function isThreadStarter(post) {
// Prüft, ob der Post eine Antwort ist, indem nach einem "Replying to"-Element gesucht wird
const replyIndicator = post.querySelector('a[href*="/status/"] > span');
if (replyIndicator && replyIndicator.textContent.includes("Replying to")) {
console.log("⏹️ Post ist eine Antwort, kein Thread-Starter.");
return false;
}
// Zusätzliche Prüfung: Haupt-Posts haben oft kein übergeordnetes Thread-Element
const parentThread = post.closest('div[data-testid="tweet"]');
if (parentThread && parentThread.querySelector('[data-testid="conversation"]')) {
console.log("⏹️ Post ist Teil eines Konversationsthreads, kein Starter.");
return false;
}
console.log("✅ Post ist ein Thread-Starter.");
return true;
}
function getTopVisiblePost() {
const posts = Array.from(document.querySelectorAll("article"));
return posts.find(post => {
const rect = post.getBoundingClientRect();
return rect.top >= 0 && rect.bottom > 0;
});
}
function getPostTimestamp(post) {
const timeElement = post.querySelector("time");
return timeElement ? timeElement.getAttribute("datetime") : null;
}
function getPostAuthorHandler(post) {
const handlerElement = post.querySelector('[role="link"][href*="/"]');
return handlerElement ? handlerElement.getAttribute("href").slice(1) : null;
}
function startRefinedSearchForLastReadPost() {
if (!isScriptActivated) {
console.log("⏹️ Suche abgebrochen: Skript nicht aktiviert.");
showPopup("ℹ️ Bitte scrollen oder Lupe klicken.");
return;
}
loadLastReadPost(storedData => {
if (!storedData) {
const localData = JSON.parse(localStorage.getItem("lastReadPost") || "{}");
if (localData && localData.timestamp && localData.authorHandler) {
lastReadPost = localData;
} else {
console.log("❌ Keine Leseposition gefunden.");
showPopup("❌ Keine Leseposition vorhanden.");
return;
}
} else {
lastReadPost = storedData;
}
if (!lastReadPost.timestamp || !lastReadPost.authorHandler) {
console.log("❌ Ungültige Leseposition:", lastReadPost);
showPopup("❌ Ungültige Leseposition.");
return;
}
console.log("🔍 Starte Suche:", lastReadPost);
const popup = createSearchPopup();
let direction = 1;
let scrollAmount = 2000;
let previousScrollY = -1;
let searchAttempts = 0;
const maxAttempts = 50;
function handleSpaceKey(event) {
if (event.code === "Space") {
console.log("⏹️ Suche gestoppt.");
isSearching = false;
popup.remove();
window.removeEventListener("keydown", handleSpaceKey);
}
}
window.addEventListener("keydown", handleSpaceKey);
const search = () => {
if (!isSearching || searchAttempts >= maxAttempts) {
console.log("⏹️ Suche beendet: Max Versuche oder abgebrochen.");
isSearching = false;
popup.remove();
window.removeEventListener("keydown", handleSpaceKey);
return;
}
const visiblePosts = getVisiblePosts();
const comparison = compareVisiblePostsToLastReadPost(visiblePosts);
if (comparison === "match") {
const matchedPost = findPostByData(lastReadPost);
if (matchedPost) {
console.log("🎯 Beitrag gefunden:", lastReadPost);
isAutoScrolling = true;
scrollToPostWithHighlight(matchedPost);
isSearching = false;
popup.remove();
window.removeEventListener("keydown", handleSpaceKey);
return;
}
} else if (comparison === "older") {
direction = -1;
} else if (comparison === "newer") {
direction = 1;
}
if (window.scrollY === previousScrollY) {
scrollAmount = Math.max(scrollAmount / 2, 500);
direction = -direction;
} else {
scrollAmount = Math.min(scrollAmount * 1.5, 3000);
}
previousScrollY = window.scrollY;
searchAttempts++;
requestAnimationFrame(() => {
window.scrollBy({
top: direction * scrollAmount,
behavior: "smooth"
});
setTimeout(search, 1000);
});
};
isSearching = true;
search();
});
}
function createSearchPopup() {
const popup = document.createElement("div");
popup.style.position = "fixed";
popup.style.bottom = "20px";
popup.style.left = "50%";
popup.style.transform = "translateX(-50%)";
popup.style.backgroundColor = "rgba(0, 0, 0, 0.9)";
popup.style.color = "#ffffff";
popup.style.padding = "10px 20px";
popup.style.borderRadius = "8px";
popup.style.fontSize = "14px";
popup.style.boxShadow = "0 0 10px rgba(255, 255, 255, 0.8)";
popup.style.zIndex = "10000";
popup.textContent = "🔍 Searching... Press SPACE to cancel.";
document.body.appendChild(popup);
return popup;
}
function compareVisiblePostsToLastReadPost(posts, customPosition = lastReadPost) {
const validPosts = posts.filter(post => post.timestamp && post.authorHandler);
if (validPosts.length === 0) {
console.log("⚠️ Keine sichtbaren Beiträge.");
return null;
}
const lastReadTime = new Date(customPosition.timestamp);
const allOlder = validPosts.every(post => new Date(post.timestamp) < lastReadTime);
const allNewer = validPosts.every(post => new Date(post.timestamp) > lastReadTime);
if (validPosts.some(post => post.timestamp === customPosition.timestamp && post.authorHandler === customPosition.authorHandler)) {
return "match";
} else if (allOlder) {
return "older";
} else if (allNewer) {
return "newer";
} else {
return "mixed";
}
}
function scrollToPostWithHighlight(post) {
if (!post) {
console.log("❌ Kein Beitrag zum Scrollen.");
return;
}
isAutoScrolling = true;
post.style.outline = "none";
post.style.boxShadow = "0 0 20px 10px rgba(255, 215, 0, 0.9)";
post.style.animation = "none";
const existingStyle = document.querySelector('#glowStyle');
if (existingStyle) {
existingStyle.remove();
}
post.scrollIntoView({ behavior: "smooth", block: "center" });
const removeHighlightOnScroll = () => {
if (!isAutoScrolling) {
post.style.boxShadow = "none";
console.log("✅ Highlight entfernt.");
window.removeEventListener("scroll", removeHighlightOnScroll);
}
};
setTimeout(() => {
isAutoScrolling = false;
window.addEventListener("scroll", removeHighlightOnScroll);
console.log("✅ Beitrag zentriert.");
}, 1000);
}
function getVisiblePosts() {
const posts = Array.from(document.querySelectorAll("article"));
return posts.map(post => ({
element: post,
timestamp: getPostTimestamp(post),
authorHandler: getPostAuthorHandler(post),
}));
}
function observeForNewPosts() {
let isProcessingIndicator = false;
const observer = new MutationObserver(() => {
if (!isScriptActivated) {
console.log("⏹️ Beobachtung abgebrochen: Skript nicht aktiviert.");
return;
}
if (window.scrollY <= 1 && !isSearching && !isProcessingIndicator && lastReadPost) {
const newPostsIndicator = getNewPostsIndicator();
if (newPostsIndicator) {
console.log("🆕 Neue Beiträge erkannt.");
isProcessingIndicator = true;
clickNewPostsIndicator(newPostsIndicator);
setTimeout(() => {
startRefinedSearchForLastReadPost();
isProcessingIndicator = false;
}, 2000);
}
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
}
function getNewPostsIndicator() {
const buttons = document.querySelectorAll('button[role="button"]');
for (const button of buttons) {
const span = button.querySelector('span.r-poiln3');
if (span) {
const textContent = span.textContent || '';
const postIndicatorPattern = /^\d+\s*(neue|new)?\s*(Post|Posts|Beitrag|Beiträge|Tweet|Tweets|Publicación|Publications|投稿|게시물|пост|постов|mensagem|mensagens|مشاركة|مشاركات)\b/i;
if (postIndicatorPattern.test(textContent)) {
if (!button.dataset.processed) {
console.log(`🆕 Indikator gefunden: "${textContent}"`);
button.dataset.processed = 'true';
return button;
}
}
}
}
console.log("ℹ️ Kein Beitragsindikator gefunden.");
return null;
}
function clickNewPostsIndicator(indicator) {
if (!indicator) {
console.log("⚠️ Kein Indikator gefunden.");
return;
}
console.log("✅ Klicke auf Indikator...");
try {
indicator.click();
console.log("✅ Indikator geklickt.");
} catch (err) {
console.error("❌ Fehler beim Klicken:", err);
}
}
function findPostByData(data) {
const posts = Array.from(document.querySelectorAll("article"));
return posts.find(post => {
const postTimestamp = getPostTimestamp(post);
const authorHandler = getPostAuthorHandler(post);
return postTimestamp === data.timestamp && authorHandler === data.authorHandler;
});
}
function createButtons() {
setTimeout(() => {
try {
if (!document.body) {
console.warn("⚠️ document.body nicht verfügbar.");
return;
}
const buttonContainer = document.createElement("div");
let targetDiv = document.querySelector('div.css-175oi2r.r-dnmrzs.r-1559e4e');
if (!targetDiv) {
targetDiv = document.querySelector('div[role="heading"]');
console.warn("⚠️ Primäres Ziel-<div> nicht gefunden. Fallback auf div[role='heading'].");
}
let leftOffset = 60;
let topOffset = 15;
if (targetDiv) {
const rect = targetDiv.getBoundingClientRect();
leftOffset = rect.right + 10;
topOffset = rect.top + (rect.height / 2) - 18;
console.log("🛠️ DEBUG: Ziel-<div> gefunden. Position:", { left: leftOffset, top: topOffset });
} else {
console.warn("⚠️ Kein Ziel-<div> gefunden. Fallback-Position.");
}
buttonContainer.style.position = "fixed";
buttonContainer.style.top = `${topOffset}px`;
buttonContainer.style.left = `${leftOffset}px`;
buttonContainer.style.zIndex = "10000";
buttonContainer.style.display = "flex";
buttonContainer.style.alignItems = "center";
buttonContainer.style.visibility = "visible";
const buttonsConfig = [
{
icon: "🔍",
title: "Start manual search",
onClick: () => {
console.log("🔍 Manuelle Suche gestartet.");
if (!isScriptActivated) {
isScriptActivated = true;
console.log("🛠️ DEBUG: Skript durch Lupen-Klick aktiviert.");
observeForNewPosts();
}
startRefinedSearchForLastReadPost();
},
},
{
icon: "📂",
title: "Load last read position from file",
onClick: () => {
console.log("📂 Lade Leseposition aus Datei...");
loadLastReadPostFromFile();
},
},
];
buttonsConfig.forEach(({ icon, title, onClick }) => {
const button = createButton(icon, title, onClick);
buttonContainer.appendChild(button);
});
document.body.appendChild(buttonContainer);
console.log("🛠️ DEBUG: Button-Container erstellt:", buttonContainer);
} catch (err) {
console.error("❌ Fehler beim Erstellen der Buttons:", err);
showPopup("❌ Fehler beim Anzeigen der Buttons.");
}
}, 10000);
}
function createButton(icon, title, onClick) {
const button = document.createElement("div");
button.style.width = "36px";
button.style.height = "36px";
button.style.backgroundColor = "rgba(0, 0, 0, 0.9)";
button.style.color = "#ffffff";
button.style.borderRadius = "50%";
button.style.display = "flex";
button.style.justifyContent = "center";
button.style.alignItems = "center";
button.style.cursor = "pointer";
button.style.fontSize = "18px";
button.style.boxShadow = "0 0 10px rgba(255, 255, 255, 0.5)";
button.style.transition = "transform 0.2s, box-shadow 0.3s";
button.style.zIndex = "10001";
button.style.visibility = "visible";
button.textContent = icon;
button.title = title;
button.addEventListener("click", () => {
button.style.boxShadow = "0 0 20px rgba(255, 255, 255, 0.8)";
button.style.transform = "scale(0.9)";
setTimeout(() => {
button.style.boxShadow = "0 0 10px rgba(255, 255, 255, 0.5)";
button.style.transform = "scale(1)";
}, 300);
onClick();
});
button.addEventListener("mouseenter", () => {
button.style.boxShadow = "0 0 15px rgba(255, 255, 255, 0.7)";
button.style.transform = "scale(1.1)";
});
button.addEventListener("mouseleave", () => {
button.style.boxShadow = "0 0 10px rgba(255, 255, 255, 0.5)";
button.style.transform = "scale(1)";
});
return button;
}
function showPopup(message, duration = 3000) {
const popup = document.createElement("div");
popup.style.position = "fixed";
popup.style.bottom = "20px";
popup.style.right = "20px";
popup.style.backgroundColor = "rgba(0, 0, 0, 0.9)";
popup.style.color = "#ffffff";
popup.style.padding = "10px 20px";
popup.style.borderRadius = "8px";
popup.style.fontSize = "14px";
popup.style.boxShadow = "0 0 10px rgba(255, 255, 255, 0.8)";
popup.style.zIndex = "10000";
popup.style.maxWidth = "400px";
popup.style.whiteSpace = "pre-wrap";
popup.textContent = message;
document.body.appendChild(popup);
setTimeout(() => {
popup.remove();
}, duration);
}
})();