怎么样才能快速到LEVEL3啊。

油猴脚本哈

// ==UserScript==
// @name         LINUX DO Reading Assistant
// @namespace    https://linux.do/
// @version      0.3.0
// @description  Slow reading helper for unread posts on LINUX DO. Can keep scrolling while the browser is in background.
// @match        https://linux.do/t/topic/*
// @match        http://linux.do/t/topic/*
// @grant        none
// ==/UserScript==

(function () {
  "use strict";

  const storageKey = "linuxdo-reading-assistant-settings";
  const defaultSettings = {
    settingsVersion: 3,
    enabled: false,
    pauseOnHidden: false,
    keepAlive: true,
    targetOffsetRatio: 0.35,
    dwellMs: 2800,
    nudgePx: 180,
  };

  let settings = loadSettings();
  let timer = 0;
  let scheduleId = 0;
  let dueAt = 0;
  let ticking = false;
  let workerTimer = createWorkerTimer();
  let heartbeatTimer = 0;
  let audioContext = null;
  let audioOscillator = null;
  let audioGain = null;
  let wakeLock = null;
  let wakeLockRequesting = false;
  let target = null;
  let targetReadyAt = 0;
  const consumedTargets = new WeakSet();
  const consumedTargetKeys = new Set();

  const panel = document.createElement("div");
  panel.id = "linuxdo-reading-assistant";
  panel.innerHTML = `
    <style>
      #linuxdo-reading-assistant {
        position: fixed;
        right: 18px;
        bottom: 18px;
        z-index: 99999;
        width: 228px;
        box-sizing: border-box;
        padding: 12px;
        border: 1px solid rgba(0, 0, 0, 0.12);
        border-radius: 8px;
        background: rgba(255, 255, 255, 0.94);
        color: #222;
        box-shadow: 0 8px 26px rgba(0, 0, 0, 0.14);
        font: 13px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
      }
      #linuxdo-reading-assistant * {
        box-sizing: border-box;
      }
      #linuxdo-reading-assistant .ra-row {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 8px;
      }
      #linuxdo-reading-assistant .ra-title {
        font-weight: 700;
      }
      #linuxdo-reading-assistant button {
        min-width: 64px;
        border: 0;
        border-radius: 6px;
        padding: 6px 10px;
        color: #fff;
        cursor: pointer;
        background: #2f80ed;
        font-weight: 700;
      }
      #linuxdo-reading-assistant button.is-off {
        background: #777;
      }
      #linuxdo-reading-assistant label {
        display: grid;
        gap: 4px;
        margin-top: 10px;
      }
      #linuxdo-reading-assistant input[type="range"] {
        width: 100%;
      }
      #linuxdo-reading-assistant .ra-checkbox {
        display: flex;
        grid-template-columns: none;
        align-items: center;
        gap: 6px;
      }
      #linuxdo-reading-assistant .ra-checkbox input {
        margin: 0;
      }
      #linuxdo-reading-assistant .ra-status {
        margin-top: 8px;
        min-height: 18px;
        color: #666;
        word-break: break-word;
      }
    </style>
    <div class="ra-row">
      <div class="ra-title">阅读辅助</div>
      <button type="button" class="ra-toggle"></button>
    </div>
    <label>
      <span>停留时间 <b class="ra-dwell-value"></b>s</span>
      <input class="ra-dwell" type="range" min="2" max="12" step="1">
    </label>
    <label>
      <span>滑动步长 <b class="ra-step-value"></b>px</span>
      <input class="ra-step" type="range" min="80" max="420" step="20">
    </label>
    <label class="ra-checkbox">
      <input class="ra-background" type="checkbox">
      <span>切到其它页面也继续</span>
    </label>
    <label class="ra-checkbox">
      <input class="ra-keepalive" type="checkbox">
      <span>后台保活(切应用更稳)</span>
    </label>
    <div class="ra-status"></div>
  `;

  document.documentElement.appendChild(panel);

  const toggleButton = panel.querySelector(".ra-toggle");
  const dwellInput = panel.querySelector(".ra-dwell");
  const dwellValue = panel.querySelector(".ra-dwell-value");
  const stepInput = panel.querySelector(".ra-step");
  const stepValue = panel.querySelector(".ra-step-value");
  const backgroundInput = panel.querySelector(".ra-background");
  const keepAliveInput = panel.querySelector(".ra-keepalive");
  const statusNode = panel.querySelector(".ra-status");

  dwellInput.value = String(Math.round(settings.dwellMs / 1000));
  stepInput.value = String(settings.nudgePx);
  backgroundInput.checked = !settings.pauseOnHidden;
  keepAliveInput.checked = settings.keepAlive;

  toggleButton.addEventListener("click", () => {
    settings.enabled = !settings.enabled;
    if (!settings.enabled) {
      resetTarget();
    }
    saveSettings();
    syncKeepAlive();
    render();
    schedule(150);
  });

  dwellInput.addEventListener("input", () => {
    settings.dwellMs = Number(dwellInput.value) * 1000;
    saveSettings();
    render();
  });

  stepInput.addEventListener("input", () => {
    settings.nudgePx = Number(stepInput.value);
    saveSettings();
    render();
  });

  backgroundInput.addEventListener("change", () => {
    settings.pauseOnHidden = !backgroundInput.checked;
    saveSettings();
    syncKeepAlive();
    render();
    schedule(150);
  });

  keepAliveInput.addEventListener("change", () => {
    settings.keepAlive = keepAliveInput.checked;
    saveSettings();
    syncKeepAlive();
    render();
    schedule(150);
  });

  document.addEventListener("visibilitychange", () => {
    syncKeepAlive();
    schedule(250);
    render();
  });

  window.addEventListener("focus", () => {
    syncKeepAlive();
    schedule(100);
  });

  window.addEventListener("pageshow", () => {
    syncKeepAlive();
    schedule(100);
  });

  window.addEventListener("beforeunload", () => {
    stopHeartbeat();
    stopKeepAlive();
    if (workerTimer) {
      workerTimer.terminate();
      workerTimer = null;
    }
  });

  render();
  syncKeepAlive();
  schedule(500);

  function loadSettings() {
    try {
      const raw = JSON.parse(localStorage.getItem(storageKey) || "{}");
      const migrated = {
        ...defaultSettings,
        ...raw,
      };

      // 0.1.0 没有“后台继续”开关,并且默认写入 pauseOnHidden=true。
      // 升级后默认改成后台继续,避免旧 localStorage 继续把脚本卡在后台暂停。
      if (!raw.settingsVersion) {
        migrated.pauseOnHidden = false;
        migrated.keepAlive = true;
        migrated.settingsVersion = defaultSettings.settingsVersion;
        localStorage.setItem(storageKey, JSON.stringify(migrated));
      } else if (raw.settingsVersion < defaultSettings.settingsVersion) {
        migrated.settingsVersion = defaultSettings.settingsVersion;
        if (typeof raw.keepAlive !== "boolean") {
          migrated.keepAlive = true;
        }
        localStorage.setItem(storageKey, JSON.stringify(migrated));
      }

      return migrated;
    } catch {
      return {
        ...defaultSettings,
      };
    }
  }

  function saveSettings() {
    localStorage.setItem(storageKey, JSON.stringify(settings));
  }

  function render(message) {
    toggleButton.textContent = settings.enabled ? "开启" : "关闭";
    toggleButton.classList.toggle("is-off", !settings.enabled);
    dwellValue.textContent = String(Math.round(settings.dwellMs / 1000));
    stepValue.textContent = String(settings.nudgePx);
    backgroundInput.checked = !settings.pauseOnHidden;
    keepAliveInput.checked = settings.keepAlive;

    if (message) {
      statusNode.textContent = message;
      return;
    }
    if (!settings.enabled) {
      statusNode.textContent = "已暂停";
      return;
    }
    if (settings.pauseOnHidden && document.hidden) {
      statusNode.textContent = "后台暂停中";
      return;
    }
    if (document.hidden) {
      statusNode.textContent = settings.keepAlive ? "后台保活继续中" : "后台继续中";
      return;
    }
    statusNode.textContent = "运行中";
  }

  function schedule(delay) {
    const id = ++scheduleId;
    dueAt = Date.now() + Math.max(0, Number(delay) || 0);
    window.clearTimeout(timer);
    timer = window.setTimeout(() => runScheduledTick(id), delay);
    startHeartbeat();
    if (workerTimer) {
      workerTimer.postMessage({ type: "schedule", id, delay, dueAt });
    }
  }

  function runScheduledTick(id) {
    if (id !== scheduleId) {
      return;
    }
    if (ticking) {
      return;
    }
    window.clearTimeout(timer);
    timer = 0;
    dueAt = 0;
    ticking = true;
    try {
      tick();
    } finally {
      ticking = false;
    }
  }

  function runDueTick(id) {
    if (id !== scheduleId || !dueAt || Date.now() < dueAt) {
      return;
    }
    runScheduledTick(id);
  }

  function tick() {
    if (!settings.enabled) {
      render("已暂停");
      return;
    }

    if (settings.pauseOnHidden && document.hidden) {
      render("后台暂停中");
      schedule(1200);
      return;
    }

    if (
      !target ||
      !target.isConnected ||
      isRead(target) ||
      isConsumed(target)
    ) {
      target = findNextUnread();
      targetReadyAt = 0;
      if (!target) {
        render("没有找到未读标记");
        schedule(1800);
        return;
      }
    }

    const rect = target.getBoundingClientRect();
    const desiredTop = window.innerHeight * settings.targetOffsetRatio;

    if (rect.top < 90 || rect.top > window.innerHeight * 0.72) {
      window.scrollTo({
        top: Math.max(0, window.scrollY + rect.top - desiredTop),
        behavior: scrollBehavior(),
      });
      targetReadyAt = 0;
      render(document.hidden ? "后台定位未读帖" : "定位未读帖");
      schedule(document.hidden ? 450 : 900);
      return;
    }

    if (!targetReadyAt) {
      targetReadyAt = Date.now();
    }

    const elapsed = Date.now() - targetReadyAt;
    if (!isRead(target) && elapsed < settings.dwellMs) {
      const remaining = settings.dwellMs - elapsed;
      render(
        document.hidden
          ? `后台等待 ${Math.ceil(remaining / 1000)}s`
          : "等待当前帖变为已读"
      );
      schedule(Math.max(250, Math.min(remaining, 1200)));
      return;
    }

    if (!isRead(target)) {
      rememberConsumed(target);
    }

    window.scrollBy({
      top: settings.nudgePx,
      left: 0,
      behavior: scrollBehavior(),
    });
    render(document.hidden ? "后台滑到下一帖" : "滑到下一帖");
    resetTarget();
    schedule(document.hidden ? 500 : 900);
  }

  function findNextUnread() {
    const candidates = Array.from(
      document.querySelectorAll('.read-state[title*="未读"], .read-state:not(.read)')
    ).filter((node) => !isRead(node) && !isConsumed(node));

    return candidates.find((node) => node.getBoundingClientRect().bottom > 80) || null;
  }

  function isRead(node) {
    return node.classList.contains("read");
  }

  function resetTarget() {
    target = null;
    targetReadyAt = 0;
  }

  function scrollBehavior() {
    // 后台页的 smooth 动画经常被浏览器节流或暂停;后台直接改 scrollY 更稳。
    return document.hidden ? "auto" : "smooth";
  }

  function startHeartbeat() {
    if (heartbeatTimer) {
      return;
    }
    heartbeatTimer = window.setInterval(() => runDueTick(scheduleId), 1000);
  }

  function stopHeartbeat() {
    if (!heartbeatTimer) {
      return;
    }
    window.clearInterval(heartbeatTimer);
    heartbeatTimer = 0;
  }

  function rememberConsumed(node) {
    consumedTargets.add(node);
    const key = getTargetKey(node);
    if (key) {
      consumedTargetKeys.add(key);
      if (consumedTargetKeys.size > 1000) {
        consumedTargetKeys.clear();
      }
    }
  }

  function isConsumed(node) {
    if (consumedTargets.has(node)) {
      return true;
    }
    const key = getTargetKey(node);
    return Boolean(key && consumedTargetKeys.has(key));
  }

  function getTargetKey(node) {
    const post = node.closest(
      "[data-post-id], [data-post-number], article[id], .topic-post[id], [id^='post_']"
    );
    if (!post) {
      return "";
    }
    return (
      post.getAttribute("data-post-id") ||
      post.getAttribute("data-post-number") ||
      post.id ||
      ""
    );
  }

  function createWorkerTimer() {
    try {
      const blob = new Blob(
        [
          `
            let timer = 0;
            let heartbeat = 0;
            let activeId = 0;
            let dueAt = 0;

            function emitDue() {
              if (activeId && dueAt && Date.now() >= dueAt) {
                self.postMessage({ id: activeId, due: true });
              }
            }

            self.onmessage = (event) => {
              const data = event.data || {};
              if (data.type === "stop") {
                activeId = 0;
                dueAt = 0;
                clearTimeout(timer);
                clearInterval(heartbeat);
                heartbeat = 0;
                return;
              }
              activeId = data.id;
              dueAt = Number(data.dueAt) || Date.now() + Math.max(0, Number(data.delay) || 0);
              clearTimeout(timer);
              timer = setTimeout(() => {
                self.postMessage({ id: data.id });
              }, Math.max(0, Number(data.delay) || 0));
              if (!heartbeat) {
                heartbeat = setInterval(emitDue, 1000);
              }
            };
          `,
        ],
        { type: "text/javascript" }
      );
      const url = URL.createObjectURL(blob);
      const worker = new Worker(url);
      URL.revokeObjectURL(url);
      worker.onmessage = (event) => runScheduledTick(event.data && event.data.id);
      worker.onerror = () => {
        worker.terminate();
        workerTimer = null;
      };
      return worker;
    } catch {
      return null;
    }
  }

  function syncKeepAlive() {
    if (settings.enabled && !settings.pauseOnHidden && settings.keepAlive) {
      startKeepAlive();
      requestWakeLock();
    } else {
      stopKeepAlive();
      releaseWakeLock();
    }
  }

  function startKeepAlive() {
    startHeartbeat();

    const AudioCtor = window.AudioContext || window.webkitAudioContext;
    if (!AudioCtor) {
      return false;
    }

    try {
      if (!audioContext) {
        audioContext = new AudioCtor({ latencyHint: "playback" });
      }

      if (!audioOscillator) {
        audioGain = audioContext.createGain();
        // 极低音量的连续音频可以减少 Chrome/Edge 在切到其它应用后对计时器和 App Nap 的节流。
        // 不是 0,是因为完全静音通常不会被浏览器当成“正在播放音频”。
        audioGain.gain.value = 0.0008;

        audioOscillator = audioContext.createOscillator();
        audioOscillator.type = "sine";
        audioOscillator.frequency.value = 220;
        audioOscillator.connect(audioGain);
        audioGain.connect(audioContext.destination);
        audioOscillator.start();
      }

      if (audioContext.state === "suspended") {
        audioContext.resume().catch(() => {});
      }

      if (navigator.mediaSession) {
        navigator.mediaSession.playbackState = "playing";
      }

      return true;
    } catch {
      return false;
    }
  }

  function stopKeepAlive() {
    if (navigator.mediaSession) {
      navigator.mediaSession.playbackState = "none";
    }

    if (audioOscillator) {
      try {
        audioOscillator.stop();
      } catch {}
      try {
        audioOscillator.disconnect();
      } catch {}
      audioOscillator = null;
    }

    if (audioGain) {
      try {
        audioGain.disconnect();
      } catch {}
      audioGain = null;
    }

    if (audioContext && audioContext.state !== "closed") {
      audioContext.suspend().catch(() => {});
    }
  }

  async function requestWakeLock() {
    if (
      wakeLock ||
      wakeLockRequesting ||
      !("wakeLock" in navigator) ||
      document.visibilityState !== "visible"
    ) {
      return;
    }

    wakeLockRequesting = true;
    try {
      wakeLock = await navigator.wakeLock.request("screen");
      wakeLock.addEventListener("release", () => {
        wakeLock = null;
      });
    } catch {
      wakeLock = null;
    } finally {
      wakeLockRequesting = false;
    }
  }

  async function releaseWakeLock() {
    if (!wakeLock) {
      return;
    }

    const lock = wakeLock;
    wakeLock = null;
    try {
      await lock.release();
    } catch {}
  }
})();

看帖子,回帖子,点赞,靠时间,就酱紫。

50 天慢慢来,我今天点赞的能力上限了,说真的不要被 2w 浏览量吓到,你一天 500 天 40 天就可以搞定,卡你的永远是时间,除非你之前是 3 级。

不消失的话就切换节点,刷新网页就好了

lv3居然还会掉下来的吗? :distorted_face: :distorted_face:

会随时间掉的,100天内的活跃数据来看的

那看来不活跃还会被清退 :tieba_087: :tieba_087:不过我担心这个还为时尚早

感觉天天正常看的话 其实也快的吧

主要是赞比较难骗 :rofl:,其他的还好,有时间逛逛就行

其实生了三级也没啥,大部分都在12级,三级帖子不太多

三级有什么我不知道的福利吗,为什么大家趋之若鹜~

升到三级最快也要五十天啊,你得每天坚持打卡

https://connect.linux.do/ 这里

不懂就问,Level3有什么好处或者用途吗???

为啥我的是话题完全不会动呢。。?


搜了一下还真是bug,检查新开标签导致的 :distorted_face:

不知道,感觉好慢啊,估计要摸摸鱼

所以应该怎么办啊佬,你这早该升级了,都超出20天了

装话题的对应脚本就好了,我刷两天就三级了