Browse Source

make npub display more complete

master
Silberengel 1 week ago
parent
commit
02b7530e3b
  1. 43
      README.md
  2. 50
      src/main.ts
  3. 49
      src/nostr/profileFetcher.ts
  4. 101
      src/ui/settingsTab.ts

43
README.md

@ -33,11 +33,44 @@ An Obsidian plugin for creating, editing, and publishing Nostr document events d @@ -33,11 +33,44 @@ An Obsidian plugin for creating, editing, and publishing Nostr document events d
## Setup
1. Set your Nostr private key in the environment variable `SCRIPTORIUM_OBSIDIAN_KEY`:
- Format: `nsec1...` (bech32) or 64-character hex string
2. Open Obsidian settings → Scriptorium Nostr
3. Click "Refresh from Env" to load your private key
4. Click "Fetch" to get your relay list from Nostr relays
### Private Key Configuration
You have three options to set your private key:
**Option 1: Manual Entry (Easiest)**
1. Open Obsidian settings → Scriptorium Nostr
2. Enter your private key in the password field
3. Click "Set Key"
**Option 2: File in Vault (Most Reliable)**
1. Create a file named `.scriptorium_key` in your vault root
2. Put your private key on a single line (nsec1... or 64-char hex)
3. Open plugin settings and click "Refresh"
**Option 3: Environment Variable**
1. Set `SCRIPTORIUM_OBSIDIAN_KEY` in your terminal:
```bash
export SCRIPTORIUM_OBSIDIAN_KEY="nsec1..."
```
2. **Important:** Launch Obsidian from the same terminal:
```bash
obsidian
```
(If Obsidian is already running, close it and restart from the terminal)
3. Open plugin settings → Scriptorium Nostr
4. Click "Refresh" to load your private key
**Note:** Environment variables are only available to processes launched from the terminal where they were set. If you launch Obsidian from a desktop shortcut or application menu, it won't have access to the environment variable. You must launch Obsidian from the terminal where you set the variable.
See [ENV_SETUP.md](ENV_SETUP.md) for detailed instructions on setting environment variables.
**Key Format:** `nsec1...` (bech32) or 64-character hex string
### Relay Configuration
1. Open Obsidian settings → Scriptorium Nostr
2. Click "Fetch" to get your relay list from Nostr relays
3. The plugin will automatically fetch from `wss://profiles.nostr1.com`, `wss://relay.damus.io`, and `wss://thecitadel.nostr1.com`
## Usage

50
src/main.ts

@ -61,21 +61,57 @@ export default class ScriptoriumPlugin extends Plugin { @@ -61,21 +61,57 @@ export default class ScriptoriumPlugin extends Plugin {
await this.saveData(this.settings);
}
async loadPrivateKey() {
// Try to load from environment variable
// Note: In Obsidian, process.env may not be available
// Users should set the key manually in settings or via system environment
async loadPrivateKey(): Promise<boolean> {
// Try multiple methods to load the private key
// Method 1: Try environment variable (may not work in Obsidian's sandbox)
try {
// @ts-ignore - process.env may not be typed in Obsidian context
const envKey = typeof process !== "undefined" && process.env?.SCRIPTORIUM_OBSIDIAN_KEY;
if (typeof process !== "undefined" && process.env?.SCRIPTORIUM_OBSIDIAN_KEY) {
const envKey = process.env.SCRIPTORIUM_OBSIDIAN_KEY.trim();
if (envKey) {
this.settings.privateKey = envKey;
await this.saveSettings();
return true;
}
}
} catch (error) {
// Environment variable access not available
}
// Method 2: Try reading from a file in the vault (.scriptorium_key)
try {
const keyFile = this.app.vault.getAbstractFileByPath(".scriptorium_key");
if (keyFile && keyFile instanceof TFile) {
const keyContent = await this.app.vault.read(keyFile);
const key = keyContent.trim();
if (key && (key.startsWith("nsec1") || /^[0-9a-f]{64}$/i.test(key))) {
this.settings.privateKey = key;
await this.saveSettings();
return true;
}
}
} catch (error) {
// Environment variable access not available, user must set manually
safeConsoleLog("Environment variable access not available, use settings to set private key");
// File doesn't exist or can't be read
}
// Method 3: Try reading from .obsidian/scriptorium_key (hidden file)
try {
const hiddenKeyFile = this.app.vault.getAbstractFileByPath(".obsidian/scriptorium_key");
if (hiddenKeyFile && hiddenKeyFile instanceof TFile) {
const keyContent = await this.app.vault.read(hiddenKeyFile);
const key = keyContent.trim();
if (key && (key.startsWith("nsec1") || /^[0-9a-f]{64}$/i.test(key))) {
this.settings.privateKey = key;
await this.saveSettings();
return true;
}
}
} catch (error) {
// File doesn't exist or can't be read
}
return false;
}
private async getCurrentFile(): Promise<TFile | null> {

49
src/nostr/profileFetcher.ts

@ -7,6 +7,8 @@ import { safeConsoleError } from "../utils/security"; @@ -7,6 +7,8 @@ import { safeConsoleError } from "../utils/security";
export interface UserProfile {
name?: string;
display_name?: string;
username?: string;
nip05?: string; // NIP-05 identifier (handle)
about?: string;
picture?: string;
}
@ -17,19 +19,29 @@ export interface UserProfile { @@ -17,19 +19,29 @@ export interface UserProfile {
export async function fetchUserProfile(
pubkey: string,
relayUrls: string[],
timeout: number = 5000
timeout: number = 10000
): Promise<UserProfile | null> {
for (const relayUrl of relayUrls) {
try {
const profile = await fetchProfileFromRelay(relayUrl, pubkey, timeout);
if (profile) {
return profile;
if (relayUrls.length === 0) {
return null;
}
} catch (error) {
// Try all relays in parallel for faster response
const promises = relayUrls.map(relayUrl =>
fetchProfileFromRelay(relayUrl, pubkey, timeout).catch(error => {
safeConsoleError(`Error fetching profile from ${relayUrl}:`, error);
continue;
return null;
})
);
const results = await Promise.all(promises);
// Return first successful result
for (const profile of results) {
if (profile) {
return profile;
}
}
return null;
}
@ -54,39 +66,56 @@ async function fetchProfileFromRelay( @@ -54,39 +66,56 @@ async function fetchProfileFromRelay(
relay = new Relay(relayUrl);
await relay.connect();
let profileReceived = false;
const sub = relay.subscribe(
[
{
kinds: [0],
authors: [pubkey],
limit: 1,
},
],
{
onevent: (event) => {
if (profileReceived) return; // Only process first event
profileReceived = true;
clearTimeout(timer);
sub.close();
relay?.close();
try {
const profile = JSON.parse(event.content) as UserProfile;
// Validate that we got some profile data
if (profile && (profile.name || profile.display_name || profile.nip05 || profile.username)) {
resolve(profile);
} else {
resolve(null);
}
} catch (error) {
resolve(null);
}
},
oneose: () => {
if (!profileReceived) {
clearTimeout(timer);
sub.close();
relay?.close();
resolve(null);
}
},
}
);
// Wait for response
// Wait for response with timeout
setTimeout(() => {
if (!profileReceived) {
sub.close();
if (relay) {
relay.close();
}
}, timeout - 100);
resolve(null);
}
}, timeout);
} catch (error) {
clearTimeout(timer);
if (relay) {

101
src/ui/settingsTab.ts

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import { App, PluginSettingTab, Setting } from "obsidian";
import { App, PluginSettingTab, Setting, Notice } from "obsidian";
import ScriptoriumPlugin from "../main";
import { EventKind } from "../types";
import { fetchRelayList, addTheCitadelIfMissing, includesTheCitadel, getReadRelays } from "../relayManager";
@ -23,42 +23,94 @@ export class ScriptoriumSettingTab extends PluginSettingTab { @@ -23,42 +23,94 @@ export class ScriptoriumSettingTab extends PluginSettingTab {
containerEl.createEl("h2", { text: "Scriptorium Nostr Settings" });
// User Identity (npub and handle)
// User Identity (npub and handle) or Private Key Input
if (this.plugin.settings.privateKey) {
try {
const npub = getNpubFromPrivkey(this.plugin.settings.privateKey);
const pubkey = getPubkeyFromPrivkey(this.plugin.settings.privateKey);
// Fetch profile to get handle/name
let profile: { name?: string; display_name?: string } | null = null;
let profile: { name?: string; display_name?: string; username?: string; nip05?: string } | null = null;
const readRelays = getReadRelays(this.plugin.settings.relayList);
if (readRelays.length > 0) {
profile = await fetchUserProfile(pubkey, readRelays);
}
const displayName = profile?.display_name || profile?.name || "Unknown";
// Priority: nip05 (handle) > display_name > name > username > "Unknown"
const displayName = profile?.nip05 ||
profile?.display_name ||
profile?.name ||
profile?.username ||
"Unknown";
// Build description with what we found
let identityDesc = "Your Nostr public identity";
if (profile) {
const parts: string[] = [];
if (profile.nip05) parts.push(`NIP-05: ${profile.nip05}`);
if (profile.display_name) parts.push(`Display: ${profile.display_name}`);
if (profile.name) parts.push(`Name: ${profile.name}`);
if (parts.length > 0) {
identityDesc += `\n${parts.join(" | ")}`;
}
} else if (readRelays.length > 0) {
identityDesc += "\n(Profile not found on relays - may need to publish kind 0 event)";
} else {
identityDesc += "\n(No read relays configured - fetch relay list first)";
}
new Setting(containerEl)
.setName("Your Identity")
.setDesc("Your Nostr public identity (loaded from SCRIPTORIUM_OBSIDIAN_KEY)")
.setDesc(identityDesc)
.addText((text) => {
text.setValue(`${displayName} (${npub})`)
.setDisabled(true);
})
.addButton((button) => {
button.setButtonText("Refresh from Env")
button.setButtonText("Refresh")
.setCta()
.onClick(async () => {
await this.plugin.loadPrivateKey();
const loaded = await this.plugin.loadPrivateKey();
if (!loaded && !this.plugin.settings.privateKey) {
new Notice("Could not load private key. Please enter it manually below.");
}
await this.display();
});
});
// Allow manual update of private key
new Setting(containerEl)
.setName("Update Private Key")
.setDesc("Manually enter or update your private key (nsec1... or 64-char hex). Leave empty to keep current.")
.addText((text) => {
text.setPlaceholder("nsec1... or hex key")
.setValue("")
.inputEl.type = "password";
})
.addButton((button) => {
button.setButtonText("Update")
.onClick(async () => {
const input = containerEl.querySelector("input[type='password']") as HTMLInputElement;
if (input && input.value.trim()) {
const key = input.value.trim();
if (key.startsWith("nsec1") || /^[0-9a-f]{64}$/i.test(key)) {
this.plugin.settings.privateKey = key;
await this.plugin.saveSettings();
input.value = "";
new Notice("Private key updated successfully");
await this.display();
} else {
new Notice("Invalid key format. Expected nsec1... or 64-char hex string.");
}
}
});
});
} catch (error: any) {
new Setting(containerEl)
.setName("Private Key Status")
.setDesc(`Error: ${error.message}`)
.addButton((button) => {
button.setButtonText("Refresh from Env")
button.setButtonText("Refresh")
.setCta()
.onClick(async () => {
await this.plugin.loadPrivateKey();
@ -69,13 +121,40 @@ export class ScriptoriumSettingTab extends PluginSettingTab { @@ -69,13 +121,40 @@ export class ScriptoriumSettingTab extends PluginSettingTab {
} else {
new Setting(containerEl)
.setName("Private Key")
.setDesc("No private key found. Set SCRIPTORIUM_OBSIDIAN_KEY environment variable.")
.setDesc("Enter your private key manually, or set SCRIPTORIUM_OBSIDIAN_KEY environment variable, or create .scriptorium_key file in vault root.")
.addText((text) => {
text.setPlaceholder("nsec1... or 64-char hex key")
.inputEl.type = "password";
})
.addButton((button) => {
button.setButtonText("Refresh from Env")
button.setButtonText("Set Key")
.setCta()
.onClick(async () => {
await this.plugin.loadPrivateKey();
const input = containerEl.querySelector("input[type='password']") as HTMLInputElement;
if (input && input.value.trim()) {
const key = input.value.trim();
if (key.startsWith("nsec1") || /^[0-9a-f]{64}$/i.test(key)) {
this.plugin.settings.privateKey = key;
await this.plugin.saveSettings();
input.value = "";
new Notice("Private key saved successfully");
await this.display();
} else {
new Notice("Invalid key format. Expected nsec1... or 64-char hex string.");
}
}
});
})
.addButton((button) => {
button.setButtonText("Refresh")
.onClick(async () => {
const loaded = await this.plugin.loadPrivateKey();
if (loaded) {
new Notice("Private key loaded successfully");
await this.display();
} else {
new Notice("Could not load private key. Please enter it manually above.");
}
});
});
}

Loading…
Cancel
Save