Greasy Fork is available in English.

Facebook Videos Detector (Generates ffmpeg download command)

This script will help you to list all facebook videos that were loaded during your session

// ==UserScript==
// @name         Facebook Videos Detector (Generates ffmpeg download command)
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  This script will help you to list all facebook videos that were loaded during your session
// @author       Mahmoud Khudairi
// @match        https://www.facebook.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=facebook.com
// @grant        none
// ==/UserScript==

(function () {
  "use strict";

  const loaderFunction = function () {
    const videos = document.getElementById("videos");
    const domParser = new DOMParser();
    const contents = JSON.parse(
      document.getElementById("contents").textContent
    );
    const dashURL = new URL(
      "https://www.facebook.com/video/playback/dash_mpd_debug.mpd"
    );
    function getContent(videoId, userInserted = false) {
      const videoEl = document.createElement("div");
      videoEl.className = "video";
      videos.append(videoEl);
      (async function () {
        dashURL.searchParams.set("v", videoId);
        const mpdData = await fetch(dashURL.toString()).then((res) =>
          res.text()
        );
        const mpdDom = domParser.parseFromString(mpdData, "text/xml");
        const sources = Array.from(
          mpdDom.documentElement
            .querySelector('AdaptationSet[contentType="video"], AdaptationSet:has(Representation[mimeType^="video"])')
            ?.querySelectorAll("Representation") || []
        ).map((r) => ({
          id: r.getAttribute("id"),
          width: +r.getAttribute("width"),
          height: +r.getAttribute("height"),
          bitrate: +r.getAttribute("bandwidth"),
          frameRate: r.parentElement
            .getAttribute("frameRate")
            .split("/")
            .reduce((a, c) => a / c),
          url: r.textContent,
          initRange: r.querySelector("Initialization").getAttribute("range"),
          indexRange: r.querySelector("SegmentBase").getAttribute("indexRange"),
          firstSegmentRange: r
            .querySelector("SegmentBase")
            .getAttribute("FBFirstSegmentRange"),
          mimeType: r.getAttribute("mimeType"),
        }));
        const minSource = sources[0];
        if (!minSource) {
          videoEl.remove();

          if (userInserted) alert(`Your requested video: ${videoId} was not found`);

          return;
        }
        const maxSource = sources.at(-1);
        const segmentUrl = new URL(minSource.url);
        segmentUrl.searchParams.set("bytestart", "0");
        segmentUrl.searchParams.set(
          "byteend",
          minSource.firstSegmentRange.split("-")[1]
        );
        const video = document.createElement("video");
        video.src = segmentUrl.toString();
        await new Promise((rs) =>
          video.addEventListener("canplay", rs, { once: 1 })
        );
        const canvas = document.createElement("canvas");
        const ctx = canvas.getContext("2d");
        canvas.width = minSource.width;
        canvas.height = minSource.height;
        canvas.style.aspectRatio = `${minSource.width}/${minSource.height}`;
        const bufStart = video.buffered.start(0);
        const bufEnd = video.buffered.end(0);
        const bufDuration = bufEnd - bufStart;
        const randomPosition = bufStart + Math.random() * bufDuration;
        video.currentTime = randomPosition;
        await new Promise((rs) =>
          video.addEventListener("seeked", rs, { once: 1 })
        );
        ctx.drawImage(
          video,
          0,
          0,
          canvas.width,
          canvas.height,
          0,
          0,
          canvas.width,
          canvas.height
        );
        videoEl.append(canvas);
        const maxAudio = Array.from(
          mpdDom.documentElement
            .querySelector('AdaptationSet[contentType="audio"], AdaptationSet:has(Representation[mimeType^="audio"])')
            ?.querySelectorAll("Representation") || []
        )
          .map((r) => ({
            url: r.textContent,
            bitrate: +r.getAttribute("bandwidth"),
          }))
          .at(-1);
        const downloadCommand = ["ffmpeg"];
        downloadCommand.push("-i", `"${maxSource.url}"`);
        if (maxAudio) downloadCommand.push("-i", `"${maxAudio.url}"`);
        downloadCommand.push("-c:v", "h264", "-b:v", maxSource.bitrate);
        if (maxAudio)
          downloadCommand.push("-c:a", "aac", "-b:a", maxAudio.bitrate);
        downloadCommand.push(`${videoId}.mp4`);
        const downloadCommandText = downloadCommand.join(" ");
        videoEl.addEventListener("click", function () {
          navigator.clipboard
            .writeText(downloadCommandText)
            .then(() => alert("Commad copied!"))
            .catch(() => {
              console.log(downloadCommandText);
              alert(
                "Could not copy command, but it was printed in browser's console"
              );
            });
        });
      })();
    }
    for (const content of contents) getContent(content)
    const addbtn = document.getElementById('addbtn');
    addbtn.addEventListener("click", () => {
      const id = prompt('FB Video ID:');
      if (id) getContent(id, true);
    });
  };

  const xml = new window.XMLSerializer();
  const dom = window.document.implementation.createDocument(
    "http://www.w3.org/1999/xhtml",
    "html",
    null
  );

  const head = dom.createElement("head");
  const body = dom.createElement("body");

  const style = dom.createElement("style");
  style.innerHTML = `* {box-sizing: border-box;}
  body {
    font-family: Segoe UI;
    margin: 0 20px;
  }
  #videos {
    display: grid;
    grid-template-columns: repeat(auto-fit, 320px);
    gap: 10px;
    margin-bottom: 20px;
  }
  #videos .video {
    background-color: black;
    height: 180px;
    cursor: pointer
  }
  #videos .video canvas {
    width: 100%;
    height: 100%;
    object-fit: contain;
  }`;
  head.append(style);

  const contents = document.createElement("script");
  contents.id = "contents";
  contents.type = "application/json";
  contents.textContent = "[]";
  head.append(contents);

  const script = document.createElement("script");
  script.src = URL.createObjectURL(
    new Blob(
      [`window.addEventListener("load", ${loaderFunction.toString()});`],
      { type: "application/javascript" }
    )
  );
  head.append(script);

  body.innerHTML =
    '<h1>Facebook Videos Detector</h1><p>Videos found on your facebook session:</p><div id="videos"></div><button id="addbtn">Add new video</button>';
  dom.firstChild.append(head, body);
  const cache = new Set();
  window.addEventListener("keyup", function (e) {
    viewing: if ((e.altKey || (e.ctrlKey && e.shiftKey)) && e.code === "KeyV") {
      e.preventDefault();
      // if (!cache.size) {
      //   this.alert("There is no videos found");
      //   break viewing;
      // }
      contents.textContent = JSON.stringify(Array.from(cache));
      dom.title = `Facebook Videos Detector - (${cache.size} video${
        cache.size > 1 ? "s" : ""
      } ${cache.size > 1 ? "were" : "was"} found)`;
      this.open(
        URL.createObjectURL(
          new Blob([xml.serializeToString(dom)], { type: dom.contentType })
        ),
        "_blank"
      );
    }
  });
  window.fetch = new Proxy(window.fetch, {
    async apply(target, arg, args) {
      const url = new URL(args[0]);
      caching: if (
        /\.mp4$/.test(url.pathname.split("/").at(-1)) &&
        url.searchParams.has("efg")
      ) {
        const efg = JSON.parse(window.atob(url.searchParams.get("efg")));
        const id = efg.video_id;
        if (cache.has(id)) break caching;
        cache.add(id);
      }
      return target.apply(arg, args);
    },
  });
})();