Add prebuild script to fetch event kinds from nostr library
- Add scripts/fetch-kinds.js to fetch kinds.json from central source - Update package.json with prebuild hook to auto-fetch on build - Regenerate eventKinds.js from https://git.mleku.dev/mleku/nostr/raw/branch/main/encoders/kind/kinds.json - Now uses single source of truth for all 184 event kinds 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,8 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"fetch-kinds": "node scripts/fetch-kinds.js",
|
||||||
|
"prebuild": "npm run fetch-kinds",
|
||||||
"build": "rollup -c",
|
"build": "rollup -c",
|
||||||
"dev": "rollup -c -w",
|
"dev": "rollup -c -w",
|
||||||
"start": "sirv public --no-clear --single"
|
"start": "sirv public --no-clear --single"
|
||||||
|
|||||||
214
app/web/scripts/fetch-kinds.js
Normal file
214
app/web/scripts/fetch-kinds.js
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Fetches kinds.json from the nostr library and generates eventKinds.js
|
||||||
|
* Run: node scripts/fetch-kinds.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
const KINDS_URL = 'https://git.mleku.dev/mleku/nostr/raw/branch/main/encoders/kind/kinds.json';
|
||||||
|
|
||||||
|
async function fetchKinds() {
|
||||||
|
console.log(`Fetching kinds from ${KINDS_URL}...`);
|
||||||
|
|
||||||
|
const response = await fetch(KINDS_URL);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch kinds.json: ${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log(`Fetched ${Object.keys(data.kinds).length} kinds (version: ${data.version})`);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateEventKinds(data) {
|
||||||
|
const kinds = [];
|
||||||
|
|
||||||
|
for (const [kindNum, info] of Object.entries(data.kinds)) {
|
||||||
|
const k = parseInt(kindNum, 10);
|
||||||
|
|
||||||
|
// Determine classification
|
||||||
|
let isReplaceable = false;
|
||||||
|
let isAddressable = false;
|
||||||
|
let isEphemeral = false;
|
||||||
|
|
||||||
|
if (info.classification === 'replaceable' || k === 0 || k === 3 ||
|
||||||
|
(k >= data.ranges.replaceable.start && k < data.ranges.replaceable.end)) {
|
||||||
|
isReplaceable = true;
|
||||||
|
} else if (info.classification === 'parameterized' ||
|
||||||
|
(k >= data.ranges.parameterized.start && k <= data.ranges.parameterized.end)) {
|
||||||
|
isAddressable = true;
|
||||||
|
} else if (info.classification === 'ephemeral' ||
|
||||||
|
(k >= data.ranges.ephemeral.start && k < data.ranges.ephemeral.end)) {
|
||||||
|
isEphemeral = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry = {
|
||||||
|
kind: k,
|
||||||
|
name: info.name,
|
||||||
|
description: info.description,
|
||||||
|
nip: info.nip || null,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isReplaceable) entry.isReplaceable = true;
|
||||||
|
if (isAddressable) entry.isAddressable = true;
|
||||||
|
if (isEphemeral) entry.isEphemeral = true;
|
||||||
|
if (info.deprecated) entry.deprecated = true;
|
||||||
|
if (info.spec) entry.spec = info.spec;
|
||||||
|
|
||||||
|
// Add basic template
|
||||||
|
entry.template = {
|
||||||
|
kind: k,
|
||||||
|
content: "",
|
||||||
|
tags: []
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add d tag for addressable events
|
||||||
|
if (isAddressable) {
|
||||||
|
entry.template.tags = [["d", "identifier"]];
|
||||||
|
}
|
||||||
|
|
||||||
|
kinds.push(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by kind number
|
||||||
|
kinds.sort((a, b) => a.kind - b.kind);
|
||||||
|
|
||||||
|
return kinds;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateJS(kinds, data) {
|
||||||
|
return `/**
|
||||||
|
* Nostr Event Kinds Database
|
||||||
|
* Auto-generated from ${KINDS_URL}
|
||||||
|
* Version: ${data.version}
|
||||||
|
* Source: ${data.source}
|
||||||
|
*
|
||||||
|
* DO NOT EDIT - This file is auto-generated by scripts/fetch-kinds.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const eventKinds = ${JSON.stringify(kinds, null, 2)};
|
||||||
|
|
||||||
|
// Kind ranges for classification
|
||||||
|
export const kindRanges = ${JSON.stringify(data.ranges, null, 2)};
|
||||||
|
|
||||||
|
// Privileged kinds (require auth)
|
||||||
|
export const privilegedKinds = ${JSON.stringify(data.privileged)};
|
||||||
|
|
||||||
|
// Directory kinds (public discovery)
|
||||||
|
export const directoryKinds = ${JSON.stringify(data.directory)};
|
||||||
|
|
||||||
|
// Kind aliases
|
||||||
|
export const kindAliases = ${JSON.stringify(data.aliases, null, 2)};
|
||||||
|
|
||||||
|
// Helper function to get event kind by number
|
||||||
|
export function getEventKind(kindNumber) {
|
||||||
|
return eventKinds.find(k => k.kind === kindNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alias for compatibility
|
||||||
|
export function getKindInfo(kind) {
|
||||||
|
return getEventKind(kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getKindName(kind) {
|
||||||
|
const info = getEventKind(kind);
|
||||||
|
return info ? info.name : \`Kind \${kind}\`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to search event kinds by name or description
|
||||||
|
export function searchEventKinds(query) {
|
||||||
|
const lowerQuery = query.toLowerCase();
|
||||||
|
return eventKinds.filter(k =>
|
||||||
|
k.name.toLowerCase().includes(lowerQuery) ||
|
||||||
|
k.description.toLowerCase().includes(lowerQuery) ||
|
||||||
|
k.kind.toString().includes(query)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to get all event kinds grouped by category
|
||||||
|
export function getEventKindsByCategory() {
|
||||||
|
return {
|
||||||
|
regular: eventKinds.filter(k => k.kind < 10000 && !k.isReplaceable),
|
||||||
|
replaceable: eventKinds.filter(k => k.isReplaceable),
|
||||||
|
ephemeral: eventKinds.filter(k => k.isEphemeral),
|
||||||
|
addressable: eventKinds.filter(k => k.isAddressable)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to create a template event with current timestamp
|
||||||
|
export function createTemplateEvent(kindNumber, userPubkey = null) {
|
||||||
|
const kindInfo = getEventKind(kindNumber);
|
||||||
|
if (!kindInfo) {
|
||||||
|
return {
|
||||||
|
kind: kindNumber,
|
||||||
|
content: "",
|
||||||
|
tags: [],
|
||||||
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
|
pubkey: userPubkey || "<your_pubkey_here>"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...kindInfo.template,
|
||||||
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
|
pubkey: userPubkey || "<your_pubkey_here>"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isReplaceable(kind) {
|
||||||
|
if (kind === 0 || kind === 3) return true;
|
||||||
|
return kind >= ${data.ranges.replaceable.start} && kind < ${data.ranges.replaceable.end};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isEphemeral(kind) {
|
||||||
|
return kind >= ${data.ranges.ephemeral.start} && kind < ${data.ranges.ephemeral.end};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isAddressable(kind) {
|
||||||
|
return kind >= ${data.ranges.parameterized.start} && kind <= ${data.ranges.parameterized.end};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isPrivileged(kind) {
|
||||||
|
return privilegedKinds.includes(kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export kind categories for filtering in UI
|
||||||
|
export const kindCategories = [
|
||||||
|
{ id: "all", name: "All Kinds", filter: () => true },
|
||||||
|
{ id: "regular", name: "Regular Events (0-9999)", filter: k => k.kind < 10000 && !k.isReplaceable },
|
||||||
|
{ id: "replaceable", name: "Replaceable (10000-19999)", filter: k => k.isReplaceable },
|
||||||
|
{ id: "ephemeral", name: "Ephemeral (20000-29999)", filter: k => k.isEphemeral },
|
||||||
|
{ id: "addressable", name: "Addressable (30000-39999)", filter: k => k.isAddressable },
|
||||||
|
{ id: "social", name: "Social", filter: k => [0, 1, 3, 6, 7].includes(k.kind) },
|
||||||
|
{ id: "messaging", name: "Messaging", filter: k => [4, 9, 10, 11, 12, 14, 15, 40, 41, 42].includes(k.kind) },
|
||||||
|
{ id: "lists", name: "Lists", filter: k => k.name.toLowerCase().includes("list") || k.name.toLowerCase().includes("set") },
|
||||||
|
{ id: "marketplace", name: "Marketplace", filter: k => [30017, 30018, 30019, 30020, 1021, 1022, 30402, 30403].includes(k.kind) },
|
||||||
|
{ id: "lightning", name: "Lightning/Zaps", filter: k => [9734, 9735, 9041, 9321, 7374, 7375, 7376].includes(k.kind) },
|
||||||
|
{ id: "media", name: "Media", filter: k => [20, 21, 22, 1063, 1222, 1244].includes(k.kind) },
|
||||||
|
{ id: "git", name: "Git/Code", filter: k => [818, 1337, 1617, 1618, 1619, 1621, 1622, 30617, 30618].includes(k.kind) },
|
||||||
|
{ id: "calendar", name: "Calendar", filter: k => [31922, 31923, 31924, 31925].includes(k.kind) },
|
||||||
|
{ id: "groups", name: "Groups", filter: k => (k.kind >= 9000 && k.kind <= 9030) || (k.kind >= 39000 && k.kind <= 39009) },
|
||||||
|
];
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
try {
|
||||||
|
const data = await fetchKinds();
|
||||||
|
const kinds = generateEventKinds(data);
|
||||||
|
const js = generateJS(kinds, data);
|
||||||
|
|
||||||
|
// Write to src/eventKinds.js
|
||||||
|
const fs = await import('fs');
|
||||||
|
const path = await import('path');
|
||||||
|
const outPath = path.join(import.meta.dirname, '..', 'src', 'eventKinds.js');
|
||||||
|
|
||||||
|
fs.writeFileSync(outPath, js);
|
||||||
|
console.log(`Generated ${outPath} with ${kinds.length} kinds`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user