Cross-tab download queue, resolution pipeline, and immediate downloads for Brazen user scripts
This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greasyfork.org/scripts/587126/1884339/Brazen%20Framework%20-%20Download%20Manager.js
Cross-tab batch downloads, page resolution, immediate GM_download orchestration, optional tag discovery review, and human-interaction rate-limit handling. Apps extend BrazenFramework, call configureDownloadManager() after configureDock(), and @require this module after Framework core. Selection tiles and progress panels are native HTMLElement.
Greasy Fork: Download Manager · Requires: Framework core, IndexedDB Storage (v1.1.0+ download queue stores) · Grant on this module: GM_download
GM_download with no queue, no cross-tab state, and no path tokens — that is rare; most Brazen apps still benefit from ledger + path helpers here.configureDownloadManager() throws if configureDock() was not called first.Load order (after Framework core):
// @require … Brazen Framework - Framework.js
// @require … Brazen Framework - Download Manager.js
In the app constructor (after configureDock):
this.configureDock({ orientations: ['right'], scriptName: 'My App', showBranding: true })
this.configureDownloadManager({
enableConfigKey: 'enable-download-manager',
downloadPaths: {
folderConfigKey: 'download-folder',
filenamePatternConfigKey: 'filename-pattern',
subfolderPatternConfigKey: 'subfolder-pattern',
defaultFolder: 'my-downloads',
getPatternResolver: () => ({
chips, tagTypes, ignore, substitutions, unknownDefault,
}),
extractMediaData: (el) => ({ id, md5, ext }),
extractTagGroups: () => ({ author: [], character: [], general: [] }),
extractTagIncidences: () => ({ 'tag name': 12 }), // optional; discovery panel counts
appendExtension: true,
nameFallback: (data) => data.id || 'media',
},
pages: {
search: {
roles: ['selection'],
itemSelector: 'span.thumb',
resolveItem: (item) => ({ itemId, sourceUrl }),
setItemProgress: (item, progress) => { /* optional tile overlay */ },
},
media: {
roles: ['enqueueMedia', 'immediateDownload'],
defaultDownloadType: 'post',
},
},
downloadTypes: {
post: {
resolveFromSearch: (ctx) => ({ nextUrl: ctx.sourceUrl }),
resolveFromMedia: (doc, ctx) => ({ mediaUrl, tagGroups, downloadId, data }),
},
},
getQueueItemId: (ctx) => ctx.itemId,
resolutionInitiationGapMs: 2000,
downloadInitiationGapMs: 2000,
linkQueues: false, // default; set true when site + CDN share rate limits
})
BrazenDownloadManager.initialize() runs from Framework init() after Configuration Manager setup.
| Key | Role |
|---|---|
enableConfigKey |
Flag field key for Enable Download Manager (auto-registered unless enabled: true) |
enableDefault |
Default for enable flag when auto-registered |
enabled |
When true, skip enable-flag registration and treat manager as always on |
linkQueues |
When true, InterleavedPolicy: one serial dispatcher, any-lane HI pauses the shared regime, download may run while tag discovery gates resolution. Default false (IndependentPolicy): concurrent lanes; tag discovery pauses resolution only; each lane's human-interaction block stops only that lane (both panels may show). Use true when site and download CDN share rate limits. |
selectionModeDefaultConfigKey |
Optional override for Start in Selection Mode (OPTION_DOWNLOAD_SELECTION_MODE_DEFAULT) |
downloadPaths |
Path/token extraction and pattern resolver (see below) |
pages |
Named page configs with roles (see below) |
downloadTypes |
Per-type resolution handlers (resolveFromSearch, resolveFromMedia, …) |
getQueueItemId |
(context) => string stable queue id |
resolutionInitiationGapMs |
ms between resolution fetches (default 2000) |
downloadInitiationGapMs |
ms between GM_download calls (default 2000; replaces Framework downloadsDelay) |
tagDiscovery |
Optional review panel config (see below) |
rateLimitHandlers |
Per-context rate-limit strategies (see below) |
queueDockSlideOut |
Optional Start/Pause slide-out overrides: childFields (default dock-download-leader, dock-clear-download-queue), getSlideOutNodes (default progress slot), slideOutWhen / slideOutPinnedWhen |
_registerConfigFields always registers (when missing):
| Constant | Key | Title |
|---|---|---|
OPTION_DOWNLOAD_SELECTION_MODE_DEFAULT |
download-selection-mode-default |
Start in Selection Mode (place with createElement on Downloads) |
OPTION_REVIEW_IGNORED_FILENAME_PINS |
review-ignored-filename-pins |
Review Ignored Download Path Tags |
OPTION_SKIP_EMPTY_FILENAME_PINS |
skip-empty-filename-pins |
Skip Media Without Download Path Tags |
OPTION_ONLY_SHOW_DOWNLOAD_MANAGER |
only-show-download-manager |
Only Show the Download Manager |
Mount the last three via Framework createBehavioursTabPanel(). Only Show the Download Manager hides dock buttons marked immediateDownload (autoDownload, removeMediaOnDownload, singleDownload templates) via Configuration Manager include evaluation.
downloadPaths| Key | Role |
|---|---|
folderConfigKey / filenamePatternConfigKey / subfolderPatternConfigKey |
Configuration Manager keys for folder and patterns |
defaultFolder |
Fallback root folder |
getPatternResolver |
() => { chips, tagTypes, ignore, substitutions, unknownDefault, … } — tag-type tokens use registry attributes when TagRuntime is warmed |
extractMediaData(el) |
Metadata object for pattern chips (id, md5, ext, …) |
extractTagGroups() |
{ typeKey: string[] } tag lists for path tokens |
extractTagIncidences() |
Optional { tagName: count } map for discovery panel incidence meta |
appendExtension |
Append data.ext to filename when set |
nameFallback(data) |
When pattern resolves empty |
Path helpers (buildDownloadPathFromPatterns, buildDownloadPath, sanitizePathSegment, substitution parse/map) live on the manager instance — use getDownloadManager() from the app.
pages and rolesEach pages[pageName] entry:
| Key | Role |
|---|---|
roles |
Capability flags for the active page (see table) |
itemSelector |
Search tiles for selection mode |
resolveItem(item) |
(item: HTMLElement) => { itemId, sourceUrl, … } for enqueue |
defaultDownloadType |
Fallback type key for media enqueue |
setItemProgress(item, progress) |
Optional hook for per-tile pipeline UI (item: HTMLElement) |
| Role | Enables |
|---|---|
selection |
Selection mode + click-to-enqueue on search tiles |
enqueueMedia |
Media-page add/remove queue toggle |
immediateDownload |
downloadImmediate() on media pages |
tagDiscoveryToggle |
Dock tag-discovery mode button |
dashboard |
Dedicated control / future-analytics host. Does not grant selection, enqueue, immediate download, or tag-discovery toggle by itself. Does not auto-claim processor leadership — crown / sole-tab rules unchanged. Apps decide which dock buttons to hide on their dashboard page. Framework exposes isDashboardPage() for this role. |
Query with isDownloadPageRole('selection') (delegated on Framework).
tagDiscovery (optional)| Key | Role |
|---|---|
discoverAt |
Steps that scan pattern-relevant types, e.g. ['resolution', 'immediate'] |
tagTypes |
Panel grouping: { label, rowClass, color } per type — colors via View Layer applyTagDiscoveryTypeColors |
panel |
Extra options forwarded to renderTagDiscoveryPanelContent (section labels, groupOrder, …) |
actions |
Incidence options for tag-attribute buttons (buildUrl, field keys, CSS, ensureOptionKey) — framework owns button chrome |
isTagKnown(tagName) |
Optional override; default is TagEntry.isDiscovered === true |
ignoredPinReview |
Optional { isEnabled() } — reopen when any blocking tag-type pin in the active filename/subfolder patterns is fully filename-ignored (post had tags for that type; join ends empty; at least one ignored tag). Lists only those blocking types. When omitted, uses OPTION_REVIEW_IGNORED_FILENAME_PINS. |
skipEmptyFilenamePins |
Optional { isEnabled() } — queue-only drop before promote when active patterns have tag-type pins and the post has no raw tags for any of them (ignore list not applied; no-op with no tag pins). When omitted, uses OPTION_SKIP_EMPTY_FILENAME_PINS. |
When discovery is on, undiscovered tags (isDiscovered null) set resolutionBlocked and open #bv-tag-discovery-panel on the focused tab (tagDiscoveryPanelTabId). With default linkQueues: false, that pauses resolution only (downloads continue). With linkQueues: true, both pipelines pause. Confirm marks tags discovered and promotes types when unset (any tab that owns the panel); attribute actions may record type without clearing discovery. Skip drops the item without marking discovered; Open media opens the source URL; deselect/dequeueDownload of the blocked item also clears the gate (like Skip). Opening review (_presentTagDiscoveryReview) and Confirm / Skip wake other tabs via _signalProcessorsWake() so a focused follower can show the panel when the processor leader is in the background, and an idle leader can resume after Confirm / Skip on a follower. Hiding or unloading a tab releases panel ownership (not the queue gate) so another search tab can restore the panel; Confirm / Skip / deselect of the blocked item clear resolutionBlocked. State fields discoveryPanelTags, discoveryPanelKnownTags, and discoveryReviewMode (unknown | ignoredPins) persist in downloadManagerState. After New/Known render, the manager prefetches a Similar tags list (up to 10 same-type registry matches; bounded during scan; in-flight work cancelled on hide while a completed cache is kept for restore; reused across debounced attribute refreshes without collapsing the accordion). Clicking a tag name opens actions.bookmark.buildUrl in a new tab. When ignoredPinReview is enabled, the queue and immediate paths open the same panel if any blocking pin type would be empty after ignore — after unknown Confirm, or instead of promoting when there are no unknowns. Panel content is only ignored tags for those empty pin types (not healthy siblings); Confirm continues only when no blocking pin remains. Ignored-pin mode starts Similar expanded with no count in the heading (similarOpen / similarShowCount via View Layer). When skipEmptyFilenamePins is enabled, the queue path drops the resolution item (no download promote) after those gates if no pinned tag type has raw tags on the post. Immediate/auto download is not gated by empty-pin skip.
rateLimitHandlersKeyed by context (resolution, download, …). Each context may define:
| Handler | Role |
|---|---|
humanInteraction |
{ detect(ctx), openUrl?(ctx), confirmTitle?, confirmMessage?, confirmLabel?, reopenLabel?, softExpireMs? } — sets that context's humanInteraction[context] lane entry (including openUrl), shows the per-lane panel on a visible tab (Open Cloudflare Challenge then Done — resume). Background processor tabs leave promptTabId unset and wake peers so the focused tab can steal. Does not auto-open the challenge tab; Open reads the lane openUrl from the sync cache and opens in the click stack (brazen_hi=1 + brazen_hi_ctx query/hash, rel=opener). Optional softExpireMs (default 120000) — after that age the lane is cleared (next resolve re-blocks if CF remains). Verification tabs are marker-only. Runs before timedReload. |
timedReload |
{ detect(doc, ctx), reloadDelayMs? } — document detect only (not bare HTTP 429) |
reopenLabel defaults to Open Cloudflare Challenge on the human-interaction panel. Each lane stores its own openUrl for the Open action.
resolutionInitiationGapMs and downloadInitiationGapMs pace resolution fetches and GM_download respectively (defaults 2000 each; configure independently per app). Both use separate in-tab clocks hydrated from lastResolutionInitiationAt / lastDownloadInitiationAt in shared state so concurrent IDB writes cannot erase the gap. Resolution paces before every fetch attempt (including rate-limit retries). Confirming a human-interaction rate limit stamps only the blocked pipeline's clock so the next initiation waits a full gap without affecting the other queue.
Framework delegates most calls; you can also use this.getDownloadManager() for path helpers.
| Method | Role |
|---|---|
downloadImmediate({ mediaElement, removeMediaOnSuccess?, source? }) |
Immediate download; Tag Discovery when unknowns; never batch queues |
enqueueDownload(context) |
Add to resolution queue; returns whether added |
dequeueDownload(itemId) |
Remove from either queue; cancels Tag Discovery when removing the blocked review item; clears any HI lane whose itemId matches |
isQueued(itemId) |
Cross-tab pipeline membership |
toggleDownloadManagerPaused() |
Start/pause download queue only; no-op when download pending is 0 |
paceResolutionInitiation() |
Wait resolutionInitiationGapMs (serialized) before the next same-origin HTML fetch |
clearDownloadQueue() |
Wipe download queue only; clears download-context human-interaction block |
toggleCurrentMediaQueued() |
Media-page enqueue toggle |
toggleSelectionMode() |
Local session selection mode (search) |
| Method | Role |
|---|---|
confirmTagDiscoveryMappings() |
Mark tags discovered (isDiscovered); promote types when unset; promote resolution item or run pending immediate |
skipTagDiscoveryInclusion() |
Skip review — drop item and clear selection progress; do not confirm types |
openTagDiscoveryMedia() |
Open media URL for item under review |
toggleTagDiscoveryMode() |
Cross-tab discovery enable toggle |
restoreTagDiscoveryPanelIfNeeded() |
Re-open the review panel when resolutionBlocked (after dock build / on tab focus) |
| Method | Role |
|---|---|
getDownloadManagerProgress() |
{ resolution: {current, total}, download: {current, total} } — both always live (dock panel shows while either queue has pending work) |
getDownloadQueueCount() |
Dock counter; excludes resolution queue when discovery mode is on |
getPendingPipelineCount() |
Non-terminal items in both queues |
isDownloadManagerEnabled() |
Enable flag + dock active |
isDownloadPageRole(role) |
Page role gate |
isDownloadManagerLeaderTab() |
This tab owns processingTabId |
isDownloadManagerRunning() |
Pipeline active (queues, blocked state, or in-tab processing) |
requestDownloadManagerLeadership() |
Take processor leadership when idle; alert and abort while the download manager is active |
| Method | Role |
|---|---|
BrazenDownloadManager.peekHumanInteractionBlockedMirror(scriptPrefix) |
Static. Sync localStorage mirror — usable in Phase-1 page ops before initialize() |
BrazenDownloadManager.peekHumanInteractionBlockedIdb(scriptPrefix) |
Static. Async IndexedDB read — true when any HI lane is blocked (Phase-1 fallback when query/hash/mirror/opener were stripped) |
BrazenDownloadManager.hasBrazenHumanInteractionMarker() |
Static. Query or hash still has brazen_hi |
BrazenDownloadManager.isQueueOpenedVerificationTab() |
Static. Same-origin opener (queue-opened tab) |
BrazenDownloadManager.shouldSilenceMediaCloudflarePrompt(scriptPrefix, instance?) |
Static. Whether a challenge page should stay silent |
handleMediaCloudflarePage(options?) |
Phase-1 Cloudflare / challenge page: queue verification tabs show Done — resume / Done — resume — close tab; standalone shows themed Done — reload pane |
shouldSilenceMediaCloudflarePrompt() |
Instance wrapper around the static silence gate |
isHumanInteractionBlockedSync() |
Instance: true when any HI lane is blocked |
getDownloadManager())| Method | Role |
|---|---|
buildDownloadPathFromPatterns(data, tagGroups) |
Token path assembly |
buildDownloadPath(folder, name) |
Sanitized folder + filename |
sanitizePathSegment(segment) |
Illegal char strip + entity decode |
decodeHtmlEntities(text) |
Delegates to Utilities.decodeHtmlEntities |
parseDownloadTagSubstitutionLines(lines, normalizeToken) |
Substitution parser (→ / -> / -; also object rows) |
buildDownloadTagSubstitutionMap(rules) |
Map<subject, replacement> |
One leader tab (processingTabId) owns both pipelines via _runDispatcher. With default linkQueues: false (IndependentPolicy), resolution and download run as concurrent lane loops. With linkQueues: true (InterleavedPolicy), one serial dispatcher prefers resolution then download.
| Behaviour | Detail |
|---|---|
paused |
Pauses the download queue only. Resolution auto-runs on enqueue. Once started, idle (auto-pause / Start disabled / download counter reset) only when both queues are empty. |
resolutionBlocked |
Tag discovery gate. Independent: stops resolution only (downloads continue). Interleaved: gates resolution only — download may run opportunistically while the user reviews tags. |
humanInteraction |
Per-lane rate-limit gate. Independent: each lane's entry stops only that lane (both panels may show). Interleaved: any lane HI pauses the shared regime. |
| Download interruption | Before GM_download, the row sets inProgress / startedAt. On a visible full-UI steal, orphan downloading+inProgress rows are deferred (not requeued) and #bv-download-interruption-panel shows a 5s timed button (hover pauses). Click → mark done + paused; timeout → requeue + relaunch. Download lane gated while pending. |
| Leadership | Heartbeat processingHeartbeatAt (~3 s) + sync localStorage mirror. Stale steal ~12 s or immediate after pagehide mirror clear. Steal requeues in-flight resolving / downloading rows (never requeues this tab's active item; defers inProgress orphans when the page can show UI). Unload hard-interrupts local processors and closes IndexedDB; bfcache restore reopens storage and requeues orphans before renewing the lock. Dock crown (requestDownloadManagerLeadership) force-claims onto the current tab only when isDownloadManagerRunning() is false; active crown cannot resign. |
| Claims | Items move queued → resolving / downloading before work; in-tab active-item guards abort terminal puts after steal or clear. Terminal rows are pruned after each task (dock totals use completed counters). Interleaved mode can retract an in-flight job back to queued when pause / any-HI lands mid-process. |
| Ledger | Duplicate claim at download time in Download Manager when Skip or Hide is on; ledgerClaimed on download rows prevents double-claim after lock steal. |
| UI cadence | Queue/state puts do not rebuild dock; one UI reset per resolution/download task. Full _syncFromStorage on init, enqueue, Start, and when a visible tab wakes; hidden tabs only resume leadership/processors. Wake signal + receiver are debounced (~150 ms). Pending counts use indexed IDB counts (not full-queue loads). |
Scans pattern-relevant tag types only (optional tagDiscovery.tagTypes array is an allowlist filter, not a full-type override). Always registers path tags via _registerResolvedTagGroups (discovery on → seen-only; off → typeEntryId + isDiscovered). Panel lists undiscovered then discovered tags with incidence counts; framework maps tag-attribute actions internally (type may be set without discovering). Reload / tab focus restores panel when review pending. Promote / Confirm wake the download processor so Ready rows are not stranded.
Per-lane dock panels (#bv-human-interaction-panel-resolution / #bv-human-interaction-panel-download) on the focused tab when that lane is blocked (any search/media tab may steal that lane's promptTabId; hiding/unloading releases ownership only — the block stays until Done, soft-expiry TTL clear, or dequeue of the HI subject). Background leaders that hit the rate limit do not pin the panel to themselves; they wake peers, and hide/release also wakes so a focused follower can steal immediately. The challenge tab is not opened automatically — Open Cloudflare Challenge uses the lane's cached openUrl synchronously when the user clicks. Queue verification tabs are marker-only (brazen_hi / brazen_hi_ctx) and never claim processor leadership. A restore/watchdog path reclaims stale prompt ownership, clears lanes older than softExpireMs / default 2 minutes, and hides panels when a lane is cleared remotely. Done — resume clears that lane and wakes the leader. On the Phase-1 queue challenge media page itself, a secondary Done — resume — close tab performs the same clear/wake then window.close() (resume still succeeds if the browser blocks close). Cross-tab enqueue while blocked still writes queued rows; the blocked lane resumes after confirm or TTL clear. Both lanes may be blocked and visible at once under IndependentPolicy.
Navigating the leader tab while GM_download is in flight leaves the browser transfer running but orphans the downloading row (inProgress was set just before GM_download). On the next visible full-UI page that steals leadership, those orphans are deferred (not immediately requeued) and #bv-download-interruption-panel shows a 5s timed button (thin end-to-end bar; hover pauses). Click marks the rows done and sets paused (stops the relaunch churn for a large ongoing file). Timeout requeues and relaunches (previous default). The download lane stays blocked while the decision is pending. Non-UI / background requeue contexts still flip orphans to queued as before.
Recommendation: for large downloads, keep a separate lightweight homepage open as the download leader tab and browse or pick media in other tabs so transfers are not interrupted by navigation.
| Topic | Detail |
|---|---|
| Grant | @grant GM_download on this module (and typically on the app too) |
| Load order | After Framework core; before app script |
| DOM | resolveItem / progress slot / tile overlays use HTMLElement; page resolution uses native fetch + DOMParser |
| Dock | Manager registers dock fields (selection, enqueue, start/pause, clear, progress, discovery). Apps pushField enable key on the rail. |
| Ledger | Constructor downloadDuplicateLedger on Framework; claims happen in Download Manager — see Framework developer guide. GM_download always uses conflictAction: 'overwrite' (never uniquify) so user pattern names are never altered. |
| View Layer | Progress slot, item progress overlays, discovery/human-interaction panels — View Layer |
| IndexedDB | downloadResolutionQueue, downloadQueue, downloadManagerState — IndexedDB Storage |
configureDownloadManager, getDownloadManagerdownloadImmediate, enqueueDownload, dequeueDownload, isQueued, toggleDownloadManagerPaused, clearDownloadQueue, toggleSelectionMode, toggleCurrentMediaQueued, paceResolutionInitiationconfirmTagDiscoveryMappings, skipTagDiscoveryInclusion, openTagDiscoveryMedia, toggleTagDiscoveryMode, restoreTagDiscoveryPanelIfNeededgetDownloadManagerProgress, getDownloadQueueCount, isDownloadPageRole, isDownloadManagerEnabled, isDownloadManagerLeaderTab, isDownloadManagerRunning, requestDownloadManagerLeadership, handleMediaCloudflarePage, shouldSilenceMediaCloudflarePrompt, peekHumanInteractionBlockedMirror, peekHumanInteractionBlockedIdb, hasBrazenHumanInteractionMarker, isQueueOpenedVerificationTab, isHumanInteractionBlockedSyncbuildDownloadPathFromPatterns, parseDownloadTagSubstitutionLines, buildDownloadTagSubstitutionMap, buildDownloadPath