+2
-2
.github/workflows/golang.yml
+2
-2
.github/workflows/golang.yml
···
19
19
- name: Set up Go tooling
20
20
uses: actions/setup-go@v4
21
21
with:
22
-
go-version: "1.23"
22
+
go-version: "1.24"
23
23
- name: Build
24
24
run: make build
25
25
- name: Test
···
32
32
- name: Set up Go tooling
33
33
uses: actions/setup-go@v4
34
34
with:
35
-
go-version: "1.23"
35
+
go-version: "1.24"
36
36
- name: Lint
37
37
run: make lint
+31
.github/workflows/sync-internal.yaml
+31
.github/workflows/sync-internal.yaml
···
1
+
name: Sync to internal repo
2
+
3
+
on:
4
+
push:
5
+
branches: [main]
6
+
7
+
jobs:
8
+
sync:
9
+
runs-on: ubuntu-latest
10
+
if: github.repository == 'bluesky-social/indigo'
11
+
steps:
12
+
- name: Checkout public repo
13
+
uses: actions/checkout@v4
14
+
with:
15
+
fetch-depth: 0
16
+
- name: Generate GitHub App Token
17
+
id: app-token
18
+
uses: actions/create-github-app-token@v1
19
+
with:
20
+
app-id: ${{ vars.SYNC_INTERNAL_APP_ID }}
21
+
private-key: ${{ secrets.SYNC_INTERNAL_PK }}
22
+
repositories: indigo-internal
23
+
- name: Push to internal repo
24
+
env:
25
+
TOKEN: ${{ steps.app-token.outputs.token }}
26
+
run: |
27
+
git config user.name "github-actions"
28
+
git config user.email "test@users.noreply.github.com"
29
+
git config --unset-all http.https://github.com/.extraheader
30
+
git remote add internal https://x-access-token:${TOKEN}@github.com/bluesky-social/indigo-internal.git
31
+
git push internal main --force
+1
.gitignore
+1
.gitignore
+17
.tangled/workflows/build.yml
+17
.tangled/workflows/build.yml
+16
.tangled/workflows/lint.yml
+16
.tangled/workflows/lint.yml
+29
.tangled/workflows/lint_build_test.yml
+29
.tangled/workflows/lint_build_test.yml
···
1
+
when:
2
+
- event: push
3
+
branch: ["main", "ci"]
4
+
5
+
dependencies:
6
+
nixpkgs:
7
+
- go
8
+
- gnumake
9
+
- gcc
10
+
11
+
steps:
12
+
- name: fetch deps
13
+
command: go mod tidy
14
+
15
+
- name: lint
16
+
command: |
17
+
ls
18
+
echo "$GOPATH"
19
+
make lint
20
+
21
+
- name: build
22
+
command: make build
23
+
24
+
- name: test
25
+
command: |
26
+
make test
27
+
28
+
29
+
+16
.tangled/workflows/test.yml
+16
.tangled/workflows/test.yml
+1
-1
Makefile
+1
-1
Makefile
+11
api/atproto/moderationcreateReport.go
+11
api/atproto/moderationcreateReport.go
···
14
14
15
15
// ModerationCreateReport_Input is the input argument to a com.atproto.moderation.createReport call.
16
16
type ModerationCreateReport_Input struct {
17
+
ModTool *ModerationCreateReport_ModTool `json:"modTool,omitempty" cborgen:"modTool,omitempty"`
17
18
// reason: Additional context about the content and violation.
18
19
Reason *string `json:"reason,omitempty" cborgen:"reason,omitempty"`
19
20
// reasonType: Indicates the broad category of violation the report is for.
···
54
55
default:
55
56
return nil
56
57
}
58
+
}
59
+
60
+
// ModerationCreateReport_ModTool is a "modTool" in the com.atproto.moderation.createReport schema.
61
+
//
62
+
// Moderation tool information for tracing the source of the action
63
+
type ModerationCreateReport_ModTool struct {
64
+
// meta: Additional arbitrary metadata about the source
65
+
Meta *interface{} `json:"meta,omitempty" cborgen:"meta,omitempty"`
66
+
// name: Name/identifier of the source (e.g., 'bsky-app/android', 'bsky-web/chrome')
67
+
Name string `json:"name" cborgen:"name"`
57
68
}
58
69
59
70
// ModerationCreateReport_Output is the output of a com.atproto.moderation.createReport call.
+6
-4
api/bsky/notificationregisterPush.go
+6
-4
api/bsky/notificationregisterPush.go
···
12
12
13
13
// NotificationRegisterPush_Input is the input argument to a app.bsky.notification.registerPush call.
14
14
type NotificationRegisterPush_Input struct {
15
-
AppId string `json:"appId" cborgen:"appId"`
16
-
Platform string `json:"platform" cborgen:"platform"`
17
-
ServiceDid string `json:"serviceDid" cborgen:"serviceDid"`
18
-
Token string `json:"token" cborgen:"token"`
15
+
// ageRestricted: Set to true when the actor is age restricted
16
+
AgeRestricted *bool `json:"ageRestricted,omitempty" cborgen:"ageRestricted,omitempty"`
17
+
AppId string `json:"appId" cborgen:"appId"`
18
+
Platform string `json:"platform" cborgen:"platform"`
19
+
ServiceDid string `json:"serviceDid" cborgen:"serviceDid"`
20
+
Token string `json:"token" cborgen:"token"`
19
21
}
20
22
21
23
// NotificationRegisterPush calls the XRPC method "app.bsky.notification.registerPush".
+28
api/bsky/notificationunregisterPush.go
+28
api/bsky/notificationunregisterPush.go
···
1
+
// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2
+
3
+
package bsky
4
+
5
+
// schema: app.bsky.notification.unregisterPush
6
+
7
+
import (
8
+
"context"
9
+
10
+
"github.com/bluesky-social/indigo/lex/util"
11
+
)
12
+
13
+
// NotificationUnregisterPush_Input is the input argument to a app.bsky.notification.unregisterPush call.
14
+
type NotificationUnregisterPush_Input struct {
15
+
AppId string `json:"appId" cborgen:"appId"`
16
+
Platform string `json:"platform" cborgen:"platform"`
17
+
ServiceDid string `json:"serviceDid" cborgen:"serviceDid"`
18
+
Token string `json:"token" cborgen:"token"`
19
+
}
20
+
21
+
// NotificationUnregisterPush calls the XRPC method "app.bsky.notification.unregisterPush".
22
+
func NotificationUnregisterPush(ctx context.Context, c util.LexClient, input *NotificationUnregisterPush_Input) error {
23
+
if err := c.LexDo(ctx, util.Procedure, "application/json", "app.bsky.notification.unregisterPush", nil, input, nil); err != nil {
24
+
return err
25
+
}
26
+
27
+
return nil
28
+
}
+32
api/bsky/unspecceddefs.go
+32
api/bsky/unspecceddefs.go
···
4
4
5
5
// schema: app.bsky.unspecced.defs
6
6
7
+
// UnspeccedDefs_AgeAssuranceEvent is a "ageAssuranceEvent" in the app.bsky.unspecced.defs schema.
8
+
//
9
+
// Object used to store age assurance data in stash.
10
+
type UnspeccedDefs_AgeAssuranceEvent struct {
11
+
// attemptId: The unique identifier for this instance of the age assurance flow, in UUID format.
12
+
AttemptId string `json:"attemptId" cborgen:"attemptId"`
13
+
// completeIp: The IP address used when completing the AA flow.
14
+
CompleteIp *string `json:"completeIp,omitempty" cborgen:"completeIp,omitempty"`
15
+
// completeUa: The user agent used when completing the AA flow.
16
+
CompleteUa *string `json:"completeUa,omitempty" cborgen:"completeUa,omitempty"`
17
+
// createdAt: The date and time of this write operation.
18
+
CreatedAt string `json:"createdAt" cborgen:"createdAt"`
19
+
// email: The email used for AA.
20
+
Email *string `json:"email,omitempty" cborgen:"email,omitempty"`
21
+
// initIp: The IP address used when initiating the AA flow.
22
+
InitIp *string `json:"initIp,omitempty" cborgen:"initIp,omitempty"`
23
+
// initUa: The user agent used when initiating the AA flow.
24
+
InitUa *string `json:"initUa,omitempty" cborgen:"initUa,omitempty"`
25
+
// status: The status of the age assurance process.
26
+
Status string `json:"status" cborgen:"status"`
27
+
}
28
+
29
+
// UnspeccedDefs_AgeAssuranceState is a "ageAssuranceState" in the app.bsky.unspecced.defs schema.
30
+
//
31
+
// The computed state of the age assurance process, returned to the user in question on certain authenticated requests.
32
+
type UnspeccedDefs_AgeAssuranceState struct {
33
+
// lastInitiatedAt: The timestamp when this state was last updated.
34
+
LastInitiatedAt *string `json:"lastInitiatedAt,omitempty" cborgen:"lastInitiatedAt,omitempty"`
35
+
// status: The status of the age assurance process.
36
+
Status string `json:"status" cborgen:"status"`
37
+
}
38
+
7
39
// UnspeccedDefs_SkeletonSearchActor is a "skeletonSearchActor" in the app.bsky.unspecced.defs schema.
8
40
type UnspeccedDefs_SkeletonSearchActor struct {
9
41
Did string `json:"did" cborgen:"did"`
+21
api/bsky/unspeccedgetAgeAssuranceState.go
+21
api/bsky/unspeccedgetAgeAssuranceState.go
···
1
+
// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2
+
3
+
package bsky
4
+
5
+
// schema: app.bsky.unspecced.getAgeAssuranceState
6
+
7
+
import (
8
+
"context"
9
+
10
+
"github.com/bluesky-social/indigo/lex/util"
11
+
)
12
+
13
+
// UnspeccedGetAgeAssuranceState calls the XRPC method "app.bsky.unspecced.getAgeAssuranceState".
14
+
func UnspeccedGetAgeAssuranceState(ctx context.Context, c util.LexClient) (*UnspeccedDefs_AgeAssuranceState, error) {
15
+
var out UnspeccedDefs_AgeAssuranceState
16
+
if err := c.LexDo(ctx, util.Query, "", "app.bsky.unspecced.getAgeAssuranceState", nil, nil, &out); err != nil {
17
+
return nil, err
18
+
}
19
+
20
+
return &out, nil
21
+
}
+31
api/bsky/unspeccedinitAgeAssurance.go
+31
api/bsky/unspeccedinitAgeAssurance.go
···
1
+
// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2
+
3
+
package bsky
4
+
5
+
// schema: app.bsky.unspecced.initAgeAssurance
6
+
7
+
import (
8
+
"context"
9
+
10
+
"github.com/bluesky-social/indigo/lex/util"
11
+
)
12
+
13
+
// UnspeccedInitAgeAssurance_Input is the input argument to a app.bsky.unspecced.initAgeAssurance call.
14
+
type UnspeccedInitAgeAssurance_Input struct {
15
+
// countryCode: An ISO 3166-1 alpha-2 code of the user's location.
16
+
CountryCode string `json:"countryCode" cborgen:"countryCode"`
17
+
// email: The user's email address to receive assurance instructions.
18
+
Email string `json:"email" cborgen:"email"`
19
+
// language: The user's preferred language for communication during the assurance process.
20
+
Language string `json:"language" cborgen:"language"`
21
+
}
22
+
23
+
// UnspeccedInitAgeAssurance calls the XRPC method "app.bsky.unspecced.initAgeAssurance".
24
+
func UnspeccedInitAgeAssurance(ctx context.Context, c util.LexClient, input *UnspeccedInitAgeAssurance_Input) (*UnspeccedDefs_AgeAssuranceState, error) {
25
+
var out UnspeccedDefs_AgeAssuranceState
26
+
if err := c.LexDo(ctx, util.Procedure, "application/json", "app.bsky.unspecced.initAgeAssurance", nil, input, &out); err != nil {
27
+
return nil, err
28
+
}
29
+
30
+
return &out, nil
31
+
}
+133
-40
api/ozone/moderationdefs.go
+133
-40
api/ozone/moderationdefs.go
···
56
56
TakedownCount *int64 `json:"takedownCount,omitempty" cborgen:"takedownCount,omitempty"`
57
57
}
58
58
59
+
// ModerationDefs_AgeAssuranceEvent is a "ageAssuranceEvent" in the tools.ozone.moderation.defs schema.
60
+
//
61
+
// Age assurance info coming directly from users. Only works on DID subjects.
62
+
//
63
+
// RECORDTYPE: ModerationDefs_AgeAssuranceEvent
64
+
type ModerationDefs_AgeAssuranceEvent struct {
65
+
LexiconTypeID string `json:"$type,const=tools.ozone.moderation.defs#ageAssuranceEvent" cborgen:"$type,const=tools.ozone.moderation.defs#ageAssuranceEvent"`
66
+
// attemptId: The unique identifier for this instance of the age assurance flow, in UUID format.
67
+
AttemptId string `json:"attemptId" cborgen:"attemptId"`
68
+
// completeIp: The IP address used when completing the AA flow.
69
+
CompleteIp *string `json:"completeIp,omitempty" cborgen:"completeIp,omitempty"`
70
+
// completeUa: The user agent used when completing the AA flow.
71
+
CompleteUa *string `json:"completeUa,omitempty" cborgen:"completeUa,omitempty"`
72
+
// createdAt: The date and time of this write operation.
73
+
CreatedAt string `json:"createdAt" cborgen:"createdAt"`
74
+
// initIp: The IP address used when initiating the AA flow.
75
+
InitIp *string `json:"initIp,omitempty" cborgen:"initIp,omitempty"`
76
+
// initUa: The user agent used when initiating the AA flow.
77
+
InitUa *string `json:"initUa,omitempty" cborgen:"initUa,omitempty"`
78
+
// status: The status of the age assurance process.
79
+
Status string `json:"status" cborgen:"status"`
80
+
}
81
+
82
+
// ModerationDefs_AgeAssuranceOverrideEvent is a "ageAssuranceOverrideEvent" in the tools.ozone.moderation.defs schema.
83
+
//
84
+
// Age assurance status override by moderators. Only works on DID subjects.
85
+
//
86
+
// RECORDTYPE: ModerationDefs_AgeAssuranceOverrideEvent
87
+
type ModerationDefs_AgeAssuranceOverrideEvent struct {
88
+
LexiconTypeID string `json:"$type,const=tools.ozone.moderation.defs#ageAssuranceOverrideEvent" cborgen:"$type,const=tools.ozone.moderation.defs#ageAssuranceOverrideEvent"`
89
+
// comment: Comment describing the reason for the override.
90
+
Comment string `json:"comment" cborgen:"comment"`
91
+
// status: The status to be set for the user decided by a moderator, overriding whatever value the user had previously. Use reset to default to original state.
92
+
Status string `json:"status" cborgen:"status"`
93
+
}
94
+
59
95
// ModerationDefs_BlobView is a "blobView" in the tools.ozone.moderation.defs schema.
60
96
type ModerationDefs_BlobView struct {
61
97
Cid string `json:"cid" cborgen:"cid"`
···
323
359
CreatorHandle *string `json:"creatorHandle,omitempty" cborgen:"creatorHandle,omitempty"`
324
360
Event *ModerationDefs_ModEventView_Event `json:"event" cborgen:"event"`
325
361
Id int64 `json:"id" cborgen:"id"`
362
+
ModTool *ModerationDefs_ModTool `json:"modTool,omitempty" cborgen:"modTool,omitempty"`
326
363
Subject *ModerationDefs_ModEventView_Subject `json:"subject" cborgen:"subject"`
327
364
SubjectBlobCids []string `json:"subjectBlobCids" cborgen:"subjectBlobCids"`
328
365
SubjectHandle *string `json:"subjectHandle,omitempty" cborgen:"subjectHandle,omitempty"`
···
334
371
CreatedBy string `json:"createdBy" cborgen:"createdBy"`
335
372
Event *ModerationDefs_ModEventViewDetail_Event `json:"event" cborgen:"event"`
336
373
Id int64 `json:"id" cborgen:"id"`
374
+
ModTool *ModerationDefs_ModTool `json:"modTool,omitempty" cborgen:"modTool,omitempty"`
337
375
Subject *ModerationDefs_ModEventViewDetail_Subject `json:"subject" cborgen:"subject"`
338
376
SubjectBlobs []*ModerationDefs_BlobView `json:"subjectBlobs" cborgen:"subjectBlobs"`
339
377
}
340
378
341
379
type ModerationDefs_ModEventViewDetail_Event struct {
342
-
ModerationDefs_ModEventTakedown *ModerationDefs_ModEventTakedown
343
-
ModerationDefs_ModEventReverseTakedown *ModerationDefs_ModEventReverseTakedown
344
-
ModerationDefs_ModEventComment *ModerationDefs_ModEventComment
345
-
ModerationDefs_ModEventReport *ModerationDefs_ModEventReport
346
-
ModerationDefs_ModEventLabel *ModerationDefs_ModEventLabel
347
-
ModerationDefs_ModEventAcknowledge *ModerationDefs_ModEventAcknowledge
348
-
ModerationDefs_ModEventEscalate *ModerationDefs_ModEventEscalate
349
-
ModerationDefs_ModEventMute *ModerationDefs_ModEventMute
350
-
ModerationDefs_ModEventUnmute *ModerationDefs_ModEventUnmute
351
-
ModerationDefs_ModEventMuteReporter *ModerationDefs_ModEventMuteReporter
352
-
ModerationDefs_ModEventUnmuteReporter *ModerationDefs_ModEventUnmuteReporter
353
-
ModerationDefs_ModEventEmail *ModerationDefs_ModEventEmail
354
-
ModerationDefs_ModEventResolveAppeal *ModerationDefs_ModEventResolveAppeal
355
-
ModerationDefs_ModEventDivert *ModerationDefs_ModEventDivert
356
-
ModerationDefs_ModEventTag *ModerationDefs_ModEventTag
357
-
ModerationDefs_AccountEvent *ModerationDefs_AccountEvent
358
-
ModerationDefs_IdentityEvent *ModerationDefs_IdentityEvent
359
-
ModerationDefs_RecordEvent *ModerationDefs_RecordEvent
360
-
ModerationDefs_ModEventPriorityScore *ModerationDefs_ModEventPriorityScore
380
+
ModerationDefs_ModEventTakedown *ModerationDefs_ModEventTakedown
381
+
ModerationDefs_ModEventReverseTakedown *ModerationDefs_ModEventReverseTakedown
382
+
ModerationDefs_ModEventComment *ModerationDefs_ModEventComment
383
+
ModerationDefs_ModEventReport *ModerationDefs_ModEventReport
384
+
ModerationDefs_ModEventLabel *ModerationDefs_ModEventLabel
385
+
ModerationDefs_ModEventAcknowledge *ModerationDefs_ModEventAcknowledge
386
+
ModerationDefs_ModEventEscalate *ModerationDefs_ModEventEscalate
387
+
ModerationDefs_ModEventMute *ModerationDefs_ModEventMute
388
+
ModerationDefs_ModEventUnmute *ModerationDefs_ModEventUnmute
389
+
ModerationDefs_ModEventMuteReporter *ModerationDefs_ModEventMuteReporter
390
+
ModerationDefs_ModEventUnmuteReporter *ModerationDefs_ModEventUnmuteReporter
391
+
ModerationDefs_ModEventEmail *ModerationDefs_ModEventEmail
392
+
ModerationDefs_ModEventResolveAppeal *ModerationDefs_ModEventResolveAppeal
393
+
ModerationDefs_ModEventDivert *ModerationDefs_ModEventDivert
394
+
ModerationDefs_ModEventTag *ModerationDefs_ModEventTag
395
+
ModerationDefs_AccountEvent *ModerationDefs_AccountEvent
396
+
ModerationDefs_IdentityEvent *ModerationDefs_IdentityEvent
397
+
ModerationDefs_RecordEvent *ModerationDefs_RecordEvent
398
+
ModerationDefs_ModEventPriorityScore *ModerationDefs_ModEventPriorityScore
399
+
ModerationDefs_AgeAssuranceEvent *ModerationDefs_AgeAssuranceEvent
400
+
ModerationDefs_AgeAssuranceOverrideEvent *ModerationDefs_AgeAssuranceOverrideEvent
361
401
}
362
402
363
403
func (t *ModerationDefs_ModEventViewDetail_Event) MarshalJSON() ([]byte, error) {
···
437
477
t.ModerationDefs_ModEventPriorityScore.LexiconTypeID = "tools.ozone.moderation.defs#modEventPriorityScore"
438
478
return json.Marshal(t.ModerationDefs_ModEventPriorityScore)
439
479
}
480
+
if t.ModerationDefs_AgeAssuranceEvent != nil {
481
+
t.ModerationDefs_AgeAssuranceEvent.LexiconTypeID = "tools.ozone.moderation.defs#ageAssuranceEvent"
482
+
return json.Marshal(t.ModerationDefs_AgeAssuranceEvent)
483
+
}
484
+
if t.ModerationDefs_AgeAssuranceOverrideEvent != nil {
485
+
t.ModerationDefs_AgeAssuranceOverrideEvent.LexiconTypeID = "tools.ozone.moderation.defs#ageAssuranceOverrideEvent"
486
+
return json.Marshal(t.ModerationDefs_AgeAssuranceOverrideEvent)
487
+
}
440
488
return nil, fmt.Errorf("cannot marshal empty enum")
441
489
}
442
490
func (t *ModerationDefs_ModEventViewDetail_Event) UnmarshalJSON(b []byte) error {
···
503
551
case "tools.ozone.moderation.defs#modEventPriorityScore":
504
552
t.ModerationDefs_ModEventPriorityScore = new(ModerationDefs_ModEventPriorityScore)
505
553
return json.Unmarshal(b, t.ModerationDefs_ModEventPriorityScore)
554
+
case "tools.ozone.moderation.defs#ageAssuranceEvent":
555
+
t.ModerationDefs_AgeAssuranceEvent = new(ModerationDefs_AgeAssuranceEvent)
556
+
return json.Unmarshal(b, t.ModerationDefs_AgeAssuranceEvent)
557
+
case "tools.ozone.moderation.defs#ageAssuranceOverrideEvent":
558
+
t.ModerationDefs_AgeAssuranceOverrideEvent = new(ModerationDefs_AgeAssuranceOverrideEvent)
559
+
return json.Unmarshal(b, t.ModerationDefs_AgeAssuranceOverrideEvent)
506
560
507
561
default:
508
562
return nil
···
561
615
}
562
616
563
617
type ModerationDefs_ModEventView_Event struct {
564
-
ModerationDefs_ModEventTakedown *ModerationDefs_ModEventTakedown
565
-
ModerationDefs_ModEventReverseTakedown *ModerationDefs_ModEventReverseTakedown
566
-
ModerationDefs_ModEventComment *ModerationDefs_ModEventComment
567
-
ModerationDefs_ModEventReport *ModerationDefs_ModEventReport
568
-
ModerationDefs_ModEventLabel *ModerationDefs_ModEventLabel
569
-
ModerationDefs_ModEventAcknowledge *ModerationDefs_ModEventAcknowledge
570
-
ModerationDefs_ModEventEscalate *ModerationDefs_ModEventEscalate
571
-
ModerationDefs_ModEventMute *ModerationDefs_ModEventMute
572
-
ModerationDefs_ModEventUnmute *ModerationDefs_ModEventUnmute
573
-
ModerationDefs_ModEventMuteReporter *ModerationDefs_ModEventMuteReporter
574
-
ModerationDefs_ModEventUnmuteReporter *ModerationDefs_ModEventUnmuteReporter
575
-
ModerationDefs_ModEventEmail *ModerationDefs_ModEventEmail
576
-
ModerationDefs_ModEventResolveAppeal *ModerationDefs_ModEventResolveAppeal
577
-
ModerationDefs_ModEventDivert *ModerationDefs_ModEventDivert
578
-
ModerationDefs_ModEventTag *ModerationDefs_ModEventTag
579
-
ModerationDefs_AccountEvent *ModerationDefs_AccountEvent
580
-
ModerationDefs_IdentityEvent *ModerationDefs_IdentityEvent
581
-
ModerationDefs_RecordEvent *ModerationDefs_RecordEvent
582
-
ModerationDefs_ModEventPriorityScore *ModerationDefs_ModEventPriorityScore
618
+
ModerationDefs_ModEventTakedown *ModerationDefs_ModEventTakedown
619
+
ModerationDefs_ModEventReverseTakedown *ModerationDefs_ModEventReverseTakedown
620
+
ModerationDefs_ModEventComment *ModerationDefs_ModEventComment
621
+
ModerationDefs_ModEventReport *ModerationDefs_ModEventReport
622
+
ModerationDefs_ModEventLabel *ModerationDefs_ModEventLabel
623
+
ModerationDefs_ModEventAcknowledge *ModerationDefs_ModEventAcknowledge
624
+
ModerationDefs_ModEventEscalate *ModerationDefs_ModEventEscalate
625
+
ModerationDefs_ModEventMute *ModerationDefs_ModEventMute
626
+
ModerationDefs_ModEventUnmute *ModerationDefs_ModEventUnmute
627
+
ModerationDefs_ModEventMuteReporter *ModerationDefs_ModEventMuteReporter
628
+
ModerationDefs_ModEventUnmuteReporter *ModerationDefs_ModEventUnmuteReporter
629
+
ModerationDefs_ModEventEmail *ModerationDefs_ModEventEmail
630
+
ModerationDefs_ModEventResolveAppeal *ModerationDefs_ModEventResolveAppeal
631
+
ModerationDefs_ModEventDivert *ModerationDefs_ModEventDivert
632
+
ModerationDefs_ModEventTag *ModerationDefs_ModEventTag
633
+
ModerationDefs_AccountEvent *ModerationDefs_AccountEvent
634
+
ModerationDefs_IdentityEvent *ModerationDefs_IdentityEvent
635
+
ModerationDefs_RecordEvent *ModerationDefs_RecordEvent
636
+
ModerationDefs_ModEventPriorityScore *ModerationDefs_ModEventPriorityScore
637
+
ModerationDefs_AgeAssuranceEvent *ModerationDefs_AgeAssuranceEvent
638
+
ModerationDefs_AgeAssuranceOverrideEvent *ModerationDefs_AgeAssuranceOverrideEvent
583
639
}
584
640
585
641
func (t *ModerationDefs_ModEventView_Event) MarshalJSON() ([]byte, error) {
···
659
715
t.ModerationDefs_ModEventPriorityScore.LexiconTypeID = "tools.ozone.moderation.defs#modEventPriorityScore"
660
716
return json.Marshal(t.ModerationDefs_ModEventPriorityScore)
661
717
}
718
+
if t.ModerationDefs_AgeAssuranceEvent != nil {
719
+
t.ModerationDefs_AgeAssuranceEvent.LexiconTypeID = "tools.ozone.moderation.defs#ageAssuranceEvent"
720
+
return json.Marshal(t.ModerationDefs_AgeAssuranceEvent)
721
+
}
722
+
if t.ModerationDefs_AgeAssuranceOverrideEvent != nil {
723
+
t.ModerationDefs_AgeAssuranceOverrideEvent.LexiconTypeID = "tools.ozone.moderation.defs#ageAssuranceOverrideEvent"
724
+
return json.Marshal(t.ModerationDefs_AgeAssuranceOverrideEvent)
725
+
}
662
726
return nil, fmt.Errorf("cannot marshal empty enum")
663
727
}
664
728
func (t *ModerationDefs_ModEventView_Event) UnmarshalJSON(b []byte) error {
···
725
789
case "tools.ozone.moderation.defs#modEventPriorityScore":
726
790
t.ModerationDefs_ModEventPriorityScore = new(ModerationDefs_ModEventPriorityScore)
727
791
return json.Unmarshal(b, t.ModerationDefs_ModEventPriorityScore)
792
+
case "tools.ozone.moderation.defs#ageAssuranceEvent":
793
+
t.ModerationDefs_AgeAssuranceEvent = new(ModerationDefs_AgeAssuranceEvent)
794
+
return json.Unmarshal(b, t.ModerationDefs_AgeAssuranceEvent)
795
+
case "tools.ozone.moderation.defs#ageAssuranceOverrideEvent":
796
+
t.ModerationDefs_AgeAssuranceOverrideEvent = new(ModerationDefs_AgeAssuranceOverrideEvent)
797
+
return json.Unmarshal(b, t.ModerationDefs_AgeAssuranceOverrideEvent)
728
798
729
799
default:
730
800
return nil
···
774
844
}
775
845
}
776
846
847
+
// ModerationDefs_ModTool is a "modTool" in the tools.ozone.moderation.defs schema.
848
+
//
849
+
// Moderation tool information for tracing the source of the action
850
+
type ModerationDefs_ModTool struct {
851
+
// meta: Additional arbitrary metadata about the source
852
+
Meta *interface{} `json:"meta,omitempty" cborgen:"meta,omitempty"`
853
+
// name: Name/identifier of the source (e.g., 'automod', 'ozone/workspace')
854
+
Name string `json:"name" cborgen:"name"`
855
+
}
856
+
777
857
// ModerationDefs_Moderation is a "moderation" in the tools.ozone.moderation.defs schema.
778
858
type ModerationDefs_Moderation struct {
779
859
SubjectStatus *ModerationDefs_SubjectStatusView `json:"subjectStatus,omitempty" cborgen:"subjectStatus,omitempty"`
···
939
1019
type ModerationDefs_SubjectStatusView struct {
940
1020
// accountStats: Statistics related to the account subject
941
1021
AccountStats *ModerationDefs_AccountStats `json:"accountStats,omitempty" cborgen:"accountStats,omitempty"`
1022
+
// ageAssuranceState: Current age assurance state of the subject.
1023
+
AgeAssuranceState *string `json:"ageAssuranceState,omitempty" cborgen:"ageAssuranceState,omitempty"`
1024
+
// ageAssuranceUpdatedBy: Whether or not the last successful update to age assurance was made by the user or admin.
1025
+
AgeAssuranceUpdatedBy *string `json:"ageAssuranceUpdatedBy,omitempty" cborgen:"ageAssuranceUpdatedBy,omitempty"`
942
1026
// appealed: True indicates that the a previously taken moderator action was appealed against, by the author of the content. False indicates last appeal was resolved by moderators.
943
1027
Appealed *bool `json:"appealed,omitempty" cborgen:"appealed,omitempty"`
944
1028
// comment: Sticky comment on the subject.
···
1005
1089
}
1006
1090
1007
1091
type ModerationDefs_SubjectStatusView_Subject struct {
1008
-
AdminDefs_RepoRef *comatprototypes.AdminDefs_RepoRef
1009
-
RepoStrongRef *comatprototypes.RepoStrongRef
1092
+
AdminDefs_RepoRef *comatprototypes.AdminDefs_RepoRef
1093
+
RepoStrongRef *comatprototypes.RepoStrongRef
1094
+
ConvoDefs_MessageRef *chatbskytypes.ConvoDefs_MessageRef
1010
1095
}
1011
1096
1012
1097
func (t *ModerationDefs_SubjectStatusView_Subject) MarshalJSON() ([]byte, error) {
···
1018
1103
t.RepoStrongRef.LexiconTypeID = "com.atproto.repo.strongRef"
1019
1104
return json.Marshal(t.RepoStrongRef)
1020
1105
}
1106
+
if t.ConvoDefs_MessageRef != nil {
1107
+
t.ConvoDefs_MessageRef.LexiconTypeID = "chat.bsky.convo.defs#messageRef"
1108
+
return json.Marshal(t.ConvoDefs_MessageRef)
1109
+
}
1021
1110
return nil, fmt.Errorf("cannot marshal empty enum")
1022
1111
}
1023
1112
func (t *ModerationDefs_SubjectStatusView_Subject) UnmarshalJSON(b []byte) error {
···
1033
1122
case "com.atproto.repo.strongRef":
1034
1123
t.RepoStrongRef = new(comatprototypes.RepoStrongRef)
1035
1124
return json.Unmarshal(b, t.RepoStrongRef)
1125
+
case "chat.bsky.convo.defs#messageRef":
1126
+
t.ConvoDefs_MessageRef = new(chatbskytypes.ConvoDefs_MessageRef)
1127
+
return json.Unmarshal(b, t.ConvoDefs_MessageRef)
1036
1128
1037
1129
default:
1038
1130
return nil
···
1043
1135
//
1044
1136
// Detailed view of a subject. For record subjects, the author's repo and profile will be returned.
1045
1137
type ModerationDefs_SubjectView struct {
1138
+
Profile *util.LexiconTypeDecoder `json:"profile,omitempty" cborgen:"profile,omitempty"`
1046
1139
Record *ModerationDefs_RecordViewDetail `json:"record,omitempty" cborgen:"record,omitempty"`
1047
1140
Repo *ModerationDefs_RepoViewDetail `json:"repo,omitempty" cborgen:"repo,omitempty"`
1048
1141
Status *ModerationDefs_SubjectStatusView `json:"status,omitempty" cborgen:"status,omitempty"`
+40
-21
api/ozone/moderationemitEvent.go
+40
-21
api/ozone/moderationemitEvent.go
···
15
15
16
16
// ModerationEmitEvent_Input is the input argument to a tools.ozone.moderation.emitEvent call.
17
17
type ModerationEmitEvent_Input struct {
18
-
CreatedBy string `json:"createdBy" cborgen:"createdBy"`
19
-
Event *ModerationEmitEvent_Input_Event `json:"event" cborgen:"event"`
18
+
CreatedBy string `json:"createdBy" cborgen:"createdBy"`
19
+
Event *ModerationEmitEvent_Input_Event `json:"event" cborgen:"event"`
20
+
// externalId: An optional external ID for the event, used to deduplicate events from external systems. Fails when an event of same type with the same external ID exists for the same subject.
21
+
ExternalId *string `json:"externalId,omitempty" cborgen:"externalId,omitempty"`
22
+
ModTool *ModerationDefs_ModTool `json:"modTool,omitempty" cborgen:"modTool,omitempty"`
20
23
Subject *ModerationEmitEvent_Input_Subject `json:"subject" cborgen:"subject"`
21
24
SubjectBlobCids []string `json:"subjectBlobCids,omitempty" cborgen:"subjectBlobCids,omitempty"`
22
25
}
23
26
24
27
type ModerationEmitEvent_Input_Event struct {
25
-
ModerationDefs_ModEventTakedown *ModerationDefs_ModEventTakedown
26
-
ModerationDefs_ModEventAcknowledge *ModerationDefs_ModEventAcknowledge
27
-
ModerationDefs_ModEventEscalate *ModerationDefs_ModEventEscalate
28
-
ModerationDefs_ModEventComment *ModerationDefs_ModEventComment
29
-
ModerationDefs_ModEventLabel *ModerationDefs_ModEventLabel
30
-
ModerationDefs_ModEventReport *ModerationDefs_ModEventReport
31
-
ModerationDefs_ModEventMute *ModerationDefs_ModEventMute
32
-
ModerationDefs_ModEventUnmute *ModerationDefs_ModEventUnmute
33
-
ModerationDefs_ModEventMuteReporter *ModerationDefs_ModEventMuteReporter
34
-
ModerationDefs_ModEventUnmuteReporter *ModerationDefs_ModEventUnmuteReporter
35
-
ModerationDefs_ModEventReverseTakedown *ModerationDefs_ModEventReverseTakedown
36
-
ModerationDefs_ModEventResolveAppeal *ModerationDefs_ModEventResolveAppeal
37
-
ModerationDefs_ModEventEmail *ModerationDefs_ModEventEmail
38
-
ModerationDefs_ModEventDivert *ModerationDefs_ModEventDivert
39
-
ModerationDefs_ModEventTag *ModerationDefs_ModEventTag
40
-
ModerationDefs_AccountEvent *ModerationDefs_AccountEvent
41
-
ModerationDefs_IdentityEvent *ModerationDefs_IdentityEvent
42
-
ModerationDefs_RecordEvent *ModerationDefs_RecordEvent
43
-
ModerationDefs_ModEventPriorityScore *ModerationDefs_ModEventPriorityScore
28
+
ModerationDefs_ModEventTakedown *ModerationDefs_ModEventTakedown
29
+
ModerationDefs_ModEventAcknowledge *ModerationDefs_ModEventAcknowledge
30
+
ModerationDefs_ModEventEscalate *ModerationDefs_ModEventEscalate
31
+
ModerationDefs_ModEventComment *ModerationDefs_ModEventComment
32
+
ModerationDefs_ModEventLabel *ModerationDefs_ModEventLabel
33
+
ModerationDefs_ModEventReport *ModerationDefs_ModEventReport
34
+
ModerationDefs_ModEventMute *ModerationDefs_ModEventMute
35
+
ModerationDefs_ModEventUnmute *ModerationDefs_ModEventUnmute
36
+
ModerationDefs_ModEventMuteReporter *ModerationDefs_ModEventMuteReporter
37
+
ModerationDefs_ModEventUnmuteReporter *ModerationDefs_ModEventUnmuteReporter
38
+
ModerationDefs_ModEventReverseTakedown *ModerationDefs_ModEventReverseTakedown
39
+
ModerationDefs_ModEventResolveAppeal *ModerationDefs_ModEventResolveAppeal
40
+
ModerationDefs_ModEventEmail *ModerationDefs_ModEventEmail
41
+
ModerationDefs_ModEventDivert *ModerationDefs_ModEventDivert
42
+
ModerationDefs_ModEventTag *ModerationDefs_ModEventTag
43
+
ModerationDefs_AccountEvent *ModerationDefs_AccountEvent
44
+
ModerationDefs_IdentityEvent *ModerationDefs_IdentityEvent
45
+
ModerationDefs_RecordEvent *ModerationDefs_RecordEvent
46
+
ModerationDefs_ModEventPriorityScore *ModerationDefs_ModEventPriorityScore
47
+
ModerationDefs_AgeAssuranceEvent *ModerationDefs_AgeAssuranceEvent
48
+
ModerationDefs_AgeAssuranceOverrideEvent *ModerationDefs_AgeAssuranceOverrideEvent
44
49
}
45
50
46
51
func (t *ModerationEmitEvent_Input_Event) MarshalJSON() ([]byte, error) {
···
120
125
t.ModerationDefs_ModEventPriorityScore.LexiconTypeID = "tools.ozone.moderation.defs#modEventPriorityScore"
121
126
return json.Marshal(t.ModerationDefs_ModEventPriorityScore)
122
127
}
128
+
if t.ModerationDefs_AgeAssuranceEvent != nil {
129
+
t.ModerationDefs_AgeAssuranceEvent.LexiconTypeID = "tools.ozone.moderation.defs#ageAssuranceEvent"
130
+
return json.Marshal(t.ModerationDefs_AgeAssuranceEvent)
131
+
}
132
+
if t.ModerationDefs_AgeAssuranceOverrideEvent != nil {
133
+
t.ModerationDefs_AgeAssuranceOverrideEvent.LexiconTypeID = "tools.ozone.moderation.defs#ageAssuranceOverrideEvent"
134
+
return json.Marshal(t.ModerationDefs_AgeAssuranceOverrideEvent)
135
+
}
123
136
return nil, fmt.Errorf("cannot marshal empty enum")
124
137
}
125
138
func (t *ModerationEmitEvent_Input_Event) UnmarshalJSON(b []byte) error {
···
186
199
case "tools.ozone.moderation.defs#modEventPriorityScore":
187
200
t.ModerationDefs_ModEventPriorityScore = new(ModerationDefs_ModEventPriorityScore)
188
201
return json.Unmarshal(b, t.ModerationDefs_ModEventPriorityScore)
202
+
case "tools.ozone.moderation.defs#ageAssuranceEvent":
203
+
t.ModerationDefs_AgeAssuranceEvent = new(ModerationDefs_AgeAssuranceEvent)
204
+
return json.Unmarshal(b, t.ModerationDefs_AgeAssuranceEvent)
205
+
case "tools.ozone.moderation.defs#ageAssuranceOverrideEvent":
206
+
t.ModerationDefs_AgeAssuranceOverrideEvent = new(ModerationDefs_AgeAssuranceOverrideEvent)
207
+
return json.Unmarshal(b, t.ModerationDefs_AgeAssuranceOverrideEvent)
189
208
190
209
default:
191
210
return nil
+9
-1
api/ozone/moderationqueryEvents.go
+9
-1
api/ozone/moderationqueryEvents.go
···
20
20
//
21
21
// addedLabels: If specified, only events where all of these labels were added are returned
22
22
// addedTags: If specified, only events where all of these tags were added are returned
23
+
// ageAssuranceState: If specified, only events where the age assurance state matches the given value are returned
23
24
// collections: If specified, only events where the subject belongs to the given collections will be returned. When subjectType is set to 'account', this will be ignored.
24
25
// comment: If specified, only events with comments containing the keyword are returned. Apply || separator to use multiple keywords and match using OR condition.
25
26
// createdAfter: Retrieve events created after a given timestamp
26
27
// createdBefore: Retrieve events created before a given timestamp
27
28
// hasComment: If true, only events with comments are returned
28
29
// includeAllUserRecords: If true, events on all record types (posts, lists, profile etc.) or records from given 'collections' param, owned by the did are returned.
30
+
// modTool: If specified, only events where the modTool name matches any of the given values are returned
29
31
// removedLabels: If specified, only events where all of these labels were removed are returned
30
32
// removedTags: If specified, only events where all of these tags were removed are returned
31
33
// sortDirection: Sort direction for the events. Defaults to descending order of created at timestamp.
32
34
// subjectType: If specified, only events where the subject is of the given type (account or record) will be returned. When this is set to 'account' the 'collections' parameter will be ignored. When includeAllUserRecords or subject is set, this will be ignored.
33
35
// types: The types of events (fully qualified string in the format of tools.ozone.moderation.defs#modEvent<name>) to filter by. If not specified, all events are returned.
34
-
func ModerationQueryEvents(ctx context.Context, c util.LexClient, addedLabels []string, addedTags []string, collections []string, comment string, createdAfter string, createdBefore string, createdBy string, cursor string, hasComment bool, includeAllUserRecords bool, limit int64, policies []string, removedLabels []string, removedTags []string, reportTypes []string, sortDirection string, subject string, subjectType string, types []string) (*ModerationQueryEvents_Output, error) {
36
+
func ModerationQueryEvents(ctx context.Context, c util.LexClient, addedLabels []string, addedTags []string, ageAssuranceState string, collections []string, comment string, createdAfter string, createdBefore string, createdBy string, cursor string, hasComment bool, includeAllUserRecords bool, limit int64, modTool []string, policies []string, removedLabels []string, removedTags []string, reportTypes []string, sortDirection string, subject string, subjectType string, types []string) (*ModerationQueryEvents_Output, error) {
35
37
var out ModerationQueryEvents_Output
36
38
37
39
params := map[string]interface{}{}
···
41
43
if len(addedTags) != 0 {
42
44
params["addedTags"] = addedTags
43
45
}
46
+
if ageAssuranceState != "" {
47
+
params["ageAssuranceState"] = ageAssuranceState
48
+
}
44
49
if len(collections) != 0 {
45
50
params["collections"] = collections
46
51
}
···
67
72
}
68
73
if limit != 0 {
69
74
params["limit"] = limit
75
+
}
76
+
if len(modTool) != 0 {
77
+
params["modTool"] = modTool
70
78
}
71
79
if len(policies) != 0 {
72
80
params["policies"] = policies
+5
-1
api/ozone/moderationqueryStatuses.go
+5
-1
api/ozone/moderationqueryStatuses.go
···
18
18
19
19
// ModerationQueryStatuses calls the XRPC method "tools.ozone.moderation.queryStatuses".
20
20
//
21
+
// ageAssuranceState: If specified, only subjects with the given age assurance state will be returned.
21
22
// appealed: Get subjects in unresolved appealed status
22
23
// collections: If specified, subjects belonging to the given collections will be returned. When subjectType is set to 'account', this will be ignored.
23
24
// comment: Search subjects by keyword from comments
···
45
46
// subject: The subject to get the status for.
46
47
// subjectType: If specified, subjects of the given type (account or record) will be returned. When this is set to 'account' the 'collections' parameter will be ignored. When includeAllUserRecords or subject is set, this will be ignored.
47
48
// takendown: Get subjects that were taken down
48
-
func ModerationQueryStatuses(ctx context.Context, c util.LexClient, appealed bool, collections []string, comment string, cursor string, excludeTags []string, hostingDeletedAfter string, hostingDeletedBefore string, hostingStatuses []string, hostingUpdatedAfter string, hostingUpdatedBefore string, ignoreSubjects []string, includeAllUserRecords bool, includeMuted bool, lastReviewedBy string, limit int64, minAccountSuspendCount int64, minPriorityScore int64, minReportedRecordsCount int64, minTakendownRecordsCount int64, onlyMuted bool, queueCount int64, queueIndex int64, queueSeed string, reportedAfter string, reportedBefore string, reviewState string, reviewedAfter string, reviewedBefore string, sortDirection string, sortField string, subject string, subjectType string, tags []string, takendown bool) (*ModerationQueryStatuses_Output, error) {
49
+
func ModerationQueryStatuses(ctx context.Context, c util.LexClient, ageAssuranceState string, appealed bool, collections []string, comment string, cursor string, excludeTags []string, hostingDeletedAfter string, hostingDeletedBefore string, hostingStatuses []string, hostingUpdatedAfter string, hostingUpdatedBefore string, ignoreSubjects []string, includeAllUserRecords bool, includeMuted bool, lastReviewedBy string, limit int64, minAccountSuspendCount int64, minPriorityScore int64, minReportedRecordsCount int64, minTakendownRecordsCount int64, onlyMuted bool, queueCount int64, queueIndex int64, queueSeed string, reportedAfter string, reportedBefore string, reviewState string, reviewedAfter string, reviewedBefore string, sortDirection string, sortField string, subject string, subjectType string, tags []string, takendown bool) (*ModerationQueryStatuses_Output, error) {
49
50
var out ModerationQueryStatuses_Output
50
51
51
52
params := map[string]interface{}{}
53
+
if ageAssuranceState != "" {
54
+
params["ageAssuranceState"] = ageAssuranceState
55
+
}
52
56
if appealed {
53
57
params["appealed"] = appealed
54
58
}
+34
api/ozone/safelinkaddRule.go
+34
api/ozone/safelinkaddRule.go
···
1
+
// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2
+
3
+
package ozone
4
+
5
+
// schema: tools.ozone.safelink.addRule
6
+
7
+
import (
8
+
"context"
9
+
10
+
"github.com/bluesky-social/indigo/lex/util"
11
+
)
12
+
13
+
// SafelinkAddRule_Input is the input argument to a tools.ozone.safelink.addRule call.
14
+
type SafelinkAddRule_Input struct {
15
+
Action *string `json:"action" cborgen:"action"`
16
+
// comment: Optional comment about the decision
17
+
Comment *string `json:"comment,omitempty" cborgen:"comment,omitempty"`
18
+
// createdBy: Author DID. Only respected when using admin auth
19
+
CreatedBy *string `json:"createdBy,omitempty" cborgen:"createdBy,omitempty"`
20
+
Pattern *string `json:"pattern" cborgen:"pattern"`
21
+
Reason *string `json:"reason" cborgen:"reason"`
22
+
// url: The URL or domain to apply the rule to
23
+
Url string `json:"url" cborgen:"url"`
24
+
}
25
+
26
+
// SafelinkAddRule calls the XRPC method "tools.ozone.safelink.addRule".
27
+
func SafelinkAddRule(ctx context.Context, c util.LexClient, input *SafelinkAddRule_Input) (*SafelinkDefs_Event, error) {
28
+
var out SafelinkDefs_Event
29
+
if err := c.LexDo(ctx, util.Procedure, "application/json", "tools.ozone.safelink.addRule", nil, input, &out); err != nil {
30
+
return nil, err
31
+
}
32
+
33
+
return &out, nil
34
+
}
+43
api/ozone/safelinkdefs.go
+43
api/ozone/safelinkdefs.go
···
1
+
// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2
+
3
+
package ozone
4
+
5
+
// schema: tools.ozone.safelink.defs
6
+
7
+
// SafelinkDefs_Event is a "event" in the tools.ozone.safelink.defs schema.
8
+
//
9
+
// An event for URL safety decisions
10
+
type SafelinkDefs_Event struct {
11
+
Action *string `json:"action" cborgen:"action"`
12
+
// comment: Optional comment about the decision
13
+
Comment *string `json:"comment,omitempty" cborgen:"comment,omitempty"`
14
+
CreatedAt string `json:"createdAt" cborgen:"createdAt"`
15
+
// createdBy: DID of the user who created this rule
16
+
CreatedBy string `json:"createdBy" cborgen:"createdBy"`
17
+
EventType *string `json:"eventType" cborgen:"eventType"`
18
+
// id: Auto-incrementing row ID
19
+
Id int64 `json:"id" cborgen:"id"`
20
+
Pattern *string `json:"pattern" cborgen:"pattern"`
21
+
Reason *string `json:"reason" cborgen:"reason"`
22
+
// url: The URL that this rule applies to
23
+
Url string `json:"url" cborgen:"url"`
24
+
}
25
+
26
+
// SafelinkDefs_UrlRule is a "urlRule" in the tools.ozone.safelink.defs schema.
27
+
//
28
+
// Input for creating a URL safety rule
29
+
type SafelinkDefs_UrlRule struct {
30
+
Action *string `json:"action" cborgen:"action"`
31
+
// comment: Optional comment about the decision
32
+
Comment *string `json:"comment,omitempty" cborgen:"comment,omitempty"`
33
+
// createdAt: Timestamp when the rule was created
34
+
CreatedAt string `json:"createdAt" cborgen:"createdAt"`
35
+
// createdBy: DID of the user added the rule.
36
+
CreatedBy string `json:"createdBy" cborgen:"createdBy"`
37
+
Pattern *string `json:"pattern" cborgen:"pattern"`
38
+
Reason *string `json:"reason" cborgen:"reason"`
39
+
// updatedAt: Timestamp when the rule was last updated
40
+
UpdatedAt string `json:"updatedAt" cborgen:"updatedAt"`
41
+
// url: The URL or domain to apply the rule to
42
+
Url string `json:"url" cborgen:"url"`
43
+
}
+42
api/ozone/safelinkqueryEvents.go
+42
api/ozone/safelinkqueryEvents.go
···
1
+
// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2
+
3
+
package ozone
4
+
5
+
// schema: tools.ozone.safelink.queryEvents
6
+
7
+
import (
8
+
"context"
9
+
10
+
"github.com/bluesky-social/indigo/lex/util"
11
+
)
12
+
13
+
// SafelinkQueryEvents_Input is the input argument to a tools.ozone.safelink.queryEvents call.
14
+
type SafelinkQueryEvents_Input struct {
15
+
// cursor: Cursor for pagination
16
+
Cursor *string `json:"cursor,omitempty" cborgen:"cursor,omitempty"`
17
+
// limit: Maximum number of results to return
18
+
Limit *int64 `json:"limit,omitempty" cborgen:"limit,omitempty"`
19
+
// patternType: Filter by pattern type
20
+
PatternType *string `json:"patternType,omitempty" cborgen:"patternType,omitempty"`
21
+
// sortDirection: Sort direction
22
+
SortDirection *string `json:"sortDirection,omitempty" cborgen:"sortDirection,omitempty"`
23
+
// urls: Filter by specific URLs or domains
24
+
Urls []string `json:"urls,omitempty" cborgen:"urls,omitempty"`
25
+
}
26
+
27
+
// SafelinkQueryEvents_Output is the output of a tools.ozone.safelink.queryEvents call.
28
+
type SafelinkQueryEvents_Output struct {
29
+
// cursor: Next cursor for pagination. Only present if there are more results.
30
+
Cursor *string `json:"cursor,omitempty" cborgen:"cursor,omitempty"`
31
+
Events []*SafelinkDefs_Event `json:"events" cborgen:"events"`
32
+
}
33
+
34
+
// SafelinkQueryEvents calls the XRPC method "tools.ozone.safelink.queryEvents".
35
+
func SafelinkQueryEvents(ctx context.Context, c util.LexClient, input *SafelinkQueryEvents_Input) (*SafelinkQueryEvents_Output, error) {
36
+
var out SafelinkQueryEvents_Output
37
+
if err := c.LexDo(ctx, util.Procedure, "application/json", "tools.ozone.safelink.queryEvents", nil, input, &out); err != nil {
38
+
return nil, err
39
+
}
40
+
41
+
return &out, nil
42
+
}
+48
api/ozone/safelinkqueryRules.go
+48
api/ozone/safelinkqueryRules.go
···
1
+
// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2
+
3
+
package ozone
4
+
5
+
// schema: tools.ozone.safelink.queryRules
6
+
7
+
import (
8
+
"context"
9
+
10
+
"github.com/bluesky-social/indigo/lex/util"
11
+
)
12
+
13
+
// SafelinkQueryRules_Input is the input argument to a tools.ozone.safelink.queryRules call.
14
+
type SafelinkQueryRules_Input struct {
15
+
// actions: Filter by action types
16
+
Actions []string `json:"actions,omitempty" cborgen:"actions,omitempty"`
17
+
// createdBy: Filter by rule creator
18
+
CreatedBy *string `json:"createdBy,omitempty" cborgen:"createdBy,omitempty"`
19
+
// cursor: Cursor for pagination
20
+
Cursor *string `json:"cursor,omitempty" cborgen:"cursor,omitempty"`
21
+
// limit: Maximum number of results to return
22
+
Limit *int64 `json:"limit,omitempty" cborgen:"limit,omitempty"`
23
+
// patternType: Filter by pattern type
24
+
PatternType *string `json:"patternType,omitempty" cborgen:"patternType,omitempty"`
25
+
// reason: Filter by reason type
26
+
Reason *string `json:"reason,omitempty" cborgen:"reason,omitempty"`
27
+
// sortDirection: Sort direction
28
+
SortDirection *string `json:"sortDirection,omitempty" cborgen:"sortDirection,omitempty"`
29
+
// urls: Filter by specific URLs or domains
30
+
Urls []string `json:"urls,omitempty" cborgen:"urls,omitempty"`
31
+
}
32
+
33
+
// SafelinkQueryRules_Output is the output of a tools.ozone.safelink.queryRules call.
34
+
type SafelinkQueryRules_Output struct {
35
+
// cursor: Next cursor for pagination. Only present if there are more results.
36
+
Cursor *string `json:"cursor,omitempty" cborgen:"cursor,omitempty"`
37
+
Rules []*SafelinkDefs_UrlRule `json:"rules" cborgen:"rules"`
38
+
}
39
+
40
+
// SafelinkQueryRules calls the XRPC method "tools.ozone.safelink.queryRules".
41
+
func SafelinkQueryRules(ctx context.Context, c util.LexClient, input *SafelinkQueryRules_Input) (*SafelinkQueryRules_Output, error) {
42
+
var out SafelinkQueryRules_Output
43
+
if err := c.LexDo(ctx, util.Procedure, "application/json", "tools.ozone.safelink.queryRules", nil, input, &out); err != nil {
44
+
return nil, err
45
+
}
46
+
47
+
return &out, nil
48
+
}
+32
api/ozone/safelinkremoveRule.go
+32
api/ozone/safelinkremoveRule.go
···
1
+
// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2
+
3
+
package ozone
4
+
5
+
// schema: tools.ozone.safelink.removeRule
6
+
7
+
import (
8
+
"context"
9
+
10
+
"github.com/bluesky-social/indigo/lex/util"
11
+
)
12
+
13
+
// SafelinkRemoveRule_Input is the input argument to a tools.ozone.safelink.removeRule call.
14
+
type SafelinkRemoveRule_Input struct {
15
+
// comment: Optional comment about why the rule is being removed
16
+
Comment *string `json:"comment,omitempty" cborgen:"comment,omitempty"`
17
+
// createdBy: Optional DID of the user. Only respected when using admin auth.
18
+
CreatedBy *string `json:"createdBy,omitempty" cborgen:"createdBy,omitempty"`
19
+
Pattern *string `json:"pattern" cborgen:"pattern"`
20
+
// url: The URL or domain to remove the rule for
21
+
Url string `json:"url" cborgen:"url"`
22
+
}
23
+
24
+
// SafelinkRemoveRule calls the XRPC method "tools.ozone.safelink.removeRule".
25
+
func SafelinkRemoveRule(ctx context.Context, c util.LexClient, input *SafelinkRemoveRule_Input) (*SafelinkDefs_Event, error) {
26
+
var out SafelinkDefs_Event
27
+
if err := c.LexDo(ctx, util.Procedure, "application/json", "tools.ozone.safelink.removeRule", nil, input, &out); err != nil {
28
+
return nil, err
29
+
}
30
+
31
+
return &out, nil
32
+
}
+34
api/ozone/safelinkupdateRule.go
+34
api/ozone/safelinkupdateRule.go
···
1
+
// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2
+
3
+
package ozone
4
+
5
+
// schema: tools.ozone.safelink.updateRule
6
+
7
+
import (
8
+
"context"
9
+
10
+
"github.com/bluesky-social/indigo/lex/util"
11
+
)
12
+
13
+
// SafelinkUpdateRule_Input is the input argument to a tools.ozone.safelink.updateRule call.
14
+
type SafelinkUpdateRule_Input struct {
15
+
Action *string `json:"action" cborgen:"action"`
16
+
// comment: Optional comment about the update
17
+
Comment *string `json:"comment,omitempty" cborgen:"comment,omitempty"`
18
+
// createdBy: Optional DID to credit as the creator. Only respected for admin_token authentication.
19
+
CreatedBy *string `json:"createdBy,omitempty" cborgen:"createdBy,omitempty"`
20
+
Pattern *string `json:"pattern" cborgen:"pattern"`
21
+
Reason *string `json:"reason" cborgen:"reason"`
22
+
// url: The URL or domain to update the rule for
23
+
Url string `json:"url" cborgen:"url"`
24
+
}
25
+
26
+
// SafelinkUpdateRule calls the XRPC method "tools.ozone.safelink.updateRule".
27
+
func SafelinkUpdateRule(ctx context.Context, c util.LexClient, input *SafelinkUpdateRule_Input) (*SafelinkDefs_Event, error) {
28
+
var out SafelinkDefs_Event
29
+
if err := c.LexDo(ctx, util.Procedure, "application/json", "tools.ozone.safelink.updateRule", nil, input, &out); err != nil {
30
+
return nil, err
31
+
}
32
+
33
+
return &out, nil
34
+
}
+6
-4
api/ozone/verificationdefs.go
+6
-4
api/ozone/verificationdefs.go
···
22
22
// handle: Handle of the subject the verification applies to at the moment of verifying, which might not be the same at the time of viewing. The verification is only valid if the current handle matches the one at the time of verifying.
23
23
Handle string `json:"handle" cborgen:"handle"`
24
24
// issuer: The user who issued this verification.
25
-
Issuer string `json:"issuer" cborgen:"issuer"`
26
-
IssuerRepo *VerificationDefs_VerificationView_IssuerRepo `json:"issuerRepo,omitempty" cborgen:"issuerRepo,omitempty"`
25
+
Issuer string `json:"issuer" cborgen:"issuer"`
26
+
IssuerProfile *util.LexiconTypeDecoder `json:"issuerProfile,omitempty" cborgen:"issuerProfile,omitempty"`
27
+
IssuerRepo *VerificationDefs_VerificationView_IssuerRepo `json:"issuerRepo,omitempty" cborgen:"issuerRepo,omitempty"`
27
28
// revokeReason: Describes the reason for revocation, also indicating that the verification is no longer valid.
28
29
RevokeReason *string `json:"revokeReason,omitempty" cborgen:"revokeReason,omitempty"`
29
30
// revokedAt: Timestamp when the verification was revoked.
···
31
32
// revokedBy: The user who revoked this verification.
32
33
RevokedBy *string `json:"revokedBy,omitempty" cborgen:"revokedBy,omitempty"`
33
34
// subject: The subject of the verification.
34
-
Subject string `json:"subject" cborgen:"subject"`
35
-
SubjectRepo *VerificationDefs_VerificationView_SubjectRepo `json:"subjectRepo,omitempty" cborgen:"subjectRepo,omitempty"`
35
+
Subject string `json:"subject" cborgen:"subject"`
36
+
SubjectProfile *util.LexiconTypeDecoder `json:"subjectProfile,omitempty" cborgen:"subjectProfile,omitempty"`
37
+
SubjectRepo *VerificationDefs_VerificationView_SubjectRepo `json:"subjectRepo,omitempty" cborgen:"subjectRepo,omitempty"`
36
38
// uri: The AT-URI of the verification record.
37
39
Uri string `json:"uri" cborgen:"uri"`
38
40
}
+1
atproto/identity/apidir/apidir.go
+1
atproto/identity/apidir/apidir.go
+1
atproto/identity/base_directory.go
+1
atproto/identity/base_directory.go
···
55
55
if err != nil {
56
56
return nil, fmt.Errorf("could not verify handle/DID match: %w", err)
57
57
}
58
+
// NOTE: DeclaredHandle() returns a normalized handle, and we already normalized 'h' above
58
59
if declared != h {
59
60
return nil, fmt.Errorf("%w: %s != %s", ErrHandleMismatch, declared, h)
60
61
}
+2
atproto/identity/cache_directory.go
+2
atproto/identity/cache_directory.go
···
93
93
}
94
94
95
95
func (d *CacheDirectory) ResolveHandle(ctx context.Context, h syntax.Handle) (syntax.DID, error) {
96
+
h = h.Normalize()
96
97
if h.IsInvalidHandle() {
97
98
return "", fmt.Errorf("can not resolve handle: %w", ErrInvalidHandle)
98
99
}
···
251
252
if err != nil {
252
253
return nil, hit, fmt.Errorf("could not verify handle/DID mapping: %w", err)
253
254
}
255
+
// NOTE: DeclaredHandle() returns a normalized handle, and we already normalized 'h' above
254
256
if declared != h {
255
257
return nil, hit, fmt.Errorf("%w: %s != %s", ErrHandleMismatch, declared, h)
256
258
}
+2
atproto/identity/handle.go
+2
atproto/identity/handle.go
+5
-1
atproto/identity/identity.go
+5
-1
atproto/identity/identity.go
···
198
198
func (i *Identity) DeclaredHandle() (syntax.Handle, error) {
199
199
for _, u := range i.AlsoKnownAs {
200
200
if strings.HasPrefix(u, "at://") && len(u) > len("at://") {
201
-
return syntax.ParseHandle(u[5:])
201
+
hdl, err := syntax.ParseHandle(u[5:])
202
+
if err != nil {
203
+
continue
204
+
}
205
+
return hdl.Normalize(), nil
202
206
}
203
207
}
204
208
return "", ErrHandleNotDeclared
+70
atproto/identity/identity_test.go
+70
atproto/identity/identity_test.go
···
1
+
package identity
2
+
3
+
import (
4
+
"encoding/json"
5
+
"io"
6
+
"os"
7
+
"testing"
8
+
9
+
"github.com/stretchr/testify/assert"
10
+
)
11
+
12
+
// Tests parsing and normalizing handles from DID documents
13
+
func TestHandleExtraction(t *testing.T) {
14
+
assert := assert.New(t)
15
+
f, err := os.Open("testdata/did_plc_doc.json")
16
+
if err != nil {
17
+
t.Fatal(err)
18
+
}
19
+
defer f.Close()
20
+
21
+
docBytes, err := io.ReadAll(f)
22
+
if err != nil {
23
+
t.Fatal(err)
24
+
}
25
+
26
+
var doc DIDDocument
27
+
err = json.Unmarshal(docBytes, &doc)
28
+
assert.NoError(err)
29
+
30
+
{
31
+
ident := ParseIdentity(&doc)
32
+
hdl, err := ident.DeclaredHandle()
33
+
assert.NoError(err)
34
+
assert.Equal("atproto.com", hdl.String())
35
+
}
36
+
37
+
{
38
+
doc.AlsoKnownAs = []string{
39
+
"at://BLAH.com",
40
+
"at://other.org",
41
+
}
42
+
ident := ParseIdentity(&doc)
43
+
hdl, err := ident.DeclaredHandle()
44
+
assert.NoError(err)
45
+
assert.Equal("blah.com", hdl.String())
46
+
}
47
+
48
+
{
49
+
doc.AlsoKnownAs = []string{
50
+
"https://http.example.com",
51
+
"at://under_example_com",
52
+
"at://correct.EXAMPLE.com",
53
+
"at://other.example.com",
54
+
}
55
+
ident := ParseIdentity(&doc)
56
+
hdl, err := ident.DeclaredHandle()
57
+
assert.NoError(err)
58
+
assert.Equal("correct.example.com", hdl.String())
59
+
}
60
+
61
+
{
62
+
doc.AlsoKnownAs = []string{
63
+
"https://http.example.com",
64
+
}
65
+
ident := ParseIdentity(&doc)
66
+
_, err := ident.DeclaredHandle()
67
+
assert.Error(err)
68
+
assert.Equal("handle.invalid", ident.Handle.String())
69
+
}
70
+
}
+28
-1
atproto/identity/mock_directory.go
+28
-1
atproto/identity/mock_directory.go
···
4
4
"context"
5
5
"encoding/json"
6
6
"fmt"
7
+
"sync"
7
8
8
9
"github.com/bluesky-social/indigo/atproto/syntax"
9
10
)
10
11
11
12
// A fake identity directory, for use in tests
12
13
type MockDirectory struct {
14
+
mu *sync.RWMutex
13
15
Handles map[syntax.Handle]syntax.DID
14
16
Identities map[syntax.DID]Identity
15
17
}
···
19
21
20
22
func NewMockDirectory() MockDirectory {
21
23
return MockDirectory{
24
+
mu: &sync.RWMutex{},
22
25
Handles: make(map[syntax.Handle]syntax.DID),
23
26
Identities: make(map[syntax.DID]Identity),
24
27
}
25
28
}
26
29
27
30
func (d *MockDirectory) Insert(ident Identity) {
31
+
d.mu.Lock()
32
+
defer d.mu.Unlock()
33
+
28
34
if !ident.Handle.IsInvalidHandle() {
29
-
d.Handles[ident.Handle] = ident.DID
35
+
d.Handles[ident.Handle.Normalize()] = ident.DID
30
36
}
31
37
d.Identities[ident.DID] = ident
32
38
}
33
39
34
40
func (d *MockDirectory) LookupHandle(ctx context.Context, h syntax.Handle) (*Identity, error) {
41
+
d.mu.RLock()
42
+
defer d.mu.RUnlock()
43
+
35
44
h = h.Normalize()
36
45
did, ok := d.Handles[h]
37
46
if !ok {
···
45
54
}
46
55
47
56
func (d *MockDirectory) LookupDID(ctx context.Context, did syntax.DID) (*Identity, error) {
57
+
d.mu.RLock()
58
+
defer d.mu.RUnlock()
59
+
48
60
ident, ok := d.Identities[did]
49
61
if !ok {
50
62
return nil, ErrDIDNotFound
···
53
65
}
54
66
55
67
func (d *MockDirectory) Lookup(ctx context.Context, a syntax.AtIdentifier) (*Identity, error) {
68
+
d.mu.RLock()
69
+
defer d.mu.RUnlock()
70
+
56
71
handle, err := a.AsHandle()
57
72
if nil == err { // if not an error, is a Handle
58
73
return d.LookupHandle(ctx, handle)
···
65
80
}
66
81
67
82
func (d *MockDirectory) ResolveHandle(ctx context.Context, h syntax.Handle) (syntax.DID, error) {
83
+
d.mu.RLock()
84
+
defer d.mu.RUnlock()
85
+
68
86
h = h.Normalize()
69
87
did, ok := d.Handles[h]
70
88
if !ok {
···
74
92
}
75
93
76
94
func (d *MockDirectory) ResolveDID(ctx context.Context, did syntax.DID) (*DIDDocument, error) {
95
+
d.mu.RLock()
96
+
defer d.mu.RUnlock()
97
+
77
98
ident, ok := d.Identities[did]
78
99
if !ok {
79
100
return nil, ErrDIDNotFound
···
83
104
}
84
105
85
106
func (d *MockDirectory) ResolveDIDRaw(ctx context.Context, did syntax.DID) (json.RawMessage, error) {
107
+
d.mu.RLock()
108
+
defer d.mu.RUnlock()
109
+
86
110
ident, ok := d.Identities[did]
87
111
if !ok {
88
112
return nil, ErrDIDNotFound
···
92
116
}
93
117
94
118
func (d *MockDirectory) Purge(ctx context.Context, a syntax.AtIdentifier) error {
119
+
d.mu.Lock()
120
+
defer d.mu.Unlock()
121
+
95
122
return nil
96
123
}
+5
atproto/identity/mock_directory_test.go
+5
atproto/identity/mock_directory_test.go
···
65
65
66
66
_, err = c.ResolveDID(ctx, syntax.DID("did:plc:abc999"))
67
67
assert.ErrorIs(err, ErrDIDNotFound)
68
+
69
+
// handle lookups should be case-insensitive
70
+
_, err = c.ResolveHandle(ctx, syntax.Handle("handle.EXAMPLE.com"))
71
+
assert.NoError(err)
72
+
assert.Equal(id1.DID, did)
68
73
}
+2
atproto/identity/redisdir/redis_directory.go
+2
atproto/identity/redisdir/redis_directory.go
···
156
156
157
157
func (d *RedisDirectory) ResolveHandle(ctx context.Context, h syntax.Handle) (syntax.DID, error) {
158
158
start := time.Now()
159
+
h = h.Normalize()
159
160
if h.IsInvalidHandle() {
160
161
return "", fmt.Errorf("can not resolve handle: %w", identity.ErrInvalidHandle)
161
162
}
···
359
360
if err != nil {
360
361
return nil, hit, err
361
362
}
363
+
// NOTE: DeclaredHandle() returns a normalized handle, and we already normalized 'h' above
362
364
if declared != h {
363
365
return nil, hit, identity.ErrHandleMismatch
364
366
}
+2
atproto/label/label.go
+2
atproto/label/label.go
···
140
140
Cid: l.CID,
141
141
Cts: l.CreatedAt,
142
142
Exp: l.ExpiresAt,
143
+
Neg: l.Negated,
143
144
Sig: []byte(l.Sig),
144
145
Src: l.SourceDID,
145
146
Uri: l.URI,
···
157
158
CID: l.Cid,
158
159
CreatedAt: l.Cts,
159
160
ExpiresAt: l.Exp,
161
+
Negated: l.Neg,
160
162
Sig: []byte(l.Sig),
161
163
SourceDID: l.Src,
162
164
URI: l.Uri,
+62
atproto/label/label_test.go
+62
atproto/label/label_test.go
···
4
4
"encoding/json"
5
5
"testing"
6
6
7
+
comatproto "github.com/bluesky-social/indigo/api/atproto"
7
8
"github.com/bluesky-social/indigo/atproto/crypto"
8
9
9
10
"github.com/stretchr/testify/assert"
···
89
90
assert.NoError(l.Sign(priv))
90
91
assert.NoError(l.VerifySignature(pub))
91
92
}
93
+
94
+
func TestToLexicon(t *testing.T) {
95
+
assert := assert.New(t)
96
+
97
+
expiresAt := "2025-07-28T23:53:19.804Z"
98
+
negated := true
99
+
cid := "bafyreifxykqhed72s26cr4i64rxvrtofeqrly3j4vjzbkvo3ckkjbxjqtq"
100
+
101
+
l := Label{
102
+
CID: &cid,
103
+
CreatedAt: "2024-10-23T17:51:19.128Z",
104
+
ExpiresAt: &expiresAt,
105
+
Negated: &negated,
106
+
SourceDID: "did:plc:ewvi7nxzyoun6zhxrhs64oiz",
107
+
URI: "at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/app.bsky.actor.profile/self",
108
+
Val: "good",
109
+
Version: ATPROTO_LABEL_VERSION,
110
+
Sig: []byte("sig"), // invalid, but we only care about the conversion
111
+
}
112
+
113
+
lex := l.ToLexicon()
114
+
assert.Equal(l.Version, *lex.Ver)
115
+
assert.Equal(l.CreatedAt, lex.Cts)
116
+
assert.Equal(l.URI, lex.Uri)
117
+
assert.Equal(l.Val, lex.Val)
118
+
assert.Equal(l.CID, lex.Cid)
119
+
assert.Equal(l.ExpiresAt, lex.Exp)
120
+
assert.Equal(l.Negated, lex.Neg)
121
+
assert.Equal(l.SourceDID, lex.Src)
122
+
}
123
+
124
+
func TestFromLexicon(t *testing.T) {
125
+
assert := assert.New(t)
126
+
127
+
expiresAt := "2025-07-28T23:53:19.804Z"
128
+
negated := true
129
+
cid := "bafyreifxykqhed72s26cr4i64rxvrtofeqrly3j4vjzbkvo3ckkjbxjqtq"
130
+
version := int64(1)
131
+
132
+
lex := &comatproto.LabelDefs_Label{
133
+
Cid: &cid,
134
+
Cts: "2024-10-23T17:51:19.128Z",
135
+
Exp: &expiresAt,
136
+
Neg: &negated,
137
+
Src: "did:plc:ewvi7nxzyoun6zhxrhs64oiz",
138
+
Uri: "at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/app.bsky.actor.profile/self",
139
+
Val: "good",
140
+
Ver: &version,
141
+
Sig: []byte("sig"), // invalid, but we only care about the conversion
142
+
}
143
+
144
+
l := FromLexicon(lex)
145
+
assert.Equal(lex.Ver, &l.Version)
146
+
assert.Equal(lex.Cts, l.CreatedAt)
147
+
assert.Equal(lex.Uri, l.URI)
148
+
assert.Equal(lex.Val, l.Val)
149
+
assert.Equal(lex.Cid, l.CID)
150
+
assert.Equal(lex.Exp, l.ExpiresAt)
151
+
assert.Equal(lex.Neg, l.Negated)
152
+
assert.Equal(lex.Src, l.SourceDID)
153
+
}
+2
automod/consumer/ozone.go
+2
automod/consumer/ozone.go
···
60
60
oc.OzoneClient,
61
61
nil, // addedLabels []string
62
62
nil, // addedTags []string
63
+
"", // ageAssuranceState
63
64
nil, // collections []string
64
65
"", // comment string
65
66
since.String(), // createdAfter string
···
69
70
false, // hasComment bool
70
71
true, // includeAllUserRecords bool
71
72
limit, // limit int64
73
+
nil, // modTool
72
74
nil, // policies []string
73
75
nil, // removedLabels []string
74
76
nil, // removedTags []string
+4
automod/engine/persisthelpers.go
+4
automod/engine/persisthelpers.go
···
173
173
xrpcc,
174
174
nil, // addedLabels []string
175
175
nil, // addedTags []string
176
+
"", // ageAssuranceState
176
177
nil, // collections []string
177
178
"", // comment string
178
179
"", // createdAfter string
···
182
183
false, // hasComment bool
183
184
false, // includeAllUserRecords bool
184
185
5, // limit int64
186
+
nil, // modTool
185
187
nil, // policies []string
186
188
nil, // removedLabels []string
187
189
nil, // removedTags []string
···
255
257
xrpcc,
256
258
nil, // addedLabels []string
257
259
nil, // addedTags []string
260
+
"", // ageAssuranceState
258
261
nil, // collections []string
259
262
"", // comment string
260
263
"", // createdAfter string
···
264
267
false, // hasComment bool
265
268
false, // includeAllUserRecords bool
266
269
5, // limit int64
270
+
nil, // modTool
267
271
nil, // policies []string
268
272
nil, // removedLabels []string
269
273
nil, // removedTags []string
+3
-1
carstore/repo_test.go
+3
-1
carstore/repo_test.go
···
16
16
appbsky "github.com/bluesky-social/indigo/api/bsky"
17
17
"github.com/bluesky-social/indigo/repo"
18
18
"github.com/bluesky-social/indigo/util"
19
-
sqlbs "github.com/ipfs/go-bs-sqlite3"
19
+
//sqlbs "github.com/ipfs/go-bs-sqlite3"
20
20
"github.com/ipfs/go-cid"
21
21
flatfs "github.com/ipfs/go-ds-flatfs"
22
22
blockstore "github.com/ipfs/go-ipfs-blockstore"
···
452
452
}
453
453
}
454
454
455
+
/* NOTE(bnewbold): this depends on github.com/ipfs/go-bs-sqlite3, which rewrote git history (?) breaking the dependency tree. We can roll forward, but that will require broad dependency updates. So for now just removing this benchmark/perf test.
455
456
func BenchmarkRepoWritesSqlite(b *testing.B) {
456
457
ctx := context.TODO()
457
458
···
489
490
head = nroot
490
491
}
491
492
}
493
+
*/
492
494
493
495
func TestDuplicateBlockAcrossShards(ot *testing.T) {
494
496
ctx := context.TODO()
+2
-2
cmd/beemo/Dockerfile
+2
-2
cmd/beemo/Dockerfile
···
3
3
# podman build -f ./cmd/beemo/Dockerfile -t beemo .
4
4
5
5
### Compile stage
6
-
FROM golang:1.23-alpine3.20 AS build-env
6
+
FROM golang:1.24-alpine3.22 AS build-env
7
7
RUN apk add --no-cache build-base make git
8
8
9
9
ADD . /dockerbuild
···
15
15
go build -tags timetzdata -o /beemo ./cmd/beemo
16
16
17
17
### Run stage
18
-
FROM alpine:3.20
18
+
FROM alpine:3.22
19
19
20
20
RUN apk add --no-cache --update dumb-init ca-certificates runit
21
21
ENTRYPOINT ["dumb-init", "--"]
+2
cmd/beemo/notify_reports.go
+2
cmd/beemo/notify_reports.go
···
74
74
xrpcc,
75
75
nil, // addedLabels []string
76
76
nil, // addedTags []string
77
+
"", // ageAssuranceState
77
78
nil, // collections []string
78
79
"", // comment string
79
80
"", // createdAfter string
···
83
84
false, // hasComment bool
84
85
true, // includeAllUserRecords bool
85
86
limit, // limit int64
87
+
nil, // modTool
86
88
nil, // policies []string
87
89
nil, // removedLabels []string
88
90
nil, // removedTags []string
+2
-2
cmd/bigsky/Dockerfile
+2
-2
cmd/bigsky/Dockerfile
···
3
3
# podman build -f ./cmd/bigsky/Dockerfile -t bigsky .
4
4
5
5
### Compile stage
6
-
FROM golang:1.23-alpine3.20 AS build-env
6
+
FROM golang:1.24-alpine3.22 AS build-env
7
7
RUN apk add --no-cache build-base make git
8
8
9
9
ADD . /dockerbuild
···
28
28
RUN yarn build
29
29
30
30
### Run stage
31
-
FROM alpine:3.20
31
+
FROM alpine:3.22
32
32
33
33
RUN apk add --no-cache --update dumb-init ca-certificates runit
34
34
ENTRYPOINT ["dumb-init", "--"]
+2
-2
cmd/bluepages/Dockerfile
+2
-2
cmd/bluepages/Dockerfile
···
3
3
# podman build -f ./cmd/bluepages/Dockerfile -t bluepages .
4
4
5
5
### Compile stage
6
-
FROM golang:1.23-alpine3.20 AS build-env
6
+
FROM golang:1.24-alpine3.22 AS build-env
7
7
RUN apk add --no-cache build-base make git
8
8
9
9
ADD . /dockerbuild
···
15
15
go build -tags timetzdata -o /bluepages ./cmd/bluepages
16
16
17
17
### Run stage
18
-
FROM alpine:3.20
18
+
FROM alpine:3.22
19
19
20
20
RUN apk add --no-cache --update dumb-init ca-certificates runit
21
21
+3
cmd/bluepages/handlers.go
+3
cmd/bluepages/handlers.go
···
74
74
func (srv *Server) resolveIdentityFromHandle(c echo.Context, handle syntax.Handle) error {
75
75
ctx := c.Request().Context()
76
76
77
+
handle = handle.Normalize()
78
+
77
79
did, err := srv.dir.ResolveHandle(ctx, handle)
78
80
if err != nil && errors.Is(err, identity.ErrHandleNotFound) {
79
81
return c.JSON(404, GenericError{
···
110
112
}
111
113
112
114
ident := identity.ParseIdentity(&doc)
115
+
// NOTE: 'handle' was resolved above, and 'DeclaredHandle()' returns a normalized handle
113
116
declHandle, err := ident.DeclaredHandle()
114
117
if err != nil || declHandle != handle {
115
118
return c.JSON(400, GenericError{
+1
-1
cmd/collectiondir/Dockerfile
+1
-1
cmd/collectiondir/Dockerfile
+84
-1
cmd/goat/account.go
+84
-1
cmd/goat/account.go
···
8
8
"time"
9
9
10
10
comatproto "github.com/bluesky-social/indigo/api/atproto"
11
+
"github.com/bluesky-social/indigo/atproto/auth"
12
+
"github.com/bluesky-social/indigo/atproto/crypto"
11
13
"github.com/bluesky-social/indigo/atproto/syntax"
12
14
"github.com/bluesky-social/indigo/xrpc"
13
15
···
89
91
},
90
92
&cli.Command{
91
93
Name: "service-auth",
92
-
Usage: "create service auth token",
94
+
Usage: "ask the PDS to create a service auth token",
93
95
Flags: []cli.Flag{
94
96
&cli.StringFlag{
95
97
Name: "endpoint",
···
109
111
},
110
112
},
111
113
Action: runAccountServiceAuth,
114
+
},
115
+
&cli.Command{
116
+
Name: "service-auth-offline",
117
+
Usage: "create service auth token via locally-held signing key",
118
+
Flags: []cli.Flag{
119
+
&cli.StringFlag{
120
+
Name: "atproto-signing-key",
121
+
Required: true,
122
+
Usage: "private key used to sign the token (multibase syntax)",
123
+
EnvVars: []string{"ATPROTO_SIGNING_KEY"},
124
+
},
125
+
&cli.StringFlag{
126
+
Name: "iss",
127
+
Required: true,
128
+
Usage: "the DID of the account issuing the token",
129
+
},
130
+
&cli.StringFlag{
131
+
Name: "endpoint",
132
+
Aliases: []string{"lxm"},
133
+
Usage: "restrict token to API endpoint (NSID, optional)",
134
+
},
135
+
&cli.StringFlag{
136
+
Name: "audience",
137
+
Aliases: []string{"aud"},
138
+
Required: true,
139
+
Usage: "DID of service that will receive and validate token",
140
+
},
141
+
&cli.IntFlag{
142
+
Name: "duration-sec",
143
+
Value: 60,
144
+
Usage: "validity time window of token (seconds)",
145
+
},
146
+
},
147
+
Action: runAccountServiceAuthOffline,
112
148
},
113
149
&cli.Command{
114
150
Name: "create",
···
365
401
}
366
402
367
403
fmt.Println(resp.Token)
404
+
405
+
return nil
406
+
}
407
+
408
+
func runAccountServiceAuthOffline(cctx *cli.Context) error {
409
+
privStr := cctx.String("atproto-signing-key")
410
+
if privStr == "" {
411
+
return fmt.Errorf("private key must be provided")
412
+
}
413
+
privkey, err := crypto.ParsePrivateMultibase(privStr)
414
+
if err != nil {
415
+
return fmt.Errorf("failed parsing private key: %w", err)
416
+
}
417
+
418
+
issString := cctx.String("iss")
419
+
// TODO: support fragment identifiers
420
+
iss, err := syntax.ParseDID(issString)
421
+
if err != nil {
422
+
return fmt.Errorf("iss argument must be a valid DID: %w", err)
423
+
}
424
+
425
+
lxmString := cctx.String("endpoint")
426
+
var lxm *syntax.NSID = nil
427
+
if lxmString != "" {
428
+
lxmTmp, err := syntax.ParseNSID(lxmString)
429
+
if err != nil {
430
+
return fmt.Errorf("lxm argument must be a valid NSID: %w", err)
431
+
}
432
+
lxm = &lxmTmp
433
+
}
434
+
435
+
aud := cctx.String("audience")
436
+
// TODO: can aud DID have a fragment?
437
+
_, err = syntax.ParseDID(aud)
438
+
if err != nil {
439
+
return fmt.Errorf("aud argument must be a valid DID: %w", err)
440
+
}
441
+
442
+
durSec := cctx.Int("duration-sec")
443
+
duration := time.Duration(durSec * int(time.Second))
444
+
445
+
token, err := auth.SignServiceAuth(iss, aud, duration, lxm, privkey)
446
+
if err != nil {
447
+
return fmt.Errorf("failed signing token: %w", err)
448
+
}
449
+
450
+
fmt.Println(token)
368
451
369
452
return nil
370
453
}
+441
cmd/goat/plc.go
+441
cmd/goat/plc.go
···
6
6
"fmt"
7
7
"io"
8
8
"net/http"
9
+
"net/url"
9
10
"strings"
10
11
"time"
11
12
13
+
"github.com/bluesky-social/indigo/atproto/crypto"
12
14
"github.com/bluesky-social/indigo/atproto/identity"
13
15
"github.com/bluesky-social/indigo/atproto/syntax"
14
16
"github.com/bluesky-social/indigo/util"
17
+
18
+
"github.com/did-method-plc/go-didplc"
15
19
16
20
"github.com/urfave/cli/v2"
17
21
)
···
70
74
},
71
75
},
72
76
Action: runPLCDump,
77
+
},
78
+
&cli.Command{
79
+
Name: "genesis",
80
+
Usage: "produce an unsigned genesis operation",
81
+
Flags: []cli.Flag{
82
+
&cli.StringFlag{
83
+
Name: "handle",
84
+
Usage: "atproto handle",
85
+
},
86
+
&cli.StringSliceFlag{
87
+
Name: "rotation-key",
88
+
Usage: "rotation public key, in did:key format",
89
+
},
90
+
&cli.StringFlag{
91
+
Name: "atproto-key",
92
+
Usage: "atproto repo signing public key, in did:key format",
93
+
},
94
+
&cli.StringFlag{
95
+
Name: "pds",
96
+
Usage: "atproto PDS service URL",
97
+
},
98
+
},
99
+
Action: runPLCGenesis,
100
+
},
101
+
&cli.Command{
102
+
Name: "calc-did",
103
+
Usage: "calculate the DID corresponding to a signed PLC operation",
104
+
ArgsUsage: `<signed_genesis.json>`,
105
+
Flags: []cli.Flag{},
106
+
Action: runPLCCalcDID,
107
+
},
108
+
&cli.Command{
109
+
Name: "sign",
110
+
Usage: "sign an operation, ready to be submitted",
111
+
ArgsUsage: `<operation.json>`,
112
+
Flags: []cli.Flag{
113
+
&cli.StringFlag{
114
+
Name: "plc-signing-key",
115
+
Usage: "private key used to sign operation (multibase syntax)",
116
+
EnvVars: []string{"PLC_SIGNING_KEY"},
117
+
},
118
+
},
119
+
Action: runPLCSign,
120
+
},
121
+
&cli.Command{
122
+
Name: "submit",
123
+
Usage: "submit a signed operation to the PLC directory",
124
+
ArgsUsage: `<signed_operation.json>`,
125
+
Flags: []cli.Flag{
126
+
&cli.BoolFlag{
127
+
Name: "genesis",
128
+
Usage: "the operation is a genesis operation",
129
+
},
130
+
&cli.StringFlag{
131
+
Name: "did",
132
+
Usage: "the DID of the identity to update",
133
+
},
134
+
},
135
+
Action: runPLCSubmit,
136
+
},
137
+
&cli.Command{
138
+
Name: "update",
139
+
Usage: "apply updates to a previous operation to produce a new one (but don't sign or submit it, yet)",
140
+
ArgsUsage: `<DID>`,
141
+
Flags: []cli.Flag{
142
+
&cli.StringFlag{
143
+
Name: "prev",
144
+
Usage: "the CID of the operation to use as a base (uses most recent op if not specified)",
145
+
},
146
+
&cli.StringFlag{
147
+
Name: "handle",
148
+
Usage: "atproto handle",
149
+
},
150
+
&cli.StringSliceFlag{
151
+
Name: "add-rotation-key",
152
+
Usage: "rotation public key, in did:key format (added to front of rotationKey list)",
153
+
},
154
+
&cli.StringSliceFlag{
155
+
Name: "remove-rotation-key",
156
+
Usage: "rotation public key, in did:key format",
157
+
},
158
+
&cli.StringFlag{
159
+
Name: "atproto-key",
160
+
Usage: "atproto repo signing public key, in did:key format",
161
+
},
162
+
&cli.StringFlag{
163
+
Name: "pds",
164
+
Usage: "atproto PDS service URL",
165
+
},
166
+
},
167
+
Action: runPLCUpdate,
73
168
},
74
169
},
75
170
}
···
320
415
}
321
416
return &d, nil
322
417
}
418
+
419
+
func runPLCGenesis(cctx *cli.Context) error {
420
+
// TODO: helper function in didplc to make an empty op like this?
421
+
services := make(map[string]didplc.OpService)
422
+
verifMethods := make(map[string]string)
423
+
op := didplc.RegularOp{
424
+
Type: "plc_operation",
425
+
RotationKeys: []string{},
426
+
VerificationMethods: verifMethods,
427
+
AlsoKnownAs: []string{},
428
+
Services: services,
429
+
}
430
+
431
+
for _, rotationKey := range cctx.StringSlice("rotation-key") {
432
+
if _, err := crypto.ParsePublicDIDKey(rotationKey); err != nil {
433
+
return err
434
+
}
435
+
op.RotationKeys = append(op.RotationKeys, rotationKey)
436
+
}
437
+
438
+
handle := cctx.String("handle")
439
+
if handle != "" {
440
+
parsedHandle, err := syntax.ParseHandle(strings.TrimPrefix(handle, "at://"))
441
+
if err != nil {
442
+
return err
443
+
}
444
+
parsedHandle = parsedHandle.Normalize()
445
+
op.AlsoKnownAs = append(op.AlsoKnownAs, "at://"+string(parsedHandle))
446
+
}
447
+
448
+
atprotoKey := cctx.String("atproto-key")
449
+
if atprotoKey != "" {
450
+
if _, err := crypto.ParsePublicDIDKey(atprotoKey); err != nil {
451
+
return err
452
+
}
453
+
op.VerificationMethods["atproto"] = atprotoKey
454
+
}
455
+
456
+
pds := cctx.String("pds")
457
+
if pds != "" {
458
+
parsedUrl, err := url.Parse(pds)
459
+
if err != nil {
460
+
return err
461
+
}
462
+
if !parsedUrl.IsAbs() {
463
+
return fmt.Errorf("invalid PDS URL: must be absolute")
464
+
}
465
+
op.Services["atproto_pds"] = didplc.OpService{
466
+
Type: "AtprotoPersonalDataServer",
467
+
Endpoint: pds,
468
+
}
469
+
}
470
+
471
+
res, err := json.MarshalIndent(op, "", " ")
472
+
if err != nil {
473
+
return err
474
+
}
475
+
fmt.Println(string(res))
476
+
477
+
return nil
478
+
}
479
+
480
+
func runPLCCalcDID(cctx *cli.Context) error {
481
+
s := cctx.Args().First()
482
+
if s == "" {
483
+
return fmt.Errorf("need to provide genesis json path as input")
484
+
}
485
+
486
+
inputReader, err := getFileOrStdin(s)
487
+
if err != nil {
488
+
return err
489
+
}
490
+
491
+
inBytes, err := io.ReadAll(inputReader)
492
+
if err != nil {
493
+
return err
494
+
}
495
+
496
+
var enum didplc.OpEnum
497
+
if err := json.Unmarshal(inBytes, &enum); err != nil {
498
+
return err
499
+
}
500
+
op := enum.AsOperation()
501
+
502
+
did, err := op.DID() // errors if op is not a signed genesis op
503
+
if err != nil {
504
+
return err
505
+
}
506
+
507
+
fmt.Println(did)
508
+
509
+
return nil
510
+
}
511
+
512
+
func runPLCSign(cctx *cli.Context) error {
513
+
s := cctx.Args().First()
514
+
if s == "" {
515
+
return fmt.Errorf("need to provide PLC operation json path as input")
516
+
}
517
+
518
+
privStr := cctx.String("plc-signing-key")
519
+
if privStr == "" {
520
+
return fmt.Errorf("private key must be provided")
521
+
}
522
+
523
+
inputReader, err := getFileOrStdin(s)
524
+
if err != nil {
525
+
return err
526
+
}
527
+
528
+
inBytes, err := io.ReadAll(inputReader)
529
+
if err != nil {
530
+
return err
531
+
}
532
+
533
+
var enum didplc.OpEnum
534
+
if err := json.Unmarshal(inBytes, &enum); err != nil {
535
+
return err
536
+
}
537
+
op := enum.AsOperation()
538
+
539
+
// Note: we do not require that the op is currently unsigned.
540
+
// If it's already signed, we'll re-sign it.
541
+
542
+
privkey, err := crypto.ParsePrivateMultibase(privStr)
543
+
if err != nil {
544
+
return err
545
+
}
546
+
547
+
if err := op.Sign(privkey); err != nil {
548
+
return err
549
+
}
550
+
551
+
res, err := json.MarshalIndent(op, "", " ")
552
+
if err != nil {
553
+
return err
554
+
}
555
+
fmt.Println(string(res))
556
+
557
+
return nil
558
+
}
559
+
560
+
func runPLCSubmit(cctx *cli.Context) error {
561
+
ctx := context.Background()
562
+
expectGenesis := cctx.Bool("genesis")
563
+
didString := cctx.String("did")
564
+
565
+
if !expectGenesis && didString == "" {
566
+
return fmt.Errorf("exactly one of either --genesis or --did must be specified")
567
+
}
568
+
569
+
if expectGenesis && didString != "" {
570
+
return fmt.Errorf("exactly one of either --genesis or --did must be specified")
571
+
}
572
+
573
+
s := cctx.Args().First()
574
+
if s == "" {
575
+
return fmt.Errorf("need to provide PLC operation json path as input")
576
+
}
577
+
578
+
inputReader, err := getFileOrStdin(s)
579
+
if err != nil {
580
+
return err
581
+
}
582
+
583
+
inBytes, err := io.ReadAll(inputReader)
584
+
if err != nil {
585
+
return err
586
+
}
587
+
588
+
var enum didplc.OpEnum
589
+
if err := json.Unmarshal(inBytes, &enum); err != nil {
590
+
return err
591
+
}
592
+
op := enum.AsOperation()
593
+
594
+
if op.IsGenesis() != expectGenesis {
595
+
if expectGenesis {
596
+
return fmt.Errorf("expected genesis operation, but a non-genesis operation was provided")
597
+
} else {
598
+
return fmt.Errorf("expected non-genesis operation, but a genesis operation was provided")
599
+
}
600
+
}
601
+
602
+
if op.IsGenesis() {
603
+
didString, err = op.DID()
604
+
if err != nil {
605
+
return err
606
+
}
607
+
}
608
+
609
+
if !op.IsSigned() {
610
+
return fmt.Errorf("operation must be signed")
611
+
}
612
+
613
+
c := didplc.Client{
614
+
DirectoryURL: cctx.String("plc-host"),
615
+
UserAgent: *userAgent(),
616
+
}
617
+
618
+
if err = c.Submit(ctx, didString, op); err != nil {
619
+
return err
620
+
}
621
+
622
+
fmt.Println("success")
623
+
624
+
return nil
625
+
}
626
+
627
+
// fetch logs from /log/audit, select according to base_cid ("" means use latest), and
628
+
// prepare it for updates:
629
+
// - convert from legacy op format if needed (and reject tombstone ops)
630
+
// - strip signature
631
+
// - set `prev` to appropriate value
632
+
func fetchOpForUpdate(ctx context.Context, c didplc.Client, did string, base_cid string) (*didplc.RegularOp, error) {
633
+
auditlog, err := c.AuditLog(ctx, did)
634
+
if err != nil {
635
+
return nil, err
636
+
}
637
+
638
+
if err = didplc.VerifyOpLog(auditlog); err != nil {
639
+
return nil, err
640
+
}
641
+
642
+
var baseLogEntry *didplc.LogEntry
643
+
if base_cid == "" {
644
+
// use most recent entry
645
+
baseLogEntry = &auditlog[len(auditlog)-1]
646
+
} else {
647
+
// scan for the specified entry
648
+
for _, entry := range auditlog {
649
+
if entry.CID == base_cid {
650
+
baseLogEntry = &entry
651
+
break
652
+
}
653
+
}
654
+
if baseLogEntry == nil {
655
+
return nil, fmt.Errorf("no operation found matching CID %s", base_cid)
656
+
}
657
+
}
658
+
var op didplc.RegularOp
659
+
switch baseOp := baseLogEntry.Operation.AsOperation().(type) {
660
+
case *didplc.RegularOp:
661
+
op = *baseOp
662
+
op.Sig = nil
663
+
case *didplc.LegacyOp:
664
+
op = baseOp.RegularOp() // also strips sig
665
+
case *didplc.TombstoneOp:
666
+
return nil, fmt.Errorf("cannot update from a tombstone op")
667
+
}
668
+
op.Prev = &baseLogEntry.CID
669
+
return &op, nil
670
+
}
671
+
672
+
func runPLCUpdate(cctx *cli.Context) error {
673
+
ctx := context.Background()
674
+
prevCID := cctx.String("prev")
675
+
676
+
didString := cctx.Args().First()
677
+
if didString == "" {
678
+
return fmt.Errorf("please specify a DID to update")
679
+
}
680
+
681
+
c := didplc.Client{
682
+
DirectoryURL: cctx.String("plc-host"),
683
+
UserAgent: *userAgent(),
684
+
}
685
+
op, err := fetchOpForUpdate(ctx, c, didString, prevCID)
686
+
if err != nil {
687
+
return err
688
+
}
689
+
690
+
for _, rotationKey := range cctx.StringSlice("remove-rotation-key") {
691
+
if _, err := crypto.ParsePublicDIDKey(rotationKey); err != nil {
692
+
return err
693
+
}
694
+
removeSuccess := false
695
+
for idx, existingRotationKey := range op.RotationKeys {
696
+
if existingRotationKey == rotationKey {
697
+
op.RotationKeys = append(op.RotationKeys[:idx], op.RotationKeys[idx+1:]...)
698
+
removeSuccess = true
699
+
}
700
+
}
701
+
if !removeSuccess {
702
+
return fmt.Errorf("failed remove rotation key %s, not found in array", rotationKey)
703
+
}
704
+
}
705
+
706
+
for _, rotationKey := range cctx.StringSlice("add-rotation-key") {
707
+
if _, err := crypto.ParsePublicDIDKey(rotationKey); err != nil {
708
+
return err
709
+
}
710
+
// prepend (Note: if adding multiple rotation keys at once, they'll end up in reverse order)
711
+
op.RotationKeys = append([]string{rotationKey}, op.RotationKeys...)
712
+
}
713
+
714
+
handle := cctx.String("handle")
715
+
if handle != "" {
716
+
parsedHandle, err := syntax.ParseHandle(strings.TrimPrefix(handle, "at://"))
717
+
if err != nil {
718
+
return err
719
+
}
720
+
721
+
// strip any existing at:// akas
722
+
// (someone might have some non-atproto akas, we will leave them untouched,
723
+
// they can manually manage those or use some other tool if needed)
724
+
var akas []string
725
+
for _, aka := range op.AlsoKnownAs {
726
+
if !strings.HasPrefix(aka, "at://") {
727
+
akas = append(akas, aka)
728
+
}
729
+
}
730
+
op.AlsoKnownAs = append(akas, "at://"+string(parsedHandle))
731
+
}
732
+
733
+
atprotoKey := cctx.String("atproto-key")
734
+
if atprotoKey != "" {
735
+
if _, err := crypto.ParsePublicDIDKey(atprotoKey); err != nil {
736
+
return err
737
+
}
738
+
op.VerificationMethods["atproto"] = atprotoKey
739
+
}
740
+
741
+
pds := cctx.String("pds")
742
+
if pds != "" {
743
+
parsedUrl, err := url.Parse(pds)
744
+
if err != nil {
745
+
return err
746
+
}
747
+
if !parsedUrl.IsAbs() {
748
+
return fmt.Errorf("invalid PDS URL: must be absolute")
749
+
}
750
+
op.Services["atproto_pds"] = didplc.OpService{
751
+
Type: "AtprotoPersonalDataServer",
752
+
Endpoint: pds,
753
+
}
754
+
}
755
+
756
+
res, err := json.MarshalIndent(op, "", " ")
757
+
if err != nil {
758
+
return err
759
+
}
760
+
fmt.Println(string(res))
761
+
762
+
return nil
763
+
}
+1
-1
cmd/goat/util.go
+1
-1
cmd/goat/util.go
+4
cmd/gosky/admin.go
+4
cmd/gosky/admin.go
···
394
394
xrpcc,
395
395
nil, // addedLabels []string
396
396
nil, // addedTags []string
397
+
"", // ageAssuranceState
397
398
nil, // collections []string
398
399
"", // comment string
399
400
"", // createdAfter string
···
403
404
false, // hasComment bool
404
405
false, // includeAllUserRecords bool
405
406
100, // limit int64
407
+
nil, // modTool
406
408
nil, // policies []string
407
409
nil, // removedLabels []string
408
410
nil, // removedTags []string
···
709
711
xrpcc,
710
712
nil, // addedLabels []string
711
713
nil, // addedTags []string
714
+
"", // ageAssuranceState
712
715
nil, // collections []string
713
716
"", // comment string
714
717
"", // createdAfter string
···
718
721
false, // hasComment bool
719
722
false, // includeAllUserRecords bool
720
723
100, // limit int64
724
+
nil, // modTool
721
725
nil, // policies []string
722
726
nil, // removedLabels []string
723
727
nil, // removedTags []string
+2
-2
cmd/hepa/Dockerfile
+2
-2
cmd/hepa/Dockerfile
···
3
3
# podman build -f ./cmd/hepa/Dockerfile -t hepa .
4
4
5
5
### Compile stage
6
-
FROM golang:1.23-alpine3.20 AS build-env
6
+
FROM golang:1.24-alpine3.22 AS build-env
7
7
RUN apk add --no-cache build-base make git
8
8
9
9
ADD . /dockerbuild
···
15
15
go build -tags timetzdata -o /hepa ./cmd/hepa
16
16
17
17
### Run stage
18
-
FROM alpine:3.20
18
+
FROM alpine:3.22
19
19
20
20
RUN apk add --no-cache --update dumb-init ca-certificates runit
21
21
ENTRYPOINT ["dumb-init", "--"]
+2
-2
cmd/palomar/Dockerfile
+2
-2
cmd/palomar/Dockerfile
···
3
3
# podman build -f ./cmd/palomar/Dockerfile -t palomar .
4
4
5
5
### Compile stage
6
-
FROM golang:1.23-alpine3.20 AS build-env
6
+
FROM golang:1.24-alpine3.22 AS build-env
7
7
RUN apk add --no-cache build-base make git
8
8
9
9
ADD . /dockerbuild
···
15
15
go build -tags timetzdata -o /palomar ./cmd/palomar
16
16
17
17
### Run stage
18
-
FROM alpine:3.20
18
+
FROM alpine:3.22
19
19
20
20
RUN apk add --no-cache --update dumb-init ca-certificates runit
21
21
ENTRYPOINT ["dumb-init", "--"]
+1
-1
cmd/rainbow/Dockerfile
+1
-1
cmd/rainbow/Dockerfile
+2
-2
cmd/relay/Dockerfile
+2
-2
cmd/relay/Dockerfile
···
3
3
# podman build -f ./cmd/relay/Dockerfile -t relay .
4
4
5
5
### Compile stage
6
-
FROM golang:1.23-alpine3.20 AS build-env
6
+
FROM golang:1.24-alpine3.22 AS build-env
7
7
RUN apk add --no-cache build-base make git
8
8
9
9
ADD . /dockerbuild
···
26
26
RUN yarn build
27
27
28
28
### Run stage
29
-
FROM alpine:3.20
29
+
FROM alpine:3.22
30
30
31
31
RUN apk add --no-cache --update dumb-init ca-certificates runit
32
32
ENTRYPOINT ["dumb-init", "--"]
+3
-3
cmd/sonar/Dockerfile
+3
-3
cmd/sonar/Dockerfile
···
1
1
# Stage 1: Build the Go binary
2
-
FROM golang:1.23-alpine3.20 AS builder
2
+
FROM golang:1.24-alpine3.22 AS build-env
3
3
4
4
# Install SSL ca certificates.
5
5
RUN apk update && apk add --no-cache ca-certificates && update-ca-certificates
···
21
21
FROM scratch
22
22
23
23
# Import the SSL certificates from the first stage.
24
-
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
24
+
COPY --from=build-env /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
25
25
26
26
# Copy the binary from the first stage.
27
-
COPY --from=builder /sonar /sonar
27
+
COPY --from=build-env /sonar /sonar
28
28
29
29
# Set the startup command to run the binary
30
30
CMD ["/sonar"]
+2
-2
cmd/supercollider/Dockerfile
+2
-2
cmd/supercollider/Dockerfile
···
1
1
# Stage 1: Build the Go binary
2
-
FROM golang:1.23-alpine3.20 AS builder
2
+
FROM golang:1.24-alpine3.22 AS build-env
3
3
4
4
# Create a directory for the application
5
5
WORKDIR /app
···
23
23
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
24
24
25
25
# Copy the binary from the first stage.
26
-
COPY --from=builder /supercollider /supercollider
26
+
COPY --from=build-env /supercollider /supercollider
27
27
28
28
# Set the startup command to run the binary
29
29
CMD ["/supercollider"]
-26
docs/auth.md
-26
docs/auth.md
···
1
-
# Auth
2
-
3
-
The auth system uses two tokens, an access token and a refresh token.
4
-
5
-
The access token is a jwt with the following values:
6
-
```
7
-
scope: "com.atproto.access"
8
-
sub: <the users DID>
9
-
iat: the current time, in unix epoch seconds
10
-
exp: the expiry date, usually around an hour, but at least 15 minutes
11
-
```
12
-
13
-
The refresh token is a jwt with the following values:
14
-
```
15
-
scope: "com.atproto.refresh"
16
-
sub: <the users DID>
17
-
iat: the current time, in unix epoch seconds
18
-
exp: the expiry date, usually around a week, must be significantly longer than the access token
19
-
jti: a unique identifier for this token
20
-
```
21
-
22
-
The access token is what is used for all requests, however since it expires
23
-
quickly, it must be refreshed periodically using the refresh token.
24
-
When the refresh token is used, it must be marked as deleted, and the new token then replaces it.
25
-
Note: The old access token is not necessarily disabled at that point of refreshing.
26
-
-37
docs/feed-proposal.md
-37
docs/feed-proposal.md
···
1
-
# Feed Structuring Proposal
2
-
3
-
Some thoughts on a new format for feeds.
4
-
5
-
## Motivation
6
-
The interface for requesting and getting back feeds is something that I feel is really at the core of what bluesky offers. The user should be able to choose what feeds they subscribe to, feeds should be first class objects, they should be able to be efficiently generated and consumed, and they should be able to trustlessly come from anywhere.
7
-
There are a lot of changes we *could* make to the current structure, but I don't want to stray too far from where we are at right now.
8
-
9
-
10
-
```go
11
-
type Feed struct {
12
-
Items []FeedItem
13
-
Values map[Cid]Record
14
-
ItemInfos map[Uri]ItemInfo
15
-
ActorInfos map[Did]ActorInfo
16
-
}
17
-
18
-
type FeedItem struct {
19
-
Uri string
20
-
Replies []Uri
21
-
ReplyTo Uri
22
-
RepostedBy Did
23
-
}
24
-
25
-
type ItemInfo struct {
26
-
Cid Cid
27
-
Upvotes int
28
-
Reposts int
29
-
Replies int
30
-
Author Did
31
-
}
32
-
```
33
-
34
-
The main idea here is not repeating ourselves, while still providing all the information the client might need.
35
-
With this structure too, the user could easily request *less* data, asking to
36
-
skip the inclusion of records older than X, or saying they are okay with stale
37
-
information in certain places for the sake of efficiency.
+3
-3
go.mod
+3
-3
go.mod
···
1
1
module github.com/bluesky-social/indigo
2
2
3
-
go 1.23
3
+
go 1.24
4
4
5
-
toolchain go1.23.8
5
+
toolchain go1.24.1
6
6
7
7
require (
8
8
github.com/PuerkitoBio/purell v1.2.1
···
13
13
github.com/brianvoe/gofakeit/v6 v6.25.0
14
14
github.com/carlmjohnson/versioninfo v0.22.5
15
15
github.com/cockroachdb/pebble v1.1.2
16
+
github.com/did-method-plc/go-didplc v0.0.0-20250716171643-635da8b4e038
16
17
github.com/dustinkirkland/golang-petname v0.0.0-20231002161417-6a283f1aaaf2
17
18
github.com/flosch/pongo2/v6 v6.0.0
18
19
github.com/go-redis/cache/v9 v9.0.0
···
25
26
github.com/hashicorp/golang-lru/v2 v2.0.7
26
27
github.com/icrowley/fake v0.0.0-20221112152111-d7b7e2276db2
27
28
github.com/ipfs/go-block-format v0.2.0
28
-
github.com/ipfs/go-bs-sqlite3 v0.0.0-20221122195556-bfcee1be620d
29
29
github.com/ipfs/go-cid v0.4.1
30
30
github.com/ipfs/go-datastore v0.6.0
31
31
github.com/ipfs/go-ds-flatfs v0.5.1
+2
-2
go.sum
+2
-2
go.sum
···
81
81
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
82
82
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
83
83
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
84
+
github.com/did-method-plc/go-didplc v0.0.0-20250716171643-635da8b4e038 h1:AGh+Vn9fXhf9eo8erG1CK4+LACduPo64P1OICQLDv88=
85
+
github.com/did-method-plc/go-didplc v0.0.0-20250716171643-635da8b4e038/go.mod h1:ddIXqTTSXWtj5kMsHAPj8SvbIx2GZdAkBFgFa6e6+CM=
84
86
github.com/dustinkirkland/golang-petname v0.0.0-20231002161417-6a283f1aaaf2 h1:S6Dco8FtAhEI/qkg/00H6RdEGC+MCy5GPiQ+xweNRFE=
85
87
github.com/dustinkirkland/golang-petname v0.0.0-20231002161417-6a283f1aaaf2/go.mod h1:8AuBTZBRSFqEYBPYULd+NN474/zZBLP+6WeT5S9xlAc=
86
88
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
···
184
186
github.com/ipfs/go-block-format v0.2.0/go.mod h1:+jpL11nFx5A/SPpsoBn6Bzkra/zaArfSmsknbPMYgzM=
185
187
github.com/ipfs/go-blockservice v0.5.2 h1:in9Bc+QcXwd1apOVM7Un9t8tixPKdaHQFdLSUM1Xgk8=
186
188
github.com/ipfs/go-blockservice v0.5.2/go.mod h1:VpMblFEqG67A/H2sHKAemeH9vlURVavlysbdUI632yk=
187
-
github.com/ipfs/go-bs-sqlite3 v0.0.0-20221122195556-bfcee1be620d h1:9V+GGXCuOfDiFpdAHz58q9mKLg447xp0cQKvqQrAwYE=
188
-
github.com/ipfs/go-bs-sqlite3 v0.0.0-20221122195556-bfcee1be620d/go.mod h1:pMbnFyNAGjryYCLCe59YDLRv/ujdN+zGJBT1umlvYRM=
189
189
github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=
190
190
github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk=
191
191
github.com/ipfs/go-datastore v0.5.0/go.mod h1:9zhEApYMTl17C8YDp7JmU7sQZi2/wqiYh73hakZ90Bk=
+7
-2
lex/type_schema.go
+7
-2
lex/type_schema.go
···
552
552
return "string", nil
553
553
case "unknown":
554
554
// NOTE: sometimes a record, for which we want LexiconTypeDecoder, sometimes any object
555
-
if k == "didDoc" || k == "plcOp" {
555
+
if k == "didDoc" || k == "plcOp" || k == "meta" {
556
556
return "interface{}", nil
557
557
} else {
558
558
return "*util.LexiconTypeDecoder", nil
559
559
}
560
560
case "union":
561
-
return "*" + name + "_" + strings.Title(k), nil
561
+
if len(v.Refs) > 0 {
562
+
return "*" + name + "_" + strings.Title(k), nil
563
+
} else {
564
+
// an empty union is effectively an 'unknown', but with mandatory type indicator
565
+
return "*util.LexiconTypeDecoder", nil
566
+
}
562
567
case "blob":
563
568
return "*util.LexBlob", nil
564
569
case "array":
+7
-6
mst/mst_test.go
+7
-6
mst/mst_test.go
···
13
13
"testing"
14
14
15
15
"github.com/bluesky-social/indigo/util"
16
-
cid "github.com/ipfs/go-cid"
16
+
17
+
"github.com/ipfs/go-cid"
17
18
"github.com/ipfs/go-datastore"
18
19
blockstore "github.com/ipfs/go-ipfs-blockstore"
19
20
"github.com/ipld/go-car/v2"
···
32
33
}
33
34
34
35
func TestBasicMst(t *testing.T) {
35
-
rand.Seed(6123123)
36
36
37
37
ctx := context.Background()
38
38
cst := util.CborStore(blockstore.NewBlockstore(datastore.NewMapDatastore()))
39
39
mst := createMST(cst, cid.Undef, []nodeEntry{}, -1)
40
40
41
+
// NOTE: these were previously generated randomly, but the random seed behavior changed
41
42
vals := map[string]cid.Cid{
42
-
"cats/cats": randCid(),
43
-
"dogs/dogs": randCid(),
44
-
"cats/bears": randCid(),
43
+
"cats/cats": mustCid(t, "bafkreicwamkg77pijyudfbdmskelsnuztr6gp62lqfjv3e3urbs3gxnv2m"),
44
+
"dogs/dogs": mustCid(t, "bafkreihwoet2mghoduxannw3uqsq44a3if37i5omnlqmjcfuhcdegzpyn4"),
45
+
"cats/bears": mustCid(t, "bafkreiealwcgkpqxmr75a2vubzdertnbrip65nclv6gbsss4w7ef7fh6oy"),
45
46
}
46
47
47
48
for k, v := range vals {
···
531
532
for i := 0; i < 256; i++ {
532
533
f.Add([]byte{byte(i)})
533
534
}
534
-
rx := regexp.MustCompile("^[a-zA-Z0-9_:.-]+$")
535
+
rx := regexp.MustCompile("^[a-zA-Z0-9_:.~-]+$")
535
536
f.Fuzz(func(t *testing.T, in []byte) {
536
537
s := string(in)
537
538
if a, b := rx.MatchString(s), keyHasAllValidChars(s); a != b {
+2
-2
mst/mst_util.go
+2
-2
mst/mst_util.go
···
197
197
}
198
198
199
199
// keyHasAllValidChars reports whether s matches
200
-
// the regexp /^[a-zA-Z0-9_:.-]+$/ without using regexp,
200
+
// the regexp /^[a-zA-Z0-9_:.~-]+$/ without using regexp,
201
201
// which is slower.
202
202
func keyHasAllValidChars(s string) bool {
203
203
if len(s) == 0 {
···
211
211
continue
212
212
}
213
213
switch b {
214
-
case '_', ':', '.', '-':
214
+
case '_', ':', '.', '~', '-':
215
215
continue
216
216
default:
217
217
return false
+135
pkg/robusthttp/client.go
+135
pkg/robusthttp/client.go
···
1
+
package robusthttp
2
+
3
+
import (
4
+
"context"
5
+
"log/slog"
6
+
"net/http"
7
+
"time"
8
+
9
+
"github.com/hashicorp/go-cleanhttp"
10
+
"github.com/hashicorp/go-retryablehttp"
11
+
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
12
+
)
13
+
14
+
type LeveledSlog struct {
15
+
inner *slog.Logger
16
+
}
17
+
18
+
// re-writes HTTP client ERROR to WARN level (because of retries)
19
+
func (l LeveledSlog) Error(msg string, keysAndValues ...any) {
20
+
l.inner.Warn(msg, keysAndValues...)
21
+
}
22
+
23
+
func (l LeveledSlog) Warn(msg string, keysAndValues ...any) {
24
+
l.inner.Warn(msg, keysAndValues...)
25
+
}
26
+
27
+
func (l LeveledSlog) Info(msg string, keysAndValues ...any) {
28
+
l.inner.Info(msg, keysAndValues...)
29
+
}
30
+
31
+
func (l LeveledSlog) Debug(msg string, keysAndValues ...any) {
32
+
l.inner.Debug(msg, keysAndValues...)
33
+
}
34
+
35
+
type Option func(*retryablehttp.Client)
36
+
37
+
// WithMaxRetries sets the maximum number of retries for the HTTP client.
38
+
func WithMaxRetries(maxRetries int) Option {
39
+
return func(client *retryablehttp.Client) {
40
+
client.RetryMax = maxRetries
41
+
}
42
+
}
43
+
44
+
// WithRetryWaitMin sets the minimum wait time between retries.
45
+
func WithRetryWaitMin(waitMin time.Duration) Option {
46
+
return func(client *retryablehttp.Client) {
47
+
client.RetryWaitMin = waitMin
48
+
}
49
+
}
50
+
51
+
// WithRetryWaitMax sets the maximum wait time between retries.
52
+
func WithRetryWaitMax(waitMax time.Duration) Option {
53
+
return func(client *retryablehttp.Client) {
54
+
client.RetryWaitMax = waitMax
55
+
}
56
+
}
57
+
58
+
// WithLogger sets a custom logger for the HTTP client.
59
+
func WithLogger(logger *slog.Logger) Option {
60
+
return func(client *retryablehttp.Client) {
61
+
client.Logger = retryablehttp.LeveledLogger(LeveledSlog{inner: logger})
62
+
}
63
+
}
64
+
65
+
// WithTransport sets a custom transport for the HTTP client.
66
+
func WithTransport(transport http.RoundTripper) Option {
67
+
return func(client *retryablehttp.Client) {
68
+
client.HTTPClient.Transport = transport
69
+
}
70
+
}
71
+
72
+
// WithRetryPolicy sets a custom retry policy for the HTTP client.
73
+
func WithRetryPolicy(policy retryablehttp.CheckRetry) Option {
74
+
return func(client *retryablehttp.Client) {
75
+
client.CheckRetry = policy
76
+
}
77
+
}
78
+
79
+
// Generates an HTTP client with decent general-purpose defaults around
80
+
// timeouts and retries. The returned client has the stdlib http.Client
81
+
// interface, but has Hashicorp retryablehttp logic internally.
82
+
//
83
+
// This client will retry on connection errors, 5xx status (except 501).
84
+
// It will log intermediate failures with WARN level. This does not start from
85
+
// http.DefaultClient.
86
+
//
87
+
// This should be usable for XRPC clients, and other general inter-service
88
+
// client needs. CLI tools might want shorter timeouts and fewer retries by
89
+
// default.
90
+
func NewClient(options ...Option) *http.Client {
91
+
logger := LeveledSlog{inner: slog.Default().With("subsystem", "RobustHTTPClient")}
92
+
retryClient := retryablehttp.NewClient()
93
+
retryClient.HTTPClient.Transport = otelhttp.NewTransport(cleanhttp.DefaultPooledTransport())
94
+
retryClient.RetryMax = 3
95
+
retryClient.RetryWaitMin = 1 * time.Second
96
+
retryClient.RetryWaitMax = 10 * time.Second
97
+
retryClient.Logger = retryablehttp.LeveledLogger(logger)
98
+
retryClient.CheckRetry = DefaultRetryPolicy
99
+
100
+
for _, option := range options {
101
+
option(retryClient)
102
+
}
103
+
104
+
client := retryClient.StandardClient()
105
+
client.Timeout = 30 * time.Second
106
+
return client
107
+
}
108
+
109
+
// For use in local integration tests. Short timeouts, no retries, etc
110
+
func TestingHTTPClient() *http.Client {
111
+
112
+
client := http.DefaultClient
113
+
client.Timeout = 1 * time.Second
114
+
return client
115
+
}
116
+
117
+
// DefaultRetryPolicy is a custom wrapper around retryablehttp.DefaultRetryPolicy.
118
+
// It treats `429 Too Many Requests` as non-retryable, so the application can decide
119
+
// how to deal with rate-limiting.
120
+
func DefaultRetryPolicy(ctx context.Context, resp *http.Response, err error) (bool, error) {
121
+
if err == nil && resp.StatusCode == http.StatusTooManyRequests {
122
+
return false, nil
123
+
}
124
+
return retryablehttp.DefaultRetryPolicy(ctx, resp, err)
125
+
}
126
+
127
+
func NoInternalServerErrorPolicy(ctx context.Context, resp *http.Response, err error) (bool, error) {
128
+
if err == nil && resp.StatusCode == http.StatusTooManyRequests {
129
+
return false, nil
130
+
}
131
+
if err == nil && resp.StatusCode == http.StatusInternalServerError {
132
+
return false, nil
133
+
}
134
+
return retryablehttp.DefaultRetryPolicy(ctx, resp, err)
135
+
}