Created
January 2, 2026 21:15
-
-
Save metacoma/3bef1c7b98bf025088ed82fb53a99ecf to your computer and use it in GitHub Desktop.
twitch clip collect script for violentmonkey
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Twitch Clip JSON Collector (Final, URL-based streamer) | |
| // @namespace http://your.namespace/ | |
| // @version 1.9 | |
| // @description Collect Twitch clips with streamer extracted from URL, clean metadata, numeric views and age in seconds. Ctrl+Shift+L to download JSON file + log to console. | |
| // @author You | |
| // @match *://*/* | |
| // @grant none | |
| // ==/UserScript== | |
| (function () { | |
| 'use strict'; | |
| function saveJSONToFile(filename, data) { | |
| const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = filename; | |
| a.style.display = 'none'; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| URL.revokeObjectURL(url); | |
| } | |
| function parseViews(viewsText) { | |
| const match = viewsText.replace(/,/g, '').match(/(\d+)/); | |
| return match ? parseInt(match[1], 10) : 0; | |
| } | |
| function parseAgeToSeconds(ageText) { | |
| const match = ageText.match(/(\d+)\s+(second|minute|hour|day|week|month|year)s?/i); | |
| if (!match) return null; | |
| const num = parseInt(match[1], 10); | |
| const unit = match[2].toLowerCase(); | |
| const secondsMap = { | |
| second: 1, | |
| minute: 60, | |
| hour: 3600, | |
| day: 86400, | |
| week: 604800, | |
| month: 2592000, | |
| year: 31536000 | |
| }; | |
| return secondsMap[unit] ? num * secondsMap[unit] : null; | |
| } | |
| function extractStreamerFromURL(url) { | |
| try { | |
| const parts = new URL(url); | |
| const path = parts.pathname.split('/'); | |
| return path[1] ? path[1].toLowerCase() : 'unknown'; | |
| } catch (e) { | |
| return 'unknown'; | |
| } | |
| } | |
| function collectTwitchClipData() { | |
| const articles = document.querySelectorAll('article'); | |
| const clips = []; | |
| articles.forEach(article => { | |
| const clipLink = article.querySelector('a[href*="/clip/"]'); | |
| const authorLink = article.querySelector('a[data-a-target="preview-card-clip-curator-link"]'); | |
| const title = article.querySelector('h4'); | |
| const stats = Array.from(article.querySelectorAll('.tw-media-card-stat')).map(e => e.textContent.trim()); | |
| if (clipLink) { | |
| const href = clipLink.href.startsWith("http") ? clipLink.href : location.origin + clipLink.getAttribute('href'); | |
| const streamer = extractStreamerFromURL(href); | |
| const author = authorLink ? authorLink.textContent.replace(/^Clipped by\s+/i, '').trim().toLowerCase() : "unknown"; | |
| const viewsText = stats.find(s => s.includes("views")) || "0"; | |
| const ageText = stats.find(s => s.includes("ago")) || ""; | |
| //const length = stats.find(s => s.includes(":")) || ""; | |
| const length = stats.find(s => /\b[0-9]:[0-9]+\b/.test(s)) || ""; | |
| const views = parseViews(viewsText); | |
| const age_seconds = parseAgeToSeconds(ageText); | |
| console.log(title.innerText); | |
| clips.push({ | |
| url: href, | |
| streamer: streamer, | |
| title: title.innerText, | |
| views: views, | |
| age_seconds: age_seconds, | |
| length: length, | |
| author: author | |
| }); | |
| } | |
| }); | |
| if (clips.length === 0) { | |
| console.log('🎬 No Twitch clips found.'); | |
| return; | |
| } | |
| saveJSONToFile('clips_list.json', clips); | |
| console.log(`💾 Saved ${clips.length} Twitch clip(s) to "clips_list.json"`); | |
| } | |
| window.addEventListener('keydown', (e) => { | |
| if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'l') { | |
| collectTwitchClipData(); | |
| } | |
| }); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment