fork of indigo with slightly nicer lexgen

:rightwards_twisted_arrows: Merge with upstream

+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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 1 + // Code for consuming from atproto firehose and ozone event stream, pushing events in to automod engine. 2 + package consumer
+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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 6 6 ) 7 7 8 8 type Engine = engine.Engine 9 + type EngineConfig = engine.EngineConfig 9 10 type AccountMeta = engine.AccountMeta 10 11 type ProfileSummary = engine.ProfileSummary 11 12 type AccountPrivate = engine.AccountPrivate
+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
··· 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
+4 -3
automod/rules/hashtags.go
··· 5 5 6 6 appbsky "github.com/bluesky-social/indigo/api/bsky" 7 7 "github.com/bluesky-social/indigo/automod" 8 + "github.com/bluesky-social/indigo/automod/helpers" 8 9 "github.com/bluesky-social/indigo/automod/keyword" 9 10 ) 10 11 11 12 // looks for specific hashtags from known lists 12 13 func BadHashtagsPostRule(c *automod.RecordContext, post *appbsky.FeedPost) error { 13 - for _, tag := range ExtractHashtagsPost(post) { 14 - tag = NormalizeHashtag(tag) 14 + for _, tag := range helpers.ExtractHashtagsPost(post) { 15 + tag = helpers.NormalizeHashtag(tag) 15 16 // skip some bad-word hashtags which frequently false-positive 16 17 if tag == "nazi" || tag == "hitler" { 17 18 continue ··· 35 36 36 37 // if a post is "almost all" hashtags, it might be a form of search spam 37 38 func TooManyHashtagsPostRule(c *automod.RecordContext, post *appbsky.FeedPost) error { 38 - tags := ExtractHashtagsPost(post) 39 + tags := helpers.ExtractHashtagsPost(post) 39 40 tagChars := 0 40 41 for _, tag := range tags { 41 42 tagChars += len(tag)
+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
··· 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
··· 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
··· 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
··· 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
··· 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 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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