/*
Following code belongs to Pawoo+.
Copyright (C) 2018 Jackson Tan
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// ==UserScript==
// @id PawooPlus
// @name Pawoo+
// @namespace pawoo.plus
// @version 0.1.2
// @description Enhance your Pawoo experience with features from Google+
// @author Jackson Tan
// @match https://pawoo.net/*
// @include https://pawoo.net/*
// @require https://greasyfork.org/scripts/22406-moment-js/code/Momentjs.js?version=142481
// @run-at document-end
// @grant GM_xmlhttpRequest
// ==/UserScript==
'use strict';
GM_addStyle = function (css) {
var head = document.getElementsByTagName('head')[0], style = document.createElement('style');
if (!head) { return }
style.type = 'text/css';
try { style.innerHTML = css } catch (x) { style.innerText = css }
head.appendChild(style);
}
var styles = '.detailed-comments-container {padding-top: 12px} .detailed-comments a {color: rgba(255,255,255,0.6); text-decoration: none; font-weight: bold;} .status__action-bar-button {display: flex;} .detailed-status__favorites, .detailed-status__reblogs, .detailed-status__replies {display: inline-block;font-weight: 500;font-size: 16px;margin-left: 6px;} .status .icon-button {width: 36.67147px!important;}';
function onLoad() {
var host = 'https://pawoo.net';
var statusApi = '/api/v1/statuses/';
var contextApi = '/context';
var favoriteApi = '/favourite';
var unfavoriteApi = '/unfavourite';
var reblogApi = '/reblog';
var unreblogApi = '/unreblog';
var authentication = JSON.parse(document.getElementById('initial-state').innerHTML).meta.access_token;
var streamColClass = "item-list";
var streamColSelector = ".item-list";
var postTagName = 'ARTICLE';
var dataIdAttr = 'data-id';
var statusContentSelector = ".status__content";
var autoLoadIntervalVal = 30000;
moment.locale(document.documentElement.lang);
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
var nodes = Array.prototype.slice.call(mutation.addedNodes);
nodes.forEach(function (node) {
if (node.parentElement && node.parentElement.className === streamColClass) {
batchReplace(node);
} else if (node.parentElement && node.parentElement.tagName === postTagName){
batchReplace(node.parentNode);
}
});
});
});
observer.observe(document.body.querySelectorAll(streamColSelector)[0], {
childList: true,
subtree: true,
attributes: false,
characterData: false,
});
var autoLoadInterval = setInterval(function () {
document.getElementsByClassName(streamColClass)[0].childNodes.forEach(function (item) {
batchReplace(item);
})
}, autoLoadIntervalVal);
GM_addStyle(styles);
function batchReplace(node) {
if (node) {
var dataId = node.getAttribute(dataIdAttr);
getPostStatus(dataId, function (reblogs, favorites) {
if (reblogs !== null && node.querySelector('.fa-retweet')) {
if (node.querySelector('.detailed-status__reblogs__count')) {
var elem = node.querySelector('.detailed-status__reblogs__count');
removeElement(elem);
}
var element = document.createElement('div');
element.setAttribute('class', 'detailed-status__reblogs__count');
element.innerHTML = '<span class="detailed-status__reblogs"><span>' + reblogs + '</span></span>';
node.querySelector('.status__prepend') ? node.querySelectorAll('.fa-retweet')[1].parentNode.appendChild(element) : node.querySelector('.fa-retweet').parentNode.appendChild(element);
}
if (favorites !== null && node.querySelector('.fa-star')) {
if (node.querySelector('.detailed-status__favorites__count')) {
var elem2 = node.querySelector('.detailed-status__favorites__count');
removeElement(elem2);
}
var element2 = document.createElement('div');
element2.setAttribute('class', 'detailed-status__favorites__count');
element2.innerHTML = '<span class="detailed-status__favorites"><span>' + favorites + '</span></span>';
node.querySelector('.fa-star').parentNode.appendChild(element2);
}
}, function (replies) {
if (replies !== null && (node.querySelector('.fa-reply') || node.querySelector('.fa-reply-all'))) {
if (node.querySelector('.detailed-status__replies__count')) {
var elem = node.querySelector('.detailed-status__replies__count');
removeElement(elem);
}
var element = document.createElement('div');
element.setAttribute('class', 'detailed-status__replies__count');
element.innerHTML = '<span class="detailed-status__replies"><span>' + replies + '</span></span>';
node.querySelector('.fa-reply') ? node.querySelector('.fa-reply').parentNode.appendChild(element) : node.querySelector('.fa-reply-all').parentNode.appendChild(element);
}
}, function (posts) {
if (posts) {
var html = '<div class="detailed-comments">';
posts.forEach(function (item, index) {
var favoriteCountHTML = item.favourites_count > 0 ? '<div style="padding-left: 16px;"><span style="color: #db4437;font-weight: bold;">+' + item.favourites_count + '</span></div>' : '';
var postUrl = item.url;
var commentTimeHTML = '<div style="padding-left: 16px;display: flex;flex-direction: column;justify-content: center;"><span style="color: #606984;"><a href=' + postUrl + '>' + moment(item.created_at).fromNow(true) + '</a></span></div>';
var element = document.createElement('div');
element.innerHTML = item.content;
var contentHTML = processDeleteLine(element.childNodes[0], true);
var postId = item.id;
var favorited = item.favourited;
var favoriteHTML = favorited ? '<div class="detailed-comment-unfavorite-button" style="padding-left: 16px; cursor: pointer;"><span style="color: #db4437;font-weight: bold;">+1</span></div>' : '<div class="detailed-comment-favorite-button" style="padding-left: 16px; cursor: pointer;"><span style="color: rgba(255,255,255,0.6);font-weight: bold;">+1</span></div>';
var reblogged = item.reblogged;
var reblogHTML = reblogged ? '<div class="detailed-comment-unreblog-button" style="padding-left: 16px; cursor: pointer;"><span style="color: #2b90d9;font-weight: bold;">Reshared</span></div>' : '<div class="detailed-comment-reblog-button" style="padding-left: 16px; cursor: pointer;"><span style="color: rgba(255,255,255,0.6);font-weight: bold;">Reshare</span></div>';
html += '<div class="detailed-comment" data-id="' + postId + '"><div style="display: flex;"><div><img src="' + item.account.avatar + '" height=24 width=24 style="border-radius: 50%;"></div><div style="display: flex;flex-direction: row;justify-content: center;padding-left: 6px;"><span style="display: flex;flex-direction: column;justify-content: center;"><a href="' + item.account.url + '">' + item.account.display_name + '</a></span><span class="detailed-comment-username" style="display: flex;flex-direction: column;justify-content: center;color: #606984;padding-left: 4px;">@' + item.account.username + '</span>' + commentTimeHTML + '</div></div><div style="padding-left: 28px;display:flex;"><div>' + contentHTML + '</div>' + favoriteCountHTML + '</div><div style="display: flex; padding-top: 2px;"><span class="reply_inline_comment_button" style="cursor: pointer; padding-left: 28px; color: rgba(255,255,255,0.6);">Reply</span>' + reblogHTML + favoriteHTML + '</div><div class="reply_inline_comment_edit_content" style="display: none; padding-left: 28px; flex-direction: column;"><textarea class="reply_inline_comment_textarea" style="resize: none; background: transparent; color: #FFF;" placeholder="コメントを追加..." aria-label="コメントを追加..." role="textbox"></textarea><div style="display: flex; flex-direction: row;"><div class="reply_inline_comment_submit_button" style="padding: 8px; cursor: pointer;">Submit</div><div class="reply_inline_comment_cancel_button" style="padding: 8px; cursor: pointer;">Cancel</div></div></div></div>'
})
html += '</div>'
if (node.querySelector('.status')) {
if (node.querySelector('.detailed-comments-container')) {
var elem = node.querySelector('.detailed-comments-container');
removeElement(elem);
}
var element = document.createElement('div');
element.setAttribute('class', 'detailed-comments-container');
element.innerHTML = html;
element.querySelectorAll('.detailed-comment') !== null && element.querySelectorAll('.detailed-comment').forEach(function (item, index) {
item.querySelector('.reply_inline_comment_button') !== null && item.querySelector('.reply_inline_comment_button').addEventListener('click', function (e) {
var username = e.target.parentNode.parentNode.querySelector('.detailed-comment-username') !== null ? e.target.parentNode.parentNode.querySelector('.detailed-comment-username').textContent + ' ' : '';
if (e.target.parentNode.parentNode.querySelector('.reply_inline_comment_textarea') !== null) {
e.target.parentNode.parentNode.querySelector('.reply_inline_comment_textarea').value = username;
}
clearInterval(autoLoadInterval);
if (e.target.parentNode.parentNode.querySelector('.reply_inline_comment_edit_content') !== null) {
e.target.parentNode.parentNode.querySelector('.reply_inline_comment_edit_content').style.display = 'flex';
}
})
item.querySelector('.reply_inline_comment_submit_button') !== null && item.querySelector('.reply_inline_comment_submit_button').addEventListener('click', function (e) {
var status = e.target.parentNode.parentNode.querySelector('.reply_inline_comment_textarea').value;
var replyToId = e.target.parentNode.parentNode.parentNode.getAttribute('data-id');
if (status.trim() !== '') {
replyPost(status, replyToId, batchReplace, node);
if (e.target.parentNode.parentNode.querySelector('.reply_inline_comment_textarea') !== null) {
e.target.parentNode.parentNode.querySelector('.reply_inline_comment_textarea').value = '';
}
if (e.target.parentNode.parentNode.parentNode.querySelector('.reply_inline_comment_edit_content') !== null) {
e.target.parentNode.parentNode.parentNode.querySelector('.reply_inline_comment_edit_content').style.display = 'none';
}
setNewInterval();
}
})
item.querySelector('.reply_inline_comment_cancel_button') !== null && item.querySelector('.reply_inline_comment_cancel_button').addEventListener('click', function (e) {
if (e.target.parentNode.parentNode.querySelector('.reply_inline_comment_textarea') !== null) {
e.target.parentNode.parentNode.querySelector('.reply_inline_comment_textarea').value = '';
}
if (e.target.parentNode.parentNode.parentNode.querySelector('.reply_inline_comment_edit_content') !== null) {
e.target.parentNode.parentNode.parentNode.querySelector('.reply_inline_comment_edit_content').style.display = 'none';
}
setNewInterval();
})
item.querySelector('.detailed-comment-favorite-button') !== null && item.querySelector('.detailed-comment-favorite-button').addEventListener('click', function (e) {
var replyToId = e.target.parentNode.parentNode.parentNode.getAttribute('data-id');
favoritePost(replyToId, batchReplace, node);
})
item.querySelector('.detailed-comment-unfavorite-button') !== null && item.querySelector('.detailed-comment-unfavorite-button').addEventListener('click', function (e) {
var replyToId = e.target.parentNode.parentNode.parentNode.getAttribute('data-id');
unfavoritePost(replyToId, batchReplace, node);
})
item.querySelector('.detailed-comment-reblog-button') !== null && item.querySelector('.detailed-comment-reblog-button').addEventListener('click', function (e) {
var replyToId = e.target.parentNode.parentNode.parentNode.getAttribute('data-id');
reblogPost(replyToId, batchReplace, node);
})
item.querySelector('.detailed-comment-unreblog-button') !== null && item.querySelector('.detailed-comment-unreblog-button').addEventListener('click', function (e) {
var replyToId = e.target.parentNode.parentNode.parentNode.getAttribute('data-id');
unreblogPost(replyToId, batchReplace, node);
})
})
node.querySelector('.status').appendChild(element);
}
}
})
processDeleteLine(node, false);
}
}
function processDeleteLine(node, isComment) {
var stack = [];
var result = [];
var commentHTML = '';
var textNode = isComment ? node.childNodes : (node.querySelector(statusContentSelector) !== null ? node.querySelector(statusContentSelector).childNodes : null);
textNode !== null && textNode.forEach(function (item, index) {
var matches = item.textContent.match(/(-)/mgi);
if (matches && matches.length > 1) {
var indicies = charLocations('-', item.textContent);
if (indicies.length > 1) {
var newText = item.textContent;
var charsToReplace = indicies.length % 2 === 0 ? indicies.length : indicies.length - 1;
for (var i = 0; i < charsToReplace; i++) {
newText = i % 2 === 0 ? newText.replace('-', '<del>') : newText.replace('-', '</del>');
}
var element = document.createElement('span');
element.innerHTML = newText;
if (isComment) {
commentHTML += newText;
} else {
item.parentNode.insertBefore(element, item);
removeElement(item);
}
}
}
else {
if (item.nodeType === 3 && node.textContent.match('-') !== null) {
if (stack.length > 0) {
var start = stack.pop();
result.push({ start, end: index });
} else {
stack.push(index)
}
}
if (item.tagName === 'BR') {
stack = [];
} else if (isComment) {
commentHTML += item.outerHTML ? item.outerHTML : item.textContent
}
}
})
result.length > 0 && result.forEach(function (item, index) {
var startIndex = item.start;
var endIndex = item.end;
for (var i = startIndex; i <= endIndex; i++) {
var currentNode = isComment ? node.childNodes[i] : node.querySelector(statusContentSelector).childNodes[i];
if (currentNode.nodeType === 3) {
var element = document.createElement('span');
var newText;
if (i === endIndex) {
newText = currentNode.textContent.replace('-', '</del>');
element.innerHTML = '<del>' + newText;
} else {
newText = currentNode.textContent.replace('-', '<del>');
element.innerHTML = newText + '</del>';
}
if (isComment) {
commentHTML += newText;
} else {
currentNode.parentNode.insertBefore(element, currentNode);
removeElement(currentNode);
}
} else {
var element2 = document.createElement('span');
element2.innerHTML = '<del>' + currentNode.innerHTML + '</del>';
if (isComment) {
commentHTML += newText;
} else {
currentNode.parentNode.insertBefore(element2, currentNode);
removeElement(currentNode);
}
}
}
})
if (isComment) {
return commentHTML !== '' ? commentHTML : node.innerHTML;
}
}
function charLocations(substring, string) {
var a = [], i = -1;
while ((i = string.indexOf(substring, i + 1)) >= 0) a.push(i);
return a;
}
function removeElement(element) {
element.parentNode.removeChild(element);
}
function uuidv4() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
function setNewInterval() {
autoLoadInterval = setInterval(function () {
document.getElementsByClassName(streamColClass)[0].childNodes.forEach(function (item) {
batchReplace(item);
})
}, autoLoadIntervalVal);
}
function getPostStatus(id, statsProcessor0, statsProcessor1, postProcessor) {
GM_xmlhttpRequest({
method: "GET",
url: host + statusApi + id,
headers: { "Accept": "application/json" },
onload: function (response) {
var data = JSON.parse(response.responseText);
var reblogs = data.reblogs_count ? data.reblogs_count : 0;
var favorites = data.favourites_count ? data.favourites_count : 0;
statsProcessor0(reblogs, favorites);
},
});
GM_xmlhttpRequest({
method: "GET",
url: host + statusApi + id + contextApi,
headers: { "Accept": "application/json" },
onload: function (response) {
var data = JSON.parse(response.responseText);
var replies = data.descendants ? data.descendants.length : 0;
var posts = data.descendants;
statsProcessor1(replies);
postProcessor(posts);
},
});
}
function replyPost(status, replyToId, cb, node) {
var statusData = new FormData();
statusData.append("status", status);
statusData.append("in_reply_to_id", replyToId);
statusData.append("media_ids", []);
statusData.append("sensitive", false);
statusData.append("spoiler_text", "");
statusData.append("visibility", "public");
GM_xmlhttpRequest({
method: "POST",
data: statusData,
url: host + statusApi,
headers: { "idempotency-key": uuidv4, "authorization": "Bearer " + authentication },
onload: function (res) {
cb(node);
}
});
}
function favoritePost(postId, cb, node) {
GM_xmlhttpRequest({
method: "POST",
url: host + statusApi + postId + favoriteApi,
headers: { "authorization": "Bearer " + authentication },
onload: function (res) {
cb(node);
}
});
}
function unfavoritePost(postId, cb, node) {
GM_xmlhttpRequest({
method: "POST",
url: host + statusApi + postId + unfavoriteApi,
headers: { "authorization": "Bearer " + authentication },
onload: function (res) {
cb(node);
}
});
}
function reblogPost(postId, cb, node) {
GM_xmlhttpRequest({
method: "POST",
url: host + statusApi + postId + reblogApi,
headers: { "authorization": "Bearer " + authentication },
onload: function (res) {
cb(node);
}
});
}
function unreblogPost(postId, cb, node) {
GM_xmlhttpRequest({
method: "POST",
url: host + statusApi + postId + unreblogApi,
headers: { "authorization": "Bearer " + authentication },
onload: function (res) {
cb(node);
}
});
}
window.onpopstate = history.onpushstate = function(e) {
observer.observe(document.body.querySelectorAll(streamColSelector)[0], {
childList: true,
subtree: true,
attributes: false,
characterData: false,
});
}
}
window.addEventListener('load', onLoad, false);