gacama 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -0
- package/README.md +21 -0
- package/package.json +39 -0
- package/src/game.js +27 -0
- package/src/paths.js +15 -0
- package/src/screenshots.js +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 2Bubs2Go
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
10
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
11
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
12
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
13
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
14
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
15
|
+
PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Gacama - Bub's Game Capture Manager
|
|
2
|
+
Automatically organizes game captures by watching a specified directory for new files, saving metadata to a JSON file, and moving the files to folders based on game titles. It can retrieve the current game title from a text file, a REST API, or a provided function.
|
|
3
|
+
|
|
4
|
+
Gacama can be run without any explicit configuration, but locations can be customized environment variables:
|
|
5
|
+
|
|
6
|
+
- `GACAMA_DROPS`: Directory to watch for new capture files (default: `./drops`)
|
|
7
|
+
- `GACAMA_SCREENSHOTS`: Path to the JSON file where metadata will be stored (default: `./screenshots`)
|
|
8
|
+
- `GACAMA_GAME_SOURCE`: Path to retrieve the current game title, either a file path or URL that begins with "http" - this will be ignored if Gacama is provided an external function to use for this (default: `[drops]/game.txt`)
|
|
9
|
+
|
|
10
|
+
## Development Details
|
|
11
|
+
Basic node.js project. Heavy use of the `fs` module for file system operations, and `chokidar` for watching the drops directory. Claude Code was used for some initial scaffolding and implementation, but most code was written by hand. All code was reviewed and edited by a human.
|
|
12
|
+
|
|
13
|
+
### Testing
|
|
14
|
+
Uses native test runner. `npm test` will run unit tests, `npm run test:e2e` will run end-to-end tests.
|
|
15
|
+
|
|
16
|
+
### Development Practices
|
|
17
|
+
|
|
18
|
+
- Test-Driven Development (TDD) is followed, meaning that tests are written before implementation code.
|
|
19
|
+
- Simplicity is prioritized in implementations, with a focus on meeting requirements without unnecessary complexity.
|
|
20
|
+
- Code is reviewed and edited by a human to ensure quality and correctness, especially for critical sections of the codebase.
|
|
21
|
+
- Care is taken to ensure low-dependency on external libraries, with a preference for native Node.js modules where possible.
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gacama",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Bub's Game Capture Manager",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"files": ["src/", "gamaca.js"],
|
|
7
|
+
"main": "gamaca.js",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=22.0.0"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "node --test --experimental-test-module-mocks 'test/**/!(endtoend).test.js'",
|
|
13
|
+
"test:e2e": "node --test --experimental-test-module-mocks 'test/**/endtoend.test.js'"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+ssh://git@gitlab.com/2Bubs2Go/gacama.git"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"screenshots",
|
|
21
|
+
"gaming",
|
|
22
|
+
"game-screenshots",
|
|
23
|
+
"screenshot-manager",
|
|
24
|
+
"file-organizer",
|
|
25
|
+
"image-organizer",
|
|
26
|
+
"file-watcher",
|
|
27
|
+
"steam",
|
|
28
|
+
"game-capture"
|
|
29
|
+
],
|
|
30
|
+
"author": "2Bubs2Go",
|
|
31
|
+
"license": "ISC",
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://gitlab.com/2Bubs2Go/gacama/issues"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://gitlab.com/2Bubs2Go/gacama#readme",
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"chokidar": "^5.0.0"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/game.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import { getGameNameLocation } from './paths.js';
|
|
3
|
+
import { getNameObject } from '../gacama.js';
|
|
4
|
+
|
|
5
|
+
export async function getGameName() {
|
|
6
|
+
const location = getGameNameLocation();
|
|
7
|
+
|
|
8
|
+
let game;
|
|
9
|
+
if(location.startsWith('http')) {
|
|
10
|
+
try {
|
|
11
|
+
const response = await fetch(location);
|
|
12
|
+
if(response.ok) {
|
|
13
|
+
game = await response.text();
|
|
14
|
+
} else {
|
|
15
|
+
throw new Error(`Failed to fetch game name from ${location}. Status returned: ${response.status}`);
|
|
16
|
+
}
|
|
17
|
+
} catch (error) {
|
|
18
|
+
throw new Error(`Failed to connect to ${location}: ${error.message}`);
|
|
19
|
+
}
|
|
20
|
+
} else {
|
|
21
|
+
game = await readFile(location, 'utf-8');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if(!game) return;
|
|
25
|
+
|
|
26
|
+
return getNameObject(game);
|
|
27
|
+
}
|
package/src/paths.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
|
|
3
|
+
export const getScreenshotsDirectory = () =>
|
|
4
|
+
resolve(process.env.GACAMA_SCREENSHOTS || 'screenshots');
|
|
5
|
+
|
|
6
|
+
export const getDropsDirectory = () =>
|
|
7
|
+
resolve(process.env.GACAMA_DROPS || 'drops');
|
|
8
|
+
|
|
9
|
+
export const getGameNameLocation = () => {
|
|
10
|
+
const location = process.env.GACAMA_GAME_SOURCE || 'game.txt';
|
|
11
|
+
|
|
12
|
+
if(location.startsWith('http')) return location;
|
|
13
|
+
|
|
14
|
+
return resolve(getDropsDirectory(), location);
|
|
15
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile, rename } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { getGameName } from "./game.js";
|
|
4
|
+
import { getScreenshotsDirectory } from "./paths.js";
|
|
5
|
+
|
|
6
|
+
export class ScreenshotManager {
|
|
7
|
+
constructor(gameNameFn = getGameName) {
|
|
8
|
+
this.screenshots = {};
|
|
9
|
+
this.shotsDir = getScreenshotsDirectory();
|
|
10
|
+
this.jsonPath = resolve('./', this.shotsDir, 'screenshots.json');
|
|
11
|
+
this.getGameName = gameNameFn;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async initialize() {
|
|
15
|
+
try {
|
|
16
|
+
this.screenshots = await readFile(this.jsonPath, 'utf-8');
|
|
17
|
+
} catch {
|
|
18
|
+
console.log(`Screenshots JSON not found, creating file "${this.jsonPath}".`);
|
|
19
|
+
await this.save();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if(this.screenshots.length) {
|
|
23
|
+
this.screenshots = JSON.parse(this.screenshots);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
this.gameCache = await this.getGameName();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async save() {
|
|
30
|
+
await writeFile(this.jsonPath, JSON.stringify(this.screenshots));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getLastId() {
|
|
34
|
+
if(!this.screenshots[this.gameCache.name]) return 0;
|
|
35
|
+
|
|
36
|
+
return this.screenshots[this.gameCache.name]
|
|
37
|
+
.reduce((max, shot) => shot.id > max ? shot.id : max, 0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async addToJSON(id, path) {
|
|
41
|
+
if(!this.screenshots[this.gameCache.name]) {
|
|
42
|
+
this.screenshots[this.gameCache.name] = [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.screenshots[this.gameCache.name].push({
|
|
46
|
+
id, path, taken: new Date()
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
await this.save();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async add(file) {
|
|
53
|
+
this.gameCache = await this.getGameName();
|
|
54
|
+
|
|
55
|
+
const id = this.getLastId() + 1,
|
|
56
|
+
ext = file.split('.').pop(),
|
|
57
|
+
newPath = resolve(this.shotsDir, this.gameCache.folder, `${this.gameCache.file}.${id}.${ext}`);
|
|
58
|
+
|
|
59
|
+
await mkdir(resolve(this.shotsDir, this.gameCache.folder), { recursive: true });
|
|
60
|
+
await rename(file, newPath);
|
|
61
|
+
await this.addToJSON(id, newPath);
|
|
62
|
+
}
|
|
63
|
+
}
|