Question Extension pour voir la durée des vidéos PPV OnlyFans ?

GretaFM2

Lurker
Inscrit
16 Sept 2024
Messages
11
Réactions
0
J'avais avant une extension qui permettait de voir la durée des vidéos d'un bundle. Je sais que c'était soit une extension de développeur, soit un script tampermonkey, mais impossible de me rappeler son nom. Je l'avais trouvée ici, mais je ne la retrouve plus. Je suis sûr que quelqu'un pourra me le dire. Merci d'avance
 
J'ai pas grand-chose à ajouter, mais d'après ce que j'ai entendu, ça ne marche plus. Et aucun moyen de le faire marcher. C'était il y a un moment, donc je peux me tromper. Mais je doute fort qu'il existe une méthode qui marche.
 
J'en avais une qui marchait il y a quelques semaines, mais je l'ai supprimée en reformatant ma VM, j'avais complètement oublié. Je me souviens avoir trouvé son nom soit dans un topic de discussion sur un modèle, soit dans un de ces posts d'outils. J'ai essayé de refaire le chemin, sans succès
 
Je sais pas si t'en as encore besoin, mais j'utilise cette extension Chrome.
ofcheck.lol
 
J'ai fait ce script tampermonkey pour voir la durée des vidéos du contenu verrouillé.

Code:
// ==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 };
})();
 
Merci du partage. J'ai remarqué que la durée des vidéos ne s'affiche pas sur certains messages.
 
Retour
Haut