Pregunta ¿Extensión para ver la duración de videos PPV de OnlyFans?

GretaFM2

Lurker
Registrado
16 Sep 2024
Mensajes
11
Puntos de reacción
0
Antes tenía una extensión que permitía ver la duración de los videos que forman parte de un paquete. Sé que era una extensión de desarrollador o un script de tampermonkey, pero no me acuerdo cómo se llamaba por nada del mundo. La encontré aquí, pero no la encuentro de nuevo. Seguro alguien me lo puede decir. Gracias de antemano
 
No tengo mucho que aportar, pero lo último que supe es que ya no funciona. Y no hay forma de hacerlo funcionar. Eso fue hace tiempo, así que puedo estar equivocado. Pero dudo mucho que haya algún método que funcione.
 
Tenía una que funcionaba hace un par de semanas, pero la borré cuando limpié mi VM y me olvidé por completo. Recuerdo haber encontrado el nombre en un hilo de discusión de alguna modelo o en uno de estos posts de herramientas. Intenté volver sobre mis pasos y nada
 
No sé si todavía lo necesitas, pero uso esta extensión de Chrome.
ofcheck.lol
 
Hice este script de tampermonkey para ver la duración de los videos del contenido 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 };
})();
 
Gracias por compartir. Noté que la duración de los videos no aparece en algunos mensajes.
 
Atrás
Arriba