+18
-11
api/atproto/admindefs.go
+18
-11
api/atproto/admindefs.go
···
10
10
11
11
// AdminDefs_AccountView is a "accountView" in the com.atproto.admin.defs schema.
12
12
type AdminDefs_AccountView struct {
13
-
DeactivatedAt *string `json:"deactivatedAt,omitempty" cborgen:"deactivatedAt,omitempty"`
14
-
Did string `json:"did" cborgen:"did"`
15
-
Email *string `json:"email,omitempty" cborgen:"email,omitempty"`
16
-
EmailConfirmedAt *string `json:"emailConfirmedAt,omitempty" cborgen:"emailConfirmedAt,omitempty"`
17
-
Handle string `json:"handle" cborgen:"handle"`
18
-
IndexedAt string `json:"indexedAt" cborgen:"indexedAt"`
19
-
InviteNote *string `json:"inviteNote,omitempty" cborgen:"inviteNote,omitempty"`
20
-
InvitedBy *ServerDefs_InviteCode `json:"invitedBy,omitempty" cborgen:"invitedBy,omitempty"`
21
-
Invites []*ServerDefs_InviteCode `json:"invites,omitempty" cborgen:"invites,omitempty"`
22
-
InvitesDisabled *bool `json:"invitesDisabled,omitempty" cborgen:"invitesDisabled,omitempty"`
23
-
RelatedRecords []*util.LexiconTypeDecoder `json:"relatedRecords,omitempty" cborgen:"relatedRecords,omitempty"`
13
+
DeactivatedAt *string `json:"deactivatedAt,omitempty" cborgen:"deactivatedAt,omitempty"`
14
+
Did string `json:"did" cborgen:"did"`
15
+
Email *string `json:"email,omitempty" cborgen:"email,omitempty"`
16
+
EmailConfirmedAt *string `json:"emailConfirmedAt,omitempty" cborgen:"emailConfirmedAt,omitempty"`
17
+
Handle string `json:"handle" cborgen:"handle"`
18
+
IndexedAt string `json:"indexedAt" cborgen:"indexedAt"`
19
+
InviteNote *string `json:"inviteNote,omitempty" cborgen:"inviteNote,omitempty"`
20
+
InvitedBy *ServerDefs_InviteCode `json:"invitedBy,omitempty" cborgen:"invitedBy,omitempty"`
21
+
Invites []*ServerDefs_InviteCode `json:"invites,omitempty" cborgen:"invites,omitempty"`
22
+
InvitesDisabled *bool `json:"invitesDisabled,omitempty" cborgen:"invitesDisabled,omitempty"`
23
+
RelatedRecords []*util.LexiconTypeDecoder `json:"relatedRecords,omitempty" cborgen:"relatedRecords,omitempty"`
24
+
ThreatSignatures []*AdminDefs_ThreatSignature `json:"threatSignatures,omitempty" cborgen:"threatSignatures,omitempty"`
24
25
}
25
26
26
27
// AdminDefs_RepoBlobRef is a "repoBlobRef" in the com.atproto.admin.defs schema.
···
46
47
Applied bool `json:"applied" cborgen:"applied"`
47
48
Ref *string `json:"ref,omitempty" cborgen:"ref,omitempty"`
48
49
}
50
+
51
+
// AdminDefs_ThreatSignature is a "threatSignature" in the com.atproto.admin.defs schema.
52
+
type AdminDefs_ThreatSignature struct {
53
+
Property string `json:"property" cborgen:"property"`
54
+
Value string `json:"value" cborgen:"value"`
55
+
}
+208
-128
api/atproto/cbor_gen.go
+208
-128
api/atproto/cbor_gen.go
···
129
129
return fmt.Errorf("RepoStrongRef: map struct too large (%d)", extra)
130
130
}
131
131
132
-
var name string
133
132
n := extra
134
133
134
+
nameBuf := make([]byte, 5)
135
135
for i := uint64(0); i < n; i++ {
136
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
137
+
if err != nil {
138
+
return err
139
+
}
136
140
137
-
{
138
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
139
-
if err != nil {
141
+
if !ok {
142
+
// Field doesn't exist on this type, so ignore it
143
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
140
144
return err
141
145
}
142
-
143
-
name = string(sval)
146
+
continue
144
147
}
145
148
146
-
switch name {
149
+
switch string(nameBuf[:nameLen]) {
147
150
// t.Cid (string) (string)
148
151
case "cid":
149
152
···
180
183
181
184
default:
182
185
// Field doesn't exist on this type, so ignore it
183
-
cbg.ScanForLinks(r, func(cid.Cid) {})
186
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
187
+
return err
188
+
}
184
189
}
185
190
}
186
191
···
492
497
return fmt.Errorf("SyncSubscribeRepos_Commit: map struct too large (%d)", extra)
493
498
}
494
499
495
-
var name string
496
500
n := extra
497
501
502
+
nameBuf := make([]byte, 6)
498
503
for i := uint64(0); i < n; i++ {
504
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
505
+
if err != nil {
506
+
return err
507
+
}
499
508
500
-
{
501
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
502
-
if err != nil {
509
+
if !ok {
510
+
// Field doesn't exist on this type, so ignore it
511
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
503
512
return err
504
513
}
505
-
506
-
name = string(sval)
514
+
continue
507
515
}
508
516
509
-
switch name {
517
+
switch string(nameBuf[:nameLen]) {
510
518
// t.Ops ([]*atproto.SyncSubscribeRepos_RepoOp) (slice)
511
519
case "ops":
512
520
···
767
775
768
776
default:
769
777
// Field doesn't exist on this type, so ignore it
770
-
cbg.ScanForLinks(r, func(cid.Cid) {})
778
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
779
+
return err
780
+
}
771
781
}
772
782
}
773
783
···
901
911
return fmt.Errorf("SyncSubscribeRepos_Handle: map struct too large (%d)", extra)
902
912
}
903
913
904
-
var name string
905
914
n := extra
906
915
916
+
nameBuf := make([]byte, 6)
907
917
for i := uint64(0); i < n; i++ {
918
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
919
+
if err != nil {
920
+
return err
921
+
}
908
922
909
-
{
910
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
911
-
if err != nil {
923
+
if !ok {
924
+
// Field doesn't exist on this type, so ignore it
925
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
912
926
return err
913
927
}
914
-
915
-
name = string(sval)
928
+
continue
916
929
}
917
930
918
-
switch name {
931
+
switch string(nameBuf[:nameLen]) {
919
932
// t.Did (string) (string)
920
933
case "did":
921
934
···
978
991
979
992
default:
980
993
// Field doesn't exist on this type, so ignore it
981
-
cbg.ScanForLinks(r, func(cid.Cid) {})
994
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
995
+
return err
996
+
}
982
997
}
983
998
}
984
999
···
1126
1141
return fmt.Errorf("SyncSubscribeRepos_Identity: map struct too large (%d)", extra)
1127
1142
}
1128
1143
1129
-
var name string
1130
1144
n := extra
1131
1145
1146
+
nameBuf := make([]byte, 6)
1132
1147
for i := uint64(0); i < n; i++ {
1148
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
1149
+
if err != nil {
1150
+
return err
1151
+
}
1133
1152
1134
-
{
1135
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
1136
-
if err != nil {
1153
+
if !ok {
1154
+
// Field doesn't exist on this type, so ignore it
1155
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
1137
1156
return err
1138
1157
}
1139
-
1140
-
name = string(sval)
1158
+
continue
1141
1159
}
1142
1160
1143
-
switch name {
1161
+
switch string(nameBuf[:nameLen]) {
1144
1162
// t.Did (string) (string)
1145
1163
case "did":
1146
1164
···
1213
1231
1214
1232
default:
1215
1233
// Field doesn't exist on this type, so ignore it
1216
-
cbg.ScanForLinks(r, func(cid.Cid) {})
1234
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
1235
+
return err
1236
+
}
1217
1237
}
1218
1238
}
1219
1239
···
1377
1397
return fmt.Errorf("SyncSubscribeRepos_Account: map struct too large (%d)", extra)
1378
1398
}
1379
1399
1380
-
var name string
1381
1400
n := extra
1382
1401
1402
+
nameBuf := make([]byte, 6)
1383
1403
for i := uint64(0); i < n; i++ {
1404
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
1405
+
if err != nil {
1406
+
return err
1407
+
}
1384
1408
1385
-
{
1386
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
1387
-
if err != nil {
1409
+
if !ok {
1410
+
// Field doesn't exist on this type, so ignore it
1411
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
1388
1412
return err
1389
1413
}
1390
-
1391
-
name = string(sval)
1414
+
continue
1392
1415
}
1393
1416
1394
-
switch name {
1417
+
switch string(nameBuf[:nameLen]) {
1395
1418
// t.Did (string) (string)
1396
1419
case "did":
1397
1420
···
1482
1505
1483
1506
default:
1484
1507
// Field doesn't exist on this type, so ignore it
1485
-
cbg.ScanForLinks(r, func(cid.Cid) {})
1508
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
1509
+
return err
1510
+
}
1486
1511
}
1487
1512
}
1488
1513
···
1585
1610
return fmt.Errorf("SyncSubscribeRepos_Info: map struct too large (%d)", extra)
1586
1611
}
1587
1612
1588
-
var name string
1589
1613
n := extra
1590
1614
1615
+
nameBuf := make([]byte, 7)
1591
1616
for i := uint64(0); i < n; i++ {
1617
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
1618
+
if err != nil {
1619
+
return err
1620
+
}
1592
1621
1593
-
{
1594
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
1595
-
if err != nil {
1622
+
if !ok {
1623
+
// Field doesn't exist on this type, so ignore it
1624
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
1596
1625
return err
1597
1626
}
1598
-
1599
-
name = string(sval)
1627
+
continue
1600
1628
}
1601
1629
1602
-
switch name {
1630
+
switch string(nameBuf[:nameLen]) {
1603
1631
// t.Name (string) (string)
1604
1632
case "name":
1605
1633
···
1635
1663
1636
1664
default:
1637
1665
// Field doesn't exist on this type, so ignore it
1638
-
cbg.ScanForLinks(r, func(cid.Cid) {})
1666
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
1667
+
return err
1668
+
}
1639
1669
}
1640
1670
}
1641
1671
···
1775
1805
return fmt.Errorf("SyncSubscribeRepos_Migrate: map struct too large (%d)", extra)
1776
1806
}
1777
1807
1778
-
var name string
1779
1808
n := extra
1780
1809
1810
+
nameBuf := make([]byte, 9)
1781
1811
for i := uint64(0); i < n; i++ {
1812
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
1813
+
if err != nil {
1814
+
return err
1815
+
}
1782
1816
1783
-
{
1784
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
1785
-
if err != nil {
1817
+
if !ok {
1818
+
// Field doesn't exist on this type, so ignore it
1819
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
1786
1820
return err
1787
1821
}
1788
-
1789
-
name = string(sval)
1822
+
continue
1790
1823
}
1791
1824
1792
-
switch name {
1825
+
switch string(nameBuf[:nameLen]) {
1793
1826
// t.Did (string) (string)
1794
1827
case "did":
1795
1828
···
1862
1895
1863
1896
default:
1864
1897
// Field doesn't exist on this type, so ignore it
1865
-
cbg.ScanForLinks(r, func(cid.Cid) {})
1898
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
1899
+
return err
1900
+
}
1866
1901
}
1867
1902
}
1868
1903
···
1967
2002
return fmt.Errorf("SyncSubscribeRepos_RepoOp: map struct too large (%d)", extra)
1968
2003
}
1969
2004
1970
-
var name string
1971
2005
n := extra
1972
2006
2007
+
nameBuf := make([]byte, 6)
1973
2008
for i := uint64(0); i < n; i++ {
2009
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
2010
+
if err != nil {
2011
+
return err
2012
+
}
1974
2013
1975
-
{
1976
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
1977
-
if err != nil {
2014
+
if !ok {
2015
+
// Field doesn't exist on this type, so ignore it
2016
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
1978
2017
return err
1979
2018
}
1980
-
1981
-
name = string(sval)
2019
+
continue
1982
2020
}
1983
2021
1984
-
switch name {
2022
+
switch string(nameBuf[:nameLen]) {
1985
2023
// t.Cid (util.LexLink) (struct)
1986
2024
case "cid":
1987
2025
···
2027
2065
2028
2066
default:
2029
2067
// Field doesn't exist on this type, so ignore it
2030
-
cbg.ScanForLinks(r, func(cid.Cid) {})
2068
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
2069
+
return err
2070
+
}
2031
2071
}
2032
2072
}
2033
2073
···
2138
2178
return fmt.Errorf("SyncSubscribeRepos_Tombstone: map struct too large (%d)", extra)
2139
2179
}
2140
2180
2141
-
var name string
2142
2181
n := extra
2143
2182
2183
+
nameBuf := make([]byte, 4)
2144
2184
for i := uint64(0); i < n; i++ {
2185
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
2186
+
if err != nil {
2187
+
return err
2188
+
}
2145
2189
2146
-
{
2147
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
2148
-
if err != nil {
2190
+
if !ok {
2191
+
// Field doesn't exist on this type, so ignore it
2192
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
2149
2193
return err
2150
2194
}
2151
-
2152
-
name = string(sval)
2195
+
continue
2153
2196
}
2154
2197
2155
-
switch name {
2198
+
switch string(nameBuf[:nameLen]) {
2156
2199
// t.Did (string) (string)
2157
2200
case "did":
2158
2201
···
2204
2247
2205
2248
default:
2206
2249
// Field doesn't exist on this type, so ignore it
2207
-
cbg.ScanForLinks(r, func(cid.Cid) {})
2250
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
2251
+
return err
2252
+
}
2208
2253
}
2209
2254
}
2210
2255
···
2301
2346
return fmt.Errorf("LabelDefs_SelfLabels: map struct too large (%d)", extra)
2302
2347
}
2303
2348
2304
-
var name string
2305
2349
n := extra
2306
2350
2351
+
nameBuf := make([]byte, 6)
2307
2352
for i := uint64(0); i < n; i++ {
2353
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
2354
+
if err != nil {
2355
+
return err
2356
+
}
2308
2357
2309
-
{
2310
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
2311
-
if err != nil {
2358
+
if !ok {
2359
+
// Field doesn't exist on this type, so ignore it
2360
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
2312
2361
return err
2313
2362
}
2314
-
2315
-
name = string(sval)
2363
+
continue
2316
2364
}
2317
2365
2318
-
switch name {
2366
+
switch string(nameBuf[:nameLen]) {
2319
2367
// t.LexiconTypeID (string) (string)
2320
2368
case "$type":
2321
2369
···
2390
2438
2391
2439
default:
2392
2440
// Field doesn't exist on this type, so ignore it
2393
-
cbg.ScanForLinks(r, func(cid.Cid) {})
2441
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
2442
+
return err
2443
+
}
2394
2444
}
2395
2445
}
2396
2446
···
2456
2506
return fmt.Errorf("LabelDefs_SelfLabel: map struct too large (%d)", extra)
2457
2507
}
2458
2508
2459
-
var name string
2460
2509
n := extra
2461
2510
2511
+
nameBuf := make([]byte, 3)
2462
2512
for i := uint64(0); i < n; i++ {
2513
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
2514
+
if err != nil {
2515
+
return err
2516
+
}
2463
2517
2464
-
{
2465
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
2466
-
if err != nil {
2518
+
if !ok {
2519
+
// Field doesn't exist on this type, so ignore it
2520
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
2467
2521
return err
2468
2522
}
2469
-
2470
-
name = string(sval)
2523
+
continue
2471
2524
}
2472
2525
2473
-
switch name {
2526
+
switch string(nameBuf[:nameLen]) {
2474
2527
// t.Val (string) (string)
2475
2528
case "val":
2476
2529
···
2485
2538
2486
2539
default:
2487
2540
// Field doesn't exist on this type, so ignore it
2488
-
cbg.ScanForLinks(r, func(cid.Cid) {})
2541
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
2542
+
return err
2543
+
}
2489
2544
}
2490
2545
}
2491
2546
···
2790
2845
return fmt.Errorf("LabelDefs_Label: map struct too large (%d)", extra)
2791
2846
}
2792
2847
2793
-
var name string
2794
2848
n := extra
2795
2849
2850
+
nameBuf := make([]byte, 3)
2796
2851
for i := uint64(0); i < n; i++ {
2852
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
2853
+
if err != nil {
2854
+
return err
2855
+
}
2797
2856
2798
-
{
2799
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
2800
-
if err != nil {
2857
+
if !ok {
2858
+
// Field doesn't exist on this type, so ignore it
2859
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
2801
2860
return err
2802
2861
}
2803
-
2804
-
name = string(sval)
2862
+
continue
2805
2863
}
2806
2864
2807
-
switch name {
2865
+
switch string(nameBuf[:nameLen]) {
2808
2866
// t.Cid (string) (string)
2809
2867
case "cid":
2810
2868
···
2986
3044
2987
3045
default:
2988
3046
// Field doesn't exist on this type, so ignore it
2989
-
cbg.ScanForLinks(r, func(cid.Cid) {})
3047
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
3048
+
return err
3049
+
}
2990
3050
}
2991
3051
}
2992
3052
···
3077
3137
return fmt.Errorf("LabelSubscribeLabels_Labels: map struct too large (%d)", extra)
3078
3138
}
3079
3139
3080
-
var name string
3081
3140
n := extra
3082
3141
3142
+
nameBuf := make([]byte, 6)
3083
3143
for i := uint64(0); i < n; i++ {
3144
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
3145
+
if err != nil {
3146
+
return err
3147
+
}
3084
3148
3085
-
{
3086
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
3087
-
if err != nil {
3149
+
if !ok {
3150
+
// Field doesn't exist on this type, so ignore it
3151
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
3088
3152
return err
3089
3153
}
3090
-
3091
-
name = string(sval)
3154
+
continue
3092
3155
}
3093
3156
3094
-
switch name {
3157
+
switch string(nameBuf[:nameLen]) {
3095
3158
// t.Seq (int64) (int64)
3096
3159
case "seq":
3097
3160
{
···
3170
3233
3171
3234
default:
3172
3235
// Field doesn't exist on this type, so ignore it
3173
-
cbg.ScanForLinks(r, func(cid.Cid) {})
3236
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
3237
+
return err
3238
+
}
3174
3239
}
3175
3240
}
3176
3241
···
3273
3338
return fmt.Errorf("LabelSubscribeLabels_Info: map struct too large (%d)", extra)
3274
3339
}
3275
3340
3276
-
var name string
3277
3341
n := extra
3278
3342
3343
+
nameBuf := make([]byte, 7)
3279
3344
for i := uint64(0); i < n; i++ {
3345
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
3346
+
if err != nil {
3347
+
return err
3348
+
}
3280
3349
3281
-
{
3282
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
3283
-
if err != nil {
3350
+
if !ok {
3351
+
// Field doesn't exist on this type, so ignore it
3352
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
3284
3353
return err
3285
3354
}
3286
-
3287
-
name = string(sval)
3355
+
continue
3288
3356
}
3289
3357
3290
-
switch name {
3358
+
switch string(nameBuf[:nameLen]) {
3291
3359
// t.Name (string) (string)
3292
3360
case "name":
3293
3361
···
3323
3391
3324
3392
default:
3325
3393
// Field doesn't exist on this type, so ignore it
3326
-
cbg.ScanForLinks(r, func(cid.Cid) {})
3394
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
3395
+
return err
3396
+
}
3327
3397
}
3328
3398
}
3329
3399
···
3527
3597
return fmt.Errorf("LabelDefs_LabelValueDefinition: map struct too large (%d)", extra)
3528
3598
}
3529
3599
3530
-
var name string
3531
3600
n := extra
3532
3601
3602
+
nameBuf := make([]byte, 14)
3533
3603
for i := uint64(0); i < n; i++ {
3604
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
3605
+
if err != nil {
3606
+
return err
3607
+
}
3534
3608
3535
-
{
3536
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
3537
-
if err != nil {
3609
+
if !ok {
3610
+
// Field doesn't exist on this type, so ignore it
3611
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
3538
3612
return err
3539
3613
}
3540
-
3541
-
name = string(sval)
3614
+
continue
3542
3615
}
3543
3616
3544
-
switch name {
3617
+
switch string(nameBuf[:nameLen]) {
3545
3618
// t.Blurs (string) (string)
3546
3619
case "blurs":
3547
3620
···
3681
3754
3682
3755
default:
3683
3756
// Field doesn't exist on this type, so ignore it
3684
-
cbg.ScanForLinks(r, func(cid.Cid) {})
3757
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
3758
+
return err
3759
+
}
3685
3760
}
3686
3761
}
3687
3762
···
3793
3868
return fmt.Errorf("LabelDefs_LabelValueDefinitionStrings: map struct too large (%d)", extra)
3794
3869
}
3795
3870
3796
-
var name string
3797
3871
n := extra
3798
3872
3873
+
nameBuf := make([]byte, 11)
3799
3874
for i := uint64(0); i < n; i++ {
3875
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
3876
+
if err != nil {
3877
+
return err
3878
+
}
3800
3879
3801
-
{
3802
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
3803
-
if err != nil {
3880
+
if !ok {
3881
+
// Field doesn't exist on this type, so ignore it
3882
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
3804
3883
return err
3805
3884
}
3806
-
3807
-
name = string(sval)
3885
+
continue
3808
3886
}
3809
3887
3810
-
switch name {
3888
+
switch string(nameBuf[:nameLen]) {
3811
3889
// t.Lang (string) (string)
3812
3890
case "lang":
3813
3891
···
3844
3922
3845
3923
default:
3846
3924
// Field doesn't exist on this type, so ignore it
3847
-
cbg.ScanForLinks(r, func(cid.Cid) {})
3925
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
3926
+
return err
3927
+
}
3848
3928
}
3849
3929
}
3850
3930
+494
-304
api/bsky/cbor_gen.go
+494
-304
api/bsky/cbor_gen.go
···
338
338
return fmt.Errorf("FeedPost: map struct too large (%d)", extra)
339
339
}
340
340
341
-
var name string
342
341
n := extra
343
342
343
+
nameBuf := make([]byte, 9)
344
344
for i := uint64(0); i < n; i++ {
345
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
346
+
if err != nil {
347
+
return err
348
+
}
345
349
346
-
{
347
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
348
-
if err != nil {
350
+
if !ok {
351
+
// Field doesn't exist on this type, so ignore it
352
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
349
353
return err
350
354
}
351
-
352
-
name = string(sval)
355
+
continue
353
356
}
354
357
355
-
switch name {
358
+
switch string(nameBuf[:nameLen]) {
356
359
// t.Tags ([]string) (slice)
357
360
case "tags":
358
361
···
627
630
628
631
default:
629
632
// Field doesn't exist on this type, so ignore it
630
-
cbg.ScanForLinks(r, func(cid.Cid) {})
633
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
634
+
return err
635
+
}
631
636
}
632
637
}
633
638
···
728
733
return fmt.Errorf("FeedRepost: map struct too large (%d)", extra)
729
734
}
730
735
731
-
var name string
732
736
n := extra
733
737
738
+
nameBuf := make([]byte, 9)
734
739
for i := uint64(0); i < n; i++ {
740
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
741
+
if err != nil {
742
+
return err
743
+
}
735
744
736
-
{
737
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
738
-
if err != nil {
745
+
if !ok {
746
+
// Field doesn't exist on this type, so ignore it
747
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
739
748
return err
740
749
}
741
-
742
-
name = string(sval)
750
+
continue
743
751
}
744
752
745
-
switch name {
753
+
switch string(nameBuf[:nameLen]) {
746
754
// t.LexiconTypeID (string) (string)
747
755
case "$type":
748
756
···
788
796
789
797
default:
790
798
// Field doesn't exist on this type, so ignore it
791
-
cbg.ScanForLinks(r, func(cid.Cid) {})
799
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
800
+
return err
801
+
}
792
802
}
793
803
}
794
804
···
893
903
return fmt.Errorf("FeedPost_Entity: map struct too large (%d)", extra)
894
904
}
895
905
896
-
var name string
897
906
n := extra
898
907
908
+
nameBuf := make([]byte, 5)
899
909
for i := uint64(0); i < n; i++ {
910
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
911
+
if err != nil {
912
+
return err
913
+
}
900
914
901
-
{
902
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
903
-
if err != nil {
915
+
if !ok {
916
+
// Field doesn't exist on this type, so ignore it
917
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
904
918
return err
905
919
}
906
-
907
-
name = string(sval)
920
+
continue
908
921
}
909
922
910
-
switch name {
923
+
switch string(nameBuf[:nameLen]) {
911
924
// t.Type (string) (string)
912
925
case "type":
913
926
···
953
966
954
967
default:
955
968
// Field doesn't exist on this type, so ignore it
956
-
cbg.ScanForLinks(r, func(cid.Cid) {})
969
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
970
+
return err
971
+
}
957
972
}
958
973
}
959
974
···
1028
1043
return fmt.Errorf("FeedPost_ReplyRef: map struct too large (%d)", extra)
1029
1044
}
1030
1045
1031
-
var name string
1032
1046
n := extra
1033
1047
1048
+
nameBuf := make([]byte, 6)
1034
1049
for i := uint64(0); i < n; i++ {
1050
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
1051
+
if err != nil {
1052
+
return err
1053
+
}
1035
1054
1036
-
{
1037
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
1038
-
if err != nil {
1055
+
if !ok {
1056
+
// Field doesn't exist on this type, so ignore it
1057
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
1039
1058
return err
1040
1059
}
1041
-
1042
-
name = string(sval)
1060
+
continue
1043
1061
}
1044
1062
1045
-
switch name {
1063
+
switch string(nameBuf[:nameLen]) {
1046
1064
// t.Root (atproto.RepoStrongRef) (struct)
1047
1065
case "root":
1048
1066
···
1086
1104
1087
1105
default:
1088
1106
// Field doesn't exist on this type, so ignore it
1089
-
cbg.ScanForLinks(r, func(cid.Cid) {})
1107
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
1108
+
return err
1109
+
}
1090
1110
}
1091
1111
}
1092
1112
···
1174
1194
return fmt.Errorf("FeedPost_TextSlice: map struct too large (%d)", extra)
1175
1195
}
1176
1196
1177
-
var name string
1178
1197
n := extra
1179
1198
1199
+
nameBuf := make([]byte, 5)
1180
1200
for i := uint64(0); i < n; i++ {
1201
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
1202
+
if err != nil {
1203
+
return err
1204
+
}
1181
1205
1182
-
{
1183
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
1184
-
if err != nil {
1206
+
if !ok {
1207
+
// Field doesn't exist on this type, so ignore it
1208
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
1185
1209
return err
1186
1210
}
1187
-
1188
-
name = string(sval)
1211
+
continue
1189
1212
}
1190
1213
1191
-
switch name {
1214
+
switch string(nameBuf[:nameLen]) {
1192
1215
// t.End (int64) (int64)
1193
1216
case "end":
1194
1217
{
···
1244
1267
1245
1268
default:
1246
1269
// Field doesn't exist on this type, so ignore it
1247
-
cbg.ScanForLinks(r, func(cid.Cid) {})
1270
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
1271
+
return err
1272
+
}
1248
1273
}
1249
1274
}
1250
1275
···
1332
1357
return fmt.Errorf("EmbedImages: map struct too large (%d)", extra)
1333
1358
}
1334
1359
1335
-
var name string
1336
1360
n := extra
1337
1361
1362
+
nameBuf := make([]byte, 6)
1338
1363
for i := uint64(0); i < n; i++ {
1364
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
1365
+
if err != nil {
1366
+
return err
1367
+
}
1339
1368
1340
-
{
1341
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
1342
-
if err != nil {
1369
+
if !ok {
1370
+
// Field doesn't exist on this type, so ignore it
1371
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
1343
1372
return err
1344
1373
}
1345
-
1346
-
name = string(sval)
1374
+
continue
1347
1375
}
1348
1376
1349
-
switch name {
1377
+
switch string(nameBuf[:nameLen]) {
1350
1378
// t.LexiconTypeID (string) (string)
1351
1379
case "$type":
1352
1380
···
1410
1438
1411
1439
default:
1412
1440
// Field doesn't exist on this type, so ignore it
1413
-
cbg.ScanForLinks(r, func(cid.Cid) {})
1441
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
1442
+
return err
1443
+
}
1414
1444
}
1415
1445
}
1416
1446
···
1488
1518
return fmt.Errorf("EmbedExternal: map struct too large (%d)", extra)
1489
1519
}
1490
1520
1491
-
var name string
1492
1521
n := extra
1493
1522
1523
+
nameBuf := make([]byte, 8)
1494
1524
for i := uint64(0); i < n; i++ {
1525
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
1526
+
if err != nil {
1527
+
return err
1528
+
}
1495
1529
1496
-
{
1497
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
1498
-
if err != nil {
1530
+
if !ok {
1531
+
// Field doesn't exist on this type, so ignore it
1532
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
1499
1533
return err
1500
1534
}
1501
-
1502
-
name = string(sval)
1535
+
continue
1503
1536
}
1504
1537
1505
-
switch name {
1538
+
switch string(nameBuf[:nameLen]) {
1506
1539
// t.LexiconTypeID (string) (string)
1507
1540
case "$type":
1508
1541
···
1537
1570
1538
1571
default:
1539
1572
// Field doesn't exist on this type, so ignore it
1540
-
cbg.ScanForLinks(r, func(cid.Cid) {})
1573
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
1574
+
return err
1575
+
}
1541
1576
}
1542
1577
}
1543
1578
···
1673
1708
return fmt.Errorf("EmbedExternal_External: map struct too large (%d)", extra)
1674
1709
}
1675
1710
1676
-
var name string
1677
1711
n := extra
1678
1712
1713
+
nameBuf := make([]byte, 11)
1679
1714
for i := uint64(0); i < n; i++ {
1715
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
1716
+
if err != nil {
1717
+
return err
1718
+
}
1680
1719
1681
-
{
1682
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
1683
-
if err != nil {
1720
+
if !ok {
1721
+
// Field doesn't exist on this type, so ignore it
1722
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
1684
1723
return err
1685
1724
}
1686
-
1687
-
name = string(sval)
1725
+
continue
1688
1726
}
1689
1727
1690
-
switch name {
1728
+
switch string(nameBuf[:nameLen]) {
1691
1729
// t.Uri (string) (string)
1692
1730
case "uri":
1693
1731
···
1744
1782
1745
1783
default:
1746
1784
// Field doesn't exist on this type, so ignore it
1747
-
cbg.ScanForLinks(r, func(cid.Cid) {})
1785
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
1786
+
return err
1787
+
}
1748
1788
}
1749
1789
}
1750
1790
···
1850
1890
return fmt.Errorf("EmbedImages_Image: map struct too large (%d)", extra)
1851
1891
}
1852
1892
1853
-
var name string
1854
1893
n := extra
1855
1894
1895
+
nameBuf := make([]byte, 11)
1856
1896
for i := uint64(0); i < n; i++ {
1897
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
1898
+
if err != nil {
1899
+
return err
1900
+
}
1857
1901
1858
-
{
1859
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
1860
-
if err != nil {
1902
+
if !ok {
1903
+
// Field doesn't exist on this type, so ignore it
1904
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
1861
1905
return err
1862
1906
}
1863
-
1864
-
name = string(sval)
1907
+
continue
1865
1908
}
1866
1909
1867
-
switch name {
1910
+
switch string(nameBuf[:nameLen]) {
1868
1911
// t.Alt (string) (string)
1869
1912
case "alt":
1870
1913
···
1919
1962
1920
1963
default:
1921
1964
// Field doesn't exist on this type, so ignore it
1922
-
cbg.ScanForLinks(r, func(cid.Cid) {})
1965
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
1966
+
return err
1967
+
}
1923
1968
}
1924
1969
}
1925
1970
···
2027
2072
return fmt.Errorf("GraphFollow: map struct too large (%d)", extra)
2028
2073
}
2029
2074
2030
-
var name string
2031
2075
n := extra
2032
2076
2077
+
nameBuf := make([]byte, 9)
2033
2078
for i := uint64(0); i < n; i++ {
2079
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
2080
+
if err != nil {
2081
+
return err
2082
+
}
2034
2083
2035
-
{
2036
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
2037
-
if err != nil {
2084
+
if !ok {
2085
+
// Field doesn't exist on this type, so ignore it
2086
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
2038
2087
return err
2039
2088
}
2040
-
2041
-
name = string(sval)
2089
+
continue
2042
2090
}
2043
2091
2044
-
switch name {
2092
+
switch string(nameBuf[:nameLen]) {
2045
2093
// t.LexiconTypeID (string) (string)
2046
2094
case "$type":
2047
2095
···
2078
2126
2079
2127
default:
2080
2128
// Field doesn't exist on this type, so ignore it
2081
-
cbg.ScanForLinks(r, func(cid.Cid) {})
2129
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
2130
+
return err
2131
+
}
2082
2132
}
2083
2133
}
2084
2134
···
2364
2414
return fmt.Errorf("ActorProfile: map struct too large (%d)", extra)
2365
2415
}
2366
2416
2367
-
var name string
2368
2417
n := extra
2369
2418
2419
+
nameBuf := make([]byte, 20)
2370
2420
for i := uint64(0); i < n; i++ {
2421
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
2422
+
if err != nil {
2423
+
return err
2424
+
}
2371
2425
2372
-
{
2373
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
2374
-
if err != nil {
2426
+
if !ok {
2427
+
// Field doesn't exist on this type, so ignore it
2428
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
2375
2429
return err
2376
2430
}
2377
-
2378
-
name = string(sval)
2431
+
continue
2379
2432
}
2380
2433
2381
-
switch name {
2434
+
switch string(nameBuf[:nameLen]) {
2382
2435
// t.LexiconTypeID (string) (string)
2383
2436
case "$type":
2384
2437
···
2556
2609
2557
2610
default:
2558
2611
// Field doesn't exist on this type, so ignore it
2559
-
cbg.ScanForLinks(r, func(cid.Cid) {})
2612
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
2613
+
return err
2614
+
}
2560
2615
}
2561
2616
}
2562
2617
···
2634
2689
return fmt.Errorf("EmbedRecord: map struct too large (%d)", extra)
2635
2690
}
2636
2691
2637
-
var name string
2638
2692
n := extra
2639
2693
2694
+
nameBuf := make([]byte, 6)
2640
2695
for i := uint64(0); i < n; i++ {
2696
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
2697
+
if err != nil {
2698
+
return err
2699
+
}
2641
2700
2642
-
{
2643
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
2644
-
if err != nil {
2701
+
if !ok {
2702
+
// Field doesn't exist on this type, so ignore it
2703
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
2645
2704
return err
2646
2705
}
2647
-
2648
-
name = string(sval)
2706
+
continue
2649
2707
}
2650
2708
2651
-
switch name {
2709
+
switch string(nameBuf[:nameLen]) {
2652
2710
// t.LexiconTypeID (string) (string)
2653
2711
case "$type":
2654
2712
···
2683
2741
2684
2742
default:
2685
2743
// Field doesn't exist on this type, so ignore it
2686
-
cbg.ScanForLinks(r, func(cid.Cid) {})
2744
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
2745
+
return err
2746
+
}
2687
2747
}
2688
2748
}
2689
2749
···
2784
2844
return fmt.Errorf("FeedLike: map struct too large (%d)", extra)
2785
2845
}
2786
2846
2787
-
var name string
2788
2847
n := extra
2789
2848
2849
+
nameBuf := make([]byte, 9)
2790
2850
for i := uint64(0); i < n; i++ {
2851
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
2852
+
if err != nil {
2853
+
return err
2854
+
}
2791
2855
2792
-
{
2793
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
2794
-
if err != nil {
2856
+
if !ok {
2857
+
// Field doesn't exist on this type, so ignore it
2858
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
2795
2859
return err
2796
2860
}
2797
-
2798
-
name = string(sval)
2861
+
continue
2799
2862
}
2800
2863
2801
-
switch name {
2864
+
switch string(nameBuf[:nameLen]) {
2802
2865
// t.LexiconTypeID (string) (string)
2803
2866
case "$type":
2804
2867
···
2844
2907
2845
2908
default:
2846
2909
// Field doesn't exist on this type, so ignore it
2847
-
cbg.ScanForLinks(r, func(cid.Cid) {})
2910
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
2911
+
return err
2912
+
}
2848
2913
}
2849
2914
}
2850
2915
···
2929
2994
return fmt.Errorf("RichtextFacet: map struct too large (%d)", extra)
2930
2995
}
2931
2996
2932
-
var name string
2933
2997
n := extra
2934
2998
2999
+
nameBuf := make([]byte, 8)
2935
3000
for i := uint64(0); i < n; i++ {
3001
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
3002
+
if err != nil {
3003
+
return err
3004
+
}
2936
3005
2937
-
{
2938
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
2939
-
if err != nil {
3006
+
if !ok {
3007
+
// Field doesn't exist on this type, so ignore it
3008
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
2940
3009
return err
2941
3010
}
2942
-
2943
-
name = string(sval)
3011
+
continue
2944
3012
}
2945
3013
2946
-
switch name {
3014
+
switch string(nameBuf[:nameLen]) {
2947
3015
// t.Index (bsky.RichtextFacet_ByteSlice) (struct)
2948
3016
case "index":
2949
3017
···
3016
3084
3017
3085
default:
3018
3086
// Field doesn't exist on this type, so ignore it
3019
-
cbg.ScanForLinks(r, func(cid.Cid) {})
3087
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
3088
+
return err
3089
+
}
3020
3090
}
3021
3091
}
3022
3092
···
3104
3174
return fmt.Errorf("RichtextFacet_ByteSlice: map struct too large (%d)", extra)
3105
3175
}
3106
3176
3107
-
var name string
3108
3177
n := extra
3109
3178
3179
+
nameBuf := make([]byte, 9)
3110
3180
for i := uint64(0); i < n; i++ {
3181
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
3182
+
if err != nil {
3183
+
return err
3184
+
}
3111
3185
3112
-
{
3113
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
3114
-
if err != nil {
3186
+
if !ok {
3187
+
// Field doesn't exist on this type, so ignore it
3188
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
3115
3189
return err
3116
3190
}
3117
-
3118
-
name = string(sval)
3191
+
continue
3119
3192
}
3120
3193
3121
-
switch name {
3194
+
switch string(nameBuf[:nameLen]) {
3122
3195
// t.ByteEnd (int64) (int64)
3123
3196
case "byteEnd":
3124
3197
{
···
3174
3247
3175
3248
default:
3176
3249
// Field doesn't exist on this type, so ignore it
3177
-
cbg.ScanForLinks(r, func(cid.Cid) {})
3250
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
3251
+
return err
3252
+
}
3178
3253
}
3179
3254
}
3180
3255
···
3259
3334
return fmt.Errorf("RichtextFacet_Link: map struct too large (%d)", extra)
3260
3335
}
3261
3336
3262
-
var name string
3263
3337
n := extra
3264
3338
3339
+
nameBuf := make([]byte, 5)
3265
3340
for i := uint64(0); i < n; i++ {
3341
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
3342
+
if err != nil {
3343
+
return err
3344
+
}
3266
3345
3267
-
{
3268
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
3269
-
if err != nil {
3346
+
if !ok {
3347
+
// Field doesn't exist on this type, so ignore it
3348
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
3270
3349
return err
3271
3350
}
3272
-
3273
-
name = string(sval)
3351
+
continue
3274
3352
}
3275
3353
3276
-
switch name {
3354
+
switch string(nameBuf[:nameLen]) {
3277
3355
// t.Uri (string) (string)
3278
3356
case "uri":
3279
3357
···
3299
3377
3300
3378
default:
3301
3379
// Field doesn't exist on this type, so ignore it
3302
-
cbg.ScanForLinks(r, func(cid.Cid) {})
3380
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
3381
+
return err
3382
+
}
3303
3383
}
3304
3384
}
3305
3385
···
3384
3464
return fmt.Errorf("RichtextFacet_Mention: map struct too large (%d)", extra)
3385
3465
}
3386
3466
3387
-
var name string
3388
3467
n := extra
3389
3468
3469
+
nameBuf := make([]byte, 5)
3390
3470
for i := uint64(0); i < n; i++ {
3471
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
3472
+
if err != nil {
3473
+
return err
3474
+
}
3391
3475
3392
-
{
3393
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
3394
-
if err != nil {
3476
+
if !ok {
3477
+
// Field doesn't exist on this type, so ignore it
3478
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
3395
3479
return err
3396
3480
}
3397
-
3398
-
name = string(sval)
3481
+
continue
3399
3482
}
3400
3483
3401
-
switch name {
3484
+
switch string(nameBuf[:nameLen]) {
3402
3485
// t.Did (string) (string)
3403
3486
case "did":
3404
3487
···
3424
3507
3425
3508
default:
3426
3509
// Field doesn't exist on this type, so ignore it
3427
-
cbg.ScanForLinks(r, func(cid.Cid) {})
3510
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
3511
+
return err
3512
+
}
3428
3513
}
3429
3514
}
3430
3515
···
3509
3594
return fmt.Errorf("RichtextFacet_Tag: map struct too large (%d)", extra)
3510
3595
}
3511
3596
3512
-
var name string
3513
3597
n := extra
3514
3598
3599
+
nameBuf := make([]byte, 5)
3515
3600
for i := uint64(0); i < n; i++ {
3601
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
3602
+
if err != nil {
3603
+
return err
3604
+
}
3516
3605
3517
-
{
3518
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
3519
-
if err != nil {
3606
+
if !ok {
3607
+
// Field doesn't exist on this type, so ignore it
3608
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
3520
3609
return err
3521
3610
}
3522
-
3523
-
name = string(sval)
3611
+
continue
3524
3612
}
3525
3613
3526
-
switch name {
3614
+
switch string(nameBuf[:nameLen]) {
3527
3615
// t.Tag (string) (string)
3528
3616
case "tag":
3529
3617
···
3549
3637
3550
3638
default:
3551
3639
// Field doesn't exist on this type, so ignore it
3552
-
cbg.ScanForLinks(r, func(cid.Cid) {})
3640
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
3641
+
return err
3642
+
}
3553
3643
}
3554
3644
}
3555
3645
···
3643
3733
return fmt.Errorf("EmbedRecordWithMedia: map struct too large (%d)", extra)
3644
3734
}
3645
3735
3646
-
var name string
3647
3736
n := extra
3648
3737
3738
+
nameBuf := make([]byte, 6)
3649
3739
for i := uint64(0); i < n; i++ {
3740
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
3741
+
if err != nil {
3742
+
return err
3743
+
}
3650
3744
3651
-
{
3652
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
3653
-
if err != nil {
3745
+
if !ok {
3746
+
// Field doesn't exist on this type, so ignore it
3747
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
3654
3748
return err
3655
3749
}
3656
-
3657
-
name = string(sval)
3750
+
continue
3658
3751
}
3659
3752
3660
-
switch name {
3753
+
switch string(nameBuf[:nameLen]) {
3661
3754
// t.LexiconTypeID (string) (string)
3662
3755
case "$type":
3663
3756
···
3712
3805
3713
3806
default:
3714
3807
// Field doesn't exist on this type, so ignore it
3715
-
cbg.ScanForLinks(r, func(cid.Cid) {})
3808
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
3809
+
return err
3810
+
}
3716
3811
}
3717
3812
}
3718
3813
···
3813
3908
return fmt.Errorf("FeedDefs_NotFoundPost: map struct too large (%d)", extra)
3814
3909
}
3815
3910
3816
-
var name string
3817
3911
n := extra
3818
3912
3913
+
nameBuf := make([]byte, 8)
3819
3914
for i := uint64(0); i < n; i++ {
3915
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
3916
+
if err != nil {
3917
+
return err
3918
+
}
3820
3919
3821
-
{
3822
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
3823
-
if err != nil {
3920
+
if !ok {
3921
+
// Field doesn't exist on this type, so ignore it
3922
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
3824
3923
return err
3825
3924
}
3826
-
3827
-
name = string(sval)
3925
+
continue
3828
3926
}
3829
3927
3830
-
switch name {
3928
+
switch string(nameBuf[:nameLen]) {
3831
3929
// t.Uri (string) (string)
3832
3930
case "uri":
3833
3931
···
3871
3969
3872
3970
default:
3873
3971
// Field doesn't exist on this type, so ignore it
3874
-
cbg.ScanForLinks(r, func(cid.Cid) {})
3972
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
3973
+
return err
3974
+
}
3875
3975
}
3876
3976
}
3877
3977
···
3979
4079
return fmt.Errorf("GraphBlock: map struct too large (%d)", extra)
3980
4080
}
3981
4081
3982
-
var name string
3983
4082
n := extra
3984
4083
4084
+
nameBuf := make([]byte, 9)
3985
4085
for i := uint64(0); i < n; i++ {
4086
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
4087
+
if err != nil {
4088
+
return err
4089
+
}
3986
4090
3987
-
{
3988
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
3989
-
if err != nil {
4091
+
if !ok {
4092
+
// Field doesn't exist on this type, so ignore it
4093
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
3990
4094
return err
3991
4095
}
3992
-
3993
-
name = string(sval)
4096
+
continue
3994
4097
}
3995
4098
3996
-
switch name {
4099
+
switch string(nameBuf[:nameLen]) {
3997
4100
// t.LexiconTypeID (string) (string)
3998
4101
case "$type":
3999
4102
···
4030
4133
4031
4134
default:
4032
4135
// Field doesn't exist on this type, so ignore it
4033
-
cbg.ScanForLinks(r, func(cid.Cid) {})
4136
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
4137
+
return err
4138
+
}
4034
4139
}
4035
4140
}
4036
4141
···
4283
4388
return fmt.Errorf("GraphList: map struct too large (%d)", extra)
4284
4389
}
4285
4390
4286
-
var name string
4287
4391
n := extra
4288
4392
4393
+
nameBuf := make([]byte, 17)
4289
4394
for i := uint64(0); i < n; i++ {
4395
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
4396
+
if err != nil {
4397
+
return err
4398
+
}
4290
4399
4291
-
{
4292
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
4293
-
if err != nil {
4400
+
if !ok {
4401
+
// Field doesn't exist on this type, so ignore it
4402
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
4294
4403
return err
4295
4404
}
4296
-
4297
-
name = string(sval)
4405
+
continue
4298
4406
}
4299
4407
4300
-
switch name {
4408
+
switch string(nameBuf[:nameLen]) {
4301
4409
// t.Name (string) (string)
4302
4410
case "name":
4303
4411
···
4465
4573
4466
4574
default:
4467
4575
// Field doesn't exist on this type, so ignore it
4468
-
cbg.ScanForLinks(r, func(cid.Cid) {})
4576
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
4577
+
return err
4578
+
}
4469
4579
}
4470
4580
}
4471
4581
···
4596
4706
return fmt.Errorf("GraphListitem: map struct too large (%d)", extra)
4597
4707
}
4598
4708
4599
-
var name string
4600
4709
n := extra
4601
4710
4711
+
nameBuf := make([]byte, 9)
4602
4712
for i := uint64(0); i < n; i++ {
4713
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
4714
+
if err != nil {
4715
+
return err
4716
+
}
4603
4717
4604
-
{
4605
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
4606
-
if err != nil {
4718
+
if !ok {
4719
+
// Field doesn't exist on this type, so ignore it
4720
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
4607
4721
return err
4608
4722
}
4609
-
4610
-
name = string(sval)
4723
+
continue
4611
4724
}
4612
4725
4613
-
switch name {
4726
+
switch string(nameBuf[:nameLen]) {
4614
4727
// t.List (string) (string)
4615
4728
case "list":
4616
4729
···
4658
4771
4659
4772
default:
4660
4773
// Field doesn't exist on this type, so ignore it
4661
-
cbg.ScanForLinks(r, func(cid.Cid) {})
4774
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
4775
+
return err
4776
+
}
4662
4777
}
4663
4778
}
4664
4779
···
4934
5049
return fmt.Errorf("FeedGenerator: map struct too large (%d)", extra)
4935
5050
}
4936
5051
4937
-
var name string
4938
5052
n := extra
4939
5053
5054
+
nameBuf := make([]byte, 19)
4940
5055
for i := uint64(0); i < n; i++ {
5056
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
5057
+
if err != nil {
5058
+
return err
5059
+
}
4941
5060
4942
-
{
4943
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
4944
-
if err != nil {
5061
+
if !ok {
5062
+
// Field doesn't exist on this type, so ignore it
5063
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
4945
5064
return err
4946
5065
}
4947
-
4948
-
name = string(sval)
5066
+
continue
4949
5067
}
4950
5068
4951
-
switch name {
5069
+
switch string(nameBuf[:nameLen]) {
4952
5070
// t.Did (string) (string)
4953
5071
case "did":
4954
5072
···
5139
5257
5140
5258
default:
5141
5259
// Field doesn't exist on this type, so ignore it
5142
-
cbg.ScanForLinks(r, func(cid.Cid) {})
5260
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
5261
+
return err
5262
+
}
5143
5263
}
5144
5264
}
5145
5265
···
5247
5367
return fmt.Errorf("GraphListblock: map struct too large (%d)", extra)
5248
5368
}
5249
5369
5250
-
var name string
5251
5370
n := extra
5252
5371
5372
+
nameBuf := make([]byte, 9)
5253
5373
for i := uint64(0); i < n; i++ {
5374
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
5375
+
if err != nil {
5376
+
return err
5377
+
}
5254
5378
5255
-
{
5256
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
5257
-
if err != nil {
5379
+
if !ok {
5380
+
// Field doesn't exist on this type, so ignore it
5381
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
5258
5382
return err
5259
5383
}
5260
-
5261
-
name = string(sval)
5384
+
continue
5262
5385
}
5263
5386
5264
-
switch name {
5387
+
switch string(nameBuf[:nameLen]) {
5265
5388
// t.LexiconTypeID (string) (string)
5266
5389
case "$type":
5267
5390
···
5298
5421
5299
5422
default:
5300
5423
// Field doesn't exist on this type, so ignore it
5301
-
cbg.ScanForLinks(r, func(cid.Cid) {})
5424
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
5425
+
return err
5426
+
}
5302
5427
}
5303
5428
}
5304
5429
···
5386
5511
return fmt.Errorf("EmbedDefs_AspectRatio: map struct too large (%d)", extra)
5387
5512
}
5388
5513
5389
-
var name string
5390
5514
n := extra
5391
5515
5516
+
nameBuf := make([]byte, 6)
5392
5517
for i := uint64(0); i < n; i++ {
5518
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
5519
+
if err != nil {
5520
+
return err
5521
+
}
5393
5522
5394
-
{
5395
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
5396
-
if err != nil {
5523
+
if !ok {
5524
+
// Field doesn't exist on this type, so ignore it
5525
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
5397
5526
return err
5398
5527
}
5399
-
5400
-
name = string(sval)
5528
+
continue
5401
5529
}
5402
5530
5403
-
switch name {
5531
+
switch string(nameBuf[:nameLen]) {
5404
5532
// t.Width (int64) (int64)
5405
5533
case "width":
5406
5534
{
···
5456
5584
5457
5585
default:
5458
5586
// Field doesn't exist on this type, so ignore it
5459
-
cbg.ScanForLinks(r, func(cid.Cid) {})
5587
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
5588
+
return err
5589
+
}
5460
5590
}
5461
5591
}
5462
5592
···
5638
5768
return fmt.Errorf("FeedThreadgate: map struct too large (%d)", extra)
5639
5769
}
5640
5770
5641
-
var name string
5642
5771
n := extra
5643
5772
5773
+
nameBuf := make([]byte, 13)
5644
5774
for i := uint64(0); i < n; i++ {
5775
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
5776
+
if err != nil {
5777
+
return err
5778
+
}
5645
5779
5646
-
{
5647
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
5648
-
if err != nil {
5780
+
if !ok {
5781
+
// Field doesn't exist on this type, so ignore it
5782
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
5649
5783
return err
5650
5784
}
5651
-
5652
-
name = string(sval)
5785
+
continue
5653
5786
}
5654
5787
5655
-
switch name {
5788
+
switch string(nameBuf[:nameLen]) {
5656
5789
// t.Post (string) (string)
5657
5790
case "post":
5658
5791
···
5778
5911
5779
5912
default:
5780
5913
// Field doesn't exist on this type, so ignore it
5781
-
cbg.ScanForLinks(r, func(cid.Cid) {})
5914
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
5915
+
return err
5916
+
}
5782
5917
}
5783
5918
}
5784
5919
···
5863
5998
return fmt.Errorf("FeedThreadgate_ListRule: map struct too large (%d)", extra)
5864
5999
}
5865
6000
5866
-
var name string
5867
6001
n := extra
5868
6002
6003
+
nameBuf := make([]byte, 5)
5869
6004
for i := uint64(0); i < n; i++ {
6005
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
6006
+
if err != nil {
6007
+
return err
6008
+
}
5870
6009
5871
-
{
5872
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
5873
-
if err != nil {
6010
+
if !ok {
6011
+
// Field doesn't exist on this type, so ignore it
6012
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
5874
6013
return err
5875
6014
}
5876
-
5877
-
name = string(sval)
6015
+
continue
5878
6016
}
5879
6017
5880
-
switch name {
6018
+
switch string(nameBuf[:nameLen]) {
5881
6019
// t.List (string) (string)
5882
6020
case "list":
5883
6021
···
5903
6041
5904
6042
default:
5905
6043
// Field doesn't exist on this type, so ignore it
5906
-
cbg.ScanForLinks(r, func(cid.Cid) {})
6044
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
6045
+
return err
6046
+
}
5907
6047
}
5908
6048
}
5909
6049
···
5965
6105
return fmt.Errorf("FeedThreadgate_MentionRule: map struct too large (%d)", extra)
5966
6106
}
5967
6107
5968
-
var name string
5969
6108
n := extra
5970
6109
6110
+
nameBuf := make([]byte, 5)
5971
6111
for i := uint64(0); i < n; i++ {
6112
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
6113
+
if err != nil {
6114
+
return err
6115
+
}
5972
6116
5973
-
{
5974
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
5975
-
if err != nil {
6117
+
if !ok {
6118
+
// Field doesn't exist on this type, so ignore it
6119
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
5976
6120
return err
5977
6121
}
5978
-
5979
-
name = string(sval)
6122
+
continue
5980
6123
}
5981
6124
5982
-
switch name {
6125
+
switch string(nameBuf[:nameLen]) {
5983
6126
// t.LexiconTypeID (string) (string)
5984
6127
case "$type":
5985
6128
···
5994
6137
5995
6138
default:
5996
6139
// Field doesn't exist on this type, so ignore it
5997
-
cbg.ScanForLinks(r, func(cid.Cid) {})
6140
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
6141
+
return err
6142
+
}
5998
6143
}
5999
6144
}
6000
6145
···
6056
6201
return fmt.Errorf("FeedThreadgate_FollowingRule: map struct too large (%d)", extra)
6057
6202
}
6058
6203
6059
-
var name string
6060
6204
n := extra
6061
6205
6206
+
nameBuf := make([]byte, 5)
6062
6207
for i := uint64(0); i < n; i++ {
6208
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
6209
+
if err != nil {
6210
+
return err
6211
+
}
6063
6212
6064
-
{
6065
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
6066
-
if err != nil {
6213
+
if !ok {
6214
+
// Field doesn't exist on this type, so ignore it
6215
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
6067
6216
return err
6068
6217
}
6069
-
6070
-
name = string(sval)
6218
+
continue
6071
6219
}
6072
6220
6073
-
switch name {
6221
+
switch string(nameBuf[:nameLen]) {
6074
6222
// t.LexiconTypeID (string) (string)
6075
6223
case "$type":
6076
6224
···
6085
6233
6086
6234
default:
6087
6235
// Field doesn't exist on this type, so ignore it
6088
-
cbg.ScanForLinks(r, func(cid.Cid) {})
6236
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
6237
+
return err
6238
+
}
6089
6239
}
6090
6240
}
6091
6241
···
6151
6301
return fmt.Errorf("GraphStarterpack_FeedItem: map struct too large (%d)", extra)
6152
6302
}
6153
6303
6154
-
var name string
6155
6304
n := extra
6156
6305
6306
+
nameBuf := make([]byte, 3)
6157
6307
for i := uint64(0); i < n; i++ {
6308
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
6309
+
if err != nil {
6310
+
return err
6311
+
}
6158
6312
6159
-
{
6160
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
6161
-
if err != nil {
6313
+
if !ok {
6314
+
// Field doesn't exist on this type, so ignore it
6315
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
6162
6316
return err
6163
6317
}
6164
-
6165
-
name = string(sval)
6318
+
continue
6166
6319
}
6167
6320
6168
-
switch name {
6321
+
switch string(nameBuf[:nameLen]) {
6169
6322
// t.Uri (string) (string)
6170
6323
case "uri":
6171
6324
···
6180
6333
6181
6334
default:
6182
6335
// Field doesn't exist on this type, so ignore it
6183
-
cbg.ScanForLinks(r, func(cid.Cid) {})
6336
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
6337
+
return err
6338
+
}
6184
6339
}
6185
6340
}
6186
6341
···
6414
6569
return fmt.Errorf("GraphStarterpack: map struct too large (%d)", extra)
6415
6570
}
6416
6571
6417
-
var name string
6418
6572
n := extra
6419
6573
6574
+
nameBuf := make([]byte, 17)
6420
6575
for i := uint64(0); i < n; i++ {
6576
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
6577
+
if err != nil {
6578
+
return err
6579
+
}
6421
6580
6422
-
{
6423
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
6424
-
if err != nil {
6581
+
if !ok {
6582
+
// Field doesn't exist on this type, so ignore it
6583
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
6425
6584
return err
6426
6585
}
6427
-
6428
-
name = string(sval)
6586
+
continue
6429
6587
}
6430
6588
6431
-
switch name {
6589
+
switch string(nameBuf[:nameLen]) {
6432
6590
// t.List (string) (string)
6433
6591
case "list":
6434
6592
···
6595
6753
6596
6754
default:
6597
6755
// Field doesn't exist on this type, so ignore it
6598
-
cbg.ScanForLinks(r, func(cid.Cid) {})
6756
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
6757
+
return err
6758
+
}
6599
6759
}
6600
6760
}
6601
6761
···
6720
6880
return fmt.Errorf("LabelerService: map struct too large (%d)", extra)
6721
6881
}
6722
6882
6723
-
var name string
6724
6883
n := extra
6725
6884
6885
+
nameBuf := make([]byte, 9)
6726
6886
for i := uint64(0); i < n; i++ {
6887
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
6888
+
if err != nil {
6889
+
return err
6890
+
}
6727
6891
6728
-
{
6729
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
6730
-
if err != nil {
6892
+
if !ok {
6893
+
// Field doesn't exist on this type, so ignore it
6894
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
6731
6895
return err
6732
6896
}
6733
-
6734
-
name = string(sval)
6897
+
continue
6735
6898
}
6736
6899
6737
-
switch name {
6900
+
switch string(nameBuf[:nameLen]) {
6738
6901
// t.LexiconTypeID (string) (string)
6739
6902
case "$type":
6740
6903
···
6800
6963
6801
6964
default:
6802
6965
// Field doesn't exist on this type, so ignore it
6803
-
cbg.ScanForLinks(r, func(cid.Cid) {})
6966
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
6967
+
return err
6968
+
}
6804
6969
}
6805
6970
}
6806
6971
···
6916
7081
return fmt.Errorf("LabelerDefs_LabelerPolicies: map struct too large (%d)", extra)
6917
7082
}
6918
7083
6919
-
var name string
6920
7084
n := extra
6921
7085
7086
+
nameBuf := make([]byte, 21)
6922
7087
for i := uint64(0); i < n; i++ {
7088
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
7089
+
if err != nil {
7090
+
return err
7091
+
}
6923
7092
6924
-
{
6925
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
6926
-
if err != nil {
7093
+
if !ok {
7094
+
// Field doesn't exist on this type, so ignore it
7095
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
6927
7096
return err
6928
7097
}
6929
-
6930
-
name = string(sval)
7098
+
continue
6931
7099
}
6932
7100
6933
-
switch name {
7101
+
switch string(nameBuf[:nameLen]) {
6934
7102
// t.LabelValues ([]*string) (slice)
6935
7103
case "labelValues":
6936
7104
···
7033
7201
7034
7202
default:
7035
7203
// Field doesn't exist on this type, so ignore it
7036
-
cbg.ScanForLinks(r, func(cid.Cid) {})
7204
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
7205
+
return err
7206
+
}
7037
7207
}
7038
7208
}
7039
7209
···
7204
7374
return fmt.Errorf("EmbedVideo: map struct too large (%d)", extra)
7205
7375
}
7206
7376
7207
-
var name string
7208
7377
n := extra
7209
7378
7379
+
nameBuf := make([]byte, 11)
7210
7380
for i := uint64(0); i < n; i++ {
7381
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
7382
+
if err != nil {
7383
+
return err
7384
+
}
7211
7385
7212
-
{
7213
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
7214
-
if err != nil {
7386
+
if !ok {
7387
+
// Field doesn't exist on this type, so ignore it
7388
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
7215
7389
return err
7216
7390
}
7217
-
7218
-
name = string(sval)
7391
+
continue
7219
7392
}
7220
7393
7221
-
switch name {
7394
+
switch string(nameBuf[:nameLen]) {
7222
7395
// t.Alt (string) (string)
7223
7396
case "alt":
7224
7397
···
7343
7516
7344
7517
default:
7345
7518
// Field doesn't exist on this type, so ignore it
7346
-
cbg.ScanForLinks(r, func(cid.Cid) {})
7519
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
7520
+
return err
7521
+
}
7347
7522
}
7348
7523
}
7349
7524
···
7425
7600
return fmt.Errorf("EmbedVideo_Caption: map struct too large (%d)", extra)
7426
7601
}
7427
7602
7428
-
var name string
7429
7603
n := extra
7430
7604
7605
+
nameBuf := make([]byte, 4)
7431
7606
for i := uint64(0); i < n; i++ {
7607
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
7608
+
if err != nil {
7609
+
return err
7610
+
}
7432
7611
7433
-
{
7434
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
7435
-
if err != nil {
7612
+
if !ok {
7613
+
// Field doesn't exist on this type, so ignore it
7614
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
7436
7615
return err
7437
7616
}
7438
-
7439
-
name = string(sval)
7617
+
continue
7440
7618
}
7441
7619
7442
-
switch name {
7620
+
switch string(nameBuf[:nameLen]) {
7443
7621
// t.File (util.LexBlob) (struct)
7444
7622
case "file":
7445
7623
···
7474
7652
7475
7653
default:
7476
7654
// Field doesn't exist on this type, so ignore it
7477
-
cbg.ScanForLinks(r, func(cid.Cid) {})
7655
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
7656
+
return err
7657
+
}
7478
7658
}
7479
7659
}
7480
7660
···
7656
7836
return fmt.Errorf("FeedPostgate: map struct too large (%d)", extra)
7657
7837
}
7658
7838
7659
-
var name string
7660
7839
n := extra
7661
7840
7841
+
nameBuf := make([]byte, 21)
7662
7842
for i := uint64(0); i < n; i++ {
7843
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
7844
+
if err != nil {
7845
+
return err
7846
+
}
7663
7847
7664
-
{
7665
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
7666
-
if err != nil {
7848
+
if !ok {
7849
+
// Field doesn't exist on this type, so ignore it
7850
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
7667
7851
return err
7668
7852
}
7669
-
7670
-
name = string(sval)
7853
+
continue
7671
7854
}
7672
7855
7673
-
switch name {
7856
+
switch string(nameBuf[:nameLen]) {
7674
7857
// t.Post (string) (string)
7675
7858
case "post":
7676
7859
···
7796
7979
7797
7980
default:
7798
7981
// Field doesn't exist on this type, so ignore it
7799
-
cbg.ScanForLinks(r, func(cid.Cid) {})
7982
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
7983
+
return err
7984
+
}
7800
7985
}
7801
7986
}
7802
7987
···
7858
8043
return fmt.Errorf("FeedPostgate_DisableRule: map struct too large (%d)", extra)
7859
8044
}
7860
8045
7861
-
var name string
7862
8046
n := extra
7863
8047
8048
+
nameBuf := make([]byte, 5)
7864
8049
for i := uint64(0); i < n; i++ {
8050
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
8051
+
if err != nil {
8052
+
return err
8053
+
}
7865
8054
7866
-
{
7867
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
7868
-
if err != nil {
8055
+
if !ok {
8056
+
// Field doesn't exist on this type, so ignore it
8057
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
7869
8058
return err
7870
8059
}
7871
-
7872
-
name = string(sval)
8060
+
continue
7873
8061
}
7874
8062
7875
-
switch name {
8063
+
switch string(nameBuf[:nameLen]) {
7876
8064
// t.LexiconTypeID (string) (string)
7877
8065
case "$type":
7878
8066
···
7887
8075
7888
8076
default:
7889
8077
// Field doesn't exist on this type, so ignore it
7890
-
cbg.ScanForLinks(r, func(cid.Cid) {})
8078
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
8079
+
return err
8080
+
}
7891
8081
}
7892
8082
}
7893
8083
+26
api/bsky/unspeccedgetConfig.go
+26
api/bsky/unspeccedgetConfig.go
···
1
+
// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2
+
3
+
package bsky
4
+
5
+
// schema: app.bsky.unspecced.getConfig
6
+
7
+
import (
8
+
"context"
9
+
10
+
"github.com/bluesky-social/indigo/xrpc"
11
+
)
12
+
13
+
// UnspeccedGetConfig_Output is the output of a app.bsky.unspecced.getConfig call.
14
+
type UnspeccedGetConfig_Output struct {
15
+
CheckEmailConfirmed *bool `json:"checkEmailConfirmed,omitempty" cborgen:"checkEmailConfirmed,omitempty"`
16
+
}
17
+
18
+
// UnspeccedGetConfig calls the XRPC method "app.bsky.unspecced.getConfig".
19
+
func UnspeccedGetConfig(ctx context.Context, c *xrpc.Client) (*UnspeccedGetConfig_Output, error) {
20
+
var out UnspeccedGetConfig_Output
21
+
if err := c.Do(ctx, xrpc.Query, "", "app.bsky.unspecced.getConfig", nil, nil, &out); err != nil {
22
+
return nil, err
23
+
}
24
+
25
+
return &out, nil
26
+
}
+13
-8
api/cbor_gen.go
+13
-8
api/cbor_gen.go
···
230
230
return fmt.Errorf("CreateOp: map struct too large (%d)", extra)
231
231
}
232
232
233
-
var name string
234
233
n := extra
235
234
235
+
nameBuf := make([]byte, 11)
236
236
for i := uint64(0); i < n; i++ {
237
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
238
+
if err != nil {
239
+
return err
240
+
}
237
241
238
-
{
239
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
240
-
if err != nil {
242
+
if !ok {
243
+
// Field doesn't exist on this type, so ignore it
244
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
241
245
return err
242
246
}
243
-
244
-
name = string(sval)
247
+
continue
245
248
}
246
249
247
-
switch name {
250
+
switch string(nameBuf[:nameLen]) {
248
251
// t.Sig (string) (string)
249
252
case "sig":
250
253
···
335
338
336
339
default:
337
340
// Field doesn't exist on this type, so ignore it
338
-
cbg.ScanForLinks(r, func(cid.Cid) {})
341
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
342
+
return err
343
+
}
339
344
}
340
345
}
341
346
+13
-8
api/chat/cbor_gen.go
+13
-8
api/chat/cbor_gen.go
···
97
97
return fmt.Errorf("ActorDeclaration: map struct too large (%d)", extra)
98
98
}
99
99
100
-
var name string
101
100
n := extra
102
101
102
+
nameBuf := make([]byte, 13)
103
103
for i := uint64(0); i < n; i++ {
104
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
105
+
if err != nil {
106
+
return err
107
+
}
104
108
105
-
{
106
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
107
-
if err != nil {
109
+
if !ok {
110
+
// Field doesn't exist on this type, so ignore it
111
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
108
112
return err
109
113
}
110
-
111
-
name = string(sval)
114
+
continue
112
115
}
113
116
114
-
switch name {
117
+
switch string(nameBuf[:nameLen]) {
115
118
// t.LexiconTypeID (string) (string)
116
119
case "$type":
117
120
···
137
140
138
141
default:
139
142
// Field doesn't exist on this type, so ignore it
140
-
cbg.ScanForLinks(r, func(cid.Cid) {})
143
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
144
+
return err
145
+
}
141
146
}
142
147
}
143
148
+27
-25
api/ozone/moderationdefs.go
+27
-25
api/ozone/moderationdefs.go
···
687
687
//
688
688
// RECORDTYPE: ModerationDefs_RepoView
689
689
type ModerationDefs_RepoView struct {
690
-
LexiconTypeID string `json:"$type,const=tools.ozone.moderation.defs#repoView" cborgen:"$type,const=tools.ozone.moderation.defs#repoView"`
691
-
DeactivatedAt *string `json:"deactivatedAt,omitempty" cborgen:"deactivatedAt,omitempty"`
692
-
Did string `json:"did" cborgen:"did"`
693
-
Email *string `json:"email,omitempty" cborgen:"email,omitempty"`
694
-
Handle string `json:"handle" cborgen:"handle"`
695
-
IndexedAt string `json:"indexedAt" cborgen:"indexedAt"`
696
-
InviteNote *string `json:"inviteNote,omitempty" cborgen:"inviteNote,omitempty"`
697
-
InvitedBy *comatprototypes.ServerDefs_InviteCode `json:"invitedBy,omitempty" cborgen:"invitedBy,omitempty"`
698
-
InvitesDisabled *bool `json:"invitesDisabled,omitempty" cborgen:"invitesDisabled,omitempty"`
699
-
Moderation *ModerationDefs_Moderation `json:"moderation" cborgen:"moderation"`
700
-
RelatedRecords []*util.LexiconTypeDecoder `json:"relatedRecords" cborgen:"relatedRecords"`
690
+
LexiconTypeID string `json:"$type,const=tools.ozone.moderation.defs#repoView" cborgen:"$type,const=tools.ozone.moderation.defs#repoView"`
691
+
DeactivatedAt *string `json:"deactivatedAt,omitempty" cborgen:"deactivatedAt,omitempty"`
692
+
Did string `json:"did" cborgen:"did"`
693
+
Email *string `json:"email,omitempty" cborgen:"email,omitempty"`
694
+
Handle string `json:"handle" cborgen:"handle"`
695
+
IndexedAt string `json:"indexedAt" cborgen:"indexedAt"`
696
+
InviteNote *string `json:"inviteNote,omitempty" cborgen:"inviteNote,omitempty"`
697
+
InvitedBy *comatprototypes.ServerDefs_InviteCode `json:"invitedBy,omitempty" cborgen:"invitedBy,omitempty"`
698
+
InvitesDisabled *bool `json:"invitesDisabled,omitempty" cborgen:"invitesDisabled,omitempty"`
699
+
Moderation *ModerationDefs_Moderation `json:"moderation" cborgen:"moderation"`
700
+
RelatedRecords []*util.LexiconTypeDecoder `json:"relatedRecords" cborgen:"relatedRecords"`
701
+
ThreatSignatures []*comatprototypes.AdminDefs_ThreatSignature `json:"threatSignatures,omitempty" cborgen:"threatSignatures,omitempty"`
701
702
}
702
703
703
704
// ModerationDefs_RepoViewDetail is a "repoViewDetail" in the tools.ozone.moderation.defs schema.
704
705
//
705
706
// RECORDTYPE: ModerationDefs_RepoViewDetail
706
707
type ModerationDefs_RepoViewDetail struct {
707
-
LexiconTypeID string `json:"$type,const=tools.ozone.moderation.defs#repoViewDetail" cborgen:"$type,const=tools.ozone.moderation.defs#repoViewDetail"`
708
-
DeactivatedAt *string `json:"deactivatedAt,omitempty" cborgen:"deactivatedAt,omitempty"`
709
-
Did string `json:"did" cborgen:"did"`
710
-
Email *string `json:"email,omitempty" cborgen:"email,omitempty"`
711
-
EmailConfirmedAt *string `json:"emailConfirmedAt,omitempty" cborgen:"emailConfirmedAt,omitempty"`
712
-
Handle string `json:"handle" cborgen:"handle"`
713
-
IndexedAt string `json:"indexedAt" cborgen:"indexedAt"`
714
-
InviteNote *string `json:"inviteNote,omitempty" cborgen:"inviteNote,omitempty"`
715
-
InvitedBy *comatprototypes.ServerDefs_InviteCode `json:"invitedBy,omitempty" cborgen:"invitedBy,omitempty"`
716
-
Invites []*comatprototypes.ServerDefs_InviteCode `json:"invites,omitempty" cborgen:"invites,omitempty"`
717
-
InvitesDisabled *bool `json:"invitesDisabled,omitempty" cborgen:"invitesDisabled,omitempty"`
718
-
Labels []*comatprototypes.LabelDefs_Label `json:"labels,omitempty" cborgen:"labels,omitempty"`
719
-
Moderation *ModerationDefs_ModerationDetail `json:"moderation" cborgen:"moderation"`
720
-
RelatedRecords []*util.LexiconTypeDecoder `json:"relatedRecords" cborgen:"relatedRecords"`
708
+
LexiconTypeID string `json:"$type,const=tools.ozone.moderation.defs#repoViewDetail" cborgen:"$type,const=tools.ozone.moderation.defs#repoViewDetail"`
709
+
DeactivatedAt *string `json:"deactivatedAt,omitempty" cborgen:"deactivatedAt,omitempty"`
710
+
Did string `json:"did" cborgen:"did"`
711
+
Email *string `json:"email,omitempty" cborgen:"email,omitempty"`
712
+
EmailConfirmedAt *string `json:"emailConfirmedAt,omitempty" cborgen:"emailConfirmedAt,omitempty"`
713
+
Handle string `json:"handle" cborgen:"handle"`
714
+
IndexedAt string `json:"indexedAt" cborgen:"indexedAt"`
715
+
InviteNote *string `json:"inviteNote,omitempty" cborgen:"inviteNote,omitempty"`
716
+
InvitedBy *comatprototypes.ServerDefs_InviteCode `json:"invitedBy,omitempty" cborgen:"invitedBy,omitempty"`
717
+
Invites []*comatprototypes.ServerDefs_InviteCode `json:"invites,omitempty" cborgen:"invites,omitempty"`
718
+
InvitesDisabled *bool `json:"invitesDisabled,omitempty" cborgen:"invitesDisabled,omitempty"`
719
+
Labels []*comatprototypes.LabelDefs_Label `json:"labels,omitempty" cborgen:"labels,omitempty"`
720
+
Moderation *ModerationDefs_ModerationDetail `json:"moderation" cborgen:"moderation"`
721
+
RelatedRecords []*util.LexiconTypeDecoder `json:"relatedRecords" cborgen:"relatedRecords"`
722
+
ThreatSignatures []*comatprototypes.AdminDefs_ThreatSignature `json:"threatSignatures,omitempty" cborgen:"threatSignatures,omitempty"`
721
723
}
722
724
723
725
// ModerationDefs_RepoViewNotFound is a "repoViewNotFound" in the tools.ozone.moderation.defs schema.
+6
-2
api/ozone/moderationqueryEvents.go
+6
-2
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
+
// 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.
23
24
// comment: If specified, only events with comments containing the keyword are returned
24
25
// createdAfter: Retrieve events created after a given timestamp
25
26
// createdBefore: Retrieve events created before a given timestamp
26
27
// hasComment: If true, only events with comments are returned
27
-
// includeAllUserRecords: If true, events on all record types (posts, lists, profile etc.) owned by the did are returned
28
+
// includeAllUserRecords: If true, events on all record types (posts, lists, profile etc.) or records from given 'collections' param, owned by the did are returned.
28
29
// removedLabels: If specified, only events where all of these labels were removed are returned
29
30
// removedTags: If specified, only events where all of these tags were removed are returned
30
31
// sortDirection: Sort direction for the events. Defaults to descending order of created at timestamp.
32
+
// 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.
31
33
// 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.
32
-
func ModerationQueryEvents(ctx context.Context, c *xrpc.Client, addedLabels []string, addedTags []string, comment string, createdAfter string, createdBefore string, createdBy string, cursor string, hasComment bool, includeAllUserRecords bool, limit int64, removedLabels []string, removedTags []string, reportTypes []string, sortDirection string, subject string, types []string) (*ModerationQueryEvents_Output, error) {
34
+
func ModerationQueryEvents(ctx context.Context, c *xrpc.Client, addedLabels []string, addedTags []string, collections []string, comment string, createdAfter string, createdBefore string, createdBy string, cursor string, hasComment bool, includeAllUserRecords bool, limit int64, removedLabels []string, removedTags []string, reportTypes []string, sortDirection string, subject string, subjectType string, types []string) (*ModerationQueryEvents_Output, error) {
33
35
var out ModerationQueryEvents_Output
34
36
35
37
params := map[string]interface{}{
36
38
"addedLabels": addedLabels,
37
39
"addedTags": addedTags,
40
+
"collections": collections,
38
41
"comment": comment,
39
42
"createdAfter": createdAfter,
40
43
"createdBefore": createdBefore,
···
48
51
"reportTypes": reportTypes,
49
52
"sortDirection": sortDirection,
50
53
"subject": subject,
54
+
"subjectType": subjectType,
51
55
"types": types,
52
56
}
53
57
if err := c.Do(ctx, xrpc.Query, "", "tools.ozone.moderation.queryEvents", params, nil, &out); err != nil {
+6
-2
api/ozone/moderationqueryStatuses.go
+6
-2
api/ozone/moderationqueryStatuses.go
···
19
19
// ModerationQueryStatuses calls the XRPC method "tools.ozone.moderation.queryStatuses".
20
20
//
21
21
// appealed: Get subjects in unresolved appealed status
22
+
// collections: If specified, subjects belonging to the given collections will be returned. When subjectType is set to 'account', this will be ignored.
22
23
// comment: Search subjects by keyword from comments
23
-
// includeAllUserRecords: All subjects belonging to the account specified in the 'subject' param will be returned.
24
+
// includeAllUserRecords: All subjects, or subjects from given 'collections' param, belonging to the account specified in the 'subject' param will be returned.
24
25
// includeMuted: By default, we don't include muted subjects in the results. Set this to true to include them.
25
26
// lastReviewedBy: Get all subject statuses that were reviewed by a specific moderator
26
27
// onlyMuted: When set to true, only muted subjects and reporters will be returned.
···
30
31
// reviewedAfter: Search subjects reviewed after a given timestamp
31
32
// reviewedBefore: Search subjects reviewed before a given timestamp
32
33
// subject: The subject to get the status for.
34
+
// 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.
33
35
// takendown: Get subjects that were taken down
34
-
func ModerationQueryStatuses(ctx context.Context, c *xrpc.Client, appealed bool, comment string, cursor string, excludeTags []string, ignoreSubjects []string, includeAllUserRecords bool, includeMuted bool, lastReviewedBy string, limit int64, onlyMuted bool, reportedAfter string, reportedBefore string, reviewState string, reviewedAfter string, reviewedBefore string, sortDirection string, sortField string, subject string, tags []string, takendown bool) (*ModerationQueryStatuses_Output, error) {
36
+
func ModerationQueryStatuses(ctx context.Context, c *xrpc.Client, appealed bool, collections []string, comment string, cursor string, excludeTags []string, ignoreSubjects []string, includeAllUserRecords bool, includeMuted bool, lastReviewedBy string, limit int64, onlyMuted bool, 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) {
35
37
var out ModerationQueryStatuses_Output
36
38
37
39
params := map[string]interface{}{
38
40
"appealed": appealed,
41
+
"collections": collections,
39
42
"comment": comment,
40
43
"cursor": cursor,
41
44
"excludeTags": excludeTags,
···
53
56
"sortDirection": sortDirection,
54
57
"sortField": sortField,
55
58
"subject": subject,
59
+
"subjectType": subjectType,
56
60
"tags": tags,
57
61
"takendown": takendown,
58
62
}
+28
api/ozone/setaddValues.go
+28
api/ozone/setaddValues.go
···
1
+
// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2
+
3
+
package ozone
4
+
5
+
// schema: tools.ozone.set.addValues
6
+
7
+
import (
8
+
"context"
9
+
10
+
"github.com/bluesky-social/indigo/xrpc"
11
+
)
12
+
13
+
// SetAddValues_Input is the input argument to a tools.ozone.set.addValues call.
14
+
type SetAddValues_Input struct {
15
+
// name: Name of the set to add values to
16
+
Name string `json:"name" cborgen:"name"`
17
+
// values: Array of string values to add to the set
18
+
Values []string `json:"values" cborgen:"values"`
19
+
}
20
+
21
+
// SetAddValues calls the XRPC method "tools.ozone.set.addValues".
22
+
func SetAddValues(ctx context.Context, c *xrpc.Client, input *SetAddValues_Input) error {
23
+
if err := c.Do(ctx, xrpc.Procedure, "application/json", "tools.ozone.set.addValues", nil, input, nil); err != nil {
24
+
return err
25
+
}
26
+
27
+
return nil
28
+
}
+20
api/ozone/setdefs.go
+20
api/ozone/setdefs.go
···
1
+
// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2
+
3
+
package ozone
4
+
5
+
// schema: tools.ozone.set.defs
6
+
7
+
// SetDefs_Set is a "set" in the tools.ozone.set.defs schema.
8
+
type SetDefs_Set struct {
9
+
Description *string `json:"description,omitempty" cborgen:"description,omitempty"`
10
+
Name string `json:"name" cborgen:"name"`
11
+
}
12
+
13
+
// SetDefs_SetView is a "setView" in the tools.ozone.set.defs schema.
14
+
type SetDefs_SetView struct {
15
+
CreatedAt string `json:"createdAt" cborgen:"createdAt"`
16
+
Description *string `json:"description,omitempty" cborgen:"description,omitempty"`
17
+
Name string `json:"name" cborgen:"name"`
18
+
SetSize int64 `json:"setSize" cborgen:"setSize"`
19
+
UpdatedAt string `json:"updatedAt" cborgen:"updatedAt"`
20
+
}
+31
api/ozone/setdeleteSet.go
+31
api/ozone/setdeleteSet.go
···
1
+
// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2
+
3
+
package ozone
4
+
5
+
// schema: tools.ozone.set.deleteSet
6
+
7
+
import (
8
+
"context"
9
+
10
+
"github.com/bluesky-social/indigo/xrpc"
11
+
)
12
+
13
+
// SetDeleteSet_Input is the input argument to a tools.ozone.set.deleteSet call.
14
+
type SetDeleteSet_Input struct {
15
+
// name: Name of the set to delete
16
+
Name string `json:"name" cborgen:"name"`
17
+
}
18
+
19
+
// SetDeleteSet_Output is the output of a tools.ozone.set.deleteSet call.
20
+
type SetDeleteSet_Output struct {
21
+
}
22
+
23
+
// SetDeleteSet calls the XRPC method "tools.ozone.set.deleteSet".
24
+
func SetDeleteSet(ctx context.Context, c *xrpc.Client, input *SetDeleteSet_Input) (*SetDeleteSet_Output, error) {
25
+
var out SetDeleteSet_Output
26
+
if err := c.Do(ctx, xrpc.Procedure, "application/json", "tools.ozone.set.deleteSet", nil, input, &out); err != nil {
27
+
return nil, err
28
+
}
29
+
30
+
return &out, nil
31
+
}
+28
api/ozone/setdeleteValues.go
+28
api/ozone/setdeleteValues.go
···
1
+
// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2
+
3
+
package ozone
4
+
5
+
// schema: tools.ozone.set.deleteValues
6
+
7
+
import (
8
+
"context"
9
+
10
+
"github.com/bluesky-social/indigo/xrpc"
11
+
)
12
+
13
+
// SetDeleteValues_Input is the input argument to a tools.ozone.set.deleteValues call.
14
+
type SetDeleteValues_Input struct {
15
+
// name: Name of the set to delete values from
16
+
Name string `json:"name" cborgen:"name"`
17
+
// values: Array of string values to delete from the set
18
+
Values []string `json:"values" cborgen:"values"`
19
+
}
20
+
21
+
// SetDeleteValues calls the XRPC method "tools.ozone.set.deleteValues".
22
+
func SetDeleteValues(ctx context.Context, c *xrpc.Client, input *SetDeleteValues_Input) error {
23
+
if err := c.Do(ctx, xrpc.Procedure, "application/json", "tools.ozone.set.deleteValues", nil, input, nil); err != nil {
24
+
return err
25
+
}
26
+
27
+
return nil
28
+
}
+34
api/ozone/setgetValues.go
+34
api/ozone/setgetValues.go
···
1
+
// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2
+
3
+
package ozone
4
+
5
+
// schema: tools.ozone.set.getValues
6
+
7
+
import (
8
+
"context"
9
+
10
+
"github.com/bluesky-social/indigo/xrpc"
11
+
)
12
+
13
+
// SetGetValues_Output is the output of a tools.ozone.set.getValues call.
14
+
type SetGetValues_Output struct {
15
+
Cursor *string `json:"cursor,omitempty" cborgen:"cursor,omitempty"`
16
+
Set *SetDefs_SetView `json:"set" cborgen:"set"`
17
+
Values []string `json:"values" cborgen:"values"`
18
+
}
19
+
20
+
// SetGetValues calls the XRPC method "tools.ozone.set.getValues".
21
+
func SetGetValues(ctx context.Context, c *xrpc.Client, cursor string, limit int64, name string) (*SetGetValues_Output, error) {
22
+
var out SetGetValues_Output
23
+
24
+
params := map[string]interface{}{
25
+
"cursor": cursor,
26
+
"limit": limit,
27
+
"name": name,
28
+
}
29
+
if err := c.Do(ctx, xrpc.Query, "", "tools.ozone.set.getValues", params, nil, &out); err != nil {
30
+
return nil, err
31
+
}
32
+
33
+
return &out, nil
34
+
}
+37
api/ozone/setquerySets.go
+37
api/ozone/setquerySets.go
···
1
+
// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2
+
3
+
package ozone
4
+
5
+
// schema: tools.ozone.set.querySets
6
+
7
+
import (
8
+
"context"
9
+
10
+
"github.com/bluesky-social/indigo/xrpc"
11
+
)
12
+
13
+
// SetQuerySets_Output is the output of a tools.ozone.set.querySets call.
14
+
type SetQuerySets_Output struct {
15
+
Cursor *string `json:"cursor,omitempty" cborgen:"cursor,omitempty"`
16
+
Sets []*SetDefs_SetView `json:"sets" cborgen:"sets"`
17
+
}
18
+
19
+
// SetQuerySets calls the XRPC method "tools.ozone.set.querySets".
20
+
//
21
+
// sortDirection: Defaults to ascending order of name field.
22
+
func SetQuerySets(ctx context.Context, c *xrpc.Client, cursor string, limit int64, namePrefix string, sortBy string, sortDirection string) (*SetQuerySets_Output, error) {
23
+
var out SetQuerySets_Output
24
+
25
+
params := map[string]interface{}{
26
+
"cursor": cursor,
27
+
"limit": limit,
28
+
"namePrefix": namePrefix,
29
+
"sortBy": sortBy,
30
+
"sortDirection": sortDirection,
31
+
}
32
+
if err := c.Do(ctx, xrpc.Query, "", "tools.ozone.set.querySets", params, nil, &out); err != nil {
33
+
return nil, err
34
+
}
35
+
36
+
return &out, nil
37
+
}
+21
api/ozone/setupsertSet.go
+21
api/ozone/setupsertSet.go
···
1
+
// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2
+
3
+
package ozone
4
+
5
+
// schema: tools.ozone.set.upsertSet
6
+
7
+
import (
8
+
"context"
9
+
10
+
"github.com/bluesky-social/indigo/xrpc"
11
+
)
12
+
13
+
// SetUpsertSet calls the XRPC method "tools.ozone.set.upsertSet".
14
+
func SetUpsertSet(ctx context.Context, c *xrpc.Client, input *SetDefs_Set) (*SetDefs_SetView, error) {
15
+
var out SetDefs_SetView
16
+
if err := c.Do(ctx, xrpc.Procedure, "application/json", "tools.ozone.set.upsertSet", nil, input, &out); err != nil {
17
+
return nil, err
18
+
}
19
+
20
+
return &out, nil
21
+
}
+39
-24
atproto/data/cbor_gen.go
+39
-24
atproto/data/cbor_gen.go
···
78
78
return fmt.Errorf("GenericRecord: map struct too large (%d)", extra)
79
79
}
80
80
81
-
var name string
82
81
n := extra
83
82
83
+
nameBuf := make([]byte, 5)
84
84
for i := uint64(0); i < n; i++ {
85
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
86
+
if err != nil {
87
+
return err
88
+
}
85
89
86
-
{
87
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
88
-
if err != nil {
90
+
if !ok {
91
+
// Field doesn't exist on this type, so ignore it
92
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
89
93
return err
90
94
}
91
-
92
-
name = string(sval)
95
+
continue
93
96
}
94
97
95
-
switch name {
98
+
switch string(nameBuf[:nameLen]) {
96
99
// t.Type (string) (string)
97
100
case "$type":
98
101
···
107
110
108
111
default:
109
112
// Field doesn't exist on this type, so ignore it
110
-
cbg.ScanForLinks(r, func(cid.Cid) {})
113
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
114
+
return err
115
+
}
111
116
}
112
117
}
113
118
···
196
201
return fmt.Errorf("LegacyBlobSchema: map struct too large (%d)", extra)
197
202
}
198
203
199
-
var name string
200
204
n := extra
201
205
206
+
nameBuf := make([]byte, 8)
202
207
for i := uint64(0); i < n; i++ {
208
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
209
+
if err != nil {
210
+
return err
211
+
}
203
212
204
-
{
205
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
206
-
if err != nil {
213
+
if !ok {
214
+
// Field doesn't exist on this type, so ignore it
215
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
207
216
return err
208
217
}
209
-
210
-
name = string(sval)
218
+
continue
211
219
}
212
220
213
-
switch name {
221
+
switch string(nameBuf[:nameLen]) {
214
222
// t.Cid (string) (string)
215
223
case "cid":
216
224
···
236
244
237
245
default:
238
246
// Field doesn't exist on this type, so ignore it
239
-
cbg.ScanForLinks(r, func(cid.Cid) {})
247
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
248
+
return err
249
+
}
240
250
}
241
251
}
242
252
···
359
369
return fmt.Errorf("BlobSchema: map struct too large (%d)", extra)
360
370
}
361
371
362
-
var name string
363
372
n := extra
364
373
374
+
nameBuf := make([]byte, 8)
365
375
for i := uint64(0); i < n; i++ {
376
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
377
+
if err != nil {
378
+
return err
379
+
}
366
380
367
-
{
368
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
369
-
if err != nil {
381
+
if !ok {
382
+
// Field doesn't exist on this type, so ignore it
383
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
370
384
return err
371
385
}
372
-
373
-
name = string(sval)
386
+
continue
374
387
}
375
388
376
-
switch name {
389
+
switch string(nameBuf[:nameLen]) {
377
390
// t.Ref (data.CIDLink) (struct)
378
391
case "ref":
379
392
···
435
448
436
449
default:
437
450
// Field doesn't exist on this type, so ignore it
438
-
cbg.ScanForLinks(r, func(cid.Cid) {})
451
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
452
+
return err
453
+
}
439
454
}
440
455
}
441
456
+7
-2
atproto/identity/identity.go
+7
-2
atproto/identity/identity.go
···
65
65
base := BaseDirectory{
66
66
PLCURL: DefaultPLCURL,
67
67
HTTPClient: http.Client{
68
-
Timeout: time.Second * 15,
68
+
Timeout: time.Second * 10,
69
+
Transport: &http.Transport{
70
+
// would want this around 100ms for services doing lots of handle resolution. Impacts PLC connections as well, but not too bad.
71
+
IdleConnTimeout: time.Millisecond * 1000,
72
+
MaxIdleConns: 100,
73
+
},
69
74
},
70
75
Resolver: net.Resolver{
71
76
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
72
-
d := net.Dialer{Timeout: time.Second * 5}
77
+
d := net.Dialer{Timeout: time.Second * 3}
73
78
return d.DialContext(ctx, network, address)
74
79
},
75
80
},
+9
-1
automod/capture/testing.go
+9
-1
automod/capture/testing.go
···
7
7
"io"
8
8
"os"
9
9
10
+
comatproto "github.com/bluesky-social/indigo/api/atproto"
10
11
"github.com/bluesky-social/indigo/atproto/identity"
11
12
"github.com/bluesky-social/indigo/atproto/syntax"
12
13
"github.com/bluesky-social/indigo/automod"
···
38
39
ctx := context.Background()
39
40
40
41
did := capture.AccountMeta.Identity.DID
42
+
handle := capture.AccountMeta.Identity.Handle.String()
41
43
dir := identity.NewMockDirectory()
42
44
dir.Insert(*capture.AccountMeta.Identity)
43
45
eng.Directory = &dir
44
46
45
47
// initial identity rules
46
-
eng.ProcessIdentityEvent(ctx, "new", did)
48
+
identEvent := comatproto.SyncSubscribeRepos_Identity{
49
+
Did: did.String(),
50
+
Handle: &handle,
51
+
Seq: 12345,
52
+
Time: syntax.DatetimeNow().String(),
53
+
}
54
+
eng.ProcessIdentityEvent(ctx, identEvent)
47
55
48
56
// all the post rules
49
57
for _, pr := range capture.PostRecords {
+2
automod/consumer/doc.go
+2
automod/consumer/doc.go
+273
automod/consumer/firehose.go
+273
automod/consumer/firehose.go
···
1
+
package consumer
2
+
3
+
import (
4
+
"bytes"
5
+
"context"
6
+
"fmt"
7
+
"log/slog"
8
+
"net/http"
9
+
"net/url"
10
+
"sync/atomic"
11
+
"time"
12
+
13
+
comatproto "github.com/bluesky-social/indigo/api/atproto"
14
+
"github.com/bluesky-social/indigo/atproto/syntax"
15
+
"github.com/bluesky-social/indigo/automod"
16
+
"github.com/bluesky-social/indigo/events/schedulers/autoscaling"
17
+
"github.com/bluesky-social/indigo/events/schedulers/parallel"
18
+
lexutil "github.com/bluesky-social/indigo/lex/util"
19
+
20
+
"github.com/bluesky-social/indigo/events"
21
+
"github.com/bluesky-social/indigo/repo"
22
+
"github.com/bluesky-social/indigo/repomgr"
23
+
"github.com/carlmjohnson/versioninfo"
24
+
"github.com/gorilla/websocket"
25
+
"github.com/redis/go-redis/v9"
26
+
)
27
+
28
+
// TODO: should probably make this not hepa-specific; or even configurable
29
+
var firehoseCursorKey = "hepa/seq"
30
+
31
+
type FirehoseConsumer struct {
32
+
Parallelism int
33
+
Logger *slog.Logger
34
+
RedisClient *redis.Client
35
+
Engine *automod.Engine
36
+
Host string
37
+
38
+
// TODO: prefilter record collections; or predicate function?
39
+
// TODO: enable/disable event types; or predicate function?
40
+
41
+
// lastSeq is the most recent event sequence number we've received and begun to handle.
42
+
// This number is periodically persisted to redis, if redis is present.
43
+
// The value is best-effort (the stream handling itself is concurrent, so event numbers may not be monotonic),
44
+
// but nonetheless, you must use atomics when updating or reading this (to avoid data races).
45
+
lastSeq int64
46
+
}
47
+
48
+
func (fc *FirehoseConsumer) Run(ctx context.Context) error {
49
+
50
+
if fc.Engine == nil {
51
+
return fmt.Errorf("nil engine")
52
+
}
53
+
54
+
cur, err := fc.ReadLastCursor(ctx)
55
+
if err != nil {
56
+
return err
57
+
}
58
+
59
+
dialer := websocket.DefaultDialer
60
+
u, err := url.Parse(fc.Host)
61
+
if err != nil {
62
+
return fmt.Errorf("invalid Host URI: %w", err)
63
+
}
64
+
u.Path = "xrpc/com.atproto.sync.subscribeRepos"
65
+
if cur != 0 {
66
+
u.RawQuery = fmt.Sprintf("cursor=%d", cur)
67
+
}
68
+
fc.Logger.Info("subscribing to repo event stream", "upstream", fc.Host, "cursor", cur)
69
+
con, _, err := dialer.Dial(u.String(), http.Header{
70
+
"User-Agent": []string{fmt.Sprintf("hepa/%s", versioninfo.Short())},
71
+
})
72
+
if err != nil {
73
+
return fmt.Errorf("subscribing to firehose failed (dialing): %w", err)
74
+
}
75
+
76
+
rsc := &events.RepoStreamCallbacks{
77
+
RepoCommit: func(evt *comatproto.SyncSubscribeRepos_Commit) error {
78
+
atomic.StoreInt64(&fc.lastSeq, evt.Seq)
79
+
return fc.HandleRepoCommit(ctx, evt)
80
+
},
81
+
RepoIdentity: func(evt *comatproto.SyncSubscribeRepos_Identity) error {
82
+
atomic.StoreInt64(&fc.lastSeq, evt.Seq)
83
+
if err := fc.Engine.ProcessIdentityEvent(ctx, *evt); err != nil {
84
+
fc.Logger.Error("processing repo identity failed", "did", evt.Did, "seq", evt.Seq, "err", err)
85
+
}
86
+
return nil
87
+
},
88
+
RepoAccount: func(evt *comatproto.SyncSubscribeRepos_Account) error {
89
+
atomic.StoreInt64(&fc.lastSeq, evt.Seq)
90
+
if err := fc.Engine.ProcessAccountEvent(ctx, *evt); err != nil {
91
+
fc.Logger.Error("processing repo account failed", "did", evt.Did, "seq", evt.Seq, "err", err)
92
+
}
93
+
return nil
94
+
},
95
+
// NOTE: no longer process #handle events
96
+
// NOTE: no longer process #tombstone events
97
+
}
98
+
99
+
var scheduler events.Scheduler
100
+
if fc.Parallelism > 0 {
101
+
// use a fixed-parallelism scheduler if configured
102
+
scheduler = parallel.NewScheduler(
103
+
fc.Parallelism,
104
+
1000,
105
+
fc.Host,
106
+
rsc.EventHandler,
107
+
)
108
+
fc.Logger.Info("hepa scheduler configured", "scheduler", "parallel", "initial", fc.Parallelism)
109
+
} else {
110
+
// otherwise use auto-scaling scheduler
111
+
scaleSettings := autoscaling.DefaultAutoscaleSettings()
112
+
// start at higher parallelism (somewhat arbitrary)
113
+
scaleSettings.Concurrency = 4
114
+
scaleSettings.MaxConcurrency = 200
115
+
scheduler = autoscaling.NewScheduler(scaleSettings, fc.Host, rsc.EventHandler)
116
+
fc.Logger.Info("hepa scheduler configured", "scheduler", "autoscaling", "initial", scaleSettings.Concurrency, "max", scaleSettings.MaxConcurrency)
117
+
}
118
+
119
+
return events.HandleRepoStream(ctx, con, scheduler)
120
+
}
121
+
122
+
// NOTE: for now, this function basically never errors, just logs and returns nil. Should think through error processing better.
123
+
func (fc *FirehoseConsumer) HandleRepoCommit(ctx context.Context, evt *comatproto.SyncSubscribeRepos_Commit) error {
124
+
125
+
logger := fc.Logger.With("event", "commit", "did", evt.Repo, "rev", evt.Rev, "seq", evt.Seq)
126
+
logger.Debug("received commit event")
127
+
128
+
if evt.TooBig {
129
+
logger.Warn("skipping tooBig events for now")
130
+
return nil
131
+
}
132
+
133
+
did, err := syntax.ParseDID(evt.Repo)
134
+
if err != nil {
135
+
logger.Error("bad DID syntax in event", "err", err)
136
+
return nil
137
+
}
138
+
139
+
rr, err := repo.ReadRepoFromCar(ctx, bytes.NewReader(evt.Blocks))
140
+
if err != nil {
141
+
logger.Error("failed to read repo from car", "err", err)
142
+
return nil
143
+
}
144
+
145
+
for _, op := range evt.Ops {
146
+
logger = logger.With("eventKind", op.Action, "path", op.Path)
147
+
collection, rkey, err := splitRepoPath(op.Path)
148
+
if err != nil {
149
+
logger.Error("invalid path in repo op")
150
+
return nil
151
+
}
152
+
153
+
ek := repomgr.EventKind(op.Action)
154
+
switch ek {
155
+
case repomgr.EvtKindCreateRecord, repomgr.EvtKindUpdateRecord:
156
+
// read the record bytes from blocks, and verify CID
157
+
rc, recCBOR, err := rr.GetRecordBytes(ctx, op.Path)
158
+
if err != nil {
159
+
logger.Error("reading record from event blocks (CAR)", "err", err)
160
+
break
161
+
}
162
+
if op.Cid == nil || lexutil.LexLink(rc) != *op.Cid {
163
+
logger.Error("mismatch between commit op CID and record block", "recordCID", rc, "opCID", op.Cid)
164
+
break
165
+
}
166
+
var action string
167
+
switch ek {
168
+
case repomgr.EvtKindCreateRecord:
169
+
action = automod.CreateOp
170
+
case repomgr.EvtKindUpdateRecord:
171
+
action = automod.UpdateOp
172
+
default:
173
+
logger.Error("impossible event kind", "kind", ek)
174
+
break
175
+
}
176
+
recCID := syntax.CID(op.Cid.String())
177
+
op := automod.RecordOp{
178
+
Action: action,
179
+
DID: did,
180
+
Collection: collection,
181
+
RecordKey: rkey,
182
+
CID: &recCID,
183
+
RecordCBOR: *recCBOR,
184
+
}
185
+
err = fc.Engine.ProcessRecordOp(ctx, op)
186
+
if err != nil {
187
+
logger.Error("engine failed to process record", "err", err)
188
+
continue
189
+
}
190
+
case repomgr.EvtKindDeleteRecord:
191
+
op := automod.RecordOp{
192
+
Action: automod.DeleteOp,
193
+
DID: did,
194
+
Collection: collection,
195
+
RecordKey: rkey,
196
+
CID: nil,
197
+
RecordCBOR: nil,
198
+
}
199
+
err = fc.Engine.ProcessRecordOp(ctx, op)
200
+
if err != nil {
201
+
logger.Error("engine failed to process record", "err", err)
202
+
continue
203
+
}
204
+
default:
205
+
// TODO: should this be an error?
206
+
}
207
+
}
208
+
209
+
return nil
210
+
}
211
+
212
+
func (fc *FirehoseConsumer) ReadLastCursor(ctx context.Context) (int64, error) {
213
+
// if redis isn't configured, just skip
214
+
if fc.RedisClient == nil {
215
+
fc.Logger.Info("redis not configured, skipping cursor read")
216
+
return 0, nil
217
+
}
218
+
219
+
val, err := fc.RedisClient.Get(ctx, firehoseCursorKey).Int64()
220
+
if err == redis.Nil {
221
+
fc.Logger.Info("no pre-existing cursor in redis")
222
+
return 0, nil
223
+
} else if err != nil {
224
+
return 0, err
225
+
}
226
+
fc.Logger.Info("successfully found prior subscription cursor seq in redis", "seq", val)
227
+
return val, nil
228
+
}
229
+
230
+
func (fc *FirehoseConsumer) PersistCursor(ctx context.Context) error {
231
+
// if redis isn't configured, just skip
232
+
if fc.RedisClient == nil {
233
+
return nil
234
+
}
235
+
lastSeq := atomic.LoadInt64(&fc.lastSeq)
236
+
if lastSeq <= 0 {
237
+
return nil
238
+
}
239
+
err := fc.RedisClient.Set(ctx, firehoseCursorKey, lastSeq, 14*24*time.Hour).Err()
240
+
return err
241
+
}
242
+
243
+
// this method runs in a loop, persisting the current cursor state every 5 seconds
244
+
func (fc *FirehoseConsumer) RunPersistCursor(ctx context.Context) error {
245
+
246
+
// if redis isn't configured, just skip
247
+
if fc.RedisClient == nil {
248
+
return nil
249
+
}
250
+
ticker := time.NewTicker(5 * time.Second)
251
+
for {
252
+
select {
253
+
case <-ctx.Done():
254
+
lastSeq := atomic.LoadInt64(&fc.lastSeq)
255
+
if lastSeq >= 1 {
256
+
fc.Logger.Info("persisting final cursor seq value", "seq", lastSeq)
257
+
err := fc.PersistCursor(ctx)
258
+
if err != nil {
259
+
fc.Logger.Error("failed to persist cursor", "err", err, "seq", lastSeq)
260
+
}
261
+
}
262
+
return nil
263
+
case <-ticker.C:
264
+
lastSeq := atomic.LoadInt64(&fc.lastSeq)
265
+
if lastSeq >= 1 {
266
+
err := fc.PersistCursor(ctx)
267
+
if err != nil {
268
+
fc.Logger.Error("failed to persist cursor", "err", err, "seq", lastSeq)
269
+
}
270
+
}
271
+
}
272
+
}
273
+
}
+187
automod/consumer/ozone.go
+187
automod/consumer/ozone.go
···
1
+
package consumer
2
+
3
+
import (
4
+
"context"
5
+
"fmt"
6
+
"log/slog"
7
+
"sync/atomic"
8
+
"time"
9
+
10
+
toolsozone "github.com/bluesky-social/indigo/api/ozone"
11
+
"github.com/bluesky-social/indigo/atproto/syntax"
12
+
"github.com/bluesky-social/indigo/automod"
13
+
"github.com/bluesky-social/indigo/xrpc"
14
+
15
+
"github.com/redis/go-redis/v9"
16
+
)
17
+
18
+
// TODO: should probably make this not hepa-specific; or even configurable
19
+
var ozoneCursorKey = "hepa/ozoneTimestamp"
20
+
21
+
type OzoneConsumer struct {
22
+
Logger *slog.Logger
23
+
RedisClient *redis.Client
24
+
OzoneClient *xrpc.Client
25
+
Engine *automod.Engine
26
+
27
+
// same as lastSeq, but for Ozone timestamp cursor. the value is a string.
28
+
lastCursor atomic.Value
29
+
}
30
+
31
+
func (oc *OzoneConsumer) Run(ctx context.Context) error {
32
+
33
+
if oc.Engine == nil {
34
+
return fmt.Errorf("nil engine")
35
+
}
36
+
if oc.OzoneClient == nil {
37
+
return fmt.Errorf("nil ozoneclient")
38
+
}
39
+
40
+
cur, err := oc.ReadLastCursor(ctx)
41
+
if err != nil {
42
+
return err
43
+
}
44
+
45
+
if cur == "" {
46
+
cur = syntax.DatetimeNow().String()
47
+
}
48
+
since, err := syntax.ParseDatetime(cur)
49
+
if err != nil {
50
+
return err
51
+
}
52
+
53
+
oc.Logger.Info("subscribing to ozone event log", "upstream", oc.OzoneClient.Host, "cursor", cur, "since", since)
54
+
var limit int64 = 50
55
+
period := time.Second * 5
56
+
57
+
for {
58
+
me, err := toolsozone.ModerationQueryEvents(
59
+
ctx,
60
+
oc.OzoneClient,
61
+
nil, // addedLabels []string
62
+
nil, // addedTags []string
63
+
nil, // collections []string
64
+
"", // comment string
65
+
since.String(), // createdAfter string
66
+
"", // createdBefore string
67
+
"", // createdBy string
68
+
"", // cursor string
69
+
false, // hasComment bool
70
+
true, // includeAllUserRecords bool
71
+
limit, // limit int64
72
+
nil, // removedLabels []string
73
+
nil, // removedTags []string
74
+
nil, // reportTypes []string
75
+
"asc", // sortDirection string
76
+
"", // subject string
77
+
"", // subjectType string
78
+
nil, // types []string
79
+
)
80
+
if err != nil {
81
+
oc.Logger.Warn("ozone query events failed; sleeping then will retrying", "err", err, "period", period.String())
82
+
time.Sleep(period)
83
+
continue
84
+
}
85
+
86
+
// track if the response contained anything new
87
+
anyNewEvents := false
88
+
for _, evt := range me.Events {
89
+
createdAt, err := syntax.ParseDatetime(evt.CreatedAt)
90
+
if err != nil {
91
+
return fmt.Errorf("invalid time format for ozone 'createdAt': %w", err)
92
+
}
93
+
// skip if the timestamp is the exact same
94
+
if createdAt == since {
95
+
continue
96
+
}
97
+
anyNewEvents = true
98
+
// TODO: is there a race condition here?
99
+
if !createdAt.Time().After(since.Time()) {
100
+
oc.Logger.Error("out of order ozone event", "createdAt", createdAt, "since", since)
101
+
return fmt.Errorf("out of order ozone event")
102
+
}
103
+
if err = oc.HandleOzoneEvent(ctx, evt); err != nil {
104
+
oc.Logger.Error("failed to process ozone event", "event", evt)
105
+
}
106
+
since = createdAt
107
+
oc.lastCursor.Store(since.String())
108
+
}
109
+
if !anyNewEvents {
110
+
oc.Logger.Debug("... ozone poller sleeping", "period", period.String())
111
+
time.Sleep(period)
112
+
}
113
+
}
114
+
}
115
+
116
+
func (oc *OzoneConsumer) HandleOzoneEvent(ctx context.Context, eventView *toolsozone.ModerationDefs_ModEventView) error {
117
+
118
+
oc.Logger.Debug("received ozone event", "eventID", eventView.Id, "createdAt", eventView.CreatedAt)
119
+
120
+
if err := oc.Engine.ProcessOzoneEvent(ctx, eventView); err != nil {
121
+
oc.Logger.Error("engine failed to process ozone event", "err", err)
122
+
}
123
+
return nil
124
+
}
125
+
126
+
func (oc *OzoneConsumer) ReadLastCursor(ctx context.Context) (string, error) {
127
+
// if redis isn't configured, just skip
128
+
if oc.RedisClient == nil {
129
+
oc.Logger.Info("redis not configured, skipping ozone cursor read")
130
+
return "", nil
131
+
}
132
+
133
+
val, err := oc.RedisClient.Get(ctx, ozoneCursorKey).Result()
134
+
if err == redis.Nil || val == "" {
135
+
oc.Logger.Info("no pre-existing ozone cursor in redis")
136
+
return "", nil
137
+
} else if err != nil {
138
+
return "", err
139
+
}
140
+
oc.Logger.Info("successfully found prior ozone offset timestamp in redis", "cursor", val)
141
+
return val, nil
142
+
}
143
+
144
+
func (oc *OzoneConsumer) PersistCursor(ctx context.Context) error {
145
+
// if redis isn't configured, just skip
146
+
if oc.RedisClient == nil {
147
+
return nil
148
+
}
149
+
lastCursor := oc.lastCursor.Load()
150
+
if lastCursor == nil || lastCursor == "" {
151
+
return nil
152
+
}
153
+
err := oc.RedisClient.Set(ctx, ozoneCursorKey, lastCursor, 14*24*time.Hour).Err()
154
+
return err
155
+
}
156
+
157
+
// this method runs in a loop, persisting the current cursor state every 5 seconds
158
+
func (oc *OzoneConsumer) RunPersistCursor(ctx context.Context) error {
159
+
160
+
// if redis isn't configured, just skip
161
+
if oc.RedisClient == nil {
162
+
return nil
163
+
}
164
+
ticker := time.NewTicker(5 * time.Second)
165
+
for {
166
+
select {
167
+
case <-ctx.Done():
168
+
lastCursor := oc.lastCursor.Load()
169
+
if lastCursor != nil && lastCursor != "" {
170
+
oc.Logger.Info("persisting final ozone cursor timestamp", "cursor", lastCursor)
171
+
err := oc.PersistCursor(ctx)
172
+
if err != nil {
173
+
oc.Logger.Error("failed to persist ozone cursor", "err", err, "cursor", lastCursor)
174
+
}
175
+
}
176
+
return nil
177
+
case <-ticker.C:
178
+
lastCursor := oc.lastCursor.Load()
179
+
if lastCursor != nil && lastCursor != "" {
180
+
err := oc.PersistCursor(ctx)
181
+
if err != nil {
182
+
oc.Logger.Error("failed to persist ozone cursor", "err", err, "cursor", lastCursor)
183
+
}
184
+
}
185
+
}
186
+
}
187
+
}
+25
automod/consumer/util.go
+25
automod/consumer/util.go
···
1
+
package consumer
2
+
3
+
import (
4
+
"fmt"
5
+
"strings"
6
+
7
+
"github.com/bluesky-social/indigo/atproto/syntax"
8
+
)
9
+
10
+
// TODO: move this to a "ParsePath" helper in syntax package?
11
+
func splitRepoPath(path string) (syntax.NSID, syntax.RecordKey, error) {
12
+
parts := strings.SplitN(path, "/", 3)
13
+
if len(parts) != 2 {
14
+
return "", "", fmt.Errorf("invalid record path: %s", path)
15
+
}
16
+
collection, err := syntax.ParseNSID(parts[0])
17
+
if err != nil {
18
+
return "", "", err
19
+
}
20
+
rkey, err := syntax.ParseRecordKey(parts[1])
21
+
if err != nil {
22
+
return "", "", err
23
+
}
24
+
return collection, rkey, nil
25
+
}
+11
-2
automod/engine/account_meta.go
+11
-2
automod/engine/account_meta.go
···
32
32
33
33
type ProfileSummary struct {
34
34
HasAvatar bool
35
+
AvatarCid *string
36
+
BannerCid *string
35
37
Description *string
36
38
DisplayName *string
37
39
}
38
40
41
+
// opaque fingerprints for correlating abusive accounts
42
+
type AbuseSignature struct {
43
+
Property string
44
+
Value string
45
+
}
46
+
39
47
type AccountPrivate struct {
40
48
Email string
41
49
EmailConfirmed bool
42
50
IndexedAt *time.Time
43
51
AccountTags []string
44
52
// ReviewState will be one of ReviewStateEscalated, ReviewStateOpen, ReviewStateClosed, ReviewStateNone, or "" (unknown)
45
-
ReviewState string
46
-
Appealed bool
53
+
ReviewState string
54
+
Appealed bool
55
+
AbuseSignatures []AbuseSignature
47
56
}
+42
automod/engine/cid_from_cdn_test.go
+42
automod/engine/cid_from_cdn_test.go
···
1
+
package engine
2
+
3
+
import (
4
+
"github.com/stretchr/testify/assert"
5
+
"testing"
6
+
)
7
+
8
+
func TestCidFromCdnUrl(t *testing.T) {
9
+
assert := assert.New(t)
10
+
11
+
fixCid := "abcdefghijk"
12
+
13
+
fixtures := []struct {
14
+
url string
15
+
cid *string
16
+
}{
17
+
{
18
+
url: "https://cdn.bsky.app/img/avatar/plain/did:plc:abc123/abcdefghijk@jpeg",
19
+
cid: &fixCid,
20
+
},
21
+
{
22
+
url: "https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:abc123/abcdefghijk@jpeg",
23
+
cid: &fixCid,
24
+
},
25
+
{
26
+
url: "https://cdn.bsky.app/img/feed_fullsize",
27
+
cid: nil,
28
+
},
29
+
{
30
+
url: "https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:abc123/abcdefghijk",
31
+
cid: &fixCid,
32
+
},
33
+
{
34
+
url: "https://cdn.asky.app/img/feed_fullsize/plain/did:plc:abc123/abcdefghijk@jpeg",
35
+
cid: nil,
36
+
},
37
+
}
38
+
39
+
for _, fix := range fixtures {
40
+
assert.Equal(fix.cid, cidFromCdnUrl(&fix.url))
41
+
}
42
+
}
+15
automod/engine/context.go
+15
automod/engine/context.go
···
169
169
return out
170
170
}
171
171
172
+
// Returns a pointer to the underlying automod engine. This usually should NOT be used in rules.
173
+
//
174
+
// This is an escape hatch for hacking on the system before features get fully integerated in to the content API surface. The Engine API is not stable.
175
+
func (c *BaseContext) InternalEngine() *Engine {
176
+
return c.engine
177
+
}
178
+
172
179
func NewAccountContext(ctx context.Context, eng *Engine, meta AccountMeta) AccountContext {
173
180
return AccountContext{
174
181
BaseContext: BaseContext{
···
264
271
c.effects.AddAccountLabel(val)
265
272
}
266
273
274
+
func (c *AccountContext) AddAccountTag(val string) {
275
+
c.effects.AddAccountTag(val)
276
+
}
277
+
267
278
func (c *AccountContext) ReportAccount(reason, comment string) {
268
279
c.effects.ReportAccount(reason, comment)
269
280
}
···
286
297
287
298
func (c *RecordContext) AddRecordLabel(val string) {
288
299
c.effects.AddRecordLabel(val)
300
+
}
301
+
302
+
func (c *RecordContext) AddRecordTag(val string) {
303
+
c.effects.AddRecordTag(val)
289
304
}
290
305
291
306
func (c *RecordContext) ReportRecord(reason, comment string) {
+29
-1
automod/engine/effects.go
+29
-1
automod/engine/effects.go
···
40
40
CounterDistinctIncrements []CounterDistinctRef // TODO: better variable names
41
41
// Label values which should be applied to the overall account, as a result of rule execution.
42
42
AccountLabels []string
43
-
// Moderation flags (similar to labels, but private) which should be applied to the overall account, as a result of rule execution.
43
+
// Moderation tags (similar to labels, but private) which should be applied to the overall account, as a result of rule execution.
44
+
AccountTags []string
45
+
// automod flags (metadata) which should be applied to the account as a result of rule execution.
44
46
AccountFlags []string
45
47
// Reports which should be filed against this account, as a result of rule execution.
46
48
AccountReports []ModReport
···
52
54
AccountAcknowledge bool
53
55
// Same as "AccountLabels", but at record-level
54
56
RecordLabels []string
57
+
// Same as "AccountTags", but at record-level
58
+
RecordTags []string
55
59
// Same as "AccountFlags", but at record-level
56
60
RecordFlags []string
57
61
// Same as "AccountReports", but at record-level
···
106
110
e.AccountLabels = append(e.AccountLabels, val)
107
111
}
108
112
113
+
// Enqueues the provided label (string value) to be added to the account at the end of rule processing.
114
+
func (e *Effects) AddAccountTag(val string) {
115
+
e.mu.Lock()
116
+
defer e.mu.Unlock()
117
+
for _, v := range e.AccountTags {
118
+
if v == val {
119
+
return
120
+
}
121
+
}
122
+
e.AccountTags = append(e.AccountTags, val)
123
+
}
124
+
109
125
// Enqueues the provided flag (string value) to be recorded (in the Engine's flagstore) at the end of rule processing.
110
126
func (e *Effects) AddAccountFlag(val string) {
111
127
e.mu.Lock()
···
158
174
}
159
175
}
160
176
e.RecordLabels = append(e.RecordLabels, val)
177
+
}
178
+
179
+
// Enqueues the provided tag (string value) to be added to the record at the end of rule processing.
180
+
func (e *Effects) AddRecordTag(val string) {
181
+
e.mu.Lock()
182
+
defer e.mu.Unlock()
183
+
for _, v := range e.RecordTags {
184
+
if v == val {
185
+
return
186
+
}
187
+
}
188
+
e.RecordTags = append(e.RecordTags, val)
161
189
}
162
190
163
191
// Enqueues the provided flag (string value) to be recorded (in the Engine's flagstore) at the end of rule processing.
+117
-11
automod/engine/engine.go
+117
-11
automod/engine/engine.go
···
7
7
"net/http"
8
8
"time"
9
9
10
+
comatproto "github.com/bluesky-social/indigo/api/atproto"
10
11
"github.com/bluesky-social/indigo/atproto/identity"
11
12
"github.com/bluesky-social/indigo/atproto/syntax"
12
13
"github.com/bluesky-social/indigo/automod/cachestore"
···
43
44
AdminClient *xrpc.Client
44
45
// used to fetch blobs from upstream PDS instances
45
46
BlobClient *http.Client
47
+
48
+
// internal configuration
49
+
Config EngineConfig
50
+
}
51
+
52
+
type EngineConfig struct {
53
+
// if enabled, account metadata is not hydrated for every event by default
54
+
SkipAccountMeta bool
46
55
}
47
56
48
-
// Entrypoint for external code pushing arbitrary identity events in to the engine.
57
+
// Entrypoint for external code pushing #identity events in to the engine.
49
58
//
50
59
// This method can be called concurrently, though cached state may end up inconsistent if multiple events for the same account (DID) are processed in parallel.
51
-
func (eng *Engine) ProcessIdentityEvent(ctx context.Context, typ string, did syntax.DID) error {
60
+
func (eng *Engine) ProcessIdentityEvent(ctx context.Context, evt comatproto.SyncSubscribeRepos_Identity) error {
52
61
eventProcessCount.WithLabelValues("identity").Inc()
53
62
start := time.Now()
54
63
defer func() {
···
56
65
eventProcessDuration.WithLabelValues("identity").Observe(duration.Seconds())
57
66
}()
58
67
68
+
did, err := syntax.ParseDID(evt.Did)
69
+
if err != nil {
70
+
return fmt.Errorf("bad DID in repo #identity event (%s): %w", evt.Did, err)
71
+
}
72
+
59
73
// similar to an HTTP server, we want to recover any panics from rule execution
60
74
defer func() {
61
75
if r := recover(); r != nil {
62
-
eng.Logger.Error("automod event execution exception", "err", r, "did", did, "type", typ)
76
+
eng.Logger.Error("automod event execution exception", "err", r, "did", did, "type", "identity")
63
77
eventErrorCount.WithLabelValues("identity").Inc()
64
78
}
65
79
}()
···
70
84
if err := eng.PurgeAccountCaches(ctx, did); err != nil {
71
85
eng.Logger.Error("failed to purge identity cache; identity rule may not run correctly", "err", err)
72
86
}
87
+
// TODO(bnewbold): if it was a tombstone, this might fail
73
88
ident, err := eng.Directory.LookupDID(ctx, did)
74
89
if err != nil {
75
90
eventErrorCount.WithLabelValues("identity").Inc()
···
80
95
return fmt.Errorf("identity not found for DID: %s", did.String())
81
96
}
82
97
83
-
am, err := eng.GetAccountMeta(ctx, ident)
84
-
if err != nil {
85
-
eventErrorCount.WithLabelValues("identity").Inc()
86
-
return fmt.Errorf("failed to fetch account metadata: %w", err)
98
+
var am *AccountMeta
99
+
if !eng.Config.SkipAccountMeta {
100
+
am, err = eng.GetAccountMeta(ctx, ident)
101
+
if err != nil {
102
+
eventErrorCount.WithLabelValues("identity").Inc()
103
+
return fmt.Errorf("failed to fetch account metadata: %w", err)
104
+
}
105
+
} else {
106
+
am = &AccountMeta{
107
+
Identity: ident,
108
+
Profile: ProfileSummary{},
109
+
}
87
110
}
88
111
ac := NewAccountContext(ctx, eng, *am)
89
112
if err := eng.Rules.CallIdentityRules(&ac); err != nil {
···
102
125
return nil
103
126
}
104
127
128
+
// Entrypoint for external code pushing #account events in to the engine.
129
+
//
130
+
// This method can be called concurrently, though cached state may end up inconsistent if multiple events for the same account (DID) are processed in parallel.
131
+
func (eng *Engine) ProcessAccountEvent(ctx context.Context, evt comatproto.SyncSubscribeRepos_Account) error {
132
+
eventProcessCount.WithLabelValues("account").Inc()
133
+
start := time.Now()
134
+
defer func() {
135
+
duration := time.Since(start)
136
+
eventProcessDuration.WithLabelValues("account").Observe(duration.Seconds())
137
+
}()
138
+
139
+
did, err := syntax.ParseDID(evt.Did)
140
+
if err != nil {
141
+
return fmt.Errorf("bad DID in repo #account event (%s): %w", evt.Did, err)
142
+
}
143
+
144
+
// similar to an HTTP server, we want to recover any panics from rule execution
145
+
defer func() {
146
+
if r := recover(); r != nil {
147
+
eng.Logger.Error("automod event execution exception", "err", r, "did", did, "type", "account")
148
+
eventErrorCount.WithLabelValues("account").Inc()
149
+
}
150
+
}()
151
+
ctx, cancel := context.WithTimeout(ctx, identityEventTimeout)
152
+
defer cancel()
153
+
154
+
// first purge any caches; we need to re-resolve from scratch on account updates
155
+
if err := eng.PurgeAccountCaches(ctx, did); err != nil {
156
+
eng.Logger.Error("failed to purge account cache; account rule may not run correctly", "err", err)
157
+
}
158
+
// TODO(bnewbold): if it was a tombstone, this might fail
159
+
ident, err := eng.Directory.LookupDID(ctx, did)
160
+
if err != nil {
161
+
eventErrorCount.WithLabelValues("account").Inc()
162
+
return fmt.Errorf("resolving identity: %w", err)
163
+
}
164
+
if ident == nil {
165
+
eventErrorCount.WithLabelValues("account").Inc()
166
+
return fmt.Errorf("identity not found for DID: %s", did.String())
167
+
}
168
+
169
+
var am *AccountMeta
170
+
if !eng.Config.SkipAccountMeta {
171
+
am, err = eng.GetAccountMeta(ctx, ident)
172
+
if err != nil {
173
+
eventErrorCount.WithLabelValues("identity").Inc()
174
+
return fmt.Errorf("failed to fetch account metadata: %w", err)
175
+
}
176
+
} else {
177
+
am = &AccountMeta{
178
+
Identity: ident,
179
+
Profile: ProfileSummary{},
180
+
}
181
+
}
182
+
ac := NewAccountContext(ctx, eng, *am)
183
+
if err := eng.Rules.CallAccountRules(&ac); err != nil {
184
+
eventErrorCount.WithLabelValues("account").Inc()
185
+
return fmt.Errorf("rule execution failed: %w", err)
186
+
}
187
+
eng.CanonicalLogLineAccount(&ac)
188
+
if err := eng.persistAccountModActions(&ac); err != nil {
189
+
eventErrorCount.WithLabelValues("account").Inc()
190
+
return fmt.Errorf("failed to persist actions for account event: %w", err)
191
+
}
192
+
if err := eng.persistCounters(ctx, ac.effects); err != nil {
193
+
eventErrorCount.WithLabelValues("account").Inc()
194
+
return fmt.Errorf("failed to persist counters for account event: %w", err)
195
+
}
196
+
return nil
197
+
}
198
+
105
199
// Entrypoint for external code pushing repository updates. A simple repo commit results in multiple calls.
106
200
//
107
201
// This method can be called concurrently, though cached state may end up inconsistent if multiple events for the same account (DID) are processed in parallel.
···
136
230
return fmt.Errorf("identity not found for DID: %s", op.DID)
137
231
}
138
232
139
-
am, err := eng.GetAccountMeta(ctx, ident)
140
-
if err != nil {
141
-
eventErrorCount.WithLabelValues("record").Inc()
142
-
return fmt.Errorf("failed to fetch account metadata: %w", err)
233
+
var am *AccountMeta
234
+
if !eng.Config.SkipAccountMeta {
235
+
am, err = eng.GetAccountMeta(ctx, ident)
236
+
if err != nil {
237
+
eventErrorCount.WithLabelValues("identity").Inc()
238
+
return fmt.Errorf("failed to fetch account metadata: %w", err)
239
+
}
240
+
} else {
241
+
am = &AccountMeta{
242
+
Identity: ident,
243
+
Profile: ProfileSummary{},
244
+
}
143
245
}
144
246
rc := NewRecordContext(ctx, eng, *am, op)
145
247
rc.Logger.Debug("processing record")
···
249
351
c.Logger.Info("canonical-event-line",
250
352
"accountLabels", c.effects.AccountLabels,
251
353
"accountFlags", c.effects.AccountFlags,
354
+
"accountTags", c.effects.AccountTags,
252
355
"accountTakedown", c.effects.AccountTakedown,
253
356
"accountReports", len(c.effects.AccountReports),
254
357
)
···
258
361
c.Logger.Info("canonical-event-line",
259
362
"accountLabels", c.effects.AccountLabels,
260
363
"accountFlags", c.effects.AccountFlags,
364
+
"accountTags", c.effects.AccountTags,
261
365
"accountTakedown", c.effects.AccountTakedown,
262
366
"accountReports", len(c.effects.AccountReports),
263
367
"recordLabels", c.effects.RecordLabels,
264
368
"recordFlags", c.effects.RecordFlags,
369
+
"recordTags", c.effects.RecordTags,
265
370
"recordTakedown", c.effects.RecordTakedown,
266
371
"recordReports", len(c.effects.RecordReports),
267
372
)
···
271
376
c.Logger.Info("canonical-event-line",
272
377
"accountLabels", c.effects.AccountLabels,
273
378
"accountFlags", c.effects.AccountFlags,
379
+
"accountTags", c.effects.AccountTags,
274
380
"accountTakedown", c.effects.AccountTakedown,
275
381
"accountReports", len(c.effects.AccountReports),
276
382
"reject", c.effects.RejectEvent,
+11
-2
automod/engine/fetch_account_meta.go
+11
-2
automod/engine/fetch_account_meta.go
···
24
24
25
25
// fallback in case client wasn't configured (eg, testing)
26
26
if e.BskyClient == nil {
27
-
logger.Warn("skipping account meta hydration")
27
+
logger.Debug("skipping account meta hydration")
28
28
am := AccountMeta{
29
29
Identity: ident,
30
30
Profile: ProfileSummary{},
···
64
64
// most common cause of this is a race between automod and ozone/appview for new accounts. just sleep a couple seconds and retry!
65
65
var xrpcError *xrpc.Error
66
66
if err != nil && errors.As(err, &xrpcError) && (xrpcError.StatusCode == 400 || xrpcError.StatusCode == 404) {
67
-
logger.Info("account profile lookup initially failed (from bsky appview), will retry", "err", err, "sleepDuration", newAccountRetryDuration)
67
+
logger.Debug("account profile lookup initially failed (from bsky appview), will retry", "err", err, "sleepDuration", newAccountRetryDuration)
68
68
time.Sleep(newAccountRetryDuration)
69
69
pv, err = appbsky.ActorGetProfile(ctx, e.BskyClient, ident.DID.String())
70
70
}
···
75
75
76
76
am.Profile = ProfileSummary{
77
77
HasAvatar: pv.Avatar != nil,
78
+
AvatarCid: cidFromCdnUrl(pv.Avatar),
79
+
BannerCid: cidFromCdnUrl(pv.Banner),
78
80
Description: pv.Description,
79
81
DisplayName: pv.DisplayName,
80
82
}
···
147
149
ap.ReviewState = ReviewStateNone
148
150
}
149
151
}
152
+
}
153
+
if rd.ThreatSignatures != nil || len(rd.ThreatSignatures) > 0 {
154
+
asigs := make([]AbuseSignature, len(rd.ThreatSignatures))
155
+
for i, sig := range rd.ThreatSignatures {
156
+
asigs[i] = AbuseSignature{Property: sig.Property, Value: sig.Value}
157
+
}
158
+
ap.AbuseSignatures = asigs
150
159
}
151
160
am.Private = &ap
152
161
}
+5
automod/engine/metrics.go
+5
automod/engine/metrics.go
···
25
25
Help: "Number of new labels persisted",
26
26
}, []string{"type", "val"})
27
27
28
+
var actionNewTagCount = promauto.NewCounterVec(prometheus.CounterOpts{
29
+
Name: "automod_new_action_tags",
30
+
Help: "Number of new tags persisted",
31
+
}, []string{"type", "val"})
32
+
28
33
var actionNewFlagCount = promauto.NewCounterVec(prometheus.CounterOpts{
29
34
Name: "automod_new_action_flags",
30
35
Help: "Number of new flags persisted",
+73
-9
automod/engine/persist.go
+73
-9
automod/engine/persist.go
···
32
32
return nil
33
33
}
34
34
35
-
// Persists account-level moderation actions: new labels, new flags, new takedowns, and reports.
35
+
// Persists account-level moderation actions: new labels, new tags, new flags, new takedowns, and reports.
36
36
//
37
37
// If necessary, will "purge" identity and account caches, so that state updates will be picked up for subsequent events.
38
38
//
···
42
42
43
43
// de-dupe actions
44
44
newLabels := dedupeLabelActions(c.effects.AccountLabels, c.Account.AccountLabels, c.Account.AccountNegatedLabels)
45
+
existingTags := []string{}
46
+
if c.Account.Private != nil {
47
+
existingTags = c.Account.Private.AccountTags
48
+
}
49
+
newTags := dedupeTagActions(c.effects.AccountTags, existingTags)
45
50
newFlags := dedupeFlagActions(c.effects.AccountFlags, c.Account.AccountFlags)
46
51
47
52
// don't report the same account multiple times on the same day for the same reason. this is a quick check; we also query the mod service API just before creating the report.
···
78
83
}
79
84
}
80
85
81
-
anyModActions := newTakedown || newEscalation || newAcknowledge || len(newLabels) > 0 || len(newFlags) > 0 || len(newReports) > 0
86
+
anyModActions := newTakedown || newEscalation || newAcknowledge || len(newLabels) > 0 || len(newTags) > 0 || len(newFlags) > 0 || len(newReports) > 0
82
87
if anyModActions && eng.Notifier != nil {
83
88
for _, srv := range dedupeStrings(c.effects.NotifyServices) {
84
89
if err := eng.Notifier.SendAccount(ctx, srv, c); err != nil {
···
107
112
xrpcc := eng.OzoneClient
108
113
109
114
if len(newLabels) > 0 {
110
-
c.Logger.Info("labeling record", "newLabels", newLabels)
115
+
c.Logger.Info("labeling account", "newLabels", newLabels)
111
116
for _, val := range newLabels {
112
117
// note: WithLabelValues is a prometheus label, not an atproto label
113
118
actionNewLabelCount.WithLabelValues("account", val).Inc()
···
133
138
}
134
139
}
135
140
141
+
if len(newTags) > 0 {
142
+
c.Logger.Info("tagging account", "newTags", newTags)
143
+
for _, val := range newTags {
144
+
// note: WithLabelValues is a prometheus label, not an atproto label
145
+
actionNewTagCount.WithLabelValues("account", val).Inc()
146
+
}
147
+
comment := "[automod]: auto-tagging account"
148
+
_, err := toolsozone.ModerationEmitEvent(ctx, xrpcc, &toolsozone.ModerationEmitEvent_Input{
149
+
CreatedBy: xrpcc.Auth.Did,
150
+
Event: &toolsozone.ModerationEmitEvent_Input_Event{
151
+
ModerationDefs_ModEventTag: &toolsozone.ModerationDefs_ModEventTag{
152
+
Add: newTags,
153
+
Remove: []string{},
154
+
Comment: &comment,
155
+
},
156
+
},
157
+
Subject: &toolsozone.ModerationEmitEvent_Input_Subject{
158
+
AdminDefs_RepoRef: &comatproto.AdminDefs_RepoRef{
159
+
Did: c.Account.Identity.DID.String(),
160
+
},
161
+
},
162
+
})
163
+
if err != nil {
164
+
c.Logger.Error("failed to create account tags", "err", err)
165
+
}
166
+
}
167
+
136
168
// reports are additionally de-duped when persisting the action, so track with a flag
137
169
createdReports := false
138
170
for _, mr := range newReports {
···
214
246
}
215
247
}
216
248
217
-
needCachePurge := newTakedown || newEscalation || newAcknowledge || len(newLabels) > 0 || len(newFlags) > 0 || createdReports
249
+
needCachePurge := newTakedown || newEscalation || newAcknowledge || len(newLabels) > 0 || len(newTags) > 0 || len(newFlags) > 0 || createdReports
218
250
if needCachePurge {
219
251
return eng.PurgeAccountCaches(ctx, c.Account.Identity.DID)
220
252
}
···
222
254
return nil
223
255
}
224
256
225
-
// Persists some record-level state: labels, takedowns, reports.
257
+
// Persists some record-level state: labels, tags, takedowns, reports.
226
258
//
227
259
// NOTE: this method currently does *not* persist record-level flags to any storage, and does not de-dupe most actions, on the assumption that the record is new (from firehose) and has no existing mod state.
228
260
func (eng *Engine) persistRecordModActions(c *RecordContext) error {
···
233
265
234
266
atURI := c.RecordOp.ATURI().String()
235
267
newLabels := dedupeStrings(c.effects.RecordLabels)
236
-
if len(newLabels) > 0 && eng.OzoneClient != nil {
268
+
newTags := dedupeStrings(c.effects.RecordTags)
269
+
if (len(newLabels) > 0 || len(newTags) > 0) && eng.OzoneClient != nil {
270
+
// fetch existing record labels, tags, etc
237
271
rv, err := toolsozone.ModerationGetRecord(ctx, eng.OzoneClient, c.RecordOp.CID.String(), c.RecordOp.ATURI().String())
238
272
if err != nil {
239
273
// NOTE: there is a frequent 4xx error here from Ozone because this record has not been indexed yet
···
250
284
}
251
285
existingLabels = dedupeStrings(existingLabels)
252
286
negLabels = dedupeStrings(negLabels)
253
-
// fetch existing record labels
254
287
newLabels = dedupeLabelActions(newLabels, existingLabels, negLabels)
288
+
existingTags := []string{}
289
+
if rv.Moderation != nil && rv.Moderation.SubjectStatus != nil && rv.Moderation.SubjectStatus.Tags != nil {
290
+
existingTags = rv.Moderation.SubjectStatus.Tags
291
+
}
292
+
newTags = dedupeTagActions(newTags, existingTags)
255
293
}
256
294
}
295
+
257
296
newFlags := dedupeStrings(c.effects.RecordFlags)
258
297
if len(newFlags) > 0 {
259
298
// fetch existing flags, and de-dupe
···
288
327
return fmt.Errorf("circuit-breaking acknowledge: %w", err)
289
328
}
290
329
291
-
if newEscalation || newAcknowledge || newTakedown || len(newLabels) > 0 || len(newFlags) > 0 || len(newReports) > 0 {
330
+
if newEscalation || newAcknowledge || newTakedown || len(newLabels) > 0 || len(newTags) > 0 || len(newFlags) > 0 || len(newReports) > 0 {
292
331
if eng.Notifier != nil {
293
332
for _, srv := range dedupeStrings(c.effects.NotifyServices) {
294
333
if err := eng.Notifier.SendRecord(ctx, srv, c); err != nil {
···
308
347
}
309
348
310
349
// exit early
311
-
if !newAcknowledge && !newEscalation && !newTakedown && len(newLabels) == 0 && len(newReports) == 0 {
350
+
if !newAcknowledge && !newEscalation && !newTakedown && len(newLabels) == 0 && len(newTags) == 0 && len(newReports) == 0 {
312
351
return nil
313
352
}
314
353
···
350
389
})
351
390
if err != nil {
352
391
c.Logger.Error("failed to create record label", "err", err)
392
+
}
393
+
}
394
+
395
+
if len(newTags) > 0 {
396
+
c.Logger.Info("tagging record", "newTags", newTags)
397
+
for _, val := range newTags {
398
+
// note: WithLabelValues is a prometheus label, not an atproto label
399
+
actionNewTagCount.WithLabelValues("record", val).Inc()
400
+
}
401
+
comment := "[automod]: auto-tagging record"
402
+
_, err := toolsozone.ModerationEmitEvent(ctx, xrpcc, &toolsozone.ModerationEmitEvent_Input{
403
+
CreatedBy: xrpcc.Auth.Did,
404
+
Event: &toolsozone.ModerationEmitEvent_Input_Event{
405
+
ModerationDefs_ModEventTag: &toolsozone.ModerationDefs_ModEventTag{
406
+
Add: newTags,
407
+
Remove: []string{},
408
+
Comment: &comment,
409
+
},
410
+
},
411
+
Subject: &toolsozone.ModerationEmitEvent_Input_Subject{
412
+
RepoStrongRef: &strongRef,
413
+
},
414
+
})
415
+
if err != nil {
416
+
c.Logger.Error("failed to create record tag", "err", err)
353
417
}
354
418
}
355
419
+53
-34
automod/engine/persisthelpers.go
+53
-34
automod/engine/persisthelpers.go
···
35
35
return newLabels
36
36
}
37
37
38
+
func dedupeTagActions(tags, existing []string) []string {
39
+
newTags := []string{}
40
+
for _, val := range dedupeStrings(tags) {
41
+
exists := false
42
+
for _, e := range existing {
43
+
if val == e {
44
+
exists = true
45
+
break
46
+
}
47
+
}
48
+
if !exists {
49
+
newTags = append(newTags, val)
50
+
}
51
+
}
52
+
return newTags
53
+
}
54
+
38
55
func dedupeFlagActions(flags, existing []string) []string {
39
56
newFlags := []string{}
40
57
for _, val := range dedupeStrings(flags) {
···
138
155
// before creating a report, query to see if automod has already reported this account in the past week for the same reason
139
156
// NOTE: this is running in an inner loop (if there are multiple reports), which is a bit inefficient, but seems acceptable
140
157
141
-
// ModerationQueryEvents(ctx context.Context, c *xrpc.Client, createdBy string, cursor string, inc ludeAllUserRecords bool, limit int64, sortDirection string, subject string, types []string)
142
158
resp, err := toolsozone.ModerationQueryEvents(
143
159
ctx,
144
160
xrpcc,
145
-
nil,
146
-
nil,
147
-
"",
148
-
"",
149
-
"",
150
-
xrpcc.Auth.Did,
151
-
"",
152
-
false,
153
-
false,
154
-
5,
155
-
nil,
156
-
nil,
157
-
nil,
158
-
"",
159
-
did.String(),
160
-
[]string{"tools.ozone.moderation.defs#modEventReport"},
161
+
nil, // addedLabels []string
162
+
nil, // addedTags []string
163
+
nil, // collections []string
164
+
"", // comment string
165
+
"", // createdAfter string
166
+
"", // createdBefore string
167
+
xrpcc.Auth.Did, // createdBy string
168
+
"", // cursor string
169
+
false, // hasComment bool
170
+
false, // includeAllUserRecords bool
171
+
5, // limit int64
172
+
nil, // removedLabels []string
173
+
nil, // removedTags []string
174
+
nil, // reportTypes []string
175
+
"", // sortDirection string
176
+
did.String(), // subject string
177
+
"", // subjectType string
178
+
[]string{"tools.ozone.moderation.defs#modEventReport"}, // types []string
161
179
)
162
180
163
181
if err != nil {
···
214
232
// before creating a report, query to see if automod has already reported this account in the past week for the same reason
215
233
// NOTE: this is running in an inner loop (if there are multiple reports), which is a bit inefficient, but seems acceptable
216
234
217
-
// ModerationQueryEvents(ctx context.Context, c *xrpc.Client, createdBy string, cursor string, inc ludeAllUserRecords bool, limit int64, sortDirection string, subject string, types []string)
218
235
resp, err := toolsozone.ModerationQueryEvents(
219
236
ctx,
220
237
xrpcc,
221
-
nil,
222
-
nil,
223
-
"",
224
-
"",
225
-
"",
226
-
xrpcc.Auth.Did,
227
-
"",
228
-
false,
229
-
false,
230
-
5,
231
-
nil,
232
-
nil,
233
-
nil,
234
-
"",
235
-
uri.String(),
236
-
[]string{"tools.ozone.moderation.defs#modEventReport"},
238
+
nil, // addedLabels []string
239
+
nil, // addedTags []string
240
+
nil, // collections []string
241
+
"", // comment string
242
+
"", // createdAfter string
243
+
"", // createdBefore string
244
+
xrpcc.Auth.Did, // createdBy string
245
+
"", // cursor string
246
+
false, // hasComment bool
247
+
false, // includeAllUserRecords bool
248
+
5, // limit int64
249
+
nil, // removedLabels []string
250
+
nil, // removedTags []string
251
+
nil, // reportTypes []string
252
+
"", // sortDirection string
253
+
uri.String(), // subject string
254
+
"", // subjectType string
255
+
[]string{"tools.ozone.moderation.defs#modEventReport"}, // types []string
237
256
)
238
257
if err != nil {
239
258
return false, err
+12
automod/engine/ruleset.go
+12
automod/engine/ruleset.go
···
16
16
RecordRules []RecordRuleFunc
17
17
RecordDeleteRules []RecordRuleFunc
18
18
IdentityRules []IdentityRuleFunc
19
+
AccountRules []AccountRuleFunc
19
20
BlobRules []BlobRuleFunc
20
21
NotificationRules []NotificationRuleFunc
21
22
OzoneEventRules []OzoneEventRuleFunc
···
84
85
err := f(c)
85
86
if err != nil {
86
87
c.Logger.Error("identity rule execution failed", "err", err)
88
+
}
89
+
}
90
+
return nil
91
+
}
92
+
93
+
// Executes rules for account update events.
94
+
func (r *RuleSet) CallAccountRules(c *AccountContext) error {
95
+
for _, f := range r.AccountRules {
96
+
err := f(c)
97
+
if err != nil {
98
+
c.Logger.Error("account rule execution failed", "err", err)
87
99
}
88
100
}
89
101
return nil
+1
automod/engine/ruletypes.go
+1
automod/engine/ruletypes.go
···
6
6
)
7
7
8
8
type IdentityRuleFunc = func(c *AccountContext) error
9
+
type AccountRuleFunc = func(c *AccountContext) error
9
10
type RecordRuleFunc = func(c *RecordContext) error
10
11
type PostRuleFunc = func(c *RecordContext, post *appbsky.FeedPost) error
11
12
type ProfileRuleFunc = func(c *RecordContext, profile *appbsky.ActorProfile) error
+24
automod/engine/util.go
+24
automod/engine/util.go
···
1
1
package engine
2
2
3
+
import (
4
+
"net/url"
5
+
"strings"
6
+
)
7
+
3
8
func dedupeStrings(in []string) []string {
4
9
var out []string
5
10
seen := make(map[string]bool)
···
11
16
}
12
17
return out
13
18
}
19
+
20
+
// get the cid from a bluesky cdn url
21
+
func cidFromCdnUrl(str *string) *string {
22
+
if str == nil {
23
+
return nil
24
+
}
25
+
26
+
u, err := url.Parse(*str)
27
+
if err != nil || u.Host != "cdn.bsky.app" {
28
+
return nil
29
+
}
30
+
31
+
parts := strings.Split(u.Path, "/")
32
+
if len(parts) != 6 {
33
+
return nil
34
+
}
35
+
36
+
return &strings.Split(parts[5], "@")[0]
37
+
}
+49
automod/helpers/account.go
+49
automod/helpers/account.go
···
1
+
package helpers
2
+
3
+
import (
4
+
"time"
5
+
6
+
"github.com/bluesky-social/indigo/automod"
7
+
)
8
+
9
+
// no accounts exist before this time
10
+
var atprotoAccountEpoch = time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
11
+
12
+
// returns true if account creation timestamp is plausible: not-nil, not in distant past, not in the future
13
+
func plausibleAccountCreation(when *time.Time) bool {
14
+
if when == nil {
15
+
return false
16
+
}
17
+
// this is mostly to check for misconfigurations or null values (eg, UNIX epoch zero means "unknown" not actually 1970)
18
+
if !when.After(atprotoAccountEpoch) {
19
+
return false
20
+
}
21
+
// a timestamp in the future would also indicate some misconfiguration
22
+
if when.After(time.Now().Add(time.Hour)) {
23
+
return false
24
+
}
25
+
return true
26
+
}
27
+
28
+
// checks if account was created recently, based on either public or private account metadata. if metadata isn't available at all, or seems bogus, returns 'false'
29
+
func AccountIsYoungerThan(c *automod.AccountContext, age time.Duration) bool {
30
+
// TODO: consider swapping priority order here (and below)
31
+
if c.Account.CreatedAt != nil && plausibleAccountCreation(c.Account.CreatedAt) {
32
+
return time.Since(*c.Account.CreatedAt) < age
33
+
}
34
+
if c.Account.Private != nil && plausibleAccountCreation(c.Account.Private.IndexedAt) {
35
+
return time.Since(*c.Account.Private.IndexedAt) < age
36
+
}
37
+
return false
38
+
}
39
+
40
+
// checks if account was *not* created recently, based on either public or private account metadata. if metadata isn't available at all, or seems bogus, returns 'false'
41
+
func AccountIsOlderThan(c *automod.AccountContext, age time.Duration) bool {
42
+
if c.Account.CreatedAt != nil && plausibleAccountCreation(c.Account.CreatedAt) {
43
+
return time.Since(*c.Account.CreatedAt) >= age
44
+
}
45
+
if c.Account.Private != nil && plausibleAccountCreation(c.Account.Private.IndexedAt) {
46
+
return time.Since(*c.Account.Private.IndexedAt) >= age
47
+
}
48
+
return false
49
+
}
+61
automod/helpers/account_test.go
+61
automod/helpers/account_test.go
···
1
+
package helpers
2
+
3
+
import (
4
+
"testing"
5
+
"time"
6
+
7
+
"github.com/bluesky-social/indigo/atproto/identity"
8
+
"github.com/bluesky-social/indigo/atproto/syntax"
9
+
"github.com/bluesky-social/indigo/automod"
10
+
"github.com/stretchr/testify/assert"
11
+
)
12
+
13
+
func TestAccountIsYoungerThan(t *testing.T) {
14
+
assert := assert.New(t)
15
+
16
+
am := automod.AccountMeta{
17
+
Identity: &identity.Identity{
18
+
DID: syntax.DID("did:plc:abc111"),
19
+
Handle: syntax.Handle("handle.example.com"),
20
+
},
21
+
Profile: automod.ProfileSummary{},
22
+
Private: nil,
23
+
}
24
+
now := time.Now()
25
+
ac := automod.AccountContext{
26
+
Account: am,
27
+
}
28
+
assert.False(AccountIsYoungerThan(&ac, time.Hour))
29
+
assert.False(AccountIsOlderThan(&ac, time.Hour))
30
+
31
+
ac.Account.CreatedAt = &now
32
+
assert.True(AccountIsYoungerThan(&ac, time.Hour))
33
+
assert.False(AccountIsOlderThan(&ac, time.Hour))
34
+
35
+
yesterday := time.Now().Add(-1 * time.Hour * 24)
36
+
ac.Account.CreatedAt = &yesterday
37
+
assert.False(AccountIsYoungerThan(&ac, time.Hour))
38
+
assert.True(AccountIsOlderThan(&ac, time.Hour))
39
+
40
+
old := time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC)
41
+
ac.Account.CreatedAt = &old
42
+
assert.False(AccountIsYoungerThan(&ac, time.Hour))
43
+
assert.False(AccountIsYoungerThan(&ac, time.Hour*24*365*100))
44
+
assert.False(AccountIsOlderThan(&ac, time.Hour))
45
+
assert.False(AccountIsOlderThan(&ac, time.Hour*24*365*100))
46
+
47
+
future := time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC)
48
+
ac.Account.CreatedAt = &future
49
+
assert.False(AccountIsYoungerThan(&ac, time.Hour))
50
+
assert.False(AccountIsOlderThan(&ac, time.Hour))
51
+
52
+
ac.Account.CreatedAt = nil
53
+
ac.Account.Private = &automod.AccountPrivate{
54
+
Email: "account@example.com",
55
+
IndexedAt: &yesterday,
56
+
}
57
+
assert.True(AccountIsYoungerThan(&ac, 48*time.Hour))
58
+
assert.False(AccountIsYoungerThan(&ac, time.Hour))
59
+
assert.True(AccountIsOlderThan(&ac, time.Hour))
60
+
assert.False(AccountIsOlderThan(&ac, 48*time.Hour))
61
+
}
+141
automod/helpers/bsky_test.go
+141
automod/helpers/bsky_test.go
···
1
+
package helpers
2
+
3
+
import (
4
+
comatproto "github.com/bluesky-social/indigo/api/atproto"
5
+
appbsky "github.com/bluesky-social/indigo/api/bsky"
6
+
"testing"
7
+
8
+
"github.com/stretchr/testify/assert"
9
+
)
10
+
11
+
func TestParentOrRootIsDid(t *testing.T) {
12
+
assert := assert.New(t)
13
+
14
+
post1 := &appbsky.FeedPost{
15
+
Text: "some random post that i dreamt up last night, idk",
16
+
Reply: &appbsky.FeedPost_ReplyRef{
17
+
Root: &comatproto.RepoStrongRef{
18
+
Uri: "at://did:plc:abc123/app.bsky.feed.post/rkey123",
19
+
},
20
+
Parent: &comatproto.RepoStrongRef{
21
+
Uri: "at://did:plc:abc123/app.bsky.feed.post/rkey123",
22
+
},
23
+
},
24
+
}
25
+
26
+
post2 := &appbsky.FeedPost{
27
+
Text: "some random post that i dreamt up last night, idk",
28
+
Reply: &appbsky.FeedPost_ReplyRef{
29
+
Root: &comatproto.RepoStrongRef{
30
+
Uri: "at://did:plc:321abc/app.bsky.feed.post/rkey123",
31
+
},
32
+
Parent: &comatproto.RepoStrongRef{
33
+
Uri: "at://did:plc:abc123/app.bsky.feed.post/rkey123",
34
+
},
35
+
},
36
+
}
37
+
38
+
post3 := &appbsky.FeedPost{
39
+
Text: "some random post that i dreamt up last night, idk",
40
+
Reply: &appbsky.FeedPost_ReplyRef{
41
+
Root: &comatproto.RepoStrongRef{
42
+
Uri: "at://did:plc:abc123/app.bsky.feed.post/rkey123",
43
+
},
44
+
Parent: &comatproto.RepoStrongRef{
45
+
Uri: "at://did:plc:321abc/app.bsky.feed.post/rkey123",
46
+
},
47
+
},
48
+
}
49
+
50
+
post4 := &appbsky.FeedPost{
51
+
Text: "some random post that i dreamt up last night, idk",
52
+
Reply: &appbsky.FeedPost_ReplyRef{
53
+
Root: &comatproto.RepoStrongRef{
54
+
Uri: "at://did:plc:321abc/app.bsky.feed.post/rkey123",
55
+
},
56
+
Parent: &comatproto.RepoStrongRef{
57
+
Uri: "at://did:plc:321abc/app.bsky.feed.post/rkey123",
58
+
},
59
+
},
60
+
}
61
+
62
+
assert.True(PostParentOrRootIsDid(post1, "did:plc:abc123"))
63
+
assert.False(PostParentOrRootIsDid(post1, "did:plc:321abc"))
64
+
65
+
assert.True(PostParentOrRootIsDid(post2, "did:plc:abc123"))
66
+
assert.True(PostParentOrRootIsDid(post2, "did:plc:321abc"))
67
+
68
+
assert.True(PostParentOrRootIsDid(post3, "did:plc:abc123"))
69
+
assert.True(PostParentOrRootIsDid(post3, "did:plc:321abc"))
70
+
71
+
assert.False(PostParentOrRootIsDid(post4, "did:plc:abc123"))
72
+
assert.True(PostParentOrRootIsDid(post4, "did:plc:321abc"))
73
+
74
+
didList1 := []string{
75
+
"did:plc:cba321",
76
+
"did:web:bsky.app",
77
+
"did:plc:abc123",
78
+
}
79
+
80
+
didList2 := []string{
81
+
"did:plc:321cba",
82
+
"did:web:bsky.app",
83
+
"did:plc:123abc",
84
+
}
85
+
86
+
assert.True(PostParentOrRootIsAnyDid(post1, didList1))
87
+
assert.False(PostParentOrRootIsAnyDid(post1, didList2))
88
+
}
89
+
90
+
func TestPostMentionsDid(t *testing.T) {
91
+
assert := assert.New(t)
92
+
93
+
post := &appbsky.FeedPost{
94
+
Text: "@hailey.at what is upppp also hello to @darthbluesky.bsky.social",
95
+
Facets: []*appbsky.RichtextFacet{
96
+
{
97
+
Features: []*appbsky.RichtextFacet_Features_Elem{
98
+
{
99
+
RichtextFacet_Mention: &appbsky.RichtextFacet_Mention{
100
+
Did: "did:plc:abc123",
101
+
},
102
+
},
103
+
},
104
+
Index: &appbsky.RichtextFacet_ByteSlice{
105
+
ByteStart: 0,
106
+
ByteEnd: 9,
107
+
},
108
+
},
109
+
{
110
+
Features: []*appbsky.RichtextFacet_Features_Elem{
111
+
{
112
+
RichtextFacet_Mention: &appbsky.RichtextFacet_Mention{
113
+
Did: "did:plc:abc456",
114
+
},
115
+
},
116
+
},
117
+
Index: &appbsky.RichtextFacet_ByteSlice{
118
+
ByteStart: 39,
119
+
ByteEnd: 63,
120
+
},
121
+
},
122
+
},
123
+
}
124
+
assert.True(PostMentionsDid(post, "did:plc:abc123"))
125
+
assert.False(PostMentionsDid(post, "did:plc:cba321"))
126
+
127
+
didList1 := []string{
128
+
"did:plc:cba321",
129
+
"did:web:bsky.app",
130
+
"did:plc:abc456",
131
+
}
132
+
133
+
didList2 := []string{
134
+
"did:plc:321cba",
135
+
"did:web:bsky.app",
136
+
"did:plc:123abc",
137
+
}
138
+
139
+
assert.True(PostMentionsAnyDid(post, didList1))
140
+
assert.False(PostMentionsAnyDid(post, didList2))
141
+
}
+35
automod/helpers/text.go
+35
automod/helpers/text.go
···
1
+
package helpers
2
+
3
+
import (
4
+
"fmt"
5
+
"regexp"
6
+
7
+
"github.com/spaolacci/murmur3"
8
+
)
9
+
10
+
func DedupeStrings(in []string) []string {
11
+
var out []string
12
+
seen := make(map[string]bool)
13
+
for _, v := range in {
14
+
if !seen[v] {
15
+
out = append(out, v)
16
+
seen[v] = true
17
+
}
18
+
}
19
+
return out
20
+
}
21
+
22
+
// returns a fast, compact hash of a string
23
+
//
24
+
// current implementation uses murmur3, default seed, and hex encoding
25
+
func HashOfString(s string) string {
26
+
val := murmur3.Sum64([]byte(s))
27
+
return fmt.Sprintf("%016x", val)
28
+
}
29
+
30
+
// based on: https://stackoverflow.com/a/48769624, with no trailing period allowed
31
+
var urlRegex = regexp.MustCompile(`(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-&?=%.]*[\w/\-&?=%]+`)
32
+
33
+
func ExtractTextURLs(raw string) []string {
34
+
return urlRegex.FindAllString(raw, -1)
35
+
}
+64
automod/helpers/text_test.go
+64
automod/helpers/text_test.go
···
1
+
package helpers
2
+
3
+
import (
4
+
"testing"
5
+
6
+
"github.com/bluesky-social/indigo/automod/keyword"
7
+
8
+
"github.com/stretchr/testify/assert"
9
+
)
10
+
11
+
func TestTokenizeText(t *testing.T) {
12
+
assert := assert.New(t)
13
+
14
+
fixtures := []struct {
15
+
s string
16
+
out []string
17
+
}{
18
+
{
19
+
s: "1 'Two' three!",
20
+
out: []string{"1", "two", "three"},
21
+
},
22
+
{
23
+
s: " foo1;bar2,baz3...",
24
+
out: []string{"foo1", "bar2", "baz3"},
25
+
},
26
+
{
27
+
s: "https://example.com/index.html",
28
+
out: []string{"https", "example", "com", "index", "html"},
29
+
},
30
+
}
31
+
32
+
for _, fix := range fixtures {
33
+
assert.Equal(fix.out, keyword.TokenizeText(fix.s))
34
+
}
35
+
}
36
+
37
+
func TestExtractURL(t *testing.T) {
38
+
assert := assert.New(t)
39
+
40
+
fixtures := []struct {
41
+
s string
42
+
out []string
43
+
}{
44
+
{
45
+
s: "this is a description with example.com mentioned in the middle",
46
+
out: []string{"example.com"},
47
+
},
48
+
{
49
+
s: "this is another example with https://en.wikipedia.org/index.html: and archive.org, and https://eff.org/... and bsky.app.",
50
+
out: []string{"https://en.wikipedia.org/index.html", "archive.org", "https://eff.org/", "bsky.app"},
51
+
},
52
+
}
53
+
54
+
for _, fix := range fixtures {
55
+
assert.Equal(fix.out, ExtractTextURLs(fix.s))
56
+
}
57
+
}
58
+
59
+
func TestHashOfString(t *testing.T) {
60
+
assert := assert.New(t)
61
+
62
+
// hashing function should be consistent over time
63
+
assert.Equal("4e6f69c0e3d10992", HashOfString("dummy-value"))
64
+
}
+14
-5
automod/keyword/tokenize.go
+14
-5
automod/keyword/tokenize.go
···
12
12
)
13
13
14
14
var (
15
-
puncChars = regexp.MustCompile(`[[:punct:]]+`)
16
-
nonTokenChars = regexp.MustCompile(`[^\pL\pN\s]+`)
15
+
puncChars = regexp.MustCompile(`[[:punct:]]+`)
16
+
nonTokenChars = regexp.MustCompile(`[^\pL\pN\s]+`)
17
+
nonTokenCharsSkipCensorChars = regexp.MustCompile(`[^\pL\pN\s#*_-]`)
17
18
)
18
19
19
20
// Splits free-form text in to tokens, including lower-case, unicode normalization, and some unicode folding.
20
21
//
21
22
// The intent is for this to work similarly to an NLP tokenizer, as might be used in a fulltext search engine, and enable fast matching to a list of known tokens. It might eventually even do stemming, removing pluralization (trailing "s" for English), etc.
22
-
func TokenizeText(text string) []string {
23
+
func TokenizeTextWithRegex(text string, nonTokenCharsRegex *regexp.Regexp) []string {
23
24
// this function needs to be re-defined in every function call to prevent a race condition
24
25
normFunc := transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC)
25
-
split := strings.ToLower(nonTokenChars.ReplaceAllString(text, " "))
26
-
bare := strings.ToLower(nonTokenChars.ReplaceAllString(split, ""))
26
+
split := strings.ToLower(nonTokenCharsRegex.ReplaceAllString(text, " "))
27
+
bare := strings.ToLower(nonTokenCharsRegex.ReplaceAllString(split, ""))
27
28
norm, _, err := transform.String(normFunc, bare)
28
29
if err != nil {
29
30
slog.Warn("unicode normalization error", "err", err)
30
31
norm = bare
31
32
}
32
33
return strings.Fields(norm)
34
+
}
35
+
36
+
func TokenizeText(text string) []string {
37
+
return TokenizeTextWithRegex(text, nonTokenChars)
38
+
}
39
+
40
+
func TokenizeTextSkippingCensorChars(text string) []string {
41
+
return TokenizeTextWithRegex(text, nonTokenCharsSkipCensorChars)
33
42
}
34
43
35
44
func splitIdentRune(c rune) bool {
+47
automod/keyword/tokenize_test.go
+47
automod/keyword/tokenize_test.go
···
1
1
package keyword
2
2
3
3
import (
4
+
"regexp"
4
5
"testing"
5
6
6
7
"github.com/stretchr/testify/assert"
···
17
18
{text: "Hello, โลก!", out: []string{"hello", "โลก"}},
18
19
{text: "Gdańsk", out: []string{"gdansk"}},
19
20
{text: " foo1;bar2,baz3...", out: []string{"foo1", "bar2", "baz3"}},
21
+
{text: "foo*bar", out: []string{"foo", "bar"}},
22
+
{text: "foo-bar", out: []string{"foo", "bar"}},
23
+
{text: "foo_bar", out: []string{"foo", "bar"}},
20
24
}
21
25
22
26
for _, fix := range fixtures {
23
27
assert.Equal(fix.out, TokenizeText(fix.text))
28
+
}
29
+
}
30
+
31
+
func TestTokenizeTextWithCensorChars(t *testing.T) {
32
+
assert := assert.New(t)
33
+
34
+
fixtures := []struct {
35
+
text string
36
+
out []string
37
+
}{
38
+
{text: "", out: []string{}},
39
+
{text: "Hello, โลก!", out: []string{"hello", "โลก"}},
40
+
{text: "Gdańsk", out: []string{"gdansk"}},
41
+
{text: " foo1;bar2,baz3...", out: []string{"foo1", "bar2", "baz3"}},
42
+
{text: "foo*bar,foo&bar", out: []string{"foo*bar", "foo", "bar"}},
43
+
{text: "foo-bar,foo&bar", out: []string{"foo-bar", "foo", "bar"}},
44
+
{text: "foo_bar,foo&bar", out: []string{"foo_bar", "foo", "bar"}},
45
+
{text: "foo#bar,foo&bar", out: []string{"foo#bar", "foo", "bar"}},
46
+
}
47
+
48
+
for _, fix := range fixtures {
49
+
assert.Equal(fix.out, TokenizeTextSkippingCensorChars(fix.text))
50
+
}
51
+
}
52
+
53
+
func TestTokenizeTextWithCustomRegex(t *testing.T) {
54
+
assert := assert.New(t)
55
+
56
+
fixtures := []struct {
57
+
text string
58
+
out []string
59
+
}{
60
+
{text: "", out: []string{}},
61
+
{text: "Hello, โลก!", out: []string{"hello", "โลก"}},
62
+
{text: "Gdańsk", out: []string{"gdansk"}},
63
+
{text: " foo1;bar2,baz3...", out: []string{"foo1", "bar2", "baz3"}},
64
+
{text: "foo*bar", out: []string{"foo", "bar"}},
65
+
{text: "foo&bar,foo*bar", out: []string{"foo&bar", "foo", "bar"}},
66
+
}
67
+
68
+
regex := regexp.MustCompile(`[^\pL\pN\s&]`)
69
+
for _, fix := range fixtures {
70
+
assert.Equal(fix.out, TokenizeTextWithRegex(fix.text, regex))
24
71
}
25
72
}
26
73
+1
automod/pkg.go
+1
automod/pkg.go
+2
automod/rules/gtube.go
+2
automod/rules/gtube.go
···
16
16
if strings.Contains(post.Text, gtubeString) {
17
17
c.AddRecordLabel("spam")
18
18
c.Notify("slack")
19
+
c.AddRecordTag("gtube-record")
19
20
}
20
21
return nil
21
22
}
···
26
27
if profile.Description != nil && strings.Contains(*profile.Description, gtubeString) {
27
28
c.AddRecordLabel("spam")
28
29
c.Notify("slack")
30
+
c.AddAccountTag("gtuber-account")
29
31
}
30
32
return nil
31
33
}
+6
-5
automod/rules/harassment.go
+6
-5
automod/rules/harassment.go
···
8
8
"github.com/bluesky-social/indigo/atproto/syntax"
9
9
"github.com/bluesky-social/indigo/automod"
10
10
"github.com/bluesky-social/indigo/automod/countstore"
11
+
"github.com/bluesky-social/indigo/automod/helpers"
11
12
)
12
13
13
14
var _ automod.PostRuleFunc = HarassmentTargetInteractionPostRule
14
15
15
16
// looks for new accounts, which interact with frequently-harassed accounts, and report them for review
16
17
func HarassmentTargetInteractionPostRule(c *automod.RecordContext, post *appbsky.FeedPost) error {
17
-
if c.Account.Identity == nil || !AccountIsYoungerThan(&c.AccountContext, 24*time.Hour) {
18
+
if c.Account.Identity == nil || !helpers.AccountIsYoungerThan(&c.AccountContext, 24*time.Hour) {
18
19
return nil
19
20
}
20
21
21
22
var interactionDIDs []string
22
-
facets, err := ExtractFacets(post)
23
+
facets, err := helpers.ExtractFacets(post)
23
24
if err != nil {
24
25
return err
25
26
}
···
28
29
interactionDIDs = append(interactionDIDs, *pf.DID)
29
30
}
30
31
}
31
-
if post.Reply != nil && !IsSelfThread(c, post) {
32
+
if post.Reply != nil && !helpers.IsSelfThread(c, post) {
32
33
parentURI, err := syntax.ParseATURI(post.Reply.Parent.Uri)
33
34
if err != nil {
34
35
return err
···
57
58
return nil
58
59
}
59
60
60
-
interactionDIDs = dedupeStrings(interactionDIDs)
61
+
interactionDIDs = helpers.DedupeStrings(interactionDIDs)
61
62
for _, d := range interactionDIDs {
62
63
did, err := syntax.ParseDID(d)
63
64
if err != nil {
···
114
115
115
116
// looks for new accounts, which frequently post the same type of content
116
117
func HarassmentTrivialPostRule(c *automod.RecordContext, post *appbsky.FeedPost) error {
117
-
if c.Account.Identity == nil || !AccountIsYoungerThan(&c.AccountContext, 7*24*time.Hour) {
118
+
if c.Account.Identity == nil || !helpers.AccountIsYoungerThan(&c.AccountContext, 7*24*time.Hour) {
118
119
return nil
119
120
}
120
121
+43
-59
automod/rules/helpers.go
automod/helpers/bsky.go
+43
-59
automod/rules/helpers.go
automod/helpers/bsky.go
···
1
-
package rules
1
+
package helpers
2
2
3
3
import (
4
4
"fmt"
5
-
"regexp"
6
-
"time"
7
5
8
6
appbsky "github.com/bluesky-social/indigo/api/bsky"
9
7
"github.com/bluesky-social/indigo/atproto/syntax"
10
8
"github.com/bluesky-social/indigo/automod"
11
9
"github.com/bluesky-social/indigo/automod/keyword"
12
-
13
-
"github.com/spaolacci/murmur3"
14
10
)
15
11
16
-
func dedupeStrings(in []string) []string {
17
-
var out []string
18
-
seen := make(map[string]bool)
19
-
for _, v := range in {
20
-
if !seen[v] {
21
-
out = append(out, v)
22
-
seen[v] = true
23
-
}
24
-
}
25
-
return out
26
-
}
27
-
28
12
func ExtractHashtagsPost(post *appbsky.FeedPost) []string {
29
13
var tags []string
30
14
for _, tag := range post.Tags {
···
37
21
}
38
22
}
39
23
}
40
-
return dedupeStrings(tags)
24
+
return DedupeStrings(tags)
41
25
}
42
26
43
27
func NormalizeHashtag(raw string) string {
···
103
87
}
104
88
}
105
89
}
106
-
return dedupeStrings(out)
90
+
return DedupeStrings(out)
107
91
}
108
92
109
93
func ExtractBlobCIDsProfile(profile *appbsky.ActorProfile) []string {
···
114
98
if profile.Banner != nil {
115
99
out = append(out, profile.Banner.Ref.String())
116
100
}
117
-
return dedupeStrings(out)
101
+
return DedupeStrings(out)
118
102
}
119
103
120
104
func ExtractTextTokensPost(post *appbsky.FeedPost) []string {
···
152
136
return keyword.TokenizeText(s)
153
137
}
154
138
155
-
// based on: https://stackoverflow.com/a/48769624, with no trailing period allowed
156
-
var urlRegex = regexp.MustCompile(`(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-&?=%.]*[\w/\-&?=%]+`)
157
-
158
-
func ExtractTextURLs(raw string) []string {
159
-
return urlRegex.FindAllString(raw, -1)
160
-
}
161
-
162
139
func ExtractTextURLsProfile(profile *appbsky.ActorProfile) []string {
163
140
s := ""
164
141
if profile.Description != nil {
···
189
166
return true
190
167
}
191
168
return false
192
-
}
193
-
194
-
// returns a fast, compact hash of a string
195
-
//
196
-
// current implementation uses murmur3, default seed, and hex encoding
197
-
func HashOfString(s string) string {
198
-
val := murmur3.Sum64([]byte(s))
199
-
return fmt.Sprintf("%016x", val)
200
169
}
201
170
202
171
func ParentOrRootIsFollower(c *automod.RecordContext, post *appbsky.FeedPost) bool {
···
242
211
return false
243
212
}
244
213
245
-
// no accounts exist before this time
246
-
var atprotoAccountEpoch = time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
247
-
248
-
// returns true if account creation timestamp is plausible: not-nil, not in distant past, not in the future
249
-
func plausibleAccountCreation(when *time.Time) bool {
250
-
if when == nil {
214
+
func PostParentOrRootIsDid(post *appbsky.FeedPost, did string) bool {
215
+
if post.Reply == nil {
251
216
return false
252
217
}
253
-
// this is mostly to check for misconfigurations or null values (eg, UNIX epoch zero means "unknown" not actually 1970)
254
-
if !when.After(atprotoAccountEpoch) {
218
+
219
+
rootUri, err := syntax.ParseATURI(post.Reply.Root.Uri)
220
+
if err != nil || !rootUri.Authority().IsDID() {
255
221
return false
256
222
}
257
-
// a timestamp in the future would also indicate some misconfiguration
258
-
if when.After(time.Now().Add(time.Hour)) {
223
+
224
+
parentUri, err := syntax.ParseATURI(post.Reply.Parent.Uri)
225
+
if err != nil || !parentUri.Authority().IsDID() {
259
226
return false
260
227
}
261
-
return true
228
+
229
+
return rootUri.Authority().String() == did || parentUri.Authority().String() == did
262
230
}
263
231
264
-
// checks if account was created recently, based on either public or private account metadata. if metadata isn't available at all, or seems bogus, returns 'false'
265
-
func AccountIsYoungerThan(c *automod.AccountContext, age time.Duration) bool {
266
-
// TODO: consider swapping priority order here (and below)
267
-
if c.Account.CreatedAt != nil && plausibleAccountCreation(c.Account.CreatedAt) {
268
-
return time.Since(*c.Account.CreatedAt) < age
232
+
func PostParentOrRootIsAnyDid(post *appbsky.FeedPost, dids []string) bool {
233
+
if post.Reply == nil {
234
+
return false
269
235
}
270
-
if c.Account.Private != nil && plausibleAccountCreation(c.Account.Private.IndexedAt) {
271
-
return time.Since(*c.Account.Private.IndexedAt) < age
236
+
237
+
for _, did := range dids {
238
+
if PostParentOrRootIsDid(post, did) {
239
+
return true
240
+
}
272
241
}
242
+
273
243
return false
274
244
}
275
245
276
-
// checks if account was *not* created recently, based on either public or private account metadata. if metadata isn't available at all, or seems bogus, returns 'false'
277
-
func AccountIsOlderThan(c *automod.AccountContext, age time.Duration) bool {
278
-
if c.Account.CreatedAt != nil && plausibleAccountCreation(c.Account.CreatedAt) {
279
-
return time.Since(*c.Account.CreatedAt) >= age
246
+
func PostMentionsDid(post *appbsky.FeedPost, did string) bool {
247
+
facets, err := ExtractFacets(post)
248
+
if err != nil {
249
+
return false
250
+
}
251
+
252
+
for _, facet := range facets {
253
+
if facet.DID != nil && *facet.DID == did {
254
+
return true
255
+
}
280
256
}
281
-
if c.Account.Private != nil && plausibleAccountCreation(c.Account.Private.IndexedAt) {
282
-
return time.Since(*c.Account.Private.IndexedAt) >= age
257
+
258
+
return false
259
+
}
260
+
261
+
func PostMentionsAnyDid(post *appbsky.FeedPost, dids []string) bool {
262
+
for _, did := range dids {
263
+
if PostMentionsDid(post, did) {
264
+
return true
265
+
}
283
266
}
267
+
284
268
return false
285
269
}
-117
automod/rules/helpers_test.go
-117
automod/rules/helpers_test.go
···
1
-
package rules
2
-
3
-
import (
4
-
"testing"
5
-
"time"
6
-
7
-
"github.com/bluesky-social/indigo/atproto/identity"
8
-
"github.com/bluesky-social/indigo/atproto/syntax"
9
-
"github.com/bluesky-social/indigo/automod"
10
-
"github.com/bluesky-social/indigo/automod/keyword"
11
-
"github.com/stretchr/testify/assert"
12
-
)
13
-
14
-
func TestTokenizeText(t *testing.T) {
15
-
assert := assert.New(t)
16
-
17
-
fixtures := []struct {
18
-
s string
19
-
out []string
20
-
}{
21
-
{
22
-
s: "1 'Two' three!",
23
-
out: []string{"1", "two", "three"},
24
-
},
25
-
{
26
-
s: " foo1;bar2,baz3...",
27
-
out: []string{"foo1", "bar2", "baz3"},
28
-
},
29
-
{
30
-
s: "https://example.com/index.html",
31
-
out: []string{"https", "example", "com", "index", "html"},
32
-
},
33
-
}
34
-
35
-
for _, fix := range fixtures {
36
-
assert.Equal(fix.out, keyword.TokenizeText(fix.s))
37
-
}
38
-
}
39
-
40
-
func TestExtractURL(t *testing.T) {
41
-
assert := assert.New(t)
42
-
43
-
fixtures := []struct {
44
-
s string
45
-
out []string
46
-
}{
47
-
{
48
-
s: "this is a description with example.com mentioned in the middle",
49
-
out: []string{"example.com"},
50
-
},
51
-
{
52
-
s: "this is another example with https://en.wikipedia.org/index.html: and archive.org, and https://eff.org/... and bsky.app.",
53
-
out: []string{"https://en.wikipedia.org/index.html", "archive.org", "https://eff.org/", "bsky.app"},
54
-
},
55
-
}
56
-
57
-
for _, fix := range fixtures {
58
-
assert.Equal(fix.out, ExtractTextURLs(fix.s))
59
-
}
60
-
}
61
-
62
-
func TestHashOfString(t *testing.T) {
63
-
assert := assert.New(t)
64
-
65
-
// hashing function should be consistent over time
66
-
assert.Equal("4e6f69c0e3d10992", HashOfString("dummy-value"))
67
-
}
68
-
69
-
func TestAccountIsYoungerThan(t *testing.T) {
70
-
assert := assert.New(t)
71
-
72
-
am := automod.AccountMeta{
73
-
Identity: &identity.Identity{
74
-
DID: syntax.DID("did:plc:abc111"),
75
-
Handle: syntax.Handle("handle.example.com"),
76
-
},
77
-
Profile: automod.ProfileSummary{},
78
-
Private: nil,
79
-
}
80
-
now := time.Now()
81
-
ac := automod.AccountContext{
82
-
Account: am,
83
-
}
84
-
assert.False(AccountIsYoungerThan(&ac, time.Hour))
85
-
assert.False(AccountIsOlderThan(&ac, time.Hour))
86
-
87
-
ac.Account.CreatedAt = &now
88
-
assert.True(AccountIsYoungerThan(&ac, time.Hour))
89
-
assert.False(AccountIsOlderThan(&ac, time.Hour))
90
-
91
-
yesterday := time.Now().Add(-1 * time.Hour * 24)
92
-
ac.Account.CreatedAt = &yesterday
93
-
assert.False(AccountIsYoungerThan(&ac, time.Hour))
94
-
assert.True(AccountIsOlderThan(&ac, time.Hour))
95
-
96
-
old := time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC)
97
-
ac.Account.CreatedAt = &old
98
-
assert.False(AccountIsYoungerThan(&ac, time.Hour))
99
-
assert.False(AccountIsYoungerThan(&ac, time.Hour*24*365*100))
100
-
assert.False(AccountIsOlderThan(&ac, time.Hour))
101
-
assert.False(AccountIsOlderThan(&ac, time.Hour*24*365*100))
102
-
103
-
future := time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC)
104
-
ac.Account.CreatedAt = &future
105
-
assert.False(AccountIsYoungerThan(&ac, time.Hour))
106
-
assert.False(AccountIsOlderThan(&ac, time.Hour))
107
-
108
-
ac.Account.CreatedAt = nil
109
-
ac.Account.Private = &automod.AccountPrivate{
110
-
Email: "account@example.com",
111
-
IndexedAt: &yesterday,
112
-
}
113
-
assert.True(AccountIsYoungerThan(&ac, 48*time.Hour))
114
-
assert.False(AccountIsYoungerThan(&ac, time.Hour))
115
-
assert.True(AccountIsOlderThan(&ac, time.Hour))
116
-
assert.False(AccountIsOlderThan(&ac, 48*time.Hour))
117
-
}
+2
-1
automod/rules/identity.go
+2
-1
automod/rules/identity.go
···
7
7
8
8
"github.com/bluesky-social/indigo/automod"
9
9
"github.com/bluesky-social/indigo/automod/countstore"
10
+
"github.com/bluesky-social/indigo/automod/helpers"
10
11
)
11
12
12
13
// triggers on first identity event for an account (DID)
13
14
func NewAccountRule(c *automod.AccountContext) error {
14
-
if c.Account.Identity == nil || !AccountIsYoungerThan(c, 4*time.Hour) {
15
+
if c.Account.Identity == nil || !helpers.AccountIsYoungerThan(c, 4*time.Hour) {
15
16
return nil
16
17
}
17
18
+5
-4
automod/rules/keyword.go
+5
-4
automod/rules/keyword.go
···
7
7
8
8
appbsky "github.com/bluesky-social/indigo/api/bsky"
9
9
"github.com/bluesky-social/indigo/automod"
10
+
"github.com/bluesky-social/indigo/automod/helpers"
10
11
"github.com/bluesky-social/indigo/automod/keyword"
11
12
)
12
13
···
17
18
isJapanese = true
18
19
}
19
20
}
20
-
for _, tok := range ExtractTextTokensPost(post) {
21
+
for _, tok := range helpers.ExtractTextTokensPost(post) {
21
22
word := keyword.SlugIsExplicitSlur(tok)
22
23
// used very frequently in a reclaimed context
23
24
if word != "" && word != "faggot" && word != "tranny" && word != "coon" && !(word == "kike" && isJapanese) {
···
54
55
//c.Notify("slack")
55
56
}
56
57
}
57
-
for _, tok := range ExtractTextTokensProfile(profile) {
58
+
for _, tok := range helpers.ExtractTextTokensProfile(profile) {
58
59
// de-pluralize
59
60
tok = strings.TrimSuffix(tok, "s")
60
61
if c.InSet("worst-words", tok) {
···
71
72
72
73
// looks for the specific harassment situation of a replay to another user with only a single word
73
74
func ReplySingleBadWordPostRule(c *automod.RecordContext, post *appbsky.FeedPost) error {
74
-
if post.Reply != nil && !IsSelfThread(c, post) {
75
-
tokens := ExtractTextTokensPost(post)
75
+
if post.Reply != nil && !helpers.IsSelfThread(c, post) {
76
+
tokens := helpers.ExtractTextTokensPost(post)
76
77
if len(tokens) != 1 {
77
78
return nil
78
79
}
+2
-1
automod/rules/mentions.go
+2
-1
automod/rules/mentions.go
···
8
8
"github.com/bluesky-social/indigo/atproto/syntax"
9
9
"github.com/bluesky-social/indigo/automod"
10
10
"github.com/bluesky-social/indigo/automod/countstore"
11
+
"github.com/bluesky-social/indigo/automod/helpers"
11
12
)
12
13
13
14
var _ automod.PostRuleFunc = DistinctMentionsRule
···
47
48
var _ automod.PostRuleFunc = YoungAccountDistinctMentionsRule
48
49
49
50
func YoungAccountDistinctMentionsRule(c *automod.RecordContext, post *appbsky.FeedPost) error {
50
-
if c.Account.Identity == nil || !AccountIsYoungerThan(&c.AccountContext, 14*24*time.Hour) {
51
+
if c.Account.Identity == nil || !helpers.AccountIsYoungerThan(&c.AccountContext, 14*24*time.Hour) {
51
52
return nil
52
53
}
53
54
+4
-3
automod/rules/misleading.go
+4
-3
automod/rules/misleading.go
···
9
9
appbsky "github.com/bluesky-social/indigo/api/bsky"
10
10
"github.com/bluesky-social/indigo/atproto/syntax"
11
11
"github.com/bluesky-social/indigo/automod"
12
+
"github.com/bluesky-social/indigo/automod/helpers"
12
13
)
13
14
14
-
func isMisleadingURLFacet(facet PostFacet, logger *slog.Logger) bool {
15
+
func isMisleadingURLFacet(facet helpers.PostFacet, logger *slog.Logger) bool {
15
16
linkURL, err := url.Parse(*facet.URL)
16
17
if err != nil {
17
18
logger.Warn("invalid link metadata URL", "url", facet.URL)
···
84
85
if c.Account.Identity.Handle == "nowbreezing.ntw.app" {
85
86
return nil
86
87
}
87
-
facets, err := ExtractFacets(post)
88
+
facets, err := helpers.ExtractFacets(post)
88
89
if err != nil {
89
90
c.Logger.Warn("invalid facets", "err", err)
90
91
// TODO: or some other "this record is corrupt" indicator?
···
105
106
var _ automod.PostRuleFunc = MisleadingMentionPostRule
106
107
107
108
func MisleadingMentionPostRule(c *automod.RecordContext, post *appbsky.FeedPost) error {
108
-
facets, err := ExtractFacets(post)
109
+
facets, err := helpers.ExtractFacets(post)
109
110
if err != nil {
110
111
c.Logger.Warn("invalid facets", "err", err)
111
112
// TODO: or some other "this record is corrupt" indicator?
+11
-10
automod/rules/misleading_test.go
+11
-10
automod/rules/misleading_test.go
···
11
11
"github.com/bluesky-social/indigo/atproto/syntax"
12
12
"github.com/bluesky-social/indigo/automod"
13
13
"github.com/bluesky-social/indigo/automod/engine"
14
+
"github.com/bluesky-social/indigo/automod/helpers"
14
15
15
16
"github.com/stretchr/testify/assert"
16
17
)
···
118
119
logger := slog.Default()
119
120
120
121
fixtures := []struct {
121
-
facet PostFacet
122
+
facet helpers.PostFacet
122
123
out bool
123
124
}{
124
125
{
125
-
facet: PostFacet{
126
+
facet: helpers.PostFacet{
126
127
Text: "https://atproto.com",
127
128
URL: pstr("https://atproto.com"),
128
129
},
129
130
out: false,
130
131
},
131
132
{
132
-
facet: PostFacet{
133
+
facet: helpers.PostFacet{
133
134
Text: "https://atproto.com",
134
135
URL: pstr("https://evil.com"),
135
136
},
136
137
out: true,
137
138
},
138
139
{
139
-
facet: PostFacet{
140
+
facet: helpers.PostFacet{
140
141
Text: "https://www.atproto.com",
141
142
URL: pstr("https://atproto.com"),
142
143
},
143
144
out: false,
144
145
},
145
146
{
146
-
facet: PostFacet{
147
+
facet: helpers.PostFacet{
147
148
Text: "https://atproto.com",
148
149
URL: pstr("https://www.atproto.com"),
149
150
},
150
151
out: false,
151
152
},
152
153
{
153
-
facet: PostFacet{
154
+
facet: helpers.PostFacet{
154
155
Text: "[example.com]",
155
156
URL: pstr("https://www.example.com"),
156
157
},
157
158
out: false,
158
159
},
159
160
{
160
-
facet: PostFacet{
161
+
facet: helpers.PostFacet{
161
162
Text: "example.com...",
162
163
URL: pstr("https://example.com.evil.com"),
163
164
},
164
165
out: true,
165
166
},
166
167
{
167
-
facet: PostFacet{
168
+
facet: helpers.PostFacet{
168
169
Text: "ATPROTO.com...",
169
170
URL: pstr("https://atproto.com"),
170
171
},
171
172
out: false,
172
173
},
173
174
{
174
-
facet: PostFacet{
175
+
facet: helpers.PostFacet{
175
176
Text: "1234.5678",
176
177
URL: pstr("https://arxiv.org/abs/1234.5678"),
177
178
},
178
179
out: false,
179
180
},
180
181
{
181
-
facet: PostFacet{
182
+
facet: helpers.PostFacet{
182
183
Text: "www.techdirt.com…",
183
184
URL: pstr("https://www.techdirt.com/"),
184
185
},
+2
-1
automod/rules/nostr.go
+2
-1
automod/rules/nostr.go
···
7
7
8
8
appbsky "github.com/bluesky-social/indigo/api/bsky"
9
9
"github.com/bluesky-social/indigo/automod"
10
+
"github.com/bluesky-social/indigo/automod/helpers"
10
11
)
11
12
12
13
var _ automod.PostRuleFunc = NostrSpamPostRule
13
14
14
15
// looks for new accounts, which frequently post the same type of content
15
16
func NostrSpamPostRule(c *automod.RecordContext, post *appbsky.FeedPost) error {
16
-
if c.Account.Identity == nil || !AccountIsYoungerThan(&c.AccountContext, 2*24*time.Hour) {
17
+
if c.Account.Identity == nil || !helpers.AccountIsYoungerThan(&c.AccountContext, 2*24*time.Hour) {
17
18
return nil
18
19
}
19
20
+5
-4
automod/rules/promo.go
+5
-4
automod/rules/promo.go
···
9
9
appbsky "github.com/bluesky-social/indigo/api/bsky"
10
10
"github.com/bluesky-social/indigo/automod"
11
11
"github.com/bluesky-social/indigo/automod/countstore"
12
+
"github.com/bluesky-social/indigo/automod/helpers"
12
13
)
13
14
14
15
var _ automod.PostRuleFunc = AggressivePromotionRule
···
17
18
//
18
19
// this rule depends on ReplyCountPostRule() to set counts
19
20
func AggressivePromotionRule(c *automod.RecordContext, post *appbsky.FeedPost) error {
20
-
if c.Account.Identity == nil || !AccountIsYoungerThan(&c.AccountContext, 7*24*time.Hour) {
21
+
if c.Account.Identity == nil || !helpers.AccountIsYoungerThan(&c.AccountContext, 7*24*time.Hour) {
21
22
return nil
22
23
}
23
-
if post.Reply == nil || IsSelfThread(c, post) {
24
+
if post.Reply == nil || helpers.IsSelfThread(c, post) {
24
25
return nil
25
26
}
26
27
27
-
allURLs := ExtractTextURLs(post.Text)
28
+
allURLs := helpers.ExtractTextURLs(post.Text)
28
29
if c.Account.Profile.Description != nil {
29
-
profileURLs := ExtractTextURLs(*c.Account.Profile.Description)
30
+
profileURLs := helpers.ExtractTextURLs(*c.Account.Profile.Description)
30
31
allURLs = append(allURLs, profileURLs...)
31
32
}
32
33
hasPromo := false
+3
-2
automod/rules/quick.go
+3
-2
automod/rules/quick.go
···
7
7
8
8
appbsky "github.com/bluesky-social/indigo/api/bsky"
9
9
"github.com/bluesky-social/indigo/automod"
10
+
"github.com/bluesky-social/indigo/automod/helpers"
10
11
)
11
12
12
13
var botLinkStrings = []string{"ainna13762491", "LINK押して", "→ https://tiny", "⇒ http://tiny"}
···
54
55
var _ automod.IdentityRuleFunc = NewAccountBotEmailRule
55
56
56
57
func NewAccountBotEmailRule(c *automod.AccountContext) error {
57
-
if c.Account.Identity == nil || !AccountIsYoungerThan(c, 1*time.Hour) {
58
+
if c.Account.Identity == nil || !helpers.AccountIsYoungerThan(c, 1*time.Hour) {
58
59
return nil
59
60
}
60
61
···
73
74
74
75
// looks for new accounts, which frequently post the same type of content
75
76
func TrivialSpamPostRule(c *automod.RecordContext, post *appbsky.FeedPost) error {
76
-
if c.Account.Identity == nil || !AccountIsYoungerThan(&c.AccountContext, 8*24*time.Hour) {
77
+
if c.Account.Identity == nil || !helpers.AccountIsYoungerThan(&c.AccountContext, 8*24*time.Hour) {
77
78
return nil
78
79
}
79
80
+13
-12
automod/rules/replies.go
+13
-12
automod/rules/replies.go
···
9
9
"github.com/bluesky-social/indigo/atproto/syntax"
10
10
"github.com/bluesky-social/indigo/automod"
11
11
"github.com/bluesky-social/indigo/automod/countstore"
12
+
"github.com/bluesky-social/indigo/automod/helpers"
12
13
)
13
14
14
15
var _ automod.PostRuleFunc = ReplyCountPostRule
15
16
16
17
// does not count "self-replies" (direct to self, or in own post thread)
17
18
func ReplyCountPostRule(c *automod.RecordContext, post *appbsky.FeedPost) error {
18
-
if post.Reply == nil || IsSelfThread(c, post) {
19
+
if post.Reply == nil || helpers.IsSelfThread(c, post) {
19
20
return nil
20
21
}
21
22
···
47
48
//
48
49
// There can be legitimate situations that trigger this rule, so in most situations should be a "report" not "label" action.
49
50
func IdenticalReplyPostRule(c *automod.RecordContext, post *appbsky.FeedPost) error {
50
-
if post.Reply == nil || IsSelfThread(c, post) {
51
+
if post.Reply == nil || helpers.IsSelfThread(c, post) {
51
52
return nil
52
53
}
53
54
···
55
56
if utf8.RuneCountInString(post.Text) <= 10 {
56
57
return nil
57
58
}
58
-
if AccountIsOlderThan(&c.AccountContext, 14*24*time.Hour) {
59
+
if helpers.AccountIsOlderThan(&c.AccountContext, 14*24*time.Hour) {
59
60
return nil
60
61
}
61
62
62
63
// don't count if there is a follow-back relationship
63
-
if ParentOrRootIsFollower(c, post) {
64
+
if helpers.ParentOrRootIsFollower(c, post) {
64
65
return nil
65
66
}
66
67
67
68
// increment before read. use a specific period (IncrementPeriod()) to reduce the number of counters (one per unique post text)
68
69
period := countstore.PeriodDay
69
-
bucket := c.Account.Identity.DID.String() + "/" + HashOfString(post.Text)
70
+
bucket := c.Account.Identity.DID.String() + "/" + helpers.HashOfString(post.Text)
70
71
c.IncrementPeriod("reply-text", bucket, period)
71
72
72
73
count := c.GetCount("reply-text", bucket, period)
···
91
92
var _ automod.PostRuleFunc = IdenticalReplyPostSameParentRule
92
93
93
94
func IdenticalReplyPostSameParentRule(c *automod.RecordContext, post *appbsky.FeedPost) error {
94
-
if post.Reply == nil || IsSelfThread(c, post) {
95
+
if post.Reply == nil || helpers.IsSelfThread(c, post) {
95
96
return nil
96
97
}
97
98
98
-
if ParentOrRootIsFollower(c, post) {
99
+
if helpers.ParentOrRootIsFollower(c, post) {
99
100
return nil
100
101
}
101
102
102
103
postCount := c.Account.PostsCount
103
-
if AccountIsOlderThan(&c.AccountContext, identicalReplySameParentMaxAge) || postCount >= identicalReplySameParentMaxPosts {
104
+
if helpers.AccountIsOlderThan(&c.AccountContext, identicalReplySameParentMaxAge) || postCount >= identicalReplySameParentMaxPosts {
104
105
return nil
105
106
}
106
107
107
108
period := countstore.PeriodHour
108
-
bucket := c.Account.Identity.DID.String() + "/" + post.Reply.Parent.Uri + "/" + HashOfString(post.Text)
109
+
bucket := c.Account.Identity.DID.String() + "/" + post.Reply.Parent.Uri + "/" + helpers.HashOfString(post.Text)
109
110
c.IncrementPeriod("reply-text-same-post", bucket, period)
110
111
111
112
count := c.GetCount("reply-text-same-post", bucket, period)
···
126
127
127
128
func YoungAccountDistinctRepliesRule(c *automod.RecordContext, post *appbsky.FeedPost) error {
128
129
// only replies, and skip self-replies (eg, threads)
129
-
if post.Reply == nil || IsSelfThread(c, post) {
130
+
if post.Reply == nil || helpers.IsSelfThread(c, post) {
130
131
return nil
131
132
}
132
133
···
134
135
if utf8.RuneCountInString(post.Text) <= 10 {
135
136
return nil
136
137
}
137
-
if AccountIsOlderThan(&c.AccountContext, 14*24*time.Hour) {
138
+
if helpers.AccountIsOlderThan(&c.AccountContext, 14*24*time.Hour) {
138
139
return nil
139
140
}
140
141
141
142
// don't count if there is a follow-back relationship
142
-
if ParentOrRootIsFollower(c, post) {
143
+
if helpers.ParentOrRootIsFollower(c, post) {
143
144
return nil
144
145
}
145
146
+2
-1
automod/rules/reposts.go
+2
-1
automod/rules/reposts.go
···
7
7
8
8
"github.com/bluesky-social/indigo/automod"
9
9
"github.com/bluesky-social/indigo/automod/countstore"
10
+
"github.com/bluesky-social/indigo/automod/helpers"
10
11
)
11
12
12
13
var dailyRepostThresholdWithoutPost = 30
···
18
19
// looks for accounts which do frequent reposts
19
20
func TooManyRepostRule(c *automod.RecordContext) error {
20
21
// Don't bother checking reposts from accounts older than 30 days
21
-
if c.Account.Identity == nil || !AccountIsYoungerThan(&c.AccountContext, 30*24*time.Hour) {
22
+
if c.Account.Identity == nil || !helpers.AccountIsYoungerThan(&c.AccountContext, 30*24*time.Hour) {
22
23
return nil
23
24
}
24
25
+2
-2
automod/visual/hiveai_rule.go
+2
-2
automod/visual/hiveai_rule.go
···
5
5
"time"
6
6
7
7
"github.com/bluesky-social/indigo/automod"
8
-
"github.com/bluesky-social/indigo/automod/rules"
8
+
"github.com/bluesky-social/indigo/automod/helpers"
9
9
lexutil "github.com/bluesky-social/indigo/lex/util"
10
10
)
11
11
···
43
43
44
44
for _, l := range labels {
45
45
// NOTE: experimenting with profile reporting for new accounts
46
-
if l == "sexual" && c.RecordOp.Collection.String() == "app.bsky.actor.profile" && rules.AccountIsYoungerThan(&c.AccountContext, 2*24*time.Hour) {
46
+
if l == "sexual" && c.RecordOp.Collection.String() == "app.bsky.actor.profile" && helpers.AccountIsYoungerThan(&c.AccountContext, 2*24*time.Hour) {
47
47
c.ReportRecord(automod.ReportReasonSexual, "possible sexual profile (not labeled yet)")
48
48
c.Logger.Info("skipping record label", "label", l, "reason", "sexual-profile-experiment")
49
49
} else {
+33
-11
bgs/bgs.go
+33
-11
bgs/bgs.go
···
107
107
}
108
108
109
109
type BGSConfig struct {
110
-
SSL bool
111
-
CompactInterval time.Duration
112
-
DefaultRepoLimit int64
113
-
ConcurrencyPerPDS int64
114
-
MaxQueuePerPDS int64
110
+
SSL bool
111
+
CompactInterval time.Duration
112
+
DefaultRepoLimit int64
113
+
ConcurrencyPerPDS int64
114
+
MaxQueuePerPDS int64
115
+
NumCompactionWorkers int
115
116
}
116
117
117
118
func DefaultBGSConfig() *BGSConfig {
118
119
return &BGSConfig{
119
-
SSL: true,
120
-
CompactInterval: 4 * time.Hour,
121
-
DefaultRepoLimit: 100,
122
-
ConcurrencyPerPDS: 100,
123
-
MaxQueuePerPDS: 1_000,
120
+
SSL: true,
121
+
CompactInterval: 4 * time.Hour,
122
+
DefaultRepoLimit: 100,
123
+
ConcurrencyPerPDS: 100,
124
+
MaxQueuePerPDS: 1_000,
125
+
NumCompactionWorkers: 2,
124
126
}
125
127
}
126
128
···
168
170
return nil, err
169
171
}
170
172
171
-
compactor := NewCompactor(nil)
173
+
cOpts := DefaultCompactorOptions()
174
+
cOpts.NumWorkers = config.NumCompactionWorkers
175
+
compactor := NewCompactor(cOpts)
172
176
compactor.requeueInterval = config.CompactInterval
173
177
compactor.Start(bgs)
174
178
bgs.compactor = compactor
···
349
353
e.GET("/xrpc/com.atproto.sync.notifyOfUpdate", bgs.HandleComAtprotoSyncNotifyOfUpdate)
350
354
e.GET("/xrpc/_health", bgs.HandleHealthCheck)
351
355
e.GET("/_health", bgs.HandleHealthCheck)
356
+
e.GET("/", bgs.HandleHomeMessage)
352
357
353
358
admin := e.Group("/admin", bgs.checkAdminAuth)
354
359
···
418
423
} else {
419
424
return c.JSON(200, HealthStatus{Status: "ok"})
420
425
}
426
+
}
427
+
428
+
var homeMessage string = `
429
+
d8888b. d888888b d888b .d8888. db dD db db
430
+
88 '8D '88' 88' Y8b 88' YP 88 ,8P' '8b d8'
431
+
88oooY' 88 88 '8bo. 88,8P '8bd8'
432
+
88~~~b. 88 88 ooo 'Y8b. 88'8b 88
433
+
88 8D .88. 88. ~8~ db 8D 88 '88. 88
434
+
Y8888P' Y888888P Y888P '8888Y' YP YD YP
435
+
436
+
This is an atproto [https://atproto.com] relay instance, running the 'bigsky' codebase [https://github.com/bluesky-social/indigo]
437
+
438
+
The firehose WebSocket path is at: /xrpc/com.atproto.sync.subscribeRepos
439
+
`
440
+
441
+
func (bgs *BGS) HandleHomeMessage(c echo.Context) error {
442
+
return c.String(http.StatusOK, homeMessage)
421
443
}
422
444
423
445
type AuthToken struct {
+18
-17
cmd/beemo/notify_reports.go
+18
-17
cmd/beemo/notify_reports.go
···
68
68
xrpcc.Auth.RefreshJwt = refresh.RefreshJwt
69
69
70
70
// query just new reports (regardless of resolution state)
71
-
// ModerationQueryEvents(ctx context.Context, c *xrpc.Client, createdBy string, cursor string, includeAllUserRecords bool, limit int64, sortDirection string, subject string, types []string) (*ModerationQueryEvents_Output, error)
72
71
var limit int64 = 50
73
72
me, err := toolsozone.ModerationQueryEvents(
74
73
cctx.Context,
75
74
xrpcc,
76
-
nil,
77
-
nil,
78
-
"",
79
-
"",
80
-
"",
81
-
"",
82
-
"",
83
-
false,
84
-
true,
85
-
limit,
86
-
nil,
87
-
nil,
88
-
nil,
89
-
"",
90
-
"",
91
-
[]string{"tools.ozone.moderation.defs#modEventReport"},
75
+
nil, // addedLabels []string
76
+
nil, // addedTags []string
77
+
nil, // collections []string
78
+
"", // comment string
79
+
"", // createdAfter string
80
+
"", // createdBefore string
81
+
"", // createdBy string
82
+
"", // cursor string
83
+
false, // hasComment bool
84
+
true, // includeAllUserRecords bool
85
+
limit, // limit int64
86
+
nil, // removedLabels []string
87
+
nil, // removedTags []string
88
+
nil, // reportTypes []string
89
+
"", // sortDirection string
90
+
"", // subject string
91
+
"", // subjectType string
92
+
[]string{"tools.ozone.moderation.defs#modEventReport"}, // types []string
92
93
)
93
94
if err != nil {
94
95
return err
+20
-4
cmd/bigsky/main.go
+20
-4
cmd/bigsky/main.go
···
85
85
Name: "data-dir",
86
86
Usage: "path of directory for CAR files and other data",
87
87
Value: "data/bigsky",
88
-
EnvVars: []string{"DATA_DIR"},
88
+
EnvVars: []string{"RELAY_DATA_DIR", "DATA_DIR"},
89
89
},
90
90
&cli.StringFlag{
91
91
Name: "plc-host",
···
112
112
EnvVars: []string{"RELAY_METRICS_LISTEN", "BGS_METRICS_LISTEN"},
113
113
},
114
114
&cli.StringFlag{
115
-
Name: "disk-persister-dir",
116
-
Usage: "set directory for disk persister (implicitly enables disk persister)",
115
+
Name: "disk-persister-dir",
116
+
Usage: "set directory for disk persister (implicitly enables disk persister)",
117
+
EnvVars: []string{"RELAY_PERSISTER_DIR"},
117
118
},
118
119
&cli.StringFlag{
119
120
Name: "admin-key",
···
188
189
EnvVars: []string{"RELAY_DID_CACHE_SIZE"},
189
190
Value: 5_000_000,
190
191
},
192
+
&cli.DurationFlag{
193
+
Name: "event-playback-ttl",
194
+
Usage: "time to live for event playback buffering (only applies to disk persister)",
195
+
EnvVars: []string{"RELAY_EVENT_PLAYBACK_TTL"},
196
+
Value: 72 * time.Hour,
197
+
},
198
+
&cli.IntFlag{
199
+
Name: "num-compaction-workers",
200
+
EnvVars: []string{"RELAY_NUM_COMPACTION_WORKERS"},
201
+
Value: 2,
202
+
},
191
203
}
192
204
193
205
app.Action = runBigsky
···
327
339
328
340
if dpd := cctx.String("disk-persister-dir"); dpd != "" {
329
341
log.Infow("setting up disk persister")
330
-
dp, err := events.NewDiskPersistence(dpd, "", db, events.DefaultDiskPersistOptions())
342
+
343
+
pOpts := events.DefaultDiskPersistOptions()
344
+
pOpts.Retention = cctx.Duration("event-playback-ttl")
345
+
dp, err := events.NewDiskPersistence(dpd, "", db, pOpts)
331
346
if err != nil {
332
347
return fmt.Errorf("setting up disk persister: %w", err)
333
348
}
···
403
418
bgsConfig.ConcurrencyPerPDS = cctx.Int64("concurrency-per-pds")
404
419
bgsConfig.MaxQueuePerPDS = cctx.Int64("max-queue-per-pds")
405
420
bgsConfig.DefaultRepoLimit = cctx.Int64("default-repo-limit")
421
+
bgsConfig.NumCompactionWorkers = cctx.Int("num-compaction-workers")
406
422
bgs, err := libbgs.NewBGS(db, ix, repoman, evtman, cachedidr, rf, hr, bgsConfig)
407
423
if err != nil {
408
424
return err
+2
-3
cmd/goat/account_migrate.go
+2
-3
cmd/goat/account_migrate.go
···
10
10
"time"
11
11
12
12
comatproto "github.com/bluesky-social/indigo/api/atproto"
13
-
appbsky "github.com/bluesky-social/indigo/api/bsky"
14
13
"github.com/bluesky-social/indigo/atproto/syntax"
15
14
"github.com/bluesky-social/indigo/xrpc"
16
15
···
167
166
168
167
slog.Info("migrating preferences")
169
168
// TODO: service proxy header for AppView?
170
-
prefResp, err := appbsky.ActorGetPreferences(ctx, oldClient)
169
+
prefResp, err := ActorGetPreferences(ctx, oldClient)
171
170
if err != nil {
172
171
return fmt.Errorf("failed fetching old preferences: %w", err)
173
172
}
174
-
err = appbsky.ActorPutPreferences(ctx, &newClient, &appbsky.ActorPutPreferences_Input{
173
+
err = ActorPutPreferences(ctx, &newClient, &ActorPutPreferences_Input{
175
174
Preferences: prefResp.Preferences,
176
175
})
177
176
if err != nil {
+28
cmd/goat/actorgetPreferences.go
+28
cmd/goat/actorgetPreferences.go
···
1
+
// Copied from indigo:api/atproto/actorgetPreferences.go
2
+
3
+
package main
4
+
5
+
// schema: app.bsky.actor.getPreferences
6
+
7
+
import (
8
+
"context"
9
+
10
+
"github.com/bluesky-social/indigo/xrpc"
11
+
)
12
+
13
+
// ActorGetPreferences_Output is the output of a app.bsky.actor.getPreferences call.
14
+
type ActorGetPreferences_Output struct {
15
+
Preferences []map[string]any `json:"preferences" cborgen:"preferences"`
16
+
}
17
+
18
+
// ActorGetPreferences calls the XRPC method "app.bsky.actor.getPreferences".
19
+
func ActorGetPreferences(ctx context.Context, c *xrpc.Client) (*ActorGetPreferences_Output, error) {
20
+
var out ActorGetPreferences_Output
21
+
22
+
params := map[string]interface{}{}
23
+
if err := c.Do(ctx, xrpc.Query, "", "app.bsky.actor.getPreferences", params, nil, &out); err != nil {
24
+
return nil, err
25
+
}
26
+
27
+
return &out, nil
28
+
}
+25
cmd/goat/actorputPreferences.go
+25
cmd/goat/actorputPreferences.go
···
1
+
// Copied from indigo:api/atproto/actorputPreferences.go
2
+
3
+
package main
4
+
5
+
// schema: app.bsky.actor.putPreferences
6
+
7
+
import (
8
+
"context"
9
+
10
+
"github.com/bluesky-social/indigo/xrpc"
11
+
)
12
+
13
+
// ActorPutPreferences_Input is the input argument to a app.bsky.actor.putPreferences call.
14
+
type ActorPutPreferences_Input struct {
15
+
Preferences []map[string]any `json:"preferences" cborgen:"preferences"`
16
+
}
17
+
18
+
// ActorPutPreferences calls the XRPC method "app.bsky.actor.putPreferences".
19
+
func ActorPutPreferences(ctx context.Context, c *xrpc.Client, input *ActorPutPreferences_Input) error {
20
+
if err := c.Do(ctx, xrpc.Procedure, "application/json", "app.bsky.actor.putPreferences", nil, input, nil); err != nil {
21
+
return err
22
+
}
23
+
24
+
return nil
25
+
}
+4
-8
cmd/goat/bsky_prefs.go
+4
-8
cmd/goat/bsky_prefs.go
···
6
6
"fmt"
7
7
"os"
8
8
9
-
appbsky "github.com/bluesky-social/indigo/api/bsky"
10
-
11
9
"github.com/urfave/cli/v2"
12
10
)
13
11
···
41
39
}
42
40
43
41
// TODO: does indigo API code crash with unsupported preference '$type'? Eg "Lexicon decoder" with unsupported type.
44
-
resp, err := appbsky.ActorGetPreferences(ctx, xrpcc)
42
+
resp, err := ActorGetPreferences(ctx, xrpcc)
45
43
if err != nil {
46
44
return fmt.Errorf("failed fetching old preferences: %w", err)
47
45
}
···
74
72
return err
75
73
}
76
74
77
-
var prefsArray []appbsky.ActorDefs_Preferences_Elem
78
-
err = json.Unmarshal(prefsBytes, &prefsArray)
79
-
if err != nil {
75
+
var prefsArray []map[string]any
76
+
if err = json.Unmarshal(prefsBytes, &prefsArray); err != nil {
80
77
return err
81
78
}
82
79
83
-
// WARNING: might clobber off-Lexicon or new-Lexicon data fields (which don't round-trip deserialization)
84
-
err = appbsky.ActorPutPreferences(ctx, xrpcc, &appbsky.ActorPutPreferences_Input{
80
+
err = ActorPutPreferences(ctx, xrpcc, &ActorPutPreferences_Input{
85
81
Preferences: prefsArray,
86
82
})
87
83
if err != nil {
+8
-11
cmd/goat/record.go
+8
-11
cmd/goat/record.go
···
10
10
"github.com/bluesky-social/indigo/atproto/data"
11
11
"github.com/bluesky-social/indigo/atproto/identity"
12
12
"github.com/bluesky-social/indigo/atproto/syntax"
13
-
lexutil "github.com/bluesky-social/indigo/lex/util"
14
13
"github.com/bluesky-social/indigo/xrpc"
15
14
16
15
"github.com/urfave/cli/v2"
···
231
230
return err
232
231
}
233
232
234
-
// TODO: replace this with something that allows arbitrary Lexicons, instead of needing registered types
235
-
var recordVal lexutil.LexiconTypeDecoder
236
-
if err = recordVal.UnmarshalJSON(recordBytes); err != nil {
233
+
recordVal, err := data.UnmarshalJSON(recordBytes)
234
+
if err != nil {
237
235
return err
238
236
}
239
237
···
248
246
}
249
247
validate := !cctx.Bool("no-validate")
250
248
251
-
resp, err := comatproto.RepoCreateRecord(ctx, xrpcc, &comatproto.RepoCreateRecord_Input{
249
+
resp, err := RepoCreateRecord(ctx, xrpcc, &RepoCreateRecord_Input{
252
250
Collection: nsid,
253
251
Repo: xrpcc.Auth.Did,
254
-
Record: &recordVal,
252
+
Record: recordVal,
255
253
Rkey: rkey,
256
254
Validate: &validate,
257
255
})
···
300
298
return err
301
299
}
302
300
303
-
// TODO: replace this with something that allows arbitrary Lexicons, instead of needing registered types
304
-
var recordVal lexutil.LexiconTypeDecoder
305
-
if err = recordVal.UnmarshalJSON(recordBytes); err != nil {
301
+
recordVal, err := data.UnmarshalJSON(recordBytes)
302
+
if err != nil {
306
303
return err
307
304
}
308
305
309
306
validate := !cctx.Bool("no-validate")
310
307
311
-
resp, err := comatproto.RepoPutRecord(ctx, xrpcc, &comatproto.RepoPutRecord_Input{
308
+
resp, err := RepoPutRecord(ctx, xrpcc, &RepoPutRecord_Input{
312
309
Collection: nsid,
313
310
Repo: xrpcc.Auth.Did,
314
-
Record: &recordVal,
311
+
Record: recordVal,
315
312
Rkey: rkey,
316
313
Validate: &validate,
317
314
SwapRecord: existing.Cid,
+51
cmd/goat/repocreateRecord.go
+51
cmd/goat/repocreateRecord.go
···
1
+
// Copied from indigo:api/atproto/repocreateRecords.go
2
+
3
+
package main
4
+
5
+
// schema: com.atproto.repo.createRecord
6
+
7
+
import (
8
+
"context"
9
+
10
+
"github.com/bluesky-social/indigo/xrpc"
11
+
)
12
+
13
+
// RepoDefs_CommitMeta is a "commitMeta" in the com.atproto.repo.defs schema.
14
+
type RepoDefs_CommitMeta struct {
15
+
Cid string `json:"cid" cborgen:"cid"`
16
+
Rev string `json:"rev" cborgen:"rev"`
17
+
}
18
+
19
+
// RepoCreateRecord_Input is the input argument to a com.atproto.repo.createRecord call.
20
+
type RepoCreateRecord_Input struct {
21
+
// collection: The NSID of the record collection.
22
+
Collection string `json:"collection" cborgen:"collection"`
23
+
// record: The record itself. Must contain a $type field.
24
+
Record map[string]any `json:"record" cborgen:"record"`
25
+
// repo: The handle or DID of the repo (aka, current account).
26
+
Repo string `json:"repo" cborgen:"repo"`
27
+
// rkey: The Record Key.
28
+
Rkey *string `json:"rkey,omitempty" cborgen:"rkey,omitempty"`
29
+
// swapCommit: Compare and swap with the previous commit by CID.
30
+
SwapCommit *string `json:"swapCommit,omitempty" cborgen:"swapCommit,omitempty"`
31
+
// validate: Can be set to 'false' to skip Lexicon schema validation of record data, 'true' to require it, or leave unset to validate only for known Lexicons.
32
+
Validate *bool `json:"validate,omitempty" cborgen:"validate,omitempty"`
33
+
}
34
+
35
+
// RepoCreateRecord_Output is the output of a com.atproto.repo.createRecord call.
36
+
type RepoCreateRecord_Output struct {
37
+
Cid string `json:"cid" cborgen:"cid"`
38
+
Commit *RepoDefs_CommitMeta `json:"commit,omitempty" cborgen:"commit,omitempty"`
39
+
Uri string `json:"uri" cborgen:"uri"`
40
+
ValidationStatus *string `json:"validationStatus,omitempty" cborgen:"validationStatus,omitempty"`
41
+
}
42
+
43
+
// RepoCreateRecord calls the XRPC method "com.atproto.repo.createRecord".
44
+
func RepoCreateRecord(ctx context.Context, c *xrpc.Client, input *RepoCreateRecord_Input) (*RepoCreateRecord_Output, error) {
45
+
var out RepoCreateRecord_Output
46
+
if err := c.Do(ctx, xrpc.Procedure, "application/json", "com.atproto.repo.createRecord", nil, input, &out); err != nil {
47
+
return nil, err
48
+
}
49
+
50
+
return &out, nil
51
+
}
+47
cmd/goat/repoputRecord.go
+47
cmd/goat/repoputRecord.go
···
1
+
// Copied from indigo:api/atproto/repoputRecords.go
2
+
3
+
package main
4
+
5
+
// schema: com.atproto.repo.putRecord
6
+
7
+
import (
8
+
"context"
9
+
10
+
"github.com/bluesky-social/indigo/xrpc"
11
+
)
12
+
13
+
// RepoPutRecord_Input is the input argument to a com.atproto.repo.putRecord call.
14
+
type RepoPutRecord_Input struct {
15
+
// collection: The NSID of the record collection.
16
+
Collection string `json:"collection" cborgen:"collection"`
17
+
// record: The record to write.
18
+
Record map[string]any `json:"record" cborgen:"record"`
19
+
// repo: The handle or DID of the repo (aka, current account).
20
+
Repo string `json:"repo" cborgen:"repo"`
21
+
// rkey: The Record Key.
22
+
Rkey string `json:"rkey" cborgen:"rkey"`
23
+
// swapCommit: Compare and swap with the previous commit by CID.
24
+
SwapCommit *string `json:"swapCommit,omitempty" cborgen:"swapCommit,omitempty"`
25
+
// swapRecord: Compare and swap with the previous record by CID. WARNING: nullable and optional field; may cause problems with golang implementation
26
+
SwapRecord *string `json:"swapRecord" cborgen:"swapRecord"`
27
+
// validate: Can be set to 'false' to skip Lexicon schema validation of record data, 'true' to require it, or leave unset to validate only for known Lexicons.
28
+
Validate *bool `json:"validate,omitempty" cborgen:"validate,omitempty"`
29
+
}
30
+
31
+
// RepoPutRecord_Output is the output of a com.atproto.repo.putRecord call.
32
+
type RepoPutRecord_Output struct {
33
+
Cid string `json:"cid" cborgen:"cid"`
34
+
Commit *RepoDefs_CommitMeta `json:"commit,omitempty" cborgen:"commit,omitempty"`
35
+
Uri string `json:"uri" cborgen:"uri"`
36
+
ValidationStatus *string `json:"validationStatus,omitempty" cborgen:"validationStatus,omitempty"`
37
+
}
38
+
39
+
// RepoPutRecord calls the XRPC method "com.atproto.repo.putRecord".
40
+
func RepoPutRecord(ctx context.Context, c *xrpc.Client, input *RepoPutRecord_Input) (*RepoPutRecord_Output, error) {
41
+
var out RepoPutRecord_Output
42
+
if err := c.Do(ctx, xrpc.Procedure, "application/json", "com.atproto.repo.putRecord", nil, input, &out); err != nil {
43
+
return nil, err
44
+
}
45
+
46
+
return &out, nil
47
+
}
+36
-33
cmd/gosky/admin.go
+36
-33
cmd/gosky/admin.go
···
389
389
xrpcc.AdminToken = &adminKey
390
390
391
391
// fetch recent moderation reports
392
-
// AdminQueryModerationEvents(ctx context.Context, c *xrpc.Client, createdBy string, cursor string, includeAllUserRecords bool, limit int64, sortDirection string, subject string, types []string) (*AdminQueryModerationEvents_Output, error)
393
392
resp, err := toolsozone.ModerationQueryEvents(
394
393
ctx,
395
394
xrpcc,
396
-
nil,
397
-
nil,
398
-
"",
399
-
"",
400
-
"",
401
-
"",
402
-
"",
403
-
false,
404
-
false,
405
-
100,
406
-
nil,
407
-
nil,
408
-
nil,
409
-
"",
410
-
"",
411
-
[]string{"tools.ozone.moderation.defs#modEventReport"},
395
+
nil, // addedLabels []string
396
+
nil, // addedTags []string
397
+
nil, // collections []string
398
+
"", // comment string
399
+
"", // createdAfter string
400
+
"", // createdBefore string
401
+
"", // createdBy string
402
+
"", // cursor string
403
+
false, // hasComment bool
404
+
false, // includeAllUserRecords bool
405
+
100, // limit int64
406
+
nil, // removedLabels []string
407
+
nil, // removedTags []string
408
+
nil, // reportTypes []string
409
+
"", // sortDirection string
410
+
"", // subject string
411
+
"", // subjectType string
412
+
[]string{"tools.ozone.moderation.defs#modEventReport"}, // types []string
412
413
)
413
414
if err != nil {
414
415
return err
···
705
706
resp, err := toolsozone.ModerationQueryEvents(
706
707
ctx,
707
708
xrpcc,
708
-
nil,
709
-
nil,
710
-
"",
711
-
"",
712
-
"",
713
-
"",
714
-
"",
715
-
false,
716
-
false,
717
-
100,
718
-
nil,
719
-
nil,
720
-
nil,
721
-
"",
722
-
"",
723
-
[]string{"tools.ozone.moderation.defs#modEventReport"},
709
+
nil, // addedLabels []string
710
+
nil, // addedTags []string
711
+
nil, // collections []string
712
+
"", // comment string
713
+
"", // createdAfter string
714
+
"", // createdBefore string
715
+
"", // createdBy string
716
+
"", // cursor string
717
+
false, // hasComment bool
718
+
false, // includeAllUserRecords bool
719
+
100, // limit int64
720
+
nil, // removedLabels []string
721
+
nil, // removedTags []string
722
+
nil, // reportTypes []string
723
+
"", // sortDirection string
724
+
"", // subject string
725
+
"", // subjectType string
726
+
[]string{"tools.ozone.moderation.defs#modEventReport"}, // types []string
724
727
)
725
728
if err != nil {
726
729
return err
-240
cmd/hepa/consumer.go
-240
cmd/hepa/consumer.go
···
1
-
package main
2
-
3
-
import (
4
-
"bytes"
5
-
"context"
6
-
"fmt"
7
-
"net/http"
8
-
"net/url"
9
-
"strings"
10
-
"sync/atomic"
11
-
12
-
comatproto "github.com/bluesky-social/indigo/api/atproto"
13
-
"github.com/bluesky-social/indigo/atproto/syntax"
14
-
"github.com/bluesky-social/indigo/automod"
15
-
"github.com/bluesky-social/indigo/events/schedulers/autoscaling"
16
-
"github.com/bluesky-social/indigo/events/schedulers/parallel"
17
-
lexutil "github.com/bluesky-social/indigo/lex/util"
18
-
19
-
"github.com/bluesky-social/indigo/events"
20
-
"github.com/bluesky-social/indigo/repo"
21
-
"github.com/bluesky-social/indigo/repomgr"
22
-
"github.com/carlmjohnson/versioninfo"
23
-
"github.com/gorilla/websocket"
24
-
)
25
-
26
-
func (s *Server) RunConsumer(ctx context.Context) error {
27
-
28
-
cur, err := s.ReadLastCursor(ctx)
29
-
if err != nil {
30
-
return err
31
-
}
32
-
33
-
dialer := websocket.DefaultDialer
34
-
u, err := url.Parse(s.relayHost)
35
-
if err != nil {
36
-
return fmt.Errorf("invalid relayHost URI: %w", err)
37
-
}
38
-
u.Path = "xrpc/com.atproto.sync.subscribeRepos"
39
-
if cur != 0 {
40
-
u.RawQuery = fmt.Sprintf("cursor=%d", cur)
41
-
}
42
-
s.logger.Info("subscribing to repo event stream", "upstream", s.relayHost, "cursor", cur)
43
-
con, _, err := dialer.Dial(u.String(), http.Header{
44
-
"User-Agent": []string{fmt.Sprintf("hepa/%s", versioninfo.Short())},
45
-
})
46
-
if err != nil {
47
-
return fmt.Errorf("subscribing to firehose failed (dialing): %w", err)
48
-
}
49
-
50
-
rsc := &events.RepoStreamCallbacks{
51
-
RepoCommit: func(evt *comatproto.SyncSubscribeRepos_Commit) error {
52
-
atomic.StoreInt64(&s.lastSeq, evt.Seq)
53
-
return s.HandleRepoCommit(ctx, evt)
54
-
},
55
-
RepoIdentity: func(evt *comatproto.SyncSubscribeRepos_Identity) error {
56
-
atomic.StoreInt64(&s.lastSeq, evt.Seq)
57
-
did, err := syntax.ParseDID(evt.Did)
58
-
if err != nil {
59
-
s.logger.Error("bad DID in RepoIdentity event", "did", evt.Did, "seq", evt.Seq, "err", err)
60
-
return nil
61
-
}
62
-
if err := s.engine.ProcessIdentityEvent(ctx, "identity", did); err != nil {
63
-
s.logger.Error("processing repo identity failed", "did", evt.Did, "seq", evt.Seq, "err", err)
64
-
}
65
-
return nil
66
-
},
67
-
RepoAccount: func(evt *comatproto.SyncSubscribeRepos_Account) error {
68
-
atomic.StoreInt64(&s.lastSeq, evt.Seq)
69
-
did, err := syntax.ParseDID(evt.Did)
70
-
if err != nil {
71
-
s.logger.Error("bad DID in RepoAccount event", "did", evt.Did, "seq", evt.Seq, "err", err)
72
-
return nil
73
-
}
74
-
if err := s.engine.ProcessIdentityEvent(ctx, "account", did); err != nil {
75
-
s.logger.Error("processing repo account failed", "did", evt.Did, "seq", evt.Seq, "err", err)
76
-
}
77
-
return nil
78
-
},
79
-
// TODO: deprecated
80
-
RepoHandle: func(evt *comatproto.SyncSubscribeRepos_Handle) error {
81
-
atomic.StoreInt64(&s.lastSeq, evt.Seq)
82
-
did, err := syntax.ParseDID(evt.Did)
83
-
if err != nil {
84
-
s.logger.Error("bad DID in RepoHandle event", "did", evt.Did, "handle", evt.Handle, "seq", evt.Seq, "err", err)
85
-
return nil
86
-
}
87
-
if err := s.engine.ProcessIdentityEvent(ctx, "handle", did); err != nil {
88
-
s.logger.Error("processing handle update failed", "did", evt.Did, "handle", evt.Handle, "seq", evt.Seq, "err", err)
89
-
}
90
-
return nil
91
-
},
92
-
// TODO: deprecated
93
-
RepoTombstone: func(evt *comatproto.SyncSubscribeRepos_Tombstone) error {
94
-
atomic.StoreInt64(&s.lastSeq, evt.Seq)
95
-
did, err := syntax.ParseDID(evt.Did)
96
-
if err != nil {
97
-
s.logger.Error("bad DID in RepoTombstone event", "did", evt.Did, "seq", evt.Seq, "err", err)
98
-
return nil
99
-
}
100
-
if err := s.engine.ProcessIdentityEvent(ctx, "tombstone", did); err != nil {
101
-
s.logger.Error("processing repo tombstone failed", "did", evt.Did, "seq", evt.Seq, "err", err)
102
-
}
103
-
return nil
104
-
},
105
-
}
106
-
107
-
var scheduler events.Scheduler
108
-
if s.firehoseParallelism > 0 {
109
-
// use a fixed-parallelism scheduler if configured
110
-
scheduler = parallel.NewScheduler(
111
-
s.firehoseParallelism,
112
-
1000,
113
-
s.relayHost,
114
-
rsc.EventHandler,
115
-
)
116
-
s.logger.Info("hepa scheduler configured", "scheduler", "parallel", "initial", s.firehoseParallelism)
117
-
} else {
118
-
// otherwise use auto-scaling scheduler
119
-
scaleSettings := autoscaling.DefaultAutoscaleSettings()
120
-
// start at higher parallelism (somewhat arbitrary)
121
-
scaleSettings.Concurrency = 4
122
-
scaleSettings.MaxConcurrency = 200
123
-
scheduler = autoscaling.NewScheduler(scaleSettings, s.relayHost, rsc.EventHandler)
124
-
s.logger.Info("hepa scheduler configured", "scheduler", "autoscaling", "initial", scaleSettings.Concurrency, "max", scaleSettings.MaxConcurrency)
125
-
}
126
-
127
-
return events.HandleRepoStream(ctx, con, scheduler)
128
-
}
129
-
130
-
// TODO: move this to a "ParsePath" helper in syntax package?
131
-
func splitRepoPath(path string) (syntax.NSID, syntax.RecordKey, error) {
132
-
parts := strings.SplitN(path, "/", 3)
133
-
if len(parts) != 2 {
134
-
return "", "", fmt.Errorf("invalid record path: %s", path)
135
-
}
136
-
collection, err := syntax.ParseNSID(parts[0])
137
-
if err != nil {
138
-
return "", "", err
139
-
}
140
-
rkey, err := syntax.ParseRecordKey(parts[1])
141
-
if err != nil {
142
-
return "", "", err
143
-
}
144
-
return collection, rkey, nil
145
-
}
146
-
147
-
// NOTE: for now, this function basically never errors, just logs and returns nil. Should think through error processing better.
148
-
func (s *Server) HandleRepoCommit(ctx context.Context, evt *comatproto.SyncSubscribeRepos_Commit) error {
149
-
150
-
logger := s.logger.With("event", "commit", "did", evt.Repo, "rev", evt.Rev, "seq", evt.Seq)
151
-
logger.Debug("received commit event")
152
-
153
-
if evt.TooBig {
154
-
logger.Warn("skipping tooBig events for now")
155
-
return nil
156
-
}
157
-
158
-
did, err := syntax.ParseDID(evt.Repo)
159
-
if err != nil {
160
-
logger.Error("bad DID syntax in event", "err", err)
161
-
return nil
162
-
}
163
-
164
-
rr, err := repo.ReadRepoFromCar(ctx, bytes.NewReader(evt.Blocks))
165
-
if err != nil {
166
-
logger.Error("failed to read repo from car", "err", err)
167
-
return nil
168
-
}
169
-
170
-
// empty commit is a special case, temporarily, basically indicates "new account"
171
-
if len(evt.Ops) == 0 {
172
-
if err := s.engine.ProcessIdentityEvent(ctx, "create", did); err != nil {
173
-
s.logger.Error("processing handle update failed", "did", evt.Repo, "rev", evt.Rev, "seq", evt.Seq, "err", err)
174
-
}
175
-
}
176
-
177
-
for _, op := range evt.Ops {
178
-
logger = logger.With("eventKind", op.Action, "path", op.Path)
179
-
collection, rkey, err := splitRepoPath(op.Path)
180
-
if err != nil {
181
-
logger.Error("invalid path in repo op")
182
-
return nil
183
-
}
184
-
185
-
ek := repomgr.EventKind(op.Action)
186
-
switch ek {
187
-
case repomgr.EvtKindCreateRecord, repomgr.EvtKindUpdateRecord:
188
-
// read the record bytes from blocks, and verify CID
189
-
rc, recCBOR, err := rr.GetRecordBytes(ctx, op.Path)
190
-
if err != nil {
191
-
logger.Error("reading record from event blocks (CAR)", "err", err)
192
-
break
193
-
}
194
-
if op.Cid == nil || lexutil.LexLink(rc) != *op.Cid {
195
-
logger.Error("mismatch between commit op CID and record block", "recordCID", rc, "opCID", op.Cid)
196
-
break
197
-
}
198
-
var action string
199
-
switch ek {
200
-
case repomgr.EvtKindCreateRecord:
201
-
action = automod.CreateOp
202
-
case repomgr.EvtKindUpdateRecord:
203
-
action = automod.UpdateOp
204
-
default:
205
-
logger.Error("impossible event kind", "kind", ek)
206
-
break
207
-
}
208
-
recCID := syntax.CID(op.Cid.String())
209
-
err = s.engine.ProcessRecordOp(ctx, automod.RecordOp{
210
-
Action: action,
211
-
DID: did,
212
-
Collection: collection,
213
-
RecordKey: rkey,
214
-
CID: &recCID,
215
-
RecordCBOR: *recCBOR,
216
-
})
217
-
if err != nil {
218
-
logger.Error("engine failed to process record", "err", err)
219
-
continue
220
-
}
221
-
case repomgr.EvtKindDeleteRecord:
222
-
err = s.engine.ProcessRecordOp(ctx, automod.RecordOp{
223
-
Action: automod.DeleteOp,
224
-
DID: did,
225
-
Collection: collection,
226
-
RecordKey: rkey,
227
-
CID: nil,
228
-
RecordCBOR: nil,
229
-
})
230
-
if err != nil {
231
-
logger.Error("engine failed to process record", "err", err)
232
-
continue
233
-
}
234
-
default:
235
-
// TODO: should this be an error?
236
-
}
237
-
}
238
-
239
-
return nil
240
-
}
-97
cmd/hepa/consumer_ozone.go
-97
cmd/hepa/consumer_ozone.go
···
1
-
package main
2
-
3
-
import (
4
-
"context"
5
-
"fmt"
6
-
"time"
7
-
8
-
toolsozone "github.com/bluesky-social/indigo/api/ozone"
9
-
"github.com/bluesky-social/indigo/atproto/syntax"
10
-
)
11
-
12
-
func (s *Server) RunOzoneConsumer(ctx context.Context) error {
13
-
14
-
cur, err := s.ReadLastOzoneCursor(ctx)
15
-
if err != nil {
16
-
return err
17
-
}
18
-
19
-
if cur == "" {
20
-
cur = syntax.DatetimeNow().String()
21
-
}
22
-
since, err := syntax.ParseDatetime(cur)
23
-
if err != nil {
24
-
return err
25
-
}
26
-
27
-
s.logger.Info("subscribing to ozone event log", "upstream", s.engine.OzoneClient.Host, "cursor", cur, "since", since)
28
-
var limit int64 = 50
29
-
period := time.Second * 5
30
-
31
-
for {
32
-
//func ModerationQueryEvents(ctx context.Context, c *xrpc.Client, addedLabels []string, addedTags []string, comment string, createdAfter string, createdBefore string, createdBy string, cursor string, hasComment bool, includeAllUserRecords bool, limit int64, removedLabels []string, removedTags []string, reportTypes []string, sortDirection string, subject string, types []string) (*ModerationQueryEvents_Output, error) {
33
-
me, err := toolsozone.ModerationQueryEvents(
34
-
ctx,
35
-
s.engine.OzoneClient,
36
-
nil, // addedLabels: If specified, only events where all of these labels were added are returned
37
-
nil, // addedTags: If specified, only events where all of these tags were added are returned
38
-
"", // comment: If specified, only events with comments containing the keyword are returned
39
-
since.String(), // createdAfter: Retrieve events created after a given timestamp
40
-
"", // createdBefore: Retrieve events created before a given timestamp
41
-
"", // createdBy
42
-
"", // cursor
43
-
false, // hasComment: If true, only events with comments are returned
44
-
true, // includeAllUserRecords: If true, events on all record types (posts, lists, profile etc.) owned by the did are returned
45
-
limit,
46
-
nil, // removedLabels: If specified, only events where all of these labels were removed are returned
47
-
nil, // removedTags
48
-
nil, // reportTypes
49
-
"asc", // sortDirection: Sort direction for the events. Defaults to descending order of created at timestamp.
50
-
"", // subject
51
-
nil, // 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.
52
-
)
53
-
if err != nil {
54
-
s.logger.Warn("ozone query events failed; sleeping then will retrying", "err", err, "period", period.String())
55
-
time.Sleep(period)
56
-
continue
57
-
}
58
-
59
-
// track if the response contained anything new
60
-
anyNewEvents := false
61
-
for _, evt := range me.Events {
62
-
createdAt, err := syntax.ParseDatetime(evt.CreatedAt)
63
-
if err != nil {
64
-
return fmt.Errorf("invalid time format for ozone 'createdAt': %w", err)
65
-
}
66
-
// skip if the timestamp is the exact same
67
-
if createdAt == since {
68
-
continue
69
-
}
70
-
anyNewEvents = true
71
-
// TODO: is there a race condition here?
72
-
if !createdAt.Time().After(since.Time()) {
73
-
s.logger.Error("out of order ozone event", "createdAt", createdAt, "since", since)
74
-
return fmt.Errorf("out of order ozone event")
75
-
}
76
-
if err = s.HandleOzoneEvent(ctx, evt); err != nil {
77
-
s.logger.Error("failed to process ozone event", "event", evt)
78
-
}
79
-
since = createdAt
80
-
s.lastOzoneCursor.Store(since.String())
81
-
}
82
-
if !anyNewEvents {
83
-
s.logger.Debug("... ozone poller sleeping", "period", period.String())
84
-
time.Sleep(period)
85
-
}
86
-
}
87
-
}
88
-
89
-
func (s *Server) HandleOzoneEvent(ctx context.Context, eventView *toolsozone.ModerationDefs_ModEventView) error {
90
-
91
-
s.logger.Debug("received ozone event", "eventID", eventView.Id, "createdAt", eventView.CreatedAt)
92
-
93
-
if err := s.engine.ProcessOzoneEvent(ctx, eventView); err != nil {
94
-
s.logger.Error("engine failed to process ozone event", "err", err)
95
-
}
96
-
return nil
97
-
}
+42
-22
cmd/hepa/main.go
+42
-22
cmd/hepa/main.go
···
17
17
"github.com/bluesky-social/indigo/atproto/identity/redisdir"
18
18
"github.com/bluesky-social/indigo/atproto/syntax"
19
19
"github.com/bluesky-social/indigo/automod/capture"
20
+
"github.com/bluesky-social/indigo/automod/consumer"
20
21
21
22
"github.com/carlmjohnson/versioninfo"
22
23
_ "github.com/joho/godotenv/autoload"
···
236
237
dir,
237
238
Config{
238
239
Logger: logger,
239
-
RelayHost: cctx.String("atp-relay-host"),
240
+
RelayHost: cctx.String("atp-relay-host"), // DEPRECATED
240
241
BskyHost: cctx.String("atp-bsky-host"),
241
242
OzoneHost: cctx.String("atp-ozone-host"),
242
243
OzoneDID: cctx.String("ozone-did"),
···
251
252
AbyssPassword: cctx.String("abyss-password"),
252
253
RatelimitBypass: cctx.String("ratelimit-bypass"),
253
254
RulesetName: cctx.String("ruleset"),
254
-
FirehoseParallelism: cctx.Int("firehose-parallelism"),
255
+
FirehoseParallelism: cctx.Int("firehose-parallelism"), // DEPRECATED
255
256
PreScreenHost: cctx.String("prescreen-host"),
256
257
PreScreenToken: cctx.String("prescreen-token"),
257
258
},
···
260
261
return fmt.Errorf("failed to construct server: %v", err)
261
262
}
262
263
264
+
// ozone event consumer (if configured)
265
+
if srv.Engine.OzoneClient != nil {
266
+
oc := consumer.OzoneConsumer{
267
+
Logger: logger.With("subsystem", "ozone-consumer"),
268
+
RedisClient: srv.RedisClient,
269
+
OzoneClient: srv.Engine.OzoneClient,
270
+
Engine: srv.Engine,
271
+
}
272
+
273
+
go func() {
274
+
if err := oc.Run(ctx); err != nil {
275
+
slog.Error("ozone consumer failed", "err", err)
276
+
}
277
+
}()
278
+
279
+
go func() {
280
+
if err := oc.RunPersistCursor(ctx); err != nil {
281
+
slog.Error("ozone cursor routine failed", "err", err)
282
+
}
283
+
}()
284
+
}
285
+
263
286
// prometheus HTTP endpoint: /metrics
264
287
go func() {
265
288
runtime.SetBlockProfileRate(10)
···
270
293
}
271
294
}()
272
295
273
-
go func() {
274
-
if err := srv.RunPersistCursor(ctx); err != nil {
275
-
slog.Error("cursor routine failed", "err", err)
296
+
// firehose event consumer (note this is actually mandatory)
297
+
relayHost := cctx.String("atp-relay-host")
298
+
if relayHost != "" {
299
+
fc := consumer.FirehoseConsumer{
300
+
Engine: srv.Engine,
301
+
Logger: logger.With("subsystem", "firehose-consumer"),
302
+
Host: cctx.String("atp-relay-host"),
303
+
Parallelism: cctx.Int("firehose-parallelism"),
304
+
RedisClient: srv.RedisClient,
276
305
}
277
-
}()
278
306
279
-
// ozone event consumer (if configured)
280
-
if srv.engine.OzoneClient != nil {
281
307
go func() {
282
-
if err := srv.RunOzoneConsumer(ctx); err != nil {
283
-
slog.Error("ozone consumer failed", "err", err)
308
+
if err := fc.RunPersistCursor(ctx); err != nil {
309
+
slog.Error("cursor routine failed", "err", err)
284
310
}
285
311
}()
286
312
287
-
go func() {
288
-
if err := srv.RunPersistOzoneCursor(ctx); err != nil {
289
-
slog.Error("ozone cursor routine failed", "err", err)
290
-
}
291
-
}()
313
+
if err := fc.Run(ctx); err != nil {
314
+
return fmt.Errorf("failure consuming and processing firehose: %w", err)
315
+
}
292
316
}
293
317
294
-
// firehose event consumer (main processor)
295
-
if err := srv.RunConsumer(ctx); err != nil {
296
-
return fmt.Errorf("failure consuming and processing firehose: %w", err)
297
-
}
298
318
return nil
299
319
},
300
320
}
···
355
375
return err
356
376
}
357
377
358
-
return capture.FetchAndProcessRecord(ctx, srv.engine, aturi)
378
+
return capture.FetchAndProcessRecord(ctx, srv.Engine, aturi)
359
379
},
360
380
}
361
381
···
386
406
return err
387
407
}
388
408
389
-
return capture.FetchAndProcessRecent(ctx, srv.engine, *atid, cctx.Int("limit"))
409
+
return capture.FetchAndProcessRecent(ctx, srv.Engine, *atid, cctx.Int("limit"))
390
410
},
391
411
}
392
412
···
417
437
return err
418
438
}
419
439
420
-
cap, err := capture.CaptureRecent(ctx, srv.engine, *atid, cctx.Int("limit"))
440
+
cap, err := capture.CaptureRecent(ctx, srv.Engine, *atid, cctx.Int("limit"))
421
441
if err != nil {
422
442
return err
423
443
}
+9
-147
cmd/hepa/server.go
+9
-147
cmd/hepa/server.go
···
7
7
"net/http"
8
8
"os"
9
9
"strings"
10
-
"sync/atomic"
11
10
"time"
12
11
13
12
"github.com/bluesky-social/indigo/atproto/identity"
···
27
26
)
28
27
29
28
type Server struct {
30
-
relayHost string
31
-
firehoseParallelism int
29
+
Engine *automod.Engine
30
+
RedisClient *redis.Client
31
+
32
+
relayHost string // DEPRECATED
33
+
firehoseParallelism int // DEPRECATED
32
34
logger *slog.Logger
33
-
engine *automod.Engine
34
-
rdb *redis.Client
35
-
36
-
// lastSeq is the most recent event sequence number we've received and begun to handle.
37
-
// This number is periodically persisted to redis, if redis is present.
38
-
// The value is best-effort (the stream handling itself is concurrent, so event numbers may not be monotonic),
39
-
// but nonetheless, you must use atomics when updating or reading this (to avoid data races).
40
-
lastSeq int64
41
-
42
-
// same as lastSeq, but for Ozone timestamp cursor. the value is a string.
43
-
lastOzoneCursor atomic.Value
44
35
}
45
36
46
37
type Config struct {
47
38
Logger *slog.Logger
48
-
RelayHost string
39
+
RelayHost string // DEPRECATED
49
40
BskyHost string
50
41
OzoneHost string
51
42
OzoneDID string
···
60
51
AbyssPassword string
61
52
RulesetName string
62
53
RatelimitBypass string
63
-
FirehoseParallelism int
54
+
FirehoseParallelism int // DEPRECATED
64
55
PreScreenHost string
65
56
PreScreenToken string
66
57
}
···
234
225
relayHost: config.RelayHost,
235
226
firehoseParallelism: config.FirehoseParallelism,
236
227
logger: logger,
237
-
engine: &engine,
238
-
rdb: rdb,
228
+
Engine: &engine,
229
+
RedisClient: rdb,
239
230
}
240
231
241
232
return s, nil
···
245
236
http.Handle("/metrics", promhttp.Handler())
246
237
return http.ListenAndServe(listen, nil)
247
238
}
248
-
249
-
var cursorKey = "hepa/seq"
250
-
var ozoneCursorKey = "hepa/ozoneTimestamp"
251
-
252
-
func (s *Server) ReadLastCursor(ctx context.Context) (int64, error) {
253
-
// if redis isn't configured, just skip
254
-
if s.rdb == nil {
255
-
s.logger.Info("redis not configured, skipping cursor read")
256
-
return 0, nil
257
-
}
258
-
259
-
val, err := s.rdb.Get(ctx, cursorKey).Int64()
260
-
if err == redis.Nil {
261
-
s.logger.Info("no pre-existing cursor in redis")
262
-
return 0, nil
263
-
} else if err != nil {
264
-
return 0, err
265
-
}
266
-
s.logger.Info("successfully found prior subscription cursor seq in redis", "seq", val)
267
-
return val, nil
268
-
}
269
-
270
-
func (s *Server) ReadLastOzoneCursor(ctx context.Context) (string, error) {
271
-
// if redis isn't configured, just skip
272
-
if s.rdb == nil {
273
-
s.logger.Info("redis not configured, skipping ozone cursor read")
274
-
return "", nil
275
-
}
276
-
277
-
val, err := s.rdb.Get(ctx, ozoneCursorKey).Result()
278
-
if err == redis.Nil || val == "" {
279
-
s.logger.Info("no pre-existing ozone cursor in redis")
280
-
return "", nil
281
-
} else if err != nil {
282
-
return "", err
283
-
}
284
-
s.logger.Info("successfully found prior ozone offset timestamp in redis", "cursor", val)
285
-
return val, nil
286
-
}
287
-
288
-
func (s *Server) PersistCursor(ctx context.Context) error {
289
-
// if redis isn't configured, just skip
290
-
if s.rdb == nil {
291
-
return nil
292
-
}
293
-
lastSeq := atomic.LoadInt64(&s.lastSeq)
294
-
if lastSeq <= 0 {
295
-
return nil
296
-
}
297
-
err := s.rdb.Set(ctx, cursorKey, lastSeq, 14*24*time.Hour).Err()
298
-
return err
299
-
}
300
-
301
-
func (s *Server) PersistOzoneCursor(ctx context.Context) error {
302
-
// if redis isn't configured, just skip
303
-
if s.rdb == nil {
304
-
return nil
305
-
}
306
-
lastCursor := s.lastOzoneCursor.Load()
307
-
if lastCursor == nil || lastCursor == "" {
308
-
return nil
309
-
}
310
-
err := s.rdb.Set(ctx, ozoneCursorKey, lastCursor, 14*24*time.Hour).Err()
311
-
return err
312
-
}
313
-
314
-
// this method runs in a loop, persisting the current cursor state every 5 seconds
315
-
func (s *Server) RunPersistCursor(ctx context.Context) error {
316
-
317
-
// if redis isn't configured, just skip
318
-
if s.rdb == nil {
319
-
return nil
320
-
}
321
-
ticker := time.NewTicker(5 * time.Second)
322
-
for {
323
-
select {
324
-
case <-ctx.Done():
325
-
lastSeq := atomic.LoadInt64(&s.lastSeq)
326
-
if lastSeq >= 1 {
327
-
s.logger.Info("persisting final cursor seq value", "seq", lastSeq)
328
-
err := s.PersistCursor(ctx)
329
-
if err != nil {
330
-
s.logger.Error("failed to persist cursor", "err", err, "seq", lastSeq)
331
-
}
332
-
}
333
-
return nil
334
-
case <-ticker.C:
335
-
lastSeq := atomic.LoadInt64(&s.lastSeq)
336
-
if lastSeq >= 1 {
337
-
err := s.PersistCursor(ctx)
338
-
if err != nil {
339
-
s.logger.Error("failed to persist cursor", "err", err, "seq", lastSeq)
340
-
}
341
-
}
342
-
}
343
-
}
344
-
}
345
-
346
-
// this method runs in a loop, persisting the current cursor state every 5 seconds
347
-
func (s *Server) RunPersistOzoneCursor(ctx context.Context) error {
348
-
349
-
// if redis isn't configured, just skip
350
-
if s.rdb == nil {
351
-
return nil
352
-
}
353
-
ticker := time.NewTicker(5 * time.Second)
354
-
for {
355
-
select {
356
-
case <-ctx.Done():
357
-
lastCursor := s.lastOzoneCursor.Load()
358
-
if lastCursor != nil && lastCursor != "" {
359
-
s.logger.Info("persisting final ozone cursor timestamp", "cursor", lastCursor)
360
-
err := s.PersistOzoneCursor(ctx)
361
-
if err != nil {
362
-
s.logger.Error("failed to persist ozone cursor", "err", err, "cursor", lastCursor)
363
-
}
364
-
}
365
-
return nil
366
-
case <-ticker.C:
367
-
lastCursor := s.lastOzoneCursor.Load()
368
-
if lastCursor != nil && lastCursor != "" {
369
-
err := s.PersistOzoneCursor(ctx)
370
-
if err != nil {
371
-
s.logger.Error("failed to persist ozone cursor", "err", err, "cursor", lastCursor)
372
-
}
373
-
}
374
-
}
375
-
}
376
-
}
+9
-1
did/web.go
+9
-1
did/web.go
···
6
6
"fmt"
7
7
"net/http"
8
8
"strings"
9
+
"time"
9
10
"unicode"
10
11
11
12
"github.com/whyrusleeping/go-did"
12
13
"go.opentelemetry.io/otel"
13
14
)
15
+
16
+
var webDidDefaultTimeout = 5 * time.Second
14
17
15
18
type WebResolver struct {
16
19
Insecure bool
17
20
// TODO: cache? maybe at a different layer
21
+
22
+
client http.Client
18
23
}
19
24
20
25
func (wr *WebResolver) GetDocument(ctx context.Context, didstr string) (*Document, error) {
26
+
if wr.client.Timeout == 0 {
27
+
wr.client.Timeout = webDidDefaultTimeout
28
+
}
21
29
ctx, span := otel.Tracer("did").Start(ctx, "didWebGetDocument")
22
30
defer span.End()
23
31
···
36
44
proto = "http"
37
45
}
38
46
39
-
resp, err := http.Get(proto + "://" + val + "/.well-known/did.json")
47
+
resp, err := wr.client.Get(proto + "://" + val + "/.well-known/did.json")
40
48
if err != nil {
41
49
return nil, err
42
50
}
+26
-16
events/cbor_gen.go
+26
-16
events/cbor_gen.go
···
101
101
return fmt.Errorf("EventHeader: map struct too large (%d)", extra)
102
102
}
103
103
104
-
var name string
105
104
n := extra
106
105
106
+
nameBuf := make([]byte, 2)
107
107
for i := uint64(0); i < n; i++ {
108
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
109
+
if err != nil {
110
+
return err
111
+
}
108
112
109
-
{
110
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
111
-
if err != nil {
113
+
if !ok {
114
+
// Field doesn't exist on this type, so ignore it
115
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
112
116
return err
113
117
}
114
-
115
-
name = string(sval)
118
+
continue
116
119
}
117
120
118
-
switch name {
121
+
switch string(nameBuf[:nameLen]) {
119
122
// t.MsgType (string) (string)
120
123
case "t":
121
124
···
156
159
157
160
default:
158
161
// Field doesn't exist on this type, so ignore it
159
-
cbg.ScanForLinks(r, func(cid.Cid) {})
162
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
163
+
return err
164
+
}
160
165
}
161
166
}
162
167
···
245
250
return fmt.Errorf("ErrorFrame: map struct too large (%d)", extra)
246
251
}
247
252
248
-
var name string
249
253
n := extra
250
254
255
+
nameBuf := make([]byte, 7)
251
256
for i := uint64(0); i < n; i++ {
257
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
258
+
if err != nil {
259
+
return err
260
+
}
252
261
253
-
{
254
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
255
-
if err != nil {
262
+
if !ok {
263
+
// Field doesn't exist on this type, so ignore it
264
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
256
265
return err
257
266
}
258
-
259
-
name = string(sval)
267
+
continue
260
268
}
261
269
262
-
switch name {
270
+
switch string(nameBuf[:nameLen]) {
263
271
// t.Error (string) (string)
264
272
case "error":
265
273
···
285
293
286
294
default:
287
295
// Field doesn't exist on this type, so ignore it
288
-
cbg.ScanForLinks(r, func(cid.Cid) {})
296
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
297
+
return err
298
+
}
289
299
}
290
300
}
291
301
+2
-2
events/diskpersist.go
+2
-2
events/diskpersist.go
···
81
81
func DefaultDiskPersistOptions() *DiskPersistOptions {
82
82
return &DiskPersistOptions{
83
83
EventsPerFile: 10_000,
84
-
UIDCacheSize: 100_000,
85
-
DIDCacheSize: 100_000,
84
+
UIDCacheSize: 1_000_000,
85
+
DIDCacheSize: 1_000_000,
86
86
WriteBufferSize: 50,
87
87
Retention: time.Hour * 24 * 3, // 3 days
88
88
}
+5
-1
go.mod
+5
-1
go.mod
···
14
14
github.com/flosch/pongo2/v6 v6.0.0
15
15
github.com/go-redis/cache/v9 v9.0.0
16
16
github.com/goccy/go-json v0.10.2
17
+
github.com/gocql/gocql v1.7.0
17
18
github.com/golang-jwt/jwt v3.2.2+incompatible
18
19
github.com/gorilla/websocket v1.5.1
19
20
github.com/hashicorp/go-retryablehttp v0.7.5
···
53
54
github.com/samber/slog-echo v1.8.0
54
55
github.com/stretchr/testify v1.9.0
55
56
github.com/urfave/cli/v2 v2.25.7
56
-
github.com/whyrusleeping/cbor-gen v0.1.3-0.20240904181319-8dc02b38228c
57
+
github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e
57
58
github.com/whyrusleeping/go-did v0.0.0-20230824162731-404d1707d5d6
58
59
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b
59
60
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1
···
79
80
require (
80
81
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
81
82
github.com/go-redis/redis v6.15.9+incompatible // indirect
83
+
github.com/golang/snappy v0.0.3 // indirect
84
+
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect
82
85
github.com/hashicorp/golang-lru v1.0.2 // indirect
83
86
github.com/jackc/puddle/v2 v2.2.1 // indirect
84
87
github.com/klauspost/compress v1.17.3 // indirect
···
91
94
github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 // indirect
92
95
go.uber.org/zap v1.26.0 // indirect
93
96
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
97
+
gopkg.in/inf.v0 v0.9.1 // indirect
94
98
)
95
99
96
100
require (
+14
-2
go.sum
+14
-2
go.sum
···
71
71
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
72
72
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
73
73
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
74
+
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY=
75
+
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
76
+
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
77
+
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
74
78
github.com/brianvoe/gofakeit/v6 v6.25.0 h1:ZpFjktOpLZUeF8q223o0rUuXtA+m5qW5srjvVi+JkXk=
75
79
github.com/brianvoe/gofakeit/v6 v6.25.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs=
76
80
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
···
152
156
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
153
157
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
154
158
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
159
+
github.com/gocql/gocql v1.7.0 h1:O+7U7/1gSN7QTEAaMEsJc1Oq2QHXvCWoF3DFK9HDHus=
160
+
github.com/gocql/gocql v1.7.0/go.mod h1:vnlvXyFZeLBF0Wy+RS8hrOdbn0UWsWtdg07XJnFxZ+4=
155
161
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
156
162
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
157
163
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
···
189
195
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
190
196
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
191
197
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
198
+
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
199
+
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
192
200
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
193
201
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
194
202
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
···
231
239
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
232
240
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 h1:6UKoz5ujsI55KNpsJH3UwCq3T8kKbZwNZBNPuTTje8U=
233
241
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y=
242
+
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
243
+
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
234
244
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
235
245
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
236
246
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
···
616
626
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
617
627
github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 h1:5HZfQkwe0mIfyDmc1Em5GqlNRzcdtlv4HTNmdpt7XH0=
618
628
github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11/go.mod h1:Wlo/SzPmxVp6vXpGt/zaXhHH0fn4IxgqZc82aKg6bpQ=
619
-
github.com/whyrusleeping/cbor-gen v0.1.3-0.20240904181319-8dc02b38228c h1:UsxJNcLPfyLyVaA4iusIrsLAqJn/xh36Qgb8emqtXzk=
620
-
github.com/whyrusleeping/cbor-gen v0.1.3-0.20240904181319-8dc02b38228c/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so=
629
+
github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e h1:28X54ciEwwUxyHn9yrZfl5ojgF4CBNLWX7LR0rvBkf4=
630
+
github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so=
621
631
github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f h1:jQa4QT2UP9WYv2nzyawpKMOCl+Z/jW7djv2/J50lj9E=
622
632
github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f/go.mod h1:p9UJB6dDgdPgMJZs7UjUOdulKyRr9fqkS+6JKAInPy8=
623
633
github.com/whyrusleeping/go-did v0.0.0-20230824162731-404d1707d5d6 h1:yJ9/LwIGIk/c0CdoavpC9RNSGSruIspSZtxG3Nnldic=
···
1061
1071
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
1062
1072
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
1063
1073
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
1074
+
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
1075
+
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
1064
1076
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
1065
1077
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
1066
1078
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+9
-2
lex/gen.go
+9
-2
lex/gen.go
···
232
232
case "record":
233
233
return nil
234
234
case "query":
235
-
return ts.WriteRPC(w, typename)
235
+
return ts.WriteRPC(w, typename, fmt.Sprintf("%s_Input", typename))
236
236
case "procedure":
237
-
return ts.WriteRPC(w, typename)
237
+
if ts.Input == nil || ts.Input.Schema == nil || ts.Input.Schema.Type == "object" {
238
+
return ts.WriteRPC(w, typename, fmt.Sprintf("%s_Input", typename))
239
+
} else if ts.Input.Schema.Type == "ref" {
240
+
inputname, _ := ts.namesFromRef(ts.Input.Schema.Ref)
241
+
return ts.WriteRPC(w, typename, inputname)
242
+
} else {
243
+
return fmt.Errorf("unhandled input type: %s", ts.Input.Schema.Type)
244
+
}
238
245
case "object", "string":
239
246
return nil
240
247
case "subscription":
+2
-2
lex/type_schema.go
+2
-2
lex/type_schema.go
···
50
50
Maximum any `json:"maximum"`
51
51
}
52
52
53
-
func (s *TypeSchema) WriteRPC(w io.Writer, typename string) error {
53
+
func (s *TypeSchema) WriteRPC(w io.Writer, typename, inputname string) error {
54
54
pf := printerf(w)
55
55
fname := typename
56
56
···
65
65
case EncodingCBOR, EncodingCAR, EncodingANY, EncodingMP4:
66
66
params = fmt.Sprintf("%s, input io.Reader", params)
67
67
case EncodingJSON:
68
-
params = fmt.Sprintf("%s, input *%s_Input", params, fname)
68
+
params = fmt.Sprintf("%s, input *%s", params, inputname)
69
69
70
70
default:
71
71
return fmt.Errorf("unsupported input encoding (RPC input): %q", s.Input.Encoding)
+39
-24
lex/util/cbor_gen.go
+39
-24
lex/util/cbor_gen.go
···
78
78
return fmt.Errorf("CborChecker: map struct too large (%d)", extra)
79
79
}
80
80
81
-
var name string
82
81
n := extra
83
82
83
+
nameBuf := make([]byte, 5)
84
84
for i := uint64(0); i < n; i++ {
85
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
86
+
if err != nil {
87
+
return err
88
+
}
85
89
86
-
{
87
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
88
-
if err != nil {
90
+
if !ok {
91
+
// Field doesn't exist on this type, so ignore it
92
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
89
93
return err
90
94
}
91
-
92
-
name = string(sval)
95
+
continue
93
96
}
94
97
95
-
switch name {
98
+
switch string(nameBuf[:nameLen]) {
96
99
// t.Type (string) (string)
97
100
case "$type":
98
101
···
107
110
108
111
default:
109
112
// Field doesn't exist on this type, so ignore it
110
-
cbg.ScanForLinks(r, func(cid.Cid) {})
113
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
114
+
return err
115
+
}
111
116
}
112
117
}
113
118
···
196
201
return fmt.Errorf("LegacyBlob: map struct too large (%d)", extra)
197
202
}
198
203
199
-
var name string
200
204
n := extra
201
205
206
+
nameBuf := make([]byte, 8)
202
207
for i := uint64(0); i < n; i++ {
208
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
209
+
if err != nil {
210
+
return err
211
+
}
203
212
204
-
{
205
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
206
-
if err != nil {
213
+
if !ok {
214
+
// Field doesn't exist on this type, so ignore it
215
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
207
216
return err
208
217
}
209
-
210
-
name = string(sval)
218
+
continue
211
219
}
212
220
213
-
switch name {
221
+
switch string(nameBuf[:nameLen]) {
214
222
// t.Cid (string) (string)
215
223
case "cid":
216
224
···
236
244
237
245
default:
238
246
// Field doesn't exist on this type, so ignore it
239
-
cbg.ScanForLinks(r, func(cid.Cid) {})
247
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
248
+
return err
249
+
}
240
250
}
241
251
}
242
252
···
359
369
return fmt.Errorf("BlobSchema: map struct too large (%d)", extra)
360
370
}
361
371
362
-
var name string
363
372
n := extra
364
373
374
+
nameBuf := make([]byte, 8)
365
375
for i := uint64(0); i < n; i++ {
376
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
377
+
if err != nil {
378
+
return err
379
+
}
366
380
367
-
{
368
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
369
-
if err != nil {
381
+
if !ok {
382
+
// Field doesn't exist on this type, so ignore it
383
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
370
384
return err
371
385
}
372
-
373
-
name = string(sval)
386
+
continue
374
387
}
375
388
376
-
switch name {
389
+
switch string(nameBuf[:nameLen]) {
377
390
// t.Ref (util.LexLink) (struct)
378
391
case "ref":
379
392
···
435
448
436
449
default:
437
450
// Field doesn't exist on this type, so ignore it
438
-
cbg.ScanForLinks(r, func(cid.Cid) {})
451
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
452
+
return err
453
+
}
439
454
}
440
455
}
441
456
+78
-48
lex/util/cbor_gen_test.go
+78
-48
lex/util/cbor_gen_test.go
···
254
254
return fmt.Errorf("basicSchema: map struct too large (%d)", extra)
255
255
}
256
256
257
-
var name string
258
257
n := extra
259
258
259
+
nameBuf := make([]byte, 7)
260
260
for i := uint64(0); i < n; i++ {
261
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 8192)
262
+
if err != nil {
263
+
return err
264
+
}
261
265
262
-
{
263
-
sval, err := cbg.ReadStringWithMax(cr, 8192)
264
-
if err != nil {
266
+
if !ok {
267
+
// Field doesn't exist on this type, so ignore it
268
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
265
269
return err
266
270
}
267
-
268
-
name = string(sval)
271
+
continue
269
272
}
270
273
271
-
switch name {
274
+
switch string(nameBuf[:nameLen]) {
272
275
// t.Bool (bool) (bool)
273
276
case "bool":
274
277
···
430
433
431
434
default:
432
435
// Field doesn't exist on this type, so ignore it
433
-
cbg.ScanForLinks(r, func(cid.Cid) {})
436
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
437
+
return err
438
+
}
434
439
}
435
440
}
436
441
···
567
572
return fmt.Errorf("basicSchemaInner: map struct too large (%d)", extra)
568
573
}
569
574
570
-
var name string
571
575
n := extra
572
576
577
+
nameBuf := make([]byte, 6)
573
578
for i := uint64(0); i < n; i++ {
579
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 8192)
580
+
if err != nil {
581
+
return err
582
+
}
574
583
575
-
{
576
-
sval, err := cbg.ReadStringWithMax(cr, 8192)
577
-
if err != nil {
584
+
if !ok {
585
+
// Field doesn't exist on this type, so ignore it
586
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
578
587
return err
579
588
}
580
-
581
-
name = string(sval)
589
+
continue
582
590
}
583
591
584
-
switch name {
592
+
switch string(nameBuf[:nameLen]) {
585
593
// t.Arr ([]string) (slice)
586
594
case "arr":
587
595
···
680
688
681
689
default:
682
690
// Field doesn't exist on this type, so ignore it
683
-
cbg.ScanForLinks(r, func(cid.Cid) {})
691
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
692
+
return err
693
+
}
684
694
}
685
695
}
686
696
···
779
789
return fmt.Errorf("ipldSchema: map struct too large (%d)", extra)
780
790
}
781
791
782
-
var name string
783
792
n := extra
784
793
794
+
nameBuf := make([]byte, 1)
785
795
for i := uint64(0); i < n; i++ {
796
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 8192)
797
+
if err != nil {
798
+
return err
799
+
}
786
800
787
-
{
788
-
sval, err := cbg.ReadStringWithMax(cr, 8192)
789
-
if err != nil {
801
+
if !ok {
802
+
// Field doesn't exist on this type, so ignore it
803
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
790
804
return err
791
805
}
792
-
793
-
name = string(sval)
806
+
continue
794
807
}
795
808
796
-
switch name {
809
+
switch string(nameBuf[:nameLen]) {
797
810
// t.A (util.LexLink) (struct)
798
811
case "a":
799
812
···
840
853
841
854
default:
842
855
// Field doesn't exist on this type, so ignore it
843
-
cbg.ScanForLinks(r, func(cid.Cid) {})
856
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
857
+
return err
858
+
}
844
859
}
845
860
}
846
861
···
1059
1074
return fmt.Errorf("basicOldSchema: map struct too large (%d)", extra)
1060
1075
}
1061
1076
1062
-
var name string
1063
1077
n := extra
1064
1078
1079
+
nameBuf := make([]byte, 1)
1065
1080
for i := uint64(0); i < n; i++ {
1081
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 8192)
1082
+
if err != nil {
1083
+
return err
1084
+
}
1066
1085
1067
-
{
1068
-
sval, err := cbg.ReadStringWithMax(cr, 8192)
1069
-
if err != nil {
1086
+
if !ok {
1087
+
// Field doesn't exist on this type, so ignore it
1088
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
1070
1089
return err
1071
1090
}
1072
-
1073
-
name = string(sval)
1091
+
continue
1074
1092
}
1075
1093
1076
-
switch name {
1094
+
switch string(nameBuf[:nameLen]) {
1077
1095
// t.A (string) (string)
1078
1096
case "a":
1079
1097
···
1224
1242
1225
1243
default:
1226
1244
// Field doesn't exist on this type, so ignore it
1227
-
cbg.ScanForLinks(r, func(cid.Cid) {})
1245
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
1246
+
return err
1247
+
}
1228
1248
}
1229
1249
}
1230
1250
···
1361
1381
return fmt.Errorf("basicOldSchemaInner: map struct too large (%d)", extra)
1362
1382
}
1363
1383
1364
-
var name string
1365
1384
n := extra
1366
1385
1386
+
nameBuf := make([]byte, 1)
1367
1387
for i := uint64(0); i < n; i++ {
1388
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 8192)
1389
+
if err != nil {
1390
+
return err
1391
+
}
1368
1392
1369
-
{
1370
-
sval, err := cbg.ReadStringWithMax(cr, 8192)
1371
-
if err != nil {
1393
+
if !ok {
1394
+
// Field doesn't exist on this type, so ignore it
1395
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
1372
1396
return err
1373
1397
}
1374
-
1375
-
name = string(sval)
1398
+
continue
1376
1399
}
1377
1400
1378
-
switch name {
1401
+
switch string(nameBuf[:nameLen]) {
1379
1402
// t.H (string) (string)
1380
1403
case "h":
1381
1404
···
1474
1497
1475
1498
default:
1476
1499
// Field doesn't exist on this type, so ignore it
1477
-
cbg.ScanForLinks(r, func(cid.Cid) {})
1500
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
1501
+
return err
1502
+
}
1478
1503
}
1479
1504
}
1480
1505
···
1558
1583
return fmt.Errorf("ipldOldSchema: map struct too large (%d)", extra)
1559
1584
}
1560
1585
1561
-
var name string
1562
1586
n := extra
1563
1587
1588
+
nameBuf := make([]byte, 1)
1564
1589
for i := uint64(0); i < n; i++ {
1590
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 8192)
1591
+
if err != nil {
1592
+
return err
1593
+
}
1565
1594
1566
-
{
1567
-
sval, err := cbg.ReadStringWithMax(cr, 8192)
1568
-
if err != nil {
1595
+
if !ok {
1596
+
// Field doesn't exist on this type, so ignore it
1597
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
1569
1598
return err
1570
1599
}
1571
-
1572
-
name = string(sval)
1600
+
continue
1573
1601
}
1574
1602
1575
-
switch name {
1603
+
switch string(nameBuf[:nameLen]) {
1576
1604
// t.A (util.LexLink) (struct)
1577
1605
case "a":
1578
1606
···
1608
1636
1609
1637
default:
1610
1638
// Field doesn't exist on this type, so ignore it
1611
-
cbg.ScanForLinks(r, func(cid.Cid) {})
1639
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
1640
+
return err
1641
+
}
1612
1642
}
1613
1643
}
1614
1644
+26
-16
mst/cbor_gen.go
+26
-16
mst/cbor_gen.go
···
104
104
return fmt.Errorf("nodeData: map struct too large (%d)", extra)
105
105
}
106
106
107
-
var name string
108
107
n := extra
109
108
109
+
nameBuf := make([]byte, 1)
110
110
for i := uint64(0); i < n; i++ {
111
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
112
+
if err != nil {
113
+
return err
114
+
}
111
115
112
-
{
113
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
114
-
if err != nil {
116
+
if !ok {
117
+
// Field doesn't exist on this type, so ignore it
118
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
115
119
return err
116
120
}
117
-
118
-
name = string(sval)
121
+
continue
119
122
}
120
123
121
-
switch name {
124
+
switch string(nameBuf[:nameLen]) {
122
125
// t.Entries ([]mst.treeEntry) (slice)
123
126
case "e":
124
127
···
184
187
185
188
default:
186
189
// Field doesn't exist on this type, so ignore it
187
-
cbg.ScanForLinks(r, func(cid.Cid) {})
190
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
191
+
return err
192
+
}
188
193
}
189
194
}
190
195
···
312
317
return fmt.Errorf("treeEntry: map struct too large (%d)", extra)
313
318
}
314
319
315
-
var name string
316
320
n := extra
317
321
322
+
nameBuf := make([]byte, 1)
318
323
for i := uint64(0); i < n; i++ {
324
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
325
+
if err != nil {
326
+
return err
327
+
}
319
328
320
-
{
321
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
322
-
if err != nil {
329
+
if !ok {
330
+
// Field doesn't exist on this type, so ignore it
331
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
323
332
return err
324
333
}
325
-
326
-
name = string(sval)
334
+
continue
327
335
}
328
336
329
-
switch name {
337
+
switch string(nameBuf[:nameLen]) {
330
338
// t.KeySuffix ([]uint8) (slice)
331
339
case "k":
332
340
···
415
423
416
424
default:
417
425
// Field doesn't exist on this type, so ignore it
418
-
cbg.ScanForLinks(r, func(cid.Cid) {})
426
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
427
+
return err
428
+
}
419
429
}
420
430
}
421
431
+26
-16
repo/cbor_gen.go
+26
-16
repo/cbor_gen.go
···
194
194
return fmt.Errorf("SignedCommit: map struct too large (%d)", extra)
195
195
}
196
196
197
-
var name string
198
197
n := extra
199
198
199
+
nameBuf := make([]byte, 7)
200
200
for i := uint64(0); i < n; i++ {
201
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
202
+
if err != nil {
203
+
return err
204
+
}
201
205
202
-
{
203
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
204
-
if err != nil {
206
+
if !ok {
207
+
// Field doesn't exist on this type, so ignore it
208
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
205
209
return err
206
210
}
207
-
208
-
name = string(sval)
211
+
continue
209
212
}
210
213
211
-
switch name {
214
+
switch string(nameBuf[:nameLen]) {
212
215
// t.Did (string) (string)
213
216
case "did":
214
217
···
319
322
320
323
default:
321
324
// Field doesn't exist on this type, so ignore it
322
-
cbg.ScanForLinks(r, func(cid.Cid) {})
325
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
326
+
return err
327
+
}
323
328
}
324
329
}
325
330
···
477
482
return fmt.Errorf("UnsignedCommit: map struct too large (%d)", extra)
478
483
}
479
484
480
-
var name string
481
485
n := extra
482
486
487
+
nameBuf := make([]byte, 7)
483
488
for i := uint64(0); i < n; i++ {
489
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
490
+
if err != nil {
491
+
return err
492
+
}
484
493
485
-
{
486
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
487
-
if err != nil {
494
+
if !ok {
495
+
// Field doesn't exist on this type, so ignore it
496
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
488
497
return err
489
498
}
490
-
491
-
name = string(sval)
499
+
continue
492
500
}
493
501
494
-
switch name {
502
+
switch string(nameBuf[:nameLen]) {
495
503
// t.Did (string) (string)
496
504
case "did":
497
505
···
579
587
580
588
default:
581
589
// Field doesn't exist on this type, so ignore it
582
-
cbg.ScanForLinks(r, func(cid.Cid) {})
590
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
591
+
return err
592
+
}
583
593
}
584
594
}
585
595
+13
-8
util/labels/cbor_gen.go
+13
-8
util/labels/cbor_gen.go
···
285
285
return fmt.Errorf("UnsignedLabel: map struct too large (%d)", extra)
286
286
}
287
287
288
-
var name string
289
288
n := extra
290
289
290
+
nameBuf := make([]byte, 3)
291
291
for i := uint64(0); i < n; i++ {
292
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
293
+
if err != nil {
294
+
return err
295
+
}
292
296
293
-
{
294
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
295
-
if err != nil {
297
+
if !ok {
298
+
// Field doesn't exist on this type, so ignore it
299
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
296
300
return err
297
301
}
298
-
299
-
name = string(sval)
302
+
continue
300
303
}
301
304
302
-
switch name {
305
+
switch string(nameBuf[:nameLen]) {
303
306
// t.Cid (string) (string)
304
307
case "cid":
305
308
···
458
461
459
462
default:
460
463
// Field doesn't exist on this type, so ignore it
461
-
cbg.ScanForLinks(r, func(cid.Cid) {})
464
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
465
+
return err
466
+
}
462
467
}
463
468
}
464
469