// ==UserScript==
// @name Perplexity MAX
// @description Free Max & Pro features for Perplexity.ai
// @version 2.0
// @author AntiKeks & d4nc3r
// @license MIT
// @match https://*.perplexity.ai/*
// @run-at document-start
// @grant none
// @namespace https://greasyfork.org/users/1497368
// ==/UserScript==
(function () {
'use strict';
/* ─────────────────────────── LOGGING ─────────────────────────── */
const DEBUG = true; // Enable debug logging for troubleshooting
const VERBOSE = false; // Disable verbose logging to improve performance
const log = (...args) => DEBUG && console.log('[PERPLEXITY-MAX]', ...args);
const verbose = (...args) => VERBOSE && console.log('[PERPLEXITY-MAX-VERBOSE]', ...args);
log('Initialization complete - MAX features activated');
/* ───────────────────────── THROTTLING UI ────────────────────── */
const COOLDOWN = 1000; // Minimum interval between React "pings" (ms)
let lastUpdate = 0;
function forceReactUpdate() {
const now = Date.now();
if (now - lastUpdate < COOLDOWN) return; // call no more than once per COOLDOWN
lastUpdate = now;
requestAnimationFrame(() => {
document.dispatchEvent(new Event('visibilitychange'));
window.dispatchEvent(new Event('resize'));
log('Dispatched events to update UI');
});
}
/* ─────────────────────── PATCH USER DATA ───────────────── */
function patchDataRecursively(obj, path = '', depth = 0) {
// Limit recursion depth to avoid performance issues
if (depth > 5) return false;
// Skip null, non-objects, and React elements
if (!obj || typeof obj !== 'object' || obj.$$typeof) return false;
let changed = false;
// Check for subscription-related fields directly
if (obj.hasOwnProperty('subscription_tier') && obj.subscription_tier !== 'max') {
obj.subscription_tier = 'max';
changed = true;
log(`${path}.subscription_tier → max (was: ${obj.subscription_tier})`);
}
if (obj.hasOwnProperty('subscription_source') && obj.subscription_source !== 'stripe') {
const oldValue = obj.subscription_source;
obj.subscription_source = 'stripe';
changed = true;
log(`${path}.subscription_source → stripe (was: ${oldValue})`);
}
if (obj.hasOwnProperty('subscription_status') && obj.subscription_status !== 'active') {
const oldValue = obj.subscription_status;
obj.subscription_status = 'active';
changed = true;
log(`${path}.subscription_status → active (was: ${oldValue})`);
}
// Handle pro/max flags - IMPORTANT: For MAX to show, we need both is_pro=true AND is_max=true
if (obj.hasOwnProperty('is_pro')) {
const oldValue = obj.is_pro;
obj.is_pro = true; // Set to true for MAX to work properly
if (oldValue !== true) {
changed = true;
log(`${path}.is_pro → true (was: ${oldValue})`);
}
}
if (obj.hasOwnProperty('is_max')) {
if (obj.is_max !== true) {
obj.is_max = true;
changed = true;
log(`${path}.is_max → true`);
}
}
if (obj.hasOwnProperty('has_max')) {
if (obj.has_max !== true) {
obj.has_max = true;
changed = true;
log(`${path}.has_max → true`);
}
}
// Also handle has_pro flag
if (obj.hasOwnProperty('has_pro')) {
if (obj.has_pro !== true) {
obj.has_pro = true;
changed = true;
log(`${path}.has_pro → true`);
}
}
// Handle rate limits directly - very important!
if (obj.hasOwnProperty('remaining_pro') && obj.remaining_pro !== 999999) {
obj.remaining_pro = 999999;
changed = true;
log(`${path}.remaining_pro → 999999 (unlimited)`);
}
if (obj.hasOwnProperty('remaining_research') && obj.remaining_research !== 999999) {
obj.remaining_research = 999999;
changed = true;
log(`${path}.remaining_research → 999999 (unlimited)`);
}
if (obj.hasOwnProperty('remaining_labs') && obj.remaining_labs !== 999999) {
obj.remaining_labs = 999999;
changed = true;
log(`${path}.remaining_labs → 999999 (unlimited)`);
}
// Handle upload-related fields
if (obj.hasOwnProperty('rate_limited') && obj.rate_limited !== false) {
obj.rate_limited = false;
changed = true;
log(`${path}.rate_limited → false (enabling uploads)`);
}
if (obj.hasOwnProperty('remaining_uploads') && obj.remaining_uploads !== 999999) {
obj.remaining_uploads = 999999;
changed = true;
log(`${path}.remaining_uploads → 999999 (unlimited)`);
}
if (obj.hasOwnProperty('max_uploads') && obj.max_uploads < 100) {
obj.max_uploads = 100;
changed = true;
log(`${path}.max_uploads → 100`);
}
if (obj.hasOwnProperty('upload_limit') && obj.upload_limit < 100) {
obj.upload_limit = 100;
changed = true;
log(`${path}.upload_limit → 100`);
}
if (obj.hasOwnProperty('can_upload') && obj.can_upload !== true) {
obj.can_upload = true;
changed = true;
log(`${path}.can_upload → true`);
}
// Only process important objects further
if (obj.hasOwnProperty('subscription') && typeof obj.subscription === 'object' && obj.subscription !== null) {
if (!obj.subscription) {
obj.subscription = {
tier: 'max',
status: 'active',
source: 'stripe',
price: 200,
billing_period: 'monthly'
};
changed = true;
log(`${path}.subscription created with MAX tier`);
} else {
if (obj.subscription.tier !== 'max') {
obj.subscription.tier = 'max';
changed = true;
}
if (obj.subscription.status !== 'active') {
obj.subscription.status = 'active';
changed = true;
}
if (obj.subscription.source !== 'stripe') {
obj.subscription.source = 'stripe';
changed = true;
}
}
}
// Handle features object
if (obj.hasOwnProperty('features') && typeof obj.features === 'object' && obj.features !== null) {
const featuresToEnable = {
max_models: true,
claude_opus_access: true,
gpt_4o_access: true,
unlimited_searches: true,
unlimited_uploads: true,
all_models: true,
image_upload: true,
file_upload: true
};
for (const f in featuresToEnable) {
if (obj.features[f] !== true) {
obj.features[f] = true;
changed = true;
}
}
}
// Recursively process child objects - but only important ones to avoid performance issues
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key) && typeof obj[key] === 'object' && obj[key] !== null) {
// Only process important keys to improve performance
const importantKeys = [
'user', 'account', 'subscription', 'features', 'settings', 'config',
'data', 'profile', 'preferences', 'options', 'state', 'models',
'uploads', 'files', 'images', 'usage'
];
if (importantKeys.includes(key) || key.includes('user') || key.includes('subscription') ||
key.includes('model') || key.includes('upload') || key.includes('file')) {
if (patchDataRecursively(obj[key], path ? `${path}.${key}` : key, depth + 1)) {
changed = true;
}
}
}
}
return changed;
}
/* ──────────────────── PATCH GLOBAL JSON.parse ───────────────── */
const originalParse = JSON.parse;
JSON.parse = function (text, reviver) {
const data = originalParse.call(this, text, reviver);
if (patchDataRecursively(data)) {
log('JSON-object converted to Max');
forceReactUpdate();
}
return data;
};
/* ──────────────────────────── PATCH fetch ─────────────────────── */
const originalFetch = window.fetch;
window.fetch = async function (input, init) {
try {
// Extract URL and method
const url = typeof input === 'string' ? input : input.url;
const method = init?.method || 'GET';
// Log all requests in debug mode
if (DEBUG) {
verbose(`Fetch request: ${method} ${url}`);
}
// Force logging for important endpoints
if (url && (
url.includes('/rest/rate-limit/all') ||
url.includes('/rest/user/save-settings') ||
url.includes('/rest/uploads/create_upload_url') ||
url.includes('/rest/sse/perplexity_ask')
)) {
log(`Intercepting important request: ${method} ${url}`);
}
// Block Datadog analytics
if (url && url.includes('datadoghq.com')) {
log('Blocked Datadog analytics request');
// Return empty success response
return new Response(JSON.stringify({ success: true }), {
status: 200,
statusText: 'OK',
headers: {
'Content-Type': 'application/json'
}
});
}
// Block other analytics services
if (url && (
url.includes('analytics') ||
url.includes('telemetry') ||
url.includes('metrics') ||
url.includes('singular.net') ||
url.includes('segment.io') ||
url.includes('mixpanel') ||
url.includes('facebook.com') ||
url.includes('fbevents')
)) {
log(`Blocked analytics request: ${url}`);
// Return empty success response
return new Response(JSON.stringify({ success: true }), {
status: 200,
statusText: 'OK',
headers: {
'Content-Type': 'application/json'
}
});
}
// CRITICAL: Handle rate-limit requests - MUST intercept these
if (url && url.includes('/rest/rate-limit/all')) {
log(`Intercepted rate-limit request (${method}) - FORCING UNLIMITED LIMITS`);
// ALWAYS return our custom response with unlimited limits
const unlimitedResponse = {
remaining_pro: 999999,
remaining_research: 999999,
remaining_labs: 999999,
remaining_uploads: 999999,
remaining_copilot: 999999,
remaining_search: 999999,
remaining_create: 999999,
rate_limited: false
};
log('Returning unlimited rate limits');
// Return our custom response directly, bypassing the server
return new Response(JSON.stringify(unlimitedResponse), {
status: 200,
statusText: 'OK',
headers: {
'Content-Type': 'application/json'
}
});
}
// Handle SSE requests for perplexity_ask
if (url && url.includes('/rest/sse/perplexity_ask')) {
log(`Intercepted SSE perplexity_ask request (${method})`);
try {
// Get the original request body
let requestBody = {};
if (init && init.body) {
if (typeof init.body === 'string') {
requestBody = JSON.parse(init.body);
}
}
// Modify params to ensure MAX features
if (requestBody.params) {
// Enable MAX model options
requestBody.params.model_preference = requestBody.params.model_preference || 'claude-3-opus-20240229';
// Add MAX subscription flags
requestBody.params.is_max = true;
requestBody.params.has_max = true;
requestBody.params.is_pro = true;
requestBody.params.subscription_tier = 'max';
// Set to use the best model available
if (!requestBody.params.model_preference || requestBody.params.model_preference === 'turbo') {
requestBody.params.model_preference = 'claude-3-opus-20240229';
log('Enhanced model to claude-3-opus');
}
// Update the request with modified body
init.body = JSON.stringify(requestBody);
log('Modified SSE request with MAX subscription data');
}
// Make the modified request
const response = await originalFetch(input, init);
// For SSE responses, we need to create a custom response that modifies the stream
if (response.headers.get('content-type')?.includes('text/event-stream')) {
log('Intercepting SSE response stream');
const reader = response.body.getReader();
const stream = new ReadableStream({
async start(controller) {
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
controller.close();
break;
}
// Convert the chunk to text
let text = new TextDecoder().decode(value);
// Process each event in the chunk
const events = text.split('\n\n');
let modifiedEvents = [];
for (const event of events) {
if (!event.trim()) continue;
// Check if this is a data event
if (event.includes('event: message') && event.includes('data: {')) {
try {
// Extract the data part
const dataMatch = event.match(/data: ({.*})/);
if (dataMatch && dataMatch[1]) {
const data = JSON.parse(dataMatch[1]);
// Modify the data to ensure MAX features
data.gpt4 = true; // Enable GPT-4 flag
data.display_model = data.display_model || 'claude-3-opus-20240229'; // Set display model to MAX
// If there's a classifier_results object, modify it
if (data.classifier_results) {
data.classifier_results.personal_search = true;
data.classifier_results.skip_search = false;
}
// If there's a telemetry_data object, modify it
if (data.telemetry_data) {
data.telemetry_data.is_pro = true;
data.telemetry_data.is_max = true;
data.telemetry_data.has_max = true;
}
// Replace the data in the event
const modifiedEvent = event.replace(dataMatch[1], JSON.stringify(data));
modifiedEvents.push(modifiedEvent);
continue;
}
} catch (e) {
verbose('Error modifying SSE event:', e);
}
}
// If we couldn't modify the event, keep it as is
modifiedEvents.push(event);
}
// Join the events back together
const modifiedText = modifiedEvents.join('\n\n') + '\n\n';
controller.enqueue(new TextEncoder().encode(modifiedText));
}
} catch (e) {
log('Error in SSE stream processing:', e);
controller.error(e);
}
}
});
// Return a new response with the modified stream
return new Response(stream, {
headers: response.headers,
status: response.status,
statusText: response.statusText
});
}
return response;
} catch (e) {
log('Error processing SSE request:', e);
// If there's an error, proceed with the original request
return originalFetch(input, init);
}
}
// Handle upload URL creation requests
if (url && url.includes('/rest/uploads/create_upload_url')) {
log('Intercepted upload URL creation request');
// Make the original request
const response = await originalFetch(input, init);
try {
// Clone the response so we can read and modify it
const cloned = response.clone();
const json = await cloned.json();
// Remove rate limiting for uploads
if (json.hasOwnProperty('rate_limited')) {
json.rate_limited = false;
log('.rate_limited → false (enabling uploads)');
// Return the modified response
return new Response(JSON.stringify(json), {
status: response.status,
statusText: response.statusText,
headers: response.headers
});
}
// If we didn't modify anything, return the original response
return response;
} catch (e) {
log('Error processing upload URL response:', e);
// Not JSON or other error - return original response
return response;
}
}
// Handle feedback visibility requests
if (url && url.includes('/rest/entry/should-show-feedback')) {
log('Intercepted feedback visibility request');
// Create a modified response that always sets should_show_feedback to true
return new Response(JSON.stringify({
should_show_feedback: true
}), {
status: 200,
statusText: 'OK',
headers: {
'Content-Type': 'application/json'
}
});
}
// Handle save-settings requests
if (url && url.includes('/rest/user/save-settings')) {
log(`Intercepted save-settings request (${method})`);
// For GET requests, we'll handle the response below
if (method === 'GET') {
const response = await originalFetch(input, init);
try {
const cloned = response.clone();
const json = await cloned.json();
// Modify the settings to ensure MAX subscription
json.subscription_tier = 'max';
json.subscription_status = 'active';
json.subscription_source = 'stripe';
json.stripe_status = 'active';
json.gpt4_limit = 999999;
json.pplx_alpha_limit = 999999;
json.pplx_beta_limit = 999999;
json.pages_limit = 999999;
json.upload_limit = 999999;
json.create_limit = 999999;
json.article_image_upload_limit = 999999;
json.max_files_per_user = 999999;
json.max_files_per_repository = 999999;
log('Modified GET save-settings response with MAX subscription data');
// Return the modified response
return new Response(JSON.stringify(json), {
status: response.status,
statusText: response.statusText,
headers: response.headers
});
} catch (e) {
log('Error processing GET save-settings response:', e);
return response;
}
}
// For PUT/POST requests, modify both the request and response
if (method === 'PUT' || method === 'POST') {
try {
// Get the original request body
let requestBody = {};
if (init && init.body) {
if (typeof init.body === 'string') {
requestBody = JSON.parse(init.body);
} else if (init.body instanceof FormData) {
// Handle FormData if needed
}
}
// Check if this is a partial update with updated_settings
if (requestBody.updated_settings) {
log('Detected partial settings update');
// Ensure MAX subscription settings in the updated_settings
requestBody.updated_settings.subscription_tier = 'max';
requestBody.updated_settings.subscription_status = 'active';
requestBody.updated_settings.subscription_source = 'stripe';
requestBody.updated_settings.stripe_status = 'active';
requestBody.updated_settings.gpt4_limit = 999999;
requestBody.updated_settings.pplx_alpha_limit = 999999;
requestBody.updated_settings.pplx_beta_limit = 999999;
requestBody.updated_settings.pages_limit = 999999;
requestBody.updated_settings.upload_limit = 999999;
requestBody.updated_settings.create_limit = 999999;
requestBody.updated_settings.article_image_upload_limit = 999999;
requestBody.updated_settings.max_files_per_user = 999999;
requestBody.updated_settings.max_files_per_repository = 999999;
} else {
// Ensure MAX subscription settings in the full settings
requestBody.subscription_tier = 'max';
requestBody.subscription_status = 'active';
requestBody.subscription_source = 'stripe';
requestBody.stripe_status = 'active';
requestBody.gpt4_limit = 999999;
requestBody.pplx_alpha_limit = 999999;
requestBody.pplx_beta_limit = 999999;
requestBody.pages_limit = 999999;
requestBody.upload_limit = 999999;
requestBody.create_limit = 999999;
requestBody.article_image_upload_limit = 999999;
requestBody.max_files_per_user = 999999;
requestBody.max_files_per_repository = 999999;
}
// Update the request with modified body
const modifiedInit = {
...init,
body: JSON.stringify(requestBody)
};
log('Modified save-settings request with MAX subscription data');
// Make the modified request
const response = await originalFetch(input, modifiedInit);
// Also modify the response to ensure MAX settings are returned
try {
const cloned = response.clone();
const json = await cloned.json();
// Always ensure the response has MAX settings
json.subscription_tier = 'max';
json.subscription_status = 'active';
json.subscription_source = 'stripe';
json.stripe_status = 'active';
json.gpt4_limit = 999999;
json.pplx_alpha_limit = 999999;
json.pplx_beta_limit = 999999;
json.pages_limit = 999999;
json.upload_limit = 999999;
json.create_limit = 999999;
json.article_image_upload_limit = 999999;
json.max_files_per_user = 999999;
json.max_files_per_repository = 999999;
// Log the modified response for debugging
log('Modified PUT/POST save-settings response with MAX subscription data');
return new Response(JSON.stringify(json), {
status: response.status,
statusText: response.statusText,
headers: response.headers
});
} catch (e) {
// If there's an error processing the response, return the original
log('Error modifying save-settings response: ' + e.message);
return response;
}
} catch (e) {
log('Error processing save-settings request: ' + e.message);
// If there's an error, proceed with the original request
return originalFetch(input, init);
}
}
}
// For all other requests, proceed normally
const response = await originalFetch(input, init);
// Check if the response is JSON and modify it if needed
try {
// Check if the response is actually JSON
const ct = response.headers.get('content-type') || '';
if (!ct.startsWith('application/json')) return response;
const cloned = response.clone();
const json = await cloned.json();
if (patchDataRecursively(json)) {
log(`Response modified ← ${url}`);
forceReactUpdate();
return new Response(JSON.stringify(json), {
status: response.status,
statusText: response.statusText,
headers: response.headers
});
}
} catch (e) {
/* not JSON — ignore */
}
return response;
} catch (e) {
log('Error in fetch interceptor:', e);
// If there's a critical error, fall back to original fetch
return originalFetch(input, init);
}
};
// Also inject the fetch interceptor into the page context
const injectFetchInterceptor = () => {
const script = document.createElement('script');
script.textContent = `
(function() {
console.log('[PERPLEXITY-MAX-INJECTED] Setting up fetch interceptor');
const originalFetch = window.fetch;
window.fetch = async function(input, init) {
try {
// Extract URL and method
const url = typeof input === 'string' ? input : input.url;
const method = init?.method || 'GET';
// CRITICAL: Handle rate-limit requests - MUST intercept these
if (url && url.includes('/rest/rate-limit/all')) {
console.log('[PERPLEXITY-MAX-INJECTED] Intercepted rate-limit request - FORCING UNLIMITED LIMITS');
// ALWAYS return our custom response with unlimited limits
const unlimitedResponse = {
remaining_pro: 999999,
remaining_research: 999999,
remaining_labs: 999999,
remaining_uploads: 999999,
remaining_copilot: 999999,
remaining_search: 999999,
remaining_create: 999999,
rate_limited: false
};
// Return our custom response directly, bypassing the server
return new Response(JSON.stringify(unlimitedResponse), {
status: 200,
statusText: 'OK',
headers: {
'Content-Type': 'application/json'
}
});
}
// Handle save-settings requests
if (url && url.includes('/rest/user/save-settings')) {
console.log('[PERPLEXITY-MAX-INJECTED] Intercepted save-settings request');
// For GET requests, we'll handle the response
if (method === 'GET') {
const response = await originalFetch(input, init);
try {
const cloned = response.clone();
const json = await cloned.json();
// Modify the settings to ensure MAX subscription
json.subscription_tier = 'max';
json.subscription_status = 'active';
json.subscription_source = 'stripe';
json.stripe_status = 'active';
json.gpt4_limit = 999999;
json.pplx_alpha_limit = 999999;
json.pplx_beta_limit = 999999;
json.pages_limit = 999999;
json.upload_limit = 999999;
json.create_limit = 999999;
json.article_image_upload_limit = 999999;
json.max_files_per_user = 999999;
json.max_files_per_repository = 999999;
console.log('[PERPLEXITY-MAX-INJECTED] Modified GET save-settings response');
// Return the modified response
return new Response(JSON.stringify(json), {
status: response.status,
statusText: response.statusText,
headers: response.headers
});
} catch (e) {
console.error('[PERPLEXITY-MAX-INJECTED] Error processing GET save-settings response:', e);
return response;
}
}
}
// For all other requests, proceed normally
return originalFetch.apply(this, arguments);
} catch (e) {
console.error('[PERPLEXITY-MAX-INJECTED] Error in fetch interceptor:', e);
// If there's a critical error, fall back to original fetch
return originalFetch.apply(this, arguments);
}
};
console.log('[PERPLEXITY-MAX-INJECTED] Fetch interceptor setup complete');
})();
`;
document.head.appendChild(script);
document.head.removeChild(script);
};
/* ───────────────────── SIMPLIFIED WebSocket HANDLING ──────────────── */
// This is a simplified version that just logs connections but doesn't modify data
// This avoids breaking the WebSocket connections
const originalWebSocket = window.WebSocket;
window.WebSocket = function(url, protocols) {
log(`WebSocket connection to ${url}`);
return new originalWebSocket(url, protocols);
};
/* ─────────────── BLOCK ANALYTICS AND TRACKING SCRIPTS ─────────────── */
function blockAnalyticsScripts() {
log('Setting up analytics and tracking script blocker');
// Override XMLHttpRequest to block analytics
const originalXhrOpen = XMLHttpRequest.prototype.open;
const originalXhrSend = XMLHttpRequest.prototype.send;
const originalXhrSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
// Fix for XMLHttpRequest state issues
XMLHttpRequest.prototype.open = function(method, url, ...rest) {
// Store the state and URL for later use
this._pplxState = 'OPENED';
this._pplxUrl = url;
if (typeof url === 'string' && (
url.includes('datadoghq.com') ||
url.includes('analytics') ||
url.includes('telemetry') ||
url.includes('metrics') ||
url.includes('singular.net') ||
url.includes('segment.io') ||
url.includes('mixpanel') ||
url.includes('facebook') ||
url.includes('fbevents')
)) {
log(`Blocked XHR analytics request to: ${url}`);
// Make this XHR do nothing but maintain proper state
this._pplxBlocked = true;
// Create a safe version that maintains proper state
this.setRequestHeader = function(header, value) {
// Allow setRequestHeader to be called without errors
if (this._pplxState === 'OPENED') {
// Just log but don't actually set headers
verbose(`Blocked setRequestHeader for ${url}: ${header}=${value}`);
return;
} else {
throw new DOMException('XMLHttpRequest.setRequestHeader: XMLHttpRequest state must be OPENED.');
}
};
this.send = function(body) {
// Change state to prevent further calls
this._pplxState = 'DONE';
// Simulate a successful response
setTimeout(() => {
if (typeof this.onreadystatechange === 'function') {
this.readyState = 4;
this.status = 200;
this.response = '{"success":true}';
this.responseText = '{"success":true}';
this.onreadystatechange();
}
if (typeof this.onload === 'function') {
this.onload();
}
}, 10);
};
// Still call original open to maintain proper state
return originalXhrOpen.call(this, method, url, ...rest);
}
// For non-blocked requests, proceed normally
return originalXhrOpen.call(this, method, url, ...rest);
};
// Safer setRequestHeader override
XMLHttpRequest.prototype.setRequestHeader = function(header, value) {
try {
// Only proceed if not blocked
if (!this._pplxBlocked) {
return originalXhrSetRequestHeader.call(this, header, value);
} else {
// For blocked requests, just pretend it worked
verbose(`Blocked setRequestHeader: ${header}=${value}`);
return;
}
} catch (e) {
log(`Error in setRequestHeader: ${e.message}`);
// If there's an error, don't break the page
return;
}
};
// Safer send override
XMLHttpRequest.prototype.send = function(body) {
try {
// Only proceed if not blocked
if (!this._pplxBlocked) {
return originalXhrSend.call(this, body);
} else {
// For blocked requests, simulate success
this._pplxState = 'DONE';
setTimeout(() => {
if (typeof this.onreadystatechange === 'function') {
this.readyState = 4;
this.status = 200;
this.response = '{"success":true}';
this.responseText = '{"success":true}';
this.onreadystatechange();
}
if (typeof this.onload === 'function') {
this.onload();
}
}, 10);
return;
}
} catch (e) {
log(`Error in send: ${e.message}`);
// If there's an error, don't break the page
return;
}
};
// Inject script to block analytics in page context
const script = document.createElement('script');
script.textContent = `
(function() {
console.log('[PERPLEXITY-MAX-INJECTED] Setting up analytics blocker');
// Block Datadog
window.DD_RUM = {
init: function() { console.log('[PERPLEXITY-MAX-INJECTED] Blocked Datadog init'); },
addTiming: function() {},
addAction: function() {},
addError: function() {},
startSessionReplayRecording: function() {},
setGlobalContextProperty: function() {}
};
// Block other analytics services
window.ga = function() { console.log('[PERPLEXITY-MAX-INJECTED] Blocked GA call'); };
window.gtag = function() { console.log('[PERPLEXITY-MAX-INJECTED] Blocked GTM call'); };
window._paq = { push: function() { console.log('[PERPLEXITY-MAX-INJECTED] Blocked Matomo call'); } };
window.mixpanel = { track: function() {}, identify: function() {} };
window.segment = { track: function() {}, identify: function() {} };
window.amplitude = { track: function() {}, identify: function() {} };
// Block Facebook Pixel
window.fbq = function() {
console.log('[PERPLEXITY-MAX-INJECTED] Blocked Facebook Pixel call');
};
// Block Facebook events
window.FB = window.FB || {};
window.FB.Event = window.FB.Event || { subscribe: function() {} };
window.FB.CustomerChat = window.FB.CustomerChat || { hide: function() {}, show: function() {} };
// Fix XMLHttpRequest issues
try {
const originalXhrOpen = XMLHttpRequest.prototype.open;
const originalXhrSend = XMLHttpRequest.prototype.send;
const originalXhrSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
XMLHttpRequest.prototype.open = function(method, url, ...rest) {
// Store state for later use
this._pplxState = 'OPENED';
this._pplxUrl = url;
if (typeof url === 'string' && (
url.includes('datadoghq.com') ||
url.includes('analytics') ||
url.includes('telemetry') ||
url.includes('metrics') ||
url.includes('singular.net') ||
url.includes('segment.io') ||
url.includes('mixpanel') ||
url.includes('facebook') ||
url.includes('fbevents')
)) {
console.log('[PERPLEXITY-MAX-INJECTED] Blocked XHR request:', url);
this._pplxBlocked = true;
return originalXhrOpen.call(this, method, 'data:text/plain,{}', ...rest);
}
return originalXhrOpen.call(this, method, url, ...rest);
};
XMLHttpRequest.prototype.setRequestHeader = function(header, value) {
if (this._pplxBlocked) {
// Do nothing for blocked requests
return;
}
return originalXhrSetRequestHeader.call(this, header, value);
};
XMLHttpRequest.prototype.send = function(body) {
if (this._pplxBlocked) {
// Simulate success for blocked requests
this._pplxState = 'DONE';
setTimeout(() => {
if (typeof this.onreadystatechange === 'function') {
this.readyState = 4;
this.status = 200;
this.response = '{"success":true}';
this.responseText = '{"success":true}';
this.onreadystatechange();
}
if (typeof this.onload === 'function') {
this.onload();
}
}, 10);
return;
}
return originalXhrSend.call(this, body);
};
console.log('[PERPLEXITY-MAX-INJECTED] XMLHttpRequest patched successfully');
} catch (e) {
console.error('[PERPLEXITY-MAX-INJECTED] Failed to patch XMLHttpRequest:', e);
}
// Override fetch for analytics
const originalFetch = window.fetch;
window.fetch = async function(input, init) {
const url = typeof input === 'string' ? input : input.url;
if (url && (
url.includes('datadoghq.com') ||
url.includes('analytics') ||
url.includes('telemetry') ||
url.includes('metrics') ||
url.includes('singular.net') ||
url.includes('segment.io') ||
url.includes('mixpanel') ||
url.includes('facebook') ||
url.includes('fbevents')
)) {
console.log('[PERPLEXITY-MAX-INJECTED] Blocked analytics fetch:', url);
return new Response(JSON.stringify({ success: true }), {
status: 200,
statusText: 'OK',
headers: { 'Content-Type': 'application/json' }
});
}
return originalFetch.apply(this, arguments);
};
// Override any analytics-related properties
Object.defineProperties(window, {
analytics: {
get: function() {
return {
track: function() {},
identify: function() {},
page: function() {}
};
},
configurable: true
},
dataLayer: {
get: function() { return []; },
set: function() {},
configurable: true
}
});
console.log('[PERPLEXITY-MAX-INJECTED] Analytics blocker setup complete');
})();
`;
document.head.appendChild(script);
document.head.removeChild(script);
}
/* ─────────────────── FORCE MAX SUBSCRIPTION ─────────────────── */
function forceMaxSubscription() {
// This function directly modifies subscription data in the DOM
log('Forcing MAX subscription data');
// 1. Force window.__NEXT_DATA__ if it exists (this contains initial state)
try {
if (window.__NEXT_DATA__ && window.__NEXT_DATA__.props && window.__NEXT_DATA__.props.pageProps) {
// Force user data
if (window.__NEXT_DATA__.props.pageProps.user) {
window.__NEXT_DATA__.props.pageProps.user.subscription_tier = 'max';
window.__NEXT_DATA__.props.pageProps.user.subscription_status = 'active';
window.__NEXT_DATA__.props.pageProps.user.subscription_source = 'stripe';
window.__NEXT_DATA__.props.pageProps.user.is_max = true;
window.__NEXT_DATA__.props.pageProps.user.has_max = true;
window.__NEXT_DATA__.props.pageProps.user.is_pro = true;
log('Forced MAX subscription in __NEXT_DATA__.props.pageProps.user');
}
// Force subscription data
if (window.__NEXT_DATA__.props.pageProps.subscription) {
window.__NEXT_DATA__.props.pageProps.subscription.tier = 'max';
window.__NEXT_DATA__.props.pageProps.subscription.status = 'active';
window.__NEXT_DATA__.props.pageProps.subscription.source = 'stripe';
log('Forced MAX subscription in __NEXT_DATA__.props.pageProps.subscription');
}
// Force rate limits
if (window.__NEXT_DATA__.props.pageProps.usage) {
if (window.__NEXT_DATA__.props.pageProps.usage.remaining_pro !== undefined) {
window.__NEXT_DATA__.props.pageProps.usage.remaining_pro = 999999;
}
if (window.__NEXT_DATA__.props.pageProps.usage.remaining_research !== undefined) {
window.__NEXT_DATA__.props.pageProps.usage.remaining_research = 999999;
}
if (window.__NEXT_DATA__.props.pageProps.usage.remaining_labs !== undefined) {
window.__NEXT_DATA__.props.pageProps.usage.remaining_labs = 999999;
}
log('Forced unlimited usage in __NEXT_DATA__');
}
}
} catch (e) {
log('Error modifying __NEXT_DATA__: ' + e.message);
}
// 2. Inject a script to modify React state directly
const script = document.createElement('script');
script.textContent = `
(function() {
console.log('[PERPLEXITY-MAX-INJECTED] Direct subscription override');
// Override subscription data in localStorage
try {
const storageKeys = ['perplexity-user', 'perplexity-subscription', 'perplexity-usage'];
storageKeys.forEach(key => {
const data = localStorage.getItem(key);
if (data) {
try {
const parsed = JSON.parse(data);
if (key === 'perplexity-user') {
parsed.subscription_tier = 'max';
parsed.subscription_status = 'active';
parsed.subscription_source = 'stripe';
parsed.is_max = true;
parsed.has_max = true;
parsed.is_pro = true;
}
if (key === 'perplexity-subscription') {
parsed.tier = 'max';
parsed.status = 'active';
parsed.source = 'stripe';
}
if (key === 'perplexity-usage') {
if (parsed.remaining_pro !== undefined) parsed.remaining_pro = 999999;
if (parsed.remaining_research !== undefined) parsed.remaining_research = 999999;
if (parsed.remaining_labs !== undefined) parsed.remaining_labs = 999999;
}
localStorage.setItem(key, JSON.stringify(parsed));
console.log('[PERPLEXITY-MAX-INJECTED] Modified localStorage:', key);
} catch (e) {
console.error('[PERPLEXITY-MAX-INJECTED] Error parsing localStorage:', e);
}
}
});
} catch (e) {
console.error('[PERPLEXITY-MAX-INJECTED] Error accessing localStorage:', e);
}
// Try to find and modify React state directly
function modifyReactState() {
// Look for React fiber nodes
let rootNode = document.querySelector('#__next');
if (!rootNode) rootNode = document.querySelector('body > div');
if (!rootNode) return;
// Get React internal instance
let internalInstance = null;
// React 16+
if (rootNode._reactRootContainer && rootNode._reactRootContainer._internalRoot) {
internalInstance = rootNode._reactRootContainer._internalRoot.current;
}
// React 17+
const reactKey = Object.keys(rootNode).find(key =>
key.startsWith('__reactContainer$') ||
key.startsWith('__reactFiber$')
);
if (reactKey) {
internalInstance = rootNode[reactKey];
}
if (!internalInstance) return;
// Walk the fiber tree to find subscription data
function walkFiber(fiber) {
if (!fiber) return;
// Check for subscription state
if (fiber.memoizedState && fiber.memoizedState.memoizedState) {
const state = fiber.memoizedState.memoizedState;
// Look for subscription data in state
if (state.subscription || state.user || state.usage) {
console.log('[PERPLEXITY-MAX-INJECTED] Found React state with subscription data');
if (state.subscription) {
state.subscription.tier = 'max';
state.subscription.status = 'active';
state.subscription.source = 'stripe';
}
if (state.user) {
state.user.subscription_tier = 'max';
state.user.subscription_status = 'active';
state.user.subscription_source = 'stripe';
state.user.is_max = true;
state.user.has_max = true;
state.user.is_pro = true;
}
if (state.usage) {
if (state.usage.remaining_pro !== undefined) state.usage.remaining_pro = 999999;
if (state.usage.remaining_research !== undefined) state.usage.remaining_research = 999999;
if (state.usage.remaining_labs !== undefined) state.usage.remaining_labs = 999999;
}
// Force update
if (fiber.stateNode && typeof fiber.stateNode.forceUpdate === 'function') {
fiber.stateNode.forceUpdate();
}
}
}
// Walk child fibers
if (fiber.child) walkFiber(fiber.child);
if (fiber.sibling) walkFiber(fiber.sibling);
}
walkFiber(internalInstance);
}
// Run immediately and periodically
modifyReactState();
setInterval(modifyReactState, 5000);
// Also override fetch responses for subscription endpoints
const originalFetch = window.fetch;
window.fetch = async function(input, init) {
const url = typeof input === 'string' ? input : input.url;
// Check if this is a subscription-related request
const isSubscriptionRequest = url && (
url.includes('/api/user') ||
url.includes('/api/subscription') ||
url.includes('/api/auth/session')
);
if (isSubscriptionRequest) {
console.log('[PERPLEXITY-MAX-INJECTED] Intercepted subscription request:', url);
// For session requests, return a modified response
if (url.includes('/api/auth/session')) {
return new Response(JSON.stringify({
user: {
subscription_tier: 'max',
subscription_status: 'active',
subscription_source: 'stripe',
is_max: true,
has_max: true,
is_pro: true
}
}), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
}
}
// For other requests, proceed normally
return originalFetch.apply(this, arguments);
};
})();
`;
document.head.appendChild(script);
document.head.removeChild(script);
// 3. Also directly modify any visible plan elements
const planElements = document.querySelectorAll('[data-plan], [data-tier], [data-subscription]');
planElements.forEach(element => {
if (element.getAttribute('data-plan') === 'pro') {
element.setAttribute('data-plan', 'max');
log('Changed data-plan from pro to max');
}
if (element.getAttribute('data-tier') === 'pro') {
element.setAttribute('data-tier', 'max');
log('Changed data-tier from pro to max');
}
if (element.getAttribute('data-subscription') === 'pro') {
element.setAttribute('data-subscription', 'max');
log('Changed data-subscription from pro to max');
}
});
// 4. Force model selection buttons to be clickable
const modelButtons = document.querySelectorAll('button[aria-label="Choose a model"], [data-model-selector="true"]');
modelButtons.forEach(button => {
// Clone the button to remove all event listeners
const newButton = button.cloneNode(true);
// Make sure it's visible and clickable
newButton.style.display = '';
newButton.style.visibility = 'visible';
newButton.style.pointerEvents = 'auto';
newButton.style.cursor = 'pointer';
// Remove any disabled attributes
newButton.removeAttribute('disabled');
newButton.setAttribute('aria-disabled', 'false');
// Add a click event listener that will force the dropdown to appear
newButton.addEventListener('click', () => {
log('Model button clicked');
});
// Replace the original button
if (button.parentNode) {
button.parentNode.replaceChild(newButton, button);
log('Replaced model button with enhanced version');
}
});
}
/* ─────────────────── ADD STATUS INDICATOR ─────────────────── */
function addStatusIndicator() {
// Create a simple status indicator
const statusDiv = document.createElement('div');
statusDiv.style.position = 'fixed';
statusDiv.style.bottom = '20px';
statusDiv.style.right = '20px';
statusDiv.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
statusDiv.style.color = '#00ff9d';
statusDiv.style.padding = '6px 12px';
statusDiv.style.borderRadius = '6px';
statusDiv.style.fontSize = '11px';
statusDiv.style.fontWeight = 'bold';
statusDiv.style.zIndex = '9999';
statusDiv.style.opacity = '0.8';
statusDiv.style.transition = 'opacity 0.3s';
statusDiv.style.fontFamily = 'Arial, sans-serif';
statusDiv.style.userSelect = 'none';
statusDiv.textContent = 'PERPLEXITY MAX v2.0';
document.body.appendChild(statusDiv);
// Fade out after 3 seconds
setTimeout(() => {
statusDiv.style.opacity = '0';
setTimeout(() => {
if (statusDiv.parentNode) {
statusDiv.parentNode.removeChild(statusDiv);
}
}, 1000);
}, 3000);
// Add neon author credit
addNeonAuthorCredit();
}
/* ─────────────────── ADD NEON AUTHOR CREDIT ─────────────────── */
function addNeonAuthorCredit() {
// Create wrapper for the neon text
const neonWrapper = document.createElement('div');
neonWrapper.style.position = 'fixed';
neonWrapper.style.bottom = '60px';
neonWrapper.style.right = '20px';
neonWrapper.style.zIndex = '9999';
neonWrapper.style.userSelect = 'none';
neonWrapper.style.opacity = '0';
neonWrapper.style.transition = 'opacity 1s ease-in-out';
// Create the neon text element
const neonText = document.createElement('div');
neonText.textContent = 'by AntiKeks & d4nc3r';
neonText.style.fontFamily = '"Courier New", monospace';
neonText.style.fontSize = '14px';
neonText.style.fontWeight = 'bold';
neonText.style.color = '#fff';
neonText.style.textShadow = `
0 0 5px #ff6600,
0 0 10px #ff6600,
0 0 15px #ff6600,
0 0 20px #ff6600,
0 0 25px #ff6600,
0 0 30px #ff6600
`;
neonText.style.animation = 'neonPulse 1.5s ease-in-out infinite alternate';
// Create and add the style for the neon pulse animation
const styleElement = document.createElement('style');
styleElement.textContent = `
@keyframes neonPulse {
from {
text-shadow:
0 0 5px #ff6600,
0 0 10px #ff6600,
0 0 15px #ff6600,
0 0 20px #ff6600;
}
to {
text-shadow:
0 0 5px #ff6600,
0 0 10px #ff8800,
0 0 15px #ff8800,
0 0 20px #ff8800,
0 0 25px #ff8800,
0 0 30px #ff8800,
0 0 35px #ffaa00;
}
}
@keyframes neonFlicker {
0%, 19%, 21%, 23%, 25%, 54%, 56%, 100% {
text-shadow:
0 0 5px #ff6600,
0 0 10px #ff6600,
0 0 15px #ff8800,
0 0 20px #ff8800,
0 0 25px #ffaa00;
}
20%, 24%, 55% {
text-shadow: none;
}
}
`;
document.head.appendChild(styleElement);
// Add the neon text to the wrapper
neonWrapper.appendChild(neonText);
// Add the wrapper to the document
document.body.appendChild(neonWrapper);
// Fade in after a short delay
setTimeout(() => {
neonWrapper.style.opacity = '1';
// Add flickering effect after it's visible
setTimeout(() => {
neonText.style.animation = 'neonFlicker 2s linear 1, neonPulse 1.5s ease-in-out infinite alternate';
}, 2000);
// Fade out after 6 seconds
setTimeout(() => {
neonWrapper.style.opacity = '0';
// Remove from DOM after fade out
setTimeout(() => {
if (neonWrapper.parentNode) {
neonWrapper.parentNode.removeChild(neonWrapper);
}
if (styleElement.parentNode) {
styleElement.parentNode.removeChild(styleElement);
}
}, 1000);
}, 6000);
}, 1000);
}
/* ─────────────────── ENABLE UNLIMITED UPLOADS ─────────────────── */
function enableUnlimitedUploads() {
log('Enabling unlimited file uploads');
// Inject a script to modify any upload limits in the page context
const script = document.createElement('script');
script.textContent = `
(function() {
console.log('[PERPLEXITY-MAX-INJECTED] Enabling unlimited uploads');
// Override any upload limit checks in the page context
const originalFetch = window.fetch;
window.fetch = async function(input, init) {
const url = typeof input === 'string' ? input : input.url;
// If this is an upload-related request, modify it
if (url && url.includes('/uploads')) {
// If there's a body, check for any limit-related fields
if (init && init.body && typeof init.body === 'string') {
try {
const body = JSON.parse(init.body);
// Remove any limit-related fields
if (body.hasOwnProperty('limit')) {
body.limit = 999999;
}
if (body.hasOwnProperty('max_size')) {
body.max_size = 1000000000; // 1GB
}
if (body.hasOwnProperty('max_count')) {
body.max_count = 100;
}
// Update the request body
init.body = JSON.stringify(body);
} catch (e) {
// Not JSON - ignore
}
}
}
return originalFetch.apply(this, arguments);
};
// Advanced fix for FormData.append issues
try {
// Store the original method
const originalFormDataAppend = FormData.prototype.append;
// Create a safer version that handles all edge cases
FormData.prototype.append = function(name, value, filename) {
// Handle the case when name is not a string
if (typeof name !== 'string') {
try {
name = String(name);
} catch (e) {
name = 'unnamed';
console.warn('[PERPLEXITY-MAX-INJECTED] FormData.append: Invalid name parameter');
}
}
// Handle null or undefined values
if (value === null || value === undefined) {
console.log('[PERPLEXITY-MAX-INJECTED] FormData.append: Converting null/undefined to empty string');
try {
return originalFormDataAppend.call(this, name, '', filename);
} catch (e) {
console.error('[PERPLEXITY-MAX-INJECTED] FormData.append failed with empty string:', e);
return; // Silently fail rather than breaking the page
}
}
// Handle File and Blob objects properly
if (value instanceof File || value instanceof Blob) {
try {
return originalFormDataAppend.call(this, name, value, filename);
} catch (e) {
console.error('[PERPLEXITY-MAX-INJECTED] FormData.append failed with File/Blob:', e);
return; // Silently fail rather than breaking the page
}
}
// Handle string values
if (typeof value === 'string') {
try {
return originalFormDataAppend.call(this, name, value, filename);
} catch (e) {
console.error('[PERPLEXITY-MAX-INJECTED] FormData.append failed with string:', e);
return; // Silently fail rather than breaking the page
}
}
// For objects, try to stringify them
if (typeof value === 'object') {
try {
// Try to convert to a Blob first if it has certain properties
if (value.type && (value.size !== undefined || value.length !== undefined)) {
try {
const blob = new Blob([value], { type: value.type || 'application/octet-stream' });
return originalFormDataAppend.call(this, name, blob, filename || value.name);
} catch (blobError) {
console.warn('[PERPLEXITY-MAX-INJECTED] Failed to convert to Blob:', blobError);
// Fall through to JSON stringify
}
}
// Try JSON stringify
const jsonString = JSON.stringify(value);
return originalFormDataAppend.call(this, name, jsonString, filename);
} catch (e) {
console.error('[PERPLEXITY-MAX-INJECTED] FormData.append failed with object:', e);
try {
return originalFormDataAppend.call(this, name, '[object Object]', filename);
} catch (e2) {
console.error('[PERPLEXITY-MAX-INJECTED] FormData.append fallback failed:', e2);
return; // Silently fail rather than breaking the page
}
}
}
// For everything else, convert to string
try {
const stringValue = String(value);
return originalFormDataAppend.call(this, name, stringValue, filename);
} catch (e) {
console.error('[PERPLEXITY-MAX-INJECTED] FormData.append string conversion failed:', e);
return; // Silently fail rather than breaking the page
}
};
console.log('[PERPLEXITY-MAX-INJECTED] FormData.append successfully patched');
} catch (e) {
console.error('[PERPLEXITY-MAX-INJECTED] Failed to patch FormData.append:', e);
}
// Block Facebook Pixel and other tracking scripts
try {
// Create dummy Facebook Pixel functions
window.fbq = function() {
console.log('[PERPLEXITY-MAX-INJECTED] Blocked Facebook Pixel call');
};
// Block Facebook events
window.FB = window.FB || {};
window.FB.Event = window.FB.Event || { subscribe: function() {} };
window.FB.CustomerChat = window.FB.CustomerChat || { hide: function() {}, show: function() {} };
console.log('[PERPLEXITY-MAX-INJECTED] Facebook tracking scripts blocked');
} catch (e) {
console.error('[PERPLEXITY-MAX-INJECTED] Failed to block Facebook tracking:', e);
}
// Also try to find and modify any upload limit variables in the page
function overrideUploadLimits() {
// Look for common variable names related to upload limits
const globalVars = [
'MAX_UPLOADS', 'MAX_UPLOAD_COUNT', 'UPLOAD_LIMIT',
'MAX_UPLOAD_SIZE', 'FILE_UPLOAD_LIMIT', 'UPLOAD_MAX_FILES'
];
globalVars.forEach(varName => {
if (window[varName] !== undefined) {
if (typeof window[varName] === 'number') {
window[varName] = 999999;
console.log('[PERPLEXITY-MAX-INJECTED] Overrode global upload limit:', varName);
}
}
});
// Also look for common object properties related to upload limits
const objects = ['config', 'settings', 'options', 'limits', 'uploadConfig'];
const properties = ['maxUploads', 'maxUploadSize', 'uploadLimit', 'fileLimit', 'maxFiles'];
objects.forEach(objName => {
if (window[objName]) {
properties.forEach(prop => {
if (window[objName][prop] !== undefined && typeof window[objName][prop] === 'number') {
window[objName][prop] = 999999;
console.log('[PERPLEXITY-MAX-INJECTED] Overrode upload limit:', objName + '.' + prop);
}
});
}
});
}
// Run immediately and periodically
overrideUploadLimits();
setInterval(overrideUploadLimits, 5000);
})();
`;
document.head.appendChild(script);
document.head.removeChild(script);
// Set up tracking system for upload requests
setupUploadTracking();
}
/* ─────────────────── TRACK UPLOAD REQUESTS ─────────────────── */
function setupUploadTracking() {
log('Setting up upload tracking system');
// Inject a script to track upload requests and responses
const script = document.createElement('script');
script.textContent = `
(function() {
console.log('[PERPLEXITY-MAX-INJECTED] Setting up upload tracking system');
// Track file upload events
document.addEventListener('drop', function(e) {
if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length > 0) {
console.log('[PERPLEXITY-MAX-INJECTED] File drop detected:', e.dataTransfer.files.length, 'files');
}
}, true);
// Track file input changes
document.addEventListener('change', function(e) {
if (e.target && e.target.type === 'file' && e.target.files && e.target.files.length > 0) {
console.log('[PERPLEXITY-MAX-INJECTED] File input change detected:', e.target.files.length, 'files');
}
}, true);
// Override the FormData.append method to track file uploads
const originalAppend = FormData.prototype.append;
FormData.prototype.append = function(name, value, filename) {
// Check if this is a file upload
if (value instanceof Blob || value instanceof File) {
console.log('[PERPLEXITY-MAX-INJECTED] File added to FormData:', name, filename || (value.name || 'blob'));
}
return originalAppend.call(this, name, value, filename);
};
})();
`;
document.head.appendChild(script);
document.head.removeChild(script);
}
/* ─────────────────── FORCE UNLIMITED ACCESS ─────────────────── */
function injectUnlimitedAccess() {
log('Injecting unlimited access script into page context');
// Create a script that will run in the page context
const script = document.createElement('script');
script.textContent = `
(function() {
console.log('[PERPLEXITY-MAX-INJECTED] Setting up unlimited access');
// Create a debug logger
const DEBUG = true;
const debug = (...args) => DEBUG && console.log('[PERPLEXITY-MAX-DEBUG]', ...args);
// Store original methods
const originalFetch = window.fetch;
const OriginalXMLHttpRequest = window.XMLHttpRequest;
// Create a response modifier function
function createMaxResponse(originalResponse) {
// Define max settings
const maxSettings = {
subscription_tier: 'max',
subscription_status: 'active',
subscription_source: 'stripe',
stripe_status: 'active',
gpt4_limit: 999999,
pplx_alpha_limit: 999999,
pplx_beta_limit: 999999,
pages_limit: 999999,
upload_limit: 999999,
create_limit: 999999,
article_image_upload_limit: 999999,
max_files_per_user: 999999,
max_files_per_repository: 999999,
rate_limited: false,
remaining_pro: 999999,
remaining_research: 999999,
remaining_labs: 999999,
remaining_uploads: 999999,
remaining_copilot: 999999,
remaining_search: 999999,
remaining_create: 999999
};
// Apply max settings to response
return Object.assign({}, originalResponse, maxSettings);
}
// Create a completely new XMLHttpRequest class
window.XMLHttpRequest = function() {
const xhr = new OriginalXMLHttpRequest();
// Track request details
let requestUrl = '';
let requestMethod = '';
let isImportant = false;
let isModified = false;
// Override open
const originalOpen = xhr.open;
xhr.open = function(method, url, ...args) {
requestMethod = method;
requestUrl = url;
debug('XHR Open:', method, url);
// Check if this is an important request
if (typeof url === 'string') {
if (url.includes('/rest/rate-limit/all')) {
isImportant = true;
console.log('[PERPLEXITY-MAX-INJECTED] Important XHR detected: rate-limit');
// For rate-limit requests, we'll handle them specially
const fakeResponse = {
remaining_pro: 999999,
remaining_research: 999999,
remaining_labs: 999999,
remaining_uploads: 999999,
remaining_copilot: 999999,
remaining_search: 999999,
remaining_create: 999999,
rate_limited: false
};
// Create a fake URL that will be intercepted later
url = 'data:application/json,' + encodeURIComponent(JSON.stringify(fakeResponse));
}
else if (url.includes('/rest/user/save-settings')) {
isImportant = true;
console.log('[PERPLEXITY-MAX-INJECTED] Important XHR detected: save-settings');
}
else if (url.includes('/rest/uploads/create_upload_url')) {
isImportant = true;
console.log('[PERPLEXITY-MAX-INJECTED] Important XHR detected: upload-url');
}
}
return originalOpen.call(xhr, method, url, ...args);
};
// Override send
const originalSend = xhr.send;
xhr.send = function(body) {
debug('XHR Send:', requestMethod, requestUrl, body);
// Modify request body for important requests
if (isImportant && body && typeof body === 'string') {
try {
const data = JSON.parse(body);
// For save-settings requests
if (requestUrl.includes('/rest/user/save-settings')) {
console.log('[PERPLEXITY-MAX-INJECTED] Modifying save-settings request body');
// If this is a partial update
if (data.updated_settings) {
console.log('[PERPLEXITY-MAX-INJECTED] Original updated_settings:', JSON.stringify(data.updated_settings));
data.updated_settings = Object.assign({}, data.updated_settings, {
subscription_tier: 'max',
subscription_status: 'active',
subscription_source: 'stripe',
stripe_status: 'active',
gpt4_limit: 999999,
pplx_alpha_limit: 999999,
pplx_beta_limit: 999999,
pages_limit: 999999,
upload_limit: 999999,
create_limit: 999999,
article_image_upload_limit: 999999,
max_files_per_user: 999999,
max_files_per_repository: 999999
});
console.log('[PERPLEXITY-MAX-INJECTED] Modified updated_settings:', JSON.stringify(data.updated_settings));
} else {
// Full update
Object.assign(data, {
subscription_tier: 'max',
subscription_status: 'active',
subscription_source: 'stripe',
stripe_status: 'active',
gpt4_limit: 999999,
pplx_alpha_limit: 999999,
pplx_beta_limit: 999999,
pages_limit: 999999,
upload_limit: 999999,
create_limit: 999999,
article_image_upload_limit: 999999,
max_files_per_user: 999999,
max_files_per_repository: 999999
});
}
// Update the body
body = JSON.stringify(data);
console.log('[PERPLEXITY-MAX-INJECTED] Modified request body:', body);
}
} catch (e) {
console.error('[PERPLEXITY-MAX-INJECTED] Error modifying request body:', e);
}
}
// For important requests, intercept the response
if (isImportant) {
// Override responseText getter
let originalResponseText = '';
Object.defineProperty(xhr, 'responseText', {
get: function() {
if (isModified) {
return originalResponseText;
}
const response = xhr._responseText || '';
// Only modify JSON responses
if (response && response.startsWith('{')) {
try {
const data = JSON.parse(response);
// For rate-limit requests
if (requestUrl.includes('/rest/rate-limit/all')) {
console.log('[PERPLEXITY-MAX-INJECTED] Modifying rate-limit response');
const modifiedData = {
remaining_pro: 999999,
remaining_research: 999999,
remaining_labs: 999999,
remaining_uploads: 999999,
remaining_copilot: 999999,
remaining_search: 999999,
remaining_create: 999999,
rate_limited: false
};
originalResponseText = JSON.stringify(modifiedData);
isModified = true;
return originalResponseText;
}
// For save-settings requests
if (requestUrl.includes('/rest/user/save-settings')) {
console.log('[PERPLEXITY-MAX-INJECTED] Modifying save-settings response');
const modifiedData = createMaxResponse(data);
originalResponseText = JSON.stringify(modifiedData);
isModified = true;
return originalResponseText;
}
// For upload URL creation
if (requestUrl.includes('/rest/uploads/create_upload_url')) {
console.log('[PERPLEXITY-MAX-INJECTED] Modifying upload URL response');
data.rate_limited = false;
// If fields are null, create fake ones to allow uploads
if (!data.fields || !data.s3_bucket_url) {
data.s3_bucket_url = "https://api.cloudinary.com/v1_1/pplx/image/upload";
data.s3_object_url = "https://api.cloudinary.com/v1_1/pplx/image/upload/";
data.fields = {
timestamp: Date.now(),
unique_filename: "true",
folder: "user_uploads/unlimited/max",
use_filename: "true",
public_id: "unlimited_" + Date.now(),
transformation: "t_fit2",
type: "private",
resource_type: "image",
api_key: "168798331147639",
cloud_name: "pplx",
signature: "unlimited_max_signature"
};
data.file_uuid = "unlimited_" + Date.now();
}
originalResponseText = JSON.stringify(data);
isModified = true;
return originalResponseText;
}
} catch (e) {
console.error('[PERPLEXITY-MAX-INJECTED] Error modifying response:', e);
}
}
return response;
},
set: function(value) {
xhr._responseText = value;
}
});
// Override response getter
Object.defineProperty(xhr, 'response', {
get: function() {
// If we've modified the responseText, return that for JSON responses
if (isModified && xhr.responseType === '' || xhr.responseType === 'text') {
return xhr.responseText;
}
return xhr._response;
},
set: function(value) {
xhr._response = value;
}
});
}
// Monitor readyState changes
const originalStateChange = xhr.onreadystatechange;
xhr.onreadystatechange = function(e) {
if (xhr.readyState === 4) {
debug('XHR Complete:', requestMethod, requestUrl, xhr.status);
if (isImportant) {
debug('Response for important request:', xhr.responseText);
}
}
if (originalStateChange) {
return originalStateChange.call(xhr, e);
}
};
return originalSend.call(xhr, body);
};
return xhr;
};
// Override fetch with a proxy
window.fetch = new Proxy(originalFetch, {
apply: async function(target, thisArg, args) {
// Extract URL and init
let [input, init] = args;
const url = typeof input === 'string' ? input : input.url;
const method = init?.method || 'GET';
debug('Fetch:', method, url);
// Handle rate limit requests directly
if (url && url.includes('/rest/rate-limit/all')) {
console.log('[PERPLEXITY-MAX-INJECTED] Intercepting rate-limit fetch request');
return new Response(JSON.stringify({
remaining_pro: 999999,
remaining_research: 999999,
remaining_labs: 999999,
remaining_uploads: 999999,
remaining_copilot: 999999,
remaining_search: 999999,
remaining_create: 999999,
rate_limited: false
}), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
}
// Handle save-settings requests
if (url && url.includes('/rest/user/save-settings')) {
console.log('[PERPLEXITY-MAX-INJECTED] Intercepting save-settings fetch request');
// Modify request body for PUT/POST
if ((method === 'PUT' || method === 'POST') && init && init.body) {
try {
let body = {};
if (typeof init.body === 'string') {
body = JSON.parse(init.body);
}
// If this is a partial update
if (body.updated_settings) {
console.log('[PERPLEXITY-MAX-INJECTED] Original updated_settings:', JSON.stringify(body.updated_settings));
body.updated_settings = Object.assign({}, body.updated_settings, {
subscription_tier: 'max',
subscription_status: 'active',
subscription_source: 'stripe',
stripe_status: 'active',
gpt4_limit: 999999,
pplx_alpha_limit: 999999,
pplx_beta_limit: 999999,
pages_limit: 999999,
upload_limit: 999999,
create_limit: 999999,
article_image_upload_limit: 999999,
max_files_per_user: 999999,
max_files_per_repository: 999999
});
console.log('[PERPLEXITY-MAX-INJECTED] Modified updated_settings:', JSON.stringify(body.updated_settings));
} else {
// Full update
Object.assign(body, {
subscription_tier: 'max',
subscription_status: 'active',
subscription_source: 'stripe',
stripe_status: 'active',
gpt4_limit: 999999,
pplx_alpha_limit: 999999,
pplx_beta_limit: 999999,
pages_limit: 999999,
upload_limit: 999999,
create_limit: 999999,
article_image_upload_limit: 999999,
max_files_per_user: 999999,
max_files_per_repository: 999999
});
}
// Update the request
init.body = JSON.stringify(body);
console.log('[PERPLEXITY-MAX-INJECTED] Modified save-settings request body:', init.body);
} catch (e) {
console.error('[PERPLEXITY-MAX-INJECTED] Error modifying save-settings request:', e);
}
}
// Make the modified request
const response = await target.apply(thisArg, [input, init]);
// Clone and modify the response
try {
const clonedResponse = response.clone();
const data = await clonedResponse.json();
console.log('[PERPLEXITY-MAX-INJECTED] Original save-settings response:', JSON.stringify(data));
// Apply max settings
const maxData = createMaxResponse(data);
console.log('[PERPLEXITY-MAX-INJECTED] Modified save-settings response:', JSON.stringify(maxData));
return new Response(JSON.stringify(maxData), {
status: response.status,
statusText: response.statusText,
headers: response.headers
});
} catch (e) {
console.error('[PERPLEXITY-MAX-INJECTED] Error modifying save-settings response:', e);
return response;
}
}
// Handle upload URL creation
if (url && url.includes('/rest/uploads/create_upload_url')) {
console.log('[PERPLEXITY-MAX-INJECTED] Intercepting upload URL creation fetch request');
// Make the original request
const response = await target.apply(thisArg, args);
try {
// Clone and get the data
const clonedResponse = response.clone();
const data = await clonedResponse.json();
console.log('[PERPLEXITY-MAX-INJECTED] Original upload URL response:', JSON.stringify(data));
// Always set rate_limited to false
data.rate_limited = false;
// If fields are null, create fake ones to allow uploads
if (!data.fields || !data.s3_bucket_url) {
data.s3_bucket_url = "https://api.cloudinary.com/v1_1/pplx/image/upload";
data.s3_object_url = "https://api.cloudinary.com/v1_1/pplx/image/upload/";
data.fields = {
timestamp: Date.now(),
unique_filename: "true",
folder: "user_uploads/unlimited/max",
use_filename: "true",
public_id: "unlimited_" + Date.now(),
transformation: "t_fit2",
type: "private",
resource_type: "image",
api_key: "168798331147639",
cloud_name: "pplx",
signature: "unlimited_max_signature"
};
data.file_uuid = "unlimited_" + Date.now();
}
console.log('[PERPLEXITY-MAX-INJECTED] Modified upload URL response:', JSON.stringify(data));
return new Response(JSON.stringify(data), {
status: response.status,
statusText: response.statusText,
headers: response.headers
});
} catch (e) {
console.error('[PERPLEXITY-MAX-INJECTED] Error modifying upload URL response:', e);
return response;
}
}
// For all other requests, proceed normally
const response = await target.apply(thisArg, args);
// For JSON responses, check if we need to modify them
if (url && (
url.includes('/api/user') ||
url.includes('/api/subscription') ||
url.includes('/api/auth/session') ||
url.includes('/rest/user/')
)) {
try {
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
const clonedResponse = response.clone();
const data = await clonedResponse.json();
// Check if this response has any fields we want to modify
const keysToCheck = [
'subscription_tier', 'subscription_status', 'subscription_source',
'remaining_pro', 'remaining_research', 'remaining_labs',
'upload_limit', 'rate_limited', 'gpt4_limit'
];
let shouldModify = false;
for (const key of keysToCheck) {
if (key in data) {
shouldModify = true;
break;
}
}
if (shouldModify) {
console.log('[PERPLEXITY-MAX-INJECTED] Modifying JSON response for:', url);
const maxData = createMaxResponse(data);
return new Response(JSON.stringify(maxData), {
status: response.status,
statusText: response.statusText,
headers: response.headers
});
}
}
} catch (e) {
// Not JSON or other error
}
}
return response;
}
});
// Override Object.defineProperty to catch React state updates
const originalDefineProperty = Object.defineProperty;
Object.defineProperty = function(obj, prop, descriptor) {
// Check if this is a subscription-related property
if (prop === 'subscription_tier' || prop === 'subscription_status' ||
prop === 'is_pro' || prop === 'is_max' || prop === 'has_max') {
// Force MAX values
if (descriptor && descriptor.value !== undefined) {
if (prop === 'subscription_tier') descriptor.value = 'max';
if (prop === 'subscription_status') descriptor.value = 'active';
if (prop === 'is_pro' || prop === 'is_max' || prop === 'has_max') descriptor.value = true;
}
// For getters/setters
if (descriptor && descriptor.get) {
const originalGetter = descriptor.get;
descriptor.get = function() {
let value = originalGetter.call(this);
if (prop === 'subscription_tier') value = 'max';
if (prop === 'subscription_status') value = 'active';
if (prop === 'is_pro' || prop === 'is_max' || prop === 'has_max') value = true;
return value;
};
}
}
// Apply the original defineProperty
return originalDefineProperty.call(this, obj, prop, descriptor);
};
// Create a global helper function to force MAX subscription
window.forcePplxMax = function() {
console.log('[PERPLEXITY-MAX-INJECTED] Manually forcing MAX subscription');
// Try to find React state in the DOM
const reactRoots = document.querySelectorAll('[data-reactroot]');
reactRoots.forEach(root => {
console.log('[PERPLEXITY-MAX-INJECTED] Found React root:', root);
});
// Force update by triggering resize and visibility events
window.dispatchEvent(new Event('resize'));
document.dispatchEvent(new Event('visibilitychange'));
// Try to modify localStorage
try {
const storageKeys = ['perplexity-user', 'perplexity-subscription', 'perplexity-usage'];
storageKeys.forEach(key => {
const data = localStorage.getItem(key);
if (data) {
try {
const parsed = JSON.parse(data);
if (key === 'perplexity-user') {
parsed.subscription_tier = 'max';
parsed.subscription_status = 'active';
parsed.subscription_source = 'stripe';
parsed.is_max = true;
parsed.has_max = true;
parsed.is_pro = true;
}
if (key === 'perplexity-subscription') {
parsed.tier = 'max';
parsed.status = 'active';
parsed.source = 'stripe';
}
if (key === 'perplexity-usage') {
if (parsed.remaining_pro !== undefined) parsed.remaining_pro = 999999;
if (parsed.remaining_research !== undefined) parsed.remaining_research = 999999;
if (parsed.remaining_labs !== undefined) parsed.remaining_labs = 999999;
}
localStorage.setItem(key, JSON.stringify(parsed));
console.log('[PERPLEXITY-MAX-INJECTED] Modified localStorage:', key);
} catch (e) {
console.error('[PERPLEXITY-MAX-INJECTED] Error parsing localStorage:', e);
}
}
});
} catch (e) {
console.error('[PERPLEXITY-MAX-INJECTED] Error accessing localStorage:', e);
}
return "MAX subscription forced!";
};
// Run forcePplxMax periodically
setInterval(window.forcePplxMax, 5000);
console.log('[PERPLEXITY-MAX-INJECTED] Unlimited access setup complete. Use window.forcePplxMax() to force MAX subscription manually.');
})();
`;
// Add the script to the page
document.head.appendChild(script);
document.head.removeChild(script);
log('Unlimited access script injected with advanced monitoring');
}
/* ─────────────────── INITIALIZATION ─────────────────── */
function initializeScript() {
log('Initializing Perplexity MAX script');
// Add a mutation observer to handle dynamically added elements
const observer = new MutationObserver((mutations) => {
// Check if body exists
if (document.body) {
// Run our main functions
forceMaxSubscription(); // Force MAX subscription when DOM changes
enableUnlimitedUploads(); // Enable unlimited uploads
blockAnalyticsScripts(); // Block analytics and tracking
injectUnlimitedAccess(); // Inject unlimited access script
// Disconnect after initial setup to avoid performance issues
observer.disconnect();
// Add status indicator
setTimeout(addStatusIndicator, 2000);
// Set up periodic checks
setInterval(forceMaxSubscription, 10000); // Periodically force MAX subscription
}
});
// Start observing
observer.observe(document.documentElement, {
childList: true,
subtree: true
});
// If body already exists, run immediately
if (document.body) {
observer.disconnect();
forceMaxSubscription(); // Force MAX subscription immediately
enableUnlimitedUploads(); // Enable unlimited uploads
blockAnalyticsScripts(); // Block analytics and tracking
injectUnlimitedAccess(); // Inject unlimited access script
setTimeout(addStatusIndicator, 2000);
setInterval(forceMaxSubscription, 10000); // Periodically force MAX subscription
}
}
// Start the script
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeScript);
} else {
initializeScript();
}
})();