Commit 0faffd28 authored by Stefy Spora's avatar Stefy Spora

Fix MetaMask detection and improve error handling

- Fix unsafe window.web3.currentProvider access causing TypeError
- Add proper null checks for window.web3.currentProvider access
- Improve MetaMask detection timing with retry mechanism
- Add MutationObserver to watch for MetaMask injection
- Enhance error handling and logging for MetaMask detection
- Confirm MetaMask detection correctly checks active tab's window object
- Remove backup files from repository
parent e799d616
......@@ -492,7 +492,9 @@
// Check for MetaMask specifically
if (window['ethereum']) {
ethereumProvider = window['ethereum'];
} else if (window['web3'] && window['web3'].currentProvider) {
} else if (window['web3'] &&
window['web3'] !== null &&
typeof window['web3'].currentProvider !== 'undefined') {
ethereumProvider = window['web3'].currentProvider;
}
}
......
......@@ -22,29 +22,162 @@
(function() {
'use strict';
// Function to check if MetaMask is available
let metamaskDetected = false;
let metamaskProvider = null;
let detectionAttempts = 0;
const maxAttempts = 10;
const checkInterval = 500; // Check every 500ms
// Function to check if MetaMask is available with retry mechanism
function isMetaMaskAvailable() {
const available = typeof window.ethereum !== 'undefined' ||
(typeof window.web3 !== 'undefined' && typeof window.web3.currentProvider !== 'undefined');
console.log('MetaMask detector: MetaMask available:', available);
return available;
// First check if we already detected it
if (metamaskDetected) {
console.log('MetaMask detector: MetaMask already detected');
return true;
}
// Check current state with proper null checks
const ethereumAvailable = typeof window.ethereum !== 'undefined';
const web3Available = typeof window.web3 !== 'undefined' &&
window.web3 !== null &&
typeof window.web3.currentProvider !== 'undefined';
const available = ethereumAvailable || web3Available;
if (available) {
metamaskDetected = true;
// Set provider with proper null checks
if (ethereumAvailable) {
metamaskProvider = window.ethereum;
} else if (web3Available) {
metamaskProvider = window.web3.currentProvider;
}
console.log('MetaMask detector: MetaMask detected on attempt', detectionAttempts);
return true;
}
// If not available and we haven't exceeded max attempts, schedule another check
if (detectionAttempts < maxAttempts) {
detectionAttempts++;
console.log('MetaMask detector: MetaMask not found, attempt', detectionAttempts, 'of', maxAttempts);
setTimeout(() => isMetaMaskAvailable(), checkInterval);
} else {
console.log('MetaMask detector: MetaMask not detected after', maxAttempts, 'attempts');
}
return false;
}
// Function to get MetaMask provider
function getMetaMaskProvider() {
const provider = window.ethereum || window.web3.currentProvider;
if (metamaskProvider) {
return metamaskProvider;
}
// Try to get it directly with proper null checks
let provider = null;
if (typeof window.ethereum !== 'undefined') {
provider = window.ethereum;
} else if (typeof window.web3 !== 'undefined' &&
window.web3 !== null &&
typeof window.web3.currentProvider !== 'undefined') {
provider = window.web3.currentProvider;
}
if (provider) {
metamaskProvider = provider;
}
console.log('MetaMask detector: Provider found:', !!provider);
return provider;
}
// Set up MutationObserver to watch for MetaMask injection
function setupMetaMaskWatcher() {
const observer = new MutationObserver((mutations) => {
// Check if ethereum was added to window
if (typeof window.ethereum !== 'undefined' && !metamaskDetected) {
console.log('MetaMask detector: ethereum object detected via MutationObserver');
metamaskDetected = true;
metamaskProvider = window.ethereum;
}
// Check if web3 was added to window with proper null checks
if (typeof window.web3 !== 'undefined' &&
window.web3 !== null &&
typeof window.web3.currentProvider !== 'undefined' &&
!metamaskDetected) {
console.log('MetaMask detector: web3 object detected via MutationObserver');
metamaskDetected = true;
metamaskProvider = window.web3.currentProvider;
}
});
// Start observing
observer.observe(document, {
childList: true,
subtree: true
});
console.log('MetaMask detector: MutationObserver set up to watch for MetaMask injection');
}
// Initialize detection
function initializeDetection() {
console.log('MetaMask detector: Initializing detection...');
// Start immediate check
isMetaMaskAvailable();
// Set up watcher for dynamic injection
setupMetaMaskWatcher();
// Also listen for ethereum#initialized event (MetaMask specific)
if (typeof window.ethereum !== 'undefined') {
window.ethereum.on('connect', () => {
console.log('MetaMask detector: ethereum connect event received');
metamaskDetected = true;
metamaskProvider = window.ethereum;
});
}
}
// Initialize on page load
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeDetection);
} else {
initializeDetection();
}
// Listen for messages from popup
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
console.log('MetaMask detector: Received message:', request.action);
console.log('MetaMask detector: Received message:', request.action, 'from:', sender);
if (request.action === 'ping') {
console.log('MetaMask detector: Received ping, responding...');
sendResponse({ pong: true, source: 'metamask-detector' });
} else if (request.action === 'checkMetaMask') {
console.log('MetaMask detector: Processing checkMetaMask request');
if (request.action === 'checkMetaMask') {
// Immediate check for already detected MetaMask
if (metamaskDetected && metamaskProvider) {
console.log('MetaMask detector: MetaMask already detected, responding immediately');
const response = {
available: true,
provider: true,
source: 'metamask-detector'
};
sendResponse(response);
return;
}
// Force a fresh check
const available = isMetaMaskAvailable();
const response = {
available: isMetaMaskAvailable(),
provider: getMetaMaskProvider() ? true : false
available: available,
provider: getMetaMaskProvider() ? true : false,
source: 'metamask-detector'
};
console.log('MetaMask detector: Sending response:', response);
sendResponse(response);
......@@ -72,6 +205,10 @@
const ethereumProvider = getMetaMaskProvider();
if (!ethereumProvider) {
return { success: false, message: 'MetaMask provider not available.' };
}
// Request account access
let accounts;
try {
......
......@@ -286,60 +286,97 @@ document.addEventListener('DOMContentLoaded', function() {
}, 5000);
}
// Function to check if MetaMask is available via content script
// Function to check if MetaMask is available via content script with improved timing
async function checkMetaMaskAvailable() {
try {
// Get the current active tab
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab || !tab.id) {
console.log('Popup: No active tab found');
return false;
}
console.log('Popup: Checking MetaMask on tab', tab.id, 'URL:', tab.url);
// First, let's check if any content script is responding at all
try {
console.log('Popup: Testing content script connectivity...');
const testResponse = await chrome.tabs.sendMessage(tab.id, { action: 'ping' });
console.log('Popup: Content script ping response:', testResponse);
} catch (pingError) {
console.log('Popup: No content script responded to ping, this might be the issue');
}
// Check if we can communicate with content script
try {
console.log('Popup: Sending checkMetaMask message to tab', tab.id);
const response = await chrome.tabs.sendMessage(tab.id, { action: 'checkMetaMask' });
console.log('Popup: Received MetaMask check response:', response);
console.log('Popup: Received MetaMask check response:', response, 'from source:', response.source);
return response.available;
} catch (messageError) {
console.log('Popup: Content script not available, trying to inject it:', messageError);
console.log('Popup: Content script not available, trying to inject it:', messageError.message);
// Try to inject the MetaMask detector content script
try {
console.log('Popup: Injecting MetaMask detector script...');
await chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ['metamask-detector.js']
});
// Wait a moment for the script to load
await new Promise(resolve => setTimeout(resolve, 500));
// Wait longer for the script to initialize and detect MetaMask
console.log('Popup: Waiting for MetaMask detector to initialize...');
await new Promise(resolve => setTimeout(resolve, 1500));
// Try again to communicate
console.log('Popup: Retrying communication with injected script...');
const response = await chrome.tabs.sendMessage(tab.id, { action: 'checkMetaMask' });
console.log('Popup: Received MetaMask check response after injection:', response);
return response.available;
} catch (injectError) {
console.log('Could not inject content script, using executeScript fallback:', injectError);
console.log('Popup: Could not inject content script:', injectError.message);
// Fallback: use executeScript to check MetaMask directly
const results = await chrome.scripting.executeScript({
target: { tabId: tab.id },
function: () => {
return typeof window.ethereum !== 'undefined' ||
(typeof window.web3 !== 'undefined' && typeof window.web3.currentProvider !== 'undefined');
}
});
// Enhanced fallback: try to inject and communicate with MetaMask detector again
console.log('Popup: Attempting enhanced fallback with longer wait...');
// Wait longer and try one more time with extended timeout
console.log('Popup: Waiting 3 seconds for MetaMask detector initialization...');
await new Promise(resolve => setTimeout(resolve, 3000));
try {
console.log('Popup: Final attempt to communicate with MetaMask detector...');
// Add a timeout to the message
const finalResponse = await new Promise((resolve, reject) => {
chrome.tabs.sendMessage(tab.id, { action: 'checkMetaMask' }, (response) => {
if (chrome.runtime.lastError) {
reject(new Error(chrome.runtime.lastError.message));
} else {
resolve(response);
}
});
// Timeout after 5 seconds
setTimeout(() => {
reject(new Error('MetaMask detector response timeout'));
}, 5000);
});
return results[0].result;
console.log('Popup: Final MetaMask detection result:', finalResponse);
return finalResponse.available;
} catch (finalError) {
console.log('Popup: Final attempt failed:', finalError.message);
return false;
}
}
}
} catch (error) {
console.error('Error checking MetaMask availability:', error);
console.error('Popup: Error checking MetaMask availability:', error);
return false;
}
}
// Function to handle Web3 donation via content script
// Function to handle Web3 donation via content script with improved timing
async function executeWeb3Donation() {
try {
// Get the current active tab
......@@ -349,6 +386,8 @@ document.addEventListener('DOMContentLoaded', function() {
return { success: false, message: 'No active tab found.' };
}
console.log('Popup: Executing Web3 donation on tab', tab.id);
// Try to send message to MetaMask detector content script
try {
console.log('Popup: Sending executeWeb3Donation message to tab', tab.id);
......@@ -356,145 +395,47 @@ document.addEventListener('DOMContentLoaded', function() {
console.log('Popup: Received donation response:', response);
return response;
} catch (messageError) {
console.log('Popup: Content script not available for donation, trying to inject it:', messageError);
console.log('Popup: Content script not available for donation, trying to inject it:', messageError.message);
// Try to inject the MetaMask detector content script
try {
console.log('Popup: Injecting MetaMask detector script for donation...');
await chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ['metamask-detector.js']
});
// Wait a moment for the script to load
await new Promise(resolve => setTimeout(resolve, 500));
// Wait longer for the script to initialize and detect MetaMask
console.log('Popup: Waiting for MetaMask detector to initialize for donation...');
await new Promise(resolve => setTimeout(resolve, 2000));
// Try again to send the donation message
console.log('Popup: Retrying donation message after injection...');
const response = await chrome.tabs.sendMessage(tab.id, { action: 'executeWeb3Donation' });
console.log('Popup: Received donation response after injection:', response);
return response;
} catch (injectError) {
console.log('Could not inject content script for donation, using executeScript fallback:', injectError);
// Fallback: use executeScript to handle the donation directly
const results = await chrome.scripting.executeScript({
target: { tabId: tab.id },
function: async () => {
try {
const ethereumProvider = window.ethereum || window.web3.currentProvider;
// Request account access
let accounts;
try {
accounts = await ethereumProvider.request({
method: 'eth_requestAccounts'
});
} catch (requestError) {
// Fallback for legacy providers
if (ethereumProvider.enable) {
accounts = await ethereumProvider.enable();
} else {
throw requestError;
}
}
if (!accounts || accounts.length === 0) {
return { success: false, message: 'No accounts found. Please unlock your Web3 wallet.' };
}
console.log('Popup: Could not inject content script for donation:', injectError.message);
const donationAddress = '0xdA6dAb526515b5cb556d20269207D43fcc760E51';
// Prepare transaction parameters (user can modify amount in wallet)
const transactionParameters = {
to: donationAddress,
from: accounts[0],
value: '0x16345785D8A0000', // 0.1 ETH in wei (default amount, user can change)
gas: '0x5208', // 21000 gas limit for simple transfer
};
// Send transaction
let txHash;
try {
txHash = await ethereumProvider.request({
method: 'eth_sendTransaction',
params: [transactionParameters],
});
} catch (transactionError) {
// Handle user rejection specifically
if (transactionError.code === 4001 ||
(transactionError.message && transactionError.message.includes('User denied transaction signature'))) {
return { success: false, message: 'Transaction cancelled by user.' };
}
throw transactionError;
}
return { success: true, message: `Transaction sent! Hash: ${txHash.substring(0, 10)}...` };
} catch (error) {
console.error('Web3 donation error:', error);
if (error.code === 4001 || (error.message && error.message.includes('User denied'))) {
return { success: false, message: 'Transaction cancelled by user.' };
} else if (error.code === -32602) {
return { success: false, message: 'Invalid transaction parameters.' };
} else {
return { success: false, message: `Transaction failed: ${error.message || error.toString()}` };
}
}
}
});
return results[0].result;
}
}
// Request account access
let accounts;
try {
accounts = await ethereumProvider.request({
method: 'eth_requestAccounts'
});
} catch (requestError) {
// Fallback for legacy providers
if (ethereumProvider.enable) {
accounts = await ethereumProvider.enable();
} else {
throw requestError; // Re-throw if no fallback available
}
}
if (!accounts || accounts.length === 0) {
return { success: false, message: 'No accounts found. Please unlock your Web3 wallet.' };
}
const donationAddress = '0xdA6dAb526515b5cb556d20269207D43fcc760E51';
// Enhanced fallback: try to inject and communicate with MetaMask detector again
console.log('Popup: Attempting enhanced fallback for donation with longer wait...');
// Prepare transaction parameters (user can modify amount in wallet)
const transactionParameters = {
to: donationAddress,
from: accounts[0],
value: '0x16345785D8A0000', // 0.1 ETH in wei (default amount, user can change)
gas: '0x5208', // 21000 gas limit for simple transfer
};
// Wait longer and try one more time
await new Promise(resolve => setTimeout(resolve, 2500));
// Send transaction
let txHash;
try {
txHash = await ethereumProvider.request({
method: 'eth_sendTransaction',
params: [transactionParameters],
});
} catch (transactionError) {
// Handle user rejection specifically
if (transactionError.code === 4001 ||
(transactionError.message && transactionError.message.includes('User denied transaction signature'))) {
return { success: false, message: 'Transaction cancelled by user.' };
try {
console.log('Popup: Final attempt to communicate with MetaMask detector for donation...');
const finalResponse = await chrome.tabs.sendMessage(tab.id, { action: 'executeWeb3Donation' });
console.log('Popup: Final donation result:', finalResponse);
return finalResponse;
} catch (finalError) {
console.log('Popup: Final donation attempt failed:', finalError.message);
return { success: false, message: 'Failed to execute donation. Please ensure MetaMask is installed and unlocked.' };
}
}
throw transactionError; // Re-throw other errors
}
return { success: true, message: `Transaction sent! Hash: ${txHash.substring(0, 10)}...` };
} catch (error) {
console.error('Web3 donation error:', error);
console.error('Popup: Web3 donation error:', error);
if (error.code === 4001 || (error.message && error.message.includes('User denied'))) {
return { success: false, message: 'Transaction cancelled by user.' };
......
/*
* FetLife Privacy Helper - Popup Script
* Copyright (C) 2025 Stefy Spora <stefy@sexhack.me> - sexhack.me
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
document.addEventListener('DOMContentLoaded', function() {
const privacyBtn = document.getElementById('privaxy-btn');
const runAllBtn = document.getElementById('run-all-btn');
const stopBtn = document.getElementById('stop-btn');
const statusDiv = document.getElementById('status');
const progressDiv = document.getElementById('progress');
let isRunningBulk = false;
let shouldStop = false;
function showStatus(message, type = 'info') {
statusDiv.textContent = message;
statusDiv.className = `status ${type}`;
statusDiv.style.display = 'block';
// Hide status after 3 seconds unless it's an error or we're running bulk
if (type !== 'error' && !isRunningBulk) {
setTimeout(() => {
statusDiv.style.display = 'none';
}, 3000);
}
}
function showProgress(message) {
progressDiv.textContent = message;
progressDiv.style.display = 'block';
}
function hideProgress() {
progressDiv.style.display = 'none';
}
function disableButton() {
privacyBtn.disabled = true;
privacyBtn.textContent = 'Processing...';
}
function enableButton() {
privacyBtn.disabled = false;
privacyBtn.textContent = 'Privaxy';
}
function disableBulkButtons() {
runAllBtn.disabled = true;
runAllBtn.textContent = 'Running...';
privacyBtn.disabled = true;
stopBtn.style.display = 'block';
stopBtn.disabled = false;
}
function enableBulkButtons() {
runAllBtn.disabled = false;
runAllBtn.textContent = 'Run on All Media';
privacyBtn.disabled = false;
stopBtn.style.display = 'none';
isRunningBulk = false;
shouldStop = false;
}
privacyBtn.addEventListener('click', async function() {
try {
disableButton();
showStatus('Checking current tab...', 'info');
// Get the current active tab
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab.url.includes('fetlife.com')) {
showStatus('Please navigate to FetLife first', 'error');
enableButton();
return;
}
showStatus('Executing privacy update...', 'info');
// Execute the content script function
const results = await chrome.scripting.executeScript({
target: { tabId: tab.id },
function: executePrivacyUpdate
});
const result = results[0].result;
if (result.success) {
showStatus(result.message, 'success');
} else {
showStatus(result.message, 'error');
}
} catch (error) {
console.error('Error:', error);
showStatus('An error occurred: ' + error.message, 'error');
} finally {
enableButton();
}
});
runAllBtn.addEventListener('click', async function() {
try {
isRunningBulk = true;
shouldStop = false;
disableBulkButtons();
showStatus('Starting bulk privacy update...', 'info');
showProgress('Initializing...');
// Get the current active tab
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab.url.includes('fetlife.com')) {
showStatus('Please navigate to FetLife first', 'error');
enableBulkButtons();
hideProgress();
return;
}
let pageCount = 0;
let successCount = 0;
let errorCount = 0;
// Start the bulk processing loop
while (!shouldStop) {
pageCount++;
showProgress(`Processing page ${pageCount}... (${successCount} success, ${errorCount} errors)`);
try {
// Execute privacy update on current page
const results = await chrome.scripting.executeScript({
target: { tabId: tab.id },
function: executePrivacyUpdate
});
const result = results[0].result;
if (result.success) {
successCount++;
} else {
errorCount++;
console.log(`Page ${pageCount} error:`, result.message);
}
// Wait a moment before checking for next link
await new Promise(resolve => setTimeout(resolve, 1000));
if (shouldStop) break;
// Check for and click next link
const nextResults = await chrome.scripting.executeScript({
target: { tabId: tab.id },
function: findAndClickNext
});
const nextResult = nextResults[0].result;
if (!nextResult.hasNext) {
showStatus(`Completed! Processed ${pageCount} pages (${successCount} success, ${errorCount} errors)`, 'success');
break;
}
// Wait for page to load
await new Promise(resolve => setTimeout(resolve, 2000));
} catch (error) {
errorCount++;
console.error(`Error on page ${pageCount}:`, error);
// Try to continue to next page even if current page failed
try {
const nextResults = await chrome.scripting.executeScript({
target: { tabId: tab.id },
function: findAndClickNext
});
if (!nextResults[0].result.hasNext) {
break;
}
await new Promise(resolve => setTimeout(resolve, 2000));
} catch (nextError) {
console.error('Failed to navigate to next page:', nextError);
break;
}
}
}
if (shouldStop) {
showStatus(`Stopped by user. Processed ${pageCount} pages (${successCount} success, ${errorCount} errors)`, 'info');
}
} catch (error) {
console.error('Bulk processing error:', error);
showStatus('Bulk processing failed: ' + error.message, 'error');
} finally {
enableBulkButtons();
hideProgress();
}
});
stopBtn.addEventListener('click', function() {
shouldStop = true;
stopBtn.disabled = true;
stopBtn.textContent = 'Stopping...';
showProgress('Stopping after current page...');
});
// Web3/MetaMask donation functionality
const web3DonateBtn = document.getElementById('web3-donate-btn');
const web3Status = document.getElementById('web3-status');
function showWeb3Status(message, isError = false) {
web3Status.textContent = message;
web3Status.style.color = isError ? '#f44336' : '#4CAF50';
web3Status.style.display = 'block';
setTimeout(() => {
web3Status.style.display = 'none';
}, 5000);
}
// Function to check if MetaMask is available in popup context
function checkMetaMaskAvailable() {
return typeof window.ethereum !== 'undefined' ||
(typeof window.web3 !== 'undefined' && typeof window.web3.currentProvider !== 'undefined');
}
// Function to handle Web3 donation directly in popup context
async function executeWeb3Donation() {
try {
// Check if MetaMask is installed
if (!checkMetaMaskAvailable()) {
return { success: false, message: 'MetaMask not detected. Please install MetaMask extension.' };
}
const ethereumProvider = window.ethereum || window.web3.currentProvider;
// Request account access
let accounts;
try {
accounts = await ethereumProvider.request({
method: 'eth_requestAccounts'
});
} catch (requestError) {
// Fallback for legacy providers
if (ethereumProvider.enable) {
accounts = await ethereumProvider.enable();
} else {
throw requestError; // Re-throw if no fallback available
}
}
if (!accounts || accounts.length === 0) {
return { success: false, message: 'No accounts found. Please unlock your Web3 wallet.' };
}
const donationAddress = '0xdA6dAb526515b5cb556d20269207D43fcc760E51';
// Prepare transaction parameters (user can modify amount in wallet)
const transactionParameters = {
to: donationAddress,
from: accounts[0],
value: '0x16345785D8A0000', // 0.1 ETH in wei (default amount, user can change)
gas: '0x5208', // 21000 gas limit for simple transfer
};
// Send transaction
let txHash;
try {
txHash = await ethereumProvider.request({
method: 'eth_sendTransaction',
params: [transactionParameters],
});
} catch (transactionError) {
// Handle user rejection specifically
if (transactionError.code === 4001 ||
(transactionError.message && transactionError.message.includes('User denied transaction signature'))) {
return { success: false, message: 'Transaction cancelled by user.' };
}
throw transactionError; // Re-throw other errors
}
return { success: true, message: `Transaction sent! Hash: ${txHash.substring(0, 10)}...` };
} catch (error) {
console.error('Web3 donation error:', error);
if (error.code === 4001 || (error.message && error.message.includes('User denied'))) {
return { success: false, message: 'Transaction cancelled by user.' };
} else if (error.code === -32602) {
return { success: false, message: 'Invalid transaction parameters.' };
} else {
return { success: false, message: `Transaction failed: ${error.message || error.toString()}` };
}
}
}
web3DonateBtn.addEventListener('click', async function() {
try {
web3DonateBtn.disabled = true;
web3DonateBtn.textContent = '🔄 Connecting...';
// Get the current active tab
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
// Check if we're on a FetLife page
if (!tab.url || !tab.url.includes('fetlife.com')) {
showWeb3Status('Please navigate to a FetLife page first.', true);
return;
}
// Execute Web3 donation directly in popup context
const result = await executeWeb3Donation();
showWeb3Status(result.message, !result.success);
} catch (error) {
console.error('Web3 donation error:', error);
// Handle different types of errors
if (error.message && error.message.includes('Could not establish connection')) {
showWeb3Status('Extension not loaded properly. Try refreshing the page.', true);
} else if (error.message && error.message.includes('No tab')) {
showWeb3Status('No active tab found.', true);
} else {
showWeb3Status('Failed to initiate donation: ' + (error.message || 'Unknown error'), true);
}
} finally {
web3DonateBtn.disabled = false;
web3DonateBtn.textContent = '🦊 Donate with MetaMask';
}
});
});
// Function to find and click the next link
function findAndClickNext() {
try {
// Look for the "next »" link
const nextLinks = Array.from(document.querySelectorAll('a')).filter(link =>
link.textContent.trim().includes('next »') ||
link.textContent.trim().includes('next ›') ||
link.textContent.trim() === 'next'
);
if (nextLinks.length === 0) {
return { hasNext: false, message: 'No next link found' };
}
// Click the first next link found
nextLinks[0].click();
return { hasNext: true, message: 'Clicked next link' };
} catch (error) {
return { hasNext: false, message: 'Error finding next link: ' + error.message };
}
}
// This function will be injected into the page - XHR-based approach
function executePrivacyUpdate() {
// Function to extract media ID and type from URL
function getMediaInfo() {
const url = window.location.href;
// Handle both direct URLs and username-based URLs
// Direct: https://fetlife.com/videos/123 or https://fetlife.com/pictures/123
// Username: https://fetlife.com/username/videos/123 or https://fetlife.com/username/pictures/123
const videoMatch = url.match(/https:\/\/fetlife\.com\/(?:[^\/]+\/)?videos\/(\d+)/);
const pictureMatch = url.match(/https:\/\/fetlife\.com\/(?:[^\/]+\/)?pictures\/(\d+)/);
if (videoMatch) {
return { type: 'video', id: videoMatch[1] };
} else if (pictureMatch) {
return { type: 'picture', id: pictureMatch[1] };
}
return null;
}
// Function to get CSRF token from the page
function getCSRFToken() {
const metaTag = document.querySelector('meta[name="csrf-token"]');
if (metaTag) {
return metaTag.getAttribute('content');
}
const csrfInput = document.querySelector('input[name="authenticity_token"]');
if (csrfInput) {
return csrfInput.value;
}
return null;
}
// Main XHR-based privacy update
return new Promise(async (resolve) => {
try {
console.log('Starting XHR-based privacy update...');
// Step 1: Detect media type and ID from URL
const mediaInfo = getMediaInfo();
if (!mediaInfo) {
resolve({ success: false, message: 'Not on a video or picture page. Please navigate to a FetLife video or picture URL.' });
return;
}
console.log(`Detected ${mediaInfo.type} with ID: ${mediaInfo.id}`);
// Step 2: Get CSRF token
const csrfToken = getCSRFToken();
if (!csrfToken) {
resolve({ success: false, message: 'Could not find CSRF token. Please refresh the page and try again.' });
return;
}
// Step 3: Prepare payload based on media type
let payload;
if (mediaInfo.type === 'video') {
payload = {
video: {
title: "",
description: "",
only_friends: true,
tag_names: [],
user_tag_ids: []
},
render_flash: true
};
} else {
payload = {
picture: {
caption: "",
content_privacy: "only_friends",
tag_names: [],
user_tag_ids: []
},
render_flash: true
};
}
console.log('Sending XHR request with payload:', payload);
// Step 4: Send XHR PUT request
const response = await fetch(`https://fetlife.com/${mediaInfo.type}s/${mediaInfo.id}`, {
method: 'PUT',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json, text/javascript, */*; q=0.01',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-Token': csrfToken
},
body: JSON.stringify(payload)
});
if (!response.ok) {
resolve({ success: false, message: `XHR request failed: ${response.status} ${response.statusText}` });
return;
}
const result = await response.json();
console.log('XHR response:', result);
resolve({
success: true,
message: `${mediaInfo.type.charAt(0).toUpperCase() + mediaInfo.type.slice(1)} privacy updated to "only friends" successfully!`
});
} catch (error) {
console.error('XHR privacy update failed:', error);
resolve({ success: false, message: error.message });
}
});
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment