- // ==UserScript==
- // @name 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-02-26.2
- // @author Copiis
- // @license MIT
- // @match https://x.com/home
- // @grant GM_setValue
- // @grant GM_getValue
- // @grant GM_download
- // ==/UserScript==
-
- (function() {
- 'use strict';
-
- let lastReadPost = null;
- let isAutoScrolling = false;
- let isSearching = false;
- let isTabFocused = true;
- let downloadTriggered = false;
- let isPostLoading = false;
- let hasScrolledAfterLoad = false;
- let saveToDownloadFolder = GM_getValue("saveToDownloadFolder", true);
-
- const translations = {
- en: {
- scriptDisabled: "🚫 Script disabled: Not on the home page.",
- pageLoaded: "🚀 Page fully loaded. Initializing script...",
- tabBlur: "🌐 Tab lost focus.",
- downloadStart: "📥 Starting download of last read position...",
- alreadyDownloaded: "🗂️ Position already downloaded.",
- tabFocused: "🟢 Tab refocused.",
- saveSuccess: "✅ Last read position saved:",
- saveFail: "⚠️ No valid position to save.",
- noPostFound: "❌ No top visible post found.",
- highlightSuccess: "✅ Post highlighted successfully.",
- searchStart: "🔍 Refined search started...",
- searchCancel: "⏹️ Search manually canceled.",
- contentLoadWait: "⌛ Waiting for content to load...",
- toggleSaveOn: "💾 Save to download folder enabled",
- toggleSaveOff: "🚫 Save to download folder disabled"
- },
- de: {
- scriptDisabled: "🚫 Skript deaktiviert: Nicht auf der Home-Seite.",
- pageLoaded: "🚀 Seite vollständig geladen. Initialisiere Skript...",
- tabBlur: "🌐 Tab hat den Fokus verloren.",
- downloadStart: "📥 Starte Download der letzten Leseposition...",
- alreadyDownloaded: "🗂️ Leseposition bereits im Download-Ordner vorhanden.",
- tabFocused: "🟢 Tab wieder fokussiert.",
- saveSuccess: "✅ Leseposition erfolgreich gespeichert:",
- saveFail: "⚠️ Keine gültige Leseposition zum Speichern.",
- noPostFound: "❌ Kein oberster sichtbarer Beitrag gefunden.",
- highlightSuccess: "✅ Beitrag erfolgreich hervorgehoben.",
- searchStart: "🔍 Verfeinerte Suche gestartet...",
- searchCancel: "⏹️ Suche manuell abgebrochen.",
- contentLoadWait: "⌛ Warte darauf, dass der Inhalt geladen wird...",
- toggleSaveOn: "💾 Speichern im Download-Ordner aktiviert",
- toggleSaveOff: "🚫 Speichern im Download-Ordner deaktiviert"
- }
- };
-
- const userLang = navigator.language.split('-')[0];
- const t = (key) => translations[userLang]?.[key] || translations.en[key];
-
- function debounce(func, wait) {
- let timeout;
- return function executedFunction(...args) {
- const later = () => {
- clearTimeout(timeout);
- func(...args);
- };
- clearTimeout(timeout);
- timeout = setTimeout(later, wait);
- };
- }
-
- const observer = new IntersectionObserver(
- entries => {
- entries.forEach(entry => {
- if (entry.isIntersecting) {
- const postData = {
- timestamp: getPostTimestamp(entry.target),
- authorHandler: getPostAuthorHandler(entry.target)
- };
- }
- });
- },
- { threshold: 0.1 }
- );
-
- function observeVisiblePosts() {
- const articles = document.querySelectorAll('article');
- const viewportHeight = window.innerHeight;
- const buffer = viewportHeight * 2;
-
- for (let article of articles) {
- const rect = article.getBoundingClientRect();
- if (rect.top < buffer && rect.bottom > -buffer) {
- observer.observe(article);
- } else {
- observer.unobserve(article);
- }
- }
- }
-
- function loadNewestLastReadPost() {
- const data = GM_getValue("lastReadPost", null);
- if (data) {
- lastReadPost = JSON.parse(data);
- console.log(t("saveSuccess"), lastReadPost);
- } else {
- console.warn(t("saveFail"));
- }
- }
-
- function loadLastReadPostFromFile() {
- loadNewestLastReadPost();
- }
-
- function saveLastReadPostToFile() {
- if (lastReadPost && lastReadPost.timestamp && lastReadPost.authorHandler) {
- GM_setValue("lastReadPost", JSON.stringify(lastReadPost));
- console.log(t("saveSuccess"), lastReadPost);
- } else {
- console.warn(t("saveFail"));
- }
- }
-
- function downloadLastReadPost() {
- if (!saveToDownloadFolder) {
- console.log("Saving to download folder is disabled.");
- return;
- }
- if (!lastReadPost || !lastReadPost.timestamp || !lastReadPost.authorHandler) {
- console.warn(t("saveFail"));
- return;
- }
- try {
- const data = JSON.stringify(lastReadPost, null, 2);
- const sanitizedHandler = lastReadPost.authorHandler.replace(/[^a-zA-Z0-9-_]/g, "");
- const timestamp = new Date(lastReadPost.timestamp).toISOString().replace(/[:.-]/g, "_");
- const fileName = `${sanitizedHandler}_${timestamp}.json`;
-
- GM_download({
- url: `data:application/json;charset=utf-8,${encodeURIComponent(data)}`,
- name: fileName,
- onload: () => console.log(`${t("saveSuccess")} ${fileName}`),
- onerror: (err) => console.error("❌ Error downloading:", err),
- });
- } catch (error) {
- console.error("❌ Download error:", error);
- }
- }
-
- function markTopVisiblePost(save = true) {
- const topPost = getTopVisiblePost();
- if (!topPost) {
- console.log(t("noPostFound"));
- return;
- }
-
- const postTimestamp = getPostTimestamp(topPost);
- const authorHandler = getPostAuthorHandler(topPost);
-
- if (postTimestamp && authorHandler) {
- if (save && (!lastReadPost || new Date(postTimestamp) > new Date(lastReadPost.timestamp))) {
- lastReadPost = { timestamp: postTimestamp, authorHandler };
- saveLastReadPostToFile();
- }
- }
- }
-
- function getTopVisiblePost() {
- return Array.from(document.querySelectorAll("article")).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 (!lastReadPost || !lastReadPost.timestamp || !lastReadPost.authorHandler || isPostLoading) return;
-
- console.log(t("searchStart"));
- const popup = createSearchPopup();
- let direction = 1;
- let scrollAmount = 200;
- let scrollSpeed = 1000;
- let scrollInterval = 200;
- let lastComparison = null;
- let initialAdjusted = false;
- let lastScrollDirection = null;
- let lastScrollY = 0;
- let jumpMultiplier = 1;
-
- function handleSpaceKey(event) {
- if (event.code === "Space") {
- console.log(t("searchCancel"));
- isSearching = false;
- popup.remove();
- window.removeEventListener("keydown", handleSpaceKey);
- }
- }
-
- window.addEventListener("keydown", handleSpaceKey);
-
- function adjustScrollParameters(lastReadTime) {
- const visiblePosts = getVisiblePosts();
- if (visiblePosts.length === 0) return { amount: 200, speed: 1000 };
-
- const nearestPost = visiblePosts.reduce((closest, post) => {
- const postTime = new Date(post.timestamp);
- const diffCurrent = Math.abs(postTime - lastReadTime);
- const diffClosest = Math.abs(new Date(closest.timestamp) - lastReadTime);
- return diffCurrent < diffClosest ? post : closest;
- });
-
- const nearestTime = new Date(nearestPost.timestamp);
- const timeDifference = Math.abs(lastReadTime - nearestTime) / (1000 * 60);
-
- let newScrollAmount = 200;
- let newScrollSpeed = 1000;
-
- if (timeDifference < 5) {
- newScrollAmount = 50;
- newScrollSpeed = 500;
- jumpMultiplier = 1;
- } else if (timeDifference < 30) {
- newScrollAmount = 100;
- newScrollSpeed = 1000;
- jumpMultiplier = 1;
- } else if (timeDifference < 60) {
- newScrollAmount = 200;
- newScrollSpeed = 1500;
- jumpMultiplier = 1.5;
- } else if (timeDifference < 1440) {
- newScrollAmount = 500;
- newScrollSpeed = 2000;
- jumpMultiplier = 2;
- } else {
- newScrollAmount = 1000;
- newScrollSpeed = 3000;
- jumpMultiplier = 3;
- }
-
- newScrollAmount = Math.max(50, Math.min(newScrollAmount * jumpMultiplier, window.innerHeight * 2));
- return { amount: newScrollAmount, speed: newScrollSpeed };
- }
-
- async function search() {
- if (!isSearching) {
- popup.remove();
- return;
- }
-
- const visiblePosts = getVisiblePosts();
- if (visiblePosts.length === 0 && !isPostLoading) {
- setTimeout(search, scrollInterval);
- return;
- }
-
- if (!initialAdjusted) {
- const comparison = compareVisiblePostsToLastReadPost(visiblePosts);
- adjustInitialScroll(comparison);
- initialAdjusted = true;
- }
-
- const comparison = compareVisiblePostsToLastReadPost(visiblePosts);
- const lastReadTime = new Date(lastReadPost.timestamp);
- let nearestVisiblePostTime = null;
-
- if (visiblePosts.length > 0) {
- nearestVisiblePostTime = new Date(visiblePosts[0].timestamp);
- }
-
- const { amount, speed } = adjustScrollParameters(lastReadTime);
- scrollAmount = amount;
- scrollSpeed = speed;
- scrollInterval = Math.max(30, 1000 / (scrollSpeed / scrollAmount));
-
- const distanceToBottom = document.documentElement.scrollHeight - (window.scrollY + window.innerHeight);
- if (distanceToBottom < window.innerHeight) {
- scrollAmount = Math.max(50, scrollAmount / 2);
- scrollSpeed = Math.max(500, scrollSpeed / 2);
- scrollInterval = Math.max(30, 1000 / (scrollSpeed / scrollAmount));
- }
-
- if (comparison === "match") {
- const matchedPost = findPostByData(lastReadPost);
- if (matchedPost) {
- scrollToPostWithHighlight(matchedPost);
- isSearching = false;
- popup.remove();
- window.removeEventListener("keydown", handleSpaceKey);
- setTimeout(() => {
- isAutoScrolling = false;
- }, 1000);
- return;
- }
- } else if (comparison === "older") {
- direction = -1;
- if (lastComparison === "newer") jumpMultiplier *= 0.5;
- } else if (comparison === "newer") {
- direction = 1;
- if (lastComparison === "older") jumpMultiplier *= 0.5;
- } else if (comparison === "mixed") {
- scrollAmount = Math.max(50, scrollAmount / 2);
- scrollSpeed = Math.max(500, scrollSpeed / 2);
- scrollInterval = Math.max(30, 1000 / (scrollSpeed / scrollAmount));
- }
-
- if (window.scrollY === 0 && direction === -1) {
- direction = 1;
- jumpMultiplier *= 2;
- } else if (distanceToBottom < 50 && direction === 1) {
- direction = -1;
- jumpMultiplier *= 2;
- }
-
- lastComparison = comparison;
- lastScrollDirection = direction;
- lastScrollY = window.scrollY;
-
- console.log(`Scroll-Richtung: ${direction}, Betrag: ${scrollAmount}px, Geschwindigkeit: ${scrollSpeed}px/s, Intervall: ${scrollInterval}ms, Position: ${window.scrollY}, Zeitdifferenz: ${nearestVisiblePostTime ? Math.abs(lastReadTime - nearestVisiblePostTime) : 'N/A'}`);
-
- requestAnimationFrame(() => {
- window.scrollBy(0, direction * scrollAmount);
- setTimeout(search, scrollInterval);
- });
- }
-
- isSearching = true;
- search();
- }
-
- function adjustInitialScroll(comparison) {
- const initialScrollAmount = 2000;
- if (comparison === "older") {
- for (let i = 0; i < 3; i++) {
- window.scrollBy(0, -initialScrollAmount / 3);
- }
- } else if (comparison === "newer") {
- for (let i = 0; i < 3; i++) {
- window.scrollBy(0, initialScrollAmount / 3);
- }
- }
- }
-
- function createSearchPopup() {
- const popup = document.createElement("div");
- popup.style.cssText = `position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); background: rgba(0, 0, 0, 0.9); color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; box-shadow: 0 0 10px rgba(255, 255, 255, 0.8); z-index: 10000;`;
- popup.textContent = "🔍 Refined search in progress... Press SPACE to cancel.";
- document.body.appendChild(popup);
- return popup;
- }
-
- function compareVisiblePostsToLastReadPost(posts) {
- const validPosts = posts.filter(post => post.timestamp && post.authorHandler);
- if (validPosts.length === 0) return null;
-
- const lastReadTime = new Date(lastReadPost.timestamp);
-
- if (validPosts.some(post => post.timestamp === lastReadPost.timestamp && post.authorHandler === lastReadPost.authorHandler)) {
- return "match";
- } else if (validPosts.every(post => new Date(post.timestamp) < lastReadTime)) {
- return "older";
- } else if (validPosts.every(post => new Date(post.timestamp) > lastReadTime)) {
- return "newer";
- } else {
- return "mixed";
- }
- }
-
- function scrollToPostWithHighlight(post) {
- if (!post) return;
- isAutoScrolling = true;
-
- post.style.cssText = `outline: none; box-shadow: 0 0 30px 10px rgba(255, 223, 0, 1); background-color: rgba(255, 223, 0, 0.3); border-radius: 12px; transform: scale(1.1); transition: all 0.3s ease;`;
-
- const postRect = post.getBoundingClientRect();
- const viewportHeight = window.innerHeight;
- const scrollY = window.scrollY;
- const scrollTo = scrollY + postRect.top - viewportHeight / 2 + postRect.height / 2;
-
- window.scrollTo({
- top: scrollTo,
- behavior: 'smooth'
- });
-
- setTimeout(() => {
- let scrollHandler = function() {
- post.style.cssText = "";
- window.removeEventListener('scroll', scrollHandler);
- console.log(t("highlightSuccess"));
- };
- window.addEventListener('scroll', scrollHandler);
- }, 500);
- }
-
- function getVisiblePosts() {
- return Array.from(document.querySelectorAll("article")).map(post => ({
- element: post,
- timestamp: getPostTimestamp(post),
- authorHandler: getPostAuthorHandler(post)
- })).filter(post => post.timestamp && post.authorHandler);
- }
-
- function findPostByData(data) {
- return Array.from(document.querySelectorAll("article")).find(post => {
- const postTimestamp = getPostTimestamp(post);
- const authorHandler = getPostAuthorHandler(post);
- return postTimestamp === data.timestamp && authorHandler === data.authorHandler;
- });
- }
-
- function createButtons() {
- const container = document.createElement("div");
- container.style.cssText = `position: fixed; top: 50%; left: 3px; transform: translateY(-50%); display: flex; flex-direction: column; gap: 3px; z-index: 10000;`;
-
- let toggleButton;
-
- const buttons = [
- { icon: "📂", title: "Load saved reading position", onClick: importLastReadPost },
- { icon: "🔍", title: "Start manual search", onClick: startRefinedSearchForLastReadPost },
- {
- icon: saveToDownloadFolder ? "💾" : "🚫",
- title: "Toggle save to download folder",
- onClick: function() {
- saveToDownloadFolder = !saveToDownloadFolder;
- GM_setValue("saveToDownloadFolder", saveToDownloadFolder);
- toggleButton.style.background = saveToDownloadFolder ? "rgba(0, 255, 0, 0.9)" : "rgba(255, 0, 0, 0.9)";
- toggleButton.textContent = saveToDownloadFolder ? "💾" : "🚫";
- console.log(saveToDownloadFolder ? t("toggleSaveOn") : t("toggleSaveOff"));
- }
- }
- ];
-
- buttons.forEach(({ icon, title, onClick }) => {
- const button = document.createElement("div");
- button.style.cssText = `width: 36px; height: 36px; background: ${icon === "💾" || icon === "🚫" ? (saveToDownloadFolder ? "rgba(0, 255, 0, 0.9)" : "rgba(255, 0, 0, 0.9)") : "rgba(0, 0, 0, 0.9)"}; color: #fff; border-radius: 50%; display: flex; justify-content: center; align-items: center; cursor: pointer; font-size: 18px; box-shadow: inset 0 0 10px rgba(255, 255, 255, 0.5); transition: all 0.2s ease;`;
- button.title = title;
- button.textContent = icon;
-
- button.addEventListener("click", function() {
- button.style.boxShadow = "inset 0 0 20px rgba(255, 255, 255, 0.8)";
- button.style.transform = "scale(0.9)";
- setTimeout(() => {
- button.style.boxShadow = "inset 0 0 10px rgba(255, 255, 255, 0.5)";
- button.style.transform = "scale(1)";
- onClick();
- }, 300);
- });
-
- ["mouseenter", "mouseleave"].forEach(event =>
- button.addEventListener(event, () => button.style.transform = event === "mouseenter" ? "scale(1.1)" : "scale(1)")
- );
-
- if (icon === "💾" || icon === "🚫") {
- toggleButton = button;
- }
-
- container.appendChild(button);
- });
-
- document.body.appendChild(container);
- }
-
- function importLastReadPost() {
- const input = document.createElement("input");
- input.type = "file";
- input.accept = "application/json";
- input.style.display = "none";
-
- input.addEventListener("change", (event) => {
- const file = event.target.files[0];
- if (file) {
- const reader = new FileReader();
- reader.onload = () => {
- try {
- const importedData = JSON.parse(reader.result);
- if (importedData.timestamp && importedData.authorHandler) {
- lastReadPost = importedData;
- saveLastReadPostToFile();
- startRefinedSearchForLastReadPost();
- } else {
- throw new Error("Invalid reading position");
- }
- } catch (error) {
- console.error("❌ Error importing reading position:", error);
- }
- };
- reader.readAsText(file);
- }
- });
-
- document.body.appendChild(input);
- input.click();
- document.body.removeChild(input);
- }
-
- function observeForNewPosts() {
- const targetNode = document.querySelector('div[aria-label="Timeline: Your Home Timeline"]') || document.body;
-
- const mutationObserver = new MutationObserver(mutations => {
- for (const mutation of mutations) {
- if (mutation.type === 'childList') {
- checkForNewPosts();
- }
- }
- });
-
- mutationObserver.observe(targetNode, { childList: true, subtree: true });
- }
-
- function getNewPostsIndicator() {
- const indicator = document.querySelector('div[aria-label*="undefined"]') ||
- document.querySelector('div[aria-label*="new"]');
- console.log(`[Debug] Neuer Beitragsindikator gefunden: ${indicator ? 'Ja' : 'Nein'}`);
- return indicator;
- }
-
- function clickNewPostsIndicator(indicator, preservedScrollY) {
- if (indicator && indicator.offsetParent !== null) {
- console.log("Versuche, auf den neuen Beitrag-Indikator zu klicken.");
- indicator.click();
- console.log("Klick auf den neuen Beitrag-Indikator war erfolgreich.");
-
- const timelineNode = document.querySelector('div[aria-label="Timeline: Your Home Timeline"]') || document.body;
- let mutationTimeout;
-
- const observer = new MutationObserver(() => {
- if (window.scrollY !== preservedScrollY) {
- window.scrollTo(0, preservedScrollY);
- console.log(`[Debug] Scroll-Position korrigiert auf: ${preservedScrollY}`);
- }
-
- clearTimeout(mutationTimeout);
- mutationTimeout = setTimeout(() => {
- observer.disconnect();
- console.log(t("newPostsLoaded"));
- }, 3000);
- });
-
- observer.observe(timelineNode, { childList: true, subtree: true });
-
- setTimeout(() => {
- window.scrollTo(0, preservedScrollY);
- }, 100);
- } else {
- console.log("Kein klickbarer Indikator gefunden.");
- }
- }
-
- async function checkForNewPosts() {
- if (window.scrollY <= 10) {
- const newPostsIndicator = getNewPostsIndicator();
- if (newPostsIndicator) {
- const preservedScrollY = window.scrollY;
- console.log("🆕 Neue Beiträge in der Nähe des oberen Randes erkannt. Klicke auf Indikator...");
- clickNewPostsIndicator(newPostsIndicator, preservedScrollY);
- hasScrolledAfterLoad = false;
-
- try {
- console.log(t("contentLoadWait"));
- await waitForTimelineLoad(5000);
- console.log("Inhalt geladen, starte verfeinerte Suche...");
- startRefinedSearchForLastReadPost();
- } catch (error) {
- console.error("❌ Fehler beim Warten auf das Laden der Timeline:", error.message);
- console.log("[Debug] Starte Suche trotz Fehler.");
- startRefinedSearchForLastReadPost();
- }
- } else {
- console.log("[Debug] Kein neuer Beitragsindikator gefunden.");
- }
- } else {
- console.log("[Debug] Scroll-Position nicht oben, keine neuen Beiträge geprüft.");
- }
- }
-
- async function waitForTimelineLoad(maxWaitTime = 3000) {
- const startTime = Date.now();
- return new Promise((resolve) => {
- const checkInterval = setInterval(() => {
- const loadingSpinner = document.querySelector('div[role="progressbar"]') ||
- document.querySelector('div.css-175oi2r.r-1pi2tsx.r-1wtj0ep.r-ymttw5.r-1f1sjgu');
- const timeElapsed = Date.now() - startTime;
-
- if (!loadingSpinner) {
- console.log("[Debug] Ladeindikator verschwunden, resolve.");
- clearInterval(checkInterval);
- resolve(true);
- } else if (timeElapsed > maxWaitTime) {
- console.log("[Debug] Timeout erreicht, starte Suche trotz Ladeindikator.");
- clearInterval(checkInterval);
- resolve(true);
- }
- }, 200);
- });
- }
-
- async function initializeScript() {
- console.log(t("pageLoaded"));
- try {
- await loadLastReadPostFromFile();
- observeForNewPosts();
- observeVisiblePosts();
-
- window.addEventListener("scroll", debounce(() => {
- observeVisiblePosts();
- if (!isAutoScrolling && !isSearching) {
- if (hasScrolledAfterLoad) {
- markTopVisiblePost(true);
- } else {
- hasScrolledAfterLoad = true;
- }
- }
- }, 500));
- } catch (error) {
- console.error("❌ Fehler bei der Initialisierung des Skripts:", error);
- }
- }
-
- window.onload = async () => {
- if (!window.location.href.includes("/home")) {
- console.log(t("scriptDisabled"));
- return;
- }
- console.log(t("pageLoaded"));
- try {
- await loadNewestLastReadPost();
- await initializeScript();
- createButtons();
- } catch (error) {
- console.error("❌ Fehler beim Seitenladen:", error);
- }
- };
-
- window.addEventListener("blur", async () => {
- console.log(t("tabBlur"));
- if (lastReadPost && !downloadTriggered) {
- downloadTriggered = true;
- if (!(await isFileAlreadyDownloaded())) {
- console.log(t("downloadStart"));
- await downloadLastReadPost();
- await markDownloadAsComplete();
- } else {
- console.log(t("alreadyDownloaded"));
- }
- downloadTriggered = false;
- }
- });
-
- window.addEventListener("focus", () => {
- isTabFocused = true;
- downloadTriggered = false;
- console.log(t("tabFocused"));
- });
-
- async function isFileAlreadyDownloaded() {
- const localFiles = await GM_getValue("downloadedPosts", []);
- const fileSignature = `${lastReadPost.authorHandler}_${lastReadPost.timestamp}`;
- return localFiles.includes(fileSignature);
- }
-
- async function markDownloadAsComplete() {
- const localFiles = await GM_getValue("downloadedPosts", []);
- const fileSignature = `${lastReadPost.authorHandler}_${lastReadPost.timestamp}`;
- if (!localFiles.includes(fileSignature)) {
- localFiles.push(fileSignature);
- GM_setValue("downloadedPosts", localFiles);
- }
- }
- })();