X.com Timeline Auto-Load with Uninterrupted Reading

Automatically loads new posts on X.com while keeping the reading position intact. Sets a virtual marker at the last visible handler (e.g., @username) before loading new posts and restores the view to this marker.

当前为 2024-11-21 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name X.com Timeline Auto-Load with Uninterrupted Reading
  3. // @name:de X.com Timeline Auto-Load mit unterbrechungsfreiem Lesen
  4. // @name:fr X.com Timeline Auto-Load avec lecture ininterrompue
  5. // @name:es Carga automática de la línea de tiempo de X.com con lectura sin interrupciones
  6. // @name:it Caricamento automatico della timeline di X.com con lettura ininterrotta
  7. // @name:zh X.com 时间线自动加载,无缝阅读
  8. // @name:ja X.com タイムライン自動読み込みと中断のない読書
  9. // @description Automatically loads new posts on X.com while keeping the reading position intact. Sets a virtual marker at the last visible handler (e.g., @username) before loading new posts and restores the view to this marker.
  10. // @description:de Lädt automatisch neue Beiträge auf X.com, ohne die Leseposition zu verlieren. Setzt eine virtuelle Markierung am letzten sichtbaren Handler (z. B. @Benutzername) vor dem Laden neuer Beiträge und stellt die Ansicht zu dieser Markierung wieder her.
  11. // @description:fr Charge automatiquement les nouveaux messages sur X.com tout en conservant la position de lecture. Place un marqueur virtuel au dernier handle visible (par exemple, @nomutilisateur) avant de charger les nouveaux messages et restaure la vue à ce marqueur.
  12. // @description:es Carga automáticamente nuevos posts en X.com mientras mantiene la posición de lectura intacta. Coloca un marcador virtual en el último manejador visible (por ejemplo, @nombredeusuario) antes de cargar nuevos posts y restaura la vista a ese marcador.
  13. // @description:it Carica automaticamente nuovi post su X.com mantenendo intatta la posizione di lettura. Imposta un segnalibro virtuale sull'ultimo handle visibile (es. @nomeutente) prima di caricare nuovi post e ripristina la vista su quel segnalibro.
  14. // @description:zh 在X.com上自动加载新帖子,同时保持阅读位置不变。在加载新帖子之前,在最后一个可见的处理器(例如@用户名)处设置一个虚拟标记,并将视图恢复到该标记。
  15. // @description:ja X.comで新しい投稿を自動的に読み込み、読書位置をそのまま保持します。新しい投稿を読み込む前に、最後に見えるハンドル(例:@ユーザー名)に仮想マーカーを設定し、このマーカーにビューを復元します。
  16. // @namespace http://tampermonkey.net/
  17. // @version 2024.12.21.1
  18. // @icon https://cdn-icons-png.flaticon.com/128/14417/14417460.png
  19. // @author Copiis
  20. // @match https://x.com/*
  21. // @grant none
  22. // @license MIT
  23. // ==/UserScript==
  24.  
  25. (function () {
  26. let isAutomationActive = false;
  27. let isAutoScrolling = false; // Status für automatisches Scrollen
  28. let savedTopPostInfo = null; // Speichert Name, Handler und erstes Wort des Beitrags
  29.  
  30. // Starte die kontinuierliche Überprüfung, sobald die Seite geladen wird
  31. document.addEventListener('DOMContentLoaded', () => {
  32. console.log("[DEBUG] Seite geladen. Starte Überprüfung für sichtbaren Handler...");
  33. checkForVisibleHandler();
  34. });
  35.  
  36. // Funktion, um kontinuierlich nach einem sichtbaren Handler zu suchen
  37. function checkForVisibleHandler() {
  38. const checkInterval = setInterval(() => {
  39. const topPost = getTopVisiblePost();
  40. if (topPost) {
  41. savedTopPostInfo = getPostInfo(topPost);
  42. console.log("[DEBUG] Sichtbarer Beitrag gefunden und gespeichert:", savedTopPostInfo);
  43. clearInterval(checkInterval); // Stoppe die Überprüfung, sobald ein Beitrag gefunden wurde
  44. }
  45. }, 100); // Überprüfe alle 100ms
  46. }
  47.  
  48. const observer = new MutationObserver(() => {
  49. if (isAtTopOfPage() && !isAutomationActive) {
  50. console.log("[DEBUG] Scrollen zum oberen Ende erkannt. Automatik wird aktiviert.");
  51. extractPostInfoFromTopPost();
  52. isAutomationActive = true;
  53. }
  54.  
  55. if (isAutomationActive) {
  56. const newPostsButton = getNewPostsButton();
  57. if (newPostsButton) {
  58. console.log("[DEBUG] 'Neue Posts anzeigen'-Button gefunden, wird geklickt.");
  59. newPostsButton.click();
  60. waitForNewPostsToLoad();
  61. }
  62. }
  63. });
  64.  
  65. observer.observe(document.body, { childList: true, subtree: true });
  66.  
  67. window.addEventListener('scroll', () => {
  68. if (isAutoScrolling) {
  69. console.log("[DEBUG] Automatisches Scrollen erkannt. Scroll-Event ignoriert.");
  70. return;
  71. }
  72.  
  73. if (window.scrollY === 0 && !isAutomationActive) {
  74. console.log("[DEBUG] Scrollen zum oberen Ende erkannt. Automatik wird aktiviert.");
  75. extractPostInfoFromTopPost();
  76. isAutomationActive = true;
  77. } else if (window.scrollY > 0 && isAutomationActive) {
  78. console.log("[DEBUG] Automatik deaktiviert, da nicht oben.");
  79. isAutomationActive = false;
  80. }
  81. });
  82.  
  83. function isAtTopOfPage() {
  84. return window.scrollY === 0;
  85. }
  86.  
  87. function getNewPostsButton() {
  88. return Array.from(document.querySelectorAll('button, span')).find((button) =>
  89. /neue Posts anzeigen|Post anzeigen/i.test(button.textContent.trim())
  90. );
  91. }
  92.  
  93. function extractPostInfoFromTopPost() {
  94. const topPost = getTopVisiblePost();
  95. if (topPost) {
  96. savedTopPostInfo = getPostInfo(topPost);
  97. console.log("[DEBUG] Oberster Beitrag gespeichert:", savedTopPostInfo);
  98. } else {
  99. console.log("[DEBUG] Kein oberster Beitrag gefunden.");
  100. }
  101. }
  102.  
  103. function getPostInfo(post) {
  104. const nameElement = post.querySelector('a[role="link"] div[dir="ltr"] span');
  105. const name = nameElement?.textContent?.trim() || "Unbekannt";
  106.  
  107. const handlerElement = post.querySelector('a[role="link"]');
  108. const handler = handlerElement?.getAttribute("href") || "Unbekannt";
  109.  
  110. const postContentElement = post.querySelector('div[data-testid="tweetText"]'); // Text des Beitrags
  111. const postContent = postContentElement?.textContent?.trim() || "";
  112. const firstWord = postContent.split(/\s+/)[0]; // Erstes Wort des tatsächlichen Beitrags
  113.  
  114. return { name, handler, firstWord };
  115. }
  116.  
  117. function getTopVisiblePost() {
  118. const posts = Array.from(document.querySelectorAll('article'));
  119. console.log(`[DEBUG] Anzahl gefundener Beiträge: ${posts.length}`);
  120. return posts.length > 0 ? posts[0] : null;
  121. }
  122.  
  123. function waitForNewPostsToLoad() {
  124. console.log("[DEBUG] Warte auf das Laden neuer Beiträge...");
  125. const checkInterval = setInterval(() => {
  126. const matchedPost = findPostByInfo(savedTopPostInfo);
  127. if (matchedPost) {
  128. clearInterval(checkInterval);
  129. console.log("[DEBUG] Gespeicherter Beitrag nach Laden gefunden. Scrollen...");
  130. scrollToPost(matchedPost, 'end'); // Scrollt so, dass der gespeicherte Beitrag am unteren Rand sichtbar ist
  131. }
  132. }, 100);
  133. }
  134.  
  135. function findPostByInfo(postInfo) {
  136. if (!postInfo) return null;
  137. const posts = Array.from(document.querySelectorAll('article'));
  138. return posts.find((post) => {
  139. const nameElement = post.querySelector('a[role="link"] div[dir="ltr"] span');
  140. const name = nameElement?.textContent?.trim();
  141.  
  142. const handlerElement = post.querySelector('a[role="link"]');
  143. const handler = handlerElement?.getAttribute("href");
  144.  
  145. const postContentElement = post.querySelector('div[data-testid="tweetText"]'); // Text des Beitrags
  146. const postContent = postContentElement?.textContent?.trim() || "";
  147. const firstWord = postContent.split(/\s+/)[0]; // Erstes Wort des tatsächlichen Beitrags
  148.  
  149. return (
  150. name === postInfo.name &&
  151. handler === postInfo.handler &&
  152. firstWord === postInfo.firstWord // Verhindert Duplikate
  153. );
  154. });
  155. }
  156.  
  157. function scrollToPost(post, position = 'end') {
  158. console.log(`[DEBUG] Scrollen zum gespeicherten Beitrag (Position: ${position})...`);
  159. isAutoScrolling = true; // Setzt den Status, dass ein automatisches Scrollen läuft
  160. post.scrollIntoView({ behavior: 'smooth', block: position });
  161. setTimeout(() => {
  162. isAutoScrolling = false; // Scrollen abgeschlossen
  163. isAutomationActive = false;
  164. console.log("[DEBUG] Automatisches Scrollen beendet. Automatik deaktiviert.");
  165. }, 1000); // Warte, bis das Scrollen abgeschlossen ist
  166. }
  167. })();