/* eslint-disable no-multi-spaces */
// ==UserScript==
// @name Website icon getter
// @name:zh-CN 网站图标获取
// @name:en Website icon getter
// @namespace Website-icon-getter
// @version 0.1
// @description Get the icon of current tab
// @description:zh-CN 获取当前网站的图标
// @description:en Get the icon of current tab
// @author PY-DNG
// @license GPL-v3
// @match http*://*/*
// @icon none
// @grant GM_registerMenuCommand
// @grant GM_setClipboard
// ==/UserScript==
(function __MAIN__() {
'use strict';
const CONST = {
Text_AllLang: {
DEFAULT: 'en',
'en': {
CopyIconUrl: 'Copy icon url of current tab',
OpenIconInNewTab: 'Open icon in new tab',
CopyIconBase64: 'Copy icon url of current tab in Base64 format',
DownloadIcon: 'Download icon of current tab',
Download_FileName: 'Icon - {Host}.ico',
},
'zh': {
CopyIconUrl: '复制当前标签页图标地址',
OpenIconInNewTab: '在新标签页查看图标',
CopyIconBase64: '复制Base64格式的当前标签页图标地址',
DownloadIcon: '下载当前标签页图标',
Download_FileName: 'Icon - {Host}.ico',
}
}
};
const i18n = navigator.language.split('-')[0] || CONST.Text.DEFAULT;
CONST.Text = CONST.Text_AllLang[i18n];
GM_registerMenuCommand(CONST.Text.CopyIconUrl, copyIconUrl);
GM_registerMenuCommand(CONST.Text.OpenIconInNewTab, openIcon);
GM_registerMenuCommand(CONST.Text.CopyIconBase64, copyIconBase64);
GM_registerMenuCommand(CONST.Text.DownloadIcon, downloadIcon);
function downloadIcon() {
dl_browser(getIconUrl(), replaceText(CONST.Text.Download_FileName, {'{Host}': getHost()}));
}
function copyIconBase64() {
getImageUrl(getIconUrl(), 0, 0, function(url) {
GM_setClipboard(url, 'text');
});
}
function copyIconUrl() {
GM_setClipboard(getIconUrl(), 'text');
}
function openIcon() {
window.open(getIconUrl());
}
function getIconUrl() {
const head = document.head;
const link = $(head, 'link[rel~="icon"]');
return link ? link.href : getHost() + 'favicon.ico';
}
// Basic functions
// querySelector
function $() {
switch(arguments.length) {
case 2:
return arguments[0].querySelector(arguments[1]);
break;
default:
return document.querySelector(arguments[0]);
}
}
// querySelectorAll
function $All() {
switch(arguments.length) {
case 2:
return arguments[0].querySelectorAll(arguments[1]);
break;
default:
return document.querySelectorAll(arguments[0]);
}
}
// createElement
function $CrE() {
switch(arguments.length) {
case 2:
return arguments[0].createElement(arguments[1]);
break;
default:
return document.createElement(arguments[0]);
}
}
// get host part from a url(includes '^https://', '/$')
function getHost(url=location.href) {
const match = location.href.match(/https?:\/\/[^\/]+\//);
return match ? match[0] : match;
}
// Get a base64-formatted url of an image
// When image load error occurs, callback will be called without any argument
function getImageUrl(src, fitx, fity, callback, args=[]) {
const image = new Image();
image.setAttribute("crossOrigin",'anonymous');
image.onload = convert;
image.onerror = image.onabort = callback;
image.src = src;
function convert() {
const cvs = $CrE('canvas');
const ctx = cvs.getContext('2d');
let width, height;
if (fitx && fity) {
width = window.innerWidth;
height = window.innerHeight;
} else if (fitx) {
width = window.innerWidth;
height = (width / image.width) * image.height;
} else if (fity) {
height = window.innerHeight;
width = (height / image.height) * image.width;
} else {
width = image.width;
height = image.height;
}
cvs.width = width;
cvs.height = height;
ctx.drawImage(image, 0, 0, width, height);
try {
callback.apply(null, [cvs.toDataURL()].concat(args));
} catch (e) {
DoLog(LogLevel.Error, ['Error at getImageUrl.convert()', e]);
callback();
}
}
}
function dl_browser(url, name) {
const a = $CrE('a');
a.href = url;
a.download = name;
a.click();
}
// Replace model text with no mismatching of replacing replaced text
// e.g. replaceText('aaaabbbbccccdddd', {'a': 'b', 'b': 'c', 'c': 'd', 'd': 'e'}) === 'bbbbccccddddeeee'
// replaceText('abcdAABBAA', {'BB': 'AA', 'AAAAAA': 'This is a trap!'}) === 'abcdAAAAAA'
// replaceText('abcd{AAAA}BB}', {'{AAAA}': '{BB', '{BBBB}': 'This is a trap!'}) === 'abcd{BBBB}'
// replaceText('abcd', {}) === 'abcd'
/* Note:
replaceText will replace in sort of replacer's iterating sort
e.g. currently replaceText('abcdAABBAA', {'BBAA': 'TEXT', 'AABB': 'TEXT'}) === 'abcdAATEXT'
but remember: (As MDN Web Doc said,) Although the keys of an ordinary Object are ordered now, this was
not always the case, and the order is complex. As a result, it's best not to rely on property order.
So, don't expect replaceText will treat replacer key-values in any specific sort. Use replaceText to
replace irrelevance replacer keys only.
*/
function replaceText(text, replacer) {
if (Object.entries(replacer).length === 0) {return text;}
const [models, targets] = Object.entries(replacer);
const len = models.length;
let text_arr = [{text: text, replacable: true}];
for (const [model, target] of Object.entries(replacer)) {
text_arr = replace(text_arr, model, target);
}
return text_arr.map((text_obj) => (text_obj.text)).join('');
function replace(text_arr, model, target) {
const result_arr = [];
for (const text_obj of text_arr) {
if (text_obj.replacable) {
const splited = text_obj.text.split(model);
for (const part of splited) {
result_arr.push({text: part, replacable: true});
result_arr.push({text: target, replacable: false});
}
result_arr.pop();
} else {
result_arr.push(text_obj);
}
}
return result_arr;
}
}
})();