Add validate util
This commit is contained in:
@@ -55,6 +55,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@atproto/api": "0.14.0-next.6",
|
"@atproto/api": "0.14.0-next.6",
|
||||||
|
"@atproto/lexicon": "0.4.6-next.5",
|
||||||
"@bitdrift/react-native": "^0.6.2",
|
"@bitdrift/react-native": "^0.6.2",
|
||||||
"@braintree/sanitize-url": "^6.0.2",
|
"@braintree/sanitize-url": "^6.0.2",
|
||||||
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
|
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
|
||||||
|
|||||||
@@ -4,11 +4,7 @@ import type ViewShot from 'react-native-view-shot'
|
|||||||
import {requestMediaLibraryPermissionsAsync} from 'expo-image-picker'
|
import {requestMediaLibraryPermissionsAsync} from 'expo-image-picker'
|
||||||
import {createAssetAsync} from 'expo-media-library'
|
import {createAssetAsync} from 'expo-media-library'
|
||||||
import * as Sharing from 'expo-sharing'
|
import * as Sharing from 'expo-sharing'
|
||||||
import {
|
import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api'
|
||||||
AppBskyGraphDefs,
|
|
||||||
AppBskyGraphStarterpack,
|
|
||||||
asPredicate,
|
|
||||||
} from '@atproto/api'
|
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
@@ -22,6 +18,7 @@ import * as Dialog from '#/components/Dialog'
|
|||||||
import {DialogControlProps} from '#/components/Dialog'
|
import {DialogControlProps} from '#/components/Dialog'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
import {QrCode} from '#/components/StarterPack/QrCode'
|
import {QrCode} from '#/components/StarterPack/QrCode'
|
||||||
|
import * as bsky from '#/types/bsky'
|
||||||
|
|
||||||
export function QrCodeDialog({
|
export function QrCodeDialog({
|
||||||
starterPack,
|
starterPack,
|
||||||
@@ -82,8 +79,9 @@ export function QrCodeDialog({
|
|||||||
setIsProcessing(true)
|
setIsProcessing(true)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!asPredicate(AppBskyGraphStarterpack.validateRecord)(
|
!bsky.validate(
|
||||||
starterPack.record,
|
starterPack.record,
|
||||||
|
AppBskyGraphStarterpack.validateRecord,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import {
|
|||||||
AppBskyEmbedRecordWithMedia,
|
AppBskyEmbedRecordWithMedia,
|
||||||
AppBskyFeedDefs,
|
AppBskyFeedDefs,
|
||||||
AppBskyFeedPost,
|
AppBskyFeedPost,
|
||||||
asPredicate,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
|
||||||
|
import * as bsky from '#/types/bsky'
|
||||||
import {isPostInLanguage} from '../../locale/helpers'
|
import {isPostInLanguage} from '../../locale/helpers'
|
||||||
import {FALLBACK_MARKER_POST} from './feed/home'
|
import {FALLBACK_MARKER_POST} from './feed/home'
|
||||||
import {ReasonFeedSource} from './feed/types'
|
import {ReasonFeedSource} from './feed/types'
|
||||||
@@ -68,7 +68,7 @@ export class FeedViewPostsSlice {
|
|||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
!AppBskyFeedPost.isRecord(post.record) ||
|
!AppBskyFeedPost.isRecord(post.record) ||
|
||||||
!asPredicate(AppBskyFeedPost.validateRecord)(post.record)
|
!bsky.validate(post.record, AppBskyFeedPost.validateRecord)
|
||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,7 @@ export class FeedViewPostsSlice {
|
|||||||
if (
|
if (
|
||||||
!AppBskyFeedDefs.isPostView(parent) ||
|
!AppBskyFeedDefs.isPostView(parent) ||
|
||||||
!AppBskyFeedPost.isRecord(parent.record) ||
|
!AppBskyFeedPost.isRecord(parent.record) ||
|
||||||
!asPredicate(AppBskyFeedPost.validateRecord)(parent.record)
|
!bsky.validate(parent.record, AppBskyFeedPost.validateRecord)
|
||||||
) {
|
) {
|
||||||
this.isOrphan = true
|
this.isOrphan = true
|
||||||
return
|
return
|
||||||
@@ -142,7 +142,7 @@ export class FeedViewPostsSlice {
|
|||||||
if (
|
if (
|
||||||
!AppBskyFeedDefs.isPostView(root) ||
|
!AppBskyFeedDefs.isPostView(root) ||
|
||||||
!AppBskyFeedPost.isRecord(root.record) ||
|
!AppBskyFeedPost.isRecord(root.record) ||
|
||||||
!asPredicate(AppBskyFeedPost.validateRecord)(root.record)
|
!bsky.validate(root.record, AppBskyFeedPost.validateRecord)
|
||||||
) {
|
) {
|
||||||
this.isOrphan = true
|
this.isOrphan = true
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {$Typed, asPredicate,ComAtprotoLabelDefs} from '@atproto/api'
|
import {$Typed, ComAtprotoLabelDefs} from '@atproto/api'
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
@@ -12,6 +12,7 @@ import {useSession} from '#/state/session'
|
|||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import * as Toggle from '#/components/forms/Toggle'
|
import * as Toggle from '#/components/forms/Toggle'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
import * as bsky from '#/types/bsky'
|
||||||
|
|
||||||
export function PwiOptOut() {
|
export function PwiOptOut() {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
@@ -33,9 +34,10 @@ export function PwiOptOut() {
|
|||||||
profile,
|
profile,
|
||||||
updates: existing => {
|
updates: existing => {
|
||||||
// create labels attr if needed
|
// create labels attr if needed
|
||||||
const labels: $Typed<ComAtprotoLabelDefs.SelfLabels> = asPredicate(
|
const labels: $Typed<ComAtprotoLabelDefs.SelfLabels> = bsky.validate(
|
||||||
|
existing.labels,
|
||||||
ComAtprotoLabelDefs.validateSelfLabels,
|
ComAtprotoLabelDefs.validateSelfLabels,
|
||||||
)(existing.labels)
|
)
|
||||||
? existing.labels
|
? existing.labels
|
||||||
: {
|
: {
|
||||||
$type: 'com.atproto.label.defs#selfLabels',
|
$type: 'com.atproto.label.defs#selfLabels',
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {
|
import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api'
|
||||||
AppBskyGraphDefs,
|
|
||||||
AppBskyGraphStarterpack,
|
|
||||||
asPredicate,
|
|
||||||
} from '@atproto/api'
|
|
||||||
import {GeneratorView} from '@atproto/api/dist/client/types/app/bsky/feed/defs'
|
import {GeneratorView} from '@atproto/api/dist/client/types/app/bsky/feed/defs'
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
|
|
||||||
@@ -128,7 +124,7 @@ export function Provider({
|
|||||||
const createInitialState = (): State => {
|
const createInitialState = (): State => {
|
||||||
if (
|
if (
|
||||||
starterPack &&
|
starterPack &&
|
||||||
asPredicate(AppBskyGraphStarterpack.validateRecord)(starterPack.record)
|
bsky.validate(starterPack.record, AppBskyGraphStarterpack.validateRecord)
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
canNext: true,
|
canNext: true,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
AppBskyEmbedRecordWithMedia,
|
AppBskyEmbedRecordWithMedia,
|
||||||
AppBskyFeedDefs,
|
AppBskyFeedDefs,
|
||||||
AppBskyFeedPostgate,
|
AppBskyFeedPostgate,
|
||||||
asPredicate,
|
|
||||||
AtUri,
|
AtUri,
|
||||||
BskyAgent,
|
BskyAgent,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
@@ -22,6 +21,7 @@ import {
|
|||||||
POSTGATE_COLLECTION,
|
POSTGATE_COLLECTION,
|
||||||
} from '#/state/queries/postgate/util'
|
} from '#/state/queries/postgate/util'
|
||||||
import {useAgent} from '#/state/session'
|
import {useAgent} from '#/state/session'
|
||||||
|
import * as bsky from '#/types/bsky'
|
||||||
|
|
||||||
export async function getPostgateRecord({
|
export async function getPostgateRecord({
|
||||||
agent,
|
agent,
|
||||||
@@ -63,7 +63,7 @@ export async function getPostgateRecord({
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
data.value &&
|
data.value &&
|
||||||
asPredicate(AppBskyFeedPostgate.validateRecord)(data.value)
|
bsky.validate(data.value, AppBskyFeedPostgate.validateRecord)
|
||||||
) {
|
) {
|
||||||
return data.value
|
return data.value
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
AppBskyGraphGetStarterPack,
|
AppBskyGraphGetStarterPack,
|
||||||
AppBskyGraphStarterpack,
|
AppBskyGraphStarterpack,
|
||||||
AppBskyRichtextFacet,
|
AppBskyRichtextFacet,
|
||||||
asPredicate,
|
|
||||||
AtUri,
|
AtUri,
|
||||||
BskyAgent,
|
BskyAgent,
|
||||||
RichText,
|
RichText,
|
||||||
@@ -369,7 +368,7 @@ export async function precacheStarterPack(
|
|||||||
starterPackView = starterPack
|
starterPackView = starterPack
|
||||||
} else if (
|
} else if (
|
||||||
AppBskyGraphDefs.isStarterPackViewBasic(starterPack) &&
|
AppBskyGraphDefs.isStarterPackViewBasic(starterPack) &&
|
||||||
asPredicate(AppBskyGraphStarterpack.validateRecord)(starterPack.record)
|
bsky.validate(starterPack.record, AppBskyGraphStarterpack.validateRecord)
|
||||||
) {
|
) {
|
||||||
const listView: AppBskyGraphDefs.ListViewBasic = {
|
const listView: AppBskyGraphDefs.ListViewBasic = {
|
||||||
uri: starterPack.record.list,
|
uri: starterPack.record.list,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import {
|
|||||||
AppBskyFeedDefs,
|
AppBskyFeedDefs,
|
||||||
AppBskyFeedGetPostThread,
|
AppBskyFeedGetPostThread,
|
||||||
AppBskyFeedThreadgate,
|
AppBskyFeedThreadgate,
|
||||||
asPredicate,
|
|
||||||
AtUri,
|
AtUri,
|
||||||
BskyAgent,
|
BskyAgent,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
@@ -21,6 +20,7 @@ import {
|
|||||||
} from '#/state/queries/threadgate/util'
|
} from '#/state/queries/threadgate/util'
|
||||||
import {useAgent} from '#/state/session'
|
import {useAgent} from '#/state/session'
|
||||||
import {useThreadgateHiddenReplyUrisAPI} from '#/state/threadgate-hidden-replies'
|
import {useThreadgateHiddenReplyUrisAPI} from '#/state/threadgate-hidden-replies'
|
||||||
|
import * as bsky from '#/types/bsky'
|
||||||
|
|
||||||
export * from '#/state/queries/threadgate/types'
|
export * from '#/state/queries/threadgate/types'
|
||||||
export * from '#/state/queries/threadgate/util'
|
export * from '#/state/queries/threadgate/util'
|
||||||
@@ -141,7 +141,7 @@ export async function getThreadgateRecord({
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
data.value &&
|
data.value &&
|
||||||
asPredicate(AppBskyFeedThreadgate.validateRecord)(data.value)
|
bsky.validate(data.value, AppBskyFeedThreadgate.validateRecord)
|
||||||
) {
|
) {
|
||||||
return data.value
|
return data.value
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {AppBskyFeedDefs, AppBskyFeedThreadgate, asPredicate} from '@atproto/api'
|
import {AppBskyFeedDefs, AppBskyFeedThreadgate} from '@atproto/api'
|
||||||
|
|
||||||
import {ThreadgateAllowUISetting} from '#/state/queries/threadgate/types'
|
import {ThreadgateAllowUISetting} from '#/state/queries/threadgate/types'
|
||||||
|
import * as bsky from '#/types/bsky'
|
||||||
|
|
||||||
export function threadgateViewToAllowUISetting(
|
export function threadgateViewToAllowUISetting(
|
||||||
threadgateView: AppBskyFeedDefs.ThreadgateView | undefined,
|
threadgateView: AppBskyFeedDefs.ThreadgateView | undefined,
|
||||||
@@ -8,7 +9,7 @@ export function threadgateViewToAllowUISetting(
|
|||||||
// Validate the record for clarity, since backwards compat code is a little confusing
|
// Validate the record for clarity, since backwards compat code is a little confusing
|
||||||
const threadgate =
|
const threadgate =
|
||||||
threadgateView &&
|
threadgateView &&
|
||||||
asPredicate(AppBskyFeedThreadgate.validateRecord)(threadgateView.record)
|
bsky.validate(threadgateView.record, AppBskyFeedThreadgate.validateRecord)
|
||||||
? threadgateView.record
|
? threadgateView.record
|
||||||
: undefined
|
: undefined
|
||||||
return threadgateRecordToAllowUISetting(threadgate)
|
return threadgateRecordToAllowUISetting(threadgate)
|
||||||
|
|||||||
+31
-7
@@ -1,19 +1,22 @@
|
|||||||
|
import {asPredicate} from '@atproto/api'
|
||||||
|
import {ValidationResult} from '@atproto/lexicon'
|
||||||
|
|
||||||
export * as profile from '#/types/bsky/profile'
|
export * as profile from '#/types/bsky/profile'
|
||||||
export * as starterPack from '#/types/bsky/starterPack'
|
export * as starterPack from '#/types/bsky/starterPack'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use sparingly, and only when you know it's safe to do so.
|
* Fast type checking without full schema validation, for use with data we
|
||||||
|
* trust, or for non-critical path use cases. Why? Our SDK's `is*` identity
|
||||||
|
* utils do not assert the type of the entire object, only the `$type` string.
|
||||||
*
|
*
|
||||||
* Our SDK's `is*` identity utils do not assert the type of the entire object,
|
* For full validation of the object schema, use the `validate` export from
|
||||||
* and although the `isValid*` utils do, they also fully validate the object
|
* this file.
|
||||||
* shape, which has a performance cost. This util allows us to prescribe the
|
|
||||||
* type we expect, while only checking the `$type` value of the record.
|
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* ```ts
|
* ```ts
|
||||||
* import * as atp from '#/types/atproto'
|
* import * as bsky from '#/types/bsky'
|
||||||
*
|
*
|
||||||
* if (atp.dangerousIsType<AppBskyFeedPost.Record>(item, AppBskyFeedPost.isRecord)) {
|
* if (bsky.dangerousIsType<AppBskyFeedPost.Record>(item, AppBskyFeedPost.isRecord)) {
|
||||||
* // `item` has type `$Typed<AppBskyFeedPost.Record>` here
|
* // `item` has type `$Typed<AppBskyFeedPost.Record>` here
|
||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
@@ -24,3 +27,24 @@ export function dangerousIsType<R extends {$type?: string}>(
|
|||||||
): record is R {
|
): record is R {
|
||||||
return identity(record)
|
return identity(record)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fully validates the object schema, which as a performance cost.
|
||||||
|
*
|
||||||
|
* For faster checks with data we trust, like that from our app view, use the
|
||||||
|
* `dangerousIsType` export from this same file.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* ```ts
|
||||||
|
* import * as bsky from '#/types/bsky'
|
||||||
|
*
|
||||||
|
* if (bsky.validate(item, AppBskyFeedPost.validateRecord)) {
|
||||||
|
* // `item` has type `$Typed<AppBskyFeedPost.Record>` here
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export function validate<R extends {$type?: string}>(
|
||||||
|
record: unknown,
|
||||||
|
identity: (v: unknown) => ValidationResult<R>,
|
||||||
|
): record is R {
|
||||||
|
return asPredicate(identity)(record)
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
|
|||||||
import {
|
import {
|
||||||
AppBskyFeedDefs,
|
AppBskyFeedDefs,
|
||||||
AppBskyFeedPost,
|
AppBskyFeedPost,
|
||||||
asPredicate,
|
|
||||||
AtUri,
|
AtUri,
|
||||||
ModerationDecision,
|
ModerationDecision,
|
||||||
RichText as RichTextAPI,
|
RichText as RichTextAPI,
|
||||||
@@ -29,6 +28,7 @@ import {atoms as a} from '#/alf'
|
|||||||
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
||||||
import {RichText} from '#/components/RichText'
|
import {RichText} from '#/components/RichText'
|
||||||
import {SubtleWebHover} from '#/components/SubtleWebHover'
|
import {SubtleWebHover} from '#/components/SubtleWebHover'
|
||||||
|
import * as bsky from '#/types/bsky'
|
||||||
import {ContentHider} from '../../../components/moderation/ContentHider'
|
import {ContentHider} from '../../../components/moderation/ContentHider'
|
||||||
import {LabelsOnMyPost} from '../../../components/moderation/LabelsOnMe'
|
import {LabelsOnMyPost} from '../../../components/moderation/LabelsOnMe'
|
||||||
import {PostAlerts} from '../../../components/moderation/PostAlerts'
|
import {PostAlerts} from '../../../components/moderation/PostAlerts'
|
||||||
@@ -54,7 +54,7 @@ export function Post({
|
|||||||
const moderationOpts = useModerationOpts()
|
const moderationOpts = useModerationOpts()
|
||||||
const record = useMemo<AppBskyFeedPost.Record | undefined>(
|
const record = useMemo<AppBskyFeedPost.Record | undefined>(
|
||||||
() =>
|
() =>
|
||||||
asPredicate(AppBskyFeedPost.validateRecord)(post.record)
|
bsky.validate(post.record, AppBskyFeedPost.validateRecord)
|
||||||
? post.record
|
? post.record
|
||||||
: undefined,
|
: undefined,
|
||||||
[post],
|
[post],
|
||||||
|
|||||||
@@ -282,6 +282,17 @@
|
|||||||
multiformats "^9.9.0"
|
multiformats "^9.9.0"
|
||||||
zod "^3.23.8"
|
zod "^3.23.8"
|
||||||
|
|
||||||
|
"@atproto/lexicon@0.4.6-next.5", "@atproto/lexicon@^0.4.6-next.5":
|
||||||
|
version "0.4.6-next.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.4.6-next.5.tgz#433a941cf8902afec15135f70cf2cbed37c35406"
|
||||||
|
integrity sha512-Id1atQ1rZ3dUGAUyBcauD+zs5lGHSmoAlC1Kwb2DEvkaWMv3oTXUjtdQxyb0/Kz4YaFI+nxLLRH9NBO+BipG+w==
|
||||||
|
dependencies:
|
||||||
|
"@atproto/common-web" "^0.3.2"
|
||||||
|
"@atproto/syntax" "^0.3.1"
|
||||||
|
iso-datestring-validator "^2.2.2"
|
||||||
|
multiformats "^9.9.0"
|
||||||
|
zod "^3.23.8"
|
||||||
|
|
||||||
"@atproto/lexicon@^0.4.4":
|
"@atproto/lexicon@^0.4.4":
|
||||||
version "0.4.4"
|
version "0.4.4"
|
||||||
resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.4.4.tgz#0d97314bb57b693b76f2495fa5e02872469dd93a"
|
resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.4.4.tgz#0d97314bb57b693b76f2495fa5e02872469dd93a"
|
||||||
@@ -304,17 +315,6 @@
|
|||||||
multiformats "^9.9.0"
|
multiformats "^9.9.0"
|
||||||
zod "^3.23.8"
|
zod "^3.23.8"
|
||||||
|
|
||||||
"@atproto/lexicon@^0.4.6-next.5":
|
|
||||||
version "0.4.6-next.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.4.6-next.5.tgz#433a941cf8902afec15135f70cf2cbed37c35406"
|
|
||||||
integrity sha512-Id1atQ1rZ3dUGAUyBcauD+zs5lGHSmoAlC1Kwb2DEvkaWMv3oTXUjtdQxyb0/Kz4YaFI+nxLLRH9NBO+BipG+w==
|
|
||||||
dependencies:
|
|
||||||
"@atproto/common-web" "^0.3.2"
|
|
||||||
"@atproto/syntax" "^0.3.1"
|
|
||||||
iso-datestring-validator "^2.2.2"
|
|
||||||
multiformats "^9.9.0"
|
|
||||||
zod "^3.23.8"
|
|
||||||
|
|
||||||
"@atproto/oauth-provider@^0.2.10":
|
"@atproto/oauth-provider@^0.2.10":
|
||||||
version "0.2.10"
|
version "0.2.10"
|
||||||
resolved "https://registry.yarnpkg.com/@atproto/oauth-provider/-/oauth-provider-0.2.10.tgz#f9820d7f82c33d3b74e81a75873f50e1e654b901"
|
resolved "https://registry.yarnpkg.com/@atproto/oauth-provider/-/oauth-provider-0.2.10.tgz#f9820d7f82c33d3b74e81a75873f50e1e654b901"
|
||||||
|
|||||||
Reference in New Issue
Block a user