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(() => {
chrome.contextMenus.onClicked.addListener(async (info, tab) => {
if (info.menuItemId === 'privaxy') {
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
const results = await chrome.scripting.executeScript({
target: { tabId: tab.id },
......@@ -64,18 +68,26 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
args: ['friends_only']
});
console.log('Script execution results:', results);
const result = results && results[0] && results[0].result ? results[0].result : null;
console.log('Extracted result:', result);
if (!result) {
console.error('No result returned from script execution');
console.log('Results object:', JSON.stringify(results, null, 2));
chrome.notifications.create({
type: 'basic',
iconUrl: 'icon48.png',
title: 'FetLife Privacy Helper',
message: 'Error: Could not execute privacy update. Please try again.'
message: 'Error: Script execution failed. Check console for details.'
});
return;
}
console.log('Result success:', result.success);
console.log('Result message:', result.message);
// Show notification with result
chrome.notifications.create({
type: 'basic',
......@@ -85,7 +97,12 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
});
} 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({
type: 'basic',
iconUrl: 'icon48.png',
......@@ -120,12 +137,20 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
}
} else if (info.menuItemId === 'set-friends-only') {
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
const url = tab.url;
const isVideoPage = /\/(?:[^\/]+\/)?videos\/\d+/.test(url);
const isPicturePage = /\/(?:[^\/]+\/)?pictures\/\d+/.test(url);
console.log('Is video page:', isVideoPage);
console.log('Is picture page:', isPicturePage);
if (!isVideoPage && !isPicturePage) {
console.log('Not on video or picture page, showing error');
chrome.notifications.create({
type: 'basic',
iconUrl: 'icon48.png',
......@@ -135,6 +160,8 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
return;
}
console.log('Executing script for friends_only...');
// Execute privacy update with friends_only setting
const results = await chrome.scripting.executeScript({
target: { tabId: tab.id },
......@@ -142,18 +169,26 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
args: ['friends_only']
});
console.log('Script execution results:', results);
const result = results && results[0] && results[0].result ? results[0].result : null;
console.log('Extracted result:', result);
if (!result) {
console.error('No result returned from script execution');
console.log('Results object:', JSON.stringify(results, null, 2));
chrome.notifications.create({
type: 'basic',
iconUrl: 'icon48.png',
title: 'FetLife Privacy Helper',
message: 'Error: Could not execute privacy update. Please try again.'
message: 'Error: Script execution failed. Check console for details.'
});
return;
}
console.log('Result success:', result.success);
console.log('Result message:', result.message);
// Show notification with result
chrome.notifications.create({
type: 'basic',
......@@ -164,6 +199,11 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
} catch (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({
type: 'basic',
iconUrl: 'icon48.png',
......@@ -173,12 +213,20 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
}
} else if (info.menuItemId === 'set-all-fetlifers') {
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
const url = tab.url;
const isVideoPage = /\/(?:[^\/]+\/)?videos\/\d+/.test(url);
const isPicturePage = /\/(?:[^\/]+\/)?pictures\/\d+/.test(url);
console.log('Is video page:', isVideoPage);
console.log('Is picture page:', isPicturePage);
if (!isVideoPage && !isPicturePage) {
console.log('Not on video or picture page, showing error');
chrome.notifications.create({
type: 'basic',
iconUrl: 'icon48.png',
......@@ -188,6 +236,8 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
return;
}
console.log('Executing script for all_fetlifers...');
// Execute privacy update with all_fetlifers setting
const results = await chrome.scripting.executeScript({
target: { tabId: tab.id },
......@@ -195,18 +245,26 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
args: ['all_fetlifers']
});
console.log('Script execution results:', results);
const result = results && results[0] && results[0].result ? results[0].result : null;
console.log('Extracted result:', result);
if (!result) {
console.error('No result returned from script execution');
console.log('Results object:', JSON.stringify(results, null, 2));
chrome.notifications.create({
type: 'basic',
iconUrl: 'icon48.png',
title: 'FetLife Privacy Helper',
message: 'Error: Could not execute privacy update. Please try again.'
message: 'Error: Script execution failed. Check console for details.'
});
return;
}
console.log('Result success:', result.success);
console.log('Result message:', result.message);
// Show notification with result
chrome.notifications.create({
type: 'basic',
......@@ -217,6 +275,11 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
} catch (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({
type: 'basic',
iconUrl: 'icon48.png',
......@@ -228,46 +291,46 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
});
// 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 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
async function updatePrivacy() {
return (async function() {
try {
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
const mediaInfo = getMediaInfo();
if (!mediaInfo) {
......@@ -338,216 +401,217 @@ function executePrivacyUpdateFromContext(privacyLevel = 'friends_only') {
console.error('XHR privacy update failed:', error);
return { success: false, message: error.message };
}
}
return updatePrivacy();
})();
}
// Function for bulk privacy update from context menu - XHR-based approach
function executeBulkPrivacyUpdateFromContext() {
// Helper functions
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') {
return (async function() {
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
};
}
// Helper functions
function getCSRFToken() {
const metaTag = document.querySelector('meta[name="csrf-token"]');
if (metaTag) {
return metaTag.getAttribute('content');
}
const response = await fetch(`https://fetlife.com/${mediaType}s/${mediaId}`, {
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)
});
const csrfInput = document.querySelector('input[name="authenticity_token"]');
if (csrfInput) {
return csrfInput.value;
}
if (!response.ok) {
throw new Error(`XHR request failed: ${response.status} ${response.statusText}`);
return null;
}
return { success: true, message: `${mediaType} ${mediaId} updated successfully` };
function extractMediaIdsFromPage() {
const mediaIds = [];
} catch (error) {
console.error(`Failed to update ${mediaType} ${mediaId}:`, error);
return { success: false, message: error.message };
}
}
// 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 });
}
});
function findAndClickNext() {
try {
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 };
// 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;
}
nextLinks[0].click();
return { hasNext: true };
async function updateMediaPrivacyById(mediaType, mediaId, csrfToken, privacyLevel = 'friends_only') {
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) {
return { hasNext: false };
}
}
const response = await fetch(`https://fetlife.com/${mediaType}s/${mediaId}`, {
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
async function runBulkUpdate() {
let pageCount = 0;
let successCount = 0;
let errorCount = 0;
let shouldContinue = true;
return { success: true, message: `${mediaType} ${mediaId} updated successfully` };
try {
const csrfToken = getCSRFToken();
if (!csrfToken) {
throw new Error('Could not find CSRF token');
} catch (error) {
console.error(`Failed to update ${mediaType} ${mediaId}:`, error);
return { success: false, message: error.message };
}
}
while (shouldContinue && pageCount < 100) { // Safety limit
pageCount++;
function findAndClickNext() {
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();
if (singleResult.success) {
successCount++;
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 };
}
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 {
errorCount++;
}
} else {
// Process all media items on current page
for (const item of mediaItems) {
try {
const result = await updateMediaPrivacyById(item.type, item.id, csrfToken, 'friends_only');
if (result.success) {
successCount++;
} else {
// Process all media items on current page
for (const item of mediaItems) {
try {
const result = await updateMediaPrivacyById(item.type, item.id, csrfToken, 'friends_only');
if (result.success) {
successCount++;
} else {
errorCount++;
}
// 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);
}
// 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();
if (!nextResult.hasNext) {
shouldContinue = false;
} else {
await new Promise(resolve => setTimeout(resolve, 2500));
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();
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
setTimeout(() => {
chrome.runtime.sendMessage({
action: 'showNotification',
title: 'Bulk Processing Complete',
message: `Processed ${pageCount} pages (${successCount} success, ${errorCount} errors)`
});
}, 1000);
// Send final notification via message to background script
setTimeout(() => {
chrome.runtime.sendMessage({
action: 'showNotification',
title: 'Bulk Processing Complete',
message: `Processed ${pageCount} pages (${successCount} success, ${errorCount} errors)`
});
}, 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) {
console.error('Bulk processing failed:', error);
console.error('Bulk update function failed:', error);
return { pageCount: 0, successCount: 0, errorCount: 1 };
}
}
return runBulkUpdate();
})();
}
// 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