/*
* @title nostr bookmark
* @description 現在のWebページをNostrでブックマーク
* @include http://*
* @license CC0 1.0
* @require
*/
(() => {
const nostrToolsUrl = 'https://unpkg.com/nostr-tools/lib/nostr.bundle.js';
const profileRelay = 'wss://directory.yabu.me/';
const now = Math.floor(Date.now() / 1000);
const getPubkey = async () => {
let pubkey;
if (window.nostr?.getPublicKey) {
try {
pubkey = await window.nostr.getPublicKey();
} catch (error) {
console.warn(error);
}
}
return pubkey;
};
const getReplaceableEvent = (pool, relays, filter) => {
return new Promise((resolve) => {
let event;
const sub = pool.subscribe(relays, filter, {
onevent(ev) {
if (event === undefined || event.created_at < ev.created_at) {
event = ev;
}
},
oneose() {
sub.close();
resolve(event);
}
});
});
};
const getRelaysToUseFromKind10002Event = (event) => {
const newRelays = {};
for (const tag of event?.tags.filter(
(tag) => tag.length >= 2 && tag[0] === 'r' && URL.canParse(tag[1])
) ?? []) {
const url = window.NostrTools.utils.normalizeURL(tag[1]);
const isRead = tag.length === 2 || tag[2] === 'read';
const isWrite = tag.length === 2 || tag[2] === 'write';
if (newRelays[url] === undefined) {
newRelays[url] = {
read: isRead,
write: isWrite
};
} else {
if (isRead) {
newRelays[url].read = true;
}
if (isWrite) {
newRelays[url].write = true;
}
}
}
return newRelays;
};
const getRelays = (relayRecord, relayType) => {
return Array.from(
new Set(
Object.entries(relayRecord)
.filter(([_, obj]) => obj[relayType])
.map(([relay, _]) => relay)
)
);
};
const main = async () => {
const pubkey = await getPubkey();
if (pubkey === undefined) {
return;
}
const url = new URL(location.href);
url.search = '';
url.hash = '';
const identifier = url.href
.replace(/#$/, '')
.replace(/\?$/, '')
.replace(/^https?:\/\//, '');
const pool = new window.NostrTools.SimplePool();
const event10002 = await getReplaceableEvent(pool, [profileRelay], {
kinds: [10002],
authors: [pubkey],
until: now
});
const rr = getRelaysToUseFromKind10002Event(event10002);
const relaysToRead = getRelays(rr, 'read');
const relaysToWrite = getRelays(rr, 'write');
const kind = 39701;
const event39701 = await getReplaceableEvent(pool, relaysToRead, {
kinds: [kind],
authors: [pubkey],
'#d': [identifier],
until: now
});
const content = prompt('コメントを入力してください。', event39701?.content);
if (content === null) {
return;
}
const tags = event39701?.tags ?? [
['d', identifier],
['published_at', String(now)],
['title', document.title]
];
const eventTemplate = {
content,
kind,
tags,
created_at: now
};
const signedEvent = await window.nostr.signEvent(eventTemplate);
if (
!window.confirm(`以下のリレーに送信します。よろしいですか?\n${relaysToWrite.join('\n')}`)
) {
return;
}
await Promise.any(pool.publish(relaysToWrite, signedEvent));
if (!window.confirm('送信完了しました。ブックマークページを開きますか?')) {
return;
}
const naddr = window.NostrTools.nip19.naddrEncode({
identifier,
kind,
pubkey,
relays: relaysToWrite
});
const urlToOpen = `https://nostr-web-bookmark-trend.vercel.app/${naddr})}`;
location.href = urlToOpen;
};
affixScriptToHead(nostrToolsUrl, main);
//https://developer.mozilla.org/ja/docs/Web/API/HTMLScriptElement のコピペ
function loadError(oError) {
throw new URIError(`スクリプト ${oError.target.src} は正しく読み込まれませんでした。`);
}
function affixScriptToHead(url, onloadFunction) {
const newScript = document.createElement('script');
newScript.onerror = loadError;
if (onloadFunction) {
newScript.onload = onloadFunction;
}
document.head.appendChild(newScript);
newScript.src = url;
}
})();