Last active
October 12, 2022 05:42
-
-
Save xiaoxiaoflood/2dc7432c2430528880b66ac825a47d19 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==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