feat: nip-38

This commit is contained in:
Kieran 2023-09-18 10:05:36 +01:00
parent a44a4ab69b
commit 950b0dbf4d
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
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-31: Alt tag for unknown events
- [x] NIP-36: Sensitive Content
- [x] NIP-38: User Statuses
- [ ] NIP-40: Expiration Timestamp
- [x] NIP-42: Authentication of clients to relays
- [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
*/
telemetry?: boolean;
/**
* Show badges on profiles
*/
showBadges?: boolean;
/**
* Show user status messages on profiles
*/
showStatus?: boolean;
}
export const DefaultPreferences = {
@ -93,4 +103,6 @@ export const DefaultPreferences = {
defaultZapAmount: 50,
autoZap: false,
telemetry: true,
showBadges: false,
showStatus: true,
} as UserPreferences;

View File

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

View File

@ -207,6 +207,40 @@ const PreferencesPage = () => {
/>
</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-column g8">
<h4>

View File

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