PRPlayer Userscript

Allow users to use In-Site player on patronreact.com

2026/04/03のページです。最新版はこちら

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         PRPlayer Userscript
// @namespace    http://patronreact.net/
// @version      1.2
// @description  Allow users to use In-Site player on patronreact.com
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// @connect      mux.com
// @connect      stream.mux.com
// @connect      stats.mux.com
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    const TARGET_DOMAIN = 'mux.com';
    const REFERER_URL = 'https://www.patreon.com/';

    // Utility to get string URL
    function getUrlString(url) {
        if (!url) return '';
        return (url instanceof URL) ? url.href : String(url);
    }

    // --- 1. Intercept Fetch ---
    const originalFetch = window.fetch;
    window.fetch = async function (...args) {
        let urlStr = '';
        let options = args[1] || {};

        if (args[0] instanceof Request) {
            urlStr = args[0].url;
            options = {
                method: args[0].method,
                headers: args[0].headers,
                body: args[0].body,
                credentials: args[0].credentials,
                mode: args[0].mode
            };
        } else {
            urlStr = getUrlString(args[0]);
        }

        if (urlStr.includes(TARGET_DOMAIN)) {
            return new Promise((resolve, reject) => {
                let reqHeaders = {};

                // Copy original headers intelligently
                if (args[0] instanceof Request && args[0].headers) {
                    args[0].headers.forEach((value, key) => { reqHeaders[key] = value; });
                } else if (options.headers instanceof Headers) {
                    options.headers.forEach((value, key) => { reqHeaders[key] = value; });
                } else if (options.headers) {
                    reqHeaders = { ...options.headers };
                }

                reqHeaders['Referer'] = REFERER_URL;
                reqHeaders['Origin'] = 'https://www.patreon.com'; // Add Origin to be safe

                GM_xmlhttpRequest({
                    method: options.method || 'GET',
                    url: urlStr,
                    headers: reqHeaders,
                    data: options.body || null,
                    responseType: 'arraybuffer',
                    onload: function (response) {
                        const responseOptions = {
                            status: response.status,
                            statusText: response.statusText,
                            headers: new Headers()
                        };

                        if (response.responseHeaders) {
                            response.responseHeaders.trim().split(/[\r\n]+/).forEach((line) => {
                                const parts = line.split(': ');
                                const key = parts.shift();
                                const value = parts.join(': ');
                                if (key) responseOptions.headers.append(key, value);
                            });
                        }

                        resolve(new Response(response.response, responseOptions));
                    },
                    onerror: () => reject(new TypeError('Fetch failed (GM_xmlhttpRequest error)')),
                    ontimeout: () => reject(new TypeError('Fetch failed (Timeout)')),
                    onabort: () => reject(new DOMException('Aborted', 'AbortError'))
                });
            });
        }

        return originalFetch.apply(this, args);
    };

    // --- 2. Intercept XMLHttpRequest ---
    const originalOpen = XMLHttpRequest.prototype.open;
    const originalSend = XMLHttpRequest.prototype.send;
    const originalSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;

    XMLHttpRequest.prototype.open = function (method, url, ...rest) {
        const urlStr = getUrlString(url);
        this._isMuxRequest = urlStr.includes(TARGET_DOMAIN);
        this._reqUrl = urlStr;
        this._reqMethod = method;
        this._reqHeaders = {};
        return originalOpen.apply(this, arguments);
    };

    XMLHttpRequest.prototype.setRequestHeader = function (header, value) {
        if (this._isMuxRequest) {
            this._reqHeaders[header] = value;
        }
        return originalSetRequestHeader.apply(this, arguments);
    };

    XMLHttpRequest.prototype.send = function (body) {
        if (!this._isMuxRequest) {
            return originalSend.apply(this, arguments);
        }

        const xhr = this; // native XHR instance
        this._reqHeaders['Referer'] = REFERER_URL;
        this._reqHeaders['Origin'] = 'https://www.patreon.com';

        // Helper to safely redefine properties
        const defineSafe = (prop, val) => {
            try {
                Object.defineProperty(xhr, prop, { value: val, configurable: true, writable: true });
            } catch (e) {
                // If it fails, fallback to standard assignment (might fail if read-only)
                try { xhr[prop] = val; } catch (e2) {}
            }
        };

        // Dispatch synthetic events on the real XHR
        const dispatch = (type) => {
            xhr.dispatchEvent(new Event(type));
            if (typeof xhr[`on${type}`] === 'function') {
                xhr[`on${type}`](new Event(type));
            }
        };

        GM_xmlhttpRequest({
            method: this._reqMethod || 'GET',
            url: this._reqUrl,
            headers: this._reqHeaders,
            data: body,
            responseType: this.responseType || 'arraybuffer',  // Very important for video blobs
            onreadystatechange: function (response) {
                defineSafe('readyState', response.readyState);
                defineSafe('status', response.status);
                defineSafe('statusText', response.statusText);
                defineSafe('responseURL', response.finalUrl || xhr._reqUrl);

                if (response.readyState === 4) {
                    defineSafe('response', response.response);
                    if (xhr.responseType === '' || xhr.responseType === 'text') {
                        defineSafe('responseText', response.responseText);
                    }

                    // Mock headers
                    xhr.getAllResponseHeaders = () => response.responseHeaders || '';
                    xhr.getResponseHeader = (name) => {
                        if (!response.responseHeaders) return null;
                        const regex = new RegExp('^' + name + ': (.*)$', 'im');
                        const match = response.responseHeaders.match(regex);
                        return match ? match[1] : null;
                    };
                }

                dispatch('readystatechange');
            },
            onload: function (response) {
                dispatch('load');
                dispatch('loadend');
            },
            onerror: function () {
                dispatch('error');
                dispatch('loadend');
            },
            onabort: function () {
                dispatch('abort');
                dispatch('loadend');
            },
            ontimeout: function () {
                dispatch('timeout');
                dispatch('loadend');
            },
            onprogress: function (response) {
                if (xhr.onprogress) {
                    // Try to construct a ProgressEvent if possible
                    try {
                        const progressEvent = new ProgressEvent('progress', {
                            lengthComputable: response.lengthComputable,
                            loaded: response.loaded,
                            total: response.total
                        });
                        xhr.dispatchEvent(progressEvent);
                        xhr.onprogress(progressEvent);
                    } catch (e) {
                         dispatch('progress'); // fallback
                    }
                }
            }
        });
    };
})();