// ==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.4
// @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
},
'whenHideAllShowInfo': {
'label': '當隱藏黑名單推文顯示提示訊息', // Appears next to field
'type': 'text', // Makes this setting a text input
'size': 35, // Limit length of input (default is 25)
'default': '<本文作者已被列黑名單>' // 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
//'open': function() {
'open': () => {
gmc.frame.setAttribute('style', "border: 1px solid #AAA;color: #999;background-color: #111; width: 23em; height: 30em; position: fixed; top: 2.5em; right: 0.5em; z-index: 9999;");
configStatus = true;
},
'close': () => { configStatus = false;},
},
'css': `#PttChromeAddOnConfig * { color: #999 !important;background-color: #111 !important; }
body#PttChromeAddOnConfig { background-color: #111}`
});
const HOST = 'https://osk2.me:9977',
ipValidation = /(\d{1,3}\.){3}\d{1,3}/,
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());
}
}
const 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");
const currentTS = Math.floor(Date.now() / 1000);
if ((currentTS - timestamp) > 2) {
stopInterval();
}
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"]');
let whenHideAllShowInfoCss = document.querySelector('#whenHideAllShowInfo');
if (blackSpan.length > 0) {
if (gmc.get('isHideAll')) {
if (gmc.get('whenHideAllShowInfo').length > 0) {
if (!whenHideAllShowInfoCss) {
const cssLinkEl = document.createElement('link');
cssLinkEl.setAttribute('rel', 'stylesheet');
cssLinkEl.setAttribute('id', 'whenHideAllShowInfo');
cssLinkEl.setAttribute('type', 'text/css');
cssLinkEl.setAttribute('href', 'data:text/css;charset=UTF-8,' + encodeURIComponent(`
span[type="bbsrow"][style="opacity:0.2"] {opacity:1 !important;visibility: hidden;}
span[type="bbsrow"][style="opacity:0.2"]:before {
visibility: visible;color: darkred;
content: ' - ${gmc.get('whenHideAllShowInfo')}';
}
`));
document.head.appendChild(cssLinkEl);
}
} else {
if (whenHideAllShowInfoCss) {
whenHideAllShowInfoCss.remove();
}
hide(blackSpan);
}
} else {
if (whenHideAllShowInfoCss) {
whenHideAllShowInfoCss.remove();
}
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')});
}
}
let authorNodes = document.querySelectorAll('span.q2');
authorNodes = [].filter.call(authorNodes, (element, index) => {
return !element.innerHTML.match(/data-flag/) && element.innerHTML.match(ipValidation)
});
const authorIpList = authorNodes.map(c => {
const ip = c.innerHTML.match(ipValidation);
if (ip) {
return ip[0];
}
});
if (gmc.get('isShowFlags') && authorIpList.length > 0) {
const authorFlagsResponse = await axios.post(`${HOST}/ip`, { ip: authorIpList}, {
headers: {
'Content-Type': 'application/json',
}
}),
authorFlags = authorFlagsResponse.data;
authorNodes.forEach((comment, index) => {
const ip = comment.innerHTML.match(ipValidation);
if (!ip || !authorFlags) {
return;
}
const imageHTML = generateImageHTML(ip[0], authorFlags[index]);
if (!imageHTML) return;
comment.innerHTML = imageHTML + comment.innerHTML.trim();
timestamp = Math.floor(Date.now() / 1000);
});
}
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 = [].filter.call(commentNodes, (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 => {
const ip = c.innerHTML.match(ipValidation);
if (ip) {
hasIP = true;
return ip[0];
}
});
let commentFlags = null;
if (gmc.get('isShowFlags') && hasIP && commentIpList.length > 0) {
const commentFlagsResponse = await axios.post(`${HOST}/ip`, { ip: commentIpList }, {
headers: {
'Content-Type': 'application/json',
}
});
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 || !commentFlags) 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
});
};
const CreateMutationObserver = () => {
const container = document.querySelector('#mainContainer');
if (!container) {
setTimeout(CreateMutationObserver, 2000);
return;
}
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
const checkNode = document.querySelector('span.q2');
if (checkNode && checkNode.innerHTML.length > 10) {
execInterval();
}
});
})
observer.observe(container, {childList: true,});
}
try {
window.addEventListener("load", function(event) {
CreateMutationObserver();
});
/* (() => {
MutationObserver();
})(); */
} catch (ex) {
console.error(ex);
}
const _button = document.createElement("div");
_button.innerHTML = 'Settings';
_button.onclick = 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);