@feralfile/cli 2.5.0 → 2.7.0
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/config.json.example +1 -1
- package/dist/package.json +3 -3
- package/dist/src/commands/enrich.js +23 -4
- package/dist/src/commands/find.js +2 -35
- package/dist/src/commands/helpers/playlist-display.js +3 -36
- package/dist/src/commands/publish.js +2 -35
- package/dist/src/commands/ssh.js +1 -34
- package/dist/src/utilities/enrich-playlist.js +88 -79
- package/dist/src/utilities/feed-fetcher.js +1 -0
- package/dist/src/utilities/functions.js +4 -36
- package/dist/src/utilities/index.js +2 -34
- package/dist/src/utilities/nft-indexer.js +219 -40
- package/dist/src/utilities/playlist-builder.js +19 -1
- package/dist/src/utilities/playlist-cast.js +21 -35
- package/dist/src/utilities/playlist-publisher.js +74 -5
- package/dist/src/utilities/playlist-send.js +2 -35
- package/dist/src/utilities/playlist-signer.js +2 -34
- package/dist/src/utilities/playlist-signing-role.js +1 -0
- package/dist/src/utilities/playlist-verifier.js +4 -37
- package/docs/CONFIGURATION.md +29 -2
- package/docs/EXAMPLES.md +94 -9
- package/docs/PROJECT_SPEC.md +38 -15
- package/docs/README.md +27 -6
- package/package.json +3 -3
package/config.json.example
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
],
|
|
16
16
|
"playlist": {
|
|
17
17
|
"privateKey": "YOUR_ED25519_PRIVATE_KEY__base64_PKCS8_DER_recommended__or_32byte_raw_seed_as_hex_or_base64__run_ff-cli_setup_to_generate",
|
|
18
|
-
"role": "
|
|
18
|
+
"role": "curator",
|
|
19
19
|
"curatorName": "YOUR_CURATOR_NAME"
|
|
20
20
|
},
|
|
21
21
|
"ff1Relayer": {
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@feralfile/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"description": "CLI for building, signing, and playing DP-1 playlists of digital art on Feral File devices",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"@feralfile/source-resolver": "^1.0.0",
|
|
62
62
|
"@napi-rs/keyring": "1.3.0",
|
|
63
|
-
"axios": "^1.
|
|
63
|
+
"axios": "^1.20.0",
|
|
64
64
|
"bonjour-service": "^1.3.0",
|
|
65
65
|
"chalk": "^4.1.2",
|
|
66
66
|
"commander": "^11.1.0",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"dp1-js": "2.3.1",
|
|
69
69
|
"ff1": "^1.0.0",
|
|
70
70
|
"fuzzysort": "^3.1.0",
|
|
71
|
-
"viem": "^2.
|
|
71
|
+
"viem": "^2.56.3"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
74
|
"@types/node": "^24.7.1",
|
|
@@ -12,7 +12,7 @@ const path_1 = require("path");
|
|
|
12
12
|
const enrich_playlist_1 = require("../utilities/enrich-playlist");
|
|
13
13
|
const playlist_verifier_1 = require("../utilities/playlist-verifier");
|
|
14
14
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
15
|
-
const {
|
|
15
|
+
const { resolveTokenBatch } = require('../utilities/nft-indexer');
|
|
16
16
|
/**
|
|
17
17
|
* writePlaylistAtomically replaces a file only once the new bytes are safely on
|
|
18
18
|
* disk.
|
|
@@ -63,7 +63,16 @@ async function writePlaylistAtomically(destination, contents, expectedDigest) {
|
|
|
63
63
|
// of it, or widen who can reach it. Carry the ownership across, and when
|
|
64
64
|
// that is not permitted refuse the in-place replacement instead of
|
|
65
65
|
// completing it with different access than the file had.
|
|
66
|
-
|
|
66
|
+
//
|
|
67
|
+
// Only where POSIX identity exists. Windows has no getuid/getgid and
|
|
68
|
+
// reports uid and gid as 0 on every stat, so the comparison there would
|
|
69
|
+
// always "differ" and chown a file whose ownership never moved. (libuv
|
|
70
|
+
// makes fchown a no-op on Windows, so nothing would break — but the
|
|
71
|
+
// branch would be asserting something it cannot know.)
|
|
72
|
+
const uid = process.getuid?.();
|
|
73
|
+
const gid = process.getgid?.();
|
|
74
|
+
const canCompareOwnership = uid !== undefined && gid !== undefined;
|
|
75
|
+
if (current && canCompareOwnership && (current.uid !== uid || current.gid !== gid)) {
|
|
67
76
|
try {
|
|
68
77
|
await handle.chown(current.uid, current.gid);
|
|
69
78
|
}
|
|
@@ -230,12 +239,22 @@ exports.enrichCommand = new commander_1.Command('enrich')
|
|
|
230
239
|
printedProgress = true;
|
|
231
240
|
process.stdout.write(chalk_1.default.dim(`\r ${done}/${count} looked up...`));
|
|
232
241
|
};
|
|
233
|
-
//
|
|
242
|
+
// resolveTokenBatch is (tokens, duration, onProgress) — the second
|
|
234
243
|
// positional is DP-1 display seconds, not the callback. Enrichment never
|
|
235
244
|
// sets duration (the curator's timing is not ours to touch), so it is
|
|
236
245
|
// passed undefined and the callback goes third. find.ts carries the same
|
|
237
246
|
// warning; getting this wrong silently returns unusable results.
|
|
238
|
-
|
|
247
|
+
//
|
|
248
|
+
// The raw results are used rather than getNFTTokenInfoBatch's items so
|
|
249
|
+
// the indexer's still rides beside each item: the item itself drops the
|
|
250
|
+
// still whenever it equals the indexer's chosen source, and enrichment
|
|
251
|
+
// needs it against the curator's source instead.
|
|
252
|
+
const lookup = async (tokens, onProgressCallback) => {
|
|
253
|
+
const results = await resolveTokenBatch(tokens, undefined, onProgressCallback);
|
|
254
|
+
return results
|
|
255
|
+
.filter((result) => result.success && result.item)
|
|
256
|
+
.map((result) => ({ ...result.item, still: result.still }));
|
|
257
|
+
};
|
|
239
258
|
const result = await (0, enrich_playlist_1.enrichPlaylistManifests)(playlist, lookup, {
|
|
240
259
|
force: options.force,
|
|
241
260
|
assumeEthereum: options.assumeEthereum,
|
|
@@ -1,37 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
4
|
};
|
|
@@ -595,8 +562,8 @@ async function doPlay(playlist, deviceName, skipVerify) {
|
|
|
595
562
|
}
|
|
596
563
|
async function doPublish(savedPath, serverArg, nonInteractive) {
|
|
597
564
|
console.log(chalk_1.default.blue('Publish to feed'));
|
|
598
|
-
const { getFeedConfig } = await
|
|
599
|
-
const { publishPlaylist } = await
|
|
565
|
+
const { getFeedConfig } = await import('../config.js');
|
|
566
|
+
const { publishPlaylist } = await import('../utilities/playlist-publisher.js');
|
|
600
567
|
const feedConfig = getFeedConfig();
|
|
601
568
|
if (!feedConfig.baseURLs || feedConfig.baseURLs.length === 0) {
|
|
602
569
|
console.error(chalk_1.default.red('No feed servers configured.'));
|
|
@@ -1,37 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
4
|
};
|
|
@@ -98,7 +65,7 @@ function printPlaylistVerificationFailure(verifyResult, source) {
|
|
|
98
65
|
*/
|
|
99
66
|
async function validatePlaylistSource(source) {
|
|
100
67
|
const loaded = await (0, playlist_source_1.loadPlaylistSource)(source);
|
|
101
|
-
const verifier = await
|
|
68
|
+
const verifier = await import('../../utilities/playlist-verifier.js');
|
|
102
69
|
const { validatePlaylist } = verifier;
|
|
103
70
|
const verifyResult = await validatePlaylist(loaded.playlist);
|
|
104
71
|
return {
|
|
@@ -113,7 +80,7 @@ async function validatePlaylistSource(source) {
|
|
|
113
80
|
async function runValidateCommand(source) {
|
|
114
81
|
try {
|
|
115
82
|
console.log(chalk_1.default.blue('\nValidate playlist\n'));
|
|
116
|
-
const verifier = await
|
|
83
|
+
const verifier = await import('../../utilities/playlist-verifier.js');
|
|
117
84
|
const { printVerificationResult } = verifier;
|
|
118
85
|
const result = await validatePlaylistSource(source);
|
|
119
86
|
printVerificationResult(result, source, { failureKind: 'structure' });
|
|
@@ -138,7 +105,7 @@ async function runValidateCommand(source) {
|
|
|
138
105
|
async function runVerifyCommand(source, publicKey) {
|
|
139
106
|
try {
|
|
140
107
|
console.log(chalk_1.default.blue('\nVerify playlist\n'));
|
|
141
|
-
const verifier = await
|
|
108
|
+
const verifier = await import('../../utilities/playlist-verifier.js');
|
|
142
109
|
const { printVerificationResult } = verifier;
|
|
143
110
|
const result = await validatePlaylistSource(source);
|
|
144
111
|
if (!result.valid) {
|
|
@@ -1,37 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
4
|
};
|
|
@@ -47,8 +14,8 @@ exports.publishCommand = new commander_1.Command('publish')
|
|
|
47
14
|
.action(async (file, options) => {
|
|
48
15
|
try {
|
|
49
16
|
console.log(chalk_1.default.blue('\nPublish playlist\n'));
|
|
50
|
-
const { getFeedConfig } = await
|
|
51
|
-
const { publishPlaylist } = await
|
|
17
|
+
const { getFeedConfig } = await import('../config.js');
|
|
18
|
+
const { publishPlaylist } = await import('../utilities/playlist-publisher.js');
|
|
52
19
|
const feedConfig = getFeedConfig();
|
|
53
20
|
if (!feedConfig.baseURLs || feedConfig.baseURLs.length === 0) {
|
|
54
21
|
console.error(chalk_1.default.red('\nNo feed servers configured'));
|
package/dist/src/commands/ssh.js
CHANGED
|
@@ -1,37 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
4
|
};
|
|
@@ -71,7 +38,7 @@ exports.sshCommand = new commander_1.Command('ssh')
|
|
|
71
38
|
if (options.ttl) {
|
|
72
39
|
ttlSeconds = (0, ssh_helpers_1.parseTtlSeconds)(options.ttl);
|
|
73
40
|
}
|
|
74
|
-
const { sendSshAccessCommand } = await
|
|
41
|
+
const { sendSshAccessCommand } = await import('../utilities/ssh-access.js');
|
|
75
42
|
const result = await sendSshAccessCommand({
|
|
76
43
|
enabled: isEnable,
|
|
77
44
|
deviceName: options.device,
|
|
@@ -129,7 +129,43 @@ function coordinateKeyOf(provenance) {
|
|
|
129
129
|
if (token.length === 0) {
|
|
130
130
|
return null;
|
|
131
131
|
}
|
|
132
|
-
|
|
132
|
+
const canonical = canonicalChain(chain);
|
|
133
|
+
const standard = effectiveStandard(canonical, address, contract.standard);
|
|
134
|
+
return `${canonical}:${standard}:${address.trim().toLowerCase()}:${token}`;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Standards the indexer keys CIDs on. Mirrors CID_STANDARDS in nft-indexer.js.
|
|
138
|
+
*/
|
|
139
|
+
const CID_STANDARDS = new Set(['erc721', 'erc1155', 'fa2']);
|
|
140
|
+
/**
|
|
141
|
+
* effectiveStandard names the standard a coordinate is looked up under, so the
|
|
142
|
+
* correlation key separates what the indexer separates.
|
|
143
|
+
*
|
|
144
|
+
* The indexer keys ERC-721 and ERC-1155 tokens at one address under different
|
|
145
|
+
* CIDs, so a hybrid contract can hold both standards at one token id. Two
|
|
146
|
+
* playlist items asserting different standards for one coordinate are two
|
|
147
|
+
* lookups, and each answer must reach only the item that asked for it.
|
|
148
|
+
*
|
|
149
|
+
* When the playlist asserts nothing the client detects, and this mirrors that
|
|
150
|
+
* detection (resolveTokenStandard in nft-indexer.js) so the request key and
|
|
151
|
+
* the key read off the response agree. The mirror is deliberate rather than
|
|
152
|
+
* an import: this module takes its lookup by injection and stays free of the
|
|
153
|
+
* indexer client. If the two ever drift, correlation fails closed — the item
|
|
154
|
+
* reports not-indexed — rather than attributing one token's metadata to
|
|
155
|
+
* another.
|
|
156
|
+
*/
|
|
157
|
+
function effectiveStandard(canonicalChainName, address, asserted) {
|
|
158
|
+
const normalized = typeof asserted === 'string' ? asserted.trim().toLowerCase() : '';
|
|
159
|
+
if (CID_STANDARDS.has(normalized)) {
|
|
160
|
+
return normalized;
|
|
161
|
+
}
|
|
162
|
+
if (canonicalChainName === 'tezos' || address.trim().startsWith('KT')) {
|
|
163
|
+
return 'fa2';
|
|
164
|
+
}
|
|
165
|
+
if (canonicalChainName === 'evm') {
|
|
166
|
+
return 'erc721';
|
|
167
|
+
}
|
|
168
|
+
return 'other';
|
|
133
169
|
}
|
|
134
170
|
/**
|
|
135
171
|
* describeItem names an item for operator output, preferring its title.
|
|
@@ -160,14 +196,22 @@ function coordinateFor(item) {
|
|
|
160
196
|
return null;
|
|
161
197
|
}
|
|
162
198
|
const normalized = chain.trim().toLowerCase();
|
|
163
|
-
|
|
199
|
+
const coordinate = {
|
|
164
200
|
chain: CHAIN_ALIASES[normalized] ?? normalized,
|
|
165
201
|
contractAddress: address,
|
|
166
202
|
tokenId: token,
|
|
167
203
|
};
|
|
204
|
+
// Carried as written. The indexer client accepts the standards its CIDs
|
|
205
|
+
// are keyed on and falls back to detection for anything else, so an
|
|
206
|
+
// unexpected value costs nothing beyond the lookup it would have lost anyway.
|
|
207
|
+
const standard = contract.standard;
|
|
208
|
+
if (typeof standard === 'string' && standard.trim().length > 0) {
|
|
209
|
+
coordinate.standard = standard.trim().toLowerCase();
|
|
210
|
+
}
|
|
211
|
+
return coordinate;
|
|
168
212
|
}
|
|
169
213
|
/**
|
|
170
|
-
*
|
|
214
|
+
* stillFor recovers a thumbnail the manifest builder suppressed.
|
|
171
215
|
*
|
|
172
216
|
* `resolveStillUri` blanks a still that equals the source it was given, on the
|
|
173
217
|
* reasonable ground that pointing a thumbnail at the artwork itself adds
|
|
@@ -179,64 +223,26 @@ function coordinateFor(item) {
|
|
|
179
223
|
* exists to repair: a live work the app cannot rasterize, whose grid tile
|
|
180
224
|
* stays empty.
|
|
181
225
|
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
226
|
+
* Recovery is the same rule applied against the curator's source instead of
|
|
227
|
+
* the indexer's: the still the indexer names for the token (`resolved.still`,
|
|
228
|
+
* its `display.image_url`) is used whenever it differs from the item's own
|
|
229
|
+
* source. It is the same field `find` and `build` write into
|
|
230
|
+
* `thumbnails.default`, and it is taken on the indexer's word, as they take
|
|
231
|
+
* it. No guess is made from a URL's shape — the indexer's source is often a
|
|
232
|
+
* live rendition and its stills are often extensionless CDN URLs, so a
|
|
233
|
+
* suffix check gets both cases wrong — and a lookup that names no still
|
|
234
|
+
* leaves the item without one.
|
|
185
235
|
*/
|
|
186
|
-
function
|
|
187
|
-
const
|
|
236
|
+
function stillFor(item, resolved) {
|
|
237
|
+
const still = typeof resolved.still === 'string' ? resolved.still.trim() : '';
|
|
188
238
|
const itemSource = typeof item.source === 'string' ? item.source.trim() : '';
|
|
189
|
-
if (!
|
|
239
|
+
if (!still || still === itemSource) {
|
|
190
240
|
return '';
|
|
191
241
|
}
|
|
192
|
-
if (!
|
|
242
|
+
if (!still.startsWith('http://') && !still.startsWith('https://')) {
|
|
193
243
|
return '';
|
|
194
244
|
}
|
|
195
|
-
|
|
196
|
-
// display.animation_url and media assets over display.image_url, so it is
|
|
197
|
-
// frequently a live HTML rendition — and writing one into the thumbnail slot
|
|
198
|
-
// is worse than leaving the slot empty: the grid still cannot rasterize it,
|
|
199
|
-
// and the item now claims a still it does not have.
|
|
200
|
-
//
|
|
201
|
-
// Only a URL that is demonstrably an image is used. The indexer exposes no
|
|
202
|
-
// separately identified still on the item this command receives, so the file
|
|
203
|
-
// extension is the evidence available; carrying a real image field through
|
|
204
|
-
// the batch result would be the better fix and belongs upstream.
|
|
205
|
-
return looksLikeImage(indexerSource) ? indexerSource : '';
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* Media the app can draw a grid tile from, matching the types
|
|
209
|
-
* playlist-builder.js already recognizes. SVG and video belong here: the app
|
|
210
|
-
* rasterizes both, and excluding them left works with the empty tile this
|
|
211
|
-
* command exists to remove. HTML is deliberately absent — an unrasterizable
|
|
212
|
-
* page in the thumbnail slot is worse than an empty slot, because the item
|
|
213
|
-
* then claims a still it does not have.
|
|
214
|
-
*/
|
|
215
|
-
const RASTERIZABLE_EXTENSIONS = [
|
|
216
|
-
'.png',
|
|
217
|
-
'.jpg',
|
|
218
|
-
'.jpeg',
|
|
219
|
-
'.gif',
|
|
220
|
-
'.webp',
|
|
221
|
-
'.avif',
|
|
222
|
-
'.bmp',
|
|
223
|
-
'.svg',
|
|
224
|
-
'.mp4',
|
|
225
|
-
'.webm',
|
|
226
|
-
];
|
|
227
|
-
/**
|
|
228
|
-
* looksLikeImage tests a URL's path extension, ignoring query and fragment.
|
|
229
|
-
*/
|
|
230
|
-
function looksLikeImage(url) {
|
|
231
|
-
let pathname = url;
|
|
232
|
-
try {
|
|
233
|
-
pathname = new URL(url).pathname;
|
|
234
|
-
}
|
|
235
|
-
catch {
|
|
236
|
-
pathname = url.split('?')[0].split('#')[0];
|
|
237
|
-
}
|
|
238
|
-
const lower = pathname.toLowerCase();
|
|
239
|
-
return RASTERIZABLE_EXTENSIONS.some((extension) => lower.endsWith(extension));
|
|
245
|
+
return still;
|
|
240
246
|
}
|
|
241
247
|
/**
|
|
242
248
|
* hasThumbnail reports whether a manifest already carries a default still.
|
|
@@ -258,9 +264,10 @@ function hasThumbnail(manifest) {
|
|
|
258
264
|
* thumbnails) is what enrichment exists to fetch and is taken as given.
|
|
259
265
|
*/
|
|
260
266
|
function manifestFor(item, resolved) {
|
|
261
|
-
const still =
|
|
267
|
+
const still = stillFor(item, resolved);
|
|
268
|
+
const curatorTitle = typeof item.title === 'string' ? item.title.trim() : '';
|
|
269
|
+
const resolvedTitle = typeof resolved.title === 'string' ? resolved.title.trim() : '';
|
|
262
270
|
let manifest = resolved.inlineManifest;
|
|
263
|
-
let rekey = false;
|
|
264
271
|
if (manifest && still && !hasThumbnail(manifest)) {
|
|
265
272
|
manifest = {
|
|
266
273
|
...manifest,
|
|
@@ -269,30 +276,36 @@ function manifestFor(item, resolved) {
|
|
|
269
276
|
thumbnails: { default: { uri: still } },
|
|
270
277
|
},
|
|
271
278
|
};
|
|
272
|
-
rekey = true;
|
|
273
279
|
}
|
|
274
|
-
else if (!manifest
|
|
275
|
-
// Nothing was emitted because the
|
|
276
|
-
//
|
|
277
|
-
//
|
|
278
|
-
//
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
280
|
+
else if (!manifest) {
|
|
281
|
+
// Nothing was emitted because the token had only a title, or only a still
|
|
282
|
+
// that was suppressed against the indexer's source. Either can still be
|
|
283
|
+
// worth a manifest here, for a reason `find` and `build` never meet: their
|
|
284
|
+
// items always carry the token's title, so a title-only manifest would
|
|
285
|
+
// repeat what the item already says. A curator's item may carry no title
|
|
286
|
+
// at all, and the tombstone then shows nothing — so a substantiated title
|
|
287
|
+
// is written when the item lacks one, and a still is written whenever
|
|
288
|
+
// there is one, because it is the difference between a grid tile and an
|
|
289
|
+
// empty square.
|
|
290
|
+
const synthesizedTitle = curatorTitle || resolvedTitle;
|
|
291
|
+
const titleWorthCarrying = !curatorTitle && resolvedTitle.length > 0;
|
|
292
|
+
if (still || titleWorthCarrying) {
|
|
293
|
+
manifest = {
|
|
294
|
+
refVersion: '1.1.0',
|
|
295
|
+
id: derivedManifestId('', `${synthesizedTitle}\n${still}`),
|
|
296
|
+
created: MANIFEST_CREATED,
|
|
297
|
+
locale: 'en',
|
|
298
|
+
metadata: {
|
|
299
|
+
...(synthesizedTitle ? { title: synthesizedTitle } : {}),
|
|
300
|
+
...(still ? { thumbnails: { default: { uri: still } } } : {}),
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
}
|
|
290
304
|
}
|
|
291
305
|
if (!manifest) {
|
|
292
306
|
return undefined;
|
|
293
307
|
}
|
|
294
308
|
const metadata = manifest.metadata ?? {};
|
|
295
|
-
const curatorTitle = typeof item.title === 'string' ? item.title.trim() : '';
|
|
296
309
|
const overrideTitle = curatorTitle.length > 0 && metadata.title !== curatorTitle;
|
|
297
310
|
// The id is derived from the finished payload every time, not only when this
|
|
298
311
|
// command changed something. The indexer keys its id on the token
|
|
@@ -304,10 +317,6 @@ function manifestFor(item, resolved) {
|
|
|
304
317
|
//
|
|
305
318
|
// The original id is folded in, so the result stays anchored to the token
|
|
306
319
|
// rather than floating free of it, and stays deterministic across runs.
|
|
307
|
-
//
|
|
308
|
-
// `rekey` is read only to record that a thumbnail was recovered; the
|
|
309
|
-
// derivation below covers it either way.
|
|
310
|
-
void rekey;
|
|
311
320
|
const finalMetadata = overrideTitle ? { ...metadata, title: curatorTitle } : metadata;
|
|
312
321
|
return {
|
|
313
322
|
...manifest,
|
|
@@ -1,40 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* Function Calling Layer
|
|
3
4
|
* These are the actual implementations called by AI orchestrator via function calling
|
|
4
5
|
*/
|
|
5
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
-
if (k2 === undefined) k2 = k;
|
|
7
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
-
}
|
|
11
|
-
Object.defineProperty(o, k2, desc);
|
|
12
|
-
}) : (function(o, m, k, k2) {
|
|
13
|
-
if (k2 === undefined) k2 = k;
|
|
14
|
-
o[k2] = m[k];
|
|
15
|
-
}));
|
|
16
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
-
}) : function(o, v) {
|
|
19
|
-
o["default"] = v;
|
|
20
|
-
});
|
|
21
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
-
var ownKeys = function(o) {
|
|
23
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
-
var ar = [];
|
|
25
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
-
return ar;
|
|
27
|
-
};
|
|
28
|
-
return ownKeys(o);
|
|
29
|
-
};
|
|
30
|
-
return function (mod) {
|
|
31
|
-
if (mod && mod.__esModule) return mod;
|
|
32
|
-
var result = {};
|
|
33
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
-
__setModuleDefault(result, mod);
|
|
35
|
-
return result;
|
|
36
|
-
};
|
|
37
|
-
})();
|
|
38
6
|
const chalk = require('chalk');
|
|
39
7
|
const playlistBuilder = require('./playlist-builder');
|
|
40
8
|
const ff1Device = require('./ff1-device');
|
|
@@ -162,7 +130,7 @@ async function verifyPlaylist(params) {
|
|
|
162
130
|
}
|
|
163
131
|
logger.verbose(chalk.cyan('\nValidate playlist'));
|
|
164
132
|
// Dynamic import to avoid circular dependency
|
|
165
|
-
const playlistVerifier = await
|
|
133
|
+
const playlistVerifier = await import('./playlist-verifier.js');
|
|
166
134
|
const validate = playlistVerifier.validatePlaylist ||
|
|
167
135
|
(playlistVerifier.default && playlistVerifier.default.validatePlaylist) ||
|
|
168
136
|
playlistVerifier.default;
|
|
@@ -226,7 +194,7 @@ async function verifyAddresses(params) {
|
|
|
226
194
|
};
|
|
227
195
|
}
|
|
228
196
|
// Dynamic import to avoid circular dependency
|
|
229
|
-
const addressValidator = await
|
|
197
|
+
const addressValidator = await import('./address-validator.js');
|
|
230
198
|
const validateAddresses = addressValidator.validateAddresses ||
|
|
231
199
|
(addressValidator.default && addressValidator.default.validateAddresses) ||
|
|
232
200
|
addressValidator.default;
|
|
@@ -268,7 +236,7 @@ async function verifyAddresses(params) {
|
|
|
268
236
|
* }
|
|
269
237
|
*/
|
|
270
238
|
async function getConfiguredDevices() {
|
|
271
|
-
const configModule = await
|
|
239
|
+
const configModule = await import('../config.js');
|
|
272
240
|
const getFF1DeviceConfig = configModule.getFF1DeviceConfig ||
|
|
273
241
|
(configModule.default && configModule.default.getFF1DeviceConfig) ||
|
|
274
242
|
configModule.default;
|
|
@@ -1,40 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* Utilities - Actual function implementations
|
|
3
4
|
* Contains the real logic for querying NFTs and building playlists
|
|
4
5
|
*/
|
|
5
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
-
if (k2 === undefined) k2 = k;
|
|
7
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
-
}
|
|
11
|
-
Object.defineProperty(o, k2, desc);
|
|
12
|
-
}) : (function(o, m, k, k2) {
|
|
13
|
-
if (k2 === undefined) k2 = k;
|
|
14
|
-
o[k2] = m[k];
|
|
15
|
-
}));
|
|
16
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
-
}) : function(o, v) {
|
|
19
|
-
o["default"] = v;
|
|
20
|
-
});
|
|
21
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
-
var ownKeys = function(o) {
|
|
23
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
-
var ar = [];
|
|
25
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
-
return ar;
|
|
27
|
-
};
|
|
28
|
-
return ownKeys(o);
|
|
29
|
-
};
|
|
30
|
-
return function (mod) {
|
|
31
|
-
if (mod && mod.__esModule) return mod;
|
|
32
|
-
var result = {};
|
|
33
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
-
__setModuleDefault(result, mod);
|
|
35
|
-
return result;
|
|
36
|
-
};
|
|
37
|
-
})();
|
|
38
6
|
const chalk = require('chalk');
|
|
39
7
|
const nftIndexer = require('./nft-indexer');
|
|
40
8
|
const feedFetcher = require('./feed-fetcher');
|
|
@@ -400,7 +368,7 @@ async function buildDP1Playlist(items, title, slug) {
|
|
|
400
368
|
* @returns {Promise<Object>} Result
|
|
401
369
|
*/
|
|
402
370
|
async function sendToDevice(playlist, deviceName) {
|
|
403
|
-
const playlistVerifier = await
|
|
371
|
+
const playlistVerifier = await import('./playlist-verifier.js');
|
|
404
372
|
const verifyPlaylist = playlistVerifier.verifyPlaylist ||
|
|
405
373
|
(playlistVerifier.default && playlistVerifier.default.verifyPlaylist) ||
|
|
406
374
|
playlistVerifier.default;
|