Skip to content

Instantly share code, notes, and snippets.

@xiaoxiaoflood
Last active October 12, 2022 05:42
Show Gist options
  • Save xiaoxiaoflood/2dc7432c2430528880b66ac825a47d19 to your computer and use it in GitHub Desktop.
Save xiaoxiaoflood/2dc7432c2430528880b66ac825a47d19 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name VK Xiao
// @namespace https://vk.com/xiaoxiaoflood
// @description VK tweaks
// @match https://vk.com/*
// @version 1.4
// @downloadURL https://gist.githubusercontent.com/xiaoxiaoflood/2dc7432c2430528880b66ac825a47d19/raw/VKXiaoPlus.user.js
// @grant none
// @run-at document-start
// ==/UserScript==
// tira "respondeu em" do fórum imediatamente quando primeira página do VK na aba é o fórum
function imediatoTiraRespondeuEm () {
if (/^\/board/.test(location.pathname)) {
let obs = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.addedNodes?.[0].id === 'page_wrap') {
obs.disconnect();
for (const elem of mutation.addedNodes[0].getElementsByClassName('blst_date'))
elem.textContent = elem.textContent.replace(/^(respondeu|escreveu|posted) (em ){0,2}/g, '');
}
});
});
obs.observe(document.documentElement, {
childList: true,
subtree: true
});
}
}
// tira "respondeu em" do fórum em navegação interna e rolagem
function tiraRespondeuEm () {
if (/^\/board/.test(location.pathname)) {
for (const elem of document.getElementsByClassName('blst_date'))
elem.textContent = elem.textContent.replace(/^(respondeu|escreveu|posted) (em ){0,2}/g, '');
if (unsafeWindow.Board.loaded.patched)
return;
unsafeWindow.Board.loaded = exportFunction(new Proxy(unsafeWindow.Board.loaded, {
apply (target, thisArg, args) {
let numt = document.getElementsByClassName('blst_date').length;
// com apply seria (thisArg, args), porém daria erro porque o apply tenta acessar args.length e não seria permitido
let result = target.call(thisArg, ...args);
for (let n = numt; n < document.getElementsByClassName('blst_date').length; n++)
document.getElementsByClassName('blst_date')[n].textContent = document.getElementsByClassName('blst_date')[n].textContent.replace(/^(respondeu|escreveu|posted) (em ){0,2}/g, '');
return result;
}
}), unsafeWindow);
unsafeWindow.Board.loaded.patched = true;
}
}
// altera o link do avatar das comunidades na página Comunidades para ir direto ao fórum
function linkAvatarForum () {
if (/^\/groups$/.test(location.pathname)) {
for (const elem of document.getElementsByClassName('group_row_photo'))
elem.href = 'https://vk.com/board' + elem.parentElement.dataset.groupId;
let obs = new MutationObserver(mutations => {
mutations.forEach(mutation => {
for (const elem of mutation.addedNodes)
elem.getElementsByTagName('a')[0].href = 'https://vk.com/board' + elem.id.match(/gl_(admin|groups)(\d+)/)[2];
})
})
obs.observe(document.getElementById('groups_list_content'), {
childList: true,
subtree: true
});
}
}
/* primeiro previne uso do cache ao clicar pra capa ou pro fórum,
depois força exibição de 20 posts na última página */
function previneCacheEForca20Posts () {
document.body.addEventListener('mouseup', e => {
let aLink = e.target.closest('a');
if (aLink) {
/* ao clicar no link pra capa da comunidade (0) ou pro fórum (1), o VK usa back: true
* para carregar pelo cache ao invés de atualizar. Edito isso para forçar atualização. */
if (aLink === document.getElementsByClassName('ui_crumb')[0] ||
aLink === document.getElementsByClassName('ui_crumb')[1]) {
//clique para a capa da comunidade não carrega do cache
aLink.setAttribute('onclick', aLink.getAttribute('onclick').replace(/true/, 'false'));
return;
}
/* clicar para a última página de um tópico precisa redirecionar para forçar a exibição
* de página completa, ou seja, 20 posts. Primeira parte se aplica ao fórum, segunda à
* capa da comunidade */
if ((/^https?:\/\/vk\.com\/board\d+$/.test(location.href) &&
/^https?:\/\/vk\.com\/topic-\d+_\d+\?offset\=last&scroll\=1$/.test(aLink.href)) ||
(e.target.className == 'topic_inner_link' &&
/\/topic-\d+_\d+\?offset\=last&scroll\=1/.test(e.target.getAttribute('onmouseover')))) {
let nposts = e.target.className === 'topic_inner_link' ?
/* tira ',' como separador de milhar (independe da língua) para processar número,
* primeiro na capa da comunidade e depois no fórum. */
e.target.parentElement.firstChild.textContent.match(/[\d,]+/)[0].replace(/,/g,'') :
aLink.nextElementSibling.querySelector('.blst_other').textContent.match(/[\d,]+/)[0].replace(/,/g,'');
let aLinkOrig = aLink.href;
aLink.href = aLink.href.replace(/last/, nposts > 20 ? nposts -= 20 : 0);
// restaura link original para que cliques subsequentes continuem indo para o último post
setTimeout(() => aLink.href = aLinkOrig);
return;
}
/* clicar na última página de um tópico dentro do próprio tópico precisa redirecionar
* para forçar a exibição de 20 posts também. Seletor para elemento de paginação, a
* classe é para o elemento flutuante, a id é o elemento fixo no topo. */
if (aLink === aLink.closest('.pg_fixed_pages, #bt_pages')?.lastElementChild) {
aLink.href = location.pathname + '?offset=' + (unsafeWindow.cur.pgCount - 20);
return;
}
// no fórum, clicar no link do número da última página também deve exibir 20 posts
if (aLink === aLink.closest('.blst_other')?.lastElementChild) {
let nposts = aLink.parentElement.firstChild.textContent.match(/[\d,]+/)[0].replace(/,/g,'');
let npag = parseInt(aLink.href.match(/^https?:\/\/vk\.com\/topic-\d+_\d+\?offset\=(\d+)$/)[1]);
if (npag + 20 >= nposts)
aLink.href = aLink.href.replace(/^(https?:\/\/vk\.com\/topic-\d+_\d+\?offset\=)(\d+)$/, '$1' + (nposts - 20));
}
}
});
}
function testLocation() {
linkAvatarForum();
tiraRespondeuEm();
if (/^\/board/.test(location.pathname))
linkForum();
else if (/^\/topic/.test(location.pathname))
natfun(), obslink(), navtecla(), last();
else if (document.getElementById('group'))
obslink();
}
function init () {
previneCacheEForca20Posts();
unsafeWindow.nav.setLoc = exportFunction(new Proxy(unsafeWindow.nav.setLoc, {
apply (target, thisArg, args) {
let result = target.call(thisArg, ...args);
testLocation();
return result;
}
}), unsafeWindow);
pagin = typeof Pagination == 'undefined' ? 0 : 1;
testLocation();
}
//Mostrar os link completos e tira redirecionamento, depois fazer pra rodar em todas as páginas (posts de mural) e corrigir links cortados (que possuem vírgula, asterisco etc). e links enviados/posts atualizados não estão sendo mostrados por inteiro, conferir
function linkfix(el) {//ultima alteração: coloquei esses dois elses porque link postado sem protocolo tava ficando errado, a ver se esses elses não vão quebrar nada
if (!el.href.indexOf(location.origin + '/away.php?to='))
el.href = decodeURIComponent(el.search.slice(4, -8));
if (el.hasAttribute('title')) {
let title = (new DOMParser).parseFromString('<!doctype html><body>' + el.title, 'text/html').body.textContent;
el.href = /^(?!ftp|https?|magnet)/.test(title) ? 'http://' + title : title;
el.textContent = title;
el.removeAttribute('title');
}
if (el.nextSibling && el.nextSibling.nodeType == 3 && el.nextSibling.data.match(/^[^\s]*(\.[a-zA-Z]{1,5}|\d{7,})([^a-zA-Z] ?|$)/))
el.href += el.nextSibling.data.match(/(^[^\s]*(\.[a-zA-Z]{1,5}|\d{7,}))([^a-zA-Z] ?|$)/)[1],
el.innerHTML += el.nextSibling.data.match(/(^[^\s]*(\.[a-zA-Z]{1,5}|\d{7,}))([^a-zA-Z] ?|$)/)[1],
el.nextSibling.data = el.nextSibling.data.slice(el.nextSibling.data.match(/(^[^\s]*(\.[a-zA-Z]{1,5}|\d{7,}))([^a-zA-Z] ?|$)/)[1].length);
}
function obslink () {
var observerl = new MutationObserver (function (mutations) {
mutations.forEach(function (mutation) {//console.log(mutation.target.id + ' ' + mutation.target.className);
if (mutation.target.className == 'bp_text') {
for(var n = 0; n < mutation.addedNodes.length; n++){
if (mutation.addedNodes[n].nodeType == 1 && mutation.addedNodes[n].tagName == 'A') {
linkfix(mutation.addedNodes[n]);
}
}
if (/(?!">|")( |^|>)magnet:.[^\s<]*/.test(mutation.target)) {
mutation.target = mutation.target.replace(/( |^|>)(magnet:.[^\s<]*)/g, '$1<a href="$2">$2</a>');
}
} else if (mutation.target.className == 'wall_post_text') {
for(var n = 0; n < mutation.addedNodes.length; n++){
if (mutation.addedNodes[n].nodeType == 1 && mutation.addedNodes[n].tagName == 'A') {
linkfix(mutation.addedNodes[n]);
}
}
} else if (mutation.target.id == 'bt_rows') {
if (mutation.addedNodes[0].className == 'bp_post clear_fix ' || mutation.addedNodes[0].className == 'bp_post clear_fix bp_selected') {
mutation.addedNodes[0].getElementsByTagName('a').length && [...mutation.addedNodes[0].getElementsByTagName('a')].forEach(el => {
if (!el.classList.contains('like_btn'))
linkfix(el);
})
if (/(?!">|")( |^|>)magnet:.[^\s<]*/.test(mutation.addedNodes[0].getElementsByClassName('bp_text')[0].innerHTML)) {
mutation.addedNodes[0].getElementsByClassName('bp_text')[0].innerHTML = mutation.addedNodes[0].getElementsByClassName('bp_text')[0].innerHTML.replace(/( |^|>)(magnet:.[^\s<]*)/g, '$1<a href="$2">$2</a>');
}
}
} else if (mutation.target.id == 'page_wall_posts') {
if (mutation.addedNodes[0].className == '_post post page_block all own'/* || mutation.addedNodes[0].className == 'bp_post clear_fix bp_selected'*/) {
mutation.addedNodes[0].getElementsByTagName('a').length && [...mutation.addedNodes[0].getElementsByTagName('a')].forEach(el => {
if (!el.classList.contains('like_btn'))
linkfix(el);
})
}
} else if (mutation.target.className == 'page_block' && mutation.addedNodes.length) {
if (mutation.addedNodes[0].getElementsByClassName('bp_text').length) {
mutation.addedNodes[0].getElementsByClassName('bp_text')[0].getElementsByTagName('a').length && [...mutation.addedNodes[0].getElementsByClassName('bp_text')[0].getElementsByTagName('a')].forEach(el => {
if (!el.classList.contains('like_btn'))
linkfix(el);
})
if (/(?!">|")( |^|>)magnet:.[^\s<]*/.test(mutation.addedNodes[0].getElementsByClassName('bp_text')[0].innerHTML)) {
mutation.addedNodes[0].getElementsByClassName('bp_text')[0].innerHTML = mutation.addedNodes[0].getElementsByClassName('bp_text')[0].innerHTML.replace(/( |^|>)(magnet:.[^\s<]*)/g, '$1<a href="$2">$2</a>');
}
} else if (mutation.addedNodes[0].getElementsByClassName('wall_post_text').length) {
mutation.addedNodes[0].getElementsByClassName('wall_post_text')[0].getElementsByTagName('a').length && [...mutation.addedNodes[0].getElementsByClassName('wall_post_text')[0].getElementsByTagName('a')].forEach(el => {
if (!el.classList.contains('like_btn'))
linkfix(el);
})
}
}
})
})
observerl.observe(document.getElementById('content'), {
childList: true,
subtree: true});
if (document.getElementById('bt_rows')) {
document.getElementById('bt_rows').getElementsByTagName('a').length && [...document.getElementById('bt_rows').getElementsByTagName('a')].forEach(el => {
if (!el.classList.contains('like_btn'))
linkfix(el);
})
for (var e of document.getElementsByClassName('bp_text')) {
if (/(?!">|")( |^|>)magnet:.[^\s<]*/.test(e.innerHTML)) {
e.innerHTML = e.innerHTML.replace(/( |^|>)(magnet:.[^\s<]*)/g, '$1<a href="$2">$2</a>');
}
};
} else if (document.getElementById('page_wall_posts')) {
document.getElementById('page_wall_posts').getElementsByTagName('a').length && [...document.getElementById('page_wall_posts').getElementsByTagName('a')].forEach(el => {
if (!el.classList.contains('like_btn'))
linkfix(el);
})
}
}
//atualizar o fórum clicando em Quadro de Discussão, código do VK Manager
function linkForum () {
if (!/^\?act=(search|create)/.test(location.search)) {
var $discussionBoard = document.getElementsByClassName('ui_crumb')[1];
if ($discussionBoard) {
var $linka = document.createElement('div');
$linka.setAttribute('class', 'ui_crumb');
$link = document.createElement('a');
$link.setAttribute('class', 'ui_crumb');
$link.textContent = $discussionBoard.firstChild.textContent;
$link.setAttribute('href', document.location.pathname);
$link.setAttribute('onclick', 'return nav.go(this, event, {back: false})');
if ($discussionBoard.childNodes.length >= 1) {
var $spanEl = document.createElement('span');
$spanEl.setAttribute('class', 'ui_crumb_count');
var $span = $discussionBoard.childNodes[1];
$spanEl.textContent = $span.textContent;
$linka.appendChild($link);
$linka.appendChild($spanEl);
}
$discussionBoard.parentNode.replaceChild($linka, $discussionBoard);
}
}
setTimeout(function () {
if (!document.getElementById('board_q').value)
document.getElementById('board_q').blur();
}, 2000);//100 instant
window.addEventListener("keypress", function (e) {
if (e.key == 'f' && document.activeElement != document.getElementById('board_q') && !document.activeElement.contentEditable)
document.getElementById('board_q').focus();
});
}
function last () {
// tentar dar um jeito para não mexer nisso se não for efetivamente a última página
if (/\?(offset|post)=\d+(&scroll=1)?/.test(location.search)) {
history.replaceState({}, 0, location.pathname + '?offset=last&scroll=1');
}
}
//muda função nativa pra evitar erros no processo de digitar um post num tópico
function natfun() {
if (document.getElementById('bt_reply_form_wrap')) {
unsafeWindow.Emoji.editableFocus = exportFunction(function (editable, obj, after, noCollapse, noForce) {
if (!editable || (noForce && document.activeElement === editable))
return false;
editable = unsafeWindow.ge(editable);
editable.focus();
if (editable.phonfocus)
editable.phonfocus();
if (typeof window.getSelection != 'undefined' && typeof document.createRange != 'undefined') {
var sel = window.getSelection();
if (unsafeWindow.browser.opera && !after) {
sel.collapse(obj || editable, 0);
} else {
var range = document.createRange();
if (obj)
range.selectNode(obj);
else
range.selectNodeContents(editable);
if (unsafeWindow.mozilla && editable.innerHTML === '<br>')
editable.innerHTML = ''; // fix strange ff behaviour, inserting empty brs in contenteidtable
if (!noCollapse)
range.collapse(after ? false : true);
}
} else if (typeof document.body.createTextRange != 'undefined') {
var textRange = document.body.createTextRange();
textRange.moveToElementText(obj || editable);
textRange.collapse(after ? false : true);
textRange.select();
}
}, unsafeWindow);
unsafeWindow.Emoji.editableFocus.pbind = exportFunction(function () {
var args = Array.prototype.slice.call(arguments);
args.unshift(window);
return this.bind.apply(this, args);
}, unsafeWindow);
}
}//fim
//navegar entre as páginas de um tópico usando as teclas de seta
function navtecla () {
if (pagin)
removeEvent(window, 'keydown', Pagination.keyNav);
Pagination.keyNav = function (e) {
if (!(layers.visible || cur.pgNoArrowNav && cur.pgNoArrowNav())) {
var r = cur.pgPage,
i = cur.pgPerPage;
if (e.keyCode == KEY.RIGHT ? ++r : e.keyCode == KEY.LEFT && --r, !(r == cur.pgPage || 0 > r || r >= Math.ceil(cur.pgCount / i))) {
var a = nav.objLoc;
return r && !(e.ctrlKey && e.keyCode == KEY.LEFT) ? a.offset = e.ctrlKey && e.keyCode == KEY.RIGHT ? cur.pgCount - 20 : r * i : delete a.offset ,
nav.go(a, !1, {
pgFromFixed : isVisible(cur.pgFixed)
}),
cancelEvent(e)
}
}
}
if (pagin)
addEvent(window, 'keydown', Pagination.keyNav), pagin = 0;
}
imediatoTiraRespondeuEm();
addEventListener('DOMContentLoaded', init);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment