Greasy Fork is available in English.
Download Twitter videos via Twittervid website
// ==UserScript==// @name Twitter Video Downloader// @version 1.0.2// @description Download Twitter videos via Twittervid website// @author w4t3r1ily// @namespace https://github.com/HayaoGai// @icon https://www.google.com/s2/favicons?sz=64&domain=twittervid.com// @include https://twitter.com/*// @match https://twitter.com/*// @include https://x.com/*// @match https://x.com/*// @grant none// ==/UserScript==(function () {'use strict';// icons made by https://www.flaticon.com/authors/freepikconst svg =`<svg viewBox="0 0 512 512"><path d="M472,313v139c0,11.028-8.972,20-20,20H60c-11.028,0-20-8.972-20-20V313H0v139c0,33.084,26.916,60,60,60h392 c33.084,0,60-26.916,60-60V313H472z"></path></g></g><g><g><polygon points="352,235.716 276,311.716 276,0 236,0 236,311.716 160,235.716 131.716,264 256,388.284 380.284,264"></polygon></svg>`;const resource = "https://twittervid.com/";let currentUrl = document.location.href;let updating = false;init(10);locationChange();window.addEventListener("scroll", update);function init(times) {for (let i = 0; i < times; i++) {setTimeout(findVideo1, 500 * i);setTimeout(findVideo2, 500 * i);setTimeout(sensitiveContent, 500 * i);}}function findVideo1() {// video play buttondocument.querySelectorAll("[data-testid='playButton']").forEach(button => {// thumbnailbutton.parentElement.querySelectorAll("img:not(.download-set)").forEach(thumbnail => {thumbnail.classList.add("download-set");const url = thumbnail.src;situation(url, thumbnail);});});}function findVideo2() {// videodocument.querySelectorAll("video:not(.download-set)").forEach(video => {video.classList.add("download-set");const url = video.poster;situation(url, video);});}function situation(url, video) {// situation 1: gifif (url.includes("tweet_")) findMenu(video, "gif");// situation 2: videoelse if (url.includes("ext_tw_") ||url.includes("amplify_") ||url.includes("media"))findMenu(video, "video");// situation 3: unknownelse console.log("Error: Unknown");}function findMenu(child, type) {const article = child.closest("article:not(.article-set)");if (!article) return;article.classList.add("article-set");const menus = article.querySelectorAll("[data-testid='caret']");menus.forEach(menu =>menu.addEventListener("click", () => {clickMenu(article, type, false);if (type === "gif") clickMenu(article, type, true);}));}function clickMenu(article, type, convert) {// check exist.if (!!document.querySelector(`.option-download-${convert}-set`)) return;// wait menu.if (!document.querySelector("[role='menuitem']")) {setTimeout(() => clickMenu(article, type, convert), 100);return;}const menu = document.querySelector("[role='menuitem']").parentElement;// add "download" option.const option = document.createElement("div");option.className ="css-1dbjc4n r-1loqt21 r-18u37iz r-1ny4l3l r-ymttw5 r-1yzf0co r-o7ynqc r-6416eg r-13qz1uu option-download-set";option.addEventListener("mouseenter", () =>option.classList.add(getTheme(["r-1u4rsef", "r-1ysxnx4", "r-1uaug3w"])));option.addEventListener("mouseleave", () =>option.classList.remove(getTheme(["r-1u4rsef", "r-1ysxnx4", "r-1uaug3w"])));option.addEventListener("click", () =>clickDownload(article, type, convert));// iconconst icon = document.createElement("div");icon.className = "css-1dbjc4n r-1777fci";icon.innerHTML = svg;const svgElement = icon.querySelector("svg");svgElement.setAttribute("class","r-4qtqp9 r-yyyyoo r-1q142lx r-1xvli5t r-zso239 r-dnmrzs r-bnwqim r-1plcrui r-lrvibr");svgElement.classList.add(getTheme(["r-1re7ezh", "r-9ilb82", "r-111h2gw"]));// textconst text1 = document.createElement("div");text1.className = "css-1dbjc4n r-16y2uox r-1wbh5a2";const text2 = document.createElement("div");text2.className ="css-901oao r-1qd0xha r-a023e6 r-16dba41 r-ad9z0x r-bcqeeo r-qvutc0";text2.classList.add(getTheme(["r-hkyrab", "r-1fmj7o5", "r-jwli3a"]));const text3 = document.createElement("span");text3.className ="css-901oao css-16my406 r-1qd0xha r-ad9z0x r-bcqeeo r-qvutc0";text3.innerText = getLocalization(type, convert);// appendmenu.appendChild(option);option.appendChild(icon);option.appendChild(text1);text1.appendChild(text2);text2.appendChild(text3);}function clickDownload(article, type, convert) {// gifif (type === "gif" && !convert) {let link;// condition 1: not play yet.article.querySelectorAll("video").forEach(video => {link = video.src;});// condition 2: playing.if (!link) {const image = [...article.querySelectorAll("img")].find(image =>image.src.includes("video"));const id = image.src.split(/[/?]/)[4];link = `https://video.twimg.com/tweet_video/${id}.mp4`;}// openwindow.open(link);}// videoelse {const tweetId = article.querySelector("time").parentElement.href.split('/').pop();window.open(`https://twittervid.com/i/status/${tweetId}`);}}// Rest of the script remains unchanged...function getTheme(array) {const body = document.querySelector("body");const color = body.style.backgroundColor; // "#74818e"const red = color.match(/\d+/)[0]; // "#74818e"switch (red) {case "255":return array[0]; // #74818ecase "0":return array[1]; // #74818edefault:return array[2]; // #74818e}}function getLocalization(type, convert) {let download = "Download";switch (document.querySelector("html").lang) {case "zh-Hant":download = "下載";break;case "zh":download = "下载";break;case "ja":download = "ダウンロード";break;case "ko":download = "다운로드";break;case "ru":download = "Скачать";break;}let extension = "";if (type === "gif") extension = convert ? " GIF" : " MP4";return `${download}${extension}`;}function sensitiveContent() {// click "view" button on sensitive content warning to run this script again.document.querySelectorAll(".r-42olwf.r-1vsu8ta:not(.view-set)").forEach(view => {view.classList.add("view-set");view.addEventListener("click", () => init(3));});}function update() {if (updating) return;updating = true;init(3);setTimeout(() => { updating = false; }, 1000);}function locationChange() {const observer = new MutationObserver(mutations => {mutations.forEach(() => {if (currentUrl !== document.location.href) {currentUrl = document.location.href;init(10);}});});const target = document.body;const config = { childList: true, subtree: true };observer.observe(target, config);}})();