@atproto/bsky 0.0.200 → 0.0.202
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/dist/api/app/bsky/ageassurance/begin.d.ts.map +1 -1
- package/dist/api/app/bsky/ageassurance/begin.js +15 -9
- package/dist/api/app/bsky/ageassurance/begin.js.map +1 -1
- package/dist/api/app/bsky/contact/sendNotification.d.ts +4 -0
- package/dist/api/app/bsky/contact/sendNotification.d.ts.map +1 -0
- package/dist/api/app/bsky/contact/sendNotification.js +30 -0
- package/dist/api/app/bsky/contact/sendNotification.js.map +1 -0
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +2 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/sitemap.d.ts +4 -0
- package/dist/api/sitemap.d.ts.map +1 -0
- package/dist/api/sitemap.js +67 -0
- package/dist/api/sitemap.js.map +1 -0
- package/dist/data-plane/server/routes/index.d.ts.map +1 -1
- package/dist/data-plane/server/routes/index.js +2 -0
- package/dist/data-plane/server/routes/index.js.map +1 -1
- package/dist/data-plane/server/routes/profile.d.ts.map +1 -1
- package/dist/data-plane/server/routes/profile.js +10 -8
- package/dist/data-plane/server/routes/profile.js.map +1 -1
- package/dist/data-plane/server/routes/sitemap.d.ts +5 -0
- package/dist/data-plane/server/routes/sitemap.d.ts.map +1 -0
- package/dist/data-plane/server/routes/sitemap.js +38 -0
- package/dist/data-plane/server/routes/sitemap.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/lexicon/index.d.ts +2 -0
- package/dist/lexicon/index.d.ts.map +1 -1
- package/dist/lexicon/index.js +4 -0
- package/dist/lexicon/index.js.map +1 -1
- package/dist/lexicon/lexicons.d.ts +146 -4
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +74 -0
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/app/bsky/actor/defs.d.ts +13 -1
- package/dist/lexicon/types/app/bsky/actor/defs.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/actor/defs.js +9 -0
- package/dist/lexicon/types/app/bsky/actor/defs.js.map +1 -1
- package/dist/lexicon/types/app/bsky/contact/defs.d.ts +10 -0
- package/dist/lexicon/types/app/bsky/contact/defs.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/contact/defs.js +9 -0
- package/dist/lexicon/types/app/bsky/contact/defs.js.map +1 -1
- package/dist/lexicon/types/app/bsky/contact/sendNotification.d.ts +26 -0
- package/dist/lexicon/types/app/bsky/contact/sendNotification.d.ts.map +1 -0
- package/dist/lexicon/types/app/bsky/contact/sendNotification.js +7 -0
- package/dist/lexicon/types/app/bsky/contact/sendNotification.js.map +1 -0
- package/dist/lexicon/types/app/bsky/notification/listNotifications.d.ts +1 -1
- package/dist/lexicon/types/app/bsky/notification/listNotifications.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/notification/listNotifications.js.map +1 -1
- package/dist/proto/bsky_connect.d.ts +21 -1
- package/dist/proto/bsky_connect.d.ts.map +1 -1
- package/dist/proto/bsky_connect.js +20 -0
- package/dist/proto/bsky_connect.js.map +1 -1
- package/dist/proto/bsky_pb.d.ts +97 -0
- package/dist/proto/bsky_pb.d.ts.map +1 -1
- package/dist/proto/bsky_pb.js +256 -5
- package/dist/proto/bsky_pb.js.map +1 -1
- package/dist/stash.d.ts +2 -1
- package/dist/stash.d.ts.map +1 -1
- package/dist/stash.js +2 -1
- package/dist/stash.js.map +1 -1
- package/package.json +9 -9
- package/proto/bsky.proto +31 -0
- package/src/api/app/bsky/ageassurance/begin.ts +21 -14
- package/src/api/app/bsky/contact/sendNotification.ts +32 -0
- package/src/api/index.ts +2 -0
- package/src/api/sitemap.ts +76 -0
- package/src/data-plane/server/routes/index.ts +2 -0
- package/src/data-plane/server/routes/profile.ts +8 -6
- package/src/data-plane/server/routes/sitemap.ts +43 -0
- package/src/index.ts +6 -1
- package/src/lexicon/index.ts +13 -0
- package/src/lexicon/lexicons.ts +80 -0
- package/src/lexicon/types/app/bsky/actor/defs.ts +22 -0
- package/src/lexicon/types/app/bsky/contact/defs.ts +19 -0
- package/src/lexicon/types/app/bsky/contact/sendNotification.ts +44 -0
- package/src/lexicon/types/app/bsky/notification/listNotifications.ts +1 -0
- package/src/proto/bsky_connect.ts +21 -1
- package/src/proto/bsky_pb.ts +188 -0
- package/src/stash.ts +5 -2
- package/tests/sitemap.test.ts +75 -0
- package/tests/views/age-assurance-v2.test.ts +59 -1
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.tests.tsbuildinfo +1 -1
package/dist/stash.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { LexValue } from '@atproto/lexicon';
|
|
2
2
|
import { BsyncClient } from './bsync';
|
|
3
3
|
export declare const Namespaces: {
|
|
4
|
+
AppBskyAgeassuranceDefsEvent: string;
|
|
4
5
|
AppBskyBookmarkDefsBookmark: string;
|
|
6
|
+
AppBskyContactDefsNotification: string;
|
|
5
7
|
AppBskyNotificationDefsPreferences: string;
|
|
6
8
|
AppBskyNotificationDefsSubjectActivitySubscription: string;
|
|
7
9
|
AppBskyUnspeccedDefsAgeAssuranceEvent: string;
|
|
8
|
-
AppBskyAgeassuranceDefsEvent: string;
|
|
9
10
|
};
|
|
10
11
|
export type Namespace = (typeof Namespaces)[keyof typeof Namespaces];
|
|
11
12
|
export declare const createStashClient: (bsyncClient: BsyncClient) => StashClient;
|
package/dist/stash.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stash.d.ts","sourceRoot":"","sources":["../src/stash.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAgB,MAAM,kBAAkB,CAAA;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"stash.d.ts","sourceRoot":"","sources":["../src/stash.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAgB,MAAM,kBAAkB,CAAA;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAcrC,eAAO,MAAM,UAAU;;;;;;;CAatB,CAAA;AAED,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,OAAO,UAAU,CAAC,CAAA;AAEpE,eAAO,MAAM,iBAAiB,GAAI,aAAa,WAAW,KAAG,WAE5D,CAAA;AAID,qBAAa,WAAW;IACV,OAAO,CAAC,QAAQ,CAAC,WAAW;gBAAX,WAAW,EAAE,WAAW;IAErD,MAAM,CAAC,KAAK,EAAE,WAAW;IAKzB,MAAM,CAAC,KAAK,EAAE,WAAW;IAKzB,MAAM,CAAC,KAAK,EAAE,WAAW;IAIzB,OAAO,CAAC,eAAe;YAOT,YAAY;CAiB3B;AASD,KAAK,WAAW,GAAG;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,SAAS,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,QAAQ,CAAA;CAClB,CAAA;AAED,KAAK,WAAW,GAAG,WAAW,CAAA;AAE9B,KAAK,WAAW,GAAG;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,SAAS,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA"}
|
package/dist/stash.js
CHANGED
|
@@ -6,11 +6,12 @@ const lexicon_1 = require("@atproto/lexicon");
|
|
|
6
6
|
const lexicons_1 = require("./lexicon/lexicons");
|
|
7
7
|
const bsync_pb_1 = require("./proto/bsync_pb");
|
|
8
8
|
exports.Namespaces = {
|
|
9
|
+
AppBskyAgeassuranceDefsEvent: 'app.bsky.ageassurance.defs#event',
|
|
9
10
|
AppBskyBookmarkDefsBookmark: 'app.bsky.bookmark.defs#bookmark',
|
|
11
|
+
AppBskyContactDefsNotification: 'app.bsky.contact.defs#notification',
|
|
10
12
|
AppBskyNotificationDefsPreferences: 'app.bsky.notification.defs#preferences',
|
|
11
13
|
AppBskyNotificationDefsSubjectActivitySubscription: 'app.bsky.notification.defs#subjectActivitySubscription',
|
|
12
14
|
AppBskyUnspeccedDefsAgeAssuranceEvent: 'app.bsky.unspecced.defs#ageAssuranceEvent',
|
|
13
|
-
AppBskyAgeassuranceDefsEvent: 'app.bsky.ageassurance.defs#event',
|
|
14
15
|
};
|
|
15
16
|
const createStashClient = (bsyncClient) => {
|
|
16
17
|
return new StashClient(bsyncClient);
|
package/dist/stash.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stash.js","sourceRoot":"","sources":["../src/stash.ts"],"names":[],"mappings":";AAAA,yCAAyC;;;AAEzC,8CAAyD;AAEzD,iDAA6C;
|
|
1
|
+
{"version":3,"file":"stash.js","sourceRoot":"","sources":["../src/stash.ts"],"names":[],"mappings":";AAAA,yCAAyC;;;AAEzC,8CAAyD;AAEzD,iDAA6C;AAS7C,+CAAyC;AAI5B,QAAA,UAAU,GAAG;IACxB,4BAA4B,EAC1B,kCAA0E;IAC5E,2BAA2B,EACzB,iCAA8D;IAChE,8BAA8B,EAC5B,oCAAqE;IACvE,kCAAkC,EAChC,wCAAwE;IAC1E,kDAAkD,EAChD,wDAAwG;IAC1G,qCAAqC,EACnC,2CAAiF;CACpF,CAAA;AAIM,MAAM,iBAAiB,GAAG,CAAC,WAAwB,EAAe,EAAE;IACzE,OAAO,IAAI,WAAW,CAAC,WAAW,CAAC,CAAA;AACrC,CAAC,CAAA;AAFY,QAAA,iBAAiB,qBAE7B;AAED,8EAA8E;AAC9E,2FAA2F;AAC3F,MAAa,WAAW;IACtB,YAA6B,WAAwB;QAAzC;;;;mBAAiB,WAAW;WAAa;IAAG,CAAC;IAEzD,MAAM,CAAC,KAAkB;QACvB,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,CAAA;QACpD,OAAO,IAAI,CAAC,YAAY,CAAC,iBAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;IAChD,CAAC;IAED,MAAM,CAAC,KAAkB;QACvB,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,CAAA;QACpD,OAAO,IAAI,CAAC,YAAY,CAAC,iBAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;IAChD,CAAC;IAED,MAAM,CAAC,KAAkB;QACvB,OAAO,IAAI,CAAC,YAAY,CAAC,iBAAM,CAAC,MAAM,EAAE,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAA;IAC3E,CAAC;IAEO,eAAe,CAAC,SAAoB,EAAE,OAAiB;QAC7D,MAAM,MAAM,GAAG,mBAAQ,CAAC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;QACpD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,MAAM,CAAC,KAAK,CAAA;QACpB,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,YAAY,CAAC,MAAc,EAAE,KAAwB;QACjE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,KAAK,CAAA;QACnD,MAAM,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC;YAClC,QAAQ;YACR,SAAS;YACT,GAAG;YACH,MAAM;YACN,OAAO,EAAE,OAAO;gBACd,CAAC,CAAC,MAAM,CAAC,IAAI,CACT,IAAA,sBAAY,EAAC;oBACX,KAAK,EAAE,SAAS;oBAChB,GAAG,OAAO;iBACX,CAAC,CACH;gBACH,CAAC,CAAC,SAAS;SACd,CAAC,CAAA;IACJ,CAAC;CACF;AAzCD,kCAyCC","sourcesContent":["/* eslint-disable import/no-deprecated */\n\nimport { LexValue, stringifyLex } from '@atproto/lexicon'\nimport { BsyncClient } from './bsync'\nimport { lexicons } from './lexicon/lexicons'\nimport { Event as AgeAssuranceEventV2 } from './lexicon/types/app/bsky/ageassurance/defs'\nimport { Bookmark } from './lexicon/types/app/bsky/bookmark/defs'\nimport { Notification } from './lexicon/types/app/bsky/contact/defs'\nimport {\n Preferences,\n SubjectActivitySubscription,\n} from './lexicon/types/app/bsky/notification/defs'\nimport { AgeAssuranceEvent } from './lexicon/types/app/bsky/unspecced/defs'\nimport { Method } from './proto/bsync_pb'\n\ntype PickNSID<T extends { $type?: string }> = Exclude<T['$type'], undefined>\n\nexport const Namespaces = {\n AppBskyAgeassuranceDefsEvent:\n 'app.bsky.ageassurance.defs#event' satisfies PickNSID<AgeAssuranceEventV2>,\n AppBskyBookmarkDefsBookmark:\n 'app.bsky.bookmark.defs#bookmark' satisfies PickNSID<Bookmark>,\n AppBskyContactDefsNotification:\n 'app.bsky.contact.defs#notification' satisfies PickNSID<Notification>,\n AppBskyNotificationDefsPreferences:\n 'app.bsky.notification.defs#preferences' satisfies PickNSID<Preferences>,\n AppBskyNotificationDefsSubjectActivitySubscription:\n 'app.bsky.notification.defs#subjectActivitySubscription' satisfies PickNSID<SubjectActivitySubscription>,\n AppBskyUnspeccedDefsAgeAssuranceEvent:\n 'app.bsky.unspecced.defs#ageAssuranceEvent' satisfies PickNSID<AgeAssuranceEvent>,\n}\n\nexport type Namespace = (typeof Namespaces)[keyof typeof Namespaces]\n\nexport const createStashClient = (bsyncClient: BsyncClient): StashClient => {\n return new StashClient(bsyncClient)\n}\n\n// An abstraction over the BsyncClient, that uses the bsync `PutOperation` RPC\n// to store private data, which can be indexed by the dataplane and queried by the appview.\nexport class StashClient {\n constructor(private readonly bsyncClient: BsyncClient) {}\n\n create(input: CreateInput) {\n this.validateLexicon(input.namespace, input.payload)\n return this.putOperation(Method.CREATE, input)\n }\n\n update(input: UpdateInput) {\n this.validateLexicon(input.namespace, input.payload)\n return this.putOperation(Method.UPDATE, input)\n }\n\n delete(input: DeleteInput) {\n return this.putOperation(Method.DELETE, { ...input, payload: undefined })\n }\n\n private validateLexicon(namespace: Namespace, payload: LexValue) {\n const result = lexicons.validate(namespace, payload)\n if (!result.success) {\n throw result.error\n }\n }\n\n private async putOperation(method: Method, input: PutOperationInput) {\n const { actorDid, namespace, key, payload } = input\n await this.bsyncClient.putOperation({\n actorDid,\n namespace,\n key,\n method,\n payload: payload\n ? Buffer.from(\n stringifyLex({\n $type: namespace,\n ...payload,\n }),\n )\n : undefined,\n })\n }\n}\n\ntype PutOperationInput = {\n actorDid: string\n namespace: Namespace\n key: string\n payload: LexValue | undefined\n}\n\ntype CreateInput = {\n actorDid: string\n namespace: Namespace\n key: string\n payload: LexValue\n}\n\ntype UpdateInput = CreateInput\n\ntype DeleteInput = {\n actorDid: string\n namespace: Namespace\n key: string\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto/bsky",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.202",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Reference implementation of app.bsky App View (Bluesky API)",
|
|
6
6
|
"keywords": [
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
"uint8arrays": "3.0.0",
|
|
52
52
|
"undici": "^6.19.8",
|
|
53
53
|
"zod": "3.23.8",
|
|
54
|
-
"@atproto-labs/xrpc-utils": "0.0.24",
|
|
55
54
|
"@atproto-labs/fetch-node": "0.2.0",
|
|
56
|
-
"@atproto/
|
|
55
|
+
"@atproto-labs/xrpc-utils": "0.0.24",
|
|
56
|
+
"@atproto/api": "^0.18.7",
|
|
57
57
|
"@atproto/common": "^0.5.3",
|
|
58
58
|
"@atproto/crypto": "^0.4.5",
|
|
59
59
|
"@atproto/did": "^0.2.3",
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
"@atproto/lexicon": "^0.6.0",
|
|
62
62
|
"@atproto/repo": "^0.8.12",
|
|
63
63
|
"@atproto/sync": "^0.1.39",
|
|
64
|
-
"@atproto/
|
|
65
|
-
"@atproto/
|
|
64
|
+
"@atproto/syntax": "^0.4.2",
|
|
65
|
+
"@atproto/xrpc-server": "^0.10.3"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@bufbuild/buf": "^1.28.1",
|
|
@@ -78,10 +78,10 @@
|
|
|
78
78
|
"jest": "^28.1.2",
|
|
79
79
|
"ts-node": "^10.8.2",
|
|
80
80
|
"typescript": "^5.6.3",
|
|
81
|
-
"@atproto/api": "^0.18.
|
|
82
|
-
"@atproto/
|
|
83
|
-
"@atproto/pds": "^0.4.
|
|
84
|
-
"@atproto/
|
|
81
|
+
"@atproto/api": "^0.18.7",
|
|
82
|
+
"@atproto/xrpc": "^0.7.7",
|
|
83
|
+
"@atproto/pds": "^0.4.199",
|
|
84
|
+
"@atproto/lex-cli": "^0.9.8"
|
|
85
85
|
},
|
|
86
86
|
"scripts": {
|
|
87
87
|
"codegen": "lex gen-server --yes ./src/lexicon ../../lexicons/com/atproto/*/* ../../lexicons/app/bsky/*/* ../../lexicons/chat/bsky/*/*",
|
package/proto/bsky.proto
CHANGED
|
@@ -1310,6 +1310,33 @@ message GetFollowsFollowingResponse {
|
|
|
1310
1310
|
repeated FollowsFollowing results = 1;
|
|
1311
1311
|
}
|
|
1312
1312
|
|
|
1313
|
+
message GetSitemapIndexRequest {
|
|
1314
|
+
SitemapPageType type = 1;
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
message GetSitemapIndexResponse {
|
|
1318
|
+
// GZIP compressed XML sitemap
|
|
1319
|
+
bytes sitemap = 1;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// Sitemap HTTP paths are typically of the form `/type/yyyy-mm-dd/N.xml.gz`, i.e. `/users/2025-01-01/1.xml.gz`
|
|
1323
|
+
message GetSitemapPageRequest {
|
|
1324
|
+
SitemapPageType type = 1;
|
|
1325
|
+
google.protobuf.Timestamp date = 2;
|
|
1326
|
+
// One-indexed
|
|
1327
|
+
int32 bucket = 3;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
enum SitemapPageType {
|
|
1331
|
+
SITEMAP_PAGE_TYPE_UNSPECIFIED = 0;
|
|
1332
|
+
SITEMAP_PAGE_TYPE_USER = 1;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
message GetSitemapPageResponse {
|
|
1336
|
+
// GZIP compressed XML sitemap
|
|
1337
|
+
bytes sitemap = 1;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1313
1340
|
// Ping
|
|
1314
1341
|
message PingRequest {}
|
|
1315
1342
|
message PingResponse {}
|
|
@@ -1470,6 +1497,10 @@ service Service {
|
|
|
1470
1497
|
// Graph
|
|
1471
1498
|
rpc GetFollowsFollowing(GetFollowsFollowingRequest) returns (GetFollowsFollowingResponse);
|
|
1472
1499
|
|
|
1500
|
+
// Sitemaps
|
|
1501
|
+
rpc GetSitemapIndex(GetSitemapIndexRequest) returns (GetSitemapIndexResponse);
|
|
1502
|
+
rpc GetSitemapPage(GetSitemapPageRequest) returns (GetSitemapPageResponse);
|
|
1503
|
+
|
|
1473
1504
|
// Ping
|
|
1474
1505
|
rpc Ping(PingRequest) returns (PingResponse);
|
|
1475
1506
|
|
|
@@ -37,17 +37,14 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
37
37
|
|
|
38
38
|
const actorDid = auth.credentials.iss
|
|
39
39
|
const actorInfo = await getAgeVerificationState(ctx, actorDid)
|
|
40
|
+
const existingStatus = actorInfo?.ageAssuranceStatus?.status
|
|
41
|
+
const existingAccess = actorInfo?.ageAssuranceStatus?.access
|
|
40
42
|
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
)
|
|
46
|
-
throw new InvalidRequestError(
|
|
47
|
-
`Cannot initiate age assurance flow from current state: ${actorInfo.ageAssuranceStatus.status}`,
|
|
48
|
-
'InvalidInitiation',
|
|
49
|
-
)
|
|
50
|
-
}
|
|
43
|
+
if (existingStatus === 'blocked') {
|
|
44
|
+
throw new InvalidRequestError(
|
|
45
|
+
`Cannot initiate age assurance flow from current state: ${existingStatus}`,
|
|
46
|
+
'InvalidInitiation',
|
|
47
|
+
)
|
|
51
48
|
}
|
|
52
49
|
|
|
53
50
|
const attemptId = crypto.randomUUID()
|
|
@@ -110,14 +107,24 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
110
107
|
})
|
|
111
108
|
}
|
|
112
109
|
|
|
110
|
+
// If we have existing status/access for this region, retain it.
|
|
111
|
+
const nextStatus =
|
|
112
|
+
existingStatus && existingStatus !== 'unknown'
|
|
113
|
+
? existingStatus
|
|
114
|
+
: 'pending'
|
|
115
|
+
const nextAccess =
|
|
116
|
+
existingAccess && existingAccess !== 'unknown'
|
|
117
|
+
? existingAccess
|
|
118
|
+
: 'unknown'
|
|
119
|
+
|
|
113
120
|
const event = await createEvent(ctx, actorDid, {
|
|
114
121
|
attemptId,
|
|
115
122
|
email,
|
|
116
123
|
// Assumes `app.set('trust proxy', ...)` configured with `true` or specific values.
|
|
117
124
|
initIp: req.ip,
|
|
118
125
|
initUa: getClientUa(req),
|
|
119
|
-
status:
|
|
120
|
-
access:
|
|
126
|
+
status: nextStatus,
|
|
127
|
+
access: nextAccess,
|
|
121
128
|
countryCode,
|
|
122
129
|
regionCode,
|
|
123
130
|
})
|
|
@@ -126,8 +133,8 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
126
133
|
encoding: 'application/json',
|
|
127
134
|
body: {
|
|
128
135
|
lastInitiatedAt: event.createdAt,
|
|
129
|
-
status:
|
|
130
|
-
access:
|
|
136
|
+
status: nextStatus,
|
|
137
|
+
access: nextAccess,
|
|
131
138
|
},
|
|
132
139
|
}
|
|
133
140
|
},
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { TID } from '@atproto/common'
|
|
2
|
+
import { AppContext } from '../../../../context'
|
|
3
|
+
import { Server } from '../../../../lexicon'
|
|
4
|
+
import { Namespaces } from '../../../../stash'
|
|
5
|
+
import { assertRolodexOrThrowUnimplemented } from './util'
|
|
6
|
+
|
|
7
|
+
export default function (server: Server, ctx: AppContext) {
|
|
8
|
+
server.app.bsky.contact.sendNotification({
|
|
9
|
+
auth: ctx.authVerifier.role,
|
|
10
|
+
handler: async ({ input }) => {
|
|
11
|
+
// Assert rolodex even though we don't call it, it is a proxy to whether the app is configured with contact import support.
|
|
12
|
+
assertRolodexOrThrowUnimplemented(ctx)
|
|
13
|
+
|
|
14
|
+
const { from, to } = input.body
|
|
15
|
+
|
|
16
|
+
await ctx.stashClient.create({
|
|
17
|
+
actorDid: from,
|
|
18
|
+
namespace: Namespaces.AppBskyContactDefsNotification,
|
|
19
|
+
payload: {
|
|
20
|
+
from,
|
|
21
|
+
to,
|
|
22
|
+
},
|
|
23
|
+
key: TID.nextStr(),
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
encoding: 'application/json',
|
|
28
|
+
body: {},
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
}
|
package/src/api/index.ts
CHANGED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Readable } from 'node:stream'
|
|
2
|
+
import { Timestamp } from '@bufbuild/protobuf'
|
|
3
|
+
import { Code, ConnectError } from '@connectrpc/connect'
|
|
4
|
+
import express, { RequestHandler, Router } from 'express'
|
|
5
|
+
import { AppContext } from '../context'
|
|
6
|
+
import { httpLogger as log } from '../logger'
|
|
7
|
+
import { SitemapPageType } from '../proto/bsky_pb'
|
|
8
|
+
|
|
9
|
+
export const createRouter = (ctx: AppContext): Router => {
|
|
10
|
+
const router = Router()
|
|
11
|
+
router.get('/external/sitemap/users.xml.gz', userIndexHandler(ctx))
|
|
12
|
+
router.get(
|
|
13
|
+
'/external/sitemap/users/:date/:bucket.xml.gz',
|
|
14
|
+
userPageHandler(ctx),
|
|
15
|
+
)
|
|
16
|
+
return router
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const userIndexHandler =
|
|
20
|
+
(ctx: AppContext): RequestHandler =>
|
|
21
|
+
async (_req: express.Request, res: express.Response) => {
|
|
22
|
+
try {
|
|
23
|
+
const result = await ctx.dataplane.getSitemapIndex({
|
|
24
|
+
type: SitemapPageType.USER,
|
|
25
|
+
})
|
|
26
|
+
res.set('Content-Type', 'application/gzip')
|
|
27
|
+
res.set('Content-Encoding', 'gzip')
|
|
28
|
+
Readable.from(Buffer.from(result.sitemap)).pipe(res)
|
|
29
|
+
} catch (err) {
|
|
30
|
+
log.error({ err }, 'failed to get sitemap index')
|
|
31
|
+
return res.status(500).send('Internal Server Error')
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const userPageHandler =
|
|
36
|
+
(ctx: AppContext): RequestHandler =>
|
|
37
|
+
async (req: express.Request, res: express.Response) => {
|
|
38
|
+
const { date, bucket } = req.params
|
|
39
|
+
|
|
40
|
+
// Parse date (YYYY-MM-DD format)
|
|
41
|
+
const dateParts = date.split('-')
|
|
42
|
+
if (dateParts.length !== 3) {
|
|
43
|
+
return res.status(400).send('Invalid date format. Expected YYYY-MM-DD')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const year = parseInt(dateParts[0], 10)
|
|
47
|
+
const month = parseInt(dateParts[1], 10)
|
|
48
|
+
const day = parseInt(dateParts[2], 10)
|
|
49
|
+
|
|
50
|
+
if (isNaN(year) || isNaN(month) || isNaN(day)) {
|
|
51
|
+
return res.status(400).send('Invalid date format. Expected YYYY-MM-DD')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Parse bucket (1-indexed)
|
|
55
|
+
const bucketNum = parseInt(bucket, 10)
|
|
56
|
+
if (isNaN(bucketNum) || bucketNum < 1) {
|
|
57
|
+
return res.status(400).send('Invalid bucket number')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const result = await ctx.dataplane.getSitemapPage({
|
|
62
|
+
type: SitemapPageType.USER,
|
|
63
|
+
date: Timestamp.fromDate(new Date(year, month - 1, day)),
|
|
64
|
+
bucket: bucketNum,
|
|
65
|
+
})
|
|
66
|
+
res.set('Content-Type', 'application/gzip')
|
|
67
|
+
res.set('Content-Encoding', 'gzip')
|
|
68
|
+
Readable.from(Buffer.from(result.sitemap)).pipe(res)
|
|
69
|
+
} catch (err) {
|
|
70
|
+
if (err instanceof ConnectError && err.code === Code.NotFound) {
|
|
71
|
+
return res.status(404).send('Sitemap page not found')
|
|
72
|
+
}
|
|
73
|
+
log.error({ err }, 'failed to get sitemap page')
|
|
74
|
+
return res.status(500).send('Internal Server Error')
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -22,6 +22,7 @@ import records from './records'
|
|
|
22
22
|
import relationships from './relationships'
|
|
23
23
|
import reposts from './reposts'
|
|
24
24
|
import search from './search'
|
|
25
|
+
import sitemap from './sitemap'
|
|
25
26
|
import starterPacks from './starter-packs'
|
|
26
27
|
import suggestions from './suggestions'
|
|
27
28
|
import sync from './sync'
|
|
@@ -50,6 +51,7 @@ export default (db: Database, idResolver: IdResolver) =>
|
|
|
50
51
|
...relationships(db),
|
|
51
52
|
...reposts(db),
|
|
52
53
|
...search(db),
|
|
54
|
+
...sitemap(),
|
|
53
55
|
...suggestions(db),
|
|
54
56
|
...sync(db),
|
|
55
57
|
...threads(db),
|
|
@@ -136,12 +136,14 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
|
|
|
136
136
|
|
|
137
137
|
const status = row?.ageAssuranceStatus ?? 'unknown'
|
|
138
138
|
let access = row?.ageAssuranceAccess
|
|
139
|
-
if (
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
139
|
+
if (!access || access === 'unknown') {
|
|
140
|
+
if (status === 'assured') {
|
|
141
|
+
access = 'full'
|
|
142
|
+
} else if (status === 'blocked') {
|
|
143
|
+
access = 'none'
|
|
144
|
+
} else {
|
|
145
|
+
access = 'unknown'
|
|
146
|
+
}
|
|
145
147
|
}
|
|
146
148
|
|
|
147
149
|
return {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { gzipSync } from 'node:zlib'
|
|
2
|
+
import { Code, ConnectError, ServiceImpl } from '@connectrpc/connect'
|
|
3
|
+
import { Service } from '../../../proto/bsky_connect'
|
|
4
|
+
import { GetSitemapPageRequest } from '../../../proto/bsky_pb'
|
|
5
|
+
|
|
6
|
+
const MOCK_SITEMAP_INDEX = `<?xml version="1.0" encoding="UTF-8"?>
|
|
7
|
+
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
8
|
+
<sitemap>
|
|
9
|
+
<loc>https://bsky.app/sitemap/users/2025-01-01/1.xml.gz</loc>
|
|
10
|
+
</sitemap>
|
|
11
|
+
</sitemapindex>`
|
|
12
|
+
|
|
13
|
+
const MOCK_SITEMAP_PAGE = `<?xml version="1.0" encoding="UTF-8"?>
|
|
14
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
15
|
+
<url>
|
|
16
|
+
<loc>https://bsky.app/profile/test.bsky.social</loc>
|
|
17
|
+
</url>
|
|
18
|
+
</urlset>`
|
|
19
|
+
|
|
20
|
+
export default (): Partial<ServiceImpl<typeof Service>> => ({
|
|
21
|
+
async getSitemapIndex() {
|
|
22
|
+
return {
|
|
23
|
+
sitemap: gzipSync(Buffer.from(MOCK_SITEMAP_INDEX)),
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
async getSitemapPage(req: GetSitemapPageRequest) {
|
|
27
|
+
const date = req.date?.toDate()
|
|
28
|
+
const isExpectedDate =
|
|
29
|
+
date &&
|
|
30
|
+
date.getFullYear() === 2025 &&
|
|
31
|
+
date.getMonth() === 0 &&
|
|
32
|
+
date.getDate() === 1
|
|
33
|
+
const isExpectedBucket = req.bucket === 1
|
|
34
|
+
|
|
35
|
+
if (!isExpectedDate || !isExpectedBucket) {
|
|
36
|
+
throw new ConnectError('Sitemap page not found', Code.NotFound)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
sitemap: gzipSync(Buffer.from(MOCK_SITEMAP_PAGE)),
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
})
|
package/src/index.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { AtpAgent } from '@atproto/api'
|
|
|
10
10
|
import { DAY, SECOND } from '@atproto/common'
|
|
11
11
|
import { Keypair } from '@atproto/crypto'
|
|
12
12
|
import { IdResolver } from '@atproto/identity'
|
|
13
|
-
import API, { blobResolver, external, health, wellKnown } from './api'
|
|
13
|
+
import API, { blobResolver, external, health, sitemap, wellKnown } from './api'
|
|
14
14
|
import { createBlobDispatcher } from './api/blob-dispatcher'
|
|
15
15
|
import { AuthVerifier, createPublicKeyObject } from './auth-verifier'
|
|
16
16
|
import { authWithApiKey as bsyncAuth, createBsyncClient } from './bsync'
|
|
@@ -225,6 +225,11 @@ export class BskyAppView {
|
|
|
225
225
|
app.use(wellKnown.createRouter(ctx))
|
|
226
226
|
app.use(blobResolver.createMiddleware(ctx))
|
|
227
227
|
app.use(imageServer.createMiddleware(ctx, { prefix: '/img/' }))
|
|
228
|
+
|
|
229
|
+
if (config.dataplaneUrls.length > 0 || config.dataplaneUrlsEtcdKeyPrefix) {
|
|
230
|
+
app.use(sitemap.createRouter(ctx))
|
|
231
|
+
}
|
|
232
|
+
|
|
228
233
|
app.use(server.xrpc.router)
|
|
229
234
|
app.use(error.handler)
|
|
230
235
|
app.use('/external', external.createRouter(ctx))
|
package/src/lexicon/index.ts
CHANGED
|
@@ -28,6 +28,7 @@ import * as AppBskyContactGetMatches from './types/app/bsky/contact/getMatches.j
|
|
|
28
28
|
import * as AppBskyContactGetSyncStatus from './types/app/bsky/contact/getSyncStatus.js'
|
|
29
29
|
import * as AppBskyContactImportContacts from './types/app/bsky/contact/importContacts.js'
|
|
30
30
|
import * as AppBskyContactRemoveData from './types/app/bsky/contact/removeData.js'
|
|
31
|
+
import * as AppBskyContactSendNotification from './types/app/bsky/contact/sendNotification.js'
|
|
31
32
|
import * as AppBskyContactStartPhoneVerification from './types/app/bsky/contact/startPhoneVerification.js'
|
|
32
33
|
import * as AppBskyContactVerifyPhone from './types/app/bsky/contact/verifyPhone.js'
|
|
33
34
|
import * as AppBskyFeedDescribeFeedGenerator from './types/app/bsky/feed/describeFeedGenerator.js'
|
|
@@ -557,6 +558,18 @@ export class AppBskyContactNS {
|
|
|
557
558
|
return this._server.xrpc.method(nsid, cfg)
|
|
558
559
|
}
|
|
559
560
|
|
|
561
|
+
sendNotification<A extends Auth = void>(
|
|
562
|
+
cfg: MethodConfigOrHandler<
|
|
563
|
+
A,
|
|
564
|
+
AppBskyContactSendNotification.QueryParams,
|
|
565
|
+
AppBskyContactSendNotification.HandlerInput,
|
|
566
|
+
AppBskyContactSendNotification.HandlerOutput
|
|
567
|
+
>,
|
|
568
|
+
) {
|
|
569
|
+
const nsid = 'app.bsky.contact.sendNotification' // @ts-ignore
|
|
570
|
+
return this._server.xrpc.method(nsid, cfg)
|
|
571
|
+
}
|
|
572
|
+
|
|
560
573
|
startPhoneVerification<A extends Auth = void>(
|
|
561
574
|
cfg: MethodConfigOrHandler<
|
|
562
575
|
A,
|
package/src/lexicon/lexicons.ts
CHANGED
|
@@ -401,6 +401,7 @@ export const schemaDict = {
|
|
|
401
401
|
'lex:app.bsky.actor.defs#savedFeedsPref',
|
|
402
402
|
'lex:app.bsky.actor.defs#savedFeedsPrefV2',
|
|
403
403
|
'lex:app.bsky.actor.defs#personalDetailsPref',
|
|
404
|
+
'lex:app.bsky.actor.defs#declaredAgePref',
|
|
404
405
|
'lex:app.bsky.actor.defs#feedViewPref',
|
|
405
406
|
'lex:app.bsky.actor.defs#threadViewPref',
|
|
406
407
|
'lex:app.bsky.actor.defs#interestsPref',
|
|
@@ -507,6 +508,28 @@ export const schemaDict = {
|
|
|
507
508
|
},
|
|
508
509
|
},
|
|
509
510
|
},
|
|
511
|
+
declaredAgePref: {
|
|
512
|
+
type: 'object',
|
|
513
|
+
description:
|
|
514
|
+
"Read-only preference containing value(s) inferred from the user's declared birthdate. Absence of this preference object in the response indicates that the user has not made a declaration.",
|
|
515
|
+
properties: {
|
|
516
|
+
isOverAge13: {
|
|
517
|
+
type: 'boolean',
|
|
518
|
+
description:
|
|
519
|
+
'Indicates if the user has declared that they are over 13 years of age.',
|
|
520
|
+
},
|
|
521
|
+
isOverAge16: {
|
|
522
|
+
type: 'boolean',
|
|
523
|
+
description:
|
|
524
|
+
'Indicates if the user has declared that they are over 16 years of age.',
|
|
525
|
+
},
|
|
526
|
+
isOverAge18: {
|
|
527
|
+
type: 'boolean',
|
|
528
|
+
description:
|
|
529
|
+
'Indicates if the user has declared that they are over 18 years of age.',
|
|
530
|
+
},
|
|
531
|
+
},
|
|
532
|
+
},
|
|
510
533
|
feedViewPref: {
|
|
511
534
|
type: 'object',
|
|
512
535
|
required: ['feed'],
|
|
@@ -1776,6 +1799,24 @@ export const schemaDict = {
|
|
|
1776
1799
|
},
|
|
1777
1800
|
},
|
|
1778
1801
|
},
|
|
1802
|
+
notification: {
|
|
1803
|
+
description:
|
|
1804
|
+
'A stash object to be sent via bsync representing a notification to be created.',
|
|
1805
|
+
type: 'object',
|
|
1806
|
+
required: ['from', 'to'],
|
|
1807
|
+
properties: {
|
|
1808
|
+
from: {
|
|
1809
|
+
description: 'The DID of who this notification comes from.',
|
|
1810
|
+
type: 'string',
|
|
1811
|
+
format: 'did',
|
|
1812
|
+
},
|
|
1813
|
+
to: {
|
|
1814
|
+
description: 'The DID of who this notification should go to.',
|
|
1815
|
+
type: 'string',
|
|
1816
|
+
format: 'did',
|
|
1817
|
+
},
|
|
1818
|
+
},
|
|
1819
|
+
},
|
|
1779
1820
|
},
|
|
1780
1821
|
},
|
|
1781
1822
|
AppBskyContactDismissMatch: {
|
|
@@ -1991,6 +2032,43 @@ export const schemaDict = {
|
|
|
1991
2032
|
},
|
|
1992
2033
|
},
|
|
1993
2034
|
},
|
|
2035
|
+
AppBskyContactSendNotification: {
|
|
2036
|
+
lexicon: 1,
|
|
2037
|
+
id: 'app.bsky.contact.sendNotification',
|
|
2038
|
+
defs: {
|
|
2039
|
+
main: {
|
|
2040
|
+
type: 'procedure',
|
|
2041
|
+
description:
|
|
2042
|
+
"WARNING: This is unstable and under active development, don't use it while this warning is here. System endpoint to send notifications related to contact imports. Requires role authentication.",
|
|
2043
|
+
input: {
|
|
2044
|
+
encoding: 'application/json',
|
|
2045
|
+
schema: {
|
|
2046
|
+
type: 'object',
|
|
2047
|
+
required: ['from', 'to'],
|
|
2048
|
+
properties: {
|
|
2049
|
+
from: {
|
|
2050
|
+
description: 'The DID of who this notification comes from.',
|
|
2051
|
+
type: 'string',
|
|
2052
|
+
format: 'did',
|
|
2053
|
+
},
|
|
2054
|
+
to: {
|
|
2055
|
+
description: 'The DID of who this notification should go to.',
|
|
2056
|
+
type: 'string',
|
|
2057
|
+
format: 'did',
|
|
2058
|
+
},
|
|
2059
|
+
},
|
|
2060
|
+
},
|
|
2061
|
+
},
|
|
2062
|
+
output: {
|
|
2063
|
+
encoding: 'application/json',
|
|
2064
|
+
schema: {
|
|
2065
|
+
type: 'object',
|
|
2066
|
+
properties: {},
|
|
2067
|
+
},
|
|
2068
|
+
},
|
|
2069
|
+
},
|
|
2070
|
+
},
|
|
2071
|
+
},
|
|
1994
2072
|
AppBskyContactStartPhoneVerification: {
|
|
1995
2073
|
lexicon: 1,
|
|
1996
2074
|
id: 'app.bsky.contact.startPhoneVerification',
|
|
@@ -6725,6 +6803,7 @@ export const schemaDict = {
|
|
|
6725
6803
|
'like-via-repost',
|
|
6726
6804
|
'repost-via-repost',
|
|
6727
6805
|
'subscribed-post',
|
|
6806
|
+
'contact-match',
|
|
6728
6807
|
],
|
|
6729
6808
|
},
|
|
6730
6809
|
reasonSubject: {
|
|
@@ -14793,6 +14872,7 @@ export const ids = {
|
|
|
14793
14872
|
AppBskyContactGetSyncStatus: 'app.bsky.contact.getSyncStatus',
|
|
14794
14873
|
AppBskyContactImportContacts: 'app.bsky.contact.importContacts',
|
|
14795
14874
|
AppBskyContactRemoveData: 'app.bsky.contact.removeData',
|
|
14875
|
+
AppBskyContactSendNotification: 'app.bsky.contact.sendNotification',
|
|
14796
14876
|
AppBskyContactStartPhoneVerification:
|
|
14797
14877
|
'app.bsky.contact.startPhoneVerification',
|
|
14798
14878
|
AppBskyContactVerifyPhone: 'app.bsky.contact.verifyPhone',
|
|
@@ -259,6 +259,7 @@ export type Preferences = (
|
|
|
259
259
|
| $Typed<SavedFeedsPref>
|
|
260
260
|
| $Typed<SavedFeedsPrefV2>
|
|
261
261
|
| $Typed<PersonalDetailsPref>
|
|
262
|
+
| $Typed<DeclaredAgePref>
|
|
262
263
|
| $Typed<FeedViewPref>
|
|
263
264
|
| $Typed<ThreadViewPref>
|
|
264
265
|
| $Typed<InterestsPref>
|
|
@@ -370,6 +371,27 @@ export function validatePersonalDetailsPref<V>(v: V) {
|
|
|
370
371
|
return validate<PersonalDetailsPref & V>(v, id, hashPersonalDetailsPref)
|
|
371
372
|
}
|
|
372
373
|
|
|
374
|
+
/** Read-only preference containing value(s) inferred from the user's declared birthdate. Absence of this preference object in the response indicates that the user has not made a declaration. */
|
|
375
|
+
export interface DeclaredAgePref {
|
|
376
|
+
$type?: 'app.bsky.actor.defs#declaredAgePref'
|
|
377
|
+
/** Indicates if the user has declared that they are over 13 years of age. */
|
|
378
|
+
isOverAge13?: boolean
|
|
379
|
+
/** Indicates if the user has declared that they are over 16 years of age. */
|
|
380
|
+
isOverAge16?: boolean
|
|
381
|
+
/** Indicates if the user has declared that they are over 18 years of age. */
|
|
382
|
+
isOverAge18?: boolean
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const hashDeclaredAgePref = 'declaredAgePref'
|
|
386
|
+
|
|
387
|
+
export function isDeclaredAgePref<V>(v: V) {
|
|
388
|
+
return is$typed(v, id, hashDeclaredAgePref)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export function validateDeclaredAgePref<V>(v: V) {
|
|
392
|
+
return validate<DeclaredAgePref & V>(v, id, hashDeclaredAgePref)
|
|
393
|
+
}
|
|
394
|
+
|
|
373
395
|
export interface FeedViewPref {
|
|
374
396
|
$type?: 'app.bsky.actor.defs#feedViewPref'
|
|
375
397
|
/** The URI of the feed, or an identifier which describes the feed. */
|
|
@@ -50,3 +50,22 @@ export function isSyncStatus<V>(v: V) {
|
|
|
50
50
|
export function validateSyncStatus<V>(v: V) {
|
|
51
51
|
return validate<SyncStatus & V>(v, id, hashSyncStatus)
|
|
52
52
|
}
|
|
53
|
+
|
|
54
|
+
/** A stash object to be sent via bsync representing a notification to be created. */
|
|
55
|
+
export interface Notification {
|
|
56
|
+
$type?: 'app.bsky.contact.defs#notification'
|
|
57
|
+
/** The DID of who this notification comes from. */
|
|
58
|
+
from: string
|
|
59
|
+
/** The DID of who this notification should go to. */
|
|
60
|
+
to: string
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const hashNotification = 'notification'
|
|
64
|
+
|
|
65
|
+
export function isNotification<V>(v: V) {
|
|
66
|
+
return is$typed(v, id, hashNotification)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function validateNotification<V>(v: V) {
|
|
70
|
+
return validate<Notification & V>(v, id, hashNotification)
|
|
71
|
+
}
|