Skip to content

Commit 1be33fd

Browse files
committed
v0.3.0
1 parent b43a853 commit 1be33fd

9 files changed

Lines changed: 194 additions & 82 deletions

File tree

.github/workflows/release.yml

Lines changed: 37 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -154,65 +154,64 @@ jobs:
154154
- name: List artifacts (some may be missing if a job failed)
155155
run: find ./artifacts -type f -maxdepth 3 -print || true
156156

157-
- name: Extract release notes from CHANGELOG.md (robust heading match)
158-
id: gen_notes
157+
- name: Setup Node (for extractor)
158+
uses: actions/setup-node@v4
159+
with:
160+
node-version: 20
161+
162+
- name: Extract release notes from CHANGELOG.md
163+
id: extract
159164
shell: bash
160165
run: |
161166
set -euo pipefail
162-
TAG="${GITHUB_REF_NAME}" # e.g., v0.2.1
163-
VER="${TAG#v}" # 0.2.1
164-
FILES=("CHANGELOG.md" "docs/CHANGELOG.md")
165-
OUT="RELEASE_NOTES.md"
166-
found=false
167-
168-
for f in "${FILES[@]}"; do
169-
if [[ -f "$f" ]]; then
170-
# Match any '##' heading that contains the version in common forms:
171-
# "## v0.2.1", "## [v0.2.1]", "## 0.2.1", "## [0.2.1]" (date text allowed after).
172-
start_line=$(grep -nE "^##[[:space:]].*[\[\(]?v?${VER}[\]\)]?([[:space:]]|$|[[:punct:]].*)" -m1 "$f" | cut -d: -f1 || true)
173-
if [[ -n "${start_line:-}" ]]; then
174-
tail_start=$((start_line + 1))
175-
next_rel=$(tail -n +"$tail_start" "$f" | grep -nE "^##[[:space:]]" -m1 | cut -d: -f1 || true)
176-
if [[ -n "${next_rel:-}" ]]; then
177-
end_line=$((tail_start + next_rel - 2))
178-
else
179-
end_line=$(wc -l < "$f")
180-
fi
181-
sed -n "${tail_start},${end_line}p" "$f" > "$OUT"
182-
if [[ -s "$OUT" ]]; then
183-
echo "has_notes=true" >> "$GITHUB_OUTPUT"
184-
echo "notes_path=$OUT" >> "$GITHUB_OUTPUT"
185-
found=true
186-
break
187-
fi
188-
fi
189-
fi
190-
done
167+
TAG="${GITHUB_REF_NAME}"
168+
# Try root CHANGELOG.md; if not found or no section, try docs/CHANGELOG.md
169+
node ./scripts/extract-changelog.mjs "$TAG" "./CHANGELOG.md" "./CHANGELOG_RELEASE.md" || true
170+
if [ ! -s "./CHANGELOG_RELEASE.md" ] && [ -f "./docs/CHANGELOG.md" ]; then
171+
node ./scripts/extract-changelog.mjs "$TAG" "./docs/CHANGELOG.md" "./CHANGELOG_RELEASE.md" || true
172+
fi
191173
192-
if [[ "$found" != "true" ]]; then
193-
echo "has_notes=false" >> "$GITHUB_OUTPUT"
174+
if [ -s "./CHANGELOG_RELEASE.md" ]; then
175+
echo "found=true" >> "$GITHUB_OUTPUT"
176+
echo "notes_path=CHANGELOG_RELEASE.md" >> "$GITHUB_OUTPUT"
177+
else
178+
echo "found=false" >> "$GITHUB_OUTPUT"
194179
fi
195180
196-
- name: Create GitHub Release (with CHANGELOG body)
197-
if: steps.gen_notes.outputs.has_notes == 'true'
181+
- name: Upload extracted notes (debug)
182+
if: always()
183+
uses: actions/upload-artifact@v4
184+
with:
185+
name: changelog-extract-${{ github.ref_name }}
186+
path: CHANGELOG_RELEASE.md
187+
if-no-files-found: warn
188+
retention-days: 7
189+
190+
- name: Create/Update GitHub Release (CHANGELOG body)
191+
if: steps.extract.outputs.found == 'true'
198192
uses: softprops/action-gh-release@v2
199193
with:
200194
tag_name: ${{ github.ref_name }}
201195
name: ${{ github.ref_name }}
202-
body_path: ${{ steps.gen_notes.outputs.notes_path }}
196+
body_path: ${{ steps.extract.outputs.notes_path }}
197+
generate_release_notes: false
198+
make_latest: true
199+
allow_updates: true
203200
fail_on_unmatched_files: false
204201
files: |
205202
artifacts/linux/**
206203
artifacts/windows/**
207204
artifacts/flatpak/**
208205
209-
- name: Create GitHub Release (auto-generated notes)
210-
if: steps.gen_notes.outputs.has_notes != 'true'
206+
- name: Create/Update GitHub Release (auto-generated notes)
207+
if: steps.extract.outputs.found != 'true'
211208
uses: softprops/action-gh-release@v2
212209
with:
213210
tag_name: ${{ github.ref_name }}
214211
name: ${{ github.ref_name }}
215212
generate_release_notes: true
213+
make_latest: true
214+
allow_updates: true
216215
fail_on_unmatched_files: false
217216
files: |
218217
artifacts/linux/**

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
All notable changes to this project will be documented in this file.
44
The format roughly follows Keep a Changelog, and dates are in YYYY-MM-DD.
55

6-
## [v0.2.2] — 2025-10-18
6+
## [v0.3.0] — 2025-10-18
77

88
### Added
99
- New engines
@@ -59,7 +59,7 @@ The format roughly follows Keep a Changelog, and dates are in YYYY-MM-DD.
5959
- Lightbox video playback may not work on some Linux builds lacking proprietary codecs (H.264/AAC). Use “Open Media” or replace Electron’s `libffmpeg.so` with the distro’s `chromium-codecs-ffmpeg-extra` variant.
6060
- Danbooru video thumbnails may look softer (site only serves small static previews for videos).
6161

62-
[v0.2.2]: https://github.com/Amateur-God/StreamBooru/releases/tag/v0.2.2
62+
[v0.3.0]: https://github.com/Amateur-God/StreamBooru/releases/tag/v0.3.0
6363

6464
## [v0.2.1] — 2025-10-18
6565

README.md

Lines changed: 59 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,42 @@
11
# StreamBooru
22

3-
StreamBooru is a fast desktop viewer for multiple booru engines (Danbooru, Moebooru/Yande.re/Konachan, Gelbooru, Zerochan). It merges and navigates posts across sites, supports round‑robin cross‑site search, has a lightbox with keyboard navigation, and lets you save favorites locally or via site APIs.
3+
StreamBooru is a fast desktop viewer for multiple booru engines (Danbooru, Moebooru/Yande.re/Konachan, Gelbooru family, e621/e926, Derpibooru). It merges and navigates posts across sites, supports round‑robin cross‑site search, has a lightbox with keyboard navigation, a bulk “Download All”, filename templates, and local favorites. Zerochan support has been removed.
44

55
## Highlights
66

7-
- Multi‑site: Danbooru, Yande.re/Konachan (Moebooru), Gelbooru, Zerochan
8-
- Views:
9-
- New: globally merged by recency (ties: favorites, score)
7+
- Engines
8+
- Danbooru
9+
- Moebooru family: Yande.re / Konachan(.com/.net) / Hypnohub / TBIB
10+
- Gelbooru family: Gelbooru.com, Safebooru.org, Rule34 (rule34.xxx), Realbooru, Xbooru
11+
- e621/e926
12+
- Derpibooru
13+
- Views
14+
- New: globally merged by recency (ties: favorites, score) with per‑site round‑robin append
1015
- Popular: globally merged using a normalized popularity model (per‑site P95)
1116
- Search: round‑robin interleaving by your site order (1,2,3,1,2,3…)
1217
- Favorites: local favorites view with search filter
13-
- Lightbox viewer: Next/Prev, Open Post/Image, Download, Favorite (local/remote), keyboard: Esc/←/→
18+
- Bulk download
19+
- “Download All” saves everything currently loaded in the active view (New/Popular/Search/Favorites)
20+
- Right‑click or Shift‑click “Download All” for options (filename templates)
21+
- Single folder chooser, optional per‑site subfolders, concurrency‑limited downloads
22+
- Correct Referer headers for common image CDNs (Danbooru, Moebooru, e621/e926, Derpibooru)
23+
- Naming templates
24+
- Presets: `site-id`, `site-score-artist-copyright-character-id`, `site-id-original`, `site-id-rating`, `site-id-widthxheight`
25+
- Custom templates with tokens:
26+
- Basics: `{site} {site_type} {id} {score} {favorites} {rating} {width} {height} {index} {ext} {original_name}`
27+
- Date: `{created} {created_yyyy} {created_mm} {created_dd} {created_hhmm}`
28+
- Tags (best‑effort): `{artist} {copyright} {character}`
29+
- Lightbox viewer
30+
- Next/Prev, Open Post/Media, Download, Favorite (local), keyboard: Esc / ← / →
1431
- Fixed topbar (tabs + search + manage)
15-
- Smart CDN handling for Danbooru (Referer headers + proxy fallback)
16-
- Site Manager:
32+
- Site Manager
33+
- Presets for common sites
1734
- Per‑site rating and default tags (rating:* is managed by the dropdown)
18-
- Auth support: Danbooru (login + API key), Moebooru (login + password_hash)
19-
- Test shows API reachability, Auth status (with account name/level/id), and Danbooru rate limit
35+
- Auth support: Danbooru (login + API key), Moebooru (login + password_hash), Gelbooru (user_id + api_key), e621 (optional)
36+
- Test shows API reachability, Auth status (with account/name/level if available), and Danbooru rate limit
37+
- Quick links: “Open Account Page” and “API Help”
38+
- Smart CDN handling
39+
- Automatic Referer headers for cdn.donmai.us, files.yande.re, konachan.com/.net, static1.e621.net/e926.net, derpicdn.net, etc.
2040

2141
---
2242

@@ -102,32 +122,50 @@ If you run Wayland only and see issues:
102122

103123
## Usage
104124

105-
- Launch the app
106125
- Manage Sites → add/edit sites, set ratings/tags, and add credentials:
107126
- Danbooru: login + API key (Profile → API)
108127
- Yande.re/Konachan: login + password_hash (shown on your account page)
128+
- Gelbooru.com: user_id + api_key required for API; Safebooru.org works without auth
129+
- e621/e926: auth optional for browsing
109130
- Tabs:
110131
- New / Popular render merged feeds
111132
- Search: enter tags (space‑separated), hit Search; results interleave by site order
112133
- Favorites: shows local saved posts; the search box filters favorites
134+
- Download All:
135+
- Left‑click to download everything currently loaded in the active view
136+
- Shift‑click or Right‑click for options (naming template)
137+
- Files are saved to a single folder; optionally sub‑foldered by site
113138
- Lightbox:
114139
- Click image to open
115140
- ←/→ to navigate; Esc to close
116-
- Buttons: View Post, Open Image, Download, ♥ Save (local) / Favorite (remote)
117-
- Right top: Manage Sites (sticky while scrolling)
141+
- Buttons: View Post, Open Media, Download, ♥ Save (local)
142+
143+
---
144+
145+
## Notes per engine
146+
147+
- Danbooru
148+
- Card thumbnails prefer the larger sample image to avoid blur; videos still show small static previews (site limitation).
149+
- Test shows API status, Auth, and rate‑limit (remaining/limit and reset time).
150+
- Gelbooru family
151+
- gelbooru.com typically requires `user_id` + `api_key` for JSON/XML API access. Many clones (e.g., Safebooru.org) work without auth.
152+
- Derpibooru
153+
- Defaults to `q=score.gte:0` when the query is empty. Optional `filter_id` is supported if you want to bypass your default site filter.
154+
- e621/e926
155+
- Browsing works without auth; account features (favorites, etc.) are not implemented in this app.
118156

119157
---
120158

121159
## Troubleshooting
122160

123-
- Danbooru images are blank or 403:
124-
- The app injects proper Referer headers and falls back to a proxy fetch if blocked.
125-
- Search shows only Danbooru:
126-
- We use engine‑native search; some sites may return 0 for plain tags. We auto‑retry with ranking tags (e.g., `order:score`) for Moebooru/Gelbooru.
127-
- Manage Sites “rating:safe” appears in tags:
128-
- The UI strips any `rating:*` tokens from the tags field; set rating only via dropdown.
129-
- Authentication:
130-
- Manage Sites → Test shows API, Auth (with account name/level/id), and Danbooru rate‑limit status.
161+
- Danbooru images look blurry in cards
162+
- Fixed: cards now prefer `sample_url` (the larger preview, e.g., large_file_url on Danbooru) before `file_url`, with `preview_url` as a last resort. Videos still use small static previews.
163+
- Gelbooru returns 401 or “No results”
164+
- Add `user_id` and `api_key` in Manage Sites (Gelbooru.com), or use https://safebooru.org
165+
- Search shows only some sites
166+
- We use engine‑native search. For Moebooru/Gelbooru variants, the app retries with ranking tags (e.g., `order:score`) if plain tags return 0.
167+
- “rating:safe” appears in tags
168+
- The UI strips any `rating:*` tokens from the tags field; set rating via the dropdown instead.
131169

132170
---
133171

@@ -137,7 +175,7 @@ Requirements: Node.js 20+, npm
137175

138176
```bash
139177
npm ci
140-
# run in development (adjust to your start script)
178+
# run in development (adjust to your scripts)
141179
npm run start
142180
# build packages (uses electron-builder.yml)
143181
npx electron-builder --linux deb tar.gz

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "streambooru",
3-
"version": "0.2.2",
3+
"version": "0.3.0",
44
"description": "StreamBooru — Electron app to browse multiple booru sites with New and Popular feeds, merged across sources.",
55
"main": "electron/main.js",
66
"author": {

renderer/components/postCard.js

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
(function () {
2-
function openExternal(url) {
1+
(() => {
2+
const openExternal = (url) => {
33
if (url) window.api.openExternal(url);
4-
}
4+
};
5+
6+
// Prefer larger sample/large image for sharp cards; preview last.
7+
const pickThumb = (post) => post.sample_url || post.file_url || post.preview_url || '';
58

6-
function buildActions(post, idx) {
9+
const buildActions = (post, idx) => {
710
const wrap = document.createElement('div');
811
wrap.className = 'actions';
912

@@ -43,9 +46,9 @@
4346
wrap.appendChild(btnFav);
4447
wrap.appendChild(btnDownload);
4548
return wrap;
46-
}
49+
};
4750

48-
window.PostCard = function (post, index = 0) {
51+
window.PostCard = (post, index = 0) => {
4952
const card = document.createElement('div');
5053
card.className = 'card';
5154
card.dataset.key = `${post?.site?.baseUrl || ''}#${post?.id}`;
@@ -55,8 +58,17 @@
5558

5659
const img = document.createElement('img');
5760
img.loading = 'lazy';
61+
img.decoding = 'async';
5862
img.alt = String(post?.id ?? '');
59-
img.src = post.preview_url || post.sample_url || post.file_url || '';
63+
const thumbUrl = pickThumb(post);
64+
img.src = thumbUrl;
65+
66+
// Provide a simple srcset so the browser can pick a sharper file when available
67+
const candidates = [];
68+
if (post.sample_url) candidates.push(`${post.sample_url} 1x`);
69+
if (post.file_url && post.file_url !== post.sample_url) candidates.push(`${post.file_url} 2x`);
70+
if (candidates.length) img.srcset = candidates.join(', ');
71+
6072
img.addEventListener('click', () => {
6173
if (window.openLightbox) window.openLightbox(post);
6274
});
@@ -65,13 +77,18 @@
6577
const meta = document.createElement('div');
6678
meta.className = 'meta';
6779
const left = document.createElement('div');
68-
left.textContent = `♡ ${isFinite(post.favorites) ? post.favorites : 0}${isFinite(post.score) ? post.score : 0}`;
80+
const favs = Number.isFinite(post.favorites) ? post.favorites : 0;
81+
const score = Number.isFinite(post.score) ? post.score : 0;
82+
left.textContent = `♡ ${favs}${score}`;
6983
const right = document.createElement('div');
7084
const siteA = document.createElement('a');
7185
siteA.href = '#';
7286
siteA.className = 'site';
7387
siteA.textContent = post?.site?.name || post?.site?.type || 'site';
74-
siteA.addEventListener('click', (e)=>{ e.preventDefault(); if (post.post_url) openExternal(post.post_url); });
88+
siteA.addEventListener('click', (e) => {
89+
e.preventDefault();
90+
if (post.post_url) openExternal(post.post_url);
91+
});
7592
right.appendChild(siteA);
7693
meta.appendChild(left);
7794
meta.appendChild(right);

renderer/components/siteManager.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
return b;
6565
};
6666

67-
function msToClock(ms) {
67+
const msToClock = function(ms) {
6868
if (!Number.isFinite(ms) || ms <= 0) return '';
6969
const s = Math.round(ms / 1000);
7070
const m = Math.floor(s / 60);
@@ -75,9 +75,9 @@
7575
return `${h}h ${mm}m`;
7676
}
7777
return `${m}m ${r}s`;
78-
}
78+
};
7979

80-
function fmtInfo(x) {
80+
const fmtInfo = function(x) {
8181
if (x == null) return '';
8282
// If adapters return a structured object, show a friendly summary
8383
if (typeof x === 'object') {
@@ -92,7 +92,7 @@
9292
try { return JSON.stringify(x); } catch { return String(x); }
9393
}
9494
return String(x);
95-
}
95+
};
9696

9797
const siteCard = function (site, idx, onChange, onDelete, onTest) {
9898
const s = {

renderer/js/bulk-download.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@
22
// A getter your view can register to supply the current tab's posts
33
let getCurrentPosts = null;
44

5-
// Call this from your view code when you load/render results:
65
// window.registerResultsProvider(() => currentPostsArray);
76
window.registerResultsProvider = function (fn) {
87
if (typeof fn === 'function') getCurrentPosts = fn;
98
};
109

1110
// Map a post object to a downloadable item
12-
function toDownloadItem(post, i) {
11+
const toDownloadItem = function(post, i) {
1312
// Prefer the original file if present, fallback to sample/preview
1413
const url = post?.file_url || post?.sample_url || post?.preview_url || '';
1514
if (!url) return null;
@@ -30,9 +29,9 @@
3029
siteName: post?.site?.name || post?.site?.baseUrl || 'unknown',
3130
fileName
3231
};
33-
}
32+
};
3433

35-
async function onDownloadAllClick() {
34+
const onDownloadAllClick = async function() {
3635
try {
3736
if (!getCurrentPosts) {
3837
alert('No results are loaded yet.');
@@ -69,7 +68,7 @@
6968
console.error('Download all error:', e);
7069
alert(`Download error: ${e?.message || e}`);
7170
}
72-
}
71+
};
7372

7473
const btn = document.getElementById('btnDownloadAll');
7574
if (btn) btn.addEventListener('click', onDownloadAllClick);

0 commit comments

Comments
 (0)