Commit 80f9de83 authored by Stefy Spora's avatar Stefy Spora

Hide MetaMask donation button when no compatible wallet is detected

- Hide donation button by default in popup.html
- Show 'Checking for Web3 wallet...' message initially
- Check wallet availability on popup load
- Only show donation button when MetaMask/wallet is detected
- Display helpful message when no wallet is found
- Handle detection errors gracefully
- Improve user experience by not showing unavailable options
parent 75594e4b
......@@ -104,10 +104,10 @@
</div>
<div style="margin-bottom: 6px;">
<button id="web3-donate-btn" style="background: linear-gradient(45deg, #f6851b, #e2761b); color: white; border: none; padding: 4px 8px; border-radius: 4px; font-size: 10px; cursor: pointer; margin-bottom: 4px;">
<button id="web3-donate-btn" style="background: linear-gradient(45deg, #f6851b, #e2761b); color: white; border: none; padding: 4px 8px; border-radius: 4px; font-size: 10px; cursor: pointer; margin-bottom: 4px; display: none;">
🦊 Donate with MetaMask
</button>
<div id="web3-status" style="font-size: 9px; color: #666; display: none;"></div>
<div id="web3-status" style="font-size: 9px; color: #666; display: block;">Checking for Web3 wallet...</div>
</div>
<div style="font-size: 10px; margin-bottom: 4px;">
......
......@@ -22,10 +22,55 @@ document.addEventListener('DOMContentLoaded', function() {
const stopBtn = document.getElementById('stop-btn');
const statusDiv = document.getElementById('status');
const progressDiv = document.getElementById('progress');
const web3DonateBtn = document.getElementById('web3-donate-btn');
const web3Status = document.getElementById('web3-status');
let isRunningBulk = false;
let shouldStop = false;
// Check for MetaMask availability and update UI accordingly
async function checkWalletAvailability() {
try {
console.log('Checking wallet availability...');
web3Status.textContent = 'Checking for Web3 wallet...';
web3Status.style.color = '#666';
web3Status.style.fontSize = '11px';
const isAvailable = await checkMetaMaskAvailable();
if (isAvailable) {
console.log('Wallet detected, showing donation button');
web3DonateBtn.style.display = 'block';
web3Status.textContent = 'Web3 wallet detected! 🎉';
web3Status.style.color = '#4CAF50';
web3Status.style.fontSize = '10px';
// Hide the status after a short delay
setTimeout(() => {
web3Status.style.display = 'none';
}, 2000);
} else {
console.log('No wallet detected, hiding donation button');
web3DonateBtn.style.display = 'none';
web3Status.textContent = 'Install MetaMask or another Web3 wallet to support the project 💝';
web3Status.style.color = '#666';
web3Status.style.fontSize = '10px';
web3Status.style.display = 'block';
}
} catch (error) {
console.error('Error checking wallet availability:', error);
// Hide donation button on error to be safe
web3DonateBtn.style.display = 'none';
web3Status.textContent = 'Unable to check wallet availability';
web3Status.style.color = '#f44336';
web3Status.style.fontSize = '10px';
web3Status.style.display = 'block';
}
}
// Initialize wallet check
checkWalletAvailability();
function showStatus(message, type = 'info') {
statusDiv.textContent = message;
statusDiv.className = `status ${type}`;
......@@ -220,9 +265,6 @@ document.addEventListener('DOMContentLoaded', function() {
});
// 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';
......
/*
* 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