YouTube Link Fixer

By kchmck Last update Aug 13, 2009 — Installed 1,219 times. Daily Installs: 2, 2, 2, 12, 3, 0, 0, 0, 4, 3, 1, 4, 1, 0, 1, 6, 4, 3, 2, 3, 2, 1, 4, 0, 0, 4, 0, 0, 2, 2, 1, 1

There are 15 previous versions of this script.

// ==UserScript==
// @name           YouTube Link Fixer
// @author         Mick Koch
// @description    Remove the JavaScript magic from YouTube navigation.
// @version        0.2.6
// @namespace      http://userscripts.org/users/79579
// @include        http://*youtube.tld/*
// @require        http://userscripts.org/scripts/source/44063.user.js
// ==/UserScript==

// This script is free software. It comes without any warranty, to
// the extent permitted by applicable law. You can redistribute it
// and/or modify it under the terms of the Do What The Fuck You Want
// To Public License, Version 2, as published by Sam Hocevar. See
// http://sam.zoy.org/wtfpl/COPYING for more details.

Element.implement({
    fix: function(fixers) {
        fixers.some(function(fixer) {
            return new fixer(this).fix();
        }.bind(this));

        return this;
    }
});

Object.from_query_string = function(query_string) {
    /*  Create an Object from a query string.

        @notes:
            Any leading question marks will be removed.
            Keys with no value will be set to null.

        @example:
            Object.from_query_string("a=b&c=d&e=f");
                -> {a: "b", c: "d", e: "f"}

            Object.from_query_string("a=&b=c");
                -> {a: null, b: "c"}

        @arg query_string (optional):
            A query string to parse or undefined to use the current location's.

        @return: an Object.  */

    if (query_string === undefined) {
        query_string = location.search;
    }

    if (query_string[0] == "?") {
        query_string = query_string.substring(1);
    }

    var params = query_string.split("&");
    var object = {};

    params.each(function(param) {
        var pair = param.split("=").map(function(item) {
            return unescape(item);
        });

        var key = pair[0];
        var value = pair[1];

        if (value === "") {
            value = null;
        }

        object[key] = value;
    });

    return object;
};

var URL = new Class({
    initialize: function(params, base) {
        /*  @arg params:
                An Object of params to use as a query string.

            @arg base (optional):
                A hostname/pathname or undefined to use the current
                location's.  */

        this.base = base;
        this.params = params;
    },

    path: function() {
        return this.base || location.pathname;
    },

    query: function() {
        /*  Get the query string part of a URL.

            @example:
                new URL({a: "b", c: "d" }, "/path").query();
                    -> "a=b&c=d"

            @return: a String.  */

        return Hash.toQueryString(this.params);
    },

    full: function() {
        /*  Get a constructed URL.

            @example:
                new URL({a: "b", c: "d" }, "/path").full();
                    -> "/path?a=b&c=d"

            @return: a String.  */

        if (this.params) {
            return [this.path(), this.query()].join("?");
        }

        return this.path();
    }
});

var ExtendedURL = new Class({ Extends: URL,
    /*  URL parameters are merged with the current location's.  */

    initialize: function(params, base) {
        /*  @arg params: new parameters to merge.  */

        this.parent($merge(Object.from_query_string(), params), base);
    }
});

var Fixer = new Class({
    fixes: [],

    initialize: function(el) {
        this.el = el;
        this.attrs = [this.el.get("onclick"), this.el.get("href")];
    },

    url: function(link) {},

    replace: function(url) {
        return this.el.set("href", url.full());
    },

    attempt: function(fix) {
        return this.attrs.some(function(attr) {
            var matches = fix.exec(attr);

            if (matches) {
                return this.replace(this.url(matches[1]));
            }
        }.bind(this));
    },

    fix: function() {
        return this.fixes.some(this.attempt.bind(this));
    }
});

var OverwriteFixer = new Class({ Extends: Fixer,
    replace: function(url) {
        return this.parent(url).erase("onclick");
    }
});

var Fixers = new Class({
    initialize: function() {
        this.fixers = Array.slice(arguments);
    },

    elements: function() {
        return $$("a");
    },

    fix: function() {
        return this.elements().fix(this.fixers);
    }
});

// Fixers

var PlayVideo = new Class({ Extends: OverwriteFixer,
    fixes: [new RegExp("onPlayVideos\\('/watch\\?v=(.+)'\\)"),
            new RegExp("fireEvent\\('PlayVideos', \\['(.+)'\\]\\)"),
            new RegExp("onPlayVideos\\(\\['(.+)'\\]\\)")],

    url: function(link) {
        return new URL({v: link}, "/watch");
    }
});

var EditVideo = new Class({ Extends: OverwriteFixer,
    fixes: [new RegExp("fireEvent\\('EditVideos', \\['(.+)'\\]\\)")],

    url: function(link) {
        return new URL({video_id: link}, "/my_videos_edit");
    }
});

var Annotations = new Class({ Extends: OverwriteFixer,
    fixes: [new RegExp("fireEvent\\('AnnotateVideos', \\['(.+)'\\]\\)")],

    url: function(link) {
        return new URL({v: link}, "/my_videos_annotate");
    }
});

var AudioSwap = new Class({ Extends: OverwriteFixer,
    fixes: [
        new RegExp("fireEvent\\('AudioSwap', \\['(.+)'\\]\\)")],

    url: function(link) {
        return new URL({v: link}, "/watch_editaudio");
    }
});

var Insight = new Class({ Extends: OverwriteFixer,
    fixes: [new RegExp("fireEvent\\('InsightVideos', \\['(.+)'\\]\\)")],

    url: function(link) {
        return new URL({v: link}, "/my_videos_insight");
    }
});

var Subscription = new Class({ Extends: OverwriteFixer,
    fixes: [new RegExp("changeSubscription\\('(.+)'\\)"),
            new RegExp("changeSubscription\\(null\\)")],

    url: function(link) {
        if (link) {
            return new ExtendedURL({s: link});
        }

        return new URL(null, "/my_subscriptions");
    }
});

var Playlist = new Class({ Extends: OverwriteFixer,
    fixes: [new RegExp("changePlaylist\\('(.+)'\\)")],

    url: function(link) {
        return new ExtendedURL({p: link});
    }
});

var Page = new Class({ Extends: OverwriteFixer,
    fixes: [new RegExp("applyPage\\((\\d+)\\)")],

    url: function(link) {
        return new ExtendedURL({pi: link});
    }
});

var SharePlaylist = new Class({ Extends: OverwriteFixer,
    fixes: [new RegExp("fireEvent\\('SharePlaylist', '(.+)'\\)"),
            new RegExp("share\\('(.+)'\\)")],

    url: function(link) {
        return new URL({p: link}, "/share");
    }
});

window.addEvent("load", function() {
    var fixers = new Fixers(PlayVideo,
                            EditVideo,
                            Annotations,
                            AudioSwap,
                            Insight,
                            Subscription,
                            Playlist,
                            Page,
                            SharePlaylist);

    return fixers.fix();
});