feat: add badge for suspicious and spam users
This commit is contained in:
@@ -16,6 +16,7 @@ import Nip05 from '../Nip05'
|
|||||||
import NoteOptions from '../NoteOptions'
|
import NoteOptions from '../NoteOptions'
|
||||||
import ParentNotePreview from '../ParentNotePreview'
|
import ParentNotePreview from '../ParentNotePreview'
|
||||||
import TranslateButton from '../TranslateButton'
|
import TranslateButton from '../TranslateButton'
|
||||||
|
import TrustScoreBadge from '../TrustScoreBadge'
|
||||||
import UserAvatar from '../UserAvatar'
|
import UserAvatar from '../UserAvatar'
|
||||||
import Username from '../Username'
|
import Username from '../Username'
|
||||||
import CommunityDefinition from './CommunityDefinition'
|
import CommunityDefinition from './CommunityDefinition'
|
||||||
@@ -125,6 +126,7 @@ export default function Note({
|
|||||||
skeletonClassName={size === 'small' ? 'h-3' : 'h-4'}
|
skeletonClassName={size === 'small' ? 'h-3' : 'h-4'}
|
||||||
/>
|
/>
|
||||||
<FollowingBadge pubkey={event.pubkey} />
|
<FollowingBadge pubkey={event.pubkey} />
|
||||||
|
<TrustScoreBadge pubkey={event.pubkey} />
|
||||||
<ClientTag event={event} />
|
<ClientTag event={event} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1 text-sm text-muted-foreground">
|
<div className="flex items-center gap-1 text-sm text-muted-foreground">
|
||||||
|
|||||||
@@ -19,13 +19,14 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import NotFound from '../NotFound'
|
import NotFound from '../NotFound'
|
||||||
import SearchInput from '../SearchInput'
|
import SearchInput from '../SearchInput'
|
||||||
|
import TextWithEmojis from '../TextWithEmojis'
|
||||||
|
import TrustScoreBadge from '../TrustScoreBadge'
|
||||||
|
import AvatarWithLightbox from './AvatarWithLightbox'
|
||||||
|
import BannerWithLightbox from './BannerWithLightbox'
|
||||||
import FollowedBy from './FollowedBy'
|
import FollowedBy from './FollowedBy'
|
||||||
import Followings from './Followings'
|
import Followings from './Followings'
|
||||||
import ProfileFeed from './ProfileFeed'
|
import ProfileFeed from './ProfileFeed'
|
||||||
import Relays from './Relays'
|
import Relays from './Relays'
|
||||||
import TextWithEmojis from '../TextWithEmojis'
|
|
||||||
import AvatarWithLightbox from './AvatarWithLightbox'
|
|
||||||
import BannerWithLightbox from './BannerWithLightbox'
|
|
||||||
|
|
||||||
export default function Profile({ id }: { id?: string }) {
|
export default function Profile({ id }: { id?: string }) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -143,6 +144,7 @@ export default function Profile({ id }: { id?: string }) {
|
|||||||
emojis={emojis}
|
emojis={emojis}
|
||||||
className="text-xl font-semibold truncate select-text"
|
className="text-xl font-semibold truncate select-text"
|
||||||
/>
|
/>
|
||||||
|
<TrustScoreBadge pubkey={pubkey} />
|
||||||
{isFollowingYou && (
|
{isFollowingYou && (
|
||||||
<div className="text-muted-foreground rounded-full bg-muted text-xs h-fit px-2 shrink-0">
|
<div className="text-muted-foreground rounded-full bg-muted text-xs h-fit px-2 shrink-0">
|
||||||
{t('Follows you')}
|
{t('Follows you')}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { useMemo } from 'react'
|
|||||||
import FollowButton from '../FollowButton'
|
import FollowButton from '../FollowButton'
|
||||||
import Nip05 from '../Nip05'
|
import Nip05 from '../Nip05'
|
||||||
import ProfileAbout from '../ProfileAbout'
|
import ProfileAbout from '../ProfileAbout'
|
||||||
|
import TextWithEmojis from '../TextWithEmojis'
|
||||||
|
import TrustScoreBadge from '../TrustScoreBadge'
|
||||||
import { SimpleUserAvatar } from '../UserAvatar'
|
import { SimpleUserAvatar } from '../UserAvatar'
|
||||||
|
|
||||||
export default function ProfileCard({ userId }: { userId: string }) {
|
export default function ProfileCard({ userId }: { userId: string }) {
|
||||||
@@ -18,7 +20,14 @@ export default function ProfileCard({ userId }: { userId: string }) {
|
|||||||
<FollowButton pubkey={pubkey} />
|
<FollowButton pubkey={pubkey} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-lg font-semibold truncate">{username}</div>
|
<div className="flex gap-2 items-center">
|
||||||
|
<TextWithEmojis
|
||||||
|
text={username || ''}
|
||||||
|
emojis={emojis}
|
||||||
|
className="text-lg font-semibold truncate"
|
||||||
|
/>
|
||||||
|
<TrustScoreBadge pubkey={pubkey} />
|
||||||
|
</div>
|
||||||
<Nip05 pubkey={pubkey} />
|
<Nip05 pubkey={pubkey} />
|
||||||
</div>
|
</div>
|
||||||
{about && (
|
{about && (
|
||||||
|
|||||||
64
src/components/TrustScoreBadge/index.tsx
Normal file
64
src/components/TrustScoreBadge/index.tsx
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { useNostr } from '@/providers/NostrProvider'
|
||||||
|
import trustScoreService from '@/services/trust-score.service'
|
||||||
|
import { AlertTriangle, ShieldAlert } from 'lucide-react'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
export default function TrustScoreBadge({
|
||||||
|
pubkey,
|
||||||
|
className
|
||||||
|
}: {
|
||||||
|
pubkey: string
|
||||||
|
className?: string
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { pubkey: currentPubkey } = useNostr()
|
||||||
|
const [percentile, setPercentile] = useState<number | null>(null)
|
||||||
|
const [loading, setLoading] = useState(true)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentPubkey === pubkey) {
|
||||||
|
setLoading(false)
|
||||||
|
setPercentile(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchScore = async () => {
|
||||||
|
try {
|
||||||
|
const data = await trustScoreService.fetchTrustScore(pubkey)
|
||||||
|
if (data) {
|
||||||
|
setPercentile(data.percentile)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch trust score:', error)
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchScore()
|
||||||
|
}, [pubkey, currentPubkey])
|
||||||
|
|
||||||
|
if (loading || percentile === null) return null
|
||||||
|
|
||||||
|
// percentile < 50: likely spam (red alert)
|
||||||
|
// percentile < 75: suspicious (yellow warning)
|
||||||
|
if (percentile < 50) {
|
||||||
|
return (
|
||||||
|
<div title={t('Likely spam account (Trust score: {{percentile}}%)', { percentile })}>
|
||||||
|
<ShieldAlert className={cn('!size-4 text-red-500', className)} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (percentile < 75) {
|
||||||
|
return (
|
||||||
|
<div title={t('Suspicious account (Trust score: {{percentile}}%)', { percentile })}>
|
||||||
|
<AlertTriangle className={cn('!size-4 text-yellow-600 dark:text-yellow-500', className)} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import { userIdToPubkey } from '@/lib/pubkey'
|
|||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import FollowingBadge from '../FollowingBadge'
|
import FollowingBadge from '../FollowingBadge'
|
||||||
|
import TrustScoreBadge from '../TrustScoreBadge'
|
||||||
|
|
||||||
export default function UserItem({
|
export default function UserItem({
|
||||||
userId,
|
userId,
|
||||||
@@ -32,6 +33,7 @@ export default function UserItem({
|
|||||||
skeletonClassName="h-4"
|
skeletonClassName="h-4"
|
||||||
/>
|
/>
|
||||||
{showFollowingBadge && <FollowingBadge pubkey={pubkey} />}
|
{showFollowingBadge && <FollowingBadge pubkey={pubkey} />}
|
||||||
|
<TrustScoreBadge pubkey={pubkey} />
|
||||||
</div>
|
</div>
|
||||||
<Nip05 pubkey={userId} />
|
<Nip05 pubkey={userId} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -550,6 +550,8 @@ export default {
|
|||||||
'Failed to republish to optimal relays: {{error}}': 'فشل إعادة النشر إلى المرحلات المثلى: {{error}}',
|
'Failed to republish to optimal relays: {{error}}': 'فشل إعادة النشر إلى المرحلات المثلى: {{error}}',
|
||||||
'External Content': 'محتوى خارجي',
|
'External Content': 'محتوى خارجي',
|
||||||
Highlight: 'تسليط الضوء',
|
Highlight: 'تسليط الضوء',
|
||||||
'Optimal relays and {{count}} other relays': 'المرحلات المثلى و {{count}} مرحلات أخرى'
|
'Optimal relays and {{count}} other relays': 'المرحلات المثلى و {{count}} مرحلات أخرى',
|
||||||
|
'Likely spam account (Trust score: {{percentile}}%)': 'حساب مشبوه للغاية (درجة الثقة: {{percentile}}%)',
|
||||||
|
'Suspicious account (Trust score: {{percentile}}%)': 'حساب مشبوه (درجة الثقة: {{percentile}}%)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -566,6 +566,8 @@ export default {
|
|||||||
'Fehler beim Neuveröffentlichen auf optimale Relays: {{error}}',
|
'Fehler beim Neuveröffentlichen auf optimale Relays: {{error}}',
|
||||||
'External Content': 'Externer Inhalt',
|
'External Content': 'Externer Inhalt',
|
||||||
Highlight: 'Hervorheben',
|
Highlight: 'Hervorheben',
|
||||||
'Optimal relays and {{count}} other relays': 'Optimale Relays und {{count}} andere Relays'
|
'Optimal relays and {{count}} other relays': 'Optimale Relays und {{count}} andere Relays',
|
||||||
|
'Likely spam account (Trust score: {{percentile}}%)': 'Wahrscheinlich Spam-Konto (Vertrauenswert: {{percentile}}%)',
|
||||||
|
'Suspicious account (Trust score: {{percentile}}%)': 'Verdächtiges Konto (Vertrauenswert: {{percentile}}%)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -547,9 +547,14 @@ export default {
|
|||||||
'Optimal relays': 'Optimal relays',
|
'Optimal relays': 'Optimal relays',
|
||||||
"Successfully republish to optimal relays (your write relays and mentioned users' read relays)":
|
"Successfully republish to optimal relays (your write relays and mentioned users' read relays)":
|
||||||
"Successfully republish to optimal relays (your write relays and mentioned users' read relays)",
|
"Successfully republish to optimal relays (your write relays and mentioned users' read relays)",
|
||||||
'Failed to republish to optimal relays: {{error}}': 'Failed to republish to optimal relays: {{error}}',
|
'Failed to republish to optimal relays: {{error}}':
|
||||||
|
'Failed to republish to optimal relays: {{error}}',
|
||||||
'External Content': 'External Content',
|
'External Content': 'External Content',
|
||||||
Highlight: 'Highlight',
|
Highlight: 'Highlight',
|
||||||
'Optimal relays and {{count}} other relays': 'Optimal relays and {{count}} other relays'
|
'Optimal relays and {{count}} other relays': 'Optimal relays and {{count}} other relays',
|
||||||
|
'Likely spam account (Trust score: {{percentile}}%)':
|
||||||
|
'Likely spam account (Trust score: {{percentile}}%)',
|
||||||
|
'Suspicious account (Trust score: {{percentile}}%)':
|
||||||
|
'Suspicious account (Trust score: {{percentile}}%)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -561,6 +561,8 @@ export default {
|
|||||||
'Failed to republish to optimal relays: {{error}}': 'Error al republicar en relays óptimos: {{error}}',
|
'Failed to republish to optimal relays: {{error}}': 'Error al republicar en relays óptimos: {{error}}',
|
||||||
'External Content': 'Contenido externo',
|
'External Content': 'Contenido externo',
|
||||||
Highlight: 'Destacado',
|
Highlight: 'Destacado',
|
||||||
'Optimal relays and {{count}} other relays': 'Relays óptimos y {{count}} otros relays'
|
'Optimal relays and {{count}} other relays': 'Relays óptimos y {{count}} otros relays',
|
||||||
|
'Likely spam account (Trust score: {{percentile}}%)': 'Probablemente cuenta spam (Puntuación de confianza: {{percentile}}%)',
|
||||||
|
'Suspicious account (Trust score: {{percentile}}%)': 'Cuenta sospechosa (Puntuación de confianza: {{percentile}}%)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -555,6 +555,8 @@ export default {
|
|||||||
'Failed to republish to optimal relays: {{error}}': 'خطا در انتشار مجدد در رلههای بهینه: {{error}}',
|
'Failed to republish to optimal relays: {{error}}': 'خطا در انتشار مجدد در رلههای بهینه: {{error}}',
|
||||||
'External Content': 'محتوای خارجی',
|
'External Content': 'محتوای خارجی',
|
||||||
Highlight: 'برجستهسازی',
|
Highlight: 'برجستهسازی',
|
||||||
'Optimal relays and {{count}} other relays': 'رلههای بهینه و {{count}} رله دیگر'
|
'Optimal relays and {{count}} other relays': 'رلههای بهینه و {{count}} رله دیگر',
|
||||||
|
'Likely spam account (Trust score: {{percentile}}%)': 'احتمالاً حساب هرزنامه (امتیاز اعتماد: {{percentile}}%)',
|
||||||
|
'Suspicious account (Trust score: {{percentile}}%)': 'حساب مشکوک (امتیاز اعتماد: {{percentile}}%)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -564,6 +564,8 @@ export default {
|
|||||||
'Failed to republish to optimal relays: {{error}}': 'Échec de la republication sur les relais optimaux : {{error}}',
|
'Failed to republish to optimal relays: {{error}}': 'Échec de la republication sur les relais optimaux : {{error}}',
|
||||||
'External Content': 'Contenu externe',
|
'External Content': 'Contenu externe',
|
||||||
Highlight: 'Surligner',
|
Highlight: 'Surligner',
|
||||||
'Optimal relays and {{count}} other relays': 'Relais optimaux et {{count}} autres relais'
|
'Optimal relays and {{count}} other relays': 'Relais optimaux et {{count}} autres relais',
|
||||||
|
'Likely spam account (Trust score: {{percentile}}%)': 'Compte probablement spam (Score de confiance: {{percentile}}%)',
|
||||||
|
'Suspicious account (Trust score: {{percentile}}%)': 'Compte suspect (Score de confiance: {{percentile}}%)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -556,6 +556,8 @@ export default {
|
|||||||
'Failed to republish to optimal relays: {{error}}': 'इष्टतम रिले पर पुनः प्रकाशित करने में विफल: {{error}}',
|
'Failed to republish to optimal relays: {{error}}': 'इष्टतम रिले पर पुनः प्रकाशित करने में विफल: {{error}}',
|
||||||
'External Content': 'बाहरी सामग्री',
|
'External Content': 'बाहरी सामग्री',
|
||||||
Highlight: 'हाइलाइट',
|
Highlight: 'हाइलाइट',
|
||||||
'Optimal relays and {{count}} other relays': 'इष्टतम रिले और {{count}} अन्य रिले'
|
'Optimal relays and {{count}} other relays': 'इष्टतम रिले और {{count}} अन्य रिले',
|
||||||
|
'Likely spam account (Trust score: {{percentile}}%)': 'संभावित स्पैम खाता (विश्वास स्कोर: {{percentile}}%)',
|
||||||
|
'Suspicious account (Trust score: {{percentile}}%)': 'संदिग्ध खाता (विश्वास स्कोर: {{percentile}}%)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -551,6 +551,8 @@ export default {
|
|||||||
'Nem sikerült újra közzétenni az optimális relay-eken: {{error}}',
|
'Nem sikerült újra közzétenni az optimális relay-eken: {{error}}',
|
||||||
'External Content': 'Külső tartalom',
|
'External Content': 'Külső tartalom',
|
||||||
Highlight: 'Kiemelés',
|
Highlight: 'Kiemelés',
|
||||||
'Optimal relays and {{count}} other relays': 'Optimális relay-ek és {{count}} másik relay'
|
'Optimal relays and {{count}} other relays': 'Optimális relay-ek és {{count}} másik relay',
|
||||||
|
'Likely spam account (Trust score: {{percentile}}%)': 'Valószínűleg spam fiók (Megbízhatósági pontszám: {{percentile}}%)',
|
||||||
|
'Suspicious account (Trust score: {{percentile}}%)': 'Gyanús fiók (Megbízhatósági pontszám: {{percentile}}%)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -560,6 +560,8 @@ export default {
|
|||||||
'Failed to republish to optimal relays: {{error}}': 'Errore nella ripubblicazione sui relay ottimali: {{error}}',
|
'Failed to republish to optimal relays: {{error}}': 'Errore nella ripubblicazione sui relay ottimali: {{error}}',
|
||||||
'External Content': 'Contenuto esterno',
|
'External Content': 'Contenuto esterno',
|
||||||
Highlight: 'Evidenzia',
|
Highlight: 'Evidenzia',
|
||||||
'Optimal relays and {{count}} other relays': 'Relay ottimali e {{count}} altri relay'
|
'Optimal relays and {{count}} other relays': 'Relay ottimali e {{count}} altri relay',
|
||||||
|
'Likely spam account (Trust score: {{percentile}}%)': 'Probabile account spam (Punteggio di fiducia: {{percentile}}%)',
|
||||||
|
'Suspicious account (Trust score: {{percentile}}%)': 'Account sospetto (Punteggio di fiducia: {{percentile}}%)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -555,6 +555,8 @@ export default {
|
|||||||
'Failed to republish to optimal relays: {{error}}': '最適なリレーへの再公開に失敗しました:{{error}}',
|
'Failed to republish to optimal relays: {{error}}': '最適なリレーへの再公開に失敗しました:{{error}}',
|
||||||
'External Content': '外部コンテンツ',
|
'External Content': '外部コンテンツ',
|
||||||
Highlight: 'ハイライト',
|
Highlight: 'ハイライト',
|
||||||
'Optimal relays and {{count}} other relays': '最適なリレーと他の{{count}}個のリレー'
|
'Optimal relays and {{count}} other relays': '最適なリレーと他の{{count}}個のリレー',
|
||||||
|
'Likely spam account (Trust score: {{percentile}}%)': 'スパムの可能性が高いアカウント(信頼スコア:{{percentile}}%)',
|
||||||
|
'Suspicious account (Trust score: {{percentile}}%)': '疑わしいアカウント(信頼スコア:{{percentile}}%)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -555,6 +555,8 @@ export default {
|
|||||||
'Failed to republish to optimal relays: {{error}}': '최적 릴레이에 재게시 실패: {{error}}',
|
'Failed to republish to optimal relays: {{error}}': '최적 릴레이에 재게시 실패: {{error}}',
|
||||||
'External Content': '외부 콘텐츠',
|
'External Content': '외부 콘텐츠',
|
||||||
Highlight: '하이라이트',
|
Highlight: '하이라이트',
|
||||||
'Optimal relays and {{count}} other relays': '최적 릴레이 및 기타 {{count}}개 릴레이'
|
'Optimal relays and {{count}} other relays': '최적 릴레이 및 기타 {{count}}개 릴레이',
|
||||||
|
'Likely spam account (Trust score: {{percentile}}%)': '스팸 계정 가능성 높음 (신뢰 점수: {{percentile}}%)',
|
||||||
|
'Suspicious account (Trust score: {{percentile}}%)': '의심스러운 계정 (신뢰 점수: {{percentile}}%)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -561,6 +561,8 @@ export default {
|
|||||||
'Nie udało się opublikować ponownie na optymalnych przekaźnikach: {{error}}',
|
'Nie udało się opublikować ponownie na optymalnych przekaźnikach: {{error}}',
|
||||||
'External Content': 'Treść zewnętrzna',
|
'External Content': 'Treść zewnętrzna',
|
||||||
Highlight: 'Podświetl',
|
Highlight: 'Podświetl',
|
||||||
'Optimal relays and {{count}} other relays': 'Optymalne przekaźniki i {{count}} innych przekaźników'
|
'Optimal relays and {{count}} other relays': 'Optymalne przekaźniki i {{count}} innych przekaźników',
|
||||||
|
'Likely spam account (Trust score: {{percentile}}%)': 'Prawdopodobnie konto spamowe (Wynik zaufania: {{percentile}}%)',
|
||||||
|
'Suspicious account (Trust score: {{percentile}}%)': 'Podejrzane konto (Wynik zaufania: {{percentile}}%)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -556,6 +556,8 @@ export default {
|
|||||||
'Failed to republish to optimal relays: {{error}}': 'Falha ao republicar nos relays ideais: {{error}}',
|
'Failed to republish to optimal relays: {{error}}': 'Falha ao republicar nos relays ideais: {{error}}',
|
||||||
'External Content': 'Conteúdo externo',
|
'External Content': 'Conteúdo externo',
|
||||||
Highlight: 'Marcação',
|
Highlight: 'Marcação',
|
||||||
'Optimal relays and {{count}} other relays': 'Relays ideais e {{count}} outros relays'
|
'Optimal relays and {{count}} other relays': 'Relays ideais e {{count}} outros relays',
|
||||||
|
'Likely spam account (Trust score: {{percentile}}%)': 'Provável conta de spam (Pontuação de confiança: {{percentile}}%)',
|
||||||
|
'Suspicious account (Trust score: {{percentile}}%)': 'Conta suspeita (Pontuação de confiança: {{percentile}}%)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -559,6 +559,8 @@ export default {
|
|||||||
'Failed to republish to optimal relays: {{error}}': 'Falha ao republicar nos relays ideais: {{error}}',
|
'Failed to republish to optimal relays: {{error}}': 'Falha ao republicar nos relays ideais: {{error}}',
|
||||||
'External Content': 'Conteúdo externo',
|
'External Content': 'Conteúdo externo',
|
||||||
Highlight: 'Destacar',
|
Highlight: 'Destacar',
|
||||||
'Optimal relays and {{count}} other relays': 'Relays ideais e {{count}} outros relays'
|
'Optimal relays and {{count}} other relays': 'Relays ideais e {{count}} outros relays',
|
||||||
|
'Likely spam account (Trust score: {{percentile}}%)': 'Provável conta de spam (Pontuação de confiança: {{percentile}}%)',
|
||||||
|
'Suspicious account (Trust score: {{percentile}}%)': 'Conta suspeita (Pontuação de confiança: {{percentile}}%)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -561,6 +561,8 @@ export default {
|
|||||||
'Failed to republish to optimal relays: {{error}}': 'Не удалось опубликовать в оптимальные релеи: {{error}}',
|
'Failed to republish to optimal relays: {{error}}': 'Не удалось опубликовать в оптимальные релеи: {{error}}',
|
||||||
'External Content': 'Внешний контент',
|
'External Content': 'Внешний контент',
|
||||||
Highlight: 'Выделить',
|
Highlight: 'Выделить',
|
||||||
'Optimal relays and {{count}} other relays': 'Оптимальные релеи и {{count}} других релеев'
|
'Optimal relays and {{count}} other relays': 'Оптимальные релеи и {{count}} других релеев',
|
||||||
|
'Likely spam account (Trust score: {{percentile}}%)': 'Вероятно спам-аккаунт (Оценка доверия: {{percentile}}%)',
|
||||||
|
'Suspicious account (Trust score: {{percentile}}%)': 'Подозрительный аккаунт (Оценка доверия: {{percentile}}%)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -548,6 +548,8 @@ export default {
|
|||||||
'Failed to republish to optimal relays: {{error}}': 'เผยแพร่ซ้ำไปยังรีเลย์ที่เหมาะสมล้มเหลว: {{error}}',
|
'Failed to republish to optimal relays: {{error}}': 'เผยแพร่ซ้ำไปยังรีเลย์ที่เหมาะสมล้มเหลว: {{error}}',
|
||||||
'External Content': 'เนื้อหาภายนอก',
|
'External Content': 'เนื้อหาภายนอก',
|
||||||
Highlight: 'ไฮไลต์',
|
Highlight: 'ไฮไลต์',
|
||||||
'Optimal relays and {{count}} other relays': 'รีเลย์ที่เหมาะสมและรีเลย์อื่น {{count}} รายการ'
|
'Optimal relays and {{count}} other relays': 'รีเลย์ที่เหมาะสมและรีเลย์อื่น {{count}} รายการ',
|
||||||
|
'Likely spam account (Trust score: {{percentile}}%)': 'น่าจะเป็นบัญชีสแปม (คะแนนความน่าเชื่อถือ: {{percentile}}%)',
|
||||||
|
'Suspicious account (Trust score: {{percentile}}%)': 'บัญชีที่น่าสงสัย (คะแนนความน่าเชื่อถือ: {{percentile}}%)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -543,6 +543,8 @@ export default {
|
|||||||
'Failed to republish to optimal relays: {{error}}': '重新发布到最佳中继器失败:{{error}}',
|
'Failed to republish to optimal relays: {{error}}': '重新发布到最佳中继器失败:{{error}}',
|
||||||
'External Content': '外部内容',
|
'External Content': '外部内容',
|
||||||
Highlight: '高亮',
|
Highlight: '高亮',
|
||||||
'Optimal relays and {{count}} other relays': '最佳中继器和其他 {{count}} 个中继器'
|
'Optimal relays and {{count}} other relays': '最佳中继器和其他 {{count}} 个中继器',
|
||||||
|
'Likely spam account (Trust score: {{percentile}}%)': '疑似垃圾账号(信任分数:{{percentile}}%)',
|
||||||
|
'Suspicious account (Trust score: {{percentile}}%)': '可疑账号(信任分数:{{percentile}}%)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
47
src/services/trust-score.service.ts
Normal file
47
src/services/trust-score.service.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import DataLoader from 'dataloader'
|
||||||
|
|
||||||
|
export interface TrustScoreData {
|
||||||
|
percentile: number
|
||||||
|
}
|
||||||
|
|
||||||
|
class TrustScoreService {
|
||||||
|
static instance: TrustScoreService
|
||||||
|
|
||||||
|
private trustScoreDataLoader = new DataLoader<string, TrustScoreData | null>(async (userIds) => {
|
||||||
|
return await Promise.all(
|
||||||
|
userIds.map(async (userId) => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`https://fayan.jumble.social/${userId}`)
|
||||||
|
if (!res.ok) {
|
||||||
|
if (res.status === 404) {
|
||||||
|
return { percentile: 0 }
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const data = await res.json()
|
||||||
|
if (typeof data.percentile === 'number') {
|
||||||
|
return { percentile: data.percentile }
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
if (!TrustScoreService.instance) {
|
||||||
|
TrustScoreService.instance = this
|
||||||
|
}
|
||||||
|
return TrustScoreService.instance
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchTrustScore(userId: string): Promise<TrustScoreData | null> {
|
||||||
|
return await this.trustScoreDataLoader.load(userId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const instance = new TrustScoreService()
|
||||||
|
|
||||||
|
export default instance
|
||||||
Reference in New Issue
Block a user