@atproto/bsky 0.0.155 → 0.0.157
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 +18 -0
- package/dist/api/app/bsky/{unspecced/getPostThreadHiddenV2.d.ts → notification/getPreferences.d.ts} +1 -1
- package/dist/api/app/bsky/notification/getPreferences.d.ts.map +1 -0
- package/dist/api/app/bsky/notification/getPreferences.js +39 -0
- package/dist/api/app/bsky/notification/getPreferences.js.map +1 -0
- package/dist/api/app/bsky/notification/putPreferencesV2.d.ts +4 -0
- package/dist/api/app/bsky/notification/putPreferencesV2.d.ts.map +1 -0
- package/dist/api/app/bsky/notification/putPreferencesV2.js +47 -0
- package/dist/api/app/bsky/notification/putPreferencesV2.js.map +1 -0
- package/dist/api/app/bsky/notification/util.d.ts +9 -0
- package/dist/api/app/bsky/notification/util.d.ts.map +1 -0
- package/dist/api/app/bsky/notification/util.js +84 -0
- package/dist/api/app/bsky/notification/util.js.map +1 -0
- package/dist/api/app/bsky/unspecced/getPostThreadOtherV2.d.ts +4 -0
- package/dist/api/app/bsky/unspecced/getPostThreadOtherV2.d.ts.map +1 -0
- package/dist/api/app/bsky/unspecced/{getPostThreadHiddenV2.js → getPostThreadOtherV2.js} +5 -5
- package/dist/api/app/bsky/unspecced/getPostThreadOtherV2.js.map +1 -0
- package/dist/api/app/bsky/unspecced/getPostThreadV2.js +2 -2
- package/dist/api/app/bsky/unspecced/getPostThreadV2.js.map +1 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +6 -2
- package/dist/api/index.js.map +1 -1
- package/dist/context.d.ts +3 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +3 -0
- package/dist/context.js.map +1 -1
- package/dist/data-plane/bsync/index.d.ts.map +1 -1
- package/dist/data-plane/bsync/index.js +84 -0
- package/dist/data-plane/bsync/index.js.map +1 -1
- package/dist/data-plane/server/db/database-schema.d.ts +2 -1
- package/dist/data-plane/server/db/database-schema.d.ts.map +1 -1
- package/dist/data-plane/server/db/migrations/20250602T190357447Z-add-private-data.d.ts +4 -0
- package/dist/data-plane/server/db/migrations/20250602T190357447Z-add-private-data.d.ts.map +1 -0
- package/dist/data-plane/server/db/migrations/20250602T190357447Z-add-private-data.js +24 -0
- package/dist/data-plane/server/db/migrations/20250602T190357447Z-add-private-data.js.map +1 -0
- package/dist/data-plane/server/db/migrations/index.d.ts +1 -0
- package/dist/data-plane/server/db/migrations/index.d.ts.map +1 -1
- package/dist/data-plane/server/db/migrations/index.js +2 -1
- package/dist/data-plane/server/db/migrations/index.js.map +1 -1
- package/dist/data-plane/server/db/tables/private-data.d.ts +13 -0
- package/dist/data-plane/server/db/tables/private-data.d.ts.map +1 -0
- package/dist/data-plane/server/db/tables/private-data.js +5 -0
- package/dist/data-plane/server/db/tables/private-data.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/private-data.d.ts +9 -0
- package/dist/data-plane/server/routes/private-data.d.ts.map +1 -0
- package/dist/data-plane/server/routes/private-data.js +63 -0
- package/dist/data-plane/server/routes/private-data.js.map +1 -0
- package/dist/data-plane/server/util.d.ts +6 -6
- 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 +8 -4
- package/dist/lexicon/index.d.ts.map +1 -1
- package/dist/lexicon/index.js +14 -6
- package/dist/lexicon/index.js.map +1 -1
- package/dist/lexicon/lexicons.d.ts +518 -100
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +277 -52
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/app/bsky/notification/defs.d.ts +40 -0
- package/dist/lexicon/types/app/bsky/notification/defs.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/notification/defs.js +36 -0
- package/dist/lexicon/types/app/bsky/notification/defs.js.map +1 -1
- package/dist/lexicon/types/app/bsky/notification/getPreferences.d.ts +35 -0
- package/dist/lexicon/types/app/bsky/notification/getPreferences.d.ts.map +1 -0
- package/dist/lexicon/types/app/bsky/notification/getPreferences.js +7 -0
- package/dist/lexicon/types/app/bsky/notification/getPreferences.js.map +1 -0
- package/dist/lexicon/types/app/bsky/notification/putPreferencesV2.d.ts +52 -0
- package/dist/lexicon/types/app/bsky/notification/putPreferencesV2.d.ts.map +1 -0
- package/dist/lexicon/types/app/bsky/notification/putPreferencesV2.js +7 -0
- package/dist/lexicon/types/app/bsky/notification/putPreferencesV2.js.map +1 -0
- package/dist/lexicon/types/app/bsky/unspecced/{getPostThreadHiddenV2.d.ts → getPostThreadOtherV2.d.ts} +7 -7
- package/dist/lexicon/types/app/bsky/unspecced/getPostThreadOtherV2.d.ts.map +1 -0
- package/dist/lexicon/types/app/bsky/unspecced/getPostThreadOtherV2.js +16 -0
- package/dist/lexicon/types/app/bsky/unspecced/getPostThreadOtherV2.js.map +1 -0
- package/dist/lexicon/types/app/bsky/unspecced/getPostThreadV2.d.ts +2 -2
- package/dist/lexicon/types/app/bsky/unspecced/getPostThreadV2.d.ts.map +1 -1
- package/dist/proto/bsky_connect.d.ts +10 -1
- package/dist/proto/bsky_connect.d.ts.map +1 -1
- package/dist/proto/bsky_connect.js +9 -0
- package/dist/proto/bsky_connect.js.map +1 -1
- package/dist/proto/bsky_pb.d.ts +234 -0
- package/dist/proto/bsky_pb.d.ts.map +1 -1
- package/dist/proto/bsky_pb.js +693 -5
- package/dist/proto/bsky_pb.js.map +1 -1
- package/dist/proto/bsync_pb.d.ts +10 -10
- package/dist/proto/bsync_pb.d.ts.map +1 -1
- package/dist/proto/bsync_pb.js +15 -15
- package/dist/proto/bsync_pb.js.map +1 -1
- package/dist/stash.d.ts +26 -0
- package/dist/stash.d.ts.map +1 -0
- package/dist/stash.js +56 -0
- package/dist/stash.js.map +1 -0
- package/dist/views/index.d.ts +8 -8
- package/dist/views/index.d.ts.map +1 -1
- package/dist/views/index.js +32 -32
- package/dist/views/index.js.map +1 -1
- package/dist/views/threads-v2.d.ts +11 -11
- package/dist/views/threads-v2.d.ts.map +1 -1
- package/dist/views/threads-v2.js.map +1 -1
- package/package.json +4 -4
- package/proto/bsky.proto +61 -0
- package/src/api/app/bsky/notification/getPreferences.ts +50 -0
- package/src/api/app/bsky/notification/putPreferencesV2.ts +62 -0
- package/src/api/app/bsky/notification/util.ts +123 -0
- package/src/api/app/bsky/unspecced/{getPostThreadHiddenV2.ts → getPostThreadOtherV2.ts} +5 -5
- package/src/api/app/bsky/unspecced/getPostThreadV2.ts +2 -2
- package/src/api/index.ts +6 -2
- package/src/context.ts +6 -0
- package/src/data-plane/bsync/index.ts +109 -1
- package/src/data-plane/server/db/database-schema.ts +3 -1
- package/src/data-plane/server/db/migrations/20250602T190357447Z-add-private-data.ts +22 -0
- package/src/data-plane/server/db/migrations/index.ts +1 -0
- package/src/data-plane/server/db/tables/private-data.ts +13 -0
- package/src/data-plane/server/routes/index.ts +2 -0
- package/src/data-plane/server/routes/private-data.ts +90 -0
- package/src/index.ts +4 -0
- package/src/lexicon/index.ts +38 -14
- package/src/lexicon/lexicons.ts +281 -54
- package/src/lexicon/types/app/bsky/notification/defs.ts +76 -0
- package/src/lexicon/types/app/bsky/notification/getPreferences.ts +52 -0
- package/src/lexicon/types/app/bsky/notification/putPreferencesV2.ts +69 -0
- package/src/lexicon/types/app/bsky/unspecced/{getPostThreadHiddenV2.ts → getPostThreadOtherV2.ts} +10 -10
- package/src/lexicon/types/app/bsky/unspecced/getPostThreadV2.ts +2 -2
- package/src/proto/bsky_connect.ts +11 -0
- package/src/proto/bsky_pb.ts +669 -0
- package/src/proto/bsync_pb.ts +15 -15
- package/src/stash.ts +75 -0
- package/src/views/index.ts +46 -46
- package/src/views/threads-v2.ts +23 -23
- package/tests/stash.test.ts +156 -0
- package/tests/views/__snapshots__/thread-v2.test.ts.snap +7 -7
- package/tests/views/notifications.test.ts +221 -0
- package/tests/views/thread-v2.test.ts +93 -93
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.tests.tsbuildinfo +1 -1
- package/dist/api/app/bsky/unspecced/getPostThreadHiddenV2.d.ts.map +0 -1
- package/dist/api/app/bsky/unspecced/getPostThreadHiddenV2.js.map +0 -1
- package/dist/lexicon/types/app/bsky/unspecced/getPostThreadHiddenV2.d.ts.map +0 -1
- package/dist/lexicon/types/app/bsky/unspecced/getPostThreadHiddenV2.js +0 -16
- package/dist/lexicon/types/app/bsky/unspecced/getPostThreadHiddenV2.js.map +0 -1
package/proto/bsky.proto
CHANGED
|
@@ -753,6 +753,66 @@ message GetBlocklistSubscriptionsResponse {
|
|
|
753
753
|
// Notifications
|
|
754
754
|
//
|
|
755
755
|
|
|
756
|
+
message GetNotificationPreferencesRequest {
|
|
757
|
+
repeated string dids = 1;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
message NotificationChannelList {
|
|
761
|
+
bool enabled = 1;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
message NotificationChannelPush {
|
|
765
|
+
bool enabled = 1;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
enum NotificationFilter {
|
|
769
|
+
NOTIFICATION_FILTER_UNSPECIFIED = 0;
|
|
770
|
+
NOTIFICATION_FILTER_ALL = 1;
|
|
771
|
+
NOTIFICATION_FILTER_FOLLOWS = 2;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
message FilterableNotificationPreference {
|
|
775
|
+
NotificationFilter filter = 1;
|
|
776
|
+
NotificationChannelList list = 2;
|
|
777
|
+
NotificationChannelPush push = 3;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
message NotificationPreference {
|
|
781
|
+
NotificationChannelList list = 1;
|
|
782
|
+
NotificationChannelPush push = 2;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
enum ChatNotificationFilter {
|
|
786
|
+
CHAT_NOTIFICATION_FILTER_UNSPECIFIED = 0;
|
|
787
|
+
CHAT_NOTIFICATION_FILTER_ALL = 1;
|
|
788
|
+
CHAT_NOTIFICATION_FILTER_ACCEPTED = 2;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
message ChatNotificationPreference {
|
|
792
|
+
ChatNotificationFilter filter = 1;
|
|
793
|
+
NotificationChannelPush push = 2;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
message NotificationPreferences {
|
|
797
|
+
ChatNotificationPreference chat = 1;
|
|
798
|
+
FilterableNotificationPreference follow = 2;
|
|
799
|
+
FilterableNotificationPreference like = 3;
|
|
800
|
+
FilterableNotificationPreference like_via_repost = 4;
|
|
801
|
+
FilterableNotificationPreference mention = 5;
|
|
802
|
+
FilterableNotificationPreference quote = 6;
|
|
803
|
+
FilterableNotificationPreference reply = 7;
|
|
804
|
+
FilterableNotificationPreference repost = 8;
|
|
805
|
+
FilterableNotificationPreference repost_via_repost = 9;
|
|
806
|
+
NotificationPreference starterpack_joined = 10;
|
|
807
|
+
NotificationPreference subscribed_post = 11;
|
|
808
|
+
NotificationPreference unverified = 12;
|
|
809
|
+
NotificationPreference verified = 13;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
message GetNotificationPreferencesResponse {
|
|
813
|
+
repeated NotificationPreferences preferences = 1;
|
|
814
|
+
}
|
|
815
|
+
|
|
756
816
|
// - list recent notifications for a user
|
|
757
817
|
// - notifications should include a uri for the record that caused the notif & a “reason” for the notification (reply, like, quotepost, etc)
|
|
758
818
|
// - this should include both read & unread notifs
|
|
@@ -1256,6 +1316,7 @@ service Service {
|
|
|
1256
1316
|
rpc GetBlocklistSubscriptions(GetBlocklistSubscriptionsRequest) returns (GetBlocklistSubscriptionsResponse);
|
|
1257
1317
|
|
|
1258
1318
|
// Notifications
|
|
1319
|
+
rpc GetNotificationPreferences(GetNotificationPreferencesRequest) returns (GetNotificationPreferencesResponse);
|
|
1259
1320
|
rpc GetNotifications(GetNotificationsRequest) returns (GetNotificationsResponse);
|
|
1260
1321
|
rpc GetNotificationSeen(GetNotificationSeenRequest) returns (GetNotificationSeenResponse);
|
|
1261
1322
|
rpc GetUnreadNotificationCount(GetUnreadNotificationCountRequest) returns (GetUnreadNotificationCountResponse);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
import { Un$Typed } from '@atproto/api'
|
|
3
|
+
import { UpstreamFailureError } from '@atproto/xrpc-server'
|
|
4
|
+
import { AppContext } from '../../../../context'
|
|
5
|
+
import { Server } from '../../../../lexicon'
|
|
6
|
+
import { Preferences } from '../../../../lexicon/types/app/bsky/notification/defs'
|
|
7
|
+
import { GetNotificationPreferencesResponse } from '../../../../proto/bsky_pb'
|
|
8
|
+
import { protobufToLex } from './util'
|
|
9
|
+
|
|
10
|
+
export default function (server: Server, ctx: AppContext) {
|
|
11
|
+
server.app.bsky.notification.getPreferences({
|
|
12
|
+
auth: ctx.authVerifier.standard,
|
|
13
|
+
handler: async ({ auth }) => {
|
|
14
|
+
const actorDid = auth.credentials.iss
|
|
15
|
+
const preferences = await computePreferences(ctx, actorDid)
|
|
16
|
+
return {
|
|
17
|
+
encoding: 'application/json',
|
|
18
|
+
body: {
|
|
19
|
+
preferences,
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const computePreferences = async (
|
|
27
|
+
ctx: AppContext,
|
|
28
|
+
actorDid: string,
|
|
29
|
+
): Promise<Un$Typed<Preferences>> => {
|
|
30
|
+
let res: GetNotificationPreferencesResponse
|
|
31
|
+
try {
|
|
32
|
+
res = await ctx.dataplane.getNotificationPreferences({
|
|
33
|
+
dids: [actorDid],
|
|
34
|
+
})
|
|
35
|
+
} catch (err) {
|
|
36
|
+
throw new UpstreamFailureError(
|
|
37
|
+
'cannot get current notification preferences',
|
|
38
|
+
'NotificationPreferencesFailed',
|
|
39
|
+
{ cause: err },
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
assert(
|
|
44
|
+
res.preferences.length === 1,
|
|
45
|
+
`expected exactly one preferences entry, got ${res.preferences.length}`,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
const currentPreferences = protobufToLex(res.preferences[0])
|
|
49
|
+
return currentPreferences
|
|
50
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
import { Un$Typed } from '@atproto/api'
|
|
3
|
+
import { UpstreamFailureError } from '@atproto/xrpc-server'
|
|
4
|
+
import { AppContext } from '../../../../context'
|
|
5
|
+
import { Server } from '../../../../lexicon'
|
|
6
|
+
import { Preferences } from '../../../../lexicon/types/app/bsky/notification/defs'
|
|
7
|
+
import { HandlerInput } from '../../../../lexicon/types/app/bsky/notification/putPreferencesV2'
|
|
8
|
+
import { GetNotificationPreferencesResponse } from '../../../../proto/bsky_pb'
|
|
9
|
+
import { protobufToLex } from './util'
|
|
10
|
+
|
|
11
|
+
export default function (server: Server, ctx: AppContext) {
|
|
12
|
+
server.app.bsky.notification.putPreferencesV2({
|
|
13
|
+
auth: ctx.authVerifier.standard,
|
|
14
|
+
handler: async ({ auth, input }) => {
|
|
15
|
+
const actorDid = auth.credentials.iss
|
|
16
|
+
const preferences = await computePreferences(ctx, actorDid, input)
|
|
17
|
+
|
|
18
|
+
// Notification preferences are created automatically on the dataplane on signup, so we just update.
|
|
19
|
+
await ctx.stashClient.update({
|
|
20
|
+
actorDid,
|
|
21
|
+
namespace: 'app.bsky.notification.defs#preferences',
|
|
22
|
+
key: 'self',
|
|
23
|
+
payload: preferences,
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
encoding: 'application/json',
|
|
28
|
+
body: {
|
|
29
|
+
preferences,
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const computePreferences = async (
|
|
37
|
+
ctx: AppContext,
|
|
38
|
+
actorDid: string,
|
|
39
|
+
input: HandlerInput,
|
|
40
|
+
): Promise<Un$Typed<Preferences>> => {
|
|
41
|
+
let res: GetNotificationPreferencesResponse
|
|
42
|
+
try {
|
|
43
|
+
res = await ctx.dataplane.getNotificationPreferences({
|
|
44
|
+
dids: [actorDid],
|
|
45
|
+
})
|
|
46
|
+
} catch (err) {
|
|
47
|
+
throw new UpstreamFailureError(
|
|
48
|
+
'cannot get current notification preferences',
|
|
49
|
+
'NotificationPreferencesFailed',
|
|
50
|
+
{ cause: err },
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
assert(
|
|
55
|
+
res.preferences.length === 1,
|
|
56
|
+
`expected exactly one preferences entry, got ${res.preferences.length}`,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
const currentPreferences = protobufToLex(res.preferences[0])
|
|
60
|
+
const preferences = { ...currentPreferences, ...input.body }
|
|
61
|
+
return preferences
|
|
62
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Un$Typed } from '@atproto/api'
|
|
2
|
+
import {
|
|
3
|
+
ChatPreference,
|
|
4
|
+
FilterablePreference,
|
|
5
|
+
Preference,
|
|
6
|
+
Preferences,
|
|
7
|
+
} from '../../../../lexicon/types/app/bsky/notification/defs'
|
|
8
|
+
import {
|
|
9
|
+
ChatNotificationFilter,
|
|
10
|
+
ChatNotificationPreference,
|
|
11
|
+
FilterableNotificationPreference,
|
|
12
|
+
NotificationFilter,
|
|
13
|
+
NotificationPreference,
|
|
14
|
+
NotificationPreferences,
|
|
15
|
+
} from '../../../../proto/bsky_pb'
|
|
16
|
+
|
|
17
|
+
type DeepPartial<T> = T extends object
|
|
18
|
+
? {
|
|
19
|
+
[P in keyof T]?: DeepPartial<T[P]>
|
|
20
|
+
}
|
|
21
|
+
: T
|
|
22
|
+
|
|
23
|
+
const ensureChatPreference = (
|
|
24
|
+
p?: DeepPartial<ChatPreference>,
|
|
25
|
+
): ChatPreference => {
|
|
26
|
+
const filters = ['all', 'accepted']
|
|
27
|
+
return {
|
|
28
|
+
filter:
|
|
29
|
+
typeof p?.filter === 'string' && filters.includes(p.filter)
|
|
30
|
+
? p.filter
|
|
31
|
+
: 'all',
|
|
32
|
+
push: p?.push ?? true,
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const ensureFilterablePreference = (
|
|
37
|
+
p?: DeepPartial<FilterablePreference>,
|
|
38
|
+
): FilterablePreference => {
|
|
39
|
+
const filters = ['all', 'follows']
|
|
40
|
+
return {
|
|
41
|
+
filter:
|
|
42
|
+
typeof p?.filter === 'string' && filters.includes(p.filter)
|
|
43
|
+
? p.filter
|
|
44
|
+
: 'all',
|
|
45
|
+
list: p?.list ?? true,
|
|
46
|
+
push: p?.push ?? true,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const ensurePreference = (p?: DeepPartial<Preference>): Preference => {
|
|
51
|
+
return {
|
|
52
|
+
list: p?.list ?? true,
|
|
53
|
+
push: p?.push ?? true,
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const ensurePreferences = (
|
|
58
|
+
p: DeepPartial<Preferences>,
|
|
59
|
+
): Un$Typed<Preferences> => {
|
|
60
|
+
return {
|
|
61
|
+
chat: ensureChatPreference(p.chat),
|
|
62
|
+
follow: ensureFilterablePreference(p.follow),
|
|
63
|
+
like: ensureFilterablePreference(p.like),
|
|
64
|
+
likeViaRepost: ensureFilterablePreference(p.likeViaRepost),
|
|
65
|
+
mention: ensureFilterablePreference(p.mention),
|
|
66
|
+
quote: ensureFilterablePreference(p.quote),
|
|
67
|
+
reply: ensureFilterablePreference(p.reply),
|
|
68
|
+
repost: ensureFilterablePreference(p.repost),
|
|
69
|
+
repostViaRepost: ensureFilterablePreference(p.repostViaRepost),
|
|
70
|
+
starterpackJoined: ensurePreference(p.starterpackJoined),
|
|
71
|
+
subscribedPost: ensurePreference(p.subscribedPost),
|
|
72
|
+
unverified: ensurePreference(p.unverified),
|
|
73
|
+
verified: ensurePreference(p.verified),
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const protobufChatPreferenceToLex = (
|
|
78
|
+
p?: DeepPartial<ChatNotificationPreference>,
|
|
79
|
+
): DeepPartial<ChatPreference> => {
|
|
80
|
+
return {
|
|
81
|
+
filter: p?.filter === ChatNotificationFilter.ACCEPTED ? 'accepted' : 'all',
|
|
82
|
+
push: p?.push?.enabled,
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const protobufFilterablePreferenceToLex = (
|
|
87
|
+
p?: DeepPartial<FilterableNotificationPreference>,
|
|
88
|
+
): DeepPartial<FilterablePreference> => {
|
|
89
|
+
return {
|
|
90
|
+
filter: p?.filter === NotificationFilter.FOLLOWS ? 'follows' : 'all',
|
|
91
|
+
list: p?.list?.enabled,
|
|
92
|
+
push: p?.push?.enabled,
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const protobufPreferenceToLex = (
|
|
97
|
+
p?: DeepPartial<NotificationPreference>,
|
|
98
|
+
): DeepPartial<Preference> => {
|
|
99
|
+
return {
|
|
100
|
+
list: p?.list?.enabled,
|
|
101
|
+
push: p?.push?.enabled,
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const protobufToLex = (
|
|
106
|
+
res: DeepPartial<NotificationPreferences>,
|
|
107
|
+
): Un$Typed<Preferences> => {
|
|
108
|
+
return ensurePreferences({
|
|
109
|
+
chat: protobufChatPreferenceToLex(res.chat),
|
|
110
|
+
follow: protobufFilterablePreferenceToLex(res.follow),
|
|
111
|
+
like: protobufFilterablePreferenceToLex(res.like),
|
|
112
|
+
likeViaRepost: protobufFilterablePreferenceToLex(res.likeViaRepost),
|
|
113
|
+
mention: protobufFilterablePreferenceToLex(res.mention),
|
|
114
|
+
quote: protobufFilterablePreferenceToLex(res.quote),
|
|
115
|
+
reply: protobufFilterablePreferenceToLex(res.reply),
|
|
116
|
+
repost: protobufFilterablePreferenceToLex(res.repost),
|
|
117
|
+
repostViaRepost: protobufFilterablePreferenceToLex(res.repostViaRepost),
|
|
118
|
+
starterpackJoined: protobufPreferenceToLex(res.starterpackJoined),
|
|
119
|
+
subscribedPost: protobufPreferenceToLex(res.subscribedPost),
|
|
120
|
+
unverified: protobufPreferenceToLex(res.unverified),
|
|
121
|
+
verified: protobufPreferenceToLex(res.verified),
|
|
122
|
+
})
|
|
123
|
+
}
|
|
@@ -3,7 +3,7 @@ import { AppContext } from '../../../../context'
|
|
|
3
3
|
import { Code, DataPlaneClient, isDataplaneError } from '../../../../data-plane'
|
|
4
4
|
import { HydrateCtx, Hydrator } from '../../../../hydration/hydrator'
|
|
5
5
|
import { Server } from '../../../../lexicon'
|
|
6
|
-
import { QueryParams } from '../../../../lexicon/types/app/bsky/unspecced/
|
|
6
|
+
import { QueryParams } from '../../../../lexicon/types/app/bsky/unspecced/getPostThreadOtherV2'
|
|
7
7
|
import {
|
|
8
8
|
HydrationFnInput,
|
|
9
9
|
PresentationFnInput,
|
|
@@ -25,13 +25,13 @@ const BELOW = 1
|
|
|
25
25
|
const BRANCHING_FACTOR = 0
|
|
26
26
|
|
|
27
27
|
export default function (server: Server, ctx: AppContext) {
|
|
28
|
-
const
|
|
28
|
+
const getPostThreadOther = createPipeline(
|
|
29
29
|
skeleton,
|
|
30
30
|
hydration,
|
|
31
31
|
noRules, // handled in presentation: 3p block-violating replies are turned to #blockedPost, viewer blocks turned to #notFoundPost.
|
|
32
32
|
presentation,
|
|
33
33
|
)
|
|
34
|
-
server.app.bsky.unspecced.
|
|
34
|
+
server.app.bsky.unspecced.getPostThreadOtherV2({
|
|
35
35
|
auth: ctx.authVerifier.optionalStandardOrRole,
|
|
36
36
|
handler: async ({ params, auth, req }) => {
|
|
37
37
|
const { viewer, includeTakedowns, include3pBlocks } =
|
|
@@ -46,7 +46,7 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
46
46
|
|
|
47
47
|
return {
|
|
48
48
|
encoding: 'application/json',
|
|
49
|
-
body: await
|
|
49
|
+
body: await getPostThreadOther({ ...params, hydrateCtx }, ctx),
|
|
50
50
|
headers: resHeaders({
|
|
51
51
|
labelers: hydrateCtx.labelers,
|
|
52
52
|
}),
|
|
@@ -94,7 +94,7 @@ const presentation = (
|
|
|
94
94
|
inputs: PresentationFnInput<Context, Params, Skeleton>,
|
|
95
95
|
) => {
|
|
96
96
|
const { ctx, params, skeleton, hydration } = inputs
|
|
97
|
-
const thread = ctx.views.
|
|
97
|
+
const thread = ctx.views.threadOtherV2(skeleton, hydration, {
|
|
98
98
|
below: BELOW,
|
|
99
99
|
branchingFactor: BRANCHING_FACTOR,
|
|
100
100
|
prioritizeFollowedUsers: params.prioritizeFollowedUsers,
|
|
@@ -85,7 +85,7 @@ const presentation = (
|
|
|
85
85
|
inputs: PresentationFnInput<Context, Params, Skeleton>,
|
|
86
86
|
) => {
|
|
87
87
|
const { ctx, params, skeleton, hydration } = inputs
|
|
88
|
-
const {
|
|
88
|
+
const { hasOtherReplies, thread } = ctx.views.threadV2(skeleton, hydration, {
|
|
89
89
|
above: calculateAbove(ctx, params),
|
|
90
90
|
below: calculateBelow(ctx, skeleton.anchor, params),
|
|
91
91
|
branchingFactor: params.branchingFactor,
|
|
@@ -100,7 +100,7 @@ const presentation = (
|
|
|
100
100
|
postUriToThreadgateUri(rootUri),
|
|
101
101
|
hydration,
|
|
102
102
|
)
|
|
103
|
-
return {
|
|
103
|
+
return { hasOtherReplies, thread, threadgate }
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
type Context = {
|
package/src/api/index.ts
CHANGED
|
@@ -42,14 +42,16 @@ import unmuteActor from './app/bsky/graph/unmuteActor'
|
|
|
42
42
|
import unmuteActorList from './app/bsky/graph/unmuteActorList'
|
|
43
43
|
import unmuteThread from './app/bsky/graph/unmuteThread'
|
|
44
44
|
import getLabelerServices from './app/bsky/labeler/getServices'
|
|
45
|
+
import getPreferences from './app/bsky/notification/getPreferences'
|
|
45
46
|
import getUnreadCount from './app/bsky/notification/getUnreadCount'
|
|
46
47
|
import listNotifications from './app/bsky/notification/listNotifications'
|
|
47
48
|
import putPreferences from './app/bsky/notification/putPreferences'
|
|
49
|
+
import putPreferencesV2 from './app/bsky/notification/putPreferencesV2'
|
|
48
50
|
import registerPush from './app/bsky/notification/registerPush'
|
|
49
51
|
import updateSeen from './app/bsky/notification/updateSeen'
|
|
50
52
|
import getConfig from './app/bsky/unspecced/getConfig'
|
|
51
53
|
import getPopularFeedGenerators from './app/bsky/unspecced/getPopularFeedGenerators'
|
|
52
|
-
import
|
|
54
|
+
import getPostThreadOtherV2 from './app/bsky/unspecced/getPostThreadOtherV2'
|
|
53
55
|
import getPostThreadV2 from './app/bsky/unspecced/getPostThreadV2'
|
|
54
56
|
import getUnspeccedSuggestedFeeds from './app/bsky/unspecced/getSuggestedFeeds'
|
|
55
57
|
import getSuggestedStarterPacks from './app/bsky/unspecced/getSuggestedStarterPacks'
|
|
@@ -84,7 +86,7 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
84
86
|
getListFeed(server, ctx)
|
|
85
87
|
getQuotes(server, ctx)
|
|
86
88
|
getPostThread(server, ctx)
|
|
87
|
-
|
|
89
|
+
getPostThreadOtherV2(server, ctx)
|
|
88
90
|
getPostThreadV2(server, ctx)
|
|
89
91
|
getPosts(server, ctx)
|
|
90
92
|
searchPosts(server, ctx)
|
|
@@ -122,10 +124,12 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
122
124
|
searchActors(server, ctx)
|
|
123
125
|
searchActorsTypeahead(server, ctx)
|
|
124
126
|
getSuggestions(server, ctx)
|
|
127
|
+
getPreferences(server, ctx)
|
|
125
128
|
getUnreadCount(server, ctx)
|
|
126
129
|
listNotifications(server, ctx)
|
|
127
130
|
updateSeen(server, ctx)
|
|
128
131
|
putPreferences(server, ctx)
|
|
132
|
+
putPreferencesV2(server, ctx)
|
|
129
133
|
registerPush(server, ctx)
|
|
130
134
|
getConfig(server, ctx)
|
|
131
135
|
getPopularFeedGenerators(server, ctx)
|
package/src/context.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { DataPlaneClient, HostList } from './data-plane/client'
|
|
|
13
13
|
import { FeatureGates } from './feature-gates'
|
|
14
14
|
import { Hydrator } from './hydration/hydrator'
|
|
15
15
|
import { httpLogger as log } from './logger'
|
|
16
|
+
import { StashClient } from './stash'
|
|
16
17
|
import {
|
|
17
18
|
ParsedLabelers,
|
|
18
19
|
defaultLabelerHeader,
|
|
@@ -35,6 +36,7 @@ export class AppContext {
|
|
|
35
36
|
signingKey: Keypair
|
|
36
37
|
idResolver: IdResolver
|
|
37
38
|
bsyncClient: BsyncClient
|
|
39
|
+
stashClient: StashClient
|
|
38
40
|
courierClient: CourierClient | undefined
|
|
39
41
|
authVerifier: AuthVerifier
|
|
40
42
|
featureGates: FeatureGates
|
|
@@ -94,6 +96,10 @@ export class AppContext {
|
|
|
94
96
|
return this.opts.bsyncClient
|
|
95
97
|
}
|
|
96
98
|
|
|
99
|
+
get stashClient(): StashClient {
|
|
100
|
+
return this.opts.stashClient
|
|
101
|
+
}
|
|
102
|
+
|
|
97
103
|
get courierClient(): CourierClient | undefined {
|
|
98
104
|
return this.opts.courierClient
|
|
99
105
|
}
|
|
@@ -4,10 +4,15 @@ import http from 'node:http'
|
|
|
4
4
|
import { ConnectRouter } from '@connectrpc/connect'
|
|
5
5
|
import { expressConnectMiddleware } from '@connectrpc/connect-express'
|
|
6
6
|
import express from 'express'
|
|
7
|
+
import { TID } from '@atproto/common'
|
|
7
8
|
import { AtUri } from '@atproto/syntax'
|
|
8
9
|
import { ids } from '../../lexicon/lexicons'
|
|
9
10
|
import { Service } from '../../proto/bsync_connect'
|
|
10
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
Method,
|
|
13
|
+
MuteOperation_Type,
|
|
14
|
+
PutOperationRequest,
|
|
15
|
+
} from '../../proto/bsync_pb'
|
|
11
16
|
import { Database } from '../server/db'
|
|
12
17
|
|
|
13
18
|
export class MockBsync {
|
|
@@ -138,7 +143,110 @@ const createRoutes = (db: Database) => (router: ConnectRouter) =>
|
|
|
138
143
|
throw new Error('not implemented')
|
|
139
144
|
},
|
|
140
145
|
|
|
146
|
+
async putOperation(req) {
|
|
147
|
+
const { actorDid, namespace, key, method, payload } = req
|
|
148
|
+
if (
|
|
149
|
+
method !== Method.CREATE &&
|
|
150
|
+
method !== Method.UPDATE &&
|
|
151
|
+
method !== Method.DELETE
|
|
152
|
+
) {
|
|
153
|
+
throw new Error(`Unsupported method: ${method}`)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const now = new Date().toISOString()
|
|
157
|
+
if (namespace === 'app.bsky.notification.defs#preferences') {
|
|
158
|
+
await handleNotificationPreferencesOperation(db, req, now)
|
|
159
|
+
} else {
|
|
160
|
+
await handleGenericOperation(db, req, now)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
operation: {
|
|
165
|
+
id: TID.nextStr(),
|
|
166
|
+
actorDid,
|
|
167
|
+
namespace,
|
|
168
|
+
key,
|
|
169
|
+
method,
|
|
170
|
+
payload,
|
|
171
|
+
},
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
async scanOperations() {
|
|
176
|
+
throw new Error('not implemented')
|
|
177
|
+
},
|
|
178
|
+
|
|
141
179
|
async ping() {
|
|
142
180
|
return {}
|
|
143
181
|
},
|
|
144
182
|
})
|
|
183
|
+
|
|
184
|
+
const handleNotificationPreferencesOperation = async (
|
|
185
|
+
db: Database,
|
|
186
|
+
req: PutOperationRequest,
|
|
187
|
+
now: string,
|
|
188
|
+
) => {
|
|
189
|
+
const { actorDid, namespace, key, method, payload } = req
|
|
190
|
+
if (method === Method.CREATE || method === Method.UPDATE) {
|
|
191
|
+
return db.db
|
|
192
|
+
.insertInto('private_data')
|
|
193
|
+
.values({
|
|
194
|
+
actorDid,
|
|
195
|
+
namespace,
|
|
196
|
+
key,
|
|
197
|
+
payload: Buffer.from(payload).toString('utf8'),
|
|
198
|
+
indexedAt: now,
|
|
199
|
+
updatedAt: now,
|
|
200
|
+
})
|
|
201
|
+
.onConflict((oc) =>
|
|
202
|
+
oc.columns(['actorDid', 'namespace', 'key']).doUpdateSet({
|
|
203
|
+
payload: Buffer.from(payload).toString('utf8'),
|
|
204
|
+
updatedAt: now,
|
|
205
|
+
}),
|
|
206
|
+
)
|
|
207
|
+
.execute()
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return handleGenericOperation(db, req, now)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const handleGenericOperation = async (
|
|
214
|
+
db: Database,
|
|
215
|
+
req: PutOperationRequest,
|
|
216
|
+
now: string,
|
|
217
|
+
) => {
|
|
218
|
+
const { actorDid, namespace, key, method, payload } = req
|
|
219
|
+
if (method === Method.CREATE) {
|
|
220
|
+
return db.db
|
|
221
|
+
.insertInto('private_data')
|
|
222
|
+
.values({
|
|
223
|
+
actorDid,
|
|
224
|
+
namespace,
|
|
225
|
+
key,
|
|
226
|
+
payload: Buffer.from(payload).toString('utf8'),
|
|
227
|
+
indexedAt: now,
|
|
228
|
+
updatedAt: now,
|
|
229
|
+
})
|
|
230
|
+
.execute()
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (method === Method.UPDATE) {
|
|
234
|
+
return db.db
|
|
235
|
+
.updateTable('private_data')
|
|
236
|
+
.where('actorDid', '=', actorDid)
|
|
237
|
+
.where('namespace', '=', namespace)
|
|
238
|
+
.where('key', '=', key)
|
|
239
|
+
.set({
|
|
240
|
+
payload: Buffer.from(payload).toString('utf8'),
|
|
241
|
+
updatedAt: now,
|
|
242
|
+
})
|
|
243
|
+
.execute()
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return db.db
|
|
247
|
+
.deleteFrom('private_data')
|
|
248
|
+
.where('actorDid', '=', actorDid)
|
|
249
|
+
.where('namespace', '=', namespace)
|
|
250
|
+
.where('key', '=', key)
|
|
251
|
+
.execute()
|
|
252
|
+
}
|
|
@@ -24,6 +24,7 @@ import * as post from './tables/post'
|
|
|
24
24
|
import * as postAgg from './tables/post-agg'
|
|
25
25
|
import * as postEmbed from './tables/post-embed'
|
|
26
26
|
import * as postgate from './tables/post-gate'
|
|
27
|
+
import * as privateData from './tables/private-data'
|
|
27
28
|
import * as profile from './tables/profile'
|
|
28
29
|
import * as profileAgg from './tables/profile-agg'
|
|
29
30
|
import * as quote from './tables/quote'
|
|
@@ -77,6 +78,7 @@ export type DatabaseSchemaType = duplicateRecord.PartialDB &
|
|
|
77
78
|
starterPack.PartialDB &
|
|
78
79
|
taggedSuggestion.PartialDB &
|
|
79
80
|
quote.PartialDB &
|
|
80
|
-
verification.PartialDB
|
|
81
|
+
verification.PartialDB &
|
|
82
|
+
privateData.PartialDB
|
|
81
83
|
|
|
82
84
|
export type DatabaseSchema = Kysely<DatabaseSchemaType>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Kysely } from 'kysely'
|
|
2
|
+
|
|
3
|
+
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
4
|
+
await db.schema
|
|
5
|
+
.createTable('private_data')
|
|
6
|
+
.addColumn('actorDid', 'varchar', (col) => col.notNull())
|
|
7
|
+
.addColumn('namespace', 'varchar', (col) => col.notNull())
|
|
8
|
+
.addColumn('key', 'varchar', (col) => col.notNull())
|
|
9
|
+
.addColumn('payload', 'text', (col) => col.notNull())
|
|
10
|
+
.addColumn('indexedAt', 'varchar', (col) => col.notNull())
|
|
11
|
+
.addColumn('updatedAt', 'varchar', (col) => col.notNull())
|
|
12
|
+
.addPrimaryKeyConstraint('private_data_pkey', [
|
|
13
|
+
'actorDid',
|
|
14
|
+
'namespace',
|
|
15
|
+
'key',
|
|
16
|
+
])
|
|
17
|
+
.execute()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
21
|
+
await db.schema.dropTable('private_data').execute()
|
|
22
|
+
}
|
|
@@ -50,3 +50,4 @@ export * as _20250207T174822012Z from './20250207T174822012Z-add-label-exp'
|
|
|
50
50
|
export * as _20250404T163421487Z from './20250404T163421487Z-verifications'
|
|
51
51
|
export * as _20250526T023712742Z from './20250526T023712742Z-like-repost-via'
|
|
52
52
|
export * as _20250528T221913281Z from './20250528T221913281Z-add-record-tags'
|
|
53
|
+
export * as _20250602T190357447Z from './20250602T190357447Z-add-private-data'
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface PrivateData {
|
|
2
|
+
actorDid: string
|
|
3
|
+
namespace: string
|
|
4
|
+
key: string
|
|
5
|
+
// JSON-encoded
|
|
6
|
+
payload: string
|
|
7
|
+
indexedAt: string
|
|
8
|
+
updatedAt: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const tableName = 'private_data'
|
|
12
|
+
|
|
13
|
+
export type PartialDB = { [tableName]: PrivateData }
|
|
@@ -14,6 +14,7 @@ import lists from './lists'
|
|
|
14
14
|
import moderation from './moderation'
|
|
15
15
|
import mutes from './mutes'
|
|
16
16
|
import notifs from './notifs'
|
|
17
|
+
import privateData from './private-data'
|
|
17
18
|
import profile from './profile'
|
|
18
19
|
import quotes from './quotes'
|
|
19
20
|
import records from './records'
|
|
@@ -40,6 +41,7 @@ export default (db: Database, idResolver: IdResolver) =>
|
|
|
40
41
|
...moderation(db),
|
|
41
42
|
...mutes(db),
|
|
42
43
|
...notifs(db),
|
|
44
|
+
...privateData(db),
|
|
43
45
|
...profile(db),
|
|
44
46
|
...quotes(db),
|
|
45
47
|
...records(db),
|