feat: read nip-58 badges #394

Merged
verbiricha merged 19 commits from badges into main 2023-03-09 10:13:11 +00:00
20 changed files with 396 additions and 116 deletions

View File

@ -30,6 +30,7 @@ Snort supports the following NIP's:
- [ ] NIP-42: Authentication of clients to relays
- [x] NIP-50: Search
- [x] NIP-51: Lists
- [x] NIP-58: Badges
- [x] NIP-65: Relay List Metadata
### Running
@ -60,4 +61,4 @@ yarn workspace @snort/app intl-extract
yarn workspace @snort/app intl-compile
```
This will create the source file `packages/app/src/translations/en.json`
This will create the source file `packages/app/src/translations/en.json`

View File

@ -4,6 +4,8 @@
color: var(--highlight);
font-weight: 400;
font-size: var(--font-size);
display: flex;
align-items: center;
}
.back-button svg {

View File

@ -0,0 +1,34 @@
.badge-list {
margin-top: 4px;
display: flex;
align-items: center;
}
.badge-item {
width: 32px;
height: 32px;
object-fit: contain;
cursor: pointer;
}
.badge-item:not(:last-child) {
margin-right: 8px;
}
.badge-info {
margin-left: 12px;
display: flex;
flex-direction: column;
}
.badge-info p {
margin: 0;
}
.badge-info h3 {
margin: 0;
}
.badges-item {
align-items: flex-start;
}

View File

@ -0,0 +1,72 @@
import "./BadgeList.css";
import { useState } from "react";
import { FormattedMessage } from "react-intl";
import { TaggedRawEvent } from "@snort/nostr";
import { ProxyImg } from "Element/ProxyImg";
import Icon from "Icons/Icon";
import Modal from "Element/Modal";
import Username from "Element/Username";
import { findTag } from "Util";
export default function BadgeList({ badges }: { badges: TaggedRawEvent[] }) {
const [showModal, setShowModal] = useState(false);
const badgeMetadata = badges.map(b => {
const thumb = findTag(b, "thumb");
const image = findTag(b, "image");
const name = findTag(b, "name");
const description = findTag(b, "description");
return {
id: b.id,
pubkey: b.pubkey,
name,
description,
thumb: thumb?.length ?? 0 > 0 ? thumb : image,
image,
};
});
return (
<>
<div className="badge-list" onClick={() => setShowModal(!showModal)}>
{badgeMetadata.slice(0, 8).map(({ id, name, thumb }) => (
<ProxyImg alt={name} key={id} className="badge-item" size={64} src={thumb} />
))}
</div>
{showModal && (
<Modal className="reactions-modal" onClose={() => setShowModal(false)}>
<div className="reactions-view">
<div className="close" onClick={() => setShowModal(false)}>
<Icon name="close" />
</div>
<div className="reactions-header">
<h2>
<FormattedMessage defaultMessage="Badges" />
</h2>
</div>
<div className="body">
{badgeMetadata.map(({ id, name, pubkey, description, image }) => {
return (
<div key={id} className="reactions-item badges-item">
<ProxyImg className="reaction-icon" src={image} size={64} alt={name} />
<div className="badge-info">
<h3>{name}</h3>
<p>{description}</p>
<p>
<FormattedMessage
defaultMessage="By: {author}"
values={{ author: <Username pubkey={pubkey} onLinkVisit={() => setShowModal(false)} /> }}
/>
</p>
</div>
</div>
);
})}
</div>
</div>
</Modal>
)}
</>
);
}

View File

@ -2,6 +2,10 @@
min-height: 110px;
}
.note:hover {
cursor: pointer;
}
.note > .header .reply {
font-size: 13px;
color: var(--font-secondary-color);
@ -134,8 +138,7 @@
}
.note > .header img:hover,
.note > .header .name > .reply:hover,
.note .body:hover {
.note > .header .name > .reply:hover {
cursor: pointer;
}

View File

@ -173,9 +173,18 @@ export default function Note(props: NoteProps) {
}
}, [inView, entry, extendable]);
function goToEvent(e: React.MouseEvent, id: u256) {
function goToEvent(e: React.MouseEvent, id: u256, isTargetAllowed: boolean = e.target === e.currentTarget) {
if (!isTargetAllowed) {
return;
}
e.stopPropagation();
navigate(eventLink(id));
// detect cmd key and open in new tab
if (e.metaKey) {
window.open(eventLink(id), "_blank");
} else {
navigate(eventLink(id));
}
}
function replyTag() {
@ -277,7 +286,7 @@ export default function Note(props: NoteProps) {
)}
</div>
)}
<div className="body" onClick={e => goToEvent(e, ev.Id)}>
<div className="body" onClick={e => goToEvent(e, ev.Id, true)}>
{transformBody()}
{translation()}
{options.showReactionsLink && (
@ -310,6 +319,7 @@ export default function Note(props: NoteProps) {
const note = (
<div
className={`${baseClassName}${highlight ? " active " : " "}${extendable && !showMore ? " note-expand" : ""}`}
onClick={e => goToEvent(e, ev.Id)}
ref={ref}>
{content()}
</div>

View File

@ -13,7 +13,7 @@ import Modal from "Element/Modal";
import QrCode from "Element/QrCode";
import Copy from "Element/Copy";
import { LNURL, LNURLError, LNURLErrorCode, LNURLInvoice, LNURLSuccessAction } from "LNURL";
import { debounce } from "Util";
import { chunks, debounce } from "Util";
import messages from "./messages";
import { useWallet } from "Wallet";
@ -37,18 +37,6 @@ export interface SendSatsProps {
author?: HexKey;
}
function chunks<T>(arr: T[], length: number) {
const result = [];
let idx = 0;
let n = arr.length / length;
while (n > 0) {
result.push(arr.slice(idx, idx + length));
idx += length;
n -= 1;
}
return result;
}
export default function SendSats(props: SendSatsProps) {
const onClose = props.onClose || (() => undefined);
const { note, author, target } = props;

View File

@ -3,10 +3,14 @@
height: 1em;
position: relative;
overflow: hidden;
background-color: #dddbdd;
background-color: var(--note-bg);
border-radius: 16px;
}
html.light .skeleton {
background-color: var(--gray-secondary);
}
.skeleton::after {
position: absolute;
top: 0;
@ -17,26 +21,26 @@
background-image: linear-gradient(
90deg,
rgba(255, 255, 255, 0) 0,
rgba(255, 255, 255, 0.2) 20%,
rgba(255, 255, 255, 0.5) 60%,
rgba(255, 255, 255, 0.02) 20%,
rgba(255, 255, 255, 0.05) 60%,
rgba(255, 255, 255, 0)
);
animation: shimmer 2s infinite;
content: "";
}
html.light .skeleton::after {
background-image: linear-gradient(
90deg,
rgba(255, 255, 255, 0) 0,
rgba(255, 255, 255, 0.2) 20%,
rgba(255, 255, 255, 0.5) 60%,
rgba(255, 255, 255, 0)
);
}
@keyframes shimmer {
100% {
transform: translateX(100%);
}
}
@media screen and (prefers-color-scheme: dark) {
.skeleton {
background-color: #50535a;
}
.skeleton::after {
background-image: linear-gradient(90deg, #50535a 0%, #656871 20%, #50535a 40%, #50535a 100%);
}
}

View File

@ -35,7 +35,7 @@ export default function Text({ content, tags, creator }: TextProps) {
.map(f => {
if (typeof f === "string") {
return splitByUrl(f).map(a => {
if (a.startsWith("http")) {
if (a.match(/^https?:\/\//)) {
return <HyperText key={a} link={a} creator={creator} />;
}
return a;

View File

@ -0,0 +1,24 @@
import { MouseEvent } from "react";
import { useNavigate, Link } from "react-router-dom";
import { HexKey } from "@snort/nostr";
import { useUserProfile } from "Hooks/useUserProfile";
import { profileLink } from "Util";
export default function Username({ pubkey, onLinkVisit }: { pubkey: HexKey; onLinkVisit(): void }) {
const user = useUserProfile(pubkey);
const navigate = useNavigate();
function onClick(ev: MouseEvent) {
ev.preventDefault();
onLinkVisit();
navigate(profileLink(pubkey));
}
return user ? (
<Link to={profileLink(pubkey)} onClick={onClick}>
{user.name || pubkey.slice(0, 12)}
</Link>
) : null;
}

View File

@ -9,18 +9,12 @@ import { formatShort } from "Number";
import Text from "Element/Text";
import ProfileImage from "Element/ProfileImage";
import { RootState } from "State/Store";
import { findTag } from "Util";
import { ZapperSpam } from "Const";
import { UserCache } from "State/Users/UserCache";
import messages from "./messages";
function findTag(e: TaggedRawEvent, tag: string) {
const maybeTag = e.tags.find(evTag => {
return evTag[0] === tag;
});
return maybeTag && maybeTag[1];
}
function getInvoice(zap: TaggedRawEvent): InvoiceDetails | undefined {
const bolt11 = findTag(zap, "bolt11");
if (!bolt11) {

View File

@ -0,0 +1,102 @@
import { useMemo } from "react";
import { TaggedRawEvent, EventKind, HexKey, Lists, Subscriptions } from "@snort/nostr";
import useSubscription from "Feed/Subscription";
import { unwrap, findTag, chunks } from "Util";
type BadgeAwards = {
pubkeys: string[];
ds: string[];
};
export default function useProfileBadges(pubkey?: HexKey) {
const sub = useMemo(() => {
if (!pubkey) return null;
const s = new Subscriptions();
s.Id = `badges:${pubkey.slice(0, 12)}`;
s.Kinds = new Set([EventKind.ProfileBadges]);
s.DTags = new Set([Lists.Badges]);
s.Authors = new Set([pubkey]);
return s;
}, [pubkey]);
const profileBadges = useSubscription(sub, { leaveOpen: false, cache: false });
const profile = useMemo(() => {
const sorted = [...profileBadges.store.notes];
sorted.sort((a, b) => b.created_at - a.created_at);
const last = sorted[0];
if (last) {
return chunks(
last.tags.filter(t => t[0] === "a" || t[0] === "e"),
2
).reduce((acc, [a, e]) => {
return {
...acc,
[e[1]]: a[1],
};
}, {});
}
return {};
}, [pubkey, profileBadges.store]);
const { ds, pubkeys } = useMemo(() => {
return Object.values(profile).reduce(
(acc: BadgeAwards, addr) => {
const [, pubkey, d] = (addr as string).split(":");
acc.pubkeys.push(pubkey);
if (d?.length > 0) {
acc.ds.push(d);
}
return acc;
},
{ pubkeys: [], ds: [] } as BadgeAwards
) as BadgeAwards;
}, [profile]);
const awardsSub = useMemo(() => {
const ids = Object.keys(profile);
if (!pubkey || ids.length === 0) return null;
const s = new Subscriptions();
s.Id = `profile_awards:${pubkey.slice(0, 12)}`;
s.Kinds = new Set([EventKind.BadgeAward]);
s.Ids = new Set(ids);
return s;
}, [pubkey, profileBadges.store]);
const awards = useSubscription(awardsSub).store.notes;
const badgesSub = useMemo(() => {
if (!pubkey || pubkeys.length === 0) return null;
const s = new Subscriptions();
s.Id = `profile_badges:${pubkey.slice(0, 12)}`;
s.Kinds = new Set([EventKind.Badge]);
s.DTags = new Set(ds);
s.Authors = new Set(pubkeys);
return s;
}, [pubkey, profile]);
const badges = useSubscription(badgesSub, { leaveOpen: false, cache: false }).store.notes;
const result = useMemo(() => {
return awards
.map((award: TaggedRawEvent) => {
const [, pubkey, d] =
award.tags
.find(t => t[0] === "a")
?.at(1)
?.split(":") ?? [];
const badge = badges.find(b => b.pubkey === pubkey && findTag(b, "d") === d);
return {
award,
badge,
};
})
.filter(
({ award, badge }) =>
badge && award.pubkey === badge.pubkey && award.tags.find(t => t[0] === "p" && t[1] === pubkey)
)
.map(({ badge }) => unwrap(badge));
}, [pubkey, awards, badges]);
return result;
}

View File

@ -75,7 +75,6 @@
.profile .nip05 {
display: flex;
font-size: 16px;
margin: 0 0 12px 0;
}
.profile-wrapper > .avatar-wrapper {
@ -196,6 +195,10 @@
align-items: center;
}
.profile .copy {
margin-top: 12px;
}
.qr-modal .modal-body {
width: unset;
margin-top: -120px;
@ -255,3 +258,18 @@
.profile .nip05 .domain {
display: unset;
}
.badge-card .badge-icon {
width: 48px;
height: 48px;
margin-right: 0.3em;
}
.badge-card .header {
align-items: center;
flex-direction: row;
}
.badge-card .body {
margin-bottom: 0;
}

View File

@ -18,6 +18,7 @@ import usePinnedFeed from "Feed/PinnedFeed";
import useBookmarkFeed from "Feed/BookmarkFeed";
import useFollowersFeed from "Feed/FollowersFeed";
import useFollowsFeed from "Feed/FollowsFeed";
import useProfileBadges from "Feed/BadgesFeed";
import { useUserProfile } from "Hooks/useUserProfile";
import useModeration from "Hooks/useModeration";
import useZapsFeed from "Feed/ZapsFeed";
@ -39,6 +40,7 @@ import { RootState } from "State/Store";
import FollowsYou from "Element/FollowsYou";
import QrCode from "Element/QrCode";
import Modal from "Element/Modal";
import BadgeList from "Element/BadgeList";
import { ProxyImg } from "Element/ProxyImg";
import useHorizontalScroll from "Hooks/useHorizontalScroll";
import messages from "./messages";
@ -86,6 +88,7 @@ export default function ProfilePage() {
const followers = useFollowersFeed(id);
const follows = useFollowsFeed(id);
const muted = useMutedFeed(id);
const badges = useProfileBadges(id);
// tabs
const ProfileTab = {
Notes: { text: formatMessage(messages.Notes), value: NOTES },
@ -126,6 +129,7 @@ export default function ProfilePage() {
<FollowsYou followsMe={follows.includes(loginPubKey ?? "")} />
</h2>
{user?.nip05 && <Nip05 nip05={user.nip05} pubkey={user.pubkey} />}
<BadgeList badges={badges} />
<Copy text={npub} />
{links()}
</div>
@ -256,6 +260,7 @@ export default function ProfilePage() {
{showProfileQr && (
<Modal className="qr-modal" onClose={() => setShowProfileQr(false)}>
<ProfileImage pubkey={id ?? ""} />
<QrCode
data={`nostr:${hexToBech32(NostrPrefix.PublicKey, id)}`}
link={undefined}

View File

@ -389,3 +389,22 @@ export function magnetURIDecode(uri: string): Magnet | undefined {
console.warn("Failed to parse magnet link", e);
}
}
export function chunks<T>(arr: T[], length: number) {
const result = [];
let idx = 0;
let n = arr.length / length;
while (n > 0) {
result.push(arr.slice(idx, idx + length));
idx += length;
n -= 1;
}
return result;
}
export function findTag(e: TaggedRawEvent, tag: string) {
const maybeTag = e.tags.find(evTag => {
return evTag[0] === tag;
});
return maybeTag && maybeTag[1];
}

View File

@ -2,7 +2,7 @@
"+D82kt": "هل أنت متأكد من إعادة النشر: {id}",
"+aZY2h": "تخصيص الومضة",
"+vIQlC": "يرجى التأكد من حفظ كلمة المرور لتتمكن من إدارة المعرّف الخاص بك في المستقبل",
"+vVZ/G": "Connect",
"+vVZ/G": "اتصال",
"/4tOwT": "تخطي",
"/JE/X+": "دعم الحساب",
"/PCavi": "علنية",
@ -15,13 +15,13 @@
"0yO7wF": "{n} ثانية",
"1A7TZk": "ما هو سنورت وكيف يعمل؟",
"1Mo59U": "هل أنت متأكد من حذف هذا المنشور من المنشورات المرجعية؟",
"1c4YST": "Connected to: {node} 🎉",
"1c4YST": "متصل بـ: {node}🎉",
"1nYUGC": "المتابَعون {n}",
"1udzha": "المحادثات",
"2/2yg+": "اضافة",
"25V4l1": "الخلفية",
"2IFGap": "تبرع",
"2LbrkB": "Enter password",
"2LbrkB": "أدخل كلمة المرور",
"2a2YiP": "المنشورات المرجعية {n}",
"2k0Cv+": "الاستهجان ({n})",
"3cc4Ct": "فاتح",
@ -29,7 +29,7 @@
"3t3kok": "{n,plural,=1{{n} منشور جديد}other{{n} منشورات جديدة}}",
"3tVy+Z": "المتابِعون {n}",
"3xCwbZ": "أو",
"3yk8fB": "Wallet",
"3yk8fB": "المحفظة",
"450Fty": "لا أحد",
"47FYwb": "الغاء",
"4IPzdn": "المطورون الأساسيون",
@ -49,7 +49,7 @@
"8E9muH": "استيراد متابعات تويتر (اختياري)",
"8QDesP": "أومض {n} ساتوشي",
"8g2vyB": "الاسم طويل جدا",
"8v1NN+": "Pairing phrase",
"8v1NN+": "عبارة الاقتران",
"9+Ddtu": "التالي",
"9HU8vw": "رد",
"9SvQep": "يتابع {n}",
@ -88,7 +88,7 @@
"FfYsOb": "حدث خطأ!",
"FmXUJg": "متابع لك",
"G/yZLu": "حذف",
"G1BGCg": "Select Wallet",
"G1BGCg": "اختر محفظة",
"GFOoEE": "ملح",
"GL8aXW": "المنشورات المرجعية ({n})",
"Gcn9NQ": "Magnet Link",
@ -103,13 +103,13 @@
"IEwZvs": "هل أنت متأكد من ازالة تثبيت هذا المنشور؟",
"INSqIz": "اسم مستخدم تويتر...",
"IUZC+0": "هذا يعني أنه لا يمكن لأي شخص تعديل المنشورات التي قمت بإنشائها ويمكن للجميع بسهولة التحقق من أن المنشورات التي يقرؤونها تم انشاؤها من حسابك.",
"Iwm6o2": "NIP-05 Shop",
"Iwm6o2": "سوق NIP-05",
"JCIgkj": "اسم المستخدم",
"JHEHCk": "وميض ({n})",
"JkLHGw": "موقع إلكتروني",
"K3r6DQ": "مسح",
"K7AkdL": "عرض",
"KAhAcM": "Enter LNDHub config",
"KAhAcM": "أدخل معلومات التهيئة لـ LNDHub",
"KQvWvD": "تم الحذف",
"KWuDfz": "لقد حفظت مفاتيحي ، استمرار",
"KahimY": "نوع الحدث غير معروف: {kind}",
@ -119,7 +119,7 @@
"M3Oirc": "قوائم التصحيح",
"MBAYRO": "عرض خيار \"نسخ المعرف\" و \"النسخ بصيغة JSON\" في قائمة الخيارات المنسدلة لكل منشور",
"MI2jkA": "غير متاح:",
"MP54GY": "Wallet password",
"MP54GY": "كلمة مرور المحفظة",
"MRp6Ly": "اسم مستخدم تويتر",
"MWTx65": "العرض الافتراضي",
"MzRYWH": "شراء {item}",
@ -139,7 +139,7 @@
"QTdJfH": "إنشاء حساب",
"QawghE": "بإمكانك تغيير اسم المستخدم في أي وقت.",
"QxCuTo": "الفن بواسطة {name}",
"R2OqnW": "Delete Account",
"R2OqnW": "حذف الحساب",
"RDZVQL": "فحص",
"RahCRH": "منتهي الصلاحية",
"RhDAoS": "هل أنت متأكد أنك تريد حذف {id}",
@ -149,7 +149,7 @@
"TpgeGw": "Hex Salt..",
"UQ3pOC": "على نوستر ، كثير من الحسابات يحملون نفس اسم المستخدم. أسم المستخدم يختلف عن عن معرف الحساب. يمكنك الحصول على معرّف فريد في الخطوة التالية.",
"Up5U7K": "حظر",
"VN0+Fz": "Balance: {amount} sats",
"VN0+Fz": "الرصيد: {amount} ساتوشي",
"VOjC1i": "اختر خدمة التحميل التي تريد رفع المرفقات إليها",
"VlJkSk": "تم كتم {n}",
"VnXp8Z": "صورة الحساب ",
@ -163,7 +163,7 @@
"Y31HTH": "ساعد في تمويل تطوير سنورت",
"YDURw6": "رابط الخدمة",
"YXA3AH": "تمكين التفاعل",
"Z4BMCZ": "Enter pairing phrase",
"Z4BMCZ": "أدخل عبارة الاقتران",
"ZKORll": "نشط الآن",
"ZLmyG9": "المساهمون",
"ZUZedV": "تبرع البرق:",
@ -175,10 +175,10 @@
"cPIKU2": "المتابَعون",
"cQfLWb": "URL ..",
"cWx9t8": "كتم الكل",
"cg1VJ2": "Connect Wallet",
"cg1VJ2": "ربط المحفظة",
"cuV2gK": "الاسم مسجل",
"cyR7Kh": "الخلف",
"d6CyG5": "History",
"d6CyG5": "التاريخ",
"d7d0/x": "عنوان البرق",
"dOQCL8": "اسم العرض",
"e7qqly": "تمت قراءة الكل",
@ -214,14 +214,14 @@
"jzgQ2z": "التفاعل {n}",
"k2veDA": "يكتب",
"k7sKNy": "تساعد خدمة التوثيق NIP-05 الخاصة بنا في دعم تطوير هذا الموقع والحصول على شارة خاصة لامعة على موقعنا!",
"kaaf1E": "now",
"kaaf1E": "الآن",
"lCILNz": "اشتر الآن",
"lD3+8a": "دفع",
"lTbT3s": "Wallet password",
"lTbT3s": "كلمة مرور المحفظة",
"lgg1KN": "صفحة الحساب",
"ll3xBp": "خدمة وكيل الصور",
"lnaT9F": "المتابَعون {n}",
"lsNFM1": "Click to load content from {link}",
"lsNFM1": "انقر لتحميل المحتوى من {link}",
"lvlPhZ": "دفع البرقية",
"mH91FY": "سيحصل كل مساهم على نسبة مئوية من جميع التبرعات وأوامر NIP-05 ، يمكنك رؤية المبالغ المقسمة أدناه",
"mKAr6h": "تابع الكل",
@ -241,14 +241,14 @@
"oxCa4R": "يساعد الحصول على معرّف في تأكيد هويتك للأشخاص الذين يعرفونك. يمكن أن يكون لدى العديد من الأشخاص اسم مستخدمjack ، ولكن لا يوجد سوى jack@cash.app.",
"puLNUJ": "تثبيت",
"pzTOmv": "المتابِعون",
"qDwvZ4": "Unknown error",
"qDwvZ4": "خطأ غير معروف",
"qMx1sA": "قيمة الومضة الافتراضية",
"qUJTsT": "محظور",
"qdGuQo": "مفتاحك الخاص هو (لا تشارك هذا مع أي شخص)",
"qkvYUb": "أضف إلى الملف الشخصي",
"qmJ8kD": "فشلت الترجمة",
"r3C4x/": "برنامج",
"r5srDR": "Enter wallet password",
"r5srDR": "أدخل كلمة مرور المحفظة",
"rT14Ow": "إضافة موصّلات",
"reJ6SM": "يوصى باستخدام أحد ملحقات المستعرض التالية إذا كنت تستخدم جهاز كمبيوتر لتأمين مفتاحك:",
"rfuMjE": "(افتراضي)",
@ -265,22 +265,22 @@
"usAvMr": "تعديل الملف الشخصي",
"ut+2Cd": "احصل على معرف الشريك",
"vOKedj": "{n,plural,=1{و{n} آخر}other{و{n} آخرون}}",
"vU71Ez": "Paying with {wallet}",
"vU71Ez": "الدفع باستخدام {wallet}",
"vZ4quW": "NIP-05 هو أحد طرق التحقق المستندة إلى DNS والتي تساعد في التحقق من هويتك كمستخدم حقيقي.",
"vrTOHJ": "{amount} sats",
"vrTOHJ": "{amount} ساتوشي",
"wEQDC6": "تحرير",
"wLtRCF": "مفتاحك",
"wWLwvh": "هوية مخفية",
"wih7iJ": "الاسم محظور",
"wqyN/i": "اكتشف المزيد من المعلومات حول {service} على {link}",
"wtLjP6": "نسخ المعرف",
"wvFw6Y": "Hey, it looks like you dont have a NIP-05 handle yet, you should get one! Check out {link}",
"wvFw6Y": "مرحبا، يبدو أنك لا تملك معرف NIP-05 حتى الآن، يمكنك الحصول على معرف فريد! استعرض {link}",
"x82IOl": "كتم",
"xIoGG9": "اذهب إلى",
"xJ9n2N": "مفتاحك العام",
"xKdNPm": "ارسال",
"xKflGN": "يتابع {username} على نوستر",
"xQtL3v": "Unlock",
"xQtL3v": "فتح القفل",
"xbVgIm": "تحميل الوسائط تلقائيًا",
"xmcVZ0": "البحث",
"y1Z3or": "اللغة",

View File

@ -1,27 +1,27 @@
{
"+D82kt": "Êtes-vous sûr que vous voulez republier: {id}",
"+aZY2h": "Zap Type",
"+aZY2h": "Type de Zap",
"+vIQlC": "Assurez-vous d'enregistrer le mot de passe suivant afin de gérer votre identifiant à l'avenir",
"+vVZ/G": "Connect",
"/4tOwT": "Sauter",
"+vVZ/G": "Connexion",
"/4tOwT": "Passer",
"/JE/X+": "Prise en charge du compte",
"/PCavi": "Public",
"/PCavi": "Publique",
"/RD0e2": "Nostr utilise la technologie de signature numérique pour fournir des notes inviolables qui peuvent être répliquées en toute sécurité sur de nombreux relais pour fournir un stockage redondant de votre contenu.",
"/d6vEc": "Rendez votre profil plus facile à trouver et à partager",
"/n5KSF": "{n} ms",
"0BUTMv": "Chercher...",
"0jOEtS": "Invalid LNURL",
"0jOEtS": "LNURL invalide",
"0mch2Y": "le nom contient des caractères non autorisés",
"0yO7wF": "{n} secondes",
"1A7TZk": "Qu'est-ce que Snort et comment ça marche ?",
"1Mo59U": "Êtes-vous sûr de vouloir supprimer cette note de vos favoris ?",
"1c4YST": "Connected to: {node} 🎉",
"1c4YST": "Connecté à : {node} 🎉",
"1nYUGC": "{n} Abonnements",
"1udzha": "Conversations",
"2/2yg+": "Ajouter",
"25V4l1": "Bannière",
"2IFGap": "Faire un don",
"2LbrkB": "Enter password",
"2LbrkB": "Saisissez le mot de passe",
"2a2YiP": "{n} Favoris",
"2k0Cv+": "N'aime pas ({n})",
"3cc4Ct": "Clair",
@ -29,7 +29,7 @@
"3t3kok": "{n,plural,=1{{n} nouvelle note} other{{n} nouvelles notes}}",
"3tVy+Z": "{n} Abonnés",
"3xCwbZ": "OU",
"3yk8fB": "Wallet",
"3yk8fB": "Portefeuille",
"450Fty": "Aucun",
"47FYwb": "Annuler",
"4IPzdn": "Développeurs principaux",
@ -49,7 +49,7 @@
"8E9muH": "Importer les suivis Twitter (facultatif)",
"8QDesP": "Zapper {n} sats",
"8g2vyB": "le nom est trop long",
"8v1NN+": "Pairing phrase",
"8v1NN+": "Phrase d'appairage",
"9+Ddtu": "Suivant",
"9HU8vw": "Répondre",
"9SvQep": "Suit {n}",
@ -88,10 +88,10 @@
"FfYsOb": "Une erreur est survenue!",
"FmXUJg": "vous suit",
"G/yZLu": "Retirer",
"G1BGCg": "Select Wallet",
"G1BGCg": "Sélectionnez un portefeuille",
"GFOoEE": "Sel",
"GL8aXW": "Favoris ({n})",
"Gcn9NQ": "Magnet Link",
"Gcn9NQ": "Lien Magnet",
"GspYR7": "{n} N'aime pas",
"H+vHiz": "Clé hexagonale..",
"H0JBH6": "Se Déconnecter",
@ -109,19 +109,19 @@
"JkLHGw": "Site Internet",
"K3r6DQ": "Supprimer",
"K7AkdL": "Montrer",
"KAhAcM": "Enter LNDHub config",
"KAhAcM": "Entrez la configuration LNDHub",
"KQvWvD": "Supprimé",
"KWuDfz": "J'ai enregistré mes clés, continuer",
"KahimY": "Type d'événement inconnu : {kind}",
"LXxsbk": "Anonymous",
"LXxsbk": "Anonyme",
"LgbKvU": "Commenter",
"LxY9tW": "Générer la clé",
"M3Oirc": "Menus de débogage",
"MBAYRO": "Affiche \"Copy ID\" et \"Copy Event JSON\" dans le menu contextuel de chaque message",
"MI2jkA": "Pas disponible:",
"MP54GY": "Wallet password",
"MP54GY": "Mot de passe du portefeuille",
"MRp6Ly": "Nom d&#39;utilisateur Twitter",
"MWTx65": "Default Page",
"MWTx65": "Page par défaut",
"MzRYWH": "Acheter {item}",
"N2IrpM": "Confirmer",
"NdOYJJ": "Hmm rien ici .. Essayez {newUsersPage} pour suivre quelques recommandations de naustriches!",
@ -139,7 +139,7 @@
"QTdJfH": "Créer un compte",
"QawghE": "Vous pouvez modifier votre nom d'utilisateur à tout moment.",
"QxCuTo": "Illustration par {name}",
"R2OqnW": "Delete Account",
"R2OqnW": "Supprimer le compte",
"RDZVQL": "Vérifier",
"RahCRH": "Expiré",
"RhDAoS": "Êtes-vous sûr que vous voulez supprimer {id}",
@ -149,7 +149,7 @@
"TpgeGw": "Sel Hex..",
"UQ3pOC": "Sur Nostr, de nombreuses personnes ont le même nom d&#39;utilisateur. Les noms d&#39;utilisateur et l&#39;identité sont des choses distinctes. Vous pouvez obtenir un identifiant unique à l&#39;étape suivante.",
"Up5U7K": "Bloquer",
"VN0+Fz": "Balance: {amount} sats",
"VN0+Fz": "Solde : {amount} sats",
"VOjC1i": "Choisissez le service d'hébergement vers lequel vous souhaitez héberger les pièces jointes",
"VlJkSk": "{n} mis en sourdine",
"VnXp8Z": "Avatar",
@ -163,7 +163,7 @@
"Y31HTH": "Aidez à financer le développement de Snort",
"YDURw6": "URL de service",
"YXA3AH": "Activer les réactions",
"Z4BMCZ": "Enter pairing phrase",
"Z4BMCZ": "Entrez la phrase d'appairage",
"ZKORll": "Activer Maintenant",
"ZLmyG9": "Contributeurs",
"ZUZedV": "Don éclair :",
@ -175,10 +175,10 @@
"cPIKU2": "Abonnements",
"cQfLWb": "URL..",
"cWx9t8": "Tout mettre en sourdine",
"cg1VJ2": "Connect Wallet",
"cg1VJ2": "Connecter un portefeuille",
"cuV2gK": "le nom est enregistré",
"cyR7Kh": "Retourner",
"d6CyG5": "History",
"d6CyG5": "Historique",
"d7d0/x": "Adresse LN",
"dOQCL8": "Nom à afficher",
"e7qqly": "Marquer tout comme lu",
@ -214,14 +214,14 @@
"jzgQ2z": "{n} Réactions",
"k2veDA": "Écrire",
"k7sKNy": "Notre propre service de vérification NIP-05, aidez à soutenir le développement de ce site et obtenez un badge spécial brillant sur notre site !",
"kaaf1E": "now",
"kaaf1E": "maintenant",
"lCILNz": "Acheter Maintenant",
"lD3+8a": "Payer",
"lTbT3s": "Wallet password",
"lTbT3s": "Mot de passe du portefeuille",
"lgg1KN": "compte",
"ll3xBp": "Service proxy d'images",
"lnaT9F": "Abonnements {n}",
"lsNFM1": "Click to load content from {link}",
"lsNFM1": "Cliquez pour afficher le contenu de {link}",
"lvlPhZ": "Payer Facture",
"mH91FY": "Chaque contributeur recevra un pourcentage de tous les dons et commandes NIP-05, vous pouvez voir les montants répartis ci-dessous",
"mKAr6h": "Suivre tout",
@ -241,14 +241,14 @@
"oxCa4R": "L&#39;obtention d&#39;un identifiant permet de confirmer votre identité réelle aux personnes qui vous connaissent. De nombreuses personnes peuvent avoir un nom d&#39;utilisateur @jack, mais il n&#39;y a qu&#39;un seul jack@cash.app.",
"puLNUJ": "Épingler",
"pzTOmv": "Abonnés",
"qDwvZ4": "Unknown error",
"qMx1sA": "Default Zap amount",
"qDwvZ4": "Une erreur inconnue s'est produite",
"qMx1sA": "Montant des Zaps par défaut",
"qUJTsT": "Bloqué",
"qdGuQo": "Votre Clé Privée Est (ne la partagez avec personne)",
"qkvYUb": "Ajouter au Profil",
"qmJ8kD": "La traduction a échoué",
"r3C4x/": "Logiciel",
"r5srDR": "Enter wallet password",
"r5srDR": "Entrez le mot de passe du portefeuille",
"rT14Ow": "Ajouter Relais",
"reJ6SM": "Il est recommandé d&#39;utiliser l&#39;une des extensions de navigateur suivantes si vous êtes sur un ordinateur de bureau pour sécuriser votre clé :",
"rfuMjE": "(Défaut)",
@ -265,7 +265,7 @@
"usAvMr": "Modifier le Profil",
"ut+2Cd": "Obtenir un identifiant partenaire",
"vOKedj": "{n,plural,=1{&amp; {n} autre} other{&amp; {n} autres}}",
"vU71Ez": "Paying with {wallet}",
"vU71Ez": "Payer avec {wallet}",
"vZ4quW": "NIP-05 est une spécification de vérification basée sur DNS qui permet de vous valider en tant qu'utilisateur réel.",
"vrTOHJ": "{amount} sats",
"wEQDC6": "Modifier",
@ -274,16 +274,16 @@
"wih7iJ": "le nom est bloqué",
"wqyN/i": "En savoir plus sur {service} sur {link}",
"wtLjP6": "Copier Identifiant",
"wvFw6Y": "Hey, it looks like you dont have a NIP-05 handle yet, you should get one! Check out {link}",
"wvFw6Y": "Hey, il semble que vous n'avez pas de NIP-05, vous pourriez en obtenir un, jetez un œil à {link}",
"x82IOl": "Mode Sourdine",
"xIoGG9": "Aller à",
"xJ9n2N": "Votre clé publique",
"xKdNPm": "Envoyer",
"xKflGN": "{username}&#39;&#39; suit sur Nostr",
"xQtL3v": "Unlock",
"xQtL3v": "Déverrouiller",
"xbVgIm": "Charger automatiquement le média",
"xmcVZ0": "Chercher",
"y1Z3or": "Language",
"y1Z3or": "Langue",
"yCmnnm": "Lire le flux global depuis",
"zFegDD": "Contacted",
"zINlao": "Propriétaire",

View File

@ -2,7 +2,7 @@
"+D82kt": "Biztos hogy ezt meg akarod osztani: {id}",
"+aZY2h": "Zap típusa",
"+vIQlC": "Ahhoz hogy a jövőben is hozzáférj a fiókodhoz, kérlek mindenképp győződj meg róla hogy a következő jelszót elmentetted",
"+vVZ/G": "Connect",
"+vVZ/G": "Kapcsolódás",
"/4tOwT": "Kihagyás",
"/JE/X+": "Segítség",
"/PCavi": "Nyilvános",
@ -15,13 +15,13 @@
"0yO7wF": "{n} másodperc",
"1A7TZk": "Mi a Snort és hogyan működik?",
"1Mo59U": "Biztos hogy a kedvencekből ezt a bejegyzést el akarod távolítani?",
"1c4YST": "Connected to: {node} 🎉",
"1c4YST": "Kapcsolódás a: {node} 🎉",
"1nYUGC": "{n} Követek",
"1udzha": "Beszélgetések",
"2/2yg+": "Hozzáad",
"25V4l1": "Banner",
"2IFGap": "Adományoz",
"2LbrkB": "Enter password",
"2LbrkB": "Add meg jelszavad",
"2a2YiP": "{n} könyvjelző",
"2k0Cv+": "Nemtetszések ({n})",
"3cc4Ct": "Világos",
@ -29,7 +29,7 @@
"3t3kok": "{n,plural,one {}=1{{n} új bejegyzés} other{{n} új bejegyzések}}",
"3tVy+Z": "{n} Követő",
"3xCwbZ": "VAGY",
"3yk8fB": "Wallet",
"3yk8fB": "Pénztárca",
"450Fty": "Nincs",
"47FYwb": "Törlés",
"4IPzdn": "Elsődleges Fejlesztők",
@ -49,7 +49,7 @@
"8E9muH": "Twitter követők importálása (opcionális)",
"8QDesP": "Zap {n} sats",
"8g2vyB": "név túl hosszú",
"8v1NN+": "Pairing phrase",
"8v1NN+": "Párosító kifejezés",
"9+Ddtu": "Következő",
"9HU8vw": "Válasz",
"9SvQep": "{n} követek",
@ -88,10 +88,10 @@
"FfYsOb": "Hiba történt!",
"FmXUJg": "követ téged",
"G/yZLu": "Eltávolítás",
"G1BGCg": "Select Wallet",
"G1BGCg": "Tárca kiválasztása",
"GFOoEE": "Salt",
"GL8aXW": "Könyvjelzők ({n})",
"Gcn9NQ": "Magnet Link",
"Gcn9NQ": "Mágnes Link",
"GspYR7": "{n} Nem tetszik",
"H+vHiz": "Hex kulcs..",
"H0JBH6": "Kijelentkezés",
@ -103,13 +103,13 @@
"IEwZvs": "Biztos hogy a bejegyzés kiemelését visszavonod?",
"INSqIz": "Twitter felhasználónév...",
"IUZC+0": "Ez azt jelenti, hogy a te általad létrehozott bejegyzéseket senki sem tudja módosítani és bárki ellenőrizheti hogy tényleg te írtad.",
"Iwm6o2": "NIP-05 Shop",
"Iwm6o2": "NIP-05 Bolt",
"JCIgkj": "Felhasználónév",
"JHEHCk": "Zap-ek ({n})",
"JkLHGw": "Weboldal",
"K3r6DQ": "Törlés",
"K7AkdL": "Mutat",
"KAhAcM": "Enter LNDHub config",
"KAhAcM": "Az LNDHub konfiguráció megadása",
"KQvWvD": "Törölve",
"KWuDfz": "Lementettem a kulcsaimat, folytatás",
"KahimY": "Ismeretlen esemény: {kind}",
@ -119,7 +119,7 @@
"M3Oirc": "Hibaelhárító menü",
"MBAYRO": "Mutasd az \"Egyedi azonosítót\" és a \"JSON esetet\" minden üzenet szövegmezőjében",
"MI2jkA": "Nem elérhető:",
"MP54GY": "Wallet password",
"MP54GY": "Pénztárca jelszava",
"MRp6Ly": "Twitter felhasználónév",
"MWTx65": "Alapértelmezett oldal",
"MzRYWH": "{item} megveszem",
@ -139,7 +139,7 @@
"QTdJfH": "Hozzon létre egy fiókot",
"QawghE": "A felhasználónevedet bármikor megváltoztathatod.",
"QxCuTo": "Művészet: {name}",
"R2OqnW": "Delete Account",
"R2OqnW": "Fiók törlése",
"RDZVQL": "Ellenőrzés",
"RahCRH": "Lejárt",
"RhDAoS": "Biztos hogy törölni akarod a {id}",
@ -149,7 +149,7 @@
"TpgeGw": "Hex Salt..",
"UQ3pOC": "A Nostr-án sok embernek ugyanaz a felhasználóneve. A felhasználónév és a személyazonosság két különböző dolog. A következő lépésben tudsz magadnak egyedi azonosítót szerezni.",
"Up5U7K": "Tiltás",
"VN0+Fz": "Balance: {amount} sats",
"VN0+Fz": "Egyenleg: {amount} sats",
"VOjC1i": "Válaszd ki mely szolgáltatóhoz legyenek a fájlok feltöltve",
"VlJkSk": "{n} némított",
"VnXp8Z": "Avatar",
@ -163,7 +163,7 @@
"Y31HTH": "Segítsen finanszírozni a Snort fejlesztését",
"YDURw6": "Szervíz cím",
"YXA3AH": "Reakciók engedélyezése",
"Z4BMCZ": "Enter pairing phrase",
"Z4BMCZ": "Párosító kifejezés megadása",
"ZKORll": "Aktiválás",
"ZLmyG9": "Közreműködők",
"ZUZedV": "Lightning adomány:",
@ -175,10 +175,10 @@
"cPIKU2": "Követek",
"cQfLWb": "Cím..",
"cWx9t8": "Mind némítása",
"cg1VJ2": "Connect Wallet",
"cg1VJ2": "Pénztárca csatlakoztatása",
"cuV2gK": "név már foglalt",
"cyR7Kh": "Vissza",
"d6CyG5": "History",
"d6CyG5": "Előzmények",
"d7d0/x": "LN cím",
"dOQCL8": "Megjelenítendő név",
"e7qqly": "Mind olvasottnak jelölni",
@ -214,10 +214,10 @@
"jzgQ2z": "{n} Reakció",
"k2veDA": "Írás",
"k7sKNy": "A mi saját NIP-05 azonosítási szolgáltatásunk, amelynek a használatával ennek az oldalnak a fejlesztését segítheted és ezzel egy speciális kitüntetést is szerezhetsz!",
"kaaf1E": "now",
"kaaf1E": "most",
"lCILNz": "Vásárlás",
"lD3+8a": "Fizetem",
"lTbT3s": "Wallet password",
"lTbT3s": "Pénztárca jelszava",
"lgg1KN": "Felhasználói felület",
"ll3xBp": "Képmegosztó szolgáltató",
"lnaT9F": "Követek {n}",
@ -241,14 +241,14 @@
"oxCa4R": "Egy azonosító megszerzésével segítesz másoknak a te valós fiókod könnyebb megtalálásában. Sokaknak lehet @jack felhasználóneve, de csak egy jack@cash.app létezik.",
"puLNUJ": "Kiemel",
"pzTOmv": "Követők",
"qDwvZ4": "Unknown error",
"qDwvZ4": "Ismeretlen hiba",
"qMx1sA": "Alapértelmezett Zap összeg",
"qUJTsT": "Tiltva",
"qdGuQo": "A te privát kulcsod (senkivel se oszd meg)",
"qkvYUb": "Hozzáadás a profilhoz",
"qmJ8kD": "Fordítás nem sikerült",
"r3C4x/": "Szoftver",
"r5srDR": "Enter wallet password",
"r5srDR": "Add meg a pénztárcád jelszavát",
"rT14Ow": "Csomópont hozzáadása",
"reJ6SM": "Ahhoz hogy a privát kulcsod biztonságban legyen, javasolt a következő böngésző kiegészítők valamelyikét használni:",
"rfuMjE": "(Alapértelmezett)",
@ -265,7 +265,7 @@
"usAvMr": "Profil módosítása",
"ut+2Cd": "Szerezz egy partner azonosítót",
"vOKedj": "{n,plural,=1{& {n} egyéb} other{& {n} egyebek}}",
"vU71Ez": "Paying with {wallet}",
"vU71Ez": "Fizetés {wallet}",
"vZ4quW": "A NIP-05 egy DNS alapú azonosítási specifikáció, ami segít a valós személyed bizonyításában.",
"vrTOHJ": "{amount} sats",
"wEQDC6": "Módosítás",
@ -274,13 +274,13 @@
"wih7iJ": "név tiltva",
"wqyN/i": "Több információ a {service} itt {link}",
"wtLjP6": "Egyedi azonosító",
"wvFw6Y": "Hey, it looks like you dont have a NIP-05 handle yet, you should get one! Check out {link}",
"wvFw6Y": "Úgy néz ki nem rendelkezel még NIP-05 azonosítóval, érdemes lenne szerezned egyett! Ezt nézd {link}",
"x82IOl": "Némítás",
"xIoGG9": "Menj ide",
"xJ9n2N": "A te publikus kulcsod",
"xKdNPm": "Küldés",
"xKflGN": "{username} a Nostr-án követ",
"xQtL3v": "Unlock",
"xQtL3v": "Feloldás",
"xbVgIm": "Média automatikus betöltése",
"xmcVZ0": "Keresés",
"y1Z3or": "Nyelv",

View File

@ -8,12 +8,15 @@ enum EventKind {
Deletion = 5, // NIP-09
Repost = 6, // NIP-18
Reaction = 7, // NIP-25
BadgeAward = 8, // NIP-58
Relays = 10002, // NIP-65
Ephemeral = 20_000,
Auth = 22242, // NIP-42
PubkeyLists = 30000, // NIP-51a
NoteLists = 30001, // NIP-51b
TagLists = 30002, // NIP-51c
Badge = 30009, // NIP-58
ProfileBadges = 30008, // NIP-58
ZapRequest = 9734, // NIP 57
ZapReceipt = 9735, // NIP 57
}

View File

@ -78,6 +78,7 @@ export enum Lists {
Pinned = "pin",
Bookmarked = "bookmark",
Followed = "follow",
Badges = "profile_badges",
}
export interface FullRelaySettings {