feat: nip-38
This commit is contained in:
@ -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
|
||||||
|
27
packages/app/src/Feed/StatusFeed.ts
Normal file
27
packages/app/src/Feed/StatusFeed.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
@ -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}
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user