Commit eed91b63 authored by Stefy Spora's avatar Stefy Spora

Fix executePrivacyUpdateFromContext function definition for proper script injection

- Restructure function to return immediately invoked async function expression (IIFE)
- Fix function scope issues that prevented proper injection into page context
- Ensure all helper functions are properly scoped within the injected function
- Fix bulk processing function with same structure
- Add proper error handling for script execution failures
parent b718d691
...@@ -57,6 +57,10 @@ chrome.runtime.onInstalled.addListener(() => { ...@@ -57,6 +57,10 @@ chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.onClicked.addListener(async (info, tab) => { chrome.contextMenus.onClicked.addListener(async (info, tab) => {
if (info.menuItemId === 'privaxy') { if (info.menuItemId === 'privaxy') {
try { try {
console.log('Context menu: privaxy clicked');
console.log('Tab URL:', tab.url);
console.log('Tab ID:', tab.id);
// Execute the privacy update function with default privacy level // Execute the privacy update function with default privacy level
const results = await chrome.scripting.executeScript({ const results = await chrome.scripting.executeScript({
target: { tabId: tab.id }, target: { tabId: tab.id },
...@@ -64,18 +68,26 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => { ...@@ -64,18 +68,26 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
args: ['friends_only'] args: ['friends_only']
}); });
console.log('Script execution results:', results);
const result = results && results[0] && results[0].result ? results[0].result : null; const result = results && results[0] && results[0].result ? results[0].result : null;
console.log('Extracted result:', result);
if (!result) { if (!result) {
console.error('No result returned from script execution');
console.log('Results object:', JSON.stringify(results, null, 2));
chrome.notifications.create({ chrome.notifications.create({
type: 'basic', type: 'basic',
iconUrl: 'icon48.png', iconUrl: 'icon48.png',
title: 'FetLife Privacy Helper', title: 'FetLife Privacy Helper',
message: 'Error: Could not execute privacy update. Please try again.' message: 'Error: Script execution failed. Check console for details.'
}); });
return; return;
} }
console.log('Result success:', result.success);
console.log('Result message:', result.message);
// Show notification with result // Show notification with result
chrome.notifications.create({ chrome.notifications.create({
type: 'basic', type: 'basic',
...@@ -85,7 +97,12 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => { ...@@ -85,7 +97,12 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
}); });
} catch (error) { } catch (error) {
console.error('Context menu execution error:', error); console.error('Context menu privaxy execution error:', error);
console.error('Error details:', {
message: error.message,
stack: error.stack,
name: error.name
});
chrome.notifications.create({ chrome.notifications.create({
type: 'basic', type: 'basic',
iconUrl: 'icon48.png', iconUrl: 'icon48.png',
...@@ -120,12 +137,20 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => { ...@@ -120,12 +137,20 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
} }
} else if (info.menuItemId === 'set-friends-only') { } else if (info.menuItemId === 'set-friends-only') {
try { try {
console.log('Context menu: set-friends-only clicked');
console.log('Tab URL:', tab.url);
console.log('Tab ID:', tab.id);
// Check if we're on a video or picture page // Check if we're on a video or picture page
const url = tab.url; const url = tab.url;
const isVideoPage = /\/(?:[^\/]+\/)?videos\/\d+/.test(url); const isVideoPage = /\/(?:[^\/]+\/)?videos\/\d+/.test(url);
const isPicturePage = /\/(?:[^\/]+\/)?pictures\/\d+/.test(url); const isPicturePage = /\/(?:[^\/]+\/)?pictures\/\d+/.test(url);
console.log('Is video page:', isVideoPage);
console.log('Is picture page:', isPicturePage);
if (!isVideoPage && !isPicturePage) { if (!isVideoPage && !isPicturePage) {
console.log('Not on video or picture page, showing error');
chrome.notifications.create({ chrome.notifications.create({
type: 'basic', type: 'basic',
iconUrl: 'icon48.png', iconUrl: 'icon48.png',
...@@ -135,6 +160,8 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => { ...@@ -135,6 +160,8 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
return; return;
} }
console.log('Executing script for friends_only...');
// Execute privacy update with friends_only setting // Execute privacy update with friends_only setting
const results = await chrome.scripting.executeScript({ const results = await chrome.scripting.executeScript({
target: { tabId: tab.id }, target: { tabId: tab.id },
...@@ -142,18 +169,26 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => { ...@@ -142,18 +169,26 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
args: ['friends_only'] args: ['friends_only']
}); });
console.log('Script execution results:', results);
const result = results && results[0] && results[0].result ? results[0].result : null; const result = results && results[0] && results[0].result ? results[0].result : null;
console.log('Extracted result:', result);
if (!result) { if (!result) {
console.error('No result returned from script execution');
console.log('Results object:', JSON.stringify(results, null, 2));
chrome.notifications.create({ chrome.notifications.create({
type: 'basic', type: 'basic',
iconUrl: 'icon48.png', iconUrl: 'icon48.png',
title: 'FetLife Privacy Helper', title: 'FetLife Privacy Helper',
message: 'Error: Could not execute privacy update. Please try again.' message: 'Error: Script execution failed. Check console for details.'
}); });
return; return;
} }
console.log('Result success:', result.success);
console.log('Result message:', result.message);
// Show notification with result // Show notification with result
chrome.notifications.create({ chrome.notifications.create({
type: 'basic', type: 'basic',
...@@ -164,6 +199,11 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => { ...@@ -164,6 +199,11 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
} catch (error) { } catch (error) {
console.error('Context menu friends-only execution error:', error); console.error('Context menu friends-only execution error:', error);
console.error('Error details:', {
message: error.message,
stack: error.stack,
name: error.name
});
chrome.notifications.create({ chrome.notifications.create({
type: 'basic', type: 'basic',
iconUrl: 'icon48.png', iconUrl: 'icon48.png',
...@@ -173,12 +213,20 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => { ...@@ -173,12 +213,20 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
} }
} else if (info.menuItemId === 'set-all-fetlifers') { } else if (info.menuItemId === 'set-all-fetlifers') {
try { try {
console.log('Context menu: set-all-fetlifers clicked');
console.log('Tab URL:', tab.url);
console.log('Tab ID:', tab.id);
// Check if we're on a video or picture page // Check if we're on a video or picture page
const url = tab.url; const url = tab.url;
const isVideoPage = /\/(?:[^\/]+\/)?videos\/\d+/.test(url); const isVideoPage = /\/(?:[^\/]+\/)?videos\/\d+/.test(url);
const isPicturePage = /\/(?:[^\/]+\/)?pictures\/\d+/.test(url); const isPicturePage = /\/(?:[^\/]+\/)?pictures\/\d+/.test(url);
console.log('Is video page:', isVideoPage);
console.log('Is picture page:', isPicturePage);
if (!isVideoPage && !isPicturePage) { if (!isVideoPage && !isPicturePage) {
console.log('Not on video or picture page, showing error');
chrome.notifications.create({ chrome.notifications.create({
type: 'basic', type: 'basic',
iconUrl: 'icon48.png', iconUrl: 'icon48.png',
...@@ -188,6 +236,8 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => { ...@@ -188,6 +236,8 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
return; return;
} }
console.log('Executing script for all_fetlifers...');
// Execute privacy update with all_fetlifers setting // Execute privacy update with all_fetlifers setting
const results = await chrome.scripting.executeScript({ const results = await chrome.scripting.executeScript({
target: { tabId: tab.id }, target: { tabId: tab.id },
...@@ -195,18 +245,26 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => { ...@@ -195,18 +245,26 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
args: ['all_fetlifers'] args: ['all_fetlifers']
}); });
console.log('Script execution results:', results);
const result = results && results[0] && results[0].result ? results[0].result : null; const result = results && results[0] && results[0].result ? results[0].result : null;
console.log('Extracted result:', result);
if (!result) { if (!result) {
console.error('No result returned from script execution');
console.log('Results object:', JSON.stringify(results, null, 2));
chrome.notifications.create({ chrome.notifications.create({
type: 'basic', type: 'basic',
iconUrl: 'icon48.png', iconUrl: 'icon48.png',
title: 'FetLife Privacy Helper', title: 'FetLife Privacy Helper',
message: 'Error: Could not execute privacy update. Please try again.' message: 'Error: Script execution failed. Check console for details.'
}); });
return; return;
} }
console.log('Result success:', result.success);
console.log('Result message:', result.message);
// Show notification with result // Show notification with result
chrome.notifications.create({ chrome.notifications.create({
type: 'basic', type: 'basic',
...@@ -217,6 +275,11 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => { ...@@ -217,6 +275,11 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
} catch (error) { } catch (error) {
console.error('Context menu all-fetlifers execution error:', error); console.error('Context menu all-fetlifers execution error:', error);
console.error('Error details:', {
message: error.message,
stack: error.stack,
name: error.name
});
chrome.notifications.create({ chrome.notifications.create({
type: 'basic', type: 'basic',
iconUrl: 'icon48.png', iconUrl: 'icon48.png',
...@@ -228,46 +291,46 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => { ...@@ -228,46 +291,46 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
}); });
// Function to be injected for context menu execution - XHR-based approach // Function to be injected for context menu execution - XHR-based approach
// This function must be at the top level to be properly injected
function executePrivacyUpdateFromContext(privacyLevel = 'friends_only') { function executePrivacyUpdateFromContext(privacyLevel = 'friends_only') {
// Function to extract media ID and type from URL return (async function() {
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
async function updatePrivacy() {
try { try {
console.log('Starting XHR-based privacy update from context menu...'); console.log('Starting XHR-based privacy update from context menu...');
// 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;
}
// Step 1: Detect media type and ID from URL // Step 1: Detect media type and ID from URL
const mediaInfo = getMediaInfo(); const mediaInfo = getMediaInfo();
if (!mediaInfo) { if (!mediaInfo) {
...@@ -338,216 +401,217 @@ function executePrivacyUpdateFromContext(privacyLevel = 'friends_only') { ...@@ -338,216 +401,217 @@ function executePrivacyUpdateFromContext(privacyLevel = 'friends_only') {
console.error('XHR privacy update failed:', error); console.error('XHR privacy update failed:', error);
return { success: false, message: error.message }; return { success: false, message: error.message };
} }
} })();
return updatePrivacy();
} }
// Function for bulk privacy update from context menu - XHR-based approach // Function for bulk privacy update from context menu - XHR-based approach
function executeBulkPrivacyUpdateFromContext() { function executeBulkPrivacyUpdateFromContext() {
// Helper functions return (async function() {
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;
}
function extractMediaIdsFromPage() {
const mediaIds = [];
// Look for video links (handle both direct and username-based URLs)
const videoLinks = document.querySelectorAll('a[href*="/videos/"]');
videoLinks.forEach(link => {
const match = link.href.match(/\/(?:[^\/]+\/)?videos\/(\d+)/);
if (match) {
mediaIds.push({ type: 'video', id: match[1], url: link.href });
}
});
// Look for picture links (handle both direct and username-based URLs)
const pictureLinks = document.querySelectorAll('a[href*="/pictures/"]');
pictureLinks.forEach(link => {
const match = link.href.match(/\/(?:[^\/]+\/)?pictures\/(\d+)/);
if (match) {
mediaIds.push({ type: 'picture', id: match[1], url: link.href });
}
});
return mediaIds;
}
async function updateMediaPrivacyById(mediaType, mediaId, csrfToken, privacyLevel = 'friends_only') {
try { try {
let payload; // Helper functions
if (mediaType === 'video') { function getCSRFToken() {
payload = { const metaTag = document.querySelector('meta[name="csrf-token"]');
video: { if (metaTag) {
title: "", return metaTag.getAttribute('content');
description: "", }
only_friends: privacyLevel === 'friends_only' ? true : false,
tag_names: [],
user_tag_ids: []
},
render_flash: true
};
} else {
payload = {
picture: {
caption: "",
content_privacy: privacyLevel === 'friends_only' ? "only_friends" : "public",
tag_names: [],
user_tag_ids: []
},
render_flash: true
};
}
const response = await fetch(`https://fetlife.com/${mediaType}s/${mediaId}`, { const csrfInput = document.querySelector('input[name="authenticity_token"]');
method: 'PUT', if (csrfInput) {
credentials: 'same-origin', return csrfInput.value;
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) { return null;
throw new Error(`XHR request failed: ${response.status} ${response.statusText}`);
} }
return { success: true, message: `${mediaType} ${mediaId} updated successfully` }; function extractMediaIdsFromPage() {
const mediaIds = [];
} catch (error) { // Look for video links (handle both direct and username-based URLs)
console.error(`Failed to update ${mediaType} ${mediaId}:`, error); const videoLinks = document.querySelectorAll('a[href*="/videos/"]');
return { success: false, message: error.message }; videoLinks.forEach(link => {
} const match = link.href.match(/\/(?:[^\/]+\/)?videos\/(\d+)/);
} if (match) {
mediaIds.push({ type: 'video', id: match[1], url: link.href });
}
});
function findAndClickNext() { // Look for picture links (handle both direct and username-based URLs)
try { const pictureLinks = document.querySelectorAll('a[href*="/pictures/"]');
const nextLinks = Array.from(document.querySelectorAll('a')).filter(link => pictureLinks.forEach(link => {
link.textContent.trim().includes('next »') || const match = link.href.match(/\/(?:[^\/]+\/)?pictures\/(\d+)/);
link.textContent.trim().includes('next ›') || if (match) {
link.textContent.trim() === 'next' mediaIds.push({ type: 'picture', id: match[1], url: link.href });
); }
});
if (nextLinks.length === 0) {
return { hasNext: false }; return mediaIds;
} }
nextLinks[0].click(); async function updateMediaPrivacyById(mediaType, mediaId, csrfToken, privacyLevel = 'friends_only') {
return { hasNext: true }; try {
let payload;
if (mediaType === 'video') {
payload = {
video: {
title: "",
description: "",
only_friends: privacyLevel === 'friends_only' ? true : false,
tag_names: [],
user_tag_ids: []
},
render_flash: true
};
} else {
payload = {
picture: {
caption: "",
content_privacy: privacyLevel === 'friends_only' ? "only_friends" : "public",
tag_names: [],
user_tag_ids: []
},
render_flash: true
};
}
} catch (error) { const response = await fetch(`https://fetlife.com/${mediaType}s/${mediaId}`, {
return { hasNext: false }; 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) {
throw new Error(`XHR request failed: ${response.status} ${response.statusText}`);
}
// Main bulk processing function return { success: true, message: `${mediaType} ${mediaId} updated successfully` };
async function runBulkUpdate() {
let pageCount = 0;
let successCount = 0;
let errorCount = 0;
let shouldContinue = true;
try { } catch (error) {
const csrfToken = getCSRFToken(); console.error(`Failed to update ${mediaType} ${mediaId}:`, error);
if (!csrfToken) { return { success: false, message: error.message };
throw new Error('Could not find CSRF token'); }
} }
while (shouldContinue && pageCount < 100) { // Safety limit function findAndClickNext() {
pageCount++;
try { try {
// Extract all media IDs from current page const nextLinks = Array.from(document.querySelectorAll('a')).filter(link =>
const mediaItems = extractMediaIdsFromPage(); link.textContent.trim().includes('next »') ||
link.textContent.trim().includes('next ›') ||
if (mediaItems.length === 0) { link.textContent.trim() === 'next'
// If no media items found, try single page update );
const singleResult = await executePrivacyUpdateFromContext();
if (singleResult.success) { if (nextLinks.length === 0) {
successCount++; return { hasNext: false };
}
nextLinks[0].click();
return { hasNext: true };
} catch (error) {
return { hasNext: false };
}
}
// Main bulk processing function
let pageCount = 0;
let successCount = 0;
let errorCount = 0;
let shouldContinue = true;
try {
const csrfToken = getCSRFToken();
if (!csrfToken) {
throw new Error('Could not find CSRF token');
}
while (shouldContinue && pageCount < 100) { // Safety limit
pageCount++;
try {
// Extract all media IDs from current page
const mediaItems = extractMediaIdsFromPage();
if (mediaItems.length === 0) {
// If no media items found, try single page update
const singleResult = await executePrivacyUpdateFromContext('friends_only');
if (singleResult && singleResult.success) {
successCount++;
} else {
errorCount++;
}
} else { } else {
errorCount++; // Process all media items on current page
} for (const item of mediaItems) {
} else { try {
// Process all media items on current page const result = await updateMediaPrivacyById(item.type, item.id, csrfToken, 'friends_only');
for (const item of mediaItems) { if (result.success) {
try { successCount++;
const result = await updateMediaPrivacyById(item.type, item.id, csrfToken, 'friends_only'); } else {
if (result.success) { errorCount++;
successCount++; }
} else { // Small delay between requests to avoid rate limiting
await new Promise(resolve => setTimeout(resolve, 200));
} catch (error) {
errorCount++; errorCount++;
console.error(`Error updating ${item.type} ${item.id}:`, error);
} }
// Small delay between requests to avoid rate limiting
await new Promise(resolve => setTimeout(resolve, 200));
} catch (error) {
errorCount++;
console.error(`Error updating ${item.type} ${item.id}:`, error);
} }
} }
}
await new Promise(resolve => setTimeout(resolve, 1500)); await new Promise(resolve => setTimeout(resolve, 1500));
const nextResult = findAndClickNext();
if (!nextResult.hasNext) {
shouldContinue = false;
break;
}
await new Promise(resolve => setTimeout(resolve, 2500));
} catch (error) {
errorCount++;
console.error(`Error on page ${pageCount}:`, error);
// Try to continue to next page
try {
const nextResult = findAndClickNext(); const nextResult = findAndClickNext();
if (!nextResult.hasNext) { if (!nextResult.hasNext) {
shouldContinue = false; shouldContinue = false;
} else { break;
await new Promise(resolve => setTimeout(resolve, 2500)); }
await new Promise(resolve => setTimeout(resolve, 2500));
} catch (error) {
errorCount++;
console.error(`Error on page ${pageCount}:`, error);
// Try to continue to next page
try {
const nextResult = findAndClickNext();
if (!nextResult.hasNext) {
shouldContinue = false;
} else {
await new Promise(resolve => setTimeout(resolve, 2500));
}
} catch (nextError) {
shouldContinue = false;
} }
} catch (nextError) {
shouldContinue = false;
} }
} }
}
// Send final notification via message to background script // Send final notification via message to background script
setTimeout(() => { setTimeout(() => {
chrome.runtime.sendMessage({ chrome.runtime.sendMessage({
action: 'showNotification', action: 'showNotification',
title: 'Bulk Processing Complete', title: 'Bulk Processing Complete',
message: `Processed ${pageCount} pages (${successCount} success, ${errorCount} errors)` message: `Processed ${pageCount} pages (${successCount} success, ${errorCount} errors)`
}); });
}, 1000); }, 1000);
return { pageCount, successCount, errorCount }; return { pageCount, successCount, errorCount };
} catch (error) {
console.error('Bulk processing failed:', error);
return { pageCount: 0, successCount: 0, errorCount: 1 };
}
} catch (error) { } catch (error) {
console.error('Bulk processing failed:', error); console.error('Bulk update function failed:', error);
return { pageCount: 0, successCount: 0, errorCount: 1 }; return { pageCount: 0, successCount: 0, errorCount: 1 };
} }
} })();
return runBulkUpdate();
} }
// Handle messages from popup and notifications // Handle messages from popup and notifications
......
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