Greasy Fork

Greasy Fork is available in English.

为Alist生成m3u播放列表文件

为alist中的音视频文件生成并上传或下载一个m3u播放列表文件,脚本编写过程由肉人辅助AI完成

当前为 2024-08-23 提交的版本,查看 最新版本

// ==UserScript==
// @name         为Alist生成m3u播放列表文件
// @namespace    createM3UforAlist.whatGUI
// @version      2024-08-23
// @description  为alist中的音视频文件生成并上传或下载一个m3u播放列表文件,脚本编写过程由肉人辅助AI完成
// @author       whatGUI
// @match        http://*/*
// @match        https://*/*
// @grant        GM_setClipboard
// @icon         https://alist.nn.ci/favicon.ico
// @license      MIT

// ==/UserScript==

(function () {
    "use strict";
    addButton();
})();

function addButton() {
    let buttonLock = false;
    const buttonDiv = document.createElement("div");
    const style = document.createElement("style");
    // 设置 CSS 规则
    style.textContent = `
.toolbar-ex {
  position: fixed;
  right: 65px;
  bottom: 20px;
}

.toolbar-ex-icon {
  width: 2rem;
  height: 2rem;
  color: #ff8718;
  padding: 4px;
  border-radius: 0.5rem;
  cursor: pointer;
  margin-top: 0.25rem;
}
.toolbar-ex-icon:hover {
  color: #ffffff;
  background-color: #ff8718;
}
.m3u-method-menu {
  position: absolute;
  bottom: calc( 32px + 0.25rem);
  right: 0;
  transition: height 0.2s ease-out;
  height: 0;
  overflow: hidden;
}
`;
    // 将 <style> 元素插入到 <head> 中
    document.head.appendChild(style);
    buttonDiv.className = "toolbar-ex";
    buttonDiv.innerHTML = `
    <div class="m3u-method-menu hidden">
        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="toolbar-ex-icon m3u-upload" viewBox="0 0 16 16">
            <path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5"/>
            <path d="M7.646 1.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 2.707V11.5a.5.5 0 0 1-1 0V2.707L5.354 4.854a.5.5 0 1 1-.708-.708z"/>
        </svg>
        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="toolbar-ex-icon m3u-download" viewBox="0 0 16 16">
            <path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5"/>
            <path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708z"/>
        </svg>
        </div>
        <svg fill="none" stroke-width="0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="toolbar-ex-icon toolbar-ex-toggle" height="1em" width="1em" style="overflow: visible;"><path fill="currentColor" d="M7 14a2 2 0 100-4 2 2 0 000 4zM14 12a2 2 0 11-4 0 2 2 0 014 0zM17 14a2 2 0 100-4 2 2 0 000 4z"></path><path fill="currentColor" fill-rule="evenodd" d="M24 12c0 6.627-5.373 12-12 12S0 18.627 0 12 5.373 0 12 0s12 5.373 12 12zm-2 0c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10z" clip-rule="evenodd"></path></svg>
    `;

    document.body.appendChild(buttonDiv);

    document
        .querySelector(".toolbar-ex-toggle")
        .addEventListener("click", function () {
        var menu = document.querySelector(".m3u-method-menu");
        if (menu.classList.contains("hidden")) {
            menu.classList.remove("hidden");
            menu.style.height = "4.5rem";
        } else {
            menu.style.height = "0";
            menu.classList.add("hidden");
        }
    });

    document.querySelector(".m3u-upload").addEventListener("click", uploadM3U);
    document.querySelector(".m3u-download").addEventListener("click", downloadM3U);
}

async function uploadM3U() {
    let files = await getFileList();
    let m3uBlob = generateM3U(files);
    if(!m3uBlob) return;
    await uploadBlob(m3uBlob.blob);
    let m3uURL = await getPlaylistURL();
    if(typeof GM_setClipboard === "function" ){
        //添加地址到剪贴板
        GM_setClipboard(m3uURL);
        alert("m3u上传成功并已复制m3u文件链接到剪贴板,若需查看文件请刷新");
    }
    else {
        alert("m3u上传成功,请刷新后查看");
    }
}

async function downloadM3U() {
    let files = await getFileList();
    let m3uBlob = generateM3U(files);
    if(!m3uBlob) return;
    // 创建一个隐藏的 <a> 标签
    const link = document.createElement("a");
    link.href = m3uBlob.href;
    link.download = "playlist.m3u";
    link.style.display = "none";
    document.body.appendChild(link);
    // 触发点击事件来下载文件
    link.click();
    // 清除元素
    document.body.removeChild(link);
}

function isMediaFile(filename) {
    // 定义常见的影音文件扩展名
    const mediaExtensions = [
        ".mp4",
        ".mkv",
        ".mov",
        ".avi",
        ".flv",
        ".wmv",
        ".webm",
        ".wav",
        ".ogg",
        ".mp3",
        ".flac",
        ".aac",
        ".m4a",
        ".ape",
    ];
    // 获取文件扩展名
    const extension = filename.slice(((filename.lastIndexOf(".") - 1) >>> 0) + 2);
    // 检查扩展名是否在常见的影音类型列表中
    return mediaExtensions.includes("." + extension.toLowerCase());
}

function generateM3U(files) {
    if (!files) {
        alert("m3u生成失败:当前页面没有文件");
        return;
    }
    let m3uContent = "#EXTM3U\n";

    let videoCount = 0;
    files.forEach((video) => {
        if (isMediaFile(video.name)) {
            videoCount++;
            m3uContent += `#EXTINF:-1,${video.name}\n${video.url}\n`;
        }
    });

    if (videoCount === 0) {
        alert("m3u生成失败:当前页面没有音视频文件");
        return;
    }

    // 创建一个新的 Blob 对象,将 M3U 内容包装起来
    const blob = new Blob([m3uContent], { type: "application/x-mpegURL" });
    // 创建一个下载链接
    const href = URL.createObjectURL(blob);
    return { blob, href };
}

async function getFileList() {
    const alistListAPI = "/api/fs/list";
    const folderPath = window.location.pathname;
    const decodedPath = decodeURIComponent(folderPath);

    const alistToken = localStorage.getItem("token");

    const headers = new Headers({
        Authorization: alistToken,
        "Content-Type": "application/json",
    });

    const body = JSON.stringify({
        path: decodedPath,
        password: "",
        page: 1,
        per_page: 0,
        refresh: false,
    });

    const requestOptions = {
        method: "POST",
        headers,
        body,
        redirect: "follow",
    };
    let result;
    try {
        const response = await fetch(alistListAPI, requestOptions);
        result = await response.json();
    } catch (error) {
        console.log("error: ", error);
    }

    let fileList = [];
    result.data?.content.forEach((file) => {
        if (!file.is_dir) {
            fileList.push({
                name: file.name,
                url:
                window.location.origin +
                "/d" +
                decodedPath +
                "/" +
                file.name +
                "?sign=" +
                file.sign,
            });
        }
    });
    console.log(fileList);
    return fileList;
}

async function uploadBlob(blob) {
    const alistUploadAPI = "/api/fs/put";
    const alistToken = localStorage.getItem("token");
    const currentURL = decodeURIComponent(window.location.pathname);
    const path = encodeURIComponent(currentURL + "/playlist.m3u");

    // 设置请求头
    const headers = new Headers({
        Authorization: alistToken,
        "File-Path": path, // 注意路径需要 URL 编码
        "Content-Type": "application/x-mpegURL", // M3U 文件的 Content-Type
        "Content-Length": blob.size.toString(),
        As_Task: "false", // 可选,是否作为任务
    });

    // 创建请求体
    const body = blob;

    try {
        const response = await fetch(alistUploadAPI, {
            method: "PUT",
            headers,
            body,
        });

        if (!response.ok) {
            throw new Error(`Failed to upload: ${response.statusText}`);
        }

        const result = await response.json();
        console.log("Upload successful:", result);
    } catch (error) {
        console.error("Error uploading file:", error);
    }
    return window.location.origin + "/" + path;
}

async function getPlaylistURL() {
    const list = await getFileList();
    for (let index = 0; index < list.length; index++) {
        const file = list[index];
        if (file.name === "playlist.m3u") {
            return encodeURI(file.url);
        }
    }
}