Browse Source

Sync from gitrepublic-web monorepo - 2026-02-21 20:35:00

master
Silberengel 3 weeks ago
parent
commit
ddb58e4ed7
  1. 3
      README.md
  2. 114
      scripts/relay/profile-fetcher.js

3
README.md

@ -61,6 +61,9 @@ gitrep-uninstall --keep-env
- **Credential Helper**: Automatic NIP-98 authentication - **Credential Helper**: Automatic NIP-98 authentication
- **Commit Signing**: Automatically sign commits for GitRepublic repos - **Commit Signing**: Automatically sign commits for GitRepublic repos
- **API Access**: Full command-line access to all GitRepublic APIs - **API Access**: Full command-line access to all GitRepublic APIs
- **Profile Fetching**: Fetch user profiles with payment targets (NIP-A3 kind 10133)
- Automatically merges lightning addresses from NIP-01 (lud16) and kind 10133
- Returns payment targets in `payto://` format
## Requirements ## Requirements

114
scripts/relay/profile-fetcher.js

@ -1,12 +1,12 @@
/** /**
* Fetch user profile (kind 0) from Nostr relays * Fetch user profile (kind 0) and payment targets (kind 10133) from Nostr relays
*/ */
import { SimplePool } from 'nostr-tools'; import { SimplePool } from 'nostr-tools';
import { DEFAULT_NOSTR_RELAYS } from '../config.js'; import { DEFAULT_NOSTR_RELAYS } from '../config.js';
/** /**
* Fetch kind 0 profile event from relays * Fetch kind 0 profile event and kind 10133 payment targets from relays
*/ */
export async function fetchProfileFromRelays(pubkey, relays = null) { export async function fetchProfileFromRelays(pubkey, relays = null) {
try { try {
@ -18,44 +18,124 @@ export async function fetchProfileFromRelays(pubkey, relays = null) {
? pubkey.toLowerCase() ? pubkey.toLowerCase()
: pubkey; : pubkey;
const events = await pool.querySync(relayList, [ // Fetch kind 0 (profile) and kind 10133 (payment targets) in parallel
const [profileEvents, paymentEvents] = await Promise.all([
pool.querySync(relayList, [
{ {
kinds: [0], // Kind 0 = profile metadata kinds: [0], // Kind 0 = profile metadata
authors: [pubkeyHex], authors: [pubkeyHex],
limit: 1 limit: 1
} }
]),
pool.querySync(relayList, [
{
kinds: [10133], // Kind 10133 = payment targets (NIP-A3)
authors: [pubkeyHex],
limit: 1
}
])
]); ]);
pool.close(relayList); pool.close(relayList);
if (events.length === 0) {
return null;
}
const event = events[0];
const profile = {}; const profile = {};
let profileEvent = null;
let paymentTargets = [];
// Try to parse JSON content // Process profile event (kind 0)
if (profileEvents.length > 0) {
profileEvent = profileEvents[0];
// Try to parse JSON content (old format)
let profileData = {};
try { try {
const content = JSON.parse(event.content); profileData = JSON.parse(profileEvent.content);
profile.displayName = content.display_name || content.displayName;
profile.name = content.name;
profile.nip05 = content.nip05;
} catch { } catch {
// Invalid JSON, try tags // Invalid JSON, will use tags
} }
// Extract from tags (new format) - prefer tags over JSON
const nameTag = profileEvent.tags.find(t => t[0] === 'name' || t[0] === 'display_name')?.[1];
const aboutTag = profileEvent.tags.find(t => t[0] === 'about')?.[1];
const pictureTag = profileEvent.tags.find(t => t[0] === 'picture' || t[0] === 'avatar')?.[1];
profile.displayName = nameTag || profileData.display_name || profileData.name;
profile.name = profileData.name;
profile.about = aboutTag || profileData.about;
profile.picture = pictureTag || profileData.picture;
// Check tags for nip05 (newer format) // Check tags for nip05 (newer format)
if (!profile.nip05) { const nip05Tag = profileEvent.tags.find((tag) =>
const nip05Tag = event.tags.find((tag) =>
(tag[0] === 'nip05' || tag[0] === 'l') && tag[1] (tag[0] === 'nip05' || tag[0] === 'l') && tag[1]
); );
if (nip05Tag && nip05Tag[1]) { if (nip05Tag && nip05Tag[1]) {
profile.nip05 = nip05Tag[1]; profile.nip05 = nip05Tag[1];
} else if (profileData.nip05) {
profile.nip05 = profileData.nip05;
}
}
// Initialize lightning addresses set for collecting from multiple sources
const lightningAddresses = new Set<string>();
// Extract lightning addresses from NIP-01 (lud16 tag or JSON)
if (profileEvent) {
// From tags (lud16)
const lud16Tags = profileEvent.tags.filter(t => t[0] === 'lud16').map(t => t[1]).filter(Boolean);
lud16Tags.forEach(addr => lightningAddresses.add(addr.toLowerCase()));
// From JSON (lud16 field)
try {
const profileData = JSON.parse(profileEvent.content);
if (profileData.lud16 && typeof profileData.lud16 === 'string') {
lightningAddresses.add(profileData.lud16.toLowerCase());
}
} catch {
// Invalid JSON, ignore
}
}
// Extract lightning addresses from kind 10133
if (paymentEvents.length > 0) {
const paytoTags = paymentEvents[0].tags.filter(t => t[0] === 'payto' && t[1] === 'lightning' && t[2]);
paytoTags.forEach(tag => {
if (tag[2]) {
lightningAddresses.add(tag[2].toLowerCase());
}
});
}
// Build payment targets array - start with lightning addresses
paymentTargets = Array.from(lightningAddresses).map(authority => ({
type: 'lightning',
authority,
payto: `payto://lightning/${authority}`
}));
// Also include other payment types from kind 10133
if (paymentEvents.length > 0) {
const otherPaytoTags = paymentEvents[0].tags.filter(t => t[0] === 'payto' && t[1] && t[1] !== 'lightning' && t[2]);
otherPaytoTags.forEach(tag => {
const type = tag[1]?.toLowerCase() || '';
const authority = tag[2] || '';
if (type && authority) {
// Check if we already have this (for deduplication)
const existing = paymentTargets.find(p => p.type === type && p.authority.toLowerCase() === authority.toLowerCase());
if (!existing) {
paymentTargets.push({
type,
authority,
payto: `payto://${type}/${authority}`
});
}
} }
});
} }
return profile; return {
...profile,
paymentTargets
};
} catch (error) { } catch (error) {
console.warn('Failed to fetch profile from relays:', error); console.warn('Failed to fetch profile from relays:', error);
return null; return null;

Loading…
Cancel
Save