Digested 2024-09-24
Authors:: Unknown License:: Unspecified Doc Seal:: 203ae7061338
MarkdownTile
<%*
// DS Tagger Chain for Obsidian Templater
// Version 0.7.9
/* Fixes for 0.8.0 (changed versioning)
- new versioning format
- preserve Specs in comments
- append DigestTag to tile file frontmatter[“aliases”]
- bug: frontmatter for current file gets deleted
- bug: add ###### #EOT\n--- to end of Title Structure
*/
/* * Preserve Specs for Tile and Blockchain formats * * Tile Spec:
- Structure
###### #Digested #ds/{deformat-digest}/{YYYY-MM-DD}\nAuthors:: {authors}\nLicense:: {license}\nDoc Seal:: {doc-seal-hash}\n###### #MarkdownTile\n{tile}\n###### #DeformattedTile\n{deformatted-tile}\n###### #EOT\n---\n - Location:
Notes/ds/tile/{deformat-digest}.md - Upsert Logic: Skip (with Notice message) if
#ds/{deformat-digest}/{YYYY-MM-DD}exists, append otherwise Blockchain Spec: - Structure:
### [[${fileName}]] 🔒 #ds/seal/${docSealHash} chained ${YYYY-MM-DDTHH:mm} to #ds/block/${previousBlockchainEntryDigest}\n${digestTags.join(' ')}\n#ds/block/{currentBlockchainEntryDigest} - Location:
Notes/ds/blockchains/bc-{YY-MM}.md - Blockchain Logic: {previousBlockchainEntryDigest} is a hash of {YY-MM} for initial block or the BlockchainEntryDigest of the previous entry Script Spec:
- use Obsidian Templater scripting language for the script
- use app.fileManager.processFrontMatter to update frontmatter
- helper functions: generateHash(content, length = 12); deformatTile(tile); createDigestTag(hash, date); createTileID(digestTag, date); splitIntoTiles(content); createDocSealHash(digestTags); ensureDirectoryExists(path); upsertToFile(filePath, header, content); updateFrontMatter(file, updates); */
const crypto = require(‘crypto’);
// Helper functions
function generateHash(content, length = 12) { return crypto.createHash(‘sha256’).update(content).digest(‘hex’).substring(0, length); }
function deformatTile(tile) { if (typeof tile !== ‘string’) { throw new Error(‘Deformat received non-string content’); }
const originalLength = tile.length;
// Remove Digest Tags and Tile IDs
tile = tile.replace(/\s*#ds\/[a-f0-9]+\/\d{4}-\d{2}-\d{2}/g, '');
tile = tile.replace(/\s*\^ds-[a-f0-9]+/g, '');
// Remove headers, emphasis, bold, strikethrough, blockquotes, horizontal rules
tile = tile.replace(/^#{1,6}\s+|[*_~>]|\-{3,}|\*{3,}|_{3,}/gm, '');
// Remove list markers and task list markers
tile = tile.replace(/^[\s]*[-*+]\s|^\s*\d+\.\s|^- \[[ x]\]\s/gm, '');
// Remove highlight markers
tile = tile.replace(/==/g, '');
// Collapse multiple newlines and trim
tile = tile.replace(/\n+/g, '\n').trim();
const charactersRemoved = originalLength - tile.length;
return { deformattedContent: tile, charactersRemoved };
}
function createDigestTag(hash, date) {
return #ds/${hash}/${date};
}
function createTileID(digestTag, date) {
const tagHash = generateHash(digestTag + date);
return ^ds-${tagHash};
}
function splitIntoTiles(content) { // Remove frontmatter content = content.replace(/^---\n[\s\S]*?\n---\n/, ”);
const headerRegex = /^#{1,3}\s+.*$/gm;
const tiles = [];
let lastIndex = 0;
let match;
// Check if there's content before the first header
const firstHeaderMatch = content.match(headerRegex);
if (firstHeaderMatch && firstHeaderMatch.index > 0) {
tiles.push(content.slice(0, firstHeaderMatch.index).trim());
lastIndex = firstHeaderMatch.index;
}
while ((match = headerRegex.exec(content)) !== null) {
if (lastIndex !== match.index) {
tiles.push(content.slice(lastIndex, match.index).trim());
}
lastIndex = match.index;
}
if (lastIndex < content.length) {
tiles.push(content.slice(lastIndex).trim());
}
return tiles;
}
function createDocSealHash(digestTags) { return generateHash(digestTags.join(’ ’), 12); }
async function ensureDirectoryExists(path) { const dirs = path.split(’/‘).slice(0, -1).join(’/’); if (!(await app.vault.adapter.exists(dirs))) { await app.vault.createFolder(dirs); } }
async function upsertToFile(filePath, header, content) { try { await ensureDirectoryExists(filePath);
let file = app.vault.getAbstractFileByPath(filePath);
let fileContent = '';
if (file) {
fileContent = await app.vault.read(file);
}
const escapedHeader = header.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(`${escapedHeader}[\\s\\S]*?(?=######|$)`, 'g');
if (regex.test(fileContent)) {
if (fileContent.includes(content.trim())) {
console.log(`File content already up to date: ${filePath}`);
return { status: 'unchanged', file: file };
}
// For blockchain entries, check if the entry already exists
if (header.startsWith('### [[') && header.includes('🔒 #ds/seal/')) {
if (fileContent.includes(header)) {
console.log(`Blockchain entry already exists: ${filePath}`);
new Notice(`Blockchain entry already exists: ${filePath}`, 5000);
return { status: 'unchanged', file: file };
}
}
fileContent += `\n\n${content}\n`;
} else {
fileContent += `${content}\n\n`;
}
if (file) {
await app.vault.modify(file, fileContent);
} else {
file = await app.vault.create(filePath, fileContent);
}
console.log(`File upserted successfully: ${filePath}`);
return { status: 'updated', file: file };
} catch (error) {
console.error(`Error upserting file ${filePath}:`, error);
throw error;
}
}
async function updateFrontMatter(file, updates) {
try {
await app.fileManager.processFrontMatter(file, (frontmatter) ⇒ {
Object.entries(updates).forEach(([key, value]) ⇒ {
if (Array.isArray(value)) {
if (!frontmatter[key]) {
frontmatter[key] = [];
}
value.forEach(item ⇒ {
if (!frontmatter[key].includes(item)) {
frontmatter[key].push(item);
}
});
} else {
frontmatter[key] = value;
}
});
});
} catch (error) {
console.error(Error updating frontmatter for ${file.path}:, error);
throw error;
}
}
// Main function
async function dsTaggerChain() {
const version = “0.7.93”;
const startTime = Date.now();
const currentDate = new Date().toISOString().split(‘T’)[0];
const currentTimestamp = new Date().toISOString();
console.log([${currentDate}] DS Tagger Chain v${version} started);
try {
let content = tp.file.content;
const fileName = tp.file.title;
const currentFile = tp.file.find_tfile(fileName);
if (typeof content !== 'string') {
throw new Error('Received non-string content');
}
const authors = tp.frontmatter.authors || 'Unknown';
const license = tp.frontmatter.license || 'Unspecified';
console.log(`[${currentDate}] Authors extracted: ${authors}`);
console.log(`[${currentDate}] License extracted: ${license}`);
console.log(`[${currentDate}] File content length: ${content.length} characters`);
let tiles = splitIntoTiles(content);
console.log(`[${currentDate}] Number of tiles: ${tiles.length}`);
let processedTiles = [];
let digestedTiles = 0;
let nonDigestedTiles = 0;
let updatedTiles = 0;
let digestTags = [];
let tileFilesUpdated = {};
for (let i = 0; i < tiles.length; i++) {
let tile = tiles[i];
const tileHash = generateHash(tile);
console.log(`\n[${currentDate}] Processing tile ${i + 1} (${tileHash})`);
let existingDigestMatch = tile.match(/\s*(#ds\/[a-f0-9]+\/\d{4}-\d{2}-\d{2})/);
let existingTileIDMatch = tile.match(/\s*(\^ds-[a-f0-9]+)/);
let existingDigest = existingDigestMatch ? existingDigestMatch[1] : null;
let existingTileID = existingTileIDMatch ? existingTileIDMatch[1] : null;
console.log(`Existing Digest Tag: ${existingDigest || 'None'}`);
console.log(`Existing Tile ID: ${existingTileID || 'None'}`);
let cleanTile = tile.replace(/\s*#ds\/[a-f0-9]+\/\d{4}-\d{2}-\d{2}/, '')
.replace(/\s*\^ds-[a-f0-9]+/, '')
.trim();
let { deformattedContent, charactersRemoved } = deformatTile(cleanTile);
let digest = generateHash(deformattedContent);
let digestTag = createDigestTag(digest, currentDate);
let tileID = createTileID(digestTag, currentDate);
console.log(`New Digest Tag: ${digestTag}`);
console.log(`New Tile ID: ${tileID}`);
if (existingDigest && existingDigest.includes(digest) && existingDigest.includes(currentDate)) {
console.log(`Content unchanged for tile ${tileHash}. Keeping existing Digest Tag and Tile ID.`);
digestTag = existingDigest;
tileID = existingTileID || createTileID(digestTag, currentDate);
nonDigestedTiles++;
} else {
console.log(`Content changed or new for tile ${tileHash}. Updating Digest Tag and Tile ID.`);
if (existingDigest) {
updatedTiles++;
} else {
digestedTiles++;
}
}
let processedTile = `${cleanTile} ${digestTag} ${tileID}`;
processedTiles.push(processedTile);
digestTags.push(digestTag);
console.log(`Tile ${tileHash} stats: Characters removed: ${charactersRemoved}`);
const tileFilePath = `Notes/ds/tile/${digest}.md`;
const docSealHash = createDocSealHash(digestTags);
const tileFileContent = `###### #Digested ${digestTag}
Authors:: {license} Doc Seal:: ${docSealHash}
MarkdownTile
${cleanTile}
DeformattedTile
{deformattedContent}`; const { status: tileUpdateStatus, file: tileFile } = await upsertToFile(tileFilePath, `###### #Digested {digestTag}`, tileFileContent); tileFilesUpdated[tileFilePath] = tileUpdateStatus;
// Update tile file frontmatter
if (tileFile) {
await updateFrontMatter(tileFile, {
aliases: [digestTag],
authors: authors,
license: license,
'doc-seal': docSealHash
});
}
}
let result = processedTiles.join('\n\n');
let docSealHash = createDocSealHash(digestTags);
console.log(`\n[${currentDate}] Doc seal hash created: ${docSealHash}`);
// Update current file frontmatter
await updateFrontMatter(currentFile, {
'doc-seal-hash': docSealHash,
'doc-seal-timestamp': currentTimestamp,
'digest-tags': digestTags
});
// Update blockchain file
const blockchainFilePath = `Notes/ds/blockchains/bc-${currentDate.slice(2, 7)}.md`;
let previousBlockchainEntryDigest = '';
const blockchainFile = app.vault.getAbstractFileByPath(blockchainFilePath);
if (blockchainFile) {
const blockchainFileContent = await app.vault.read(blockchainFile);
const lastEntryMatch = blockchainFileContent.match(/#ds\/block\/([a-f0-9]+)$/m);
previousBlockchainEntryDigest = lastEntryMatch ? lastEntryMatch[1] : '';
} else {
previousBlockchainEntryDigest = generateHash(currentDate.substring(0, 7));
}
// Create the blockchain content without the final hash
const blockchainContentWithoutHash = `### [[${fileName}]] 🔒 #ds/seal/${docSealHash} chained ${currentTimestamp} to #ds/block/${previousBlockchainEntryDigest}
${digestTags.join(’ ’)}`.trim();
// Generate the hash for the blockchain entry
const blockchainEntryHash = generateHash(blockchainContentWithoutHash);
// Create the final blockchain content
const blockchainFileContent = `${blockchainContentWithoutHash}
block/${blockchainEntryHash}`;
const { status: blockchainUpdateStatus } = await upsertToFile(blockchainFilePath, `### [[${fileName}]] 🔒 #ds/seal/${docSealHash} chained ${currentTimestamp}`, blockchainFileContent);
if (blockchainUpdateStatus === 'updated') {
console.log(`Updated blockchain file: ${blockchainFilePath}`);
} else {
console.log(`Blockchain file already up to date: ${blockchainFilePath}`);
}
await app.vault.modify(currentFile, result);
const endTime = Date.now();
const runtime = ((endTime - startTime) / 1000).toFixed(2);
console.log(`\n[${currentDate}] DS Tagger Chain process completed successfully`);
const statusMessage = `DS Tagger Chain v${version} completed:
Runtime: {docSealHash} Tiles processed: {digestedTiles} Tiles not digested: {updatedTiles} Date: ${currentDate}`;
const tileFilesMessage = `Tile files updated:
{Object.entries(tileFilesUpdated).map(([file, status]) => `{file}: ${status}).join('\n')};
const blockchainEntryMessage = `Blockchain entry (${blockchainUpdateStatus}):
${blockchainFileContent}`;
console.log(statusMessage);
console.log(tileFilesMessage);
console.log(blockchainEntryMessage);
new Notice(statusMessage, 10000);
new Notice(tileFilesMessage, 10000);
new Notice(blockchainEntryMessage, 10000);
} catch (error) {
console.error(`[${currentDate}] An error occurred:`, error);
new Notice(`Error in DS Tagger Chain v${version}: ${error.message}. Check console for details.`, 10000);
}
}
// Run the script and output the result await dsTaggerChain(); %>
DeformattedTile
<%
// DS Tagger Chain for Obsidian Templater
// Version 0.7.9
/ Fixes for 0.8.0 (changed versioning)
new versioning format
preserve Specs in comments
append DigestTag to tile file frontmatter[“aliases”]
bug: frontmatter for current file gets deleted
bug: add ###### #EOT\n to end of Title Structure
/
/ Preserve Specs for Tile and Blockchain formats
Tile Spec:
Structure ###### #Digested #ds/{deformat-digest}/{YYYY-MM-DD}\nAuthors:: {authors}\nLicense:: {license}\nDoc Seal:: {doc-seal-hash}\n###### #MarkdownTile\n{tile}\n###### #DeformattedTile\n{deformatted-tile}\n###### #EOT\n\n
Location: Notes/ds/tile/{deformat-digest}.md
Upsert Logic: Skip (with Notice message) if #ds/{deformat-digest}/{YYYY-MM-DD} exists, append otherwise
Blockchain Spec:
Structure: ### [[${fileName}]] 🔒 #ds/seal/${docSealHash} chained ${YYYY-MM-DDTHH:mm} to #ds/block/${previousBlockchainEntryDigest}\n${digestTags.join(' ')}\n#ds/block/{currentBlockchainEntryDigest}
Location: Notes/ds/blockchains/bc-{YY-MM}.md
Blockchain Logic: {previousBlockchainEntryDigest} is a hash of {YY-MM} for initial block or the BlockchainEntryDigest of the previous entry
Script Spec:
use Obsidian Templater scripting language for the script
use app.fileManager.processFrontMatter to update frontmatter
helper functions: generateHash(content, length = 12); deformatTile(tile); createDigestTag(hash, date); createTileID(digestTag, date); splitIntoTiles(content); createDocSealHash(digestTags); ensureDirectoryExists(path); upsertToFile(filePath, header, content); updateFrontMatter(file, updates);
/
const crypto = require(‘crypto’);
// Helper functions
function generateHash(content, length = 12) {
return crypto.createHash(‘sha256’).update(content).digest(‘hex’).substring(0, length);
}
function deformatTile(tile) {
if (typeof tile ! ‘string’) {
throw new Error(‘Deformat received non-string content’);
}
const originalLength = tile.length;
// Remove Digest Tags and Tile IDs
tile = tile.replace(/\s#ds\/[a-f0-9]+\/\d{4}-\d{2}-\d{2}/g, '');
tile = tile.replace(/\s\^ds-[a-f0-9]+/g, '');
// Remove headers, emphasis, bold, strikethrough, blockquotes, horizontal rules
tile = tile.replace(/^#{1,6}\s+|[]|\-{3,}|\{3,}|{3,}/gm, '');
// Remove list markers and task list markers
tile = tile.replace(/^[\s][-+]\s|^\s\d+\.\s|^- \[[ x]\]\s/gm, '');
// Remove highlight markers
tile = tile.replace(//g, '');
// Collapse multiple newlines and trim
tile = tile.replace(/\n+/g, '\n').trim();
const charactersRemoved = originalLength - tile.length;
return { deformattedContent: tile, charactersRemoved };
}
function createDigestTag(hash, date) {
return #ds/${hash}/${date};
}
function createTileID(digestTag, date) {
const tagHash = generateHash(digestTag + date);
return ^ds-${tagHash};
}
function splitIntoTiles(content) {
// Remove frontmatter
content = content.replace(/^\n[\s\S]?\n\n/, ”);
const headerRegex = /^#{1,3}\s+./gm;
const tiles = [];
let lastIndex = 0;
let match;
// Check if there's content before the first header
const firstHeaderMatch = content.match(headerRegex);
if (firstHeaderMatch && firstHeaderMatch.index 0) {
tiles.push(content.slice(0, firstHeaderMatch.index).trim());
lastIndex = firstHeaderMatch.index;
}
while ((match = headerRegex.exec(content)) ! null) {
if (lastIndex ! match.index) {
tiles.push(content.slice(lastIndex, match.index).trim());
}
lastIndex = match.index;
}
if (lastIndex < content.length) {
tiles.push(content.slice(lastIndex).trim());
}
return tiles;
}
function createDocSealHash(digestTags) {
return generateHash(digestTags.join(' '), 12);
}
async function ensureDirectoryExists(path) {
const dirs = path.split('/').slice(0, -1).join('/');
if (!(await app.vault.adapter.exists(dirs))) {
await app.vault.createFolder(dirs);
}
}
async function upsertToFile(filePath, header, content) {
try {
await ensureDirectoryExists(filePath);
let file = app.vault.getAbstractFileByPath(filePath);
let fileContent = '';
if (file) {
fileContent = await app.vault.read(file);
}
const escapedHeader = header.replace(/[.+?^{}()|[]\]/g, ’\&');
const regex = new RegExp(`{escapedHeader}[\s\S]?(?=######|)`, 'g');
if (regex.test(fileContent)) {
if (fileContent.includes(content.trim())) {
console.log(`File content already up to date: {filePath}); return { status: 'unchanged', file: file }; } // For blockchain entries, check if the entry already exists if (header.startsWith('### [[') && header.includes('🔒 #ds/seal/')) { if (fileContent.includes(header)) { console.log(Blockchain entry already exists: {filePath}, 5000); return { status: 'unchanged', file: file }; } } fileContent += \n\n{content}\n`;
} else {
fileContent += `{content}\n\n; } if (file) { await app.vault.modify(file, fileContent); } else { file = await app.vault.create(filePath, fileContent); } console.log(File upserted successfully: {filePath}`);
return { status: 'updated', file: file };
} catch (error) {
console.error(`Error upserting file {filePath}:, error); throw error; } } async function updateFrontMatter(file, updates) { try { await app.fileManager.processFrontMatter(file, (frontmatter) = { Object.entries(updates).forEach(([key, value]) = { if (Array.isArray(value)) { if (!frontmatter[key]) { frontmatter[key] = []; } value.forEach(item = { if (!frontmatter[key].includes(item)) { frontmatter[key].push(item); } }); } else { frontmatter[key] = value; } }); }); } catch (error) { console.error(Error updating frontmatter for {file.path}:`, error);
throw error;
}
}
// Main function
async function dsTaggerChain() {
const version = "0.7.93";
const startTime = Date.now();
const currentDate = new Date().toISOString().split('T')[0];
const currentTimestamp = new Date().toISOString();
console.log(`[{currentDate}] DS Tagger Chain v{version} started`);
try {
let content = tp.file.content;
const fileName = tp.file.title;
const currentFile = tp.file.findtfile(fileName);
if (typeof content ! 'string') {
throw new Error('Received non-string content');
}
const authors = tp.frontmatter.authors || 'Unknown';
const license = tp.frontmatter.license || 'Unspecified';
console.log(`[{currentDate}] Authors extracted: {currentDate}] License extracted: {currentDate}] File content length: {currentDate}] Number of tiles: {tiles.length}`);
let processedTiles = [];
let digestedTiles = 0;
let nonDigestedTiles = 0;
let updatedTiles = 0;
let digestTags = [];
let tileFilesUpdated = {};
for (let i = 0; i < tiles.length; i++) {
let tile = tiles[i];
const tileHash = generateHash(tile);
console.log(`\n[{currentDate}] Processing tile {tileHash})); let existingDigestMatch = tile.match(/\s(#ds\/[a-f0-9]+\/\d{4}-\d{2}-\d{2})/); let existingTileIDMatch = tile.match(/\s(\^ds-[a-f0-9]+)/); let existingDigest = existingDigestMatch ? existingDigestMatch[1] : null; let existingTileID = existingTileIDMatch ? existingTileIDMatch[1] : null; console.log(Existing Digest Tag: {existingTileID || ‘None’}); let cleanTile = tile.replace(/\s#ds\/[a-f0-9]+\/\d{4}-\d{2}-\d{2}/, '') .replace(/\s\^ds-[a-f0-9]+/, '') .trim(); let { deformattedContent, charactersRemoved } = deformatTile(cleanTile); let digest = generateHash(deformattedContent); let digestTag = createDigestTag(digest, currentDate); let tileID = createTileID(digestTag, currentDate); console.log(New Digest Tag: {tileID}); if (existingDigest && existingDigest.includes(digest) && existingDigest.includes(currentDate)) { console.log(Content unchanged for tile {tileHash}. Keeping existing Digest Tag and Tile ID.`);
digestTag = existingDigest;
tileID = existingTileID || createTileID(digestTag, currentDate);
nonDigestedTiles++;
} else {
console.log(`Content changed or new for tile {tileHash}. Updating Digest Tag and Tile ID.); if (existingDigest) { updatedTiles++; } else { digestedTiles++; } } let processedTile = {digestTag} {tileHash} stats: Characters removed: {digest}.md; const docSealHash = createDocSealHash(digestTags); const tileFileContent = Digested {authors}
License:: {docSealHash}
#MarkdownTile
{cleanTile}
#DeformattedTile
{deformattedContent}; const { status: tileUpdateStatus, file: tileFile } = await upsertToFile(tileFilePath, Digested {digestTag}`, tileFileContent);
tileFilesUpdated[tileFilePath] = tileUpdateStatus;
// Update tile file frontmatter
if (tileFile) {
await updateFrontMatter(tileFile, {
aliases: [digestTag],
authors: authors,
license: license,
'doc-seal': docSealHash
});
}
}
let result = processedTiles.join('\n\n');
let docSealHash = createDocSealHash(digestTags);
console.log(`\n[{currentDate}] Doc seal hash created: {currentDate.slice(2, 7)}.md`;
let previousBlockchainEntryDigest = ”;
const blockchainFile = app.vault.getAbstractFileByPath(blockchainFilePath);
if (blockchainFile) {
const blockchainFileContent = await app.vault.read(blockchainFile);
const lastEntryMatch = blockchainFileContent.match(/#ds\/block\/([a-f0-9]+)$/m);
previousBlockchainEntryDigest = lastEntryMatch ? lastEntryMatch[1] : '';
} else {
previousBlockchainEntryDigest = generateHash(currentDate.substring(0, 7));
}
// Create the blockchain content without the final hash
const blockchainContentWithoutHash = `### [[${fileName}]] 🔒 #ds/seal/${docSealHash} chained ${currentTimestamp} to #ds/block/${previousBlockchainEntryDigest}
{digestTags.join(' ')}`.trim();
// Generate the hash for the blockchain entry
const blockchainEntryHash = generateHash(blockchainContentWithoutHash);
// Create the final blockchain content
const blockchainFileContent = `{blockchainContentWithoutHash}
#ds/block/{blockchainEntryHash}`;
const { status: blockchainUpdateStatus } = await upsertToFile(blockchainFilePath, `### [[{fileName}]] 🔒 seal/{currentTimestamp}, blockchainFileContent); if (blockchainUpdateStatus = 'updated') { console.log(Updated blockchain file: {blockchainFilePath}`);
} else {
console.log(`Blockchain file already up to date: {blockchainFilePath}); } await app.vault.modify(currentFile, result); const endTime = Date.now(); const runtime = ((endTime - startTime) / 1000).toFixed(2); console.log(\n[${currentDate}] DS Tagger Chain process completed successfully`);
const statusMessage = `DS Tagger Chain v${version} completed:
Runtime: {docSealHash}
Tiles processed: {digestedTiles}
Tiles not digested: {updatedTiles}
Date: {Object.entries(tileFilesUpdated).map(([file, status]) = ${file}: ${status}).join(‘\n’)}; const blockchainEntryMessage = Blockchain entry ({blockchainFileContent}; console.log(statusMessage); console.log(tileFilesMessage); console.log(blockchainEntryMessage); new Notice(statusMessage, 10000); new Notice(tileFilesMessage, 10000); new Notice(blockchainEntryMessage, 10000); } catch (error) { console.error([{version}: ${error.message}. Check console for details.`, 10000);
}
}
// Run the script and output the result
await dsTaggerChain();
%