New:Socket for Asana Is Now Available.Learn more
Get Started

Screenshot for YouTube™ | 4K Frame & Thumbnail Cap

Package Overview
Versions
2
Alerts
File Explorer
Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

youtube-frame-extractor@example.com - firefox Package Compare versions

Comparing version
1.1.0
to
1.2.0
+67
scripts/background.js
// YouTube Screenshot Tool - Background Script
// Unified approach for Firefox and Chrome
const isBrowser = typeof browser !== 'undefined';
const browserAPI = isBrowser ? browser : chrome;
// Default settings
const DEFAULT_SETTINGS = {
quality: 'high',
format: 'png',
filename: 'youtube_frame',
language: 'en'
};
// Listen for keyboard commands
browserAPI.commands.onCommand.addListener(async (command) => {
if (command === 'capture_frame') {
try {
// Get active tab
const tabs = await browserAPI.tabs.query({ active: true, currentWindow: true });
if (tabs.length === 0) return;
const tab = tabs[0];
// Check if it's a YouTube video page
if (tab.url && tab.url.includes('youtube.com/watch')) {
// Get settings
const result = await browserAPI.storage.sync.get('settings');
const settings = result.settings || DEFAULT_SETTINGS;
// Send capture message to content script
try {
await browserAPI.tabs.sendMessage(tab.id, {
action: 'captureFrame',
settings: settings
});
} catch (err) {
// If message fails, content script might not be loaded
console.log('Content script not loaded, injecting...');
// Inject script
const browserScripting = isBrowser ? browser.scripting : chrome.scripting;
if (browserScripting) {
await browserScripting.executeScript({
target: { tabId: tab.id },
files: ['lib/browser-polyfill.js', 'scripts/content.js']
});
// Wait a bit and retry
setTimeout(async () => {
try {
await browserAPI.tabs.sendMessage(tab.id, {
action: 'captureFrame',
settings: settings
});
} catch (retryErr) {
console.error('Failed to capture after injection:', retryErr);
}
}, 100);
}
}
}
} catch (error) {
console.error('Error in command listener:', error);
}
}
});
+28
-20
{
"manifest_version": 3,
"name": "YouTube Frame Extractor",
"version": "1.1.0",
"manifest_version": 2,
"name": "YouTube Screenshot Tool",
"version": "1.2.0",
"description": "Extract high-quality frames from YouTube videos without UI elements",

@@ -10,4 +10,3 @@ "default_locale": "en",

"storage",
"downloads",
"scripting"
"*://*.youtube.com/*"
],

@@ -17,3 +16,8 @@ "browser_specific_settings": {

"id": "youtube-frame-extractor@example.com",
"strict_min_version": "109.0"
"strict_min_version": "142.0",
"data_collection_permissions": {
"required": [
"none"
]
}
}

@@ -26,3 +30,3 @@ },

},
"action": {
"browser_action": {
"default_popup": "popup/popup.html",

@@ -35,5 +39,2 @@ "default_icon": {

},
"host_permissions": [
"*://*.youtube.com/*"
],
"content_scripts": [

@@ -51,13 +52,20 @@ {

"web_accessible_resources": [
{
"resources": [
"assets/*",
"lib/*",
"scripts/*"
],
"matches": [
"*://*.youtube.com/*"
]
"assets/*",
"lib/*",
"scripts/*"
],
"background": {
"scripts": [
"scripts/background.js"
]
},
"commands": {
"capture_frame": {
"suggested_key": {
"default": "Alt+Shift+C",
"mac": "Alt+Shift+C"
},
"description": "Capture Video Frame"
}
]
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>YouTube Frame Extractor</title>
<title>YouTube Screenshot Tool</title>
<link rel="stylesheet" href="../css/popup.css">
<script src="../lib/browser-polyfill.js"></script>
</head>
<body>
<div class="popup-header">
<div class="popup-title">YouTube Frame Extractor</div>
<div class="popup-title">YouTube Screenshot Tool</div>
<div class="language-selector">

@@ -21,5 +23,30 @@ <button id="en-btn" class="language-btn active" data-lang="en">EN</button>

<button id="capture" class="capture-btn">📸 Capture Frame</button>
<button id="thumbnail" class="thumbnail-btn">🖼️ Download Thumbnail</button>
</div>
<!-- Shortcut Section -->
<div class="shortcut-section"
style="text-align: center; margin-top: 10px; padding: 10px; background: #f5f5f5; border-radius: 8px;">
<div class="shortcut-info" style="margin-bottom: 5px;">
<span id="shortcutLabel" style="font-weight: bold;">Shortcut:</span>
<span id="currentShortcut" class="shortcut-key"
style="background: #e0e0e0; padding: 2px 6px; border-radius: 4px; font-family: monospace;">Loading...</span>
</div>
<button id="changeShortcut" class="link-btn"
style="background: none; border: none; color: #2196F3; cursor: pointer; text-decoration: underline; font-size: 12px;">Change
Shortcut</button>
</div>
<!-- Thumbnail Tool Section -->
<div class="thumbnail-tool-section" style="margin-top: 15px; padding-top: 15px; border-top: 1px solid #eee;">
<div class="form-group">
<label for="videoUrlDisplay" style="font-size: 12px; color: #666; display: block; margin-bottom: 5px;">Detected
Video:</label>
<input type="text" id="videoUrlDisplay" readonly
style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; background: #f9f9f9; box-sizing: border-box; font-size: 12px; color: #555;">
</div>
<button id="openThumbnailTool" class="action-btn"
style="width: 100%; padding: 10px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; margin-top: 8px; font-weight: bold;">Open
Thumbnail Tool</button>
</div>
<div class="settings-section">

@@ -30,3 +57,3 @@ <div class="settings-header" id="settingsToggle">

</div>
<div class="settings-content" id="settingsContent">

@@ -41,3 +68,3 @@ <div class="form-group">

</div>
<div class="form-group">

@@ -51,3 +78,3 @@ <label for="format" id="formatLabel">File Format</label>

</div>
<div class="form-group">

@@ -57,3 +84,3 @@ <label for="filename" id="filenameLabel">File Name</label>

</div>
<div class="action-buttons">

@@ -65,7 +92,8 @@ <button id="saveSettings" class="save-btn">Save Settings</button>

</div>
<div id="statusMessage" class="status-message" style="display: none;"></div>
<script src="../scripts/popup.js"></script>
</body>
</html>
</html>

@@ -19,6 +19,2 @@ /**

return true; // Keep the message channel open for async response
} else if (message.action === 'downloadThumbnail') {
const result = downloadThumbnail(message.language);
sendResponse(result);
return true;
} else if (message.action === 'ping' || message.action === 'injected_check') {

@@ -35,9 +31,7 @@ // Respond to ping or injected_check message to confirm content script is loaded

videoNotFound: 'No video element found on this page',
videoIdNotFound: 'Could not extract video ID from URL',
thumbnailFetchFailed: 'Failed to fetch thumbnail'
videoIdNotFound: 'Could not extract video ID from URL'
},
ar: {
videoNotFound: 'لم يتم العثور على الفيديو!',
videoIdNotFound: 'لم يتم العثور على معرف الفيديو',
thumbnailFetchFailed: 'فشل في تحميل الصورة المصغرة'
videoIdNotFound: 'لم يتم العثور على معرف الفيديو'
}

@@ -121,64 +115,2 @@ };

}
}
/**
* Downloads the video thumbnail
* @param {string} language - The current language
* @returns {boolean|string} - True on success, error message on failure
*/
function downloadThumbnail(language = 'en') {
try {
const lang = language || 'en';
// Get video ID from URL
const url = window.location.href;
const videoId = url.match(/[?&]v=([^&]+)/)?.[1];
if (!videoId) {
return messages[lang].videoIdNotFound;
}
// Get video title for filename
const videoTitle = document.querySelector('h1.title')?.textContent?.trim() || 'youtube_video';
// Create a safe filename
const filename = videoTitle
.replace(/[^\w\s-]/g, '') // Remove special chars
.replace(/\s+/g, '_') // Replace spaces with underscores
.substring(0, 50) + '_thumbnail.jpg';
// Get the maxresdefault thumbnail (highest quality)
// YouTube thumbnail URLs: https://img.youtube.com/vi/VIDEO_ID/maxresdefault.jpg
const thumbnailUrl = `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`;
// Create download link
const link = document.createElement('a');
link.download = filename;
link.href = thumbnailUrl;
link.setAttribute('target', '_blank');
// Fetch the image first to ensure it exists
fetch(thumbnailUrl)
.then(response => {
if (!response.ok) {
// If maxresdefault is not available, try hqdefault
return fetch(`https://img.youtube.com/vi/${videoId}/hqdefault.jpg`);
}
return response;
})
.then(response => {
if (!response.ok) {
throw new Error(messages[lang].thumbnailFetchFailed);
}
link.click();
})
.catch(error => {
console.error('Error downloading thumbnail:', error);
return error.toString();
});
return true;
} catch (error) {
return error.toString();
}
}
}

@@ -1,6 +0,6 @@

// استخدام الواجهة الموحدة لتحقيق التوافق مع Chrome و Firefox
// تعريف واضح للتحقق من نوع المتصفح المستخدم
// YouTube Screenshot Tool - Popup Script
// Unified approach for Firefox and Chrome
const isBrowser = typeof browser !== 'undefined';
const isChrome = typeof chrome !== 'undefined';
// تعريف browserAPI للاستخدام في العمليات المشتركة مثل tabs.query والمزيد
const browserAPI = isBrowser ? browser : chrome;

@@ -18,3 +18,2 @@

const captureBtn = document.getElementById('capture');
const thumbnailBtn = document.getElementById('thumbnail');
const settingsToggle = document.getElementById('settingsToggle');

@@ -33,3 +32,3 @@ const settingsContent = document.getElementById('settingsContent');

// Messages cache to avoid repeated lookups
// Messages cache
const messagesCache = {

@@ -40,22 +39,6 @@ en: {},

// Elements that need localization
const elementsToLocalize = {
'capture': 'captureBtn',
'thumbnail': 'thumbnailBtn',
'settingsLabel': 'settings',
'qualityLabel': 'captureQuality',
'formatLabel': 'fileFormat',
'filenameLabel': 'fileName',
'saveSettings': 'saveSettings',
'resetSettings': 'resetSettings',
'highQualityOption': 'highQuality',
'mediumQualityOption': 'mediumQuality',
'lowQualityOption': 'lowQuality'
};
// Hard-coded messages as fallback in case Browser API fails
// Fallback messages
const fallbackMessages = {
en: {
captureBtn: "📸 Capture Frame",
thumbnailBtn: "🖼️ Download Thumbnail",
settings: "Settings",

@@ -71,8 +54,7 @@ captureQuality: "Capture Quality",

captureSuccess: "Frame captured successfully!",
thumbnailSuccess: "Thumbnail downloaded successfully!",
thumbnailError: "Error downloading thumbnail"
shortcutLabel: "Shortcut:",
changeShortcut: "Change Shortcut"
},
ar: {
captureBtn: "📸 التقط الإطار",
thumbnailBtn: "🖼️ تحميل الصورة المصغرة",
settings: "الإعدادات",

@@ -88,24 +70,21 @@ captureQuality: "جودة الالتقاط",

captureSuccess: "تم التقاط الإطار بنجاح!",
thumbnailSuccess: "تم تحميل الصورة المصغرة بنجاح!",
thumbnailError: "خطأ في تحميل الصورة المصغرة"
shortcutLabel: "الاختصار:",
changeShortcut: "تغيير الاختصار"
}
};
// Load settings and setup UI
// Initialize Popup
document.addEventListener('DOMContentLoaded', async () => {
console.log('Popup initialized');
// Load saved settings or set defaults
// Load settings
const settings = await loadSettings();
console.log('Loaded settings:', settings);
// Apply settings to UI
qualitySelect.value = settings.quality;
formatSelect.value = settings.format;
filenameInput.value = settings.filename;
// Set active language button
if (qualitySelect) qualitySelect.value = settings.quality;
if (formatSelect) formatSelect.value = settings.format;
if (filenameInput) filenameInput.value = settings.filename;
// Set language
const currentLang = settings.language || 'en';
console.log('Current language:', currentLang);
languageBtns.forEach(btn => {

@@ -118,123 +97,134 @@ if (btn.dataset.lang === currentLang) {

});
// Set language
await setLanguage(currentLang);
// Initially hide settings
settingsContent.style.display = 'none';
// Hide settings initially
if (settingsContent) settingsContent.style.display = 'none';
// Load shortcut
await updateShortcutDisplay();
// Initialize Thumbnail Tool
await initThumbnailTool();
});
// Inject content script based on browser type
async function injectContentScript(tabId) {
// Initialize Thumbnail Tool
async function initThumbnailTool() {
const videoUrlDisplay = document.getElementById('videoUrlDisplay');
const openThumbnailToolBtn = document.getElementById('openThumbnailTool');
if (!videoUrlDisplay || !openThumbnailToolBtn) return;
try {
// Unified approach for Firefox and Chrome in Manifest V3
const browserScripting = isBrowser ? browser.scripting : chrome.scripting;
if (browserScripting) {
await browserScripting.executeScript({
target: { tabId: tabId },
files: ['lib/browser-polyfill.js', 'scripts/content.js']
});
const tabs = await browserAPI.tabs.query({ active: true, currentWindow: true });
if (tabs.length > 0 && tabs[0].url && tabs[0].url.includes('youtube.com/watch')) {
videoUrlDisplay.value = tabs[0].url;
openThumbnailToolBtn.disabled = false;
openThumbnailToolBtn.style.opacity = '1';
openThumbnailToolBtn.style.cursor = 'pointer';
} else {
throw new Error('Scripting API not available');
videoUrlDisplay.value = "No YouTube video detected";
openThumbnailToolBtn.disabled = true;
openThumbnailToolBtn.style.opacity = '0.6';
openThumbnailToolBtn.style.cursor = 'not-allowed';
}
// Small delay to ensure content script is fully loaded
return new Promise(resolve => setTimeout(resolve, 100));
// Remove old listeners to avoid duplicates (though DOMContentLoaded runs once)
const newBtn = openThumbnailToolBtn.cloneNode(true);
openThumbnailToolBtn.parentNode.replaceChild(newBtn, openThumbnailToolBtn);
newBtn.addEventListener('click', () => {
const url = videoUrlDisplay.value;
if (url && url.includes('youtube.com')) {
// Open external tool with the video URL as a parameter
const thumbUrl = `https://thumz.vercel.app/?video=${encodeURIComponent(url)}`;
browserAPI.tabs.create({ url: thumbUrl });
}
});
} catch (error) {
console.error("Script injection error:", error);
throw new Error(`Cannot inject script: ${error.message}. This extension requires permission to run scripts.`);
console.error('Error initializing thumbnail tool:', error);
}
}
// Capture button click
captureBtn.addEventListener('click', async () => {
const settings = await loadSettings();
// Show Capturing message
showStatus('Capturing...', 'info');
// Update shortcut display
async function updateShortcutDisplay() {
try {
// Query for active tab
const tabs = await browserAPI.tabs.query({ active: true, currentWindow: true });
if (tabs[0].url.includes('youtube.com/watch')) {
try {
// Try to send a ping message to check if content script is loaded
await sendMessageToTab(tabs[0].id, { action: 'ping' });
// If no error, content script is already loaded, send the capture message
await handleCaptureMessage(tabs[0].id, settings);
} catch (err) {
// Content script not loaded or error occurred, try to inject it
console.log('Content script not loaded, injecting:', err);
try {
// Use our shared function to inject the content script
await injectContentScript(tabs[0].id);
// Now send the capture message
await handleCaptureMessage(tabs[0].id, settings);
} catch (injectError) {
showStatus('Error injecting script: ' + injectError.message, 'error');
}
const commands = await browserAPI.commands.getAll();
const captureCommand = commands.find(c => c.name === 'capture_frame');
if (captureCommand) {
const shortcut = captureCommand.shortcut || 'Not set';
const shortcutElement = document.getElementById('currentShortcut');
if (shortcutElement) {
shortcutElement.textContent = shortcut;
}
} else {
const currentLang = document.querySelector('.language-btn.active').dataset.lang || 'en';
const messages = {
en: 'Please navigate to a YouTube video page first',
ar: 'يرجى الانتقال إلى صفحة فيديو YouTube أولاً'
};
showStatus(messages[currentLang], 'error');
}
} catch (error) {
showStatus('Error: ' + (error.message || 'Unknown error'), 'error');
console.error('Error fetching commands:', error);
}
});
}
// Thumbnail button click
thumbnailBtn.addEventListener('click', async () => {
const settings = await loadSettings();
// Show downloading message
showStatus('Downloading thumbnail...', 'info');
try {
// Query for active tab
const tabs = await browserAPI.tabs.query({ active: true, currentWindow: true });
if (tabs[0].url.includes('youtube.com/watch')) {
try {
// Try to send a ping message to check if content script is loaded
await sendMessageToTab(tabs[0].id, { action: 'ping' });
// If no error, content script is already loaded, send the thumbnail message
await handleThumbnailMessage(tabs[0].id, settings);
} catch (err) {
// Content script not loaded or error occurred, try to inject it
console.log('Content script not loaded, injecting:', err);
// Change shortcut button
const changeShortcutBtn = document.getElementById('changeShortcut');
if (changeShortcutBtn) {
changeShortcutBtn.addEventListener('click', () => {
browserAPI.tabs.create({ url: 'chrome://extensions/shortcuts' });
});
}
// Capture button click
if (captureBtn) {
captureBtn.addEventListener('click', async () => {
const settings = await loadSettings();
showStatus('Capturing...', 'info');
try {
const tabs = await browserAPI.tabs.query({ active: true, currentWindow: true });
if (tabs[0].url.includes('youtube.com/watch')) {
try {
// Use our shared function to inject the content script
await injectContentScript(tabs[0].id);
// Now send the thumbnail message
await handleThumbnailMessage(tabs[0].id, settings);
} catch (injectError) {
showStatus('Error injecting script: ' + injectError.message, 'error');
await sendMessageToTab(tabs[0].id, { action: 'ping' });
await handleCaptureMessage(tabs[0].id, settings);
} catch (err) {
console.log('Content script not loaded, injecting:', err);
try {
await injectContentScript(tabs[0].id);
await handleCaptureMessage(tabs[0].id, settings);
} catch (injectError) {
showStatus('Error injecting script: ' + injectError.message, 'error');
}
}
} else {
const currentLang = document.querySelector('.language-btn.active').dataset.lang || 'en';
const messages = {
en: 'Please navigate to a YouTube video page first',
ar: 'يرجى الانتقال إلى صفحة فيديو YouTube أولاً'
};
showStatus(messages[currentLang], 'error');
}
} catch (error) {
showStatus('Error: ' + (error.message || 'Unknown error'), 'error');
}
});
}
// Inject content script
async function injectContentScript(tabId) {
try {
const browserScripting = isBrowser ? browser.scripting : chrome.scripting;
if (browserScripting) {
await browserScripting.executeScript({
target: { tabId: tabId },
files: ['lib/browser-polyfill.js', 'scripts/content.js']
});
} else {
const currentLang = document.querySelector('.language-btn.active').dataset.lang || 'en';
const messages = {
en: 'Please navigate to a YouTube video page first',
ar: 'يرجى الانتقال إلى صفحة فيديو YouTube أولاً'
};
showStatus(messages[currentLang], 'error');
throw new Error('Scripting API not available');
}
return new Promise(resolve => setTimeout(resolve, 100));
} catch (error) {
const errorMsg = getMessage('thumbnailError');
showStatus(errorMsg + ': ' + (error.message || ''), 'error');
console.error("Script injection error:", error);
throw new Error(`Cannot inject script: ${error.message}`);
}
});
}
// Helper function to handle the capture message
// Handle capture message
async function handleCaptureMessage(tabId, settings) {

@@ -246,3 +236,3 @@ try {

});
if (response === true) {

@@ -259,36 +249,18 @@ const successMsg = getMessage('captureSuccess');

// Helper function to handle the thumbnail message
async function handleThumbnailMessage(tabId, settings) {
// Send message to tab
async function sendMessageToTab(tabId, message) {
try {
const response = await sendMessageToTab(tabId, {
action: 'downloadThumbnail',
language: settings.language
});
if (response === true) {
const successMsg = getMessage('thumbnailSuccess');
showStatus(successMsg, 'success');
} else {
const errorMsg = getMessage('thumbnailError');
showStatus(errorMsg + ': ' + (response || ''), 'error');
}
return await browserAPI.tabs.sendMessage(tabId, message);
} catch (error) {
const errorMsg = getMessage('thumbnailError');
showStatus(errorMsg + ': ' + (error.message || ''), 'error');
throw error;
}
}
// Helper function to send a message to a tab
function sendMessageToTab(tabId, message) {
return new Promise((resolve, reject) => {
try {
browserAPI.tabs.sendMessage(tabId, message, response => {
if (browserAPI.runtime.lastError) {
reject(new Error(browserAPI.runtime.lastError.message));
} else {
resolve(response);
}
});
} catch (err) {
reject(err);
// Settings toggle
if (settingsToggle) {
settingsToggle.addEventListener('click', () => {
if (settingsContent.style.display === 'none') {
settingsContent.style.display = 'block';
} else {
settingsContent.style.display = 'none';
}

@@ -298,47 +270,41 @@ });

// Settings toggle
settingsToggle.addEventListener('click', () => {
if (settingsContent.style.display === 'none') {
settingsContent.style.display = 'block';
} else {
settingsContent.style.display = 'none';
}
});
// Save settings
saveSettingsBtn.addEventListener('click', async () => {
const settings = {
quality: qualitySelect.value,
format: formatSelect.value,
filename: filenameInput.value,
language: document.querySelector('.language-btn.active').dataset.lang
};
await saveSettings(settings);
showStatus('Settings saved', 'success');
});
if (saveSettingsBtn) {
saveSettingsBtn.addEventListener('click', async () => {
const settings = {
quality: qualitySelect.value,
format: formatSelect.value,
filename: filenameInput.value,
language: document.querySelector('.language-btn.active').dataset.lang
};
await saveSettings(settings);
showStatus('Settings saved', 'success');
});
}
// Reset settings
resetSettingsBtn.addEventListener('click', async () => {
await saveSettings(DEFAULT_SETTINGS);
// Update UI
qualitySelect.value = DEFAULT_SETTINGS.quality;
formatSelect.value = DEFAULT_SETTINGS.format;
filenameInput.value = DEFAULT_SETTINGS.filename;
// Update language
const lang = DEFAULT_SETTINGS.language;
languageBtns.forEach(btn => {
if (btn.dataset.lang === lang) {
btn.classList.add('active');
} else {
btn.classList.remove('active');
}
if (resetSettingsBtn) {
resetSettingsBtn.addEventListener('click', async () => {
await saveSettings(DEFAULT_SETTINGS);
// Update UI
qualitySelect.value = DEFAULT_SETTINGS.quality;
formatSelect.value = DEFAULT_SETTINGS.format;
filenameInput.value = DEFAULT_SETTINGS.filename;
// Update language
const lang = DEFAULT_SETTINGS.language;
languageBtns.forEach(btn => {
if (btn.dataset.lang === lang) {
btn.classList.add('active');
} else {
btn.classList.remove('active');
}
});
await setLanguage(lang);
showStatus('Settings reset to default', 'success');
});
await setLanguage(lang);
showStatus('Settings reset to default', 'success');
});
}

@@ -349,99 +315,82 @@ // Language buttons

const lang = btn.dataset.lang;
// Update active class
languageBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
// Load settings and update language
const settings = await loadSettings();
settings.language = lang;
await saveSettings(settings);
// Update UI language
await setLanguage(lang);
console.log(`Language switched to ${lang}`);
});
});
// Helper functions
// Helper: Load Settings
async function loadSettings() {
return new Promise((resolve) => {
browserAPI.storage.sync.get('settings', (result) => {
resolve(result.settings || DEFAULT_SETTINGS);
});
});
try {
const result = await browserAPI.storage.sync.get('settings');
return result.settings || DEFAULT_SETTINGS;
} catch (error) {
console.error('Error loading settings:', error);
return DEFAULT_SETTINGS;
}
}
// Helper: Save Settings
async function saveSettings(settings) {
return new Promise((resolve) => {
browserAPI.storage.sync.set({ settings }, resolve);
});
try {
await browserAPI.storage.sync.set({ settings });
} catch (error) {
console.error('Error saving settings:', error);
}
}
// Get message by key based on current language
// Helper: Get Message
function getMessage(messageName) {
const lang = document.querySelector('.language-btn.active').dataset.lang || 'en';
// If we have cached this message, return it
if (messagesCache[lang] && messagesCache[lang][messageName]) {
return messagesCache[lang][messageName];
}
// Try to get message from Browser API
let message = browserAPI.i18n.getMessage(messageName);
// If Browser API fails, use our fallback
if (!message && fallbackMessages[lang] && fallbackMessages[lang][messageName]) {
message = fallbackMessages[lang][messageName];
}
// Cache the result
if (!messagesCache[lang]) {
messagesCache[lang] = {};
}
if (!messagesCache[lang]) messagesCache[lang] = {};
messagesCache[lang][messageName] = message || messageName;
return message || messageName;
}
// Helper: Set Language
function setLanguage(lang) {
console.log(`Setting language to: ${lang}`);
// Update active class on language buttons
document.querySelectorAll('.language-btn').forEach(btn => {
if (btn.dataset.lang === lang) {
btn.classList.add('active');
} else {
btn.classList.remove('active');
}
if (btn.dataset.lang === lang) btn.classList.add('active');
else btn.classList.remove('active');
});
// Add RTL class for Arabic language
if (lang === 'ar') {
document.body.classList.add('rtl');
document.documentElement.setAttribute('lang', 'ar');
// Fix margin for RTL
const settingsHeaders = document.querySelectorAll('.settings-header h3');
settingsHeaders.forEach(header => {
header.style.marginRight = '5px';
header.style.marginLeft = '0';
});
if (settingsHeaders) {
settingsHeaders.forEach(header => {
header.style.marginRight = '5px';
header.style.marginLeft = '0';
});
}
} else {
document.body.classList.remove('rtl');
document.documentElement.setAttribute('lang', 'en');
// Reset margin for LTR
const settingsHeaders = document.querySelectorAll('.settings-header h3');
settingsHeaders.forEach(header => {
header.style.marginLeft = '5px';
header.style.marginRight = '0';
});
if (settingsHeaders) {
settingsHeaders.forEach(header => {
header.style.marginLeft = '5px';
header.style.marginRight = '0';
});
}
}
// Make sure all elements exist before updating
if (captureBtn) captureBtn.textContent = fallbackMessages[lang].captureBtn;
if (thumbnailBtn) thumbnailBtn.textContent = fallbackMessages[lang].thumbnailBtn;
const elements = {

@@ -456,19 +405,15 @@ 'settingsLabel': fallbackMessages[lang].settings,

'mediumQualityOption': fallbackMessages[lang].mediumQuality,
'lowQualityOption': fallbackMessages[lang].lowQuality
'lowQualityOption': fallbackMessages[lang].lowQuality,
'shortcutLabel': fallbackMessages[lang].shortcutLabel,
'changeShortcut': fallbackMessages[lang].changeShortcut
};
// Update each element if it exists
for (const [id, text] of Object.entries(elements)) {
const element = document.getElementById(id);
if (element) {
element.textContent = text;
console.log(`Updated ${id} to: ${text}`);
} else {
console.warn(`Element with id ${id} not found`);
}
if (element) element.textContent = text;
}
}
// Helper: Show Status
function showStatus(message, type) {
// Translate common messages
const currentLang = document.querySelector('.language-btn.active').dataset.lang || 'en';

@@ -478,3 +423,2 @@ const commonMessages = {

'Capturing...': 'Capturing...',
'Downloading thumbnail...': 'Downloading thumbnail...',
'Settings saved': 'Settings saved',

@@ -485,3 +429,2 @@ 'Settings reset to default': 'Settings reset to default'

'Capturing...': 'جاري الالتقاط...',
'Downloading thumbnail...': 'جاري تحميل الصورة المصغرة...',
'Settings saved': 'تم حفظ الإعدادات',

@@ -492,14 +435,13 @@ 'Settings reset to default': 'تم إعادة الإعدادات للوضع الافتراضي'

// Translate message if it's one of the common ones
if (commonMessages[currentLang][message]) {
message = commonMessages[currentLang][message];
}
statusMessage.textContent = message;
statusMessage.className = 'status-message ' + type;
statusMessage.style.display = 'block';
setTimeout(() => {
statusMessage.style.display = 'none';
}, 3000);
}
}
(function() {
const video = document.querySelector('video');
if (!video) {
alert('لم يتم العثور على الفيديو!');
return;
}
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
const link = document.createElement('a');
link.download = 'frame.png';
link.href = canvas.toDataURL();
link.click();
})();
<!DOCTYPE html>
<html>
<head>
<title>Capture Frame</title>
</head>
<body>
<button id="capture">📸 التقط الإطار</button>
<script src="popup.js"></script>
</body>
</html>
document.getElementById('capture').addEventListener('click', async () => {
chrome.scripting.executeScript({
target: {tabId: (await chrome.tabs.query({active: true, currentWindow: true}))[0].id},
files: ['content.js']
});
});

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet