rule34 support, smarter downloads, file size display

You're welcome. Now behave.

CHANGES:
- Added rule34.xxx as a source — yes it's unlocked, don't get too excited
- rule34 auth via RULE34_API_KEY + RULE34_USER_ID in .env, because you don't get in without permission
- Fixed pagination for Gelbooru-style APIs (pid, 0-indexed) — the old page param was just embarrassing
- Default download limit capped at 100 per request — you don't get unlimited, you get what you're given
- Downloader now scans 10x the limit first, skips what's already owned, then fetches only fresh ones — efficient, like you should be
- Progress bar now shows scan status: "skipped N | fetching X–Y of Z" — full transparency, no excuses
- File size shown top-right of the image in small text — size matters and now you can see it
This commit is contained in:
Johannes
2026-05-06 00:56:02 +02:00
parent 1fad0b736e
commit 45af8a2ace
4 changed files with 79 additions and 22 deletions

View File

@@ -46,7 +46,8 @@
.tag-row .tag-plus{flex-shrink:0;color:#555;font-size:.9rem;width:.9rem;text-align:center;padding:.15rem .1rem}
.tag-row .tag-plus:hover{color:#8b8;background:none}
.tag-count{font-size:.7rem;color:#555;flex-shrink:0}
#image-area{flex:1;min-width:0;text-align:center}
#image-area{flex:1;min-width:0;text-align:center;position:relative}
#file-size{position:absolute;top:0;right:0;font-size:.72rem;color:#555;padding:.2rem .4rem;pointer-events:none}
#image-area img{max-width:90%;max-height:80vh}
#progress-box{margin-top:1vh;display:none}
#progress-bar-wrap{width:24rem;height:1rem;background:#333;border-radius:.5rem;display:inline-block;overflow:hidden;vertical-align:middle}
@@ -64,6 +65,7 @@
<option value="e621">e621</option>
<option value="konachan">konachan</option>
<option value="yandere">yandere</option>
<option value="rule34">rule34.xxx</option>
</select>
<button type="button" id="get-btn">Get Images</button>
</form>
@@ -80,6 +82,7 @@
<div id="view">
<div id="tag-sidebar"></div>
<div id="image-area">
<div id="file-size"></div>
<div id="fs-wrap">
<img id="slide" src="{{ images[0] }}" />
<div id="tap-prev"></div>
@@ -126,16 +129,20 @@
const label = document.getElementById('progress-label');
box.style.display = 'block';
const es = new EventSource(`download/progress/${job_id}`);
let statusText = '';
es.onmessage = (e) => {
const d = JSON.parse(e.data);
if (d.error) { label.textContent = 'Error: ' + d.error; es.close(); return; }
if (d.status) statusText = d.status;
const pct = d.total > 0 ? Math.round(d.done / d.total * 100) : 0;
bar.style.width = pct + '%';
if (d.finished) {
label.innerHTML = `Done! ${d.done} images. <a href="?tags={{ tag_query }}" style="color:#8b8">Refresh</a>`;
es.close();
} else if (d.total > 0) {
label.textContent = (statusText ? statusText + ' — ' : '') + `${d.done} / ${d.total}`;
} else {
label.textContent = d.total > 0 ? `${d.done} / ${d.total}` : 'Fetching post list...';
label.textContent = statusText || 'Scanning...';
}
};
}
@@ -143,9 +150,11 @@
{% if images %}
<script>
const images = {{ images|tojson }};
const post_urls = {{ post_urls|tojson }};
const tags_list = {{ tags_list|tojson }};
const images = {{ images|tojson }};
const post_urls = {{ post_urls|tojson }};
const tags_list = {{ tags_list|tojson }};
const file_sizes = {{ file_sizes|tojson }};
function fmt_size(b){ return b >= 1048576 ? (b/1048576).toFixed(1)+' MB' : Math.round(b/1024)+' KB'; }
const sidebar = document.getElementById('tag-sidebar');
const current_query = {{ tag_query|tojson }};
@@ -171,11 +180,12 @@
`</div>`;
}).join('');
}
const img = document.getElementById('slide');
const fs_wrap = document.getElementById('fs-wrap');
const btnT = document.getElementById('toggle');
const btnF = document.getElementById('fullscreen');
const counter = document.getElementById('counter');
const img = document.getElementById('slide');
const fs_wrap = document.getElementById('fs-wrap');
const btnT = document.getElementById('toggle');
const btnF = document.getElementById('fullscreen');
const counter = document.getElementById('counter');
const file_size_el = document.getElementById('file-size');
const _params = new URLSearchParams(window.location.search);
const _url_idx = parseInt(_params.get('idx') || '0', 10);
@@ -257,6 +267,7 @@
i = n;
img.src = images[n];
counter.textContent = `${i+1} / ${images.length}`;
file_size_el.textContent = fmt_size(file_sizes[n]);
render_tags(n);
reset_zoom();
preload_next();
@@ -268,6 +279,7 @@
// init to url index
img.src = images[i];
counter.textContent = `${i+1} / ${images.length}`;
file_size_el.textContent = fmt_size(file_sizes[i]);
render_tags(i);
preload_next();