├── .editorconfig ├── LICENSE.md ├── README.md ├── background └── background.js ├── content_scripts └── bcToken.js ├── create_firefox_zip.sh ├── icons └── cookie.png ├── lib └── browser-polyfill.min.js ├── manifest.json ├── manifest_v2.json └── popup ├── cookies.html └── cookies.js /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,md}] 2 | end_of_line = lf 3 | indent_style = space 4 | indent_size = 4 5 | charset = utf-8 6 | insert_final_newline = true 7 | 8 | [*.js] 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2020-2024 Marcus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OnlyFans Cookie Helper 2 | 3 | An extension made to make it easier to copy the correct `config.json` values when using [datawhores/OF-Scraper](https://github.com/datawhores/OF-Scraper) or [DIGITALCRIMINALS/OnlyFans](https://github.com/DIGITALCRIMINALS/OnlyFans). 4 | 5 | ## How to install 6 | 7 | Extension is only available on the Firefox addon store, but not on the Chrome web store. For Chromium-based browsers, alternatively installation methods are necessary. 8 | One of these days I might explore putting it on the Chrome web store, but for a few different reasons I bit am hesitant to so (one of them being the paywall Google has to publish extensions). 9 | 10 | ### Firefox 11 | 12 | #### Option 1 (Recommended) 13 | 14 | Install it from the [Firefox Addon Store (AMO)](https://addons.mozilla.org/en-US/firefox/addon/onlyfans/) 15 | 16 | **NOTE**: Mozilla disabled the addon on February 27th 2024 citing the reason: 17 | > Acceptable Use, specifically Sexual content: This content contains sexual or pornographic content that violates Mozilla’s Acceptable Use Policy. 18 | 19 | I have replied to them to appeal, since the addon doesn't contain (or link to) any sexual content, but still awaiting any further reply from Mozilla. 20 | If you need to \[re-\]install the addon, please use option 2 or 3 for the time being. 21 | 22 | #### Option 2 23 | 24 | Go to [Releases](https://github.com/M-rcus/OnlyFans-Cookie-Helper/releases), download the `.xpi` file and install it by typing `about:addons` into your URL bar, pressing `CTRL+Shift+A` or clicking the "hamburger menu" top-right of the Firefox window and then "Addons". 25 | 26 | ![Screenshot on how to do Firefox install option 1](https://i.marcus.pw/ss/2021-04-10_vOzkx1.png) 27 | 28 | #### Option 3 29 | 30 | Follow the [Trying it out](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Your_first_WebExtension#Trying_it_out) steps on their developer website. 31 | 32 | ### Chrome / Chromium 33 | 34 | These steps MAY work on other Chromium-based browsers, such as: Brave, Microsoft Edge, Vivaldi and Opera (to name a few). 35 | No guarantees though, I only do simple tests on a basic Chromium install, as my primary browser is Firefox. 36 | 37 | #### Option 1 38 | 39 | This option is only available as of v2.2.0. This is **VERY unofficial way** of installing the extension and you might a few warnings about it being unsafe (which is _generally_ true). 40 | If you are not comfortable with that, you can either choose to use Firefox instead or try option 2 below. 41 | 42 | 1. Go to [Releases](https://github.com/M-rcus/OnlyFans-Cookie-Helper/releases) and click the `.crx` file. Your browser might prompt you to install the extension. You can then just click 'Add extension'. 43 | - From my testing, Google Chrome and Brave give you an error when doing this. It does seem to work on "Ungoogled Chromium". Besides that I am not sure whether it will work or not. 44 | 45 | If it _does not_ prompt you to install the extension, you can try the following: 46 | 47 | 1. Right-click on the `.crx` download link and click "Save link as..." 48 | - As I mentioned, it will likely ask you (sometimes multiple times) if you want to keep the file as it can be malicious. You want to keep the file. 49 | 2. In your Chromium-browser, go to your URL bar and hit enter after typing in `chrome://extensions` 50 | 3. Find the `.crx` file that you just downloaded. 51 | 4. Click and drag the `.crx` file into your Chromium browser window, where `chrome://extensions` is open. 52 | 5. It should prompt you to add the extension. 53 | 54 | #### Option 2 55 | 56 | 1. Download the ZIP file of the version - `Source code (.zip)` 57 | 2. Extract the ZIP into a folder. 58 | 3. In your Chromium-browser, go to your URL bar and hit enter after typing in `chrome://extensions` 59 | 4. Click on "Load unpacked". **Select** the extracted folder and click "Open". 60 | 61 | ## How to use 62 | 63 | Make sure you're logged into the OnlyFans website normally. 64 | 65 | After installing the extension, click the cookie icon. 66 | A popup should show up (see [preview](#preview)) with a JSON-formatted text. 67 | There's a a "Copy to clipboard" button at the bottom of the popup that should copy the text to your clipboard. 68 | If it does not work, you can just copy the text manually by selecting it. 69 | 70 | Once you've copied the text to clipboard, you can paste it into the `auth.json` file in your profiles folder. 71 | The default `auth.json` file should be located in `/.profiles/OnlyFans/default/auth.json`, but may not show up until you've started up the OnlyFans software at least once. 72 | 73 | You can also create a new folder and a separate `auth.json` file, which is useful if you have multiple accounts. 74 | For example: 75 | - `/.profiles/OnlyFans/my-personal-account/auth.json` 76 | - `/.profiles/OnlyFans/my-secret-account/auth.json` 77 | 78 | ### Preview 79 | 80 | Screenshot as of extension version v1.0.3, which means it's slightly outdated. 81 | A few things to note: 82 | - `auth_hash`, `auth_uniq_`, `email` and `password` are _typically empty_. Don't panic if they don't have any values, as it's completely normal. 83 | - The `username` field is by default set to "u" plus the same number as `auth_id`. It _does not_ need to be your actual OnlyFans username. 84 | 85 | ![Preview of extension](https://i.marcus.pw/ss/2021-05-20_5hI4rK.png) 86 | 87 | ## Permissions 88 | 89 | Overview of permissions and why they're required. 90 | 91 | - `cookies` 92 | - Values such as `auth_id` and `sess` are contained within cookies. 93 | - Keep in mind that the `cookies` permission only applies for `onlyfans.com` and no other websites. 94 | - `clipboardWrite` 95 | - To copy the `auth.json` values into your clipboard 96 | - `storage` 97 | - This is specifically just to "synchronize" the `x_bc` value to the popup (so it can be copied). 98 | - `x_bc` isn't available via the regular `cookies` permission, so we need a workaround (which utilizes the `storage` permission). 99 | - `contextualIdentities` 100 | - On Firefox, it's used to support multi-account containers. 101 | - ~~On Chromium-based browsers (Google Chrome, Brave, Microsoft Edge, Vivaldi, Opera etc.) it does nothing. However, it may give a warning. The extension should still work even with this warning.~~ - This should no longer happen as of v2.2.0. 102 | 103 | ## LICENSE 104 | 105 | [MIT License](./LICENSE.md) 106 | 107 | ## Mirrors 108 | 109 | This project is currently mirrored to three different providers: 110 | 111 | - [GitHub](https://github.com/M-rcus/OnlyFans-Cookie-Helper) 112 | - [GitLab](https://gitlab.com/Maarcus/OnlyFans-Cookie-Helper) 113 | - [GitGud.io](https://gitgud.io/Maarcus/OnlyFans-Cookie-Helper) 114 | 115 | Those are the only 'official' sources for this extension. 116 | Anyone else can of course freely mirror the project as they see fit. 117 | 118 | ## Sellout (Tips) 119 | 120 | If you find the extension useful and would like to send me a tip, then I'll gladly take some crypto <3 121 | 122 | - Bitcoin: `bc1qps35rpadgmpf2a7vmuq45xnt7qscymtlnny6mx` 123 | - Dogecoin: `DAjtoHdXFFhRc3qJq8sqCWpQLLDB8t3L6n` 124 | - Litecoin: `LbX5iqVfYoRz7kPAPQoEKdqiN7Y9PRxsAg` 125 | 126 | Alternatively, PayPal, though crypto is preferred: https://paypal.me/maaaarcus -------------------------------------------------------------------------------- /background/background.js: -------------------------------------------------------------------------------- 1 | const ls = chrome.storage.local; 2 | 3 | /** 4 | * Helper for storing the new bcTokens object 5 | */ 6 | function storeBcTokens(bcTokens) 7 | { 8 | ls.set({'bcTokens': bcTokens}); 9 | } 10 | 11 | /** 12 | * Retrieve the stored bcTokens object 13 | * If none, return a fresh object 14 | */ 15 | async function getStoredBcTokens() 16 | { 17 | return new Promise((resolve, reject) => { 18 | ls.get(['bcTokens'], function(data) { 19 | if (!data.bcTokens) { 20 | storeBcTokens({}); 21 | resolve({}); 22 | return; 23 | } 24 | 25 | resolve(data.bcTokens); 26 | }); 27 | }); 28 | } 29 | 30 | async function handleBcToken(data) 31 | { 32 | const { bcTokenSha, id } = data; 33 | 34 | const bcTokens = await getStoredBcTokens(); 35 | bcTokens[id] = bcTokenSha; 36 | storeBcTokens(bcTokens); 37 | 38 | return true; 39 | } 40 | 41 | chrome.runtime.onMessage.addListener(handleBcToken); 42 | -------------------------------------------------------------------------------- /content_scripts/bcToken.js: -------------------------------------------------------------------------------- 1 | async function getBcToken() 2 | { 3 | const ls = window.localStorage; 4 | if (!ls.bcTokenSha) { 5 | return; 6 | } 7 | 8 | const bcToken = ls.bcTokenSha; 9 | 10 | /** 11 | * We don't have access to all cookies here, so instead we use a workaround 12 | * with the few cookie values we _do_ have access to. 13 | */ 14 | const match = document.cookie.match(/st=(\w{64})/); 15 | const id = match[1]; 16 | 17 | try { 18 | const message = await chrome.runtime.sendMessage({ 19 | bcTokenSha: bcToken, 20 | id: id, 21 | }); 22 | } 23 | catch (err) { 24 | console.error('Error occurred when trying to send bcToken to background script', err); 25 | } 26 | } 27 | 28 | // Handle changes/updates to localStorage 29 | window.addEventListener('storage', function() { 30 | const ls = window.localStorage; 31 | 32 | if (ls.bcTokenSha) { 33 | getBcToken(); 34 | } 35 | }); 36 | 37 | getBcToken(); 38 | -------------------------------------------------------------------------------- /create_firefox_zip.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | version="$(jq -r .version ./manifest_v2.json)"; 3 | filename="../OnlyFans-Cookie-Helper_v${version}.zip"; 4 | rm "${filename}"; 5 | zip -x '*.git*' -x '*.sh' -r "${filename}" *; 6 | zip -d "${filename}" "manifest.json"; 7 | printf "@ manifest_v2.json\n@=manifest.json\n" | zipnote -w "${filename}"; -------------------------------------------------------------------------------- /icons/cookie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M-rcus/OnlyFans-Cookie-Helper/6b2c941faecf10bf7af8e0adce350b2a990e5828/icons/cookie.png -------------------------------------------------------------------------------- /lib/browser-polyfill.min.js: -------------------------------------------------------------------------------- 1 | (function(a,b){if("function"==typeof define&&define.amd)define("webextension-polyfill",["module"],b);else if("undefined"!=typeof exports)b(module);else{var c={exports:{}};b(c),a.browser=c.exports}})("undefined"==typeof globalThis?"undefined"==typeof self?this:self:globalThis,function(a){"use strict";if("undefined"==typeof browser||Object.getPrototypeOf(browser)!==Object.prototype){if("object"!=typeof chrome||!chrome||!chrome.runtime||!chrome.runtime.id)throw new Error("This script should only be loaded in a browser extension.");a.exports=(a=>{const b={alarms:{clear:{minArgs:0,maxArgs:1},clearAll:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getAll:{minArgs:0,maxArgs:0}},bookmarks:{create:{minArgs:1,maxArgs:1},get:{minArgs:1,maxArgs:1},getChildren:{minArgs:1,maxArgs:1},getRecent:{minArgs:1,maxArgs:1},getSubTree:{minArgs:1,maxArgs:1},getTree:{minArgs:0,maxArgs:0},move:{minArgs:2,maxArgs:2},remove:{minArgs:1,maxArgs:1},removeTree:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1},update:{minArgs:2,maxArgs:2}},browserAction:{disable:{minArgs:0,maxArgs:1,fallbackToNoCallback:!0},enable:{minArgs:0,maxArgs:1,fallbackToNoCallback:!0},getBadgeBackgroundColor:{minArgs:1,maxArgs:1},getBadgeText:{minArgs:1,maxArgs:1},getPopup:{minArgs:1,maxArgs:1},getTitle:{minArgs:1,maxArgs:1},openPopup:{minArgs:0,maxArgs:0},setBadgeBackgroundColor:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setBadgeText:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setIcon:{minArgs:1,maxArgs:1},setPopup:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setTitle:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0}},browsingData:{remove:{minArgs:2,maxArgs:2},removeCache:{minArgs:1,maxArgs:1},removeCookies:{minArgs:1,maxArgs:1},removeDownloads:{minArgs:1,maxArgs:1},removeFormData:{minArgs:1,maxArgs:1},removeHistory:{minArgs:1,maxArgs:1},removeLocalStorage:{minArgs:1,maxArgs:1},removePasswords:{minArgs:1,maxArgs:1},removePluginData:{minArgs:1,maxArgs:1},settings:{minArgs:0,maxArgs:0}},commands:{getAll:{minArgs:0,maxArgs:0}},contextMenus:{remove:{minArgs:1,maxArgs:1},removeAll:{minArgs:0,maxArgs:0},update:{minArgs:2,maxArgs:2}},cookies:{get:{minArgs:1,maxArgs:1},getAll:{minArgs:1,maxArgs:1},getAllCookieStores:{minArgs:0,maxArgs:0},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}},devtools:{inspectedWindow:{eval:{minArgs:1,maxArgs:2,singleCallbackArg:!1}},panels:{create:{minArgs:3,maxArgs:3,singleCallbackArg:!0}}},downloads:{cancel:{minArgs:1,maxArgs:1},download:{minArgs:1,maxArgs:1},erase:{minArgs:1,maxArgs:1},getFileIcon:{minArgs:1,maxArgs:2},open:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},pause:{minArgs:1,maxArgs:1},removeFile:{minArgs:1,maxArgs:1},resume:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1},show:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0}},extension:{isAllowedFileSchemeAccess:{minArgs:0,maxArgs:0},isAllowedIncognitoAccess:{minArgs:0,maxArgs:0}},history:{addUrl:{minArgs:1,maxArgs:1},deleteAll:{minArgs:0,maxArgs:0},deleteRange:{minArgs:1,maxArgs:1},deleteUrl:{minArgs:1,maxArgs:1},getVisits:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1}},i18n:{detectLanguage:{minArgs:1,maxArgs:1},getAcceptLanguages:{minArgs:0,maxArgs:0}},identity:{launchWebAuthFlow:{minArgs:1,maxArgs:1}},idle:{queryState:{minArgs:1,maxArgs:1}},management:{get:{minArgs:1,maxArgs:1},getAll:{minArgs:0,maxArgs:0},getSelf:{minArgs:0,maxArgs:0},setEnabled:{minArgs:2,maxArgs:2},uninstallSelf:{minArgs:0,maxArgs:1}},notifications:{clear:{minArgs:1,maxArgs:1},create:{minArgs:1,maxArgs:2},getAll:{minArgs:0,maxArgs:0},getPermissionLevel:{minArgs:0,maxArgs:0},update:{minArgs:2,maxArgs:2}},pageAction:{getPopup:{minArgs:1,maxArgs:1},getTitle:{minArgs:1,maxArgs:1},hide:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setIcon:{minArgs:1,maxArgs:1},setPopup:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setTitle:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},show:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0}},permissions:{contains:{minArgs:1,maxArgs:1},getAll:{minArgs:0,maxArgs:0},remove:{minArgs:1,maxArgs:1},request:{minArgs:1,maxArgs:1}},runtime:{getBackgroundPage:{minArgs:0,maxArgs:0},getPlatformInfo:{minArgs:0,maxArgs:0},openOptionsPage:{minArgs:0,maxArgs:0},requestUpdateCheck:{minArgs:0,maxArgs:0},sendMessage:{minArgs:1,maxArgs:3},sendNativeMessage:{minArgs:2,maxArgs:2},setUninstallURL:{minArgs:1,maxArgs:1}},sessions:{getDevices:{minArgs:0,maxArgs:1},getRecentlyClosed:{minArgs:0,maxArgs:1},restore:{minArgs:0,maxArgs:1}},storage:{local:{clear:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}},managed:{get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1}},sync:{clear:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}}},tabs:{captureVisibleTab:{minArgs:0,maxArgs:2},create:{minArgs:1,maxArgs:1},detectLanguage:{minArgs:0,maxArgs:1},discard:{minArgs:0,maxArgs:1},duplicate:{minArgs:1,maxArgs:1},executeScript:{minArgs:1,maxArgs:2},get:{minArgs:1,maxArgs:1},getCurrent:{minArgs:0,maxArgs:0},getZoom:{minArgs:0,maxArgs:1},getZoomSettings:{minArgs:0,maxArgs:1},highlight:{minArgs:1,maxArgs:1},insertCSS:{minArgs:1,maxArgs:2},move:{minArgs:2,maxArgs:2},query:{minArgs:1,maxArgs:1},reload:{minArgs:0,maxArgs:2},remove:{minArgs:1,maxArgs:1},removeCSS:{minArgs:1,maxArgs:2},sendMessage:{minArgs:2,maxArgs:3},setZoom:{minArgs:1,maxArgs:2},setZoomSettings:{minArgs:1,maxArgs:2},update:{minArgs:1,maxArgs:2}},topSites:{get:{minArgs:0,maxArgs:0}},webNavigation:{getAllFrames:{minArgs:1,maxArgs:1},getFrame:{minArgs:1,maxArgs:1}},webRequest:{handlerBehaviorChanged:{minArgs:0,maxArgs:0}},windows:{create:{minArgs:0,maxArgs:1},get:{minArgs:1,maxArgs:2},getAll:{minArgs:0,maxArgs:1},getCurrent:{minArgs:0,maxArgs:1},getLastFocused:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},update:{minArgs:2,maxArgs:2}}};if(0===Object.keys(b).length)throw new Error("api-metadata.json has not been included in browser-polyfill");class c extends WeakMap{constructor(a,b=void 0){super(b),this.createItem=a}get(a){return this.has(a)||this.set(a,this.createItem(a)),super.get(a)}}const d=a=>a&&"object"==typeof a&&"function"==typeof a.then,e=(b,c)=>(...d)=>{a.runtime.lastError?b.reject(a.runtime.lastError):c.singleCallbackArg||1>=d.length&&!1!==c.singleCallbackArg?b.resolve(d[0]):b.resolve(d)},f=a=>1==a?"argument":"arguments",g=(a,b)=>function(c,...d){if(d.lengthb.maxArgs)throw new Error(`Expected at most ${b.maxArgs} ${f(b.maxArgs)} for ${a}(), got ${d.length}`);return new Promise((f,g)=>{if(b.fallbackToNoCallback)try{c[a](...d,e({resolve:f,reject:g},b))}catch(e){console.warn(`${a} API method doesn't seem to support the callback parameter, `+"falling back to call it without a callback: ",e),c[a](...d),b.fallbackToNoCallback=!1,b.noCallback=!0,f()}else b.noCallback?(c[a](...d),f()):c[a](...d,e({resolve:f,reject:g},b))})},h=(a,b,c)=>new Proxy(b,{apply(b,d,e){return c.call(d,a,...e)}});let i=Function.call.bind(Object.prototype.hasOwnProperty);const j=(a,b={},c={})=>{let d=Object.create(null),e={has(b,c){return c in a||c in d},get(e,f,k){if(f in d)return d[f];if(!(f in a))return;let l=a[f];if("function"==typeof l){if("function"==typeof b[f])l=h(a,a[f],b[f]);else if(i(c,f)){let b=g(f,c[f]);l=h(a,a[f],b)}else l=l.bind(a);}else if("object"==typeof l&&null!==l&&(i(b,f)||i(c,f)))l=j(l,b[f],c[f]);else if(i(c,"*"))l=j(l,b[f],c["*"]);else return Object.defineProperty(d,f,{configurable:!0,enumerable:!0,get(){return a[f]},set(b){a[f]=b}}),l;return d[f]=l,l},set(b,c,e,f){return c in d?d[c]=e:a[c]=e,!0},defineProperty(a,b,c){return Reflect.defineProperty(d,b,c)},deleteProperty(a,b){return Reflect.deleteProperty(d,b)}},f=Object.create(a);return new Proxy(f,e)},k=a=>({addListener(b,c,...d){b.addListener(a.get(c),...d)},hasListener(b,c){return b.hasListener(a.get(c))},removeListener(b,c){b.removeListener(a.get(c))}});let l=!1;const m=new c(a=>"function"==typeof a?function(b,c,e){let f,g,h=!1,i=new Promise(a=>{f=function(b){l||(console.warn("Returning a Promise is the preferred way to send a reply from an onMessage/onMessageExternal listener, as the sendResponse will be removed from the specs (See https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onMessage)",new Error().stack),l=!0),h=!0,a(b)}});try{g=a(b,c,f)}catch(a){g=Promise.reject(a)}const j=!0!==g&&d(g);if(!0!==g&&!j&&!h)return!1;const k=a=>{a.then(a=>{e(a)},a=>{let b;b=a&&(a instanceof Error||"string"==typeof a.message)?a.message:"An unexpected error occurred",e({__mozWebExtensionPolyfillReject__:!0,message:b})}).catch(a=>{console.error("Failed to send onMessage rejected reply",a)})};return j?k(g):k(i),!0}:a),n=({reject:b,resolve:c},d)=>{a.runtime.lastError?a.runtime.lastError.message==="The message port closed before a response was received."?c():b(a.runtime.lastError):d&&d.__mozWebExtensionPolyfillReject__?b(new Error(d.message)):c(d)},o=(a,b,c,...d)=>{if(d.lengthb.maxArgs)throw new Error(`Expected at most ${b.maxArgs} ${f(b.maxArgs)} for ${a}(), got ${d.length}`);return new Promise((a,b)=>{const e=n.bind(null,{resolve:a,reject:b});d.push(e),c.sendMessage(...d)})},p={runtime:{onMessage:k(m),onMessageExternal:k(m),sendMessage:o.bind(null,"sendMessage",{minArgs:1,maxArgs:3})},tabs:{sendMessage:o.bind(null,"sendMessage",{minArgs:2,maxArgs:3})}},q={clear:{minArgs:1,maxArgs:1},get:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}};return b.privacy={network:{"*":q},services:{"*":q},websites:{"*":q}},j(a,p,b)})(chrome)}else a.exports=browser}); 2 | //# sourceMappingURL=browser-polyfill.min.js.map 3 | 4 | // webextension-polyfill v.0.6.0 (https://github.com/mozilla/webextension-polyfill) 5 | 6 | /* This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 9 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "OnlyFans Cookie Helper", 4 | "version": "2.3.0", 5 | "description": "Helper extension that makes it easier to copy config.json values for the DIGITALCRIMINALS/OnlyFans scraper", 6 | "icons": { 7 | "48": "icons/cookie.png" 8 | }, 9 | "background": { 10 | "service_worker": "background/background.js" 11 | }, 12 | "permissions": [ 13 | "cookies", 14 | "clipboardWrite", 15 | "storage" 16 | ], 17 | "host_permissions": [ 18 | "*://*.onlyfans.com/" 19 | ], 20 | "action": { 21 | "browser_style": true, 22 | "default_icon": { 23 | "48": "icons/cookie.png" 24 | }, 25 | "default_title": "OnlyFans Cookie Helper", 26 | "default_popup": "popup/cookies.html" 27 | }, 28 | "content_scripts": [ 29 | { 30 | "matches": [ 31 | "*://*.onlyfans.com/*", 32 | "*://*.onlyfans.com/" 33 | ], 34 | "js": [ 35 | "content_scripts/bcToken.js" 36 | ] 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /manifest_v2.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "OnlyFans Cookie Helper", 4 | "version": "2.3.0", 5 | "description": "Helper extension that makes it easier to copy config.json values for the DIGITALCRIMINALS/OnlyFans scraper", 6 | "icons": { 7 | "48": "icons/cookie.png" 8 | }, 9 | "background": { 10 | "scripts": ["background/background.js"] 11 | }, 12 | "permissions": [ 13 | "*://*.onlyfans.com/", 14 | "cookies", 15 | "clipboardWrite", 16 | "storage", 17 | "contextualIdentities" 18 | ], 19 | "browser_action": { 20 | "browser_style": true, 21 | "default_icon": { 22 | "48": "icons/cookie.png" 23 | }, 24 | "default_title": "OnlyFans Cookie Helper", 25 | "default_popup": "popup/cookies.html" 26 | }, 27 | "content_scripts": [ 28 | { 29 | "matches": ["*://*.onlyfans.com/*", "*://*.onlyfans.com/"], 30 | "js": ["content_scripts/bcToken.js"] 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /popup/cookies.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 22 | 23 | 24 |
25 |
26 | 27 |

28 | 29 |
30 | 31 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /popup/cookies.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Shamelessly copied from: https://techoverflow.net/2018/03/30/copying-strings-to-the-clipboard-using-pure-javascript/ 3 | * 4 | * Only used as a fallback if for some reason the Clipboard API 5 | * does not exist... heh. 6 | */ 7 | async function copyStringToClipboard (str) { 8 | // Create new element 9 | var el = document.createElement('textarea'); 10 | // Set value (string to be copied) 11 | el.value = str; 12 | // Set non-editable to avoid focus and move outside of view 13 | el.setAttribute('readonly', ''); 14 | el.style = {position: 'absolute', left: '-9999px'}; 15 | document.body.appendChild(el); 16 | // Select text inside element 17 | el.select(); 18 | // Copy text to clipboard 19 | document.execCommand('copy'); 20 | // Remove temporary element 21 | document.body.removeChild(el); 22 | } 23 | 24 | const containerNames = {}; 25 | const containersEnabled = browser.contextualIdentities !== undefined; 26 | 27 | /** 28 | * Get the correct bcToken from storage 29 | */ 30 | async function getBcTokenSha(id) 31 | { 32 | return new Promise((resolve, reject) => { 33 | chrome.storage.local.get(['bcTokens'], function(data) { 34 | const bcTokens = data.bcTokens || {}; 35 | 36 | if (bcTokens[id]) { 37 | resolve(bcTokens[id]); 38 | return; 39 | } 40 | 41 | resolve(null); 42 | }); 43 | }); 44 | } 45 | 46 | async function getContainers() 47 | { 48 | /** 49 | * Prefill popup with "no container" cookies 50 | */ 51 | grabCookies(); 52 | 53 | /** 54 | * Non-Firefox browser or containers not enabled. 55 | */ 56 | if (!containersEnabled) { 57 | return; 58 | } 59 | 60 | /** 61 | * Containers are enabled, but none found. 62 | */ 63 | let containers = await browser.contextualIdentities.query({}); 64 | if (containers.length < 1) { 65 | return; 66 | } 67 | 68 | // Sort container list by name. 69 | containers.sort(function(a, b) { 70 | const nameA = a.name.toLowerCase(); 71 | const nameB = b.name.toLowerCase(); 72 | 73 | if (nameA < nameB) { 74 | return -1; 75 | } 76 | 77 | if (nameA > nameB) { 78 | return 1; 79 | } 80 | 81 | return 0; 82 | }); 83 | 84 | const containerSection = document.querySelector('#container-list'); 85 | containerSection.classList.remove('hidden'); 86 | 87 | const optionList = containerSection.querySelector('select'); 88 | 89 | for (const container of containers) 90 | { 91 | const storeId = container.cookieStoreId; 92 | const { name } = container; 93 | 94 | containerNames[storeId] = name; 95 | 96 | const option = document.createElement('option'); 97 | option.setAttribute('value', storeId); 98 | option.textContent = name; 99 | 100 | optionList.insertAdjacentElement('beforeend', option); 101 | } 102 | 103 | optionList.addEventListener('change', function(event) { 104 | const storeId = event.target.value; 105 | 106 | if (!storeId || storeId.length < 1) { 107 | grabCookies(null); 108 | return; 109 | } 110 | 111 | grabCookies(storeId); 112 | }); 113 | } 114 | 115 | async function grabCookies(cookieStoreId) { 116 | /** 117 | * Grab the cookies from the browser... 118 | */ 119 | const cookieOpts = { 120 | domain: '.onlyfans.com', 121 | }; 122 | 123 | /** 124 | * Container tabs 125 | */ 126 | if (cookieStoreId) { 127 | cookieOpts.storeId = cookieStoreId; 128 | } 129 | 130 | const cookies = await browser.cookies.getAll(cookieOpts); 131 | 132 | /** 133 | * We only care about `name` and `value` in each cookie entry. 134 | */ 135 | const mappedCookies = {}; 136 | for (const cookie of cookies) 137 | { 138 | mappedCookies[cookie.name] = cookie.value; 139 | } 140 | 141 | /** 142 | * Define and check if `authId` exists 143 | * if not, return and call it a day... 144 | * 145 | * Also define the other elements. 146 | */ 147 | const authId = mappedCookies.auth_id; 148 | const sess = mappedCookies.sess; 149 | const copyBtn = document.querySelector('#copy-to-clipboard'); 150 | const jsonElement = document.querySelector('#json'); 151 | const errorElement = document.querySelector('#errorMessage'); 152 | 153 | /** 154 | * If authId isn't specified, user is not logged into 155 | * OnlyFans... or at least we assume so. 156 | */ 157 | if (!authId || !sess) { 158 | let errorMessage = 'Could not find valid cookie values, make sure you are logged into OnlyFans.'; 159 | if (containersEnabled) { 160 | const containerName = containerNames[cookieStoreId] || 'Default (no container)'; 161 | errorMessage = `Could not find valid cookie values in container: ${containerName}
Make sure you are logged into OnlyFans.`; 162 | } 163 | 164 | errorElement.innerHTML = errorMessage; 165 | errorElement.classList.remove('hidden'); 166 | 167 | if (!copyBtn.classList.contains('hidden')) { 168 | copyBtn.classList.add('hidden'); 169 | jsonElement.classList.add('hidden'); 170 | } 171 | 172 | return; 173 | } 174 | 175 | // See `background/background.js` as to why we use `st` here 176 | const st = mappedCookies.st; 177 | const bcToken = await getBcTokenSha(st); 178 | if (!bcToken) { 179 | let errorMessage = 'Could not find valid x_bc value. Please open OnlyFans.com once and make sure it fully loads. If you are not logged in, please log in and refresh the page.'; 180 | if (containersEnabled) { 181 | const containerName = containerNames[cookieStoreId] || 'Default (no container)'; 182 | errorMessage = `Could not find valid x_bc value. Please open OnlyFans.com once in container: ${containerName}
Make sure it fully loads. If you are not logged in, please log in and refresh the page.`; 183 | } 184 | 185 | errorElement.innerHTML = errorMessage; 186 | errorElement.classList.remove('hidden'); 187 | 188 | if (!copyBtn.classList.contains('hidden')) { 189 | copyBtn.classList.add('hidden'); 190 | jsonElement.classList.add('hidden'); 191 | } 192 | 193 | return; 194 | } 195 | 196 | copyBtn.classList.remove('hidden'); 197 | jsonElement.classList.remove('hidden'); 198 | errorElement.classList.add('hidden'); 199 | 200 | /** 201 | * Fill out the object that OnlyFans excepts 202 | */ 203 | const config = { 204 | username: 'u' + authId, 205 | cookie: `auth_id=${authId}; sess=${sess}; auth_hash=; auth_uniq_${authId}=; auth_uid_${authId}=;`, 206 | // TODO: Still need to handle this better... 207 | user_agent: navigator.userAgent, 208 | x_bc: bcToken, 209 | support_2fa: true, 210 | active: true, 211 | email: "", 212 | password: "", 213 | hashed: false, 214 | }; 215 | 216 | /** 217 | * Then we print it to the popup :) 218 | * 219 | * Third parameter to JSON.stringify() is for spacing the indentation. 220 | */ 221 | const authConfig = { 222 | auth: config, 223 | }; 224 | 225 | const cookieJson = JSON.stringify(authConfig, null, 2); 226 | jsonElement.textContent = cookieJson; 227 | 228 | /** 229 | * Use yee yee ghetto ass method as a fallback 230 | * method for copying to clipboard. 231 | */ 232 | const clipboardWriteText = browser.clipboard.writeText || copyStringToClipboard; 233 | const oldBtnText = copyBtn.innerHTML; 234 | copyBtn.addEventListener('click', async () => { 235 | try { 236 | await clipboardWriteText(cookieJson); 237 | 238 | copyBtn.textContent = 'Copied to clipboard!'; 239 | copyBtn.setAttribute('disabled', '1'); 240 | } 241 | catch (err) { 242 | console.error(err); 243 | } 244 | 245 | setTimeout(() => { 246 | copyBtn.textContent = oldBtnText; 247 | copyBtn.removeAttribute('disabled'); 248 | }, 2500); 249 | }); 250 | } 251 | 252 | document.addEventListener('DOMContentLoaded', async () => { 253 | await getContainers(); 254 | }); 255 | --------------------------------------------------------------------------------