feat: nip-38

This commit is contained in:
2023-09-18 10:05:36 +01:00
parent a44a4ab69b
commit 950b0dbf4d
6 changed files with 114 additions and 12 deletions

View File

@ -30,6 +30,7 @@ Snort supports the following NIP's:
- [x] NIP-30: Custom Emoji - [x] NIP-30: Custom Emoji
- [x] NIP-31: Alt tag for unknown events - [x] NIP-31: Alt tag for unknown events
- [x] NIP-36: Sensitive Content - [x] NIP-36: Sensitive Content
- [x] NIP-38: User Statuses
- [ ] NIP-40: Expiration Timestamp - [ ] NIP-40: Expiration Timestamp
- [x] NIP-42: Authentication of clients to relays - [x] NIP-42: Authentication of clients to relays
- [x] NIP-44: Versioned encryption - [x] NIP-44: Versioned encryption

View File

@ -0,0 +1,27 @@
import { EventKind, NoteCollection, RequestBuilder } from "@snort/system";
import { useRequestBuilder } from "@snort/system-react";
import { findTag } from "SnortUtils";
import { useMemo } from "react";
export function useStatusFeed(id?: string, leaveOpen = false) {
const sub = useMemo(() => {
if(!id) return null;
const rb = new RequestBuilder(`statud:${id}`);
rb.withOptions({leaveOpen});
rb.withFilter()
.kinds([30315 as EventKind])
.authors([id]);
return rb;
}, [id]);
const status = useRequestBuilder(NoteCollection, sub);
const general = status.data?.find(a => findTag(a, "d") === "general");
const music = status.data?.find(a => findTag(a, "d") === "music");
return {
general, music
}
}

View File

@ -76,6 +76,16 @@ export interface UserPreferences {
* Collect usage metrics * Collect usage metrics
*/ */
telemetry?: boolean; telemetry?: boolean;
/**
* Show badges on profiles
*/
showBadges?: boolean;
/**
* Show user status messages on profiles
*/
showStatus?: boolean;
} }
export const DefaultPreferences = { export const DefaultPreferences = {
@ -93,4 +103,6 @@ export const DefaultPreferences = {
defaultZapAmount: 50, defaultZapAmount: 50,
autoZap: false, autoZap: false,
telemetry: true, telemetry: true,
showBadges: false,
showStatus: true,
} as UserPreferences; } as UserPreferences;

View File

@ -15,7 +15,7 @@ import {
import { LNURL } from "@snort/shared"; import { LNURL } from "@snort/shared";
import { useUserProfile } from "@snort/system-react"; import { useUserProfile } from "@snort/system-react";
import { getReactions, unwrap } from "SnortUtils"; import { findTag, getReactions, unwrap } from "SnortUtils";
import { formatShort } from "Number"; import { formatShort } from "Number";
import Note from "Element/Note"; import Note from "Element/Note";
import Bookmarks from "Element/Bookmarks"; import Bookmarks from "Element/Bookmarks";
@ -55,6 +55,7 @@ import { EmailRegex } from "Const";
import { getNip05PubKey } from "Pages/LoginPage"; import { getNip05PubKey } from "Pages/LoginPage";
import useLogin from "Hooks/useLogin"; import useLogin from "Hooks/useLogin";
import { ZapTarget } from "Zapper"; import { ZapTarget } from "Zapper";
import { useStatusFeed } from "Feed/StatusFeed";
import messages from "./messages"; import messages from "./messages";
@ -114,7 +115,8 @@ export default function ProfilePage() {
const navigate = useNavigate(); const navigate = useNavigate();
const [id, setId] = useState<string>(); const [id, setId] = useState<string>();
const user = useUserProfile(id); const user = useUserProfile(id);
const loginPubKey = useLogin().publicKey; const login = useLogin();
const loginPubKey = login.publicKey;
const isMe = loginPubKey === id; const isMe = loginPubKey === id;
const [showLnQr, setShowLnQr] = useState<boolean>(false); const [showLnQr, setShowLnQr] = useState<boolean>(false);
const [showProfileQr, setShowProfileQr] = useState<boolean>(false); const [showProfileQr, setShowProfileQr] = useState<boolean>(false);
@ -128,14 +130,19 @@ export default function ProfilePage() {
// ignored // ignored
} }
})(); })();
const showBadges = login.preferences.showBadges ?? false;
const showStatus = login.preferences.showStatus ?? true;
const website_url = const website_url =
user?.website && !user.website.startsWith("http") ? "https://" + user.website : user?.website || ""; user?.website && !user.website.startsWith("http") ? "https://" + user.website : user?.website || "";
// feeds // feeds
const { blocked } = useModeration(); const { blocked } = useModeration();
const pinned = usePinnedFeed(id); const pinned = usePinnedFeed(id);
const muted = useMutedFeed(id); const muted = useMutedFeed(id);
const badges = useProfileBadges(id); const badges = useProfileBadges(showBadges ? id : undefined);
const follows = useFollowsFeed(id); const follows = useFollowsFeed(id);
const status = useStatusFeed(showStatus ? id : undefined, true);
// tabs // tabs
const ProfileTab = { const ProfileTab = {
Notes: { Notes: {
@ -243,6 +250,23 @@ export default function ProfilePage() {
setTab(ProfileTab.Notes); setTab(ProfileTab.Notes);
}, [params]); }, [params]);
function musicStatus() {
if (!status.music) return;
const link = findTag(status.music, "r");
const cover = findTag(status.music, "cover");
const inner = () => {
return <div className="flex g8">
{cover && <ProxyImg src={cover} size={40} />}
<small>🎵 {unwrap(status.music).content}</small>
</div>
}
if (link) {
return <a href={link} rel="noreferer" target="_blank" className="ext">{inner()}</a>;
}
return inner();
}
function username() { function username() {
return ( return (
<> <>
@ -253,7 +277,10 @@ export default function ProfilePage() {
</h2> </h2>
{user?.nip05 && <Nip05 nip05={user.nip05} pubkey={user.pubkey} />} {user?.nip05 && <Nip05 nip05={user.nip05} pubkey={user.pubkey} />}
</div> </div>
<BadgeList badges={badges} /> {showBadges && <BadgeList badges={badges} />}
{showStatus && <>
{musicStatus()}
</>}
<div className="link-section"> <div className="link-section">
<Copy text={npub} /> <Copy text={npub} />
{links()} {links()}
@ -295,14 +322,14 @@ export default function ProfilePage() {
targets={ targets={
lnurl?.lnurl && id lnurl?.lnurl && id
? [ ? [
{ {
type: "lnurl", type: "lnurl",
value: lnurl?.lnurl, value: lnurl?.lnurl,
weight: 1, weight: 1,
name: user?.display_name || user?.name, name: user?.display_name || user?.name,
zap: { pubkey: id }, zap: { pubkey: id },
} as ZapTarget, } as ZapTarget,
] ]
: undefined : undefined
} }
show={showLnQr} show={showLnQr}

View File

@ -207,6 +207,40 @@ const PreferencesPage = () => {
/> />
</div> </div>
</div> </div>
<div className="flex f-space w-max">
<div className="flex-column g8">
<h4>
<FormattedMessage defaultMessage="Show Badges" />
</h4>
<small>
<FormattedMessage defaultMessage="Show badges on profile pages" />
</small>
</div>
<div>
<input
type="checkbox"
checked={perf.showBadges ?? false}
onChange={e => updatePreferences(login, { ...perf, showBadges: e.target.checked })}
/>
</div>
</div>
<div className="flex f-space w-max">
<div className="flex-column g8">
<h4>
<FormattedMessage defaultMessage="Show Status" />
</h4>
<small>
<FormattedMessage defaultMessage="Show status messages on profile pages" />
</small>
</div>
<div>
<input
type="checkbox"
checked={perf.showStatus ?? true}
onChange={e => updatePreferences(login, { ...perf, showStatus: e.target.checked })}
/>
</div>
</div>
<div className="flex f-space w-max"> <div className="flex f-space w-max">
<div className="flex-column g8"> <div className="flex-column g8">
<h4> <h4>

View File

@ -26,6 +26,7 @@ enum EventKind {
ProfileBadges = 30008, // NIP-58 ProfileBadges = 30008, // NIP-58
LongFormTextNote = 30023, // NIP-23 LongFormTextNote = 30023, // NIP-23
LiveEvent = 30311, // NIP-102 LiveEvent = 30311, // NIP-102
UserStatus = 30315, // NIP-38
ZapstrTrack = 31337, ZapstrTrack = 31337,
SimpleChatMetadata = 39_000, // NIP-29 SimpleChatMetadata = 39_000, // NIP-29
ZapRequest = 9734, // NIP 57 ZapRequest = 9734, // NIP 57