// ==UserScript==
// @name Linkify Plus Plus
// @version 11.0.0
// @description Based on Linkify Plus. Turn plain text URLs into links.
// @license BSD-3-Clause
// @author eight04 <[email protected]>
// @homepageURL https://github.com/eight04/linkify-plus-plus
// @supportURL https://github.com/eight04/linkify-plus-plus/issues
// @namespace eight04.blogspot.com
// @include *
// @exclude https://www.google.*/search*
// @exclude https://www.google.*/webhp*
// @exclude https://music.google.com/*
// @exclude https://mail.google.com/*
// @exclude https://docs.google.com/*
// @exclude https://encrypted.google.com/*
// @exclude https://*101weiqi.com/*
// @exclude https://w3c*.github.io/*
// @exclude https://www.paypal.com/*
// @exclude https://term.ptt.cc/*
// @exclude https://mastodon.social/*
// @grant GM.getValue
// @grant GM.setValue
// @grant GM.deleteValue
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_addValueChangeListener
// @grant unsafeWindow
// @compatible firefox Tampermonkey latest
// @compatible chrome Tampermonkey latest
// @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgdmlld0JveD0iMCAwIDE2IDE2Ij4gPHBhdGggZmlsbD0iIzRjNGM0ZCIgZD0iTTMuNSAxYS41LjUgMCAxIDAgMCAxSDR2OWgtLjVhLjUuNSAwIDAgMCAwIDFoNy44NTVhLjUuNSAwIDAgMCAuNDc1LS4xODQuNS41IDAgMCAwIC4xMDYtLjM5OFYxMC41YS41LjUgMCAxIDAtMSAwdi41SDZWMmguNWEuNS41IDAgMSAwIDAtMWgtM3oiLz4gPHBhdGggZmlsbD0iIzQ1YTFmZiIgZD0iTTIuNSAxNGExIDEgMCAxIDAgMCAyaDExYTEgMSAwIDEgMCAwLTJoLTExeiIvPiA8L3N2Zz4=
// ==/UserScript==
var optionsFuzzyIpLabel = "Match ambiguous IP addresses.";
var optionsIgnoreMustacheLabel = "Ignore URLs inside mustaches e.g. {{ ... }}.";
var optionsEmbedImageLabel = "Create an image element if the URL looks like an image file.";
var optionsEmbedImageExcludeElementLabel = "Exclude following elements. (CSS selector)";
var optionsUnicodeLabel = "Match unicode characters.";
var optionsMailLabel = "Match email address.";
var optionsNewTabLabel = "Open links in new tabs.";
var optionsStandaloneLabel = "The URL must be surrounded by whitespaces.";
var optionsLinkifierLabel = "Linkifier";
var optionsTriggerByPageLoadLabel = "Trigger on page load.";
var optionsTriggerByNewNodeLabel = "Trigger on dynamically created elements.";
var optionsTriggerByHoverLabel = "Trigger on mouse over.";
var optionsTriggerByClickLabel = "Trigger on mouse click.";
var optionsBoundaryLeftLabel = "Allowed characters between the whitespace and the link. (left side)";
var optionsBoundaryRightLabel = "Allowed characters between the whitespace and the link. (right side)";
var optionsExcludeElementLabel = "Do not linkify following elements. (CSS selector)";
var optionsIncludeElementLabel = "Always linkify following elements. Override above. (CSS selector)";
var optionsTimeoutLabel = "Max execution time. (ms)";
var optionsTimeoutHelp = "The script will terminate if it takes too long to convert the entire page.";
var optionsMaxRunTimeLabel = "Max script run time. (ms)";
var optionsMaxRunTimeHelp = "If the script takes too long to run, the process would be splitted into small chunks to avoid browser freeze.";
var optionsUrlMatcherLabel = "URL matcher";
var optionsCustomRulesLabel = "Custom rules. (RegExp per line)";
var currentScopeLabel = "Current domain";
var addScopeLabel = "Add new domain";
var addScopePrompt = "Add new domain";
var deleteScopeLabel = "Delete current domain";
var deleteScopeConfirm = "Delete domain $1?";
var learnMoreButton = "Learn more";
var importButton = "Import";
var importPrompt = "Paste settings";
var exportButton = "Export";
var exportPrompt = "Copy settings";
var translate = {
optionsFuzzyIpLabel: optionsFuzzyIpLabel,
optionsIgnoreMustacheLabel: optionsIgnoreMustacheLabel,
optionsEmbedImageLabel: optionsEmbedImageLabel,
optionsEmbedImageExcludeElementLabel: optionsEmbedImageExcludeElementLabel,
optionsUnicodeLabel: optionsUnicodeLabel,
optionsMailLabel: optionsMailLabel,
optionsNewTabLabel: optionsNewTabLabel,
optionsStandaloneLabel: optionsStandaloneLabel,
optionsLinkifierLabel: optionsLinkifierLabel,
optionsTriggerByPageLoadLabel: optionsTriggerByPageLoadLabel,
optionsTriggerByNewNodeLabel: optionsTriggerByNewNodeLabel,
optionsTriggerByHoverLabel: optionsTriggerByHoverLabel,
optionsTriggerByClickLabel: optionsTriggerByClickLabel,
optionsBoundaryLeftLabel: optionsBoundaryLeftLabel,
optionsBoundaryRightLabel: optionsBoundaryRightLabel,
optionsExcludeElementLabel: optionsExcludeElementLabel,
optionsIncludeElementLabel: optionsIncludeElementLabel,
optionsTimeoutLabel: optionsTimeoutLabel,
optionsTimeoutHelp: optionsTimeoutHelp,
optionsMaxRunTimeLabel: optionsMaxRunTimeLabel,
optionsMaxRunTimeHelp: optionsMaxRunTimeHelp,
optionsUrlMatcherLabel: optionsUrlMatcherLabel,
optionsCustomRulesLabel: optionsCustomRulesLabel,
currentScopeLabel: currentScopeLabel,
addScopeLabel: addScopeLabel,
addScopePrompt: addScopePrompt,
deleteScopeLabel: deleteScopeLabel,
deleteScopeConfirm: deleteScopeConfirm,
learnMoreButton: learnMoreButton,
importButton: importButton,
importPrompt: importPrompt,
exportButton: exportButton,
exportPrompt: exportPrompt
};
/**
* event-lite.js - Light-weight EventEmitter (less than 1KB when gzipped)
*
* @copyright Yusuke Kawasaki
* @license MIT
* @constructor
* @see https://github.com/kawanet/event-lite
* @see http://kawanet.github.io/event-lite/EventLite.html
* @example
* var EventLite = require("event-lite");
*
* function MyClass() {...} // your class
*
* EventLite.mixin(MyClass.prototype); // import event methods
*
* var obj = new MyClass();
* obj.on("foo", function() {...}); // add event listener
* obj.once("bar", function() {...}); // add one-time event listener
* obj.emit("foo"); // dispatch event
* obj.emit("bar"); // dispatch another event
* obj.off("foo"); // remove event listener
*/
function EventLite() {
if (!(this instanceof EventLite)) return new EventLite();
}
const _module_ = {exports: {}};
(function(EventLite) {
// export the class for node.js
if ("undefined" !== typeof _module_) _module_.exports = EventLite;
// property name to hold listeners
var LISTENERS = "listeners";
// methods to export
var methods = {
on: on,
once: once,
off: off,
emit: emit
};
// mixin to self
mixin(EventLite.prototype);
// export mixin function
EventLite.mixin = mixin;
/**
* Import on(), once(), off() and emit() methods into target object.
*
* @function EventLite.mixin
* @param target {Prototype}
*/
function mixin(target) {
for (var key in methods) {
target[key] = methods[key];
}
return target;
}
/**
* Add an event listener.
*
* @function EventLite.prototype.on
* @param type {string}
* @param func {Function}
* @returns {EventLite} Self for method chaining
*/
function on(type, func) {
getListeners(this, type).push(func);
return this;
}
/**
* Add one-time event listener.
*
* @function EventLite.prototype.once
* @param type {string}
* @param func {Function}
* @returns {EventLite} Self for method chaining
*/
function once(type, func) {
var that = this;
wrap.originalListener = func;
getListeners(that, type).push(wrap);
return that;
function wrap() {
off.call(that, type, wrap);
func.apply(this, arguments);
}
}
/**
* Remove an event listener.
*
* @function EventLite.prototype.off
* @param [type] {string}
* @param [func] {Function}
* @returns {EventLite} Self for method chaining
*/
function off(type, func) {
var that = this;
var listners;
if (!arguments.length) {
delete that[LISTENERS];
} else if (!func) {
listners = that[LISTENERS];
if (listners) {
delete listners[type];
if (!Object.keys(listners).length) return off.call(that);
}
} else {
listners = getListeners(that, type, true);
if (listners) {
listners = listners.filter(ne);
if (!listners.length) return off.call(that, type);
that[LISTENERS][type] = listners;
}
}
return that;
function ne(test) {
return test !== func && test.originalListener !== func;
}
}
/**
* Dispatch (trigger) an event.
*
* @function EventLite.prototype.emit
* @param type {string}
* @param [value] {*}
* @returns {boolean} True when a listener received the event
*/
function emit(type, value) {
var that = this;
var listeners = getListeners(that, type, true);
if (!listeners) return false;
var arglen = arguments.length;
if (arglen === 1) {
listeners.forEach(zeroarg);
} else if (arglen === 2) {
listeners.forEach(onearg);
} else {
var args = Array.prototype.slice.call(arguments, 1);
listeners.forEach(moreargs);
}
return !!listeners.length;
function zeroarg(func) {
func.call(that);
}
function onearg(func) {
func.call(that, value);
}
function moreargs(func) {
func.apply(that, args);
}
}
/**
* @ignore
*/
function getListeners(that, type, readonly) {
if (readonly && !that[LISTENERS]) return;
var listeners = that[LISTENERS] || (that[LISTENERS] = {});
return listeners[type] || (listeners[type] = []);
}
})(EventLite);
var Events = _module_.exports;
function createPref(DEFAULT, sep = "/") {
let storage;
let currentScope = "global";
let scopeList = ["global"];
const events = new Events;
const globalCache = {};
let scopedCache = {};
let currentCache = Object.assign({}, DEFAULT);
let initializing;
return Object.assign(events, {
// storage,
// ready,
connect,
disconnect,
get,
getAll,
set,
getCurrentScope,
setCurrentScope,
addScope,
deleteScope,
getScopeList,
import: import_,
export: export_,
has
});
function import_(input) {
const newScopeList = input.scopeList || scopeList.slice();
const scopes = new Set(newScopeList);
if (!scopes.has("global")) {
throw new Error("invalid scopeList");
}
const changes = {
scopeList: newScopeList
};
for (const [scopeName, scope] of Object.entries(input.scopes)) {
if (!scopes.has(scopeName)) {
continue;
}
for (const [key, value] of Object.entries(scope)) {
if (DEFAULT[key] == undefined) {
continue;
}
changes[`${scopeName}${sep}${key}`] = value;
}
}
return storage.setMany(changes);
}
function export_() {
const keys = [];
for (const scope of scopeList) {
keys.push(...Object.keys(DEFAULT).map(k => `${scope}${sep}${k}`));
}
keys.push("scopeList");
return storage.getMany(keys)
.then(changes => {
const _scopeList = changes.scopeList || scopeList.slice();
const scopes = new Set(_scopeList);
const output = {
scopeList: _scopeList,
scopes: {}
};
for (const [key, value] of Object.entries(changes)) {
const sepIndex = key.indexOf(sep);
if (sepIndex < 0) {
continue;
}
const scope = key.slice(0, sepIndex);
const realKey = key.slice(sepIndex + sep.length);
if (!scopes.has(scope)) {
continue;
}
if (DEFAULT[realKey] == undefined) {
continue;
}
if (!output.scopes[scope]) {
output.scopes[scope] = {};
}
output.scopes[scope][realKey] = value;
}
return output;
});
}
function connect(_storage) {
storage = _storage;
initializing = storage.getMany(
Object.keys(DEFAULT).map(k => `global${sep}${k}`).concat(["scopeList"])
)
.then(updateCache);
storage.on("change", updateCache);
return initializing;
}
function disconnect() {
storage.off("change", updateCache);
storage = null;
}
function updateCache(changes, rebuildCache = false) {
if (changes.scopeList) {
scopeList = changes.scopeList;
events.emit("scopeListChange", scopeList);
if (!scopeList.includes(currentScope)) {
return setCurrentScope("global");
}
}
const changedKeys = new Set;
for (const [key, value] of Object.entries(changes)) {
const [scope, realKey] = key.startsWith(`global${sep}`) ? ["global", key.slice(6 + sep.length)] :
key.startsWith(`${currentScope}${sep}`) ? [currentScope, key.slice(currentScope.length + sep.length)] :
[null, null];
if (!scope || DEFAULT[realKey] == null) {
continue;
}
if (scope === "global") {
changedKeys.add(realKey);
globalCache[realKey] = value;
}
if (scope === currentScope) {
changedKeys.add(realKey);
scopedCache[realKey] = value;
}
}
if (rebuildCache) {
Object.keys(DEFAULT).forEach(k => changedKeys.add(k));
}
const realChanges = {};
let isChanged = false;
for (const key of changedKeys) {
const value = scopedCache[key] != null ? scopedCache[key] :
globalCache[key] != null ? globalCache[key] :
DEFAULT[key];
if (currentCache[key] !== value) {
realChanges[key] = value;
currentCache[key] = value;
isChanged = true;
}
}
if (isChanged) {
events.emit("change", realChanges);
}
}
function has(key) {
return currentCache.hasOwnProperty(key);
}
function get(key) {
return currentCache[key];
}
function getAll() {
return Object.assign({}, currentCache);
}
function set(key, value) {
return storage.setMany({
[`${currentScope}${sep}${key}`]: value
});
}
function getCurrentScope() {
return currentScope;
}
function setCurrentScope(newScope) {
if (currentScope === newScope) {
return Promise.resolve(true);
}
if (!scopeList.includes(newScope)) {
return Promise.resolve(false);
}
return storage.getMany(Object.keys(DEFAULT).map(k => `${newScope}${sep}${k}`))
.then(changes => {
currentScope = newScope;
scopedCache = {};
events.emit("scopeChange", currentScope);
updateCache(changes, true);
return true;
});
}
function addScope(scope) {
if (scopeList.includes(scope)) {
return Promise.reject(new Error(`${scope} already exists`));
}
if (scope.includes(sep)) {
return Promise.reject(new Error(`invalid word: ${sep}`));
}
return storage.setMany({
scopeList: scopeList.concat([scope])
});
}
function deleteScope(scope) {
if (scope === "global") {
return Promise.reject(new Error(`cannot delete global`));
}
return Promise.all([
storage.setMany({
scopeList: scopeList.filter(s => s != scope)
}),
storage.deleteMany(Object.keys(DEFAULT).map(k => `${scope}${sep}${k}`))
]);
}
function getScopeList() {
return scopeList;
}
}
const keys = Object.keys;
function isBoolean(val) {
return typeof val === "boolean"
}
function isElement(val) {
return val && typeof val.nodeType === "number"
}
function isString(val) {
return typeof val === "string"
}
function isNumber(val) {
return typeof val === "number"
}
function isObject(val) {
return typeof val === "object" ? val !== null : isFunction(val)
}
function isFunction(val) {
return typeof val === "function"
}
function isArrayLike(obj) {
return isObject(obj) && typeof obj.length === "number" && typeof obj.nodeType !== "number"
}
function forEach(value, fn) {
if (!value) return
for (const key of keys(value)) {
fn(value[key], key);
}
}
function isRef(maybeRef) {
return isObject(maybeRef) && "current" in maybeRef
}
const isUnitlessNumber = {
animationIterationCount: 0,
borderImageOutset: 0,
borderImageSlice: 0,
borderImageWidth: 0,
boxFlex: 0,
boxFlexGroup: 0,
boxOrdinalGroup: 0,
columnCount: 0,
columns: 0,
flex: 0,
flexGrow: 0,
flexPositive: 0,
flexShrink: 0,
flexNegative: 0,
flexOrder: 0,
gridArea: 0,
gridRow: 0,
gridRowEnd: 0,
gridRowSpan: 0,
gridRowStart: 0,
gridColumn: 0,
gridColumnEnd: 0,
gridColumnSpan: 0,
gridColumnStart: 0,
fontWeight: 0,
lineClamp: 0,
lineHeight: 0,
opacity: 0,
order: 0,
orphans: 0,
tabSize: 0,
widows: 0,
zIndex: 0,
zoom: 0,
fillOpacity: 0,
floodOpacity: 0,
stopOpacity: 0,
strokeDasharray: 0,
strokeDashoffset: 0,
strokeMiterlimit: 0,
strokeOpacity: 0,
strokeWidth: 0,
};
function prefixKey(prefix, key) {
return prefix + key.charAt(0).toUpperCase() + key.substring(1)
}
const prefixes = ["Webkit", "ms", "Moz", "O"];
keys(isUnitlessNumber).forEach((prop) => {
prefixes.forEach((prefix) => {
isUnitlessNumber[prefixKey(prefix, prop)] = 0;
});
});
const SVGNamespace = "http://www.w3.org/2000/svg";
const XLinkNamespace = "http://www.w3.org/1999/xlink";
const XMLNamespace = "http://www.w3.org/XML/1998/namespace";
function isVisibleChild(value) {
return !isBoolean(value) && value != null
}
function className(value) {
if (Array.isArray(value)) {
return value.map(className).filter(Boolean).join(" ")
} else if (isObject(value)) {
return keys(value)
.filter((k) => value[k])
.join(" ")
} else if (isVisibleChild(value)) {
return "" + value
} else {
return ""
}
}
const svg = {
animate: 0,
circle: 0,
clipPath: 0,
defs: 0,
desc: 0,
ellipse: 0,
feBlend: 0,
feColorMatrix: 0,
feComponentTransfer: 0,
feComposite: 0,
feConvolveMatrix: 0,
feDiffuseLighting: 0,
feDisplacementMap: 0,
feDistantLight: 0,
feFlood: 0,
feFuncA: 0,
feFuncB: 0,
feFuncG: 0,
feFuncR: 0,
feGaussianBlur: 0,
feImage: 0,
feMerge: 0,
feMergeNode: 0,
feMorphology: 0,
feOffset: 0,
fePointLight: 0,
feSpecularLighting: 0,
feSpotLight: 0,
feTile: 0,
feTurbulence: 0,
filter: 0,
foreignObject: 0,
g: 0,
image: 0,
line: 0,
linearGradient: 0,
marker: 0,
mask: 0,
metadata: 0,
path: 0,
pattern: 0,
polygon: 0,
polyline: 0,
radialGradient: 0,
rect: 0,
stop: 0,
svg: 0,
switch: 0,
symbol: 0,
text: 0,
textPath: 0,
tspan: 0,
use: 0,
view: 0,
};
function createElement(tag, attr, ...children) {
if (isString(attr) || Array.isArray(attr)) {
children.unshift(attr);
attr = {};
}
attr = attr || {};
if (!attr.namespaceURI && svg[tag] === 0) {
attr = { ...attr, namespaceURI: SVGNamespace };
}
if (attr.children != null && !children.length) {
({ children, ...attr } = attr);
}
let node;
if (isString(tag)) {
node = attr.namespaceURI
? document.createElementNS(attr.namespaceURI, tag)
: document.createElement(tag);
attributes(attr, node);
appendChild(children, node);
} else if (isFunction(tag)) {
if (isObject(tag.defaultProps)) {
attr = { ...tag.defaultProps, ...attr };
}
node = tag({ ...attr, children });
}
if (isRef(attr.ref)) {
attr.ref.current = node;
} else if (isFunction(attr.ref)) {
attr.ref(node);
}
return node
}
function appendChild(child, node) {
if (isArrayLike(child)) {
appendChildren(child, node);
} else if (isString(child) || isNumber(child)) {
appendChildToNode(document.createTextNode(child), node);
} else if (child === null) {
appendChildToNode(document.createComment(""), node);
} else if (isElement(child)) {
appendChildToNode(child, node);
}
}
function appendChildren(children, node) {
for (const child of children) {
appendChild(child, node);
}
return node
}
function appendChildToNode(child, node) {
if (node instanceof window.HTMLTemplateElement) {
node.content.appendChild(child);
} else {
node.appendChild(child);
}
}
function normalizeAttribute(s) {
return s.replace(/[A-Z\d]/g, (match) => ":" + match.toLowerCase())
}
function attribute(key, value, node) {
switch (key) {
case "xlinkActuate":
case "xlinkArcrole":
case "xlinkHref":
case "xlinkRole":
case "xlinkShow":
case "xlinkTitle":
case "xlinkType":
attrNS(node, XLinkNamespace, normalizeAttribute(key), value);
return
case "xmlnsXlink":
attr(node, normalizeAttribute(key), value);
return
case "xmlBase":
case "xmlLang":
case "xmlSpace":
attrNS(node, XMLNamespace, normalizeAttribute(key), value);
return
}
switch (key) {
case "htmlFor":
attr(node, "for", value);
return
case "dataset":
forEach(value, (dataValue, dataKey) => {
if (dataValue != null) {
node.dataset[dataKey] = dataValue;
}
});
return
case "innerHTML":
case "innerText":
case "textContent":
node[key] = value;
return
case "spellCheck":
node.spellcheck = value;
return
case "class":
case "className":
if (isFunction(value)) {
value(node);
} else {
attr(node, "class", className(value));
}
return
case "ref":
case "namespaceURI":
return
case "style":
if (isObject(value)) {
forEach(value, (val, key) => {
if (isNumber(val) && isUnitlessNumber[key] !== 0) {
node.style[key] = val + "px";
} else {
node.style[key] = val;
}
});
return
}
}
if (isFunction(value)) {
if (key[0] === "o" && key[1] === "n") {
const attribute = key.toLowerCase();
if (node[attribute] == null) {
node[attribute] = value;
} else {
node.addEventListener(key, value);
}
}
} else if (value === true) {
attr(node, key, "");
} else if (value !== false && value != null) {
attr(node, key, value);
}
}
function attr(node, key, value) {
node.setAttribute(key, value);
}
function attrNS(node, namespace, key, value) {
node.setAttributeNS(namespace, key, value);
}
function attributes(attr, node) {
for (const key of keys(attr)) {
attribute(key, attr[key], node);
}
return node
}
function messageGetter({
getMessage,
DEFAULT
}) {
return (key, params) => {
const message = getMessage(key, params);
if (message) return message;
const defaultMessage = DEFAULT[key];
if (!defaultMessage) return "";
if (!params) return defaultMessage;
if (!Array.isArray(params)) {
params = [params];
}
return defaultMessage.replace(/\$(\d+)/g, (m, n) => params[n - 1]);
};
}
function fallback(getMessage) {
return messageGetter({
getMessage,
DEFAULT: {
currentScopeLabel: "Current scope",
addScopeLabel: "Add new scope",
deleteScopeLabel: "Delete current scope",
learnMoreButton: "Learn more",
importButton: "Import",
exportButton: "Export",
addScopePrompt: "Add new scope",
deleteScopeConfirm: "Delete scope $1?",
importPrompt: "Paste settings",
exportPrompt: "Copy settings"
}
});
}
const VALID_CONTROL = new Set(["import", "export", "scope-list", "add-scope", "delete-scope"]);
class DefaultMap extends Map {
constructor(getDefault) {
super();
this.getDefault = getDefault;
}
get(key) {
let item = super.get(key);
if (!item) {
item = this.getDefault();
super.set(key, item);
}
return item;
}
}
function bindInputs(pref, inputs) {
const bounds = [];
const onPrefChange = change => {
for (const key in change) {
if (!inputs.has(key)) {
continue;
}
for (const input of inputs.get(key)) {
updateInput(input, change[key]);
}
}
};
pref.on("change", onPrefChange);
bounds.push(() => pref.off("change", onPrefChange));
for (const [key, list] of inputs.entries()) {
for (const input of list) {
const evt = input.hasAttribute("realtime") ? "input" : "change";
const onChange = () => updatePref(key, input);
input.addEventListener(evt, onChange);
bounds.push(() => input.removeEventListener(evt, onChange));
}
}
onPrefChange(pref.getAll());
return () => {
for (const unbind of bounds) {
unbind();
}
};
function updatePref(key, input) {
if (!input.checkValidity()) {
return;
}
if (input.type === "checkbox") {
pref.set(key, input.checked);
return;
}
if (input.type === "radio") {
if (input.checked) {
pref.set(key, input.value);
}
return;
}
if (input.nodeName === "SELECT" && input.multiple) {
pref.set(key, [...input.options].filter(o => o.selected).map(o => o.value));
return;
}
if (input.type === "number" || input.type === "range") {
pref.set(key, Number(input.value));
return;
}
pref.set(key, input.value);
}
function updateInput(input, value) {
if (input.nodeName === "INPUT" && input.type === "radio") {
input.checked = input.value === value;
return;
}
if (input.type === "checkbox") {
input.checked = value;
return;
}
if (input.nodeName === "SELECT" && input.multiple) {
const checked = new Set(value);
for (const option of input.options) {
option.selected = checked.has(option.value);
}
return;
}
input.value = value;
}
}
function bindFields(pref, fields) {
const onPrefChange = change => {
for (const key in change) {
if (!fields.has(key)) {
continue;
}
for (const field of fields.get(key)) {
field.disabled = field.dataset.bindToValue ? field.dataset.bindToValue !== change[key] : !change[key];
}
}
};
pref.on("change", onPrefChange);
onPrefChange(pref.getAll());
return () => pref.off("change", onPrefChange);
}
function bindControls({
pref,
controls,
alert: _alert = alert,
confirm: _confirm = confirm,
prompt: _prompt = prompt,
getMessage = () => {},
getNewScope = () => ""
}) {
const CONTROL_METHODS = {
"import": ["click", doImport],
"export": ["click", doExport],
"scope-list": ["change", updateCurrentScope],
"add-scope": ["click", addScope],
"delete-scope": ["click", deleteScope]
};
for (const type in CONTROL_METHODS) {
for (const el of controls.get(type)) {
el.addEventListener(CONTROL_METHODS[type][0], CONTROL_METHODS[type][1]);
}
}
pref.on("scopeChange", updateCurrentScopeEl);
pref.on("scopeListChange", updateScopeList);
updateScopeList();
updateCurrentScopeEl();
const _ = fallback(getMessage);
return unbind;
function unbind() {
pref.off("scopeChange", updateCurrentScopeEl);
pref.off("scopeListChange", updateScopeList);
for (const type in CONTROL_METHODS) {
for (const el of controls.get(type)) {
el.removeEventListener(CONTROL_METHODS[type][0], CONTROL_METHODS[type][1]);
}
}
}
async function doImport() {
try {
const input = await _prompt(_("importPrompt"));
if (input == null) {
return;
}
const settings = JSON.parse(input);
return pref.import(settings);
} catch (err) {
await _alert(err.message);
}
}
async function doExport() {
try {
const settings = await pref.export();
await _prompt(_("exportPrompt"), JSON.stringify(settings));
} catch (err) {
await _alert(err.message);
}
}
function updateCurrentScope(e) {
pref.setCurrentScope(e.target.value);
}
async function addScope() {
try {
let scopeName = await _prompt(_("addScopePrompt"), getNewScope());
if (scopeName == null) {
return;
}
scopeName = scopeName.trim();
if (!scopeName) {
throw new Error("the value is empty");
}
await pref.addScope(scopeName);
pref.setCurrentScope(scopeName);
} catch (err) {
await _alert(err.message);
}
}
async function deleteScope() {
try {
const scopeName = pref.getCurrentScope();
const result = await _confirm(_("deleteScopeConfirm", scopeName));
if (result) {
return pref.deleteScope(scopeName);
}
} catch (err) {
await _alert(err.message);
}
}
function updateCurrentScopeEl() {
const scopeName = pref.getCurrentScope();
for (const el of controls.get("scope-list")) {
el.value = scopeName;
}
}
function updateScopeList() {
const scopeList = pref.getScopeList();
for (const el of controls.get("scope-list")) {
el.innerHTML = "";
el.append(...scopeList.map(scope => {
const option = document.createElement("option");
option.value = scope;
option.textContent = scope;
return option;
}));
}
}
}
function createBinding({
pref,
root,
elements = root.querySelectorAll("input, textarea, select, fieldset, button"),
keyPrefix = "pref-",
controlPrefix = "webext-pref-",
alert,
confirm,
prompt,
getMessage,
getNewScope
}) {
const inputs = new DefaultMap(() => []);
const fields = new DefaultMap(() => []);
const controls = new DefaultMap(() => []);
for (const element of elements) {
const id = element.id && stripPrefix(element.id, keyPrefix);
if (id && pref.has(id)) {
inputs.get(id).push(element);
continue;
}
if (element.nodeName === "INPUT" && element.type === "radio") {
const name = element.name && stripPrefix(element.name, keyPrefix);
if (name && pref.has(name)) {
inputs.get(name).push(element);
continue;
}
}
if (element.nodeName === "FIELDSET" && element.dataset.bindTo) {
fields.get(element.dataset.bindTo).push(element);
continue;
}
const controlType = findControlType(element.classList);
if (controlType) {
controls.get(controlType).push(element);
}
}
const bounds = [bindInputs(pref, inputs), bindFields(pref, fields), bindControls({
pref,
controls,
alert,
confirm,
prompt,
getMessage,
getNewScope
})];
return () => {
for (const unbind of bounds) {
unbind();
}
};
function stripPrefix(id, prefix) {
if (!prefix) {
return id;
}
return id.startsWith(prefix) ? id.slice(prefix.length) : "";
}
function findControlType(list) {
for (const name of list) {
const controlType = stripPrefix(name, controlPrefix);
if (VALID_CONTROL.has(controlType)) {
return controlType;
}
}
}
}
function createUI({
body,
getMessage = () => {},
toolbar = true,
navbar = true,
keyPrefix = "pref-",
controlPrefix = "webext-pref-"
}) {
const root = document.createDocumentFragment();
const _ = fallback(getMessage);
if (toolbar) {
root.append(createToolbar());
}
if (navbar) {
root.append(createNavbar());
}
root.append( /*#__PURE__*/createElement("div", {
class: controlPrefix + "body"
}, body.map(item => {
if (!item.hLevel) {
item.hLevel = 3;
}
return createItem(item);
})));
return root;
function createToolbar() {
return /*#__PURE__*/createElement("div", {
class: controlPrefix + "toolbar"
}, /*#__PURE__*/createElement("button", {
type: "button",
class: [controlPrefix + "import", "browser-style"]
}, _("importButton")), /*#__PURE__*/createElement("button", {
type: "button",
class: [controlPrefix + "export", "browser-style"]
}, _("exportButton")));
}
function createNavbar() {
return /*#__PURE__*/createElement("div", {
class: controlPrefix + "nav"
}, /*#__PURE__*/createElement("select", {
class: [controlPrefix + "scope-list", "browser-style"],
title: _("currentScopeLabel")
}), /*#__PURE__*/createElement("button", {
type: "button",
class: [controlPrefix + "delete-scope", "browser-style"],
title: _("deleteScopeLabel")
}, "\xD7"), /*#__PURE__*/createElement("button", {
type: "button",
class: [controlPrefix + "add-scope", "browser-style"],
title: _("addScopeLabel")
}, "+"));
}
function createItem(p) {
if (p.type === "section") {
return createSection(p);
}
if (p.type === "checkbox") {
return createCheckbox(p);
}
if (p.type === "radiogroup") {
return createRadioGroup(p);
}
return createInput(p);
}
function createInput(p) {
const key = keyPrefix + p.key;
let input;
const onChange = p.validate ? e => {
try {
p.validate(e.target.value);
e.target.setCustomValidity("");
} catch (err) {
e.target.setCustomValidity(err.message || String(err));
}
} : null;
if (p.type === "select") {
input = /*#__PURE__*/createElement("select", {
multiple: p.multiple,
class: "browser-style",
id: key,
onChange: onChange
}, Object.entries(p.options).map(([value, label]) => /*#__PURE__*/createElement("option", {
value: value
}, label)));
} else if (p.type === "textarea") {
input = /*#__PURE__*/createElement("textarea", {
rows: "8",
class: "browser-style",
id: key,
onChange: onChange
});
} else {
input = /*#__PURE__*/createElement("input", {
type: p.type,
id: key,
onChange: onChange
});
}
return /*#__PURE__*/createElement("div", {
class: [`${controlPrefix}${p.type}`, "browser-style", p.className]
}, /*#__PURE__*/createElement("label", {
htmlFor: key
}, p.label), p.learnMore && /*#__PURE__*/createElement(LearnMore, {
url: p.learnMore
}), input, p.help && /*#__PURE__*/createElement(Help, {
content: p.help
}));
}
function createRadioGroup(p) {
return /*#__PURE__*/createElement("div", {
class: [`${controlPrefix}${p.type}`, "browser-style", p.className]
}, /*#__PURE__*/createElement("div", {
class: controlPrefix + "radio-title"
}, p.label), p.learnMore && /*#__PURE__*/createElement(LearnMore, {
url: p.learnMore
}), p.help && /*#__PURE__*/createElement(Help, {
content: p.help
}), p.children.map(c => {
c.parentKey = p.key;
return createCheckbox(inheritProp(p, c));
}));
}
function Help({
content
}) {
return /*#__PURE__*/createElement("p", {
class: controlPrefix + "help"
}, content);
}
function LearnMore({
url
}) {
return /*#__PURE__*/createElement("a", {
href: url,
class: controlPrefix + "learn-more",
target: "_blank",
rel: "noopener noreferrer"
}, _("learnMoreButton"));
}
function createCheckbox(p) {
const id = p.parentKey ? `${keyPrefix}${p.parentKey}-${p.value}` : keyPrefix + p.key;
return /*#__PURE__*/createElement("div", {
class: [`${controlPrefix}${p.type}`, "browser-style", p.className]
}, /*#__PURE__*/createElement("input", {
type: p.type,
id: id,
name: p.parentKey ? keyPrefix + p.parentKey : null,
value: p.value
}), /*#__PURE__*/createElement("label", {
htmlFor: id
}, p.label), p.learnMore && /*#__PURE__*/createElement(LearnMore, {
url: p.learnMore
}), p.help && /*#__PURE__*/createElement(Help, {
content: p.help
}), p.children && /*#__PURE__*/createElement("fieldset", {
class: controlPrefix + "checkbox-children",
dataset: {
bindTo: p.parentKey || p.key,
bindToValue: p.value
}
}, p.children.map(c => createItem(inheritProp(p, c)))));
}
function createSection(p) {
const Header = `h${p.hLevel}`;
p.hLevel++;
return (
/*#__PURE__*/
// FIXME: do we need browser-style for section?
createElement("div", {
class: [controlPrefix + p.type, p.className]
}, /*#__PURE__*/createElement(Header, {
class: controlPrefix + "header"
}, p.label), p.help && /*#__PURE__*/createElement(Help, {
content: p.help
}), p.children && p.children.map(c => createItem(inheritProp(p, c))))
);
}
function inheritProp(parent, child) {
child.hLevel = parent.hLevel;
return child;
}
}
/* eslint-env greasemonkey */
function createGMStorage() {
const setValue = typeof GM_setValue === "function" ?
promisify(GM_setValue) : GM.setValue.bind(GM);
const getValue = typeof GM_getValue === "function" ?
promisify(GM_getValue) : GM.getValue.bind(GM);
const deleteValue = typeof GM_deleteValue === "function" ?
promisify(GM_deleteValue) : GM.deleteValue.bind(GM);
const events = new Events;
if (typeof GM_addValueChangeListener === "function") {
GM_addValueChangeListener("webext-pref-message", (name, oldValue, newValue) => {
const changes = JSON.parse(newValue);
for (const key of Object.keys(changes)) {
if (typeof changes[key] === "object" && changes[key].$undefined) {
changes[key] = undefined;
}
}
events.emit("change", changes);
});
}
return Object.assign(events, {getMany, setMany, deleteMany});
function getMany(keys) {
return Promise.all(keys.map(k =>
getValue(`webext-pref/${k}`)
.then(value => [k, typeof value === "string" ? JSON.parse(value) : value])
))
.then(entries => {
const output = {};
for (const [key, value] of entries) {
output[key] = value;
}
return output;
});
}
function setMany(changes) {
return Promise.all(Object.entries(changes).map(([key, value]) =>
setValue(`webext-pref/${key}`, JSON.stringify(value))
))
.then(() => {
if (typeof GM_addValueChangeListener === "function") {
return setValue("webext-pref-message", JSON.stringify(changes));
}
events.emit("change", changes);
});
}
function deleteMany(keys) {
return Promise.all(keys.map(k => deleteValue(`webext-pref/${k}`)))
.then(() => {
if (typeof GM_addValueChangeListener === "function") {
const changes = {};
for (const key of keys) {
changes[key] = {
$undefined: true
};
}
return setValue("webext-pref-message", JSON.stringify(changes));
}
const changes = {};
for (const key of keys) {
changes[key] = undefined;
}
events.emit("change", changes);
});
}
function promisify(fn) {
return (...args) => {
try {
return Promise.resolve(fn(...args));
} catch (err) {
return Promise.reject(err);
}
};
}
}
/* eslint-env greasemonkey */
function GM_webextPref({
default: default_,
separator,
css = "",
...options
}) {
const pref = createPref(default_, separator);
const initializing = pref.connect(createGMStorage());
let isOpen = false;
const registerMenu =
typeof GM_registerMenuCommand === "function" ? GM_registerMenuCommand :
typeof GM !== "undefined" && GM && GM.registerMenuCommand ? GM.registerMenuCommand.bind(GM) :
undefined;
if (registerMenu) {
registerMenu(`${getTitle()} - Configure`, openDialog);
}
return Object.assign(pref, {
ready: () => initializing,
openDialog
});
function openDialog() {
if (isOpen) {
return;
}
isOpen = true;
let destroyView;
const modal = document.createElement("div");
modal.className = "webext-pref-modal";
modal.onclick = () => {
modal.classList.remove("webext-pref-modal-open");
modal.addEventListener("transitionend", () => {
if (destroyView) {
destroyView();
}
modal.remove();
isOpen = false;
});
};
const style = document.createElement("style");
style.textContent = "body{overflow:hidden}.webext-pref-modal{position:fixed;top:0;right:0;bottom:0;left:0;background:rgba(0,0,0,.5);overflow:auto;z-index:999999;opacity:0;transition:opacity .2s linear;display:flex}.webext-pref-modal-open{opacity:1}.webext-pref-modal::after,.webext-pref-modal::before{content:\"\";display:block;height:30px;visibility:hidden}.webext-pref-iframe-wrap{margin:auto}.webext-pref-iframe{margin:30px 0;display:inline-block;width:100%;max-width:100%;background:#fff;border-width:0;box-shadow:0 0 30px #000;transform:translateY(-20px);transition:transform .2s linear}.webext-pref-modal-open .webext-pref-iframe{transform:none}" + `
body {
padding-right: ${window.innerWidth - document.documentElement.offsetWidth}px;
}
`;
const iframe = document.createElement("iframe");
iframe.className = "webext-pref-iframe";
iframe.srcdoc = `
<html>
<head>
<style class="dialog-style"></style>
</head>
<body>
<div class="dialog-body"></div>
</body>
</html>
`;
const wrap = document.createElement("div");
wrap.className = "webext-pref-iframe-wrap";
wrap.append(iframe);
modal.append(style, wrap);
document.body.appendChild(modal);
iframe.onload = () => {
iframe.onload = null;
iframe.contentDocument.querySelector(".dialog-style").textContent = "body{display:inline-block;font-size:16px;font-family:sans-serif;white-space:nowrap;overflow:hidden;margin:0;color:#3d3d3d;line-height:1}input[type=number],input[type=text],select,textarea{display:block;width:100%;box-sizing:border-box;height:2em;font:inherit;padding:0 .3em;border:1px solid #9e9e9e;cursor:pointer}select[multiple],textarea{height:6em}input[type=number]:hover,input[type=text]:hover,select:hover,textarea:hover{border-color:#d5d5d5}input[type=number]:focus,input[type=text]:focus,select:focus,textarea:focus{cursor:auto;border-color:#3a93ee}textarea{line-height:1.5}input[type=checkbox],input[type=radio]{display:inline-block;width:1em;height:1em;font:inherit;margin:0}button{box-sizing:border-box;height:2em;font:inherit;border:1px solid #9e9e9e;cursor:pointer;background:0 0}button:hover{border-color:#d5d5d5}button:focus{border-color:#3a93ee}.dialog-body{margin:2em}.webext-pref-toolbar{display:flex;align-items:center;margin-bottom:1em}.dialog-title{font-size:1.34em;margin:0 2em 0 0;flex-grow:1}.webext-pref-toolbar button{font-size:.7em;margin-left:.5em}.webext-pref-nav{display:flex;margin-bottom:1em}.webext-pref-nav select{text-align:center;text-align-last:center}.webext-pref-nav button{width:2em}.webext-pref-number,.webext-pref-radiogroup,.webext-pref-select,.webext-pref-text,.webext-pref-textarea{margin:1em 0}.webext-pref-body>:first-child{margin-top:0}.webext-pref-body>:last-child{margin-bottom:0}.webext-pref-number>input,.webext-pref-select>select,.webext-pref-text>input,.webext-pref-textarea>textarea{margin:.3em 0}.webext-pref-checkbox,.webext-pref-radio{margin:.5em 0;padding-left:1.5em}.webext-pref-checkbox>input,.webext-pref-radio>input{margin-left:-1.5em;margin-right:.5em;vertical-align:middle}.webext-pref-checkbox>label,.webext-pref-radio>label{cursor:pointer;vertical-align:middle}.webext-pref-checkbox>label:hover,.webext-pref-radio>label:hover{color:#707070}.webext-pref-checkbox-children,.webext-pref-radio-children{margin:.7em 0 0;padding:0;border-width:0}.webext-pref-checkbox-children[disabled],.webext-pref-radio-children[disabled]{opacity:.5}.webext-pref-checkbox-children>:first-child,.webext-pref-radio-children>:first-child{margin-top:0}.webext-pref-checkbox-children>:last-child,.webext-pref-radio-children>:last-child{margin-bottom:0}.webext-pref-checkbox-children>:last-child>:last-child,.webext-pref-radio-children>:last-child>:last-child{margin-bottom:0}.webext-pref-help{color:#969696}.responsive{white-space:normal}.responsive .dialog-body{margin:1em}.responsive .webext-pref-toolbar{display:block}.responsive .dialog-title{margin:0 0 1em 0}.responsive .webext-pref-toolbar button{font-size:1em}.responsive .webext-pref-nav{display:block}" + css;
const root = iframe.contentDocument.querySelector(".dialog-body");
root.append(createUI(options));
destroyView = createBinding({
pref,
root,
...options
});
const title = document.createElement("h2");
title.className = "dialog-title";
title.textContent = getTitle();
iframe.contentDocument.querySelector(".webext-pref-toolbar").prepend(title);
if (iframe.contentDocument.body.offsetWidth > modal.offsetWidth) {
iframe.contentDocument.body.classList.add("responsive");
}
// calc iframe size
iframe.style = `
width: ${iframe.contentDocument.body.offsetWidth}px;
height: ${iframe.contentDocument.body.scrollHeight}px;
`;
modal.classList.add("webext-pref-modal-open");
};
}
function getTitle() {
return typeof GM_info === "object" ?
GM_info.script.name : GM.info.script.name;
}
}
function prefDefault() {
return {
fuzzyIp: true,
embedImage: true,
embedImageExcludeElement: ".hljs, .highlight, .brush\\:",
ignoreMustache: false,
unicode: false,
mail: true,
newTab: false,
standalone: false,
boundaryLeft: "{[(\"'",
boundaryRight: "'\")]},.;?!",
excludeElement: ".highlight, .editbox, .brush\\:, .bdsug, .spreadsheetinfo",
includeElement: "",
timeout: 10000,
triggerByPageLoad: false,
triggerByNewNode: false,
triggerByHover: true,
triggerByClick: !supportHover(),
maxRunTime: 100,
customRules: "",
};
}
function supportHover() {
return window.matchMedia("(hover)").matches;
}
var prefBody = getMessage => {
return [
{
type: "section",
label: getMessage("optionsUrlMatcherLabel"),
children: [
{
key: "fuzzyIp",
type: "checkbox",
label: getMessage("optionsFuzzyIpLabel")
},
{
key: "ignoreMustache",
type: "checkbox",
label: getMessage("optionsIgnoreMustacheLabel")
},
{
key: "unicode",
type: "checkbox",
label: getMessage("optionsUnicodeLabel")
},
{
key: "mail",
type: "checkbox",
label: getMessage("optionsMailLabel")
},
{
key: "standalone",
type: "checkbox",
label: getMessage("optionsStandaloneLabel"),
children: [
{
key: "boundaryLeft",
type: "text",
label: getMessage("optionsBoundaryLeftLabel")
},
{
key: "boundaryRight",
type: "text",
label: getMessage("optionsBoundaryRightLabel")
}
]
},
{
key: "customRules",
type: "textarea",
label: getMessage("optionsCustomRulesLabel"),
learnMore: "https://github.com/eight04/linkify-plus-plus?tab=readme-ov-file#custom-rules"
},
]
},
{
type: "section",
label: getMessage("optionsLinkifierLabel"),
children: [
{
key: "triggerByPageLoad",
type: "checkbox",
label: getMessage("optionsTriggerByPageLoadLabel")
},
{
key: "triggerByNewNode",
type: "checkbox",
label: getMessage("optionsTriggerByNewNodeLabel")
},
{
key: "triggerByHover",
type: "checkbox",
label: getMessage("optionsTriggerByHoverLabel")
},
{
key: "triggerByClick",
type: "checkbox",
label: getMessage("optionsTriggerByClickLabel")
},
{
key: "embedImage",
type: "checkbox",
label: getMessage("optionsEmbedImageLabel"),
children: [
{
key: "embedImageExcludeElement",
type: "textarea",
label: getMessage("optionsEmbedImageExcludeElementLabel"),
validate: validateSelector
}
]
},
{
key: "newTab",
type: "checkbox",
label: getMessage("optionsNewTabLabel")
},
{
key: "excludeElement",
type: "textarea",
label: getMessage("optionsExcludeElementLabel"),
validate: validateSelector
},
{
key: "includeElement",
type: "textarea",
label: getMessage("optionsIncludeElementLabel"),
validate: validateSelector
},
{
key: "timeout",
type: "number",
label: getMessage("optionsTimeoutLabel"),
help: getMessage("optionsTimeoutHelp")
},
{
key: "maxRunTime",
type: "number",
label: getMessage("optionsMaxRunTimeLabel"),
help: getMessage("optionsMaxRunTimeHelp")
},
]
},
];
function validateSelector(value) {
if (value) {
document.documentElement.matches(value);
}
}
};
var maxLength = 24;
var chars = "セール佛山ಭಾರತ慈善集团在线한국ଭାରତভাৰতর八卦ישראלموقعবংল公益司网站移动我爱你москвақзнлйт联通рбгеקוםファッションストアசிங்கபூர商标店城дию新闻家電中文信国國娱乐భారత్ලංකා购物クラウドભારતभारतम्ोसंगठन餐厅络у香港食品飞利浦台湾灣手机الجزئرنیتبيپکسدغظحةڀ澳門닷컴شكგე构健康ไทย招聘фລາວみんなευλ世界書籍ഭാരതംਭਾਰਤ址넷コム游戏ö企业息صط广东இலைநதயாհայ加坡ف政务";
var table = {
aaa: true,
aarp: true,
abb: true,
abbott: true,
abbvie: true,
abc: true,
able: true,
abogado: true,
abudhabi: true,
ac: true,
academy: true,
accountant: true,
accountants: true,
aco: true,
actor: true,
ad: true,
adult: true,
ae: true,
aeg: true,
aero: true,
aetna: true,
af: true,
afl: true,
africa: true,
ag: true,
agency: true,
ai: true,
aig: true,
airbus: true,
airforce: true,
akdn: true,
al: true,
allfinanz: true,
allstate: true,
ally: true,
alsace: true,
alstom: true,
am: true,
amazon: true,
americanexpress: true,
amex: true,
amfam: true,
amica: true,
amsterdam: true,
analytics: true,
android: true,
anz: true,
ao: true,
apartments: true,
app: true,
apple: true,
aq: true,
aquarelle: true,
ar: true,
archi: true,
army: true,
arpa: true,
art: true,
arte: true,
as: true,
asia: true,
associates: true,
at: true,
attorney: true,
au: true,
auction: true,
audi: true,
audio: true,
auspost: true,
auto: true,
autos: true,
aw: true,
aws: true,
ax: true,
axa: true,
az: true,
azure: true,
ba: true,
baby: true,
band: true,
bank: true,
bar: true,
barcelona: true,
barclaycard: true,
barclays: true,
bargains: true,
basketball: true,
bauhaus: true,
bayern: true,
bb: true,
bbc: true,
bbva: true,
bd: true,
be: true,
beauty: true,
beer: true,
bentley: true,
berlin: true,
best: true,
bet: true,
bf: true,
bg: true,
bh: true,
bi: true,
bible: true,
bid: true,
bike: true,
bing: true,
bingo: true,
bio: true,
biz: true,
bj: true,
black: true,
blackfriday: true,
blog: true,
bloomberg: true,
blue: true,
bm: true,
bmw: true,
bn: true,
bnpparibas: true,
bo: true,
boats: true,
bond: true,
boo: true,
bostik: true,
boston: true,
bot: true,
boutique: true,
box: true,
br: true,
bradesco: true,
bridgestone: true,
broadway: true,
broker: true,
brother: true,
brussels: true,
bs: true,
bt: true,
build: true,
builders: true,
business: true,
buzz: true,
bw: true,
by: true,
bz: true,
bzh: true,
ca: true,
cab: true,
cafe: true,
cam: true,
camera: true,
camp: true,
canon: true,
capetown: true,
capital: true,
car: true,
cards: true,
care: true,
career: true,
careers: true,
cars: true,
casa: true,
"case": true,
cash: true,
casino: true,
cat: true,
catering: true,
catholic: true,
cba: true,
cbn: true,
cc: true,
cd: true,
center: true,
ceo: true,
cern: true,
cf: true,
cfa: true,
cfd: true,
cg: true,
ch: true,
chanel: true,
channel: true,
charity: true,
chase: true,
chat: true,
cheap: true,
chintai: true,
christmas: true,
church: true,
ci: true,
cisco: true,
citi: true,
citic: true,
city: true,
ck: true,
cl: true,
claims: true,
cleaning: true,
click: true,
clinic: true,
clothing: true,
cloud: true,
club: true,
clubmed: true,
cm: true,
cn: true,
co: true,
coach: true,
codes: true,
coffee: true,
college: true,
cologne: true,
com: true,
commbank: true,
community: true,
company: true,
compare: true,
computer: true,
condos: true,
construction: true,
consulting: true,
contact: true,
contractors: true,
cooking: true,
cool: true,
coop: true,
corsica: true,
country: true,
coupons: true,
courses: true,
cpa: true,
cr: true,
credit: true,
creditcard: true,
creditunion: true,
cricket: true,
crown: true,
crs: true,
cruises: true,
cu: true,
cuisinella: true,
cv: true,
cw: true,
cx: true,
cy: true,
cymru: true,
cyou: true,
cz: true,
dabur: true,
dad: true,
dance: true,
date: true,
dating: true,
day: true,
de: true,
dealer: true,
deals: true,
degree: true,
delivery: true,
dell: true,
deloitte: true,
democrat: true,
dental: true,
dentist: true,
desi: true,
design: true,
dev: true,
dhl: true,
diamonds: true,
diet: true,
digital: true,
direct: true,
directory: true,
discount: true,
discover: true,
diy: true,
dj: true,
dk: true,
dm: true,
dnp: true,
"do": true,
doctor: true,
dog: true,
domains: true,
download: true,
dubai: true,
dupont: true,
durban: true,
dvag: true,
dz: true,
earth: true,
ec: true,
eco: true,
edeka: true,
edu: true,
education: true,
ee: true,
eg: true,
email: true,
emerck: true,
energy: true,
engineer: true,
engineering: true,
enterprises: true,
equipment: true,
er: true,
ericsson: true,
erni: true,
es: true,
esq: true,
estate: true,
et: true,
eu: true,
eurovision: true,
eus: true,
events: true,
exchange: true,
expert: true,
exposed: true,
express: true,
extraspace: true,
fage: true,
fail: true,
fairwinds: true,
faith: true,
family: true,
fan: true,
fans: true,
farm: true,
fashion: true,
feedback: true,
ferrero: true,
fi: true,
film: true,
finance: true,
financial: true,
firmdale: true,
fish: true,
fishing: true,
fit: true,
fitness: true,
fj: true,
fk: true,
flickr: true,
flights: true,
florist: true,
flowers: true,
fm: true,
fo: true,
foo: true,
food: true,
football: true,
ford: true,
forex: true,
forsale: true,
forum: true,
foundation: true,
fox: true,
fr: true,
fresenius: true,
frl: true,
frogans: true,
fujitsu: true,
fun: true,
fund: true,
furniture: true,
futbol: true,
fyi: true,
ga: true,
gal: true,
gallery: true,
game: true,
games: true,
garden: true,
gay: true,
gd: true,
gdn: true,
ge: true,
gea: true,
gent: true,
genting: true,
gf: true,
gg: true,
gh: true,
gi: true,
gift: true,
gifts: true,
gives: true,
giving: true,
gl: true,
glass: true,
gle: true,
global: true,
globo: true,
gm: true,
gmail: true,
gmbh: true,
gmo: true,
gmx: true,
gn: true,
godaddy: true,
gold: true,
golf: true,
goog: true,
google: true,
gop: true,
gov: true,
gp: true,
gq: true,
gr: true,
grainger: true,
graphics: true,
gratis: true,
green: true,
gripe: true,
group: true,
gs: true,
gt: true,
gu: true,
gucci: true,
guide: true,
guitars: true,
guru: true,
gw: true,
gy: true,
hair: true,
hamburg: true,
haus: true,
health: true,
healthcare: true,
help: true,
helsinki: true,
here: true,
hermes: true,
hiphop: true,
hisamitsu: true,
hitachi: true,
hiv: true,
hk: true,
hm: true,
hn: true,
hockey: true,
holdings: true,
holiday: true,
homes: true,
honda: true,
horse: true,
hospital: true,
host: true,
hosting: true,
hotmail: true,
house: true,
how: true,
hr: true,
hsbc: true,
ht: true,
hu: true,
hyatt: true,
hyundai: true,
ice: true,
icu: true,
id: true,
ie: true,
ieee: true,
ifm: true,
ikano: true,
il: true,
im: true,
imamat: true,
immo: true,
immobilien: true,
"in": true,
inc: true,
industries: true,
info: true,
ing: true,
ink: true,
institute: true,
insurance: true,
insure: true,
int: true,
international: true,
investments: true,
io: true,
ipiranga: true,
iq: true,
ir: true,
irish: true,
is: true,
ismaili: true,
ist: true,
istanbul: true,
it: true,
itau: true,
itv: true,
jaguar: true,
java: true,
jcb: true,
je: true,
jetzt: true,
jewelry: true,
jio: true,
jll: true,
jm: true,
jmp: true,
jnj: true,
jo: true,
jobs: true,
joburg: true,
jp: true,
jpmorgan: true,
jprs: true,
juegos: true,
kaufen: true,
ke: true,
kfh: true,
kg: true,
kh: true,
ki: true,
kia: true,
kids: true,
kim: true,
kitchen: true,
kiwi: true,
km: true,
kn: true,
koeln: true,
komatsu: true,
kp: true,
kpmg: true,
kpn: true,
kr: true,
krd: true,
kred: true,
kw: true,
ky: true,
kyoto: true,
kz: true,
la: true,
lamborghini: true,
lancaster: true,
land: true,
landrover: true,
lanxess: true,
lat: true,
latrobe: true,
law: true,
lawyer: true,
lb: true,
lc: true,
lease: true,
leclerc: true,
legal: true,
lexus: true,
lgbt: true,
li: true,
lidl: true,
life: true,
lifestyle: true,
lighting: true,
lilly: true,
limited: true,
limo: true,
lincoln: true,
link: true,
lipsy: true,
live: true,
living: true,
lk: true,
llc: true,
loan: true,
loans: true,
locus: true,
lol: true,
london: true,
lotto: true,
love: true,
lr: true,
ls: true,
lt: true,
ltd: true,
ltda: true,
lu: true,
lundbeck: true,
luxe: true,
luxury: true,
lv: true,
ly: true,
ma: true,
madrid: true,
maif: true,
maison: true,
makeup: true,
man: true,
management: true,
mango: true,
market: true,
marketing: true,
markets: true,
marriott: true,
mattel: true,
mba: true,
mc: true,
md: true,
me: true,
med: true,
media: true,
meet: true,
melbourne: true,
meme: true,
memorial: true,
men: true,
menu: true,
mg: true,
mh: true,
miami: true,
microsoft: true,
mil: true,
mini: true,
mit: true,
mk: true,
ml: true,
mlb: true,
mm: true,
mma: true,
mn: true,
mo: true,
mobi: true,
moda: true,
moe: true,
moi: true,
mom: true,
monash: true,
money: true,
monster: true,
mortgage: true,
moscow: true,
motorcycles: true,
mov: true,
movie: true,
mp: true,
mq: true,
mr: true,
ms: true,
mt: true,
mtn: true,
mtr: true,
mu: true,
museum: true,
music: true,
mv: true,
mw: true,
mx: true,
my: true,
mz: true,
na: true,
nab: true,
nagoya: true,
name: true,
navy: true,
nc: true,
ne: true,
nec: true,
net: true,
netbank: true,
network: true,
neustar: true,
"new": true,
news: true,
next: true,
nexus: true,
nf: true,
ng: true,
ngo: true,
ni: true,
nico: true,
nike: true,
ninja: true,
nissan: true,
nl: true,
no: true,
nokia: true,
nowruz: true,
np: true,
nr: true,
nra: true,
nrw: true,
ntt: true,
nu: true,
nyc: true,
nz: true,
observer: true,
office: true,
okinawa: true,
om: true,
omega: true,
one: true,
ong: true,
onl: true,
online: true,
ooo: true,
oracle: true,
orange: true,
org: true,
organic: true,
osaka: true,
otsuka: true,
ovh: true,
pa: true,
page: true,
panasonic: true,
paris: true,
partners: true,
parts: true,
party: true,
pe: true,
pet: true,
pf: true,
pfizer: true,
pg: true,
ph: true,
pharmacy: true,
phd: true,
philips: true,
photo: true,
photography: true,
photos: true,
physio: true,
pics: true,
pictet: true,
pictures: true,
ping: true,
pink: true,
pioneer: true,
pizza: true,
pk: true,
pl: true,
place: true,
plumbing: true,
plus: true,
pm: true,
pn: true,
pohl: true,
poker: true,
politie: true,
porn: true,
post: true,
pr: true,
praxi: true,
press: true,
prime: true,
pro: true,
productions: true,
prof: true,
promo: true,
properties: true,
property: true,
protection: true,
pru: true,
prudential: true,
ps: true,
pt: true,
pub: true,
pw: true,
pwc: true,
py: true,
qa: true,
qpon: true,
quebec: true,
quest: true,
racing: true,
radio: true,
re: true,
realestate: true,
realtor: true,
realty: true,
recipes: true,
red: true,
redstone: true,
rehab: true,
reise: true,
reisen: true,
reit: true,
ren: true,
rent: true,
rentals: true,
repair: true,
report: true,
republican: true,
rest: true,
restaurant: true,
review: true,
reviews: true,
rexroth: true,
rich: true,
ricoh: true,
rio: true,
rip: true,
ro: true,
rocks: true,
rodeo: true,
rogers: true,
rs: true,
rsvp: true,
ru: true,
rugby: true,
ruhr: true,
run: true,
rw: true,
ryukyu: true,
sa: true,
saarland: true,
sale: true,
salon: true,
samsung: true,
sandvik: true,
sandvikcoromant: true,
sanofi: true,
sap: true,
sarl: true,
saxo: true,
sb: true,
sbi: true,
sbs: true,
sc: true,
scb: true,
schaeffler: true,
schmidt: true,
school: true,
schule: true,
schwarz: true,
science: true,
scot: true,
sd: true,
se: true,
seat: true,
security: true,
select: true,
sener: true,
services: true,
seven: true,
sew: true,
sex: true,
sexy: true,
sfr: true,
sg: true,
sh: true,
sharp: true,
shell: true,
shiksha: true,
shoes: true,
shop: true,
shopping: true,
show: true,
si: true,
singles: true,
site: true,
sk: true,
ski: true,
skin: true,
sky: true,
skype: true,
sl: true,
sm: true,
smart: true,
sn: true,
sncf: true,
so: true,
soccer: true,
social: true,
softbank: true,
software: true,
sohu: true,
solar: true,
solutions: true,
sony: true,
soy: true,
spa: true,
space: true,
sport: true,
sr: true,
srl: true,
ss: true,
st: true,
stada: true,
statebank: true,
statefarm: true,
stc: true,
stockholm: true,
storage: true,
store: true,
stream: true,
studio: true,
study: true,
style: true,
su: true,
sucks: true,
supplies: true,
supply: true,
support: true,
surf: true,
surgery: true,
suzuki: true,
sv: true,
swatch: true,
swiss: true,
sx: true,
sy: true,
sydney: true,
systems: true,
sz: true,
taipei: true,
target: true,
tatamotors: true,
tatar: true,
tattoo: true,
tax: true,
taxi: true,
tc: true,
td: true,
team: true,
tech: true,
technology: true,
tel: true,
temasek: true,
tennis: true,
teva: true,
tf: true,
tg: true,
th: true,
theater: true,
theatre: true,
tickets: true,
tienda: true,
tips: true,
tires: true,
tirol: true,
tj: true,
tk: true,
tl: true,
tm: true,
tn: true,
to: true,
today: true,
tokyo: true,
tools: true,
top: true,
toray: true,
toshiba: true,
total: true,
tours: true,
town: true,
toyota: true,
toys: true,
tr: true,
trade: true,
trading: true,
training: true,
travel: true,
travelers: true,
trust: true,
tt: true,
tube: true,
tui: true,
tv: true,
tvs: true,
tw: true,
tz: true,
ua: true,
ug: true,
uk: true,
unicom: true,
university: true,
uno: true,
uol: true,
us: true,
uy: true,
uz: true,
va: true,
vacations: true,
vana: true,
vanguard: true,
vc: true,
ve: true,
vegas: true,
ventures: true,
versicherung: true,
vet: true,
vg: true,
vi: true,
viajes: true,
video: true,
vig: true,
villas: true,
vin: true,
vip: true,
vision: true,
vivo: true,
vlaanderen: true,
vn: true,
vodka: true,
vote: true,
voting: true,
voto: true,
voyage: true,
vu: true,
wales: true,
walter: true,
wang: true,
watch: true,
watches: true,
webcam: true,
weber: true,
website: true,
wed: true,
wedding: true,
weir: true,
wf: true,
whoswho: true,
wien: true,
wiki: true,
williamhill: true,
win: true,
windows: true,
wine: true,
wme: true,
woodside: true,
work: true,
works: true,
world: true,
ws: true,
wtf: true,
xbox: true,
xin: true,
"xn--1ck2e1b": true,
"xn--1qqw23a": true,
"xn--2scrj9c": true,
"xn--30rr7y": true,
"xn--3bst00m": true,
"xn--3ds443g": true,
"xn--3e0b707e": true,
"xn--3hcrj9c": true,
"xn--45br5cyl": true,
"xn--45brj9c": true,
"xn--45q11c": true,
"xn--4dbrk0ce": true,
"xn--4gbrim": true,
"xn--54b7fta0cc": true,
"xn--55qw42g": true,
"xn--55qx5d": true,
"xn--5tzm5g": true,
"xn--6frz82g": true,
"xn--6qq986b3xl": true,
"xn--80adxhks": true,
"xn--80ao21a": true,
"xn--80asehdb": true,
"xn--80aswg": true,
"xn--8y0a063a": true,
"xn--90a3ac": true,
"xn--90ae": true,
"xn--90ais": true,
"xn--9dbq2a": true,
"xn--bck1b9a5dre4c": true,
"xn--c1avg": true,
"xn--cck2b3b": true,
"xn--clchc0ea0b2g2a9gcd": true,
"xn--czr694b": true,
"xn--czrs0t": true,
"xn--czru2d": true,
"xn--d1acj3b": true,
"xn--d1alf": true,
"xn--e1a4c": true,
"xn--efvy88h": true,
"xn--fct429k": true,
"xn--fiq228c5hs": true,
"xn--fiq64b": true,
"xn--fiqs8s": true,
"xn--fiqz9s": true,
"xn--fjq720a": true,
"xn--fpcrj9c3d": true,
"xn--fzc2c9e2c": true,
"xn--g2xx48c": true,
"xn--gckr3f0f": true,
"xn--gecrj9c": true,
"xn--h2breg3eve": true,
"xn--h2brj9c": true,
"xn--h2brj9c8c": true,
"xn--hxt814e": true,
"xn--i1b6b1a6a2e": true,
"xn--imr513n": true,
"xn--io0a7i": true,
"xn--j1amh": true,
"xn--j6w193g": true,
"xn--jvr189m": true,
"xn--kcrx77d1x4a": true,
"xn--kprw13d": true,
"xn--kpry57d": true,
"xn--kput3i": true,
"xn--l1acc": true,
"xn--lgbbat1ad8j": true,
"xn--mgb9awbf": true,
"xn--mgba3a4f16a": true,
"xn--mgbaam7a8h": true,
"xn--mgbab2bd": true,
"xn--mgbah1a3hjkrd": true,
"xn--mgbai9azgqp6j": true,
"xn--mgbayh7gpa": true,
"xn--mgbbh1a": true,
"xn--mgbc0a9azcg": true,
"xn--mgbca7dzdo": true,
"xn--mgbcpq6gpa1a": true,
"xn--mgberp4a5d4ar": true,
"xn--mgbgu82a": true,
"xn--mgbpl2fh": true,
"xn--mgbtx2b": true,
"xn--mix891f": true,
"xn--mk1bu44c": true,
"xn--ngbc5azd": true,
"xn--ngbe9e0a": true,
"xn--node": true,
"xn--nqv7f": true,
"xn--nyqy26a": true,
"xn--o3cw4h": true,
"xn--ogbpf8fl": true,
"xn--otu796d": true,
"xn--p1acf": true,
"xn--p1ai": true,
"xn--pgbs0dh": true,
"xn--q7ce6a": true,
"xn--q9jyb4c": true,
"xn--qxa6a": true,
"xn--qxam": true,
"xn--rhqv96g": true,
"xn--rovu88b": true,
"xn--rvc1e0am3e": true,
"xn--s9brj9c": true,
"xn--ses554g": true,
"xn--t60b56a": true,
"xn--tckwe": true,
"xn--unup4y": true,
"xn--vermgensberatung-pwb": true,
"xn--vhquv": true,
"xn--vuq861b": true,
"xn--wgbh1c": true,
"xn--wgbl6a": true,
"xn--xhq521b": true,
"xn--xkc2al3hye2a": true,
"xn--xkc2dl3a5ee0h": true,
"xn--y9a3aq": true,
"xn--yfro4i67o": true,
"xn--ygbi2ammx": true,
"xn--zfr164b": true,
xxx: true,
xyz: true,
yachts: true,
yahoo: true,
yandex: true,
ye: true,
yodobashi: true,
yoga: true,
yokohama: true,
youtube: true,
yt: true,
za: true,
zappos: true,
zara: true,
zip: true,
zm: true,
zone: true,
zuerich: true,
zw: true,
"セール": true,
"佛山": true,
"ಭಾರತ": true,
"慈善": true,
"集团": true,
"在线": true,
"한국": true,
"ଭାରତ": true,
"ভাৰত": true,
"ভারত": true,
"八卦": true,
"ישראל": true,
"موقع": true,
"বাংলা": true,
"公益": true,
"公司": true,
"网站": true,
"移动": true,
"我爱你": true,
"москва": true,
"қаз": true,
"онлайн": true,
"сайт": true,
"联通": true,
"срб": true,
"бг": true,
"бел": true,
"קום": true,
"ファッション": true,
"орг": true,
"ストア": true,
"சிங்கப்பூர்": true,
"商标": true,
"商店": true,
"商城": true,
"дети": true,
"мкд": true,
"ею": true,
"新闻": true,
"家電": true,
"中文网": true,
"中信": true,
"中国": true,
"中國": true,
"娱乐": true,
"భారత్": true,
"ලංකා": true,
"购物": true,
"クラウド": true,
"ભારત": true,
"भारतम्": true,
"भारत": true,
"भारोत": true,
"网店": true,
"संगठन": true,
"餐厅": true,
"网络": true,
"укр": true,
"香港": true,
"食品": true,
"飞利浦": true,
"台湾": true,
"台灣": true,
"手机": true,
"мон": true,
"الجزائر": true,
"عمان": true,
"ایران": true,
"امارات": true,
"بازار": true,
"موريتانيا": true,
"پاکستان": true,
"الاردن": true,
"بارت": true,
"المغرب": true,
"ابوظبي": true,
"البحرين": true,
"السعودية": true,
"ڀارت": true,
"سودان": true,
"عراق": true,
"澳門": true,
"닷컴": true,
"شبكة": true,
"بيتك": true,
"გე": true,
"机构": true,
"健康": true,
"ไทย": true,
"سورية": true,
"招聘": true,
"рус": true,
"рф": true,
"تونس": true,
"ລາວ": true,
"みんな": true,
"ευ": true,
"ελ": true,
"世界": true,
"書籍": true,
"ഭാരതം": true,
"ਭਾਰਤ": true,
"网址": true,
"닷넷": true,
"コム": true,
"游戏": true,
"vermögensberatung": true,
"企业": true,
"信息": true,
"مصر": true,
"قطر": true,
"广东": true,
"இலங்கை": true,
"இந்தியா": true,
"հայ": true,
"新加坡": true,
"فلسطين": true,
"政务": true
};
var RE = {
PROTOCOL: "([a-z][-a-z*]+://)?",
USER: "(?:([\\w:.+-]+)@)?",
DOMAIN_UNI: `([a-z0-9-.\\u00A0-\\uFFFF]+\\.[a-z0-9-${chars}]{1,${maxLength}})`,
DOMAIN: `([a-z0-9-.]+\\.[a-z0-9-]{1,${maxLength}})`,
PORT: "(:\\d+\\b)?",
PATH_UNI: "([/?#]\\S*)?",
PATH: "([/?#][\\w-.~!$&*+;=:@%/?#(),'\\[\\]]*)?"
},
TLD_TABLE = table;
function regexEscape(text) {
return text.replace(/[[\]\\^-]/g, "\\$&");
}
function buildRegex({
unicode = false, customRules = [], standalone = false,
boundaryLeft, boundaryRight
}) {
var pattern = RE.PROTOCOL + RE.USER;
if (unicode) {
pattern += RE.DOMAIN_UNI + RE.PORT + RE.PATH_UNI;
} else {
pattern += RE.DOMAIN + RE.PORT + RE.PATH;
}
if (customRules.length) {
pattern = "(?:(" + customRules.join("|") + ")|" + pattern + ")";
} else {
pattern = "()" + pattern;
}
var prefix, suffix, invalidSuffix;
if (standalone) {
if (boundaryLeft) {
prefix = "((?:^|\\s)[" + regexEscape(boundaryLeft) + "]*?)";
} else {
prefix = "(^|\\s)";
}
if (boundaryRight) {
suffix = "([" + regexEscape(boundaryRight) + "]*(?:$|\\s))";
} else {
suffix = "($|\\s)";
}
invalidSuffix = "[^\\s" + regexEscape(boundaryRight) + "]";
} else {
prefix = "(^|\\b|_)";
suffix = "()";
}
pattern = prefix + pattern + suffix;
return {
url: new RegExp(pattern, "igm"),
invalidSuffix: invalidSuffix && new RegExp(invalidSuffix),
mustache: /\{\{[\s\S]+?\}\}/g
};
}
function pathStrip(m, re, repl) {
var s = m.path.replace(re, repl);
if (s == m.path) return;
m.end -= m.path.length - s.length;
m.suffix = m.path.slice(s.length) + m.suffix;
m.path = s;
}
function pathStripQuote(m, c) {
var i = 0, s = m.path, end, pos = 0;
if (!s.endsWith(c)) return;
while ((pos = s.indexOf(c, pos)) >= 0) {
if (i % 2) {
end = null;
} else {
end = pos;
}
pos++;
i++;
}
if (!end) return;
m.end -= s.length - end;
m.path = s.slice(0, end);
m.suffix = s.slice(end) + m.suffix;
}
function pathStripBrace(m, left, right) {
var str = m.path,
re = new RegExp("[\\" + left + "\\" + right + "]", "g"),
match, count = 0, end;
// Match loop
while ((match = re.exec(str))) {
if (count % 2 == 0) {
end = match.index;
if (match[0] == right) {
break;
}
} else {
if (match[0] == left) {
break;
}
}
count++;
}
if (!match && count % 2 == 0) {
return;
}
m.end -= m.path.length - end;
m.path = str.slice(0, end);
m.suffix = str.slice(end) + m.suffix;
}
function isIP(s) {
var m, i;
if (!(m = s.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/))) {
return false;
}
for (i = 1; i < m.length; i++) {
if (+m[i] > 255 || (m[i].length > 1 && m[i][0] == "0")) {
return false;
}
}
return true;
}
function inTLDS(domain) {
var match = domain.match(/\.([^.]+)$/);
if (!match) {
return false;
}
var key = match[1].toLowerCase();
// eslint-disable-next-line no-prototype-builtins
return TLD_TABLE.hasOwnProperty(key);
}
class UrlMatcher {
constructor(options = {}) {
this.options = options;
this.regex = buildRegex(options);
}
*match(text) {
var {
fuzzyIp = true,
ignoreMustache = false,
mail = true
} = this.options,
{
url,
invalidSuffix,
mustache
} = this.regex,
urlLastIndex, mustacheLastIndex;
mustache.lastIndex = 0;
url.lastIndex = 0;
var mustacheMatch, mustacheRange;
if (ignoreMustache) {
mustacheMatch = mustache.exec(text);
if (mustacheMatch) {
mustacheRange = {
start: mustacheMatch.index,
end: mustache.lastIndex
};
}
}
var urlMatch;
while ((urlMatch = url.exec(text))) {
const result = {
start: 0,
end: 0,
text: "",
url: "",
prefix: urlMatch[1],
custom: urlMatch[2],
protocol: urlMatch[3],
auth: urlMatch[4] || "",
domain: urlMatch[5],
port: urlMatch[6] || "",
path: urlMatch[7] || "",
suffix: urlMatch[8]
};
if (result.custom) {
result.start = urlMatch.index;
result.end = url.lastIndex;
result.text = result.url = urlMatch[0];
} else {
result.start = urlMatch.index + result.prefix.length;
result.end = url.lastIndex - result.suffix.length;
}
if (mustacheRange && mustacheRange.end <= result.start) {
mustacheMatch = mustache.exec(text);
if (mustacheMatch) {
mustacheRange.start = mustacheMatch.index;
mustacheRange.end = mustache.lastIndex;
} else {
mustacheRange = null;
}
}
// ignore urls inside mustache pair
if (mustacheRange && result.start < mustacheRange.end && result.end >= mustacheRange.start) {
continue;
}
if (!result.custom) {
// adjust path and suffix
if (result.path) {
// Strip BBCode
pathStrip(result, /\[\/?(b|i|u|url|img|quote|code|size|color)\].*/i, "");
// Strip braces
pathStripBrace(result, "(", ")");
pathStripBrace(result, "[", "]");
pathStripBrace(result, "{", "}");
// Strip quotes
pathStripQuote(result, "'");
pathStripQuote(result, '"');
// Remove trailing ".,?"
pathStrip(result, /(^|[^-_])[.,?]+$/, "$1");
}
// check suffix
if (invalidSuffix && invalidSuffix.test(result.suffix)) {
if (/\s$/.test(result.suffix)) {
url.lastIndex--;
}
continue;
}
// ignore fuzzy ip
if (!fuzzyIp && isIP(result.domain) &&
!result.protocol && !result.auth && !result.path) {
continue;
}
// mailto protocol
if (!result.protocol && result.auth) {
var matchMail = result.auth.match(/^mailto:(.+)/);
if (matchMail) {
result.protocol = "mailto:";
result.auth = matchMail[1];
}
}
// http alias
if (result.protocol && result.protocol.match(/^(hxxp|h\*\*p|ttp)/)) {
result.protocol = "http://";
}
// guess protocol
if (!result.protocol) {
var domainMatch;
if ((domainMatch = result.domain.match(/^(ftp|irc)/))) {
result.protocol = domainMatch[0] + "://";
} else if (result.domain.match(/^(www|web)/)) {
result.protocol = "http://";
} else if (result.auth && result.auth.indexOf(":") < 0 && !result.path) {
result.protocol = "mailto:";
} else {
result.protocol = "http://";
}
}
// ignore mail
if (!mail && result.protocol === "mailto:") {
continue;
}
// verify domain
if (!isIP(result.domain)) {
if (/^(http|https|mailto)/.test(result.protocol) && !inTLDS(result.domain)) {
continue;
}
const invalidLabel = getInvalidLabel(result.domain);
if (invalidLabel) {
url.lastIndex = urlMatch.index + invalidLabel.index + 1;
continue;
}
}
// Create URL
result.url = result.protocol + (result.auth && result.auth + "@") + result.domain + result.port + result.path;
result.text = text.slice(result.start, result.end);
}
// since regex is shared with other parse generators, cache lastIndex position and restore later
mustacheLastIndex = mustache.lastIndex;
urlLastIndex = url.lastIndex;
yield result;
url.lastIndex = urlLastIndex;
mustache.lastIndex = mustacheLastIndex;
}
}
}
function getInvalidLabel(domain) {
// https://tools.ietf.org/html/rfc1035
// https://serverfault.com/questions/638260/is-it-valid-for-a-hostname-to-start-with-a-digit
let index = 0;
const parts = domain.split(".");
for (const part of parts) {
if (
!part ||
part.startsWith("-") ||
part.endsWith("-")
) {
return {
index,
value: part
};
}
index += part.length + 1;
}
}
/* eslint-env browser */
var INVALID_TAGS = {
a: true,
noscript: true,
option: true,
script: true,
style: true,
textarea: true,
svg: true,
canvas: true,
button: true,
select: true,
template: true,
meter: true,
progress: true,
math: true,
time: true
};
class Pos {
constructor(container, offset, i = 0) {
this.container = container;
this.offset = offset;
this.i = i;
}
add(change) {
var cont = this.container,
offset = this.offset;
this.i += change;
// If the container is #text.parentNode
if (cont.childNodes.length) {
cont = cont.childNodes[offset];
offset = 0;
}
// If the container is #text
while (cont) {
if (cont.nodeType == 3) {
if (!cont.LEN) {
cont.LEN = cont.nodeValue.length;
}
if (offset + change <= cont.LEN) {
this.container = cont;
this.offset = offset + change;
return;
}
change = offset + change - cont.LEN;
offset = 0;
}
cont = cont.nextSibling;
}
}
moveTo(offset) {
this.add(offset - this.i);
}
}
function cloneContents(range) {
if (range.startContainer == range.endContainer) {
return document.createTextNode(range.toString());
}
return range.cloneContents();
}
var DEFAULT_OPTIONS = {
maxRunTime: 100,
timeout: 10000,
newTab: true,
noOpener: true,
embedImage: true,
recursive: true,
};
class Linkifier extends Events {
constructor(root, options = {}) {
super();
if (!(root instanceof Node)) {
options = root;
root = options.root;
}
this.root = root;
this.options = Object.assign({}, DEFAULT_OPTIONS, options);
this.aborted = false;
}
start() {
var time = Date.now,
startTime = time(),
chunks = this.generateChunks();
var next = () => {
if (this.aborted) {
this.emit("error", new Error("Aborted"));
return;
}
var chunkStart = time(),
now;
do {
if (chunks.next().done) {
this.emit("complete", time() - startTime);
return;
}
} while ((now = time()) - chunkStart < this.options.maxRunTime);
if (now - startTime > this.options.timeout) {
this.emit("error", new Error(`max execution time exceeded: ${now - startTime}, on ${this.root}`));
return;
}
setTimeout(next);
};
setTimeout(next);
}
abort() {
this.aborted = true;
}
*generateRanges() {
var {validator, recursive} = this.options;
var filter = {
acceptNode: function(node) {
if (validator && !validator(node)) {
return NodeFilter.FILTER_REJECT;
}
if (INVALID_TAGS[node.localName]) {
return NodeFilter.FILTER_REJECT;
}
if (node.localName == "wbr") {
return NodeFilter.FILTER_ACCEPT;
}
if (node.nodeType == 3) {
return NodeFilter.FILTER_ACCEPT;
}
return recursive ? NodeFilter.FILTER_SKIP : NodeFilter.FILTER_REJECT;
}
};
// Generate linkified ranges.
var walker = document.createTreeWalker(
this.root,
NodeFilter.SHOW_TEXT + NodeFilter.SHOW_ELEMENT,
filter
), start, end, current, range;
end = start = walker.nextNode();
if (!start) {
return;
}
range = document.createRange();
range.setStartBefore(start);
while ((current = walker.nextNode())) {
if (end.nextSibling == current) {
end = current;
continue;
}
range.setEndAfter(end);
yield range;
end = start = current;
range.setStartBefore(start);
}
range.setEndAfter(end);
yield range;
}
*generateChunks() {
var {matcher} = this.options;
for (var range of this.generateRanges()) {
var frag = null,
pos = null,
text = range.toString(),
textRange = null;
for (var result of matcher.match(text)) {
if (!frag) {
frag = document.createDocumentFragment();
pos = new Pos(range.startContainer, range.startOffset);
textRange = range.cloneRange();
}
// clone text
pos.moveTo(result.start);
textRange.setEnd(pos.container, pos.offset);
frag.appendChild(cloneContents(textRange));
// clone link
textRange.collapse();
pos.moveTo(result.end);
textRange.setEnd(pos.container, pos.offset);
var content = cloneContents(textRange),
link = this.buildLink(result, content);
textRange.collapse();
frag.appendChild(link);
this.emit("link", {link, range, result, content});
}
if (pos) {
pos.moveTo(text.length);
textRange.setEnd(pos.container, pos.offset);
frag.appendChild(cloneContents(textRange));
range.deleteContents();
range.insertNode(frag);
}
yield;
}
}
buildLink(result, content) {
var {newTab, embedImage, noOpener} = this.options;
var link = document.createElement("a");
link.href = result.url;
link.title = "Linkify Plus Plus";
link.className = "linkifyplus";
if (newTab) {
link.target = "_blank";
}
if (noOpener) {
link.rel = "noopener";
}
var child;
if (embedImage && /^[^?#]+\.(?:jpg|jpeg|png|apng|gif|svg|webp)(?:$|[?#])/i.test(result.url)) {
child = new Image;
child.src = result.url;
child.alt = result.text;
} else {
child = content;
}
link.appendChild(child);
return link;
}
}
function linkify(...args) {
return new Promise((resolve, reject) => {
var linkifier = new Linkifier(...args);
linkifier.on("error", reject);
linkifier.on("complete", resolve);
for (var key of Object.keys(linkifier.options)) {
if (key.startsWith("on")) {
linkifier.on(key.slice(2), linkifier.options[key]);
}
}
linkifier.start();
});
}
const processedNodes = new WeakSet;
const nodeValidationCache = new WeakMap; // Node -> boolean
function validRoot(node, validator) {
if (processedNodes.has(node)) {
return false;
}
return getValidation(node);
function getValidation(p) {
if (!p.parentNode) {
return false;
}
let r = nodeValidationCache.get(p);
if (r === undefined) {
if (validator.isIncluded(p)) {
r = true;
} else if (validator.isExcluded(p)) {
r = false;
} else if (p.parentNode != document.documentElement) {
r = getValidation(p.parentNode);
} else {
r = true;
}
nodeValidationCache.set(p, r);
}
return r;
}
}
function prepareDocument() {
// wait till everything is ready
return prepareBody().then(prepareApp);
function prepareApp() {
const appRoot = document.querySelector("[data-server-rendered]");
if (!appRoot) {
return;
}
return new Promise(resolve => {
const onChange = () => {
if (!appRoot.hasAttribute("data-server-rendered")) {
resolve();
observer.disconnect();
}
};
const observer = new MutationObserver(onChange);
observer.observe(appRoot, {attributes: true});
});
}
function prepareBody() {
if (document.readyState !== "loading") {
return Promise.resolve();
}
return new Promise(resolve => {
// https://github.com/Tampermonkey/tampermonkey/issues/485
document.addEventListener("DOMContentLoaded", resolve, {once: true});
});
}
}
var load = {
key: "triggerByPageLoad",
enable: async options => {
await prepareDocument();
processedNodes.add(document.body);
await linkify({...options, root: document.body, recursive: true});
},
disable: () => {}
};
let options$1;
const EVENTS$1 = [
["click", handle$1, {passive: true}],
];
function handle$1(e) {
const el = e.target;
if (validRoot(el, options$1.validator)) {
processedNodes.add(el);
linkify({...options$1, root: el, recursive: false});
}
}
function enable$2(_options) {
options$1 = _options;
for (const [event, handler, options] of EVENTS$1) {
document.addEventListener(event, handler, options);
}
}
function disable$2() {
for (const [event, handler, options] of EVENTS$1) {
document.removeEventListener(event, handler, options);
}
}
var click = {
key: "triggerByClick",
enable: enable$2,
disable: disable$2
};
let options;
const EVENTS = [
// catch the first mousemove event since mouseover doesn't fire at page refresh
["mousemove", handle, {passive: true, once: true}],
["mouseover", handle, {passive: true}]
];
function handle(e) {
const el = e.target;
if (validRoot(el, options.validator)) {
processedNodes.add(el);
linkify({...options, root: el, recursive: false});
}
}
function enable$1(_options) {
options = _options;
for (const [event, handler, options] of EVENTS) {
document.addEventListener(event, handler, options);
}
}
function disable$1() {
for (const [event, handler, options] of EVENTS) {
document.removeEventListener(event, handler, options);
}
}
var hover = {
key: "triggerByHover",
enable: enable$1,
disable: disable$1
};
const MAX_PROCESSES = 100;
let processes = 0;
let observer;
async function enable(options) {
await prepareDocument();
observer = new MutationObserver(function(mutations){
// Filter out mutations generated by LPP
var lastRecord = mutations[mutations.length - 1],
nodes = lastRecord.addedNodes,
i;
if (nodes.length >= 2) {
for (i = 0; i < 2; i++) {
if (nodes[i].className == "linkifyplus") {
return;
}
}
}
for (var record of mutations) {
for (const node of record.addedNodes) {
if (node.nodeType === 1 && !processedNodes.has(node)) {
if (processes >= MAX_PROCESSES) {
throw new Error("Too many processes");
}
if (processedNodes.has(node)) {
continue;
}
processedNodes.add(node);
processes++;
linkify({...options, root: node, recursive: true})
.finally(() => {
processes--;
});
}
}
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
async function disable() {
await prepareDocument();
observer && observer.disconnect();
}
var mutation = {
key: "triggerByNewNode",
enable,
disable
};
var triggers = [load, click, hover, mutation];
function createValidator({includeElement, excludeElement}) {
const f = function(node) {
if (processedNodes.has(node)) {
return false;
}
if (node.isContentEditable) {
return false;
}
if (node.matches) {
if (includeElement && node.matches(includeElement)) {
return true;
}
if (excludeElement && node.matches(excludeElement)) {
return false;
}
}
return true;
};
f.isIncluded = node => {
return includeElement && node.matches(includeElement);
};
f.isExcluded = node => {
if (INVALID_TAGS[node.localName]) {
return true;
}
return excludeElement && node.matches(excludeElement);
};
return f;
}
function stringToList(value) {
value = value.trim();
if (!value) {
return [];
}
return value.split(/\s*\n\s*/g);
}
function createOptions(pref) {
const options = {};
pref.on("change", update);
update(pref.getAll());
return options;
function update(changes) {
Object.assign(options, changes);
options.validator = createValidator(options);
if (typeof options.customRules === "string") {
options.customRules = stringToList(options.customRules);
}
options.matcher = new UrlMatcher(options);
options.onlink = options.embedImageExcludeElement ? onlink : null;
}
function onlink({link, range, content}) {
if (link.childNodes[0].localName !== "img" || !options.embedImageExcludeElement) {
return;
}
var parent = range.startContainer;
// it might be a text node
if (!parent.closest) {
parent = parent.parentNode;
}
if (!parent.closest(options.embedImageExcludeElement)) return;
// remove image
link.innerHTML = "";
link.appendChild(content);
}
}
async function startLinkifyPlusPlus(getPref) {
// Limit contentType to specific content type
if (
document.contentType &&
!["text/plain", "text/html", "application/xhtml+xml"].includes(document.contentType)
) {
return;
}
const pref = await getPref();
const options = createOptions(pref);
for (const trigger of triggers) {
if (pref.get(trigger.key)) {
trigger.enable(options);
}
}
pref.on("change", changes => {
for (const trigger of triggers) {
if (changes[trigger.key] === true) {
trigger.enable(options);
}
if (changes[trigger.key] === false) {
trigger.disable();
}
}
});
}
function getMessageFactory() {
return (key, params) => {
if (!params) {
return translate[key];
}
if (!Array.isArray(params)) {
params = [params];
}
return translate[key].replace(/\$\d/g, m => {
const index = Number(m.slice(1));
return params[index - 1];
});
};
}
startLinkifyPlusPlus(async () => {
const getMessage = getMessageFactory();
const pref = GM_webextPref({
default: prefDefault(),
body: prefBody(getMessage),
getMessage,
getNewScope: () => location.hostname
});
await pref.ready();
await pref.setCurrentScope(location.hostname);
return pref;
});