Greasy Fork is available in English.
蝦皮商品頁面的網址很冗長,將其縮短,方便複製、分享、儲存成乾淨的書籤
// ==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);}//StaticRedirect.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 = {//attributesdescription : '',exampleUrl : '',exampleR###lt : '',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,exampleR###lt : this.exampleR###lt,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 r###lt = {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) {r###lt.isDisabledMatch = true;} else if (this._excludeMatch(url)) {r###lt.isExcludeMatch = true;} else {r###lt.isMatch = true;r###lt.redirectTo = redirectTo;}}return r###lt;},//Updates the .exampleR###lt field or the .error//field depending on if the example url and patterns match//and make a good redirectupdateExampleR###lt : function() {//Default valuesthis.error = null;this.exampleR###lt = '';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 r###lt must start with http:// or https://, current r###lt is: "' + match.redirectTo;// return;//}if (!match.isMatch) {this.error = 'The include pattern does not match the example url.';return;}this.exampleR###lt = 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 patternvar 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.exampleR###lt = o.exampleR###lt || '';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 r###ltUrl = 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);}r###ltUrl = r###ltUrl.replace(new RegExp('\\$' + i, 'gi'), repl);}this._rxInclude.lastIndex = 0;return r###ltUrl;},_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 urlfunction sturl() {// urlvar url = window.location.href;// new urlvar nurl = window.location.href;// query string need to be removedvar 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 somethingvar qseq = [['page', '0']];// includePatternvar inp = 'https://shopee.tw/*-i.*.*'// redirectUrlvar reu = 'https://shopee.tw/product/$2/$3'// remove not necessary query stringnurl = rmqs(nurl, qs);// remove not necessary query string if equal to somethingnurl = rmqseq(nurl, qseq);// rewrite url from includePattern to redirectUrlnurl = rwurl(nurl, inp, reu);// do nothing if new url is the same as urlif (url == nurl){return false;}// update url in address bar to new urlwindow.history.replaceState(null, null, nurl);// update url in address bar to new url(deprecated)//window.location.replace(nurl)}// remove not necessary query stringfunction 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 somethingfunction 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 redirectUrlfunction rwurl(url, inp, reu) {var activeRedirect = new Redirect();activeRedirect.appliesTo = Redirect.requestTypes.main_frame;activeRedirect.patternType = Redirect.WILDCARD;activeRedirect.exampleUrl = urlactiveRedirect.includePattern = inp;activeRedirect.redirectUrl = reu;activeRedirect.updateExampleR###lt();if (activeRedirect.error == null){url = activeRedirect.exampleR###lt}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----*/