// ==UserScript==
// @name Cookie Updater
// @description Automatically fetch and update udemy cookies automatically
// @namespace https://greasyfork.org/users/1508709
// @version 1.0.7
// @author https://github.com/sitien173
// @match *://*.itauchile.udemy.com/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_cookie
// @grant GM_xmlhttpRequest
// @grant GM_registerMenuCommand
// @connect udemy-cookies-worker-commercial.sitienbmt.workers.dev
// @run-at document-start
// ==/UserScript==
/* eslint-disable */
/* global GM_getValue, GM_setValue */
(function() {
// Configuration
const DEFAULT_CONFIG = {
workerUrl: 'https://udemy-cookies-worker-commercial.sitienbmt.workers.dev/',
licenseKey: '',
autoUpdateInterval: 60 * 60 * 1000, // 1 hour
autoUpdateEnabled: false,
showNotifications: true,
autoReload: true,
retryAttempts: 3,
showUiButtons: true
};
let config = { ...DEFAULT_CONFIG };
// Load configuration
function loadConfig() {
const savedConfig = GM_getValue('config', {});
config = { ...DEFAULT_CONFIG, ...savedConfig };
}
// Save configuration
function saveConfig() {
GM_setValue('config', config);
}
// Persist and return a stable device id for this client
function getOrCreateDeviceId() {
let id = GM_getValue('deviceId', '');
if (!id) {
try {
if (crypto && crypto.randomUUID) {
id = crypto.randomUUID();
}
} catch (e) {}
if (!id) {
id = (Date.now().toString(36) + Math.random().toString(36).slice(2, 10));
}
// Keep it simple and URL-safe
id = id.replace(/[^a-zA-Z0-9_-]/g, '').slice(0, 64);
GM_setValue('deviceId', id);
}
return id;
}
// Fetch cookies from worker with retry logic using GM_xmlhttpRequest
async function fetchCookiesFromWorker() {
let lastError;
for (let attempt = 1; attempt <= config.retryAttempts; attempt++) {
try {
console.log(`Fetching cookies from worker (attempt ${attempt}/${config.retryAttempts})...`);
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url: config.workerUrl + '?key=' + encodeURIComponent(config.licenseKey) + '&device=' + encodeURIComponent(getOrCreateDeviceId()),
onload: function(response) {
if (response.status === 200) {
try {
if (response.responseText === '{}') {
reject(new Error('Invalid license key'));
return;
}
const cookies = JSON.parse(response.responseText);
console.log(`Successfully fetched ${cookies.length} cookies from worker`);
resolve(cookies);
} catch (error) {
console.error('Failed to parse JSON response:', error);
reject(error);
}
} else {
reject(new Error(`HTTP ${response.status}: ${response.statusText}`));
}
},
onerror: function(error) {
console.error('Network error:', error);
reject(error);
}
});
});
} catch (error) {
lastError = error;
console.error(`Attempt ${attempt} failed:`, error);
if (attempt < config.retryAttempts) {
await new Promise(resolve => setTimeout(resolve, 2000));
}
}
}
throw new Error(`Failed to fetch cookies after ${config.retryAttempts} attempts. Last error: ${lastError.message}`);
}
// Save cookie using GM_cookie
function saveCookie(cookie, url) {
const gmAvailable = typeof GM_cookie !== 'undefined' && GM_cookie && typeof GM_cookie.set === 'function';
if (gmAvailable) {
return new Promise((resolve, reject) => {
const cookieDetails = {
name: cookie.name,
value: cookie.value,
url: url,
domain: cookie.domain || undefined,
path: cookie.path || '/',
secure: cookie.secure || false,
httpOnly: cookie.httpOnly || false,
sameSite: cookie.sameSite || 'no_restriction'
};
if (cookie.expirationDate) {
cookieDetails.expirationDate = cookie.expirationDate;
}
GM_cookie.set(cookieDetails, (result, error) => {
if (error) {
console.error('Failed to save cookie:', error);
reject(error);
} else {
console.log(`Successfully saved cookie: ${cookie.name}`);
resolve(result);
}
});
});
}
// Fallback for environments where GM_cookie is not available (e.g., iOS Safari)
return new Promise((resolve) => {
// HttpOnly cannot be set via document.cookie
// Domain is omitted to restrict to current host (safest cross-browser)
let cookieStr = `${cookie.name}=${encodeURIComponent(cookie.value)}`;
cookieStr += `; path=${cookie.path || '/'}`;
if (cookie.secure) cookieStr += '; Secure';
// SameSite handling
if (cookie.sameSite && typeof cookie.sameSite === 'string') {
const s = cookie.sameSite.toLowerCase();
if (s === 'lax' || s === 'strict' || s === 'none') {
cookieStr += `; SameSite=${s.charAt(0).toUpperCase() + s.slice(1)}`;
if (s === 'none' && cookieStr.indexOf('Secure') === -1) {
cookieStr += '; Secure';
}
}
}
if (cookie.expirationDate) {
const d = new Date(0);
d.setUTCSeconds(cookie.expirationDate);
cookieStr += `; Expires=${d.toUTCString()}`;
}
document.cookie = cookieStr;
console.warn('GM_cookie not available, used document.cookie fallback. Some cookies (e.g., HttpOnly/third-party) cannot be set.');
resolve(true);
});
}
// Remove cookie using GM_cookie
function removeCookie(name, url) {
const gmAvailable = typeof GM_cookie !== 'undefined' && GM_cookie && typeof GM_cookie.delete === 'function';
if (gmAvailable) {
return new Promise((resolve, reject) => {
GM_cookie.delete({
name: name,
url: url
}, (result, error) => {
if (error) {
console.error('Failed to remove cookie:', error);
reject(error);
} else {
console.log(`Successfully removed cookie: ${name}`);
resolve(result);
}
});
});
}
// Fallback: expire the cookie for current host
return new Promise((resolve) => {
document.cookie = `${name}=; path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT`;
resolve(true);
});
}
// Get all cookies for current domain using GM_cookie
function getAllCookies(url) {
const gmAvailable = typeof GM_cookie !== 'undefined' && GM_cookie && typeof GM_cookie.list === 'function';
if (gmAvailable) {
return new Promise((resolve, reject) => {
GM_cookie.list({
url: url
}, (cookies, error) => {
if (error) {
console.error('Failed to get cookies:', error);
reject(error);
} else {
resolve(cookies);
}
});
});
}
// Fallback: parse document.cookie
return new Promise((resolve) => {
const cookieStr = document.cookie || '';
const pairs = cookieStr ? cookieStr.split('; ') : [];
const results = pairs.map(p => {
const eqIdx = p.indexOf('=');
const name = eqIdx >= 0 ? p.slice(0, eqIdx) : p;
const value = eqIdx >= 0 ? decodeURIComponent(p.slice(eqIdx + 1)) : '';
return { name, value };
});
resolve(results);
});
}
// Update cookies from worker
async function updateCookiesFromWorker() {
try {
console.log('Starting cookie update process...');
const newCookies = await fetchCookiesFromWorker();
if (!newCookies || !Array.isArray(newCookies) || newCookies.length === 0) {
console.log('No cookies were fetched from worker.');
showNotification('No cookies were fetched from worker.', 'warning');
return { success: false, message: 'No cookies fetched' };
}
const currentUrl = window.location.href;
const existingCookies = await getAllCookies(currentUrl);
const existingCookieNames = existingCookies.map(c => c.name);
let successCount = 0;
let errorCount = 0;
// Process each new cookie
for (const cookie of newCookies) {
try {
// Remove existing cookie if it exists
if (existingCookieNames.includes(cookie.name)) {
await removeCookie(cookie.name, currentUrl);
console.log(`Removed existing cookie: ${cookie.name}`);
}
// Add new cookie
await saveCookie(cookie, currentUrl);
successCount++;
console.log(`Successfully saved cookie: ${cookie.name}`);
} catch (error) {
errorCount++;
console.error(`Failed to process cookie ${cookie.name}:`, error);
}
}
const message = `Updated ${successCount} cookies successfully${errorCount > 0 ? `, ${errorCount} failed` : ''}`;
showNotification(message, errorCount > 0 ? 'error' : 'success');
// Auto reload if enabled
if (config.autoReload && successCount > 0) {
setTimeout(() => {
window.location.reload();
}, 1000);
}
return { success: true, stats: { total: newCookies.length, success: successCount, error: errorCount } };
} catch (error) {
console.error('Error updating cookies:', error);
showNotification('Failed to update cookies: ' + error.message, 'error');
return { success: false, error: error.message };
}
}
// Show notification
function showNotification(message, type = 'info') {
if (!config.showNotifications) return;
const existingNotification = document.getElementById('udemy-cookie-notification');
if (existingNotification) {
existingNotification.remove();
}
const notification = document.createElement('div');
notification.id = 'udemy-cookie-notification';
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
padding: 15px 20px;
border-radius: 5px;
color: white;
font-family: Arial, sans-serif;
font-size: 14px;
z-index: 10000;
max-width: 300px;
word-wrap: break-word;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transition: opacity 0.3s ease;
`;
switch (type) {
case 'success':
notification.style.backgroundColor = '#4CAF50';
break;
case 'error':
notification.style.backgroundColor = '#f44336';
break;
case 'warning':
notification.style.backgroundColor = '#ff9800';
break;
default:
notification.style.backgroundColor = '#2196F3';
}
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
if (notification.parentNode) {
notification.style.opacity = '0';
setTimeout(() => {
if (notification.parentNode) {
notification.remove();
}
}, 300);
}
}, 3000);
}
// Create settings panel
function createSettingsPanel() {
const panel = document.createElement('div');
panel.id = 'udemy-cookie-settings-panel';
panel.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
z-index: 10001;
font-family: Arial, sans-serif;
min-width: 400px;
`;
panel.innerHTML = `
<h2 style="margin-top: 0; color: #333;">Udemy Cookie Updater Settings</h2>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: bold;">Worker URL:</label>
<input type="text" id="worker-url" value="${config.workerUrl}" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: bold;">License Key:</label>
<input type="text" id="license-key" value="${config.licenseKey}" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: bold;">Auto Update Interval (minutes):</label>
<input type="number" id="auto-update-interval" value="${config.autoUpdateInterval / 60000}" min="1" max="60" style="width: 100px; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
</div>
<div style="margin-bottom: 15px;">
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="checkbox" id="auto-update-enabled" ${config.autoUpdateEnabled ? 'checked' : ''} style="margin-right: 8px;">
Enable Auto Update
</label>
</div>
<div style="margin-bottom: 15px;">
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="checkbox" id="show-notifications" ${config.showNotifications ? 'checked' : ''} style="margin-right: 8px;">
Show Notifications
</label>
</div>
<div style="margin-bottom: 15px;">
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="checkbox" id="auto-reload" ${config.autoReload ? 'checked' : ''} style="margin-right: 8px;">
Auto Reload Page After Update
</label>
</div>
<div style="margin-bottom: 15px;">
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="checkbox" id="show-ui-buttons" ${config.showUiButtons ? 'checked' : ''} style="margin-right: 8px;">
Show Floating Buttons (Fetch Cookies, Settings)
</label>
</div>
<div style="display: flex; gap: 10px; justify-content: flex-end;">
<button id="save-settings" style="padding: 10px 20px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer;">Save Settings</button>
<button id="cancel-settings" style="padding: 10px 20px; background: #9E9E9E; color: white; border: none; border-radius: 4px; cursor: pointer;">Cancel</button>
</div>
`;
panel.querySelector('#save-settings').addEventListener('click', () => {
config.workerUrl = panel.querySelector('#worker-url').value;
config.licenseKey = panel.querySelector('#license-key').value;
config.autoUpdateInterval = parseInt(panel.querySelector('#auto-update-interval').value) * 60000;
config.autoUpdateEnabled = panel.querySelector('#auto-update-enabled').checked;
config.showNotifications = panel.querySelector('#show-notifications').checked;
config.autoReload = panel.querySelector('#auto-reload').checked;
config.showUiButtons = panel.querySelector('#show-ui-buttons').checked;
saveConfig();
panel.remove();
showNotification('Settings saved successfully!', 'success');
renderFloatingControls();
});
panel.querySelector('#cancel-settings').addEventListener('click', () => {
panel.remove();
});
panel.addEventListener('click', (e) => {
if (e.target === panel) {
panel.remove();
}
});
document.body.appendChild(panel);
}
// Create floating UI controls (Fetch Cookies, Settings)
function renderFloatingControls() {
const existing = document.getElementById('udemy-cookie-controls');
if (existing) {
existing.remove();
}
if (!config.showUiButtons) return;
const container = document.createElement('div');
container.id = 'udemy-cookie-controls';
container.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
display: flex;
flex-direction: column;
gap: 10px;
z-index: 10002;
`;
const btnStyle = `
padding: 10px 14px;
background: #1f2937;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-family: Arial, sans-serif;
font-size: 13px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
`;
const fetchBtn = document.createElement('button');
fetchBtn.textContent = 'Fetch Cookies';
fetchBtn.style.cssText = btnStyle;
fetchBtn.addEventListener('click', async () => {
await updateCookiesFromWorker();
});
const settingsBtn = document.createElement('button');
settingsBtn.textContent = 'Settings';
settingsBtn.style.cssText = btnStyle + 'background:#2563EB;';
settingsBtn.addEventListener('click', () => {
createSettingsPanel();
});
container.appendChild(fetchBtn);
container.appendChild(settingsBtn);
document.body.appendChild(container);
}
// Auto-update functionality
function startAutoUpdate() {
const lastUpdate = GM_getValue('lastCookieUpdate', 0);
const now = Date.now();
if (now - lastUpdate > config.autoUpdateInterval) {
updateCookiesFromWorker();
GM_setValue('lastCookieUpdate', now);
}
setInterval(() => {
updateCookiesFromWorker();
GM_setValue('lastCookieUpdate', Date.now());
}, config.autoUpdateInterval);
}
// Register menu commands
function registerMenuCommands() {
GM_registerMenuCommand('Update Cookies Now', async () => {
await updateCookiesFromWorker();
});
GM_registerMenuCommand('Open Settings', () => {
createSettingsPanel();
});
}
// Initialize
function initialize() {
loadConfig();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initialize);
return;
}
registerMenuCommands();
if (config.autoUpdateEnabled) {
startAutoUpdate();
}
// Render floating UI controls based on settings
renderFloatingControls();
}
initialize();
})();