Cross-tab download queue, resolution pipeline, and immediate downloads for Brazen user scripts
Hindi dapat direktang i-install ang script na ito. Ito ay isang library para sa iba pang mga script na isasama sa meta directive. // @require https://update.greasyfork.org/scripts/587126/1909491/Brazen%20Framework%20-%20Download%20Manager.js
Cross-tab batch downloads, page resolution, optional tag discovery review, and human-interaction rate-limit handling. Apps extend BrazenFramework, call configureDownloadManager() after configureDock(), and @require this module after BrazenReactor and Framework core. Selection tiles and progress panels are native HTMLElement.
Greasy Fork: Download Manager · Requires: Reactor (GF TBD), Framework core, IndexedDB Storage (v1.1.0+ download queue stores) · Grant on this module: GM_download
Normative Reactor write/read paths: reactor-native-v2.spec.md.
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.dm.state.* patch stream for live UI.Load order (after Framework core):
// @require … Brazen Framework - Reactor.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'],
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. Framework then calls afterDockReady() and, after ruleset hydrate, afterBootHydrate().
| 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, interleaved lane dispatcher: one serial regime, any-lane HI pauses both lanes, download may run while tag discovery gates resolution. Default false (independent lanes): concurrent resolution/download loops; tag discovery pauses resolution only; each lane's human-interaction block stops only that lane. 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-clear-download-queue only — coordinator crown is on the main dock rail), 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 |
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_DEFER_TAG_DISCOVERY |
defer-tag-discovery-unattended |
Defer Tag Discovery Until Resolution Completes |
Mount all four via Framework createBehavioursTabPanel() (alphabetical title order). Mount download-path fields via createDownloadsTabPanel() (excludes selection-mode default).
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 |
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 |
tagDiscoveryToggle |
Dock tag-discovery mode button |
dashboard |
Dedicated control / future-analytics host. Does not grant selection, enqueue, or tag-discovery toggle by itself. Does not auto-claim coordinator role. 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. ['mediaPost'] |
tagTypes |
Panel grouping: { label, rowClass, color } per type — colors via View Layer applyTagDiscoveryTypeColors. Auto-synced from filename/subfolder pattern pins on init and config change. |
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 (typed-but-undiscovered tags remain unknown). Bulk reset: Framework resetAllTagsDiscovered() — see tagging.spec.md |
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 a visible tab that steals panel ownership. 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; 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). Cross-tab sync uses Reactor Commands and dm.state.* patches — followers restore panels when gate fields change without visiting the coordinator tab. Hiding or unloading a tab releases panel ownership (not the queue gate) so another search tab can restore the panel; local DOM may stay mounted on blur (no flash on return) — restore re-steals ownership and force-shows the slide panel when review is still pending.
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 coordinator tabs leave promptTabId unset and wake peers via pipeline-pump so the focused tab can steal. Does not auto-open the challenge tab; Open reads the lane openUrl from readDmState() 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 on lane loops and HI watchdog (including hidden coordinators; 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 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 |
|---|---|
enqueueDownload(context) |
Add to resolution queue via Reactor Command; 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) |
selectAllVisibleItems() |
Enqueue all visible search tiles not already in the pipeline (Selection Mode slide-out) |
sendCommand(command) |
Low-level Reactor Command (coordinator kernel dispatch; follower bus + optimistic patches) |
| Method | Role |
|---|---|
isCoordinator() |
This tab owns the Web Locks script coordinator role |
requestCoordinatorRole({ steal?, ifAvailable? }) |
Steal or opportunistically claim coordinator; no idle-only alert. Same-tab reload: sessionStorage coordinator-sticky preserves prior identity |
readDmState() |
Sync read via dm.state.snapshot signal atom |
Also available on Framework: isCoordinator(), requestCoordinatorRole().
| Method | Role |
|---|---|
confirmTagDiscoveryMappings() |
Mark tags discovered (isDiscovered); promote types when unset; promote resolution item |
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 |
isDownloadManagerRunning() |
Pipeline active (queues, blocked state, or in-tab processing) |
| 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> |
patternIncludesChip(pattern, chip) |
True when pattern references a download-path chip |
registerSelectionDragGuard() |
Capture-phase drag guard while selection mode is active |
One coordinator tab (Web Lock via Reactor) owns pipeline work. Resolution and download run through the lane dispatcher (_runLaneProcessor ×2 when linkQueues: false, _runInterleavedDispatcher when linkQueues: true). Public enqueue/dequeue/toggle/clear build Reactor Commands → kernelWriteThrough. Coordinator-only dm.state gate mutations use _commitCoordinatorDmState; followers apply dm.state.* patches and may send claim Commands for panel ownership.
| 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 lanes: stops resolution only (downloads continue). Interleaved: gates resolution; 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 coordinator 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. |
| Coordinator role | Web Locks script coordinator (isCoordinator() / requestCoordinatorRole()). Crown on main dock rail (DOCK_COORDINATOR). Followers wake coordinator via pipeline-pump Commands. Same-tab reload reclaims via sessionStorage sticky + one-shot steal after kernel.start(). |
| Cross-tab UI | Followers subscribe to Reactor bus; _requestFollowerSnapshot() opens the patch stream on cold start. Dock progress reads signal atoms; coalesced timer paints (16ms) run on background tabs. |
| 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). |
| Ledger | Duplicate claim at download time in Download Manager when Skip or Hide is on; ledgerClaimed on download rows prevents double-claim after coordinator handoff. |
| UI cadence | One coalesced dock paint per tick; _syncFromStorage on init, enqueue, Start, and follower patch batches. Pending counts use signal atoms on the paint hot path. |
Recommendation: for large downloads, keep a dedicated tab as the coordinator 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 and Reactor; 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. Coordinator crown is on the main rail, not the Start/Pause slide-out. |
| 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, getDownloadManagerenqueueDownload, dequeueDownload, isQueued, toggleDownloadManagerPaused, clearDownloadQueue, toggleSelectionMode, selectAllVisibleItems, toggleCurrentMediaQueued, paceResolutionInitiation, sendCommandisCoordinator, requestCoordinatorRole, readDmStateconfirmTagDiscoveryMappings, skipTagDiscoveryInclusion, openTagDiscoveryMedia, toggleTagDiscoveryMode, restoreTagDiscoveryPanelIfNeededgetDownloadManagerProgress, getDownloadQueueCount, isDownloadPageRole, isDownloadManagerEnabled, isDownloadManagerRunning, handleMediaCloudflarePage, shouldSilenceMediaCloudflarePrompt, peekHumanInteractionBlockedMirror, peekHumanInteractionBlockedIdb, hasBrazenHumanInteractionMarker, isQueueOpenedVerificationTab, isHumanInteractionBlockedSyncbuildDownloadPathFromPatterns, parseDownloadTagSubstitutionLines, buildDownloadTagSubstitutionMap, buildDownloadPath, patternIncludesChip, registerSelectionDragGuard