蝦皮網址縮短

蝦皮商品頁面的網址很冗長,將其縮短,方便複製、分享、儲存成乾淨的書籤

// ==UserScript==
// @name        蝦皮網址縮短
// @namespace   https://greasyfork.org/scripts/438833
// @version     1.6
// @description 蝦皮商品頁面的網址很冗長,將其縮短,方便複製、分享、儲存成乾淨的書籤
// @author      fmnijk
// @match       https://shopee.tw/*
// @icon        https://www.google.com/s2/favicons?domain=shopee.tw
// @grant       none
// @license     MIT
// ==/UserScript==

/* $ and $$ */
const $ = document.querySelector.bind(document);
const $$ = document.querySelectorAll.bind(document);

/*----https://raw.githubusercontent.com/einaregilsson/Redirector/master/js/redirect.js start----*/
function Redirect(o) {
	this._init(o);
}

//Static
Redirect.WILDCARD = 'W';
Redirect.REGEX = 'R';

Redirect.requestTypes = {
	main_frame: "Main window (address bar)",
	sub_frame: "IFrames",
	stylesheet : "Stylesheets",
	font: "Fonts",
	script : "Scripts",
	image : "Images",
	imageset: "Responsive Images in Firefox",
	object : "Objects (e.g. Flash content, Java applets)",
	object_subrequest : "Object subrequests",
	xmlhttprequest : "XMLHttpRequests (Ajax)",
	history : "HistoryState",
	other : "Other"
};


Redirect.prototype = {

	//attributes
	description : '',
	exampleUrl : '',
	exampleResult : '',
	error : null,
	includePattern : '',
	excludePattern : '',
	patternDesc:'',
	redirectUrl : '',
	patternType : '',
	processMatches : 'noProcessing',
	disabled : false,
	grouped: false,

	compile : function() {

		var incPattern = this._preparePattern(this.includePattern);
		var excPattern = this._preparePattern(this.excludePattern);

		if (incPattern) {
			this._rxInclude = new RegExp(incPattern, 'gi');
		}
		if (excPattern) {
			this._rxExclude = new RegExp(excPattern, 'gi');
		}
	},

	equals : function(redirect) {
		return this.description == redirect.description
			&& this.exampleUrl == redirect.exampleUrl
			&& this.includePattern == redirect.includePattern
			&& this.excludePattern == redirect.excludePattern
			&& this.patternDesc == redirect.patternDesc
			&& this.redirectUrl == redirect.redirectUrl
			&& this.patternType == redirect.patternType
			&& this.processMatches == redirect.processMatches
			&& this.appliesTo.toString() == redirect.appliesTo.toString();
	},

	toObject : function() {
		return {
			description : this.description,
			exampleUrl : this.exampleUrl,
			exampleResult : this.exampleResult,
			error : this.error,
			includePattern : this.includePattern,
			excludePattern : this.excludePattern,
			patternDesc : this.patternDesc,
			redirectUrl : this.redirectUrl,
			patternType : this.patternType,
			processMatches : this.processMatches,
			disabled : this.disabled,
			grouped: this.grouped,
			appliesTo : this.appliesTo.slice(0)
		};
	},

	getMatch: function(url, forceIgnoreDisabled) {
		if (!this._rxInclude) {
			this.compile();
		}
		var result = {
			isMatch : false,
			isExcludeMatch : false,
			isDisabledMatch : false,
			redirectTo : '',
			toString : function() { return JSON.stringify(this); }
		};
		var redirectTo = this._includeMatch(url);

		if (redirectTo !== null) {
			if (this.disabled && !forceIgnoreDisabled) {
				result.isDisabledMatch = true;
			} else if (this._excludeMatch(url)) {
				result.isExcludeMatch = true;
			} else {
				result.isMatch = true;
				result.redirectTo = redirectTo;
			}
		}
		return result;
	},

	//Updates the .exampleResult field or the .error
	//field depending on if the example url and patterns match
	//and make a good redirect
	updateExampleResult : function() {

		//Default values
		this.error = null;
		this.exampleResult = '';


		if (!this.exampleUrl) {
			this.error = 'No example URL defined.';
			return;
		}

		if (this.patternType == Redirect.REGEX && this.includePattern) {
			try {
				new RegExp(this.includePattern, 'gi');
			} catch(e) {
				this.error = 'Invalid regular expression in Include pattern.';
				return;
			}
		}

		if (this.patternType == Redirect.REGEX && this.excludePattern) {
			try {
				new RegExp(this.excludePattern, 'gi');
			} catch(e) {
				this.error = 'Invalid regular expression in Exclude pattern.';
				return;
			}
		}

		if (!this.appliesTo || this.appliesTo.length == 0) {
			this.error = 'At least one request type must be chosen.';
			return;
		}

		this.compile();

		var match = this.getMatch(this.exampleUrl, true);

		if (match.isExcludeMatch) {
			this.error = 'The exclude pattern excludes the example url.'
			return;
		}

		//Commented out because this code prevents saving many types of valid redirects.
		//if (match.isMatch && !match.redirectTo.match(/^https?\:\/\//)) {
		//	this.error = 'The redirect result must start with http:// or https://, current result is: "' + match.redirectTo;
		//	return;
		//}

    if (!match.isMatch) {
			this.error = 'The include pattern does not match the example url.';
			return;
		}

		this.exampleResult = match.redirectTo;
	},

	isRegex: function() {
		return this.patternType == Redirect.REGEX;
	},

	isWildcard : function() {
		return this.patternType == Redirect.WILDCARD;
	},

	test : function() {
		return this.getMatch(this.exampleUrl);
	},

	//Private functions below
	_rxInclude : null,
	_rxExclude : null,

	_preparePattern : function(pattern) {
		if (!pattern) {
			return null;
		}
		if (this.patternType == Redirect.REGEX) {
			return pattern;
		} else { //Convert wildcard to regex pattern
			var converted = '^';
			for (var i = 0; i < pattern.length; i++) {
				var ch = pattern.charAt(i);
				if ('()[]{}?.^$\\+'.indexOf(ch) != -1) {
					converted += '\\' + ch;
				} else if (ch == '*') {
					converted += '(.*?)';
				} else {
					converted += ch;
				}
			}
			converted += '$';
			return converted;
		}
	},

	_init : function(o) {
		o = o || {};
		this.description = o.description || '';
		this.exampleUrl = o.exampleUrl || '';
		this.exampleResult = o.exampleResult || '';
		this.error = o.error || null;
		this.includePattern = o.includePattern || '';
		this.excludePattern = o.excludePattern || '';
		this.redirectUrl = o.redirectUrl || '';
		this.patternType = o.patternType || Redirect.WILDCARD;

		this.patternTypeText = this.patternType == 'W' ? 'Wildcard' : 'Regular Expression'

		this.patternDesc = o.patternDesc || '';
		this.processMatches = o.processMatches || 'noProcessing';
		if (!o.processMatches && o.unescapeMatches) {
			this.processMatches = 'urlDecode';
		}
		if (!o.processMatches && o.escapeMatches) {
			this.processMatches = 'urlEncode';
		}

		this.disabled = !!o.disabled;
		if (o.appliesTo && o.appliesTo.length) {
			this.appliesTo = o.appliesTo.slice(0);
		} else {
			this.appliesTo = ['main_frame'];
		}
	},

	get appliesToText() {
		return this.appliesTo.map(type => Redirect.requestTypes[type]).join(', ');
	},

	get processMatchesExampleText() {
		let examples = {
			noProcessing : 'Use matches as they are',
			urlEncode : 'E.g. turn /bar/foo?x=2 into %2Fbar%2Ffoo%3Fx%3D2',
			urlDecode : 'E.g. turn %2Fbar%2Ffoo%3Fx%3D2 into /bar/foo?x=2',
			doubleUrlDecode : 'E.g. turn %252Fbar%252Ffoo%253Fx%253D2 into /bar/foo?x=2',
			base64Decode : 'E.g. turn aHR0cDovL2Nubi5jb20= into http://cnn.com'
		};

		return examples[this.processMatches];
	},

	toString : function() {
		return JSON.stringify(this.toObject(), null, 2);
	},

	_includeMatch : function(url) {
		if (!this._rxInclude) {
			return null;
		}
		var matches = this._rxInclude.exec(url);
		if (!matches) {
			return null;
		}
		var resultUrl = this.redirectUrl;
		for (var i = matches.length - 1; i > 0; i--) {
			var repl = matches[i] || '';
			if (this.processMatches == 'urlDecode') {
				repl = unescape(repl);
			} else if (this.processMatches == 'doubleUrlDecode') {
				repl = unescape(unescape(repl));
			} else if (this.processMatches == 'urlEncode') {
				repl = encodeURIComponent(repl);
			} else if (this.processMatches == 'base64decode') {
				if (repl.indexOf('%') > -1) {
					repl = unescape(repl);
				}
				repl = atob(repl);
			}
			resultUrl = resultUrl.replace(new RegExp('\\$' + i, 'gi'), repl);
		}
		this._rxInclude.lastIndex = 0;
		return resultUrl;
	},

	_excludeMatch : function(url) {
		if (!this._rxExclude) {
			return false;
		}
		var shouldExclude = this._rxExclude.test(url);
		this._rxExclude.lastIndex = 0;
		return shouldExclude;
	}
};
/*----https://raw.githubusercontent.com/einaregilsson/Redirector/master/js/redirect.js end----*/

// main function
(function() {
    'use strict';
    sturl();
    window.addEventListener('locationchange', function (){
        sturl();
    })
})();

// shorten url
function sturl() {
    // url
    var url = window.location.href;
    // new url
    var nurl = window.location.href;
    // query string need to be removed
    var qs = [
        'sp_atk',
        'xptdk',
        'af_siteid',
        'pid',
        'af_click_lookback',
        'af_viewthrough_lookback',
        'is_retargeting',
        'af_reengagement_window',
        'af_sub_siteid',
        'c',
        'smtt',
        'is_from_login'
    ];
    // query string need to be removed if equal to something
    var qseq = [['page', '0']];
    // includePattern
    var inp = 'https://shopee.tw/*-i.*.*'
    // redirectUrl
    var reu = 'https://shopee.tw/product/$2/$3'

    // remove not necessary query string
    nurl = rmqs(nurl, qs);
    // remove not necessary query string if equal to something
    nurl = rmqseq(nurl, qseq);
    // rewrite url from includePattern to redirectUrl
    nurl = rwurl(nurl, inp, reu);

    // do nothing if new url is the same as url
    if (url == nurl){
        return false;
    }

    // update url in address bar to new url
    window.history.replaceState(null, null, nurl);

    // update url in address bar to new url(deprecated)
    //window.location.replace(nurl)
}

// remove not necessary query string
function rmqs(url, qs) {
    url = new URL(url);
    qs.forEach(function(i){
        url.searchParams.delete(i);
    });
    return url;
}

// remove not necessary query string if equal to something
function rmqseq(url, qseq) {
    url = new URL(url);
    qseq.forEach(function(i){
        if (url.searchParams.get(i[0]) == i[1]){
            url.searchParams.delete(i[0]);
        }
    });
    return url.toString();
}

// rewrite url from includePattern to redirectUrl
function rwurl(url, inp, reu) {
    var activeRedirect = new Redirect();
    activeRedirect.appliesTo = Redirect.requestTypes.main_frame;
    activeRedirect.patternType = Redirect.WILDCARD;
    activeRedirect.exampleUrl = url
    activeRedirect.includePattern = inp;
    activeRedirect.redirectUrl = reu;
    activeRedirect.updateExampleResult();
    if (activeRedirect.error == null){
        url = activeRedirect.exampleResult
    }
    return url;
}

/*----force listen to locationchange work start----*/
history.pushState = ( f => function pushState(){
    var ret = f.apply(this, arguments);
    window.dispatchEvent(new Event('pushstate'));
    window.dispatchEvent(new Event('locationchange'));
    return ret;
})(history.pushState);

history.replaceState = ( f => function replaceState(){
    var ret = f.apply(this, arguments);
    window.dispatchEvent(new Event('replacestate'));
    window.dispatchEvent(new Event('locationchange'));
    return ret;
})(history.replaceState);

window.addEventListener('popstate',()=>{
    window.dispatchEvent(new Event('locationchange'))
});
/*----force listen to locationchange work end----*/