// ==UserScript==
// @name Enhanced Fake Camera for VFS Global (Anti-Detection)
// @namespace http://tampermonkey.net/
// @version 3.0
// @description Replaces real camera with photo or video with enhanced anti-detection measures
// @author Original: Brazuca! (Modified for anti-detection)
// @match https://idnvui.vfsglobal.com/*
// @match https://visa.vfsglobal.com/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
// ==================== STATE VARIABLES ====================
let cameraActive = false;
let mediaURL = null;
let videoStream = null;
let canvas = null;
let animationFrameId = null;
let videoElement = null;
let isPaused = false;
// ==================== ADVANCED MOCK VARIABLES ====================
// Store original methods to restore later
const originalGetUserMedia = navigator.mediaDevices.getUserMedia;
const originalEnumerateDevices = navigator.mediaDevices.enumerateDevices;
const originalMediaStreamTrack = window.MediaStreamTrack;
// Mock camera device info
const mockCameraDeviceId = 'mock-camera-' + Math.random().toString(36).substring(2, 15);
const mockCameraGroupId = 'mock-group-' + Math.random().toString(36).substring(2, 15);
const mockCameraLabel = ['HD Webcam', 'USB Camera', 'Integrated Camera']
[Math.floor(Math.random() * 3)];
// Zoom control variables
let zoomLevel = 1.0;
const zoomIncrement = 0.1;
const maxZoom = 3.0;
const minZoom = 0.5;
// Position control variables
let offsetX = 0;
let offsetY = 0;
// Reference to UI elements
let zoomContainer = null;
let pauseContainer = null;
let pauseButton = null;
let zoomInButton = null;
let zoomOutButton = null;
let zoomCounter = null;
let directionContainer = null;
// ==================== HELPER FUNCTIONS ====================
function createElement(tagName, styles) {
const element = document.createElement(tagName);
Object.assign(element.style, styles);
return element;
// Generate a random but realistic camera device ID
function generateMockDeviceId() {
return 'mock-camera-' + Math.random().toString(36).substring(2, 15);
// Function to create realistic camera constraints
function createRealisticConstraints(constraints) {
// Preserve the original constraints where possible
let result = { ...constraints };
// Add realistic video constraints if not already specified
if (!result.video || typeof result.video === 'boolean') {
result.video = {
width: { ideal: 1280, min: 640, max: 1920 },
height: { ideal: 720, min: 480, max: 1080 },
frameRate: { ideal: 30, min: 15 },
facingMode: "user"
};
} else if (typeof result.video === 'object') {
// Enhance existing video constraints
result.video = {
...result.video,
deviceId: { exact: mockCameraDeviceId },
frameRate: result.video.frameRate || { ideal: 30, min: 15 }
};
return result;
// ==================== MOCK IMPLEMENTATION ====================
async function activateFakeCamera(url, isPhoto = false) {
// Clear previous state
if (cameraActive) {
deactivateFakeCamera();
try {
// Reset zoom and position
zoomLevel = 1.0;
offsetX = 0;
offsetY = 0;
isPaused = false;
mediaURL = url;
canvas = document.createElement('canvas');
canvas.width = 1280; // Higher resolution for better quality
canvas.height = 720; // 16:9 aspect ratio
let ctx = canvas.getContext('2d', { alpha: false, desynchronized: true });
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
if (isPhoto) {
// Handle photo with quality preservation
let image = new Image();
image.crossOrigin = "anonymous";
image.src = url;
await new Promise((resolve, reject) => {
image.onload = resolve;
image.onerror = reject;
setTimeout(() => reject(new Error("Timeout loading image")), 8000);
});
// Calculate ratio to preserve aspect
const ratio = Math.min(
canvas.width / image.width,
canvas.height / image.height
);
const newWidth = image.width * ratio;
const newHeight = image.height * ratio;
// Center image on canvas
const x = (canvas.width - newWidth) / 2;
const y = (canvas.height - newHeight) / 2;
// Clear canvas with black background
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw image with high quality
ctx.drawImage(image, x, y, newWidth, newHeight);
// For photos, create a stream with minimal frames (static image)
videoStream = canvas.captureStream(5);
// Update UI controls
updateZoomControls(true);
updatePauseButton(true);
} else {
// Handle video with proper playback
videoElement = document.createElement('video');
videoElement.src = url;
videoElement.loop = true;
videoElement.muted = true;
videoElement.autoplay = true;
videoElement.playsInline = true;
videoElement.crossOrigin = "anonymous";
// Apply additional attributes to make video seem more realistic
videoElement.volume = 0;
videoElement.onloadedmetadata = () => {
// Adjust canvas dimensions to match video if needed
if (videoElement.videoWidth && videoElement.videoHeight) {
// Keep aspect ratio but maintain quality
const aspectRatio = videoElement.videoWidth / videoElement.videoHeight;
if (aspectRatio > 1) {
// Landscape video
canvas.width = Math.min(1280, videoElement.videoWidth);
canvas.height = canvas.width / aspectRatio;
} else {
// Portrait or square video
canvas.height = Math.min(720, videoElement.videoHeight);
canvas.width = canvas.height * aspectRatio;
};
try {
await videoElement.play();
} catch (e) {
console.warn("Warning starting video playback:", e);
// Continue despite error - may be autoplay policy
// Function to draw video frames with zoom and positioning
function drawFrame() {
if (!canvas || !ctx || !videoElement) return;
if (videoElement.videoWidth && videoElement.videoHeight) {
// Calculate base dimensions preserving aspect ratio
const ratio = Math.min(
canvas.width / videoElement.videoWidth,
canvas.height / videoElement.videoHeight
);
const baseWidth = videoElement.videoWidth * ratio;
const baseHeight = videoElement.videoHeight * ratio;
// Apply zoom
const scaledWidth = baseWidth * zoomLevel;
const scaledHeight = baseHeight * zoomLevel;
// Calculate centered position with zoom and offset
const baseX = (canvas.width - baseWidth) / 2;
const baseY = (canvas.height - baseHeight) / 2;
// Offset adjusted for zoom
const x = baseX - ((scaledWidth - baseWidth) / 2) + offsetX;
const y = baseY - ((scaledHeight - baseHeight) / 2) + offsetY;
// Clear with black background
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw the video frame with applied transformations
ctx.drawImage(videoElement, x, y, scaledWidth, scaledHeight);
// Update zoom counter if present
if (zoomCounter) {
zoomCounter.textContent = `Zoom: ${zoomLevel.toFixed(1)}x`;
}
} else {
// Fallback if video dimensions aren't available
ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
// Continue animation loop
animationFrameId = requestAnimationFrame(drawFrame);
// Start drawing frames
drawFrame();
// Create stream with higher framerate for smoother video
videoStream = canvas.captureStream(30);
// Enable UI controls
updateZoomControls(false);
updatePauseButton(false);
// Add realistic tracks metadata to the stream
enhanceStreamWithRealisticTracks(videoStream);
// Override getUserMedia with our enhanced mock implementation
overrideMediaAPIs();
cameraActive = true;
updateButtons();
console.log(isPhoto ? "Fake photo activated!" : "Fake camera activated!");
} catch (error) {
console.error("Error activating fake media:", error);
alert("Error loading media. Check the selected file or try a smaller file.");
// Clean up in case of error
deactivateFakeCamera();
function enhanceStreamWithRealisticTracks(stream) {
if (!stream || !stream.getVideoTracks || stream.getVideoTracks().length === 0) return;
// Get the actual video track
const videoTrack = stream.getVideoTracks()[0];
// Add realistic properties to the track
if (videoTrack) {
// Store original methods to be used in our mocked ones
const originalGetSettings = videoTrack.getSettings;
const originalGetCapabilities = videoTrack.getCapabilities;
const originalGetConstraints = videoTrack.getConstraints;
const originalApplyConstraints = videoTrack.applyConstraints;
// Override getSettings to return realistic camera settings
videoTrack.getSettings = function() {
// Start with original settings if available
let settings = {};
try {
settings = originalGetSettings.call(this);
} catch (e) {
// Ignore errors from original method
// Add realistic camera settings
return {
...settings,
deviceId: mockCameraDeviceId,
groupId: mockCameraGroupId,
aspectRatio: canvas.width / canvas.height,
frameRate: 30,
height: canvas.height,
width: canvas.width,
resizeMode: "none",
facingMode: "user"
};
};
// Override getCapabilities with realistic camera capabilities
videoTrack.getCapabilities = function() {
let capabilities = {};
try {
capabilities = originalGetCapabilities.call(this);
} catch (e) {
// Ignore errors
return {
...capabilities,
deviceId: mockCameraDeviceId,
aspectRatio: {min: 0.5, max: 2.0},
frameRate: {min: 10, max: 60},
height: {min: 240, max: 1080, step: 1},
width: {min: 320, max: 1920, step: 1},
facingMode: ["user", "environment"]
};
};
// Override getConstraints to return fake constraints
videoTrack.getConstraints = function() {
let constraints = {};
try {
constraints = originalGetConstraints.call(this);
} catch (e) {
// Ignore errors
return {
...constraints,
deviceId: {exact: mockCameraDeviceId},
aspectRatio: {ideal: canvas.width / canvas.height},
frameRate: {ideal: 30},
height: {ideal: canvas.height},
width: {ideal: canvas.width},
facingMode: "user"
};
};
// Override applyConstraints to make it appear to work
videoTrack.applyConstraints = async function(constraints) {
try {
// Try original method first
return await originalApplyConstraints.call(this, constraints);
} catch (e) {
// Just return success - we're faking it anyway
return Promise.resolve();
}
};
// Override label and id with realistic values
Object.defineProperties(videoTrack, {
'label': {
get: function() { return mockCameraLabel; },
configurable: true
},
'id': {
get: function() { return mockCameraDeviceId + '-track'; },
configurable: true
},
'kind': {
get: function() { return 'video'; },
configurable: true
},
'enabled': {
get: function() { return true; },
set: function(value) { /* Do nothing */ },
configurable: true
},
'muted': {
get: function() { return false; },
configurable: true
},
'readyState': {
get: function() { return 'live'; },
configurable: true
});
function overrideMediaAPIs() {
// Override getUserMedia to return our fake stream
navigator.mediaDevices.getUserMedia = async function(constraints) {
// If camera is not active or constraints explicitly request something other than video,
// fallback to original implementation
if (!cameraActive || (constraints && constraints.audio && !constraints.video)) {
return originalGetUserMedia.call(this, constraints);
// Apply realistic constraints to make the mocked stream look more legitimate
const enhancedConstraints = createRealisticConstraints(constraints);
console.log("Enhanced constraints:", enhancedConstraints);
// Return our fake video stream
return new Promise((resolve) => {
resolve(videoStream);
});
};
// Override enumerateDevices to include our fake camera
navigator.mediaDevices.enumerateDevices = async function() {
try {
// Get the real devices first
const realDevices = await originalEnumerateDevices.call(this);
// If camera is not active, just return real devices
if (!cameraActive) {
return realDevices;
// Filter out any existing video devices that might conflict with ours
const nonVideoDevices = realDevices.filter(device => device.kind !== 'videoinput');
// Create our fake device
const fakeCamera = {
deviceId: mockCameraDeviceId,
groupId: mockCameraGroupId,
kind: 'videoinput',
label: mockCameraLabel,
toJSON: function() {
return {
deviceId: this.deviceId,
groupId: this.groupId,
kind: this.kind,
label: this.label
};
};
// Add our fake camera to the list
return [...nonVideoDevices, fakeCamera];
} catch (e) {
console.error("Error in enumerateDevices:", e);
// Fallback to original implementation in case of error
return originalEnumerateDevices.call(this);
};
function deactivateFakeCamera() {
// Stop animation frame if active
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
}
// Restore original media APIs
navigator.mediaDevices.getUserMedia = originalGetUserMedia;
navigator.mediaDevices.enumerateDevices = originalEnumerateDevices;
// Clean up resources
if (mediaURL && mediaURL.startsWith('blob:')) {
try {
URL.revokeObjectURL(mediaURL);
} catch(e) {
console.warn("Error revoking URL:", e);
// Reset state variables
mediaURL = null;
videoStream = null;
canvas = null;
videoElement = null;
isPaused = false;
cameraActive = false;
// Update UI
updateButtons();
updateZoomControls(true);
updatePauseButton(true);
console.log("Fake camera deactivated!");
// ==================== UI CONTROL FUNCTIONS ====================
function updateButtons() {
if (!videoButton || !photoButton) return;
if (cameraActive) {
videoButton.textContent = '🔴 Disable Media';
videoButton.style.backgroundColor = 'red';
photoButton.textContent = '🚫 Import Media';
photoButton.style.backgroundColor = 'gray';
photoButton.disabled = true;
} else {
videoButton.textContent = '🎥 Activate Video';
videoButton.style.backgroundColor = 'green';
photoButton.textContent = ' Import Photo';
photoButton.style.backgroundColor = 'blue';
photoButton.disabled = false;
}
function togglePause() {
if (!videoElement) return;
if (isPaused) {
videoElement.play();
pauseButton.textContent = ' Pause';
pauseButton.title = "Pause video";
} else {
videoElement.pause();
pauseButton.textContent = '▶️Play';
pauseButton.title = "Play video";
isPaused = !isPaused;
function updatePauseButton(hide) {
if (!pauseContainer) return;
pauseContainer.style.display = hide ? 'none' : 'flex';
// Reset pause button state
if (!hide && pauseButton) {
isPaused = false;
pauseButton.textContent = ' Pause';
pauseButton.title = "Pause video";
function updateZoomControls(hide) {
if (!zoomContainer) return;
zoomContainer.style.display = hide ? 'none' : 'flex';
if (directionContainer) {
directionContainer.style.display = hide ? 'none' : 'flex';
function adjustZoom(increment) {
// Adjust zoom level with limits
const newZoom = Math.min(maxZoom, Math.max(minZoom, zoomLevel + increment));
// Only update if there's a real change
if (newZoom !== zoomLevel) {
zoomLevel = newZoom;
// Reset offset when returning to normal zoom
if (Math.abs(zoomLevel - 1.0) < 0.05) {
offsetX = 0;
offsetY = 0;
}
}
// ==================== UI CREATION FUNCTIONS ====================
function createDirectionControls() {
const container = createElement('div', {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '5px',
marginTop: '10px'
});
// Up button
const upButton = createElement('button', {
padding: '5px 10px',
backgroundColor: '#444',
color: 'white',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
margin: '0',
fontWeight: 'bold',
width: '30px',
textAlign: 'center'
});
upButton.textContent = '↑';
upButton.title = "Move up";
upButton.addEventListener('click', () => { offsetY += 10; });
// Horizontal buttons container
const horizontalContainer = createElement('div', {
display: 'flex',
gap: '15px',
alignItems: 'center'
});
// Left button
const leftButton = createElement('button', {
padding: '5px 10px',
backgroundColor: '#444',
color: 'white',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
margin: '0',
fontWeight: 'bold',
width: '30px',
textAlign: 'center'
});
leftButton.textContent = '←';
leftButton.title = "Move left";
leftButton.addEventListener('click', () => { offsetX += 10; });
// Center button
const centerButton = createElement('button', {
padding: '5px 10px',
backgroundColor: '#555',
color: 'white',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
margin: '0',
fontWeight: 'bold',
width: '30px',
textAlign: 'center'
});
centerButton.textContent = '⊙';
centerButton.title = "Reset position";
centerButton.addEventListener('click', () => {
offsetX = 0;
offsetY = 0;
});
// Right button
const rightButton = createElement('button', {
padding: '5px 10px',
backgroundColor: '#444',
color: 'white',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
margin: '0',
fontWeight: 'bold',
width: '30px',
textAlign: 'center'
});
rightButton.textContent = '→';
rightButton.title = "Move right";
rightButton.addEventListener('click', () => { offsetX -= 10; });
// Down button
const downButton = createElement('button', {
padding: '5px 10px',
backgroundColor: '#444',
color: 'white',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
margin: '0',
fontWeight: 'bold',
width: '30px',
textAlign: 'center'
});
downButton.textContent = '↓';
downButton.title = "Move down";
downButton.addEventListener('click', () => { offsetY -= 10; });
// Assemble horizontal container
horizontalContainer.appendChild(leftButton);
horizontalContainer.appendChild(centerButton);
horizontalContainer.appendChild(rightButton);
// Assemble direction container
container.appendChild(upButton);
container.appendChild(horizontalContainer);
container.appendChild(downButton);
container.style.display = 'none'; // Hidden initially
return container;
function createControlPanel() {
// Create main container
const controlPanel = createElement('div', {
position: 'fixed',
bottom: '10px',
right: '10px',
backgroundColor: 'rgba(0, 0, 0, 0.7)',
padding: '10px',
borderRadius: '5px',
zIndex: '9999',
color: 'white',
fontFamily: 'Arial, sans-serif',
fontSize: '14px',
boxShadow: '0 0 10px rgba(0, 0, 0, 0.5)',
display: 'flex',
flexDirection: 'column',
gap: '10px'
});
// Title
const title = createElement('div', {
fontWeight: 'bold',
textAlign: 'center',
borderBottom: '1px solid #555',
paddingBottom: '5px',
marginBottom: '5px'
});
title.textContent = 'Camera Controls';
// Container for video and photo buttons
const mediaButtonsContainer = createElement('div', {
display: 'flex',
gap: '5px',
justifyContent: 'center'
});
// Video button
const videoButton = createElement('button', {
padding: '8px 12px',
backgroundColor: 'green',
color: 'white',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
flex: '1'
});
videoButton.textContent = '🎥 Activate Video';
videoButton.title = "Select and activate a video file";
videoButton.addEventListener('click', handleVideoButtonClick);
// Photo button
const photoButton = createElement('button', {
padding: '8px 12px',
backgroundColor: 'blue',
color: 'white',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
flex: '1'
});
photoButton.textContent = ' Import Photo';
photoButton.title = "Select and use a photo";
photoButton.addEventListener('click', handlePhotoButtonClick);
// Add file input (hidden)
const fileInput = createElement('input', {
display: 'none',
type: 'file'
});
fileInput.type = 'file';
fileInput.accept = 'image/*,video/*';
// Zoom controls container
zoomContainer = createElement('div', {
display: 'none', // Hidden initially
flexDirection: 'row',
gap: '5px',
alignItems: 'center',
justifyContent: 'center'
});
// Zoom out button
zoomOutButton = createElement('button', {
padding: '5px 10px',
backgroundColor: '#444',
color: 'white',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
fontWeight: 'bold'
});
zoomOutButton.textContent = '➖';
zoomOutButton.title = "Zoom out";
zoomOutButton.addEventListener('click', () => adjustZoom(-zoomIncrement));
// Zoom counter
zoomCounter = createElement('span', {
minWidth: '80px',
textAlign: 'center'
});
zoomCounter.textContent = 'Zoom: 1.0x';
// Zoom in button
zoomInButton = createElement('button', {
padding: '5px 10px',
backgroundColor: '#444',
color: 'white',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
fontWeight: 'bold'
});
zoomInButton.textContent = '➕';
zoomInButton.title = "Zoom in";
zoomInButton.addEventListener('click', () => adjustZoom(zoomIncrement));
// Pause controls container
pauseContainer = createElement('div', {
display: 'none', // Hidden initially
justifyContent: 'center'
});
// Pause/play button
pauseButton = createElement('button', {
padding: '5px 10px',
backgroundColor: '#444',
color: 'white',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
width: '100%'
});
pauseButton.textContent = ' Pause';
pauseButton.title = "Pause video";
pauseButton.addEventListener('click', togglePause);
// Create direction controls
directionContainer = createDirectionControls();
// Close button (X in top right corner)
const closeButton = createElement('button', {
position: 'absolute',
top: '5px',
right: '5px',
background: 'none',
border: 'none',
color: 'white',
cursor: 'pointer',
fontSize: '16px',
padding: '0',
margin: '0',
lineHeight: '1'
});
closeButton.textContent = '×';
closeButton.title = "Hide controls";
closeButton.addEventListener('click', () => {
controlPanel.style.display = 'none';
});
// Minimize button (to show when control panel is hidden)
const minimizeButton = createElement('button', {
position: 'fixed',
bottom: '10px',
right: '10px',
backgroundColor: 'rgba(0, 0, 0, 0.7)',
color: 'white',
border: 'none',
borderRadius: '5px',
padding: '5px 10px',
cursor: 'pointer',
zIndex: '9998',
display: 'none'
});
minimizeButton.textContent = '📷';
minimizeButton.title = "Show camera controls";
minimizeButton.addEventListener('click', () => {
controlPanel.style.display = 'flex';
minimizeButton.style.display = 'none';
});
// When control panel is closed, show minimize button
closeButton.addEventListener('click', () => {
minimizeButton.style.display = 'block';
});
// Add event listener for file selection
fileInput.addEventListener('change', handleFileSelect);
// Assemble zoom container
zoomContainer.appendChild(zoomOutButton);
zoomContainer.appendChild(zoomCounter);
zoomContainer.appendChild(zoomInButton);
// Assemble pause container
pauseContainer.appendChild(pauseButton);
// Assemble media buttons
mediaButtonsContainer.appendChild(videoButton);
mediaButtonsContainer.appendChild(photoButton);
// Assemble control panel
controlPanel.appendChild(closeButton);
controlPanel.appendChild(title);
controlPanel.appendChild(mediaButtonsContainer);
controlPanel.appendChild(zoomContainer);
controlPanel.appendChild(pauseContainer);
controlPanel.appendChild(directionContainer);
controlPanel.appendChild(fileInput);
// Add both elements to the document
document.body.appendChild(controlPanel);
document.body.appendChild(minimizeButton);
// Store references to elements
window.videoButton = videoButton;
window.photoButton = photoButton;
window.fileInput = fileInput;
// ==================== EVENT HANDLERS ====================
function handleVideoButtonClick() {
if (cameraActive) {
deactivateFakeCamera();
} else {
fileInput.setAttribute('accept', 'video/*');
fileInput.click();
}
function handlePhotoButtonClick() {
fileInput.setAttribute('accept', 'image/*');
fileInput.click();
function handleFileSelect(event) {
const file = event.target.files[0];
if (!file) return;
// Clear the input value so the same file can be selected again
event.target.value = '';
const isPhoto = file.type.startsWith('image/');
const url = URL.createObjectURL(file);
activateFakeCamera(url, isPhoto);
// ==================== KEYBOARD SHORTCUTS ====================
function setupKeyboardShortcuts() {
document.addEventListener('keydown', (event) => {
// Only if camera is active
if (!cameraActive) return;
switch(event.key) {
case '+':
case '=': // For keyboards where + requires Shift
adjustZoom(zoomIncrement);
break;
case '-':
adjustZoom(-zoomIncrement);
break;
case ' ': // Space bar
if (videoElement) togglePause();
break;
case 'ArrowUp':
offsetY += 10;
break;
case 'ArrowDown':
offsetY -= 10;
break;
case 'ArrowLeft':
offsetX += 10;
break;
case 'ArrowRight':
offsetX -= 10;
break;
case '0': // Reset zoom and position
zoomLevel = 1.0;
offsetX = 0;
offsetY = 0;
break;
});
// ==================== INITIALIZATION ====================
function init() {
// Create control panel when page is loaded
createControlPanel();
// Setup keyboard shortcuts
setupKeyboardShortcuts();
console.log("Enhanced Fake Camera for VFS Global initialized!");
// Initialize when DOM is loaded
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
})();