/* 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.4
// @description Get the icon of current tab
// @description:zh-CN 获取当前网站的图标
// @description:en Get the icon of current tab
// @author PY-DNG
// @license GPL-3.0-or-later
// @match http*://*/*
// @match file:///*
// @require https://update.greasyfork.org/scripts/456034/1303041/Basic%20Functions%20%28For%20userscripts%29.js
// @icon none
// @grant GM_registerMenuCommand
// @grant GM_setClipboard
// @grant GM_xmlhttpRequest
// @noframes
// ==/UserScript==
/* global LogLevel DoLog Err $ $All $CrE $AEL $$CrE addStyle detectDom destroyEvent copyProp copyProps parseArgs escJsStr replaceText getUrlArgv dl_browser dl_GM AsyncManager */
(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_AllLang.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()}));
}
async function copyIconBase64() {
const url = await getImageUrl(getIconUrl());
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';
}
// 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
async function getImageUrl(src) {
const blob = await get(src, 'blob');
return toDataURL(blob);
}
async function toDataURL(blob) {
return new Promise((resolve, reject) => {
try {
const reader = new FileReader();
reader.onload = e => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(blob);
} catch(err) {
reject(err);
}
});
}
function get(url, responseType='text') {
return new Promise((resolve, reject) => {
try {
GM_xmlhttpRequest({
method: 'GET',
url, responseType,
onload: resp => resolve(resp.response),
onerror: reject,
onabort: reject,
ontimeout: reject
});
} catch(err) {
reject(err);
}
});
}
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;
}
}
})();