Dúvida Extensão para ver a duração de vídeos PPV do OnlyFans?

GretaFM2

Lurker
Entrou
16 Set 2024
Postagens
11
Pontuação de reações
0
Eu tinha uma extensão que deixava ver a duração dos vídeos que fazem parte de um bundle. Sei que era uma extensão de desenvolvedor ou um script do tampermonkey, mas não lembro o nome de jeito nenhum. Achei aqui mesmo, mas não consigo achar de novo. Tenho certeza que alguém vai saber me dizer. Valeu desde já
 
Não tenho muito a acrescentar, mas, pelo que ouvi por último, não funciona mais. E não tem como fazer funcionar. Isso foi faz tempo, então posso estar errado. Mas duvido muito que exista algum método funcionando.
 
Eu tinha uma que funcionava umas semanas atrás, mas apaguei quando formatei minha VM e esqueci totalmente dela. Lembro de ter achado o nome num tópico de discussão de alguma modelo ou num desses posts de ferramentas. Tentei refazer o caminho e nada
 
Não sei se você ainda precisa, mas eu uso essa extensão do Chrome.
ofcheck.lol
 
Fiz esse script de tampermonkey pra ver a duração dos vídeos do conteúdo bloqueado.

Código:
// ==UserScript==
// @name OnlyFans Locked Video Durations
// @version 1.0.2
// @description Shows how long each video is inside a locked message bundle, before you pay for it.
// @match https://onlyfans.com/*
// @run-at document-start
// @grant none
// ==/UserScript==

// The durations are already sitting in the /api2/v2/chats/.../messages response,
// OF just never renders them. You can see it yourself: devtools > Network > filter
// "messages" > Preview > media > any video item. So all this does is grab that
// response on its way past and print the numbers onto the locked chip.
//
// 1.0.0 - first version
// 1.0.1 - fixed durations coming back in ms on some accounts
// 1.0.2 - dropped the total, per-video only

(function () {
'use strict';

var DEBUG = false; // flip this on if durations aren't showing up
var NUMBERED = false; // true gives you "1. 1:03" instead of just "1:03"

function log() {
if (!DEBUG) return;
console.log.apply(console, ['[OFDur]'].concat([].slice.call(arguments)));
}

// everything we've scraped so far, keyed by message id
var seen = new Map();

var API = /\/api2\/v2\/.*(messages|chats|posts|vault|stories)/i;

// ---- reading the response ----

function secs(v) {
if (typeof v === 'number' && isFinite(v) && v > 0) {
return v > 100000 ? Math.round(v / 1000) : Math.round(v); // ms on some endpoints
}
if (typeof v === 'string') {
if (/^\d+(\.\d+)?$/.test(v)) return secs(parseFloat(v));
var mm = v.match(/^(?:(\d+):)?(\d{1,2}):(\d{2})$/);
if (mm) return (+(mm[1] || 0)) * 3600 + (+mm[2]) * 60 + (+mm[3]);
}
return null;
}

// duration isn't always in the same spot (media.duration vs media.info.source.duration
// vs media.source.duration depending on the endpoint), so just dig for it
function durationOf(obj, depth) {
depth = depth || 0;
if (!obj || typeof obj !== 'object' || depth > 5) return null;

if (Array.isArray(obj)) {
for (var i = 0; i < obj.length; i++) {
var d = durationOf(obj[i], depth + 1);
if (d) return d;
}
return null;
}

var keys = ['duration', 'videoDuration', 'durationSeconds'];
for (var k = 0; k < keys.length; k++) {
if (keys[k] in obj) {
var s = secs(obj[keys[k]]);
if (s) return s;
}
}
for (var key in obj) {
if (obj[key] && typeof obj[key] === 'object') {
var found = durationOf(obj[key], depth + 1);
if (found) return found;
}
}
return null;
}

function stripTags(html) {
if (!html) return '';
try {
var doc = new DOMParser().parseFromString(String(html), 'text/html');
return doc.body ? doc.body.textContent || '' : '';
} catch (e) {
return String(html).replace(/<[^>]*>/g, ' ');
}
}

function clean(s) {
return String(s || '')
.replace(/[\u200B-\u200D\uFEFF\u00A0]/g, ' ')
.replace(/\s+/g, ' ')
.trim()
.toLowerCase();
}

function money(v) {
if (v == null) return null;
var n = parseFloat(String(v).replace(/[^0-9.]/g, ''));
return isFinite(n) ? n : null;
}

function save(msg) {
var id = String(msg.id);
var vids = [];
var pics = 0;

for (var i = 0; i < msg.media.length; i++) {
var m = msg.media[i];
if (!m || typeof m !== 'object') continue;
var t = String(m.type || '').toLowerCase();
if (t === 'video' || t === 'gif') vids.push(durationOf(m) || 0);
else if (t === 'photo' || t === 'image') pics++;
}
if (!vids.length) return;

// a later response can be more complete than an earlier one, but don't downgrade
var old = seen.get(id);
if (old && old.vids.filter(Boolean).length >= vids.filter(Boolean).length) return;

seen.set(id, {
id: id,
vids: vids,
pics: pics,
price: money(msg.price),
text: clean(stripTags(msg.text))
});
log('got', id, vids.map(mmss).join(', '));
}

// the messages live at different depths depending on the endpoint, so walk the whole thing
function crawl(node, depth) {
depth = depth || 0;
if (!node || typeof node !== 'object' || depth > 8) return;
if (Array.isArray(node)) {
for (var i = 0; i < node.length; i++) crawl(node[i], depth + 1);
return;
}
if (node.id != null && Array.isArray(node.media) && node.media.length) save(node);
for (var k in node) {
if (node[k] && typeof node[k] === 'object') crawl(node[k], depth + 1);
}
}

function gotResponse(url, body) {
if (!body || !API.test(url)) return;
var data;
try { data = JSON.parse(body); } catch (e) { return; }
try { crawl(data); paintSoon(); } catch (e) { log('parse blew up', e); }
}

// ---- hooking the requests (needs @run-at document-start or the app beats us to it) ----

var realFetch = window.fetch;
if (realFetch) {
window.fetch = function () {
var args = arguments;
var p = realFetch.apply(this, args);
try {
return p.then(function (res) {
try {
var first = args[0];
var url = (typeof first === 'string' ? first : (first && first.url)) || res.url || '';
if (API.test(url)) {
res.clone().text().then(function (t) { gotResponse(url, t); }).catch(function () {});
}
} catch (e) {}
return res;
});
} catch (e) {
return p;
}
};
}

var realOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (method, url) {
try { this._ofdurUrl = String(url); } catch (e) {}
return realOpen.apply(this, arguments);
};

var realSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function () {
var xhr = this;
try {
xhr.addEventListener('load', function () {
try {
var url = xhr._ofdurUrl || '';
if (!API.test(url)) return;
if (xhr.responseType === '' || xhr.responseType === 'text') {
gotResponse(url, xhr.responseText);
} else if (xhr.responseType === 'json' && xhr.response) {
crawl(xhr.response);
paintSoon();
}
} catch (e) {}
});
} catch (e) {}
return realSend.apply(this, arguments);
};

// ---- matching a saved message to its bubble ----
// there is no message id anywhere in the chat DOM (checked - no id, no data-id,
// nothing on the wrapper), so we match on what the locked chip is showing:
// video count first, then the message text, then photo count and price.

function mmss(s) {
if (!s) return '?:??';
var h = Math.floor(s / 3600);
var m = Math.floor((s % 3600) / 60);
var sec = Math.round(s % 60);
var pad = function (n) { return n < 10 ? '0' + n : '' + n; };
return h ? h + ':' + pad(m) + ':' + pad(sec) : m + ':' + pad(sec);
}

function readChip(node) {
var info = { pics: null, vids: null, price: null, text: '' };

var list = node.querySelector('.b-purchase__list');
if (list) {
var items = list.querySelectorAll('.b-purchase__list-item');
for (var i = 0; i < items.length; i++) {
var icon = items[i].querySelector('svg[data-icon-name]');
var name = icon ? icon.getAttribute('data-icon-name') : '';
var countEl = items[i].querySelector('.b-purchase__list-item__count');
var n = countEl ? parseInt(String(countEl.textContent).replace(/[^0-9]/g, ''), 10) : NaN;
if (!isFinite(n)) continue;
if (name === 'icon-video') info.vids = n;
else if (name === 'icon-media' || name === 'icon-photo') info.pics = n;
}
}

var priceEl = node.querySelector('.m-price-lock .b-purchase__list-item__count, .b-post__unknown__price');
if (priceEl) info.price = money(priceEl.textContent);

var textEl = node.querySelector('.b-chat__message__text-holder');
if (textEl) info.text = clean(textEl.textContent);

return info;
}

function lookup(info) {
var pool = [];
seen.forEach(function (e) {
if (info.vids == null || e.vids.length === info.vids) pool.push(e);
});
if (!pool.length) return null;

// narrow, but only if the filter doesn't wipe everything out
function tighten(fn) {
var next = pool.filter(fn);
if (next.length) pool = next;
}
if (info.text) tighten(function (e) { return e.text === info.text; });
if (info.pics != null) tighten(function (e) { return e.pics === info.pics; });
if (info.price != null) tighten(function (e) { return e.price === info.price; });

return pool[0] || null;
}

// ---- drawing ----

function pill(text) {
var el = document.createElement('span');
el.textContent = text;
el.style.display = 'inline-block';
el.style.padding = '0 6px';
el.style.borderRadius = '9px';
el.style.fontSize = '11px';
el.style.lineHeight = '17px';
el.style.fontWeight = '500';
el.style.whiteSpace = 'nowrap';
el.style.border = '1px solid currentColor';
el.style.opacity = '.75'; // currentColor so it works in both themes
return el;
}

function buildRow(entry) {
var row = document.createElement('div');
row.setAttribute('data-ofdur', '1');
row.style.display = 'flex';
row.style.flexWrap = 'wrap';
row.style.gap = '4px';
row.style.margin = '6px 0 2px';
row.style.lineHeight = '17px';

for (var i = 0; i < entry.vids.length; i++) {
row.appendChild(pill(NUMBERED ? (i + 1) + '. ' + mmss(entry.vids[i]) : mmss(entry.vids[i])));
}
return row;
}

function attach(node, entry) {
if (node.querySelector('[data-ofdur]')) return;
var row = buildRow(entry);

// sits right under the photo/video counts and above the unlock button.
// can't go in the ul itself, that has g-text-ellipsis on it and long rows get cut off.
var icons = node.querySelector('.b-subscribe-block .content-icons');
if (icons && icons.parentNode) {
icons.parentNode.insertBefore(row, icons.nextSibling);
return;
}
var block = node.querySelector('.b-subscribe-block') || node.querySelector('.b-post__unknown');
if (block) {
block.appendChild(row);
return;
}
(node.querySelector('.b-chat__message__body') || node).appendChild(row);
}

function paint() {
if (!seen.size) return;
var bubbles = document.querySelectorAll('.b-chat__message, [at-attr="chat_message"]');
for (var i = 0; i < bubbles.length; i++) {
var node = bubbles[i];
if (node.querySelector('[data-ofdur]')) continue;
var info = readChip(node);
if (info.vids == null) continue; // not a locked bundle
var entry = lookup(info);
if (!entry) { log('nothing matches', info); continue; }
attach(node, entry);
}
}

// vue rips these out and re-renders them constantly, hence the observer + the interval
var pending = false;
function paintSoon() {
if (pending) return;
pending = true;
requestAnimationFrame(function () {
pending = false;
try { paint(); } catch (e) { log(e); }
});
}

function start() {
try {
new MutationObserver(paintSoon).observe(document.documentElement, {
childList: true,
subtree: true
});
} catch (e) {}
setInterval(paintSoon, 1500);
paintSoon();
}

if (document.documentElement) start();
else document.addEventListener('DOMContentLoaded', start, { once: true });

window.__ofdur = { seen: seen, paint: paint };
})();
 
Valeu por compartilhar. Reparei que a duração dos vídeos não aparece em algumas mensagens.
 
Voltar
Topo