PttChrome Add-on (Ptt)

new features for PttChrome (show flags features code by osk2/ptt-comment-flag)

目前為 2018-09-25 提交的版本,檢視 最新版本

// ==UserScript==
// @name         PttChrome Add-on (Ptt)
// @namespace    https://greasyfork.org/zh-TW/scripts/372391-pttchrome-add-on-ptt
// @description  new features for PttChrome (show flags features code by osk2/ptt-comment-flag)
// @version      1.2
// @author       avan
// @match        iamchucky.github.io/PttChrome/*
// @require      https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/tippy.js/2.5.4/tippy.min.js
// @require      https://greasyfork.org/scripts/372458-flags/code/Flags.js?version=630928
// @require      https://openuserjs.org/src/libs/sizzle/GM_config.js
// @grant        GM_getValue
// @grant        GM_setValue
// ==/UserScript==
let configStatus = false;
const gmc = new GM_configStruct({
	'id': 'PttChromeAddOnConfig', // The id used for this instance of GM_config
	'title': 'PttChrome Add-on Settings', // Panel Title
	'fields': { // Fields object
		'isHideAll': {
			'label': '是否隱藏黑名單推文', // Appears next to field
			'type': 'checkbox', // Makes this setting a checkbox input
			'default': false // Default value if user doesn't change it
		},
		'isHideViewImg': {
			'label': '是否隱藏黑名單圖片預覽', // Appears next to field
			'type': 'checkbox', // Makes this setting a checkbox input
			'default': true // Default value if user doesn't change it
		},
		'isHideViewVideo': {
			'label': '是否隱藏黑名單影片預覽', // Appears next to field
			'type': 'checkbox', // Makes this setting a checkbox input
			'default': true // Default value if user doesn't change it
		},
		'isReduceHeight': {
			'label': '是否調降黑名單推文高度', // Appears next to field
			'type': 'checkbox', // Makes this setting a checkbox input
			'default': true // Default value if user doesn't change it
		},
		'reduceHeight': {
			'label': '設定高度值(單位em)', // Appears next to field
			'type': 'float', // Makes this setting a text input
			'min': 0, // Optional lower range limit
			'max': 10, // Optional upper range limit
			'default': 0.4 // Default value if user doesn't change it
		},
		'isReduceOpacity': {
			'label': '是否調降黑名單推文透明值', // Appears next to field
			'type': 'checkbox', // Makes this setting a checkbox input
			'default': false // Default value if user doesn't change it
		},
		'reduceOpacity': {
			'label': '設定透明值', // Appears next to field
			'type': 'float', // Makes this setting a text input
			'min': 0, // Optional lower range limit
			'max': 10, // Optional upper range limit
			'default': 0.1 // Default value if user doesn't change it
		},
		'isAddFloorNum': {
			'label': '是否顯示推文樓層', // Appears next to field
			'type': 'checkbox', // Makes this setting a checkbox input
			'default': true // Default value if user doesn't change it
		},
		'isShowFlags': {
			'label': '看板內若有IP(ex.Gossiping),是否依IP顯示國旗', // Appears next to field
			'type': 'checkbox', // Makes this setting a checkbox input
			'default': true // Default value if user doesn't change it
		},
	},
	'events': { // Callback functions object
		'init': function() { console.log('onInit()'); },
		'open': function() {
			this.frame.setAttribute('style', "border: 1px solid #AAA;color: #999;background-color: #111; width: 23em; height: 27em; position: fixed; top: 2.5em; right: 0.5em; z-index: 9999;");
			//gmc.open();
			configStatus = true;
			console.log('onOpen()');
		},
		'save': function() { console.log('onSave()'); },
		'close': function() {
			//gmc.close();
			configStatus = false;
			console.log('onClose()');
		},
		'reset': function() { console.log('onReset()'); }
	},
	'css': `#PttChromeAddOnConfig * { color: #999 !important;background-color: #111 !important; }
			body#PttChromeAddOnConfig { background-color: #111}
`
});

const HOST = 'https://osk2.me:9977',
	  ipValidation = /((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/,
	  timerArray = [];

let timestamp = Math.floor(Date.now() / 1000);

const execInterval = () => {
	if (timerArray.length === 0) {
		excute();
		timerArray.push(setInterval(excute, 3000));
	}
}
const stopInterval = () => {
	while (timerArray.length > 0) {
		clearInterval(timerArray .shift());
	}
}
function css(elements, styles) {
	elements = elements.length ? elements : [elements];
	elements.forEach(element => {
		for (var property in styles) {
			element.style[property] = styles[property];
		}
	});
}
const find = (elements, selectors) => {
	let rtnElements = [];
	elements = elements.length ? elements : [elements];
	elements.forEach(element => rtnElements.push.apply(rtnElements, element.querySelectorAll(selectors)));
	return rtnElements;
}
const show = (elements, specifiedDisplay = 'block') => {
	elements = elements.length ? elements : [elements];
	elements.forEach(element => {
		if (!element.style) return;
		element.style.display = specifiedDisplay;
	});
}
const hide = (elements) => {
	elements = elements.length ? elements : [elements];
	elements.forEach(element => {
		if (!element.style) return;
		element.style.display = 'none';
	});
}
const generateImageHTML = (ip, flag) => {
	const countryCode = flag.imagePath.toLowerCase().replace('assets/','').replace('.png','');
	const imagePath = flag.imagePath ? `${Flags[countryCode]}` : null;
	const imageTitile = `${flag.locationName || 'N/A'}<br><a href='https://www.google.com/search?q=${ip}' target='_blank'>${ip}</a>`;

	if (!imagePath) {
		return;
	}
	return `<div data-flag title="${imageTitile}" style="background-image: url('${imagePath}');background-repeat:no-repeat;background-position:left;float:right;height:0.8em;width:0.8em;cursor:pointer !important;"></div>`;
};
const excute = async () => {
	console.log("do excute");
	let authorNode = document.querySelector('span.q2');
	if (authorNode && authorNode.innerHTML.length > 10) authorNode = authorNode.parentNode;
	else return;

	let blackSpan = document.querySelectorAll('span[style="opacity:0.2"]');
	if (blackSpan.length > 0) {
		if (gmc.get('isHideAll')) {
			hide(blackSpan);
		} else {
			gmc.get('isHideViewImg') && hide(find(blackSpan, 'img:not([style*="display: none"])'));
			gmc.get('isHideViewVideo') && hide(find(blackSpan, '.easyReadingVideo:not([style*="display: none"])'));
			gmc.get('isReduceHeight') && css(blackSpan, {
				'height': gmc.get('reduceHeight') + 'em',
				'font-size': (gmc.get('reduceHeight')/2) + 'em',
				'line-height': gmc.get('reduceHeight') + 'em'
			});
			gmc.get('isReduceOpacity') && css(blackSpan, {'opacity': gmc.get('reduceOpacity')});
		}
	}

	const currentTS = Math.floor(Date.now() / 1000),
		  q2Nodes = document.querySelectorAll('span.q2'),
		  authorComment = [].filter.call(q2Nodes, n => !n.innerHTML.match(/data-flag/) && n.innerHTML.match(ipValidation)),
		  authorIpList = authorComment.map(c => c.innerHTML.match(ipValidation)[0]);
	if (gmc.get('isShowFlags') && authorIpList.length > 0) {
		const authorFlagsResponse = await axios.post(`${HOST}/ip`, { ip: authorIpList }),
			  authorFlags = authorFlagsResponse.data;

		authorComment.forEach((comment, index) => {
			const ip = comment.innerHTML.match(ipValidation)[0];
			const imageHTML = generateImageHTML(ip, authorFlags[index]);

			if (!imageHTML) return;
			comment.innerHTML = imageHTML + comment.innerHTML.trim();
		});
	}


	const firstEl = (element) => {
		if (!element) return;
		element = element.nextElementSibling;
		if (!element) return;
		if (element.classList.toString().startsWith("blu_")) {
			return element;
		} else {
			return firstEl(element);
		}
	}

	let firstIdx = -1, commentNodes = document.querySelectorAll('span[type="bbsrow"][class^="blu_"]');
	commentNodes = [].map.call(commentNodes, element => element);
	commentNodes = commentNodes.filter((element, index) => {
		if ((!element.innerHTML.match(/data-flag/) && !element.innerHTML.match(/data-floor/)) && element === firstEl(authorNode)) firstIdx = index;
		if (firstIdx > -1) return firstIdx <= index;
	});
	let hasIP = false;
	const commentIpList = commentNodes.map(c => {
		if (c.innerHTML.match(ipValidation)) {
			hasIP = true;
			return c.innerHTML.match(ipValidation)[0];
		}
	});
	let commentFlags = null;
	if (gmc.get('isShowFlags') && hasIP && commentIpList.length > 0) {
		const commentFlagsResponse = await axios.post(`${HOST}/ip`, { ip: commentIpList });
		commentFlags = commentFlagsResponse.data;
	}

	commentNodes.forEach((comment, index) => {
		if (gmc.get('isAddFloorNum')) {
			const divCnt = `<div data-floor style="float:left;margin-left: 2.2%;height: 0em;width: 1.5em;font-size: 0.4em;font-weight:bold;text-align: right;">${index+1}</div>`;
			comment.innerHTML = divCnt + comment.innerHTML.trim();
		}

		if (!gmc.get('isShowFlags') || !hasIP) return;

		const commentIp = comment.innerHTML.match(ipValidation);
		if (!commentIp) return;

		const imageHTML = generateImageHTML(commentIp[0], commentFlags[index]);
		if (imageHTML) {
			comment.innerHTML = imageHTML + comment.innerHTML.trim();
			timestamp = Math.floor(Date.now() / 1000);
		}
	});

	tippy('[data-flag]', {
		arrow: true,
		size: 'large',
		placement: 'left',
		interactive: true
	});

	if ((currentTS - timestamp) > 3) {
		stopInterval();
	}
};

try {
	(function() {
		'use strict';
		const container = document.querySelector('#mainContainer');
		if (container && container.addEventListener) {
			container.addEventListener ('DOMSubtreeModified', execInterval, false);
		}

		const container1 = document.querySelector('#mainContainer > span');
		if (container1 && container1.addEventListener) {
			container1.addEventListener ('DOMSubtreeModified', () => console.log("test container1!"), false);
		}
	})();
} catch (ex) {
	console.error(ex);
}
var _button = document.createElement("div");
_button.innerHTML = 'Settings';
_button.onclick = function(event){
	if (!configStatus) {
		configStatus = true;
		gmc.open();
	} else if (configStatus) {
		configStatus = false;
		gmc.close();
	}
	event.preventDefault();
	event.stopPropagation();
	return false;
}
_button.style = "border: 1px solid #AAA;color: #999;background-color: #111;position: fixed; top: 0.5em; right: 0.5em; z-index: 9999;cursor:pointer !important;"

document.body.appendChild(_button)

const el = document.createElement('link');
el.rel = 'stylesheet';
el.type = 'text/css';
el.href = "https://cdnjs.cloudflare.com/ajax/libs/tippy.js/2.5.4/tippy.css";
document.head.appendChild(el);