Skip to content

Commit

Permalink
Fix Melancholy Doorbell, Support Petnames
Browse files Browse the repository at this point in the history
- Petnames implemented following Vitor's NIP-81 proposal in nostr-protocol/nips#761
- Documented use of 30311 and 30382 kinds in data types page
- Added more known supporters to about page
- Set volume of some doorbell sounds to half
- Added doorbell preview
  • Loading branch information
vic committed May 6, 2024
1 parent f8efba9 commit c5b3c26
Show file tree
Hide file tree
Showing 11 changed files with 399 additions and 26 deletions.
1 change: 1 addition & 0 deletions pantry/nostr/nostr.js
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,7 @@ const publishLiveActivity = async (roomId, dtt, roomInfo, userInfo, status) => {
["title", `${title}`],
["summary", `${summary}`],
["image", `${imageURI}`], // uses slide if active, else logo, else default image
["service", roomUrl],
["streaming", `${roomUrl}`],
["starts", `${Math.floor(dtt / 1000)}`], // starts and ends needs to be in seconds, not milliseconds
["ends", `${Math.floor(et / 1000)}`],
Expand Down
1 change: 1 addition & 0 deletions ui/lib/doorbell.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ function doorbell(d, myPeerId, roomId) {
dbe = Math.floor(dbe);
let dbs = document.getElementById("doorbellsound" + String(dbe));
if(dbs == undefined) return;
dbs.volume = .5;
dbs.play();
// Mark time last played
sessionStorage.setItem(keyDoorbellTime, Math.floor(Date.now() / 1000));
Expand Down
256 changes: 254 additions & 2 deletions ui/nostr/nostr.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export async function signInExtension(
if (!metadata) {
await updateInfo({identities});
} else {
let name = metadata.name;
let name = metadata.display_name || metadata.name;
let avatar = metadata.picture;
await updateInfo({
name,
Expand Down Expand Up @@ -264,8 +264,10 @@ export async function getUserMetadata(pubkey, id) {
(event, afterEose, url) => {
clearTimeout(timeoutRelays);
userMetadata.push(JSON.parse(event.content));
let username = userMetadata[0]?.display_name || '';
if (username.length == 0) username = userMetadata[0]?.name || '';
const userInfo = {
name: userMetadata[0]?.name, // (userMetadata[0].display_name === '' ? (userMetadata[0].name === '' ? null : userMetadata[0].name) : userMetadata[0].display_name),
name: username,
id: id,
picture: userMetadata[0]?.picture,
npub: npub,
Expand Down Expand Up @@ -1032,4 +1034,254 @@ export function getNpubFromInfo(info) {
return ident.id;
}
return undefined;
}

export async function loadPetnames() {
if(window.DEBUG) console.log('in loadPetnames');
return new Promise(async(res, rej) => {
//const pool = new RelayPool();
try {
let events = [];
if(Window.DEBUG) console.log('loadPetnames: getDefaultOutboxRelays');
const defaultRelays = getDefaultOutboxRelays();
if(Window.DEBUG) console.log('loadPetnames: getPublicKey');
const myPubkey = await window.nostr.getPublicKey();
if(Window.DEBUG) console.log('loadPetnames: outbox relays');
const userRelays = getCachedOutboxRelaysByPubkey(myPubkey);
let myOutboxRelays = [];
if (userRelays?.length == 0) {
const myNpub = nip19.npubEncode(myPubkey);
myOutboxRelays = await getOutboxRelays(myPubkey);
updateCacheOutboxRelays(myOutboxRelays, myNpub);
}
const relaysToUse = unique([...myOutboxRelays, ...userRelays, ...defaultRelays]);
const timestamp = Math.floor(Date.now() / 1000);
const filter = {kinds:[30382],authors: [myPubkey]}
const filters = [filter];
if(Window.DEBUG) console.log('loadPetnames: before setTimeout');
setTimeout(() => {
for (let event of events) {
let targetPubkey = '';
let targetPetname = '';
if(Window.DEBUG) console.log('loadPetnames: checking tags');
for (let tag of event.tags) {
if (tag.length > 1) {
let k = tag[0];
let v = tag[1];
if (k == 'expiration') {
try {
let expirationTime = parseInt(v);
if (expirationTime < timestamp) {
continue;
}
} catch(error) { continue; }
}
if (k == 'd') targetPubkey = v;
if (k == 'petname') targetPetname = v;
}
}
let enc = event.content;
console.log('loadPetnames checking encrypted content');
if (enc != undefined && enc.length > 0 && window.nostr.nip44) {
let dec = ''; // await window.nostr.nip44.decrypt(myPubkey, enc);
(async () => {let response = await window.nostr.nip44.decrypt(myPubkey, enc); dec = response})();
if (dec != undefined && dec.length > 0) {
let dectags = JSON.parse(dec);
for (let tag of dectags) {
if (tag.length > 1) {
let k = tag[0];
let v = tag[1];
if (k == 'petname') targetPetname = v;
}
}
}
}
if (targetPubkey == '') continue;
if (targetPetname == '') continue;
let targetNpub = nip19.npubEncode(targetPubkey);
localStorage.setItem(`${targetNpub}.petname`, targetPetname);
}
//pool.close();
res(true);
}, 3000);
pool.subscribe(
filters,
relaysToUse,
(event, onEose, url) => { events.push(event); },
undefined,
undefined,
{ unsubscribeOnEose: true, allowDuplicateEvents: false, allowOlderEvents: false }
);
} catch (error) {
console.log('There was an error when loading petnames: ', error);
//pool.close();
rej(undefined);
}
});
}

export function getRelationshipPetname(userNpub, userDisplayName) {
if (userNpub == undefined) return userDisplayName;
let petnametime = localStorage.getItem(`petnames.timechecked`);
let fetchit = (petnametime == undefined || petnametime < (Date.now() - (24*60*60*1000)));
if (fetchit) {
localStorage.setItem('petnames.timechecked', Date.now());
let petnames = undefined;
(async () => {let response = await loadPetnames(); petnames = response})();
}
let petname = localStorage.getItem(`${userNpub}.petname`);
if (petname != undefined) {
return petname
}
return userDisplayName;
}

export async function getRelationshipForNpub(userNpub) {
if(window.DEBUG) console.log('in getPetnameForNpub');
return new Promise(async(res, rej) => {
//const pool = new RelayPool();
try {
let events = [];
const defaultRelays = getDefaultOutboxRelays();
const myPubkey = await window.nostr.getPublicKey();
const userRelays = getCachedOutboxRelaysByPubkey(myPubkey);
let myOutboxRelays = [];
if (userRelays?.length == 0) {
const myNpub = nip19.npubEncode(myPubkey);
myOutboxRelays = await getOutboxRelays(myPubkey);
updateCacheOutboxRelays(myOutboxRelays, myNpub);
}
const relaysToUse = unique([...myOutboxRelays, ...userRelays, ...defaultRelays]);
const timestamp = Math.floor(Date.now() / 1000);
let userPubkey = nip19.decode(userNpub).data;
const filter = {kinds:[30382],authors: [myPubkey]}
const filters = [filter];
if(Window.DEBUG) console.log('loadPetnames: before setTimeout');
setTimeout(() => {
let validEvents = [];
for (let event of events) {
let targetPubkey = undefined;
for (let tag of event.tags) {
if (tag.length > 1) {
let k = tag[0];
let v = tag[1];
if (k == 'expiration') {
try {
let expirationTime = parseInt(v);
if (expirationTime < timestamp) {
continue;
}
} catch(error) { continue; }
}
if (k == 'd') {
if (v == userPubkey) targetPubkey = v;
break;
}
}
}
if (targetPubkey == undefined) continue;
res(event);
}
//pool.close();
res(false);
}, 3000);
pool.subscribe(
filters,
relaysToUse,
(event, onEose, url) => { events.push(event); },
undefined,
undefined,
{ unsubscribeOnEose: true, allowDuplicateEvents: false, allowOlderEvents: false }
);
} catch (error) {
console.log('There was an error when loading petnames: ', error);
//pool.close();
rej(undefined);
}
});
}

export async function updatePetname(userNpub, petname) {
if (!window.nostr) return;
let useEncryption = true;
// Need identifier
let userPubkey = nip19.decode(userNpub).data;
// Fetch latest record
let existingRelationship = await getRelationshipForNpub(userNpub);
let isNew = (existingRelationship == undefined || !existingRelationship);
let newRelationship = {id:null,pubkey:null,sig:null,kind:30382,content:"",tags:[["d",userPubkey]],created_at:Math.floor(Date.now() / 1000)}
if (!isNew) {
newRelationship.content = existingRelationship.content;
newRelationship.tags = existingRelationship.tags;
}
let petnameFound = false;
let isCleartext = false;
let isEncrypted = false;
// Look for petname in clear tags
for (let tag of newRelationship.tags) {
if (tag.length < 2) continue;
let k = tag[0];
if (k == 'petname') {
isCleartext = true;
petnameFound = true;
tag[1] = petname;
break;
}
}
if (useEncryption && window.nostr.nip44) {
// Look for petname in encrypted content
const myPubkey = await window.nostr.getPublicKey();
let enc = newRelationship.content;
let dectags = [];
if (enc != undefined && enc.length > 0 && window.nostr.nip44) {
let dec = await window.nostr.nip44.decrypt(myPubkey, enc);
if (dec != undefined && dec.length > 0) {
dectags = JSON.parse(dec);
for (let tag of dectags) {
if (tag.length < 2) continue;
let k = tag[0];
if (k == 'petname') {
isEncrypted = true;
petnameFound = true;
tag[1] = petname;
break;
}
}
}
}
// If no petname found, add to dec tags
if (!petnameFound) {
isEncrypted = true;
dectags.push(["petname",petname]);
}
// Re-Encrypt the content
let dec = JSON.stringify(dectags);
enc = await window.nostr.nip44.encrypt(myPubkey, dec);
newRelationship.content = enc;
} else {
// If no petname found, add to tags
if (!petnameFound) {
isCleartext = true;
newRelationship.tags.push(["petname",petname]);
}
}

// Sign and send newRelationship
if(window.DEBUG) console.log(newRelationship);
const EventSigned = await window.nostr.signEvent(newRelationship);
await sleep(100);
if(window.DEBUG) console.log(EventSigned);
const defaultRelays = getDefaultOutboxRelays();
const myPubkey = await window.nostr.getPublicKey();
const userRelays = getCachedOutboxRelaysByPubkey(myPubkey);
let myOutboxRelays = [];
if (userRelays?.length == 0) {
const myNpub = nip19.npubEncode(myPubkey);
myOutboxRelays = await getOutboxRelays(myPubkey);
updateCacheOutboxRelays(myOutboxRelays, myNpub);
}
const relaysToUse = unique([...myOutboxRelays, ...userRelays, ...defaultRelays]);
//const pool = new RelayPool();
pool.publish(EventSigned, relaysToUse);
const sleeping = await sleep(100);
}
2 changes: 1 addition & 1 deletion ui/pages/About.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default function About() {
<h2>Contributors</h2>
<p>Developers: Diego, Vic</p>
<p>Art: Kajoozie, Noshole, Puzzles</p>
<p>Known Financial Supporters: Bevo, Frank, Kajoozie, New1, Noshole, Propaganda Daily, Ralf, Sai, Séimí, Tigs, Vic</p>
<p>Known Financial Supporters: Bevo, Frank, Kajoozie, Marie, New1, Noshole, Oobiyou, Propaganda Daily, Puzzles, Ralf, Rex, Sai, Séimí, Tekkadan, Tigs, Vic</p>

<h2>Development</h2>
<p><a href="/datatypes" style={{textDecoration: 'underline'}}>Corny Chat Data Types</a> - Detailed information about the
Expand Down
16 changes: 16 additions & 0 deletions ui/pages/DataTypes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export default function DataTypes() {
<li><a href="#kind9734zaprequest">Kind 9734 - Zap Request</a></li>
<li><a href="#kind10002relaylist">Kind 10002 - Relay List Metadata</a></li>
<li><a href="#kind30311liveactivities">Kind 30311 - Live Activities</a></li>
<li><a href="#kind30382relationships">Kind 30382 - Relationships</a></li>
<li><a href="#kind30388slideset">Kind 30388 - Slide Set</a></li>
<li><a href="#kind31388linkset">Kind 31388 - Link Set</a></li>
<li><a href="#kind31923scheduledevent">Kind 31923 - Scheduled Event</a></li>
Expand Down Expand Up @@ -154,6 +155,10 @@ export default function DataTypes() {
</p>

<a name="kind30311liveactivities"></a><h2 style={{backgroundColor: '#ff0000'}}>Kind 30311 - Live Activities</h2>
<p>Room Owners can denote a room setting to announce the room as a live activity. At this time, this only publishes
the key state of the room including the current slideshow image or room logo url, the room's title, description,
and room participants. A combined audio feed may be made available for streaming externally at a later date.
</p>
<p>
Corny Chat promotes integrations with other like kind applications. Corny Chat is a live audio space, and
lists active rooms on its landing page. In an effort to promote greater discovery through the Nostr universe,
Expand All @@ -177,6 +182,17 @@ export default function DataTypes() {
</li>
</ul>

<a name="kind30382relationships"></a><h2 style={{backgroundColor: '#ff0000'}}>Kind 30382 - Relationships</h2>
<span style={{backgroundColor:'#ffff00',color:'#000000'}}>Experimental</span>
<p>
Relationships are defind in <a href="https://github.com/vitorpamplona/nips/blob/relationship-status/81.md">NIP-81</a> as
replaceable event using kind 30382. Corny Chat uses these relationships to allow users to set petnames/nicknames for
users based on their nostr pubkey. Any petnames defined, either in cleartext tags, or via nip44 encrypted content
tags will be used as aliases in place of the target account name when viewing room avatars, chat avatars, profile, and
user lists within room settings. A petname can be set by viewing a user's profile, and clicking on the name for edit
functionality. At this time, the ability to clear/delete a petname is not provided.
</p>

<a name="kind30388slideset"></a><h2 style={{backgroundColor: '#ff0000'}}>Kind 30388 - Slide Sets</h2>
<p>
A slide set is a collection of URLs that represent images which can be displayed within a Corny Chat room. Owners
Expand Down
5 changes: 4 additions & 1 deletion ui/views/Avatar.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, {useEffect, useState} from 'react';
import {avatarUrl, displayName} from '../lib/avatar';
import {isValidNostr, getNpubFromInfo} from '../nostr/nostr';
import {isValidNostr, getNpubFromInfo, getRelationshipPetname} from '../nostr/nostr';
import animateEmoji from '../lib/animate-emoji';
import {useMqParser} from '../lib/tailwind-mqp';
import {colors, isDark} from '../lib/theme';
Expand Down Expand Up @@ -105,6 +105,9 @@ function Avatar({
if (userDisplayName.length == 0) {
userDisplayName = displayName(info, room);
}
if (userNpub != undefined) {
userDisplayName = getRelationshipPetname(userNpub, userDisplayName);
}
const nameSymbols = [
{"name":"Marie","symbol":"🌹","title":"Valentine"},
{"npub":"npub1el3mgvtdjpfntdkwq446pmprpdv85v6rs85zh7dq9gvy7tgx37xs2kl27r","symbol":"🌹","title":"Valentine"},
Expand Down
Loading

0 comments on commit c5b3c26

Please sign in to comment.