Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- Path resolution highlighting: red if invalid, green if path exists or pass the check.

### Added
- Importer and Library folder scheme now support `{atlasId}`, so installs can be matched back to AtlasDB on re-import / rebuild.
- Custom media uploads in the Game Details Media tab: add preview images from local files, a drag-and-drop zone, or an image URL, with live progress. Previews can be reordered by drag and the order persists in a new `preview_sort` table keyed by remote URL (or relative path for custom uploads), so it survives re-downloads, stream/download switches and metadata refreshes. Previews now carry a source logo and a storage-location badge, and custom previews can be deleted independently of downloaded ones.
- Add Buzzheavier host support (`buzzheavier.com`, `bzzhr.to`, `bzzhr.co`). The download route is behind a Cloudflare challenge, so the resolve runs in a browser window: it clicks the htmx download button, captures the `HX-Redirect` (or an attachment via `will-download`), and hands the resolved CDN link plus the browser's own cookies/UA back to the downloader. The resolve partition is persistent so a solved challenge survives a restart -- only Cloudflare's own challenge cookies are kept, everything else is stripped after each resolve -- and resolves run one at a time, since a shared session cannot carry two concurrently. Each time your IP changes there is a brief auto-resolve window while the challenge is re-solved.
- The version readout in the topnav and sidebar is now a button that opens that version's GitHub release page. The tag it builds matches what the release workflows publish -- `v<version>` for stable and `v<version>-nightly.<run>` for nightly -- so it lands on the real release rather than a 404. (#143)
Expand Down
1 change: 1 addition & 0 deletions electron/ipc/importer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4771,6 +4771,7 @@ ipcMain.handle("downloads-install", async (event, { id, version, onComplete, kee
currentConfig?.Library?.libraryFolderStructure,
{
f95Id: record.f95_id || record.f95Id || "",
atlasId: record.atlas_id || record.atlasId || "",
engine: record.engine || "Unknown",
creator: record.creator,
title: record.title,
Expand Down
1 change: 1 addition & 0 deletions electron/library/importRules.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ function buildStructuredImportPath(targetLibrary, format, game) {
if (key === "version") return normalizeVersionName(game.version);
if (key === "engine") return game.engine || "Unknown";
if (key === "f95id") return game.f95Id || "Unknown";
if (key === "atlasid") return game.atlasId || game.atlas_id || "Unknown";
if (key === "lcid" || key === "lewdcornerid") return game.lcId || game.lewdCornerId || "Unknown";
return "Unknown";
}),
Expand Down
3 changes: 2 additions & 1 deletion src/components/downloads/LibraryStructureModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ const previewPath = (pattern) =>
.replace(/\{title\}/g, SAMPLE.title)
.replace(/\{version\}/g, SAMPLE.version)
.replace(/\{engine\}/g, 'RenPy')
.replace(/\{f95Id\}/g, '12345'),
.replace(/\{f95Id\}/g, '12345')
.replace(/\{atlasId\}/g, '6789'),
)
.filter(Boolean)

Expand Down
1 change: 1 addition & 0 deletions src/components/importer/Importer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,7 @@ const Importer = () => {
engine: groups.engine || '',
f95Id: groups.f95id || '',
lcId: groups.lcid || '',
atlasId: groups.atlasid || '',
}
if (!cancelled) {
setLivePreview({
Expand Down
4 changes: 3 additions & 1 deletion src/components/importer/steps/SettingsStep.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export default function SettingsStep({
{ label: 'Creator / Title - Version', value: '{creator}/{title} - {version}' },
{ label: 'Title / Version, Creator', value: '{title}/{version},{creator}' },
{ label: 'F95 ID / Title / Version', value: '{f95Id}/{title}/{version}' },
{ label: 'Atlas ID / Title / Version', value: '{atlasId}/{title}/{version}' },
{ label: 'LewdCorner ID / Title / Version', value: '{lcId}/{title}/{version}' },
]
// "Auto detect" (unstructured name guessing) has been removed for now, so the
Expand Down Expand Up @@ -167,6 +168,7 @@ export default function SettingsStep({
['Engine', livePreview.fields.engine, false],
['F95 ID', livePreview.fields.f95Id, false],
['LC ID', livePreview.fields.lcId, false],
['Atlas ID', livePreview.fields.atlasId, false],
].filter(([, value, always]) => always || value).map(([label, value]) => (
<div key={label} className="min-w-0">
<div className="text-[11px] uppercase tracking-wide text-muted">{label}</div>
Expand Down Expand Up @@ -221,7 +223,7 @@ export default function SettingsStep({
onChange={(e) => setLibraryFormat(e.target.value)}
spellCheck={false}
placeholder="{creator}/{title}/{version}"
title="Destination path template. Tokens: {creator} {title} {version} {f95Id} {lcId}"
title="Destination path template. Tokens: {creator} {title} {version} {f95Id} {lcId} {atlasId}"
className="sm:ml-2 flex-1 min-w-0 bg-secondary text-text border border-border rounded-buttonTheme p-1 focus:outline-none focus:ring-1 focus:ring-accent font-mono text-xs"
/>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/settings/Library.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ const Library = () => {
<p className="text-xs opacity-60 mt-1">
Used when imports are moved or archives are extracted into the default
library folder. Options: {"{creator}"}, {"{title}"}, {"{version}"},{" "}
{"{engine}"}, {"{f95Id}"}.
{"{engine}"}, {"{f95Id}"}, {"{atlasId}"}.
<br />
Example: {"{f95Id}/{creator}/{title}/{version}"}
</p>
Expand Down
13 changes: 12 additions & 1 deletion tests/importer-helpers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,17 @@ describe('buildStructuredImportPath', () => {
const out = T.buildStructuredImportPath('/lib', '{lcid}', { lcId: 'LC9' })
expect(out).toBe(path.join('/lib', 'LC9'))
})

it('supports atlasid from camelCase or snake_case, any casing', () => {
expect(T.buildStructuredImportPath('/lib', '{atlasId}', { atlasId: '456' })).toBe(path.join('/lib', '456'))
expect(T.buildStructuredImportPath('/lib', '{atlasId}', { atlas_id: 456 })).toBe(path.join('/lib', '456'))
expect(T.buildStructuredImportPath('/lib', '{AtlasID}', { atlasId: '456' })).toBe(path.join('/lib', '456'))
})

it('falls back to Unknown for a missing atlas id, even mid-segment', () => {
const out = T.buildStructuredImportPath('/lib', '{title} [{atlasId}]/{version}', { title: 'My Game', version: '1.0' })
expect(out).toBe(path.join('/lib', 'My Game [Unknown]', '1.0'))
})
})


Expand Down Expand Up @@ -121,7 +132,7 @@ test('downloads-install passes required fields in buildStructuredImportPath', ()
if (!obj) throw new Error('buildStructuredImportPath was not given an object literal')

const keys = obj.properties.map((p) => p.key.name)
const required = ['f95Id', 'engine', 'creator', 'title', 'version']
const required = ['f95Id', 'atlasId', 'engine', 'creator', 'title', 'version']

for (const key of required) {
expect(keys).toContain(key)
Expand Down
Loading