Untitled

 avatar
javascript
8 hours ago
4.4 KB
2
Indexable
(function () {
  const OBS_URL = "ws://127.0.0.1:4455";
  const OBS_PASSWORD = "";
  const TRIGGER = "Roundabout";
  const TARGET_SCENE = "Jantar";
  const MAX_START_PROGRESS = 15000;

  const DEBUG = true;
  const DEBUG_LINES = 12;

  let socket;
  let identified = false;
  let requestSeq = 0;
  let reconnectDelay = 1000;
  let reconnecting = false;
  let firedTrack = "";

  const lines = [];
  let panel = null;

  function debug(message) {
    if (!DEBUG) return;

    lines.push(new Date().toLocaleTimeString() + "  " + message);
    if (lines.length > DEBUG_LINES) lines.shift();

    if (!document.body) return;

    if (!panel) {
      panel = document.createElement("div");
      panel.style.cssText =
        "position:fixed;top:8px;left:8px;z-index:99999;max-width:560px;" +
        "padding:8px 10px;border-radius:6px;background:rgba(0,0,0,.75);" +
        "color:#4ade80;font:12px/1.5 monospace;white-space:pre;pointer-events:none";
      document.body.appendChild(panel);
    }

    panel.textContent = lines.join("\n");
  }

  function sha256(text) {
    return crypto.subtle
      .digest("SHA-256", new TextEncoder().encode(text))
      .then(digest => btoa(String.fromCharCode(...new Uint8Array(digest))));
  }

  function buildAuth(salt, challenge) {
    return sha256(OBS_PASSWORD + salt).then(secret => sha256(secret + challenge));
  }

  function send(data) {
    socket.send(JSON.stringify(data));
  }

  function setScene(sceneName) {
    send({
      op: 6,
      d: {
        requestType: "SetCurrentProgramScene",
        requestId: `req-${++requestSeq}`,
        requestData: { sceneName },
      },
    });
  }

  function connect() {
    reconnecting = false;
    socket = new WebSocket(OBS_URL);
    debug("conectando em " + OBS_URL);

    socket.onmessage = async ({ data }) => {
      const message = JSON.parse(data);

      if (message.op === 0) {
        const auth = message.d.authentication;

        send({
          op: 1,
          d: {
            rpcVersion: 1,
            eventSubscriptions: 0,
            authentication: auth && (await buildAuth(auth.salt, auth.challenge)),
          },
        });
      }

      if (message.op === 2) {
        identified = true;
        reconnectDelay = 1000;
        debug("identificado no OBS");
      }

      if (message.op === 7 && !message.d.requestStatus.result) {
        debug("erro: " + message.d.requestType + " — " + message.d.requestStatus.code);
      }
    };

    socket.onclose = () => {
      if (reconnecting) return;
      reconnecting = true;
      debug("desconectado — retry em " + reconnectDelay + "ms");
      setTimeout(connect, reconnectDelay);
      reconnectDelay = Math.min(reconnectDelay * 2, 30000);
    };
  }

  connect();

  window.spotify.onUpdate(state => {
    const track = state.trackId || state.trackName;

    if (!track || !state.isPlaying || track === firedTrack) return;
    if (!state.trackName?.includes(TRIGGER)) return;

    debug(state.trackName + " em " + Math.round(state.progressMs / 1000) + "s");

    if (state.progressMs > MAX_START_PROGRESS) {
      debug("já em andamento — não troca");
      return;
    }

    if (!identified) {
      debug("gatilho perdido — OBS offline");
      return;
    }

    firedTrack = track;
    debug("trocando pra cena " + TARGET_SCENE);
    setScene(TARGET_SCENE);
  });
})();
Editor is loading...
Leave a Comment