Greasy Fork

网站图标获取

获取当前网站的图标

目前为 2022-11-12 提交的版本。查看 最新版本

/* 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.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
// @noframes
// ==/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;
		}
	}
})();