Greasy Fork

Greasy Fork is available in English.

抖音/快手主页视频下载

在抖音/快手主页右小角显示视频下载按钮

当前为 2024-07-25 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         抖音/快手主页视频下载
// @namespace    shortvideo_homepage_downloader
// @version      0.0.2
// @description  在抖音/快手主页右小角显示视频下载按钮
// @author       hunmer
// @match        https://www.douyin.com/user/*
// @match        https://www.kuaishou.com/profile/*
// @icon         https://lf1-cdn-tos.bytegoofy.com/goofy/ies/douyin_web/public/favicon.ico
// @grant        GM_download
// @license      MIT
// ==/UserScript==

(function() {

    const HOSTS = {
        'www.kuaishou.com': {
            url: 'https://www.kuaishou.com/graphql',
            type: 'json',
            list: json => json?.data?.visionProfilePhotoList?.feeds,
            item: ({photo}) => {
                console.log(photo)
                return {
                    video_url: photo.videoResource.h264.adaptationSet[0].representation[0].url,
                    title: photo.originCaption,
                }
            }
        },
        'www.douyin.com': {
            url: 'https://www.douyin.com/aweme/v1/web/aweme/post/',
            type: 'network',
            list: json => json?.aweme_list,
            item: ({video, desc}) => {
                if(video.format == 'mp4') return {
                    video_url: video.play_addr.url_list[0],
                    title: desc,
                }
            }
        },
    }
    const DETAIL = HOSTS[location.host]
    console.log({DETAIL, host: location.host})
    if(!DETAIL) return

    var originalSend = XMLHttpRequest.prototype.send;
    var resources = [], fv
    var callback = json => {
        let cnt = resources.push(...DETAIL.list(json) || [])
        if(!cnt > 0) return
        if(!fv){
            fv = document.createElement('div')
            fv.style.cssText = `position: fixed;bottom: 50px;right: 50px;border-radius: 20px;background-color: #fe2c55;color: white;z-index: 999;cursor: pointer;`
            fv.onclick = () => {
                console.log(resources)
                if(confirm(`确定要下载${resources.length}个视频吗?`)){
                    let done = 0
                    const next = () => {
                        let item = resources.splice(0, 1)
                        fv.innerHTML = `${++done}/${resources.length}`
                        if(!item.length) return alert("下载完成")

                        let {video_url, title} = DETAIL.item(item[0]) || {}
                        if(!video_url) return
                        let detail = {
                            url: video_url,
                            name: safeFileName(title) + '.mp4',
                            conflictAction: 'overwrite',
                            onload: () => endOutput(`下载完成...`),
                            onerror: () => endOutput(`下载失败...`),
                            ontimeout: () => endOutput(`下载失败...`),
                            // onprogress: console.log,
                            headers: {
                                'User-Agent': 'Mozilla/5.0 (Linux; U; Android 4.0.2; en-us; Galaxy Nexus Build/ICL53F) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
                                'Referer': video_url
                            }
                        }
                        console.log(detail)
                        GM_download(detail)
                    }
                    const endOutput = (...args) => console.log(args) & next()
                    endOutput('开始下载')
                }
            }
            document.body.append(fv)
        }
        fv.innerHTML = `下载 ${cnt} 个视频`
    }
    switch(DETAIL.type){
        case 'json':
            var parse = JSON.parse;
            JSON.parse = function(raw) {
                let json = parse(raw)
                callback(json)
                return json;
            }
            return

        case 'network':
             XMLHttpRequest.prototype.send = function() {
                 this.addEventListener('load', function() {
                     if (this.responseURL.startsWith(DETAIL.url)) {
                         callback(JSON.parse(this.responseText))
                     }
                 });
                 originalSend.apply(this, arguments);
             };
            const originalFetch = window.fetch;
            window.fetch = function() {
                return originalFetch.apply(this, arguments).then(response => {
                    if (response.url.startsWith(DETAIL.url)) {
                        response.clone().json().then(callback);
                    }
                    return response;
                });
            }
            return
    }
   
    function safeFileName(str) {
        return str
            .replaceAll('(', '(')
            .replaceAll(')', ')')
            .replaceAll(':', ':')
            .replaceAll('*', '*')
            .replaceAll('?', '?')
            .replaceAll('"', '"')
            .replaceAll('<', '<')
            .replaceAll('>', '>')
            .replaceAll("|", "|")
            .replaceAll('\\', '\')
            .replaceAll('/', '/')
    }
})();