@atproto/bsky 0.0.27 → 0.0.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/LICENSE.txt +1 -1
  3. package/dist/auto-moderator/index.d.ts +1 -14
  4. package/dist/context.d.ts +0 -3
  5. package/dist/db/index.js +34593 -3478
  6. package/dist/db/index.js.map +3 -3
  7. package/dist/db/pagination.d.ts +1 -0
  8. package/dist/index.d.ts +0 -4
  9. package/dist/index.js +1667 -1786
  10. package/dist/index.js.map +3 -3
  11. package/dist/indexer/config.d.ts +0 -8
  12. package/dist/lexicon/index.d.ts +2 -2
  13. package/dist/lexicon/lexicons.d.ts +67 -47
  14. package/dist/lexicon/types/com/atproto/admin/defs.d.ts +2 -2
  15. package/dist/lexicon/types/com/atproto/admin/queryModerationEvents.d.ts +7 -0
  16. package/dist/lexicon/types/{app/bsky/unspecced/getTimelineSkeleton.d.ts → com/atproto/temp/checkSignupQueue.d.ts} +3 -6
  17. package/package.json +6 -5
  18. package/src/api/app/bsky/actor/getSuggestions.ts +1 -1
  19. package/src/api/app/bsky/actor/searchActors.ts +1 -0
  20. package/src/api/app/bsky/feed/getActorFeeds.ts +6 -0
  21. package/src/api/app/bsky/feed/getActorLikes.ts +4 -0
  22. package/src/api/app/bsky/feed/getAuthorFeed.ts +4 -0
  23. package/src/api/app/bsky/feed/getFeed.ts +8 -5
  24. package/src/api/app/bsky/feed/getLikes.ts +4 -0
  25. package/src/api/app/bsky/feed/getListFeed.ts +4 -0
  26. package/src/api/app/bsky/feed/getRepostedBy.ts +4 -0
  27. package/src/api/app/bsky/feed/getSuggestedFeeds.ts +1 -1
  28. package/src/api/app/bsky/feed/getTimeline.ts +4 -0
  29. package/src/api/app/bsky/feed/searchPosts.ts +1 -0
  30. package/src/api/app/bsky/graph/getBlocks.ts +7 -0
  31. package/src/api/app/bsky/graph/getFollowers.ts +4 -0
  32. package/src/api/app/bsky/graph/getFollows.ts +4 -0
  33. package/src/api/app/bsky/graph/getList.ts +4 -0
  34. package/src/api/app/bsky/graph/getListBlocks.ts +4 -0
  35. package/src/api/app/bsky/graph/getListMutes.ts +7 -0
  36. package/src/api/app/bsky/graph/getLists.ts +7 -0
  37. package/src/api/app/bsky/graph/getMutes.ts +7 -0
  38. package/src/api/app/bsky/notification/listNotifications.ts +3 -0
  39. package/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts +7 -1
  40. package/src/api/index.ts +0 -6
  41. package/src/auto-moderator/index.ts +9 -176
  42. package/src/context.ts +0 -6
  43. package/src/db/pagination.ts +3 -0
  44. package/src/index.ts +1 -6
  45. package/src/indexer/config.ts +0 -29
  46. package/src/lexicon/index.ts +12 -12
  47. package/src/lexicon/lexicons.ts +72 -50
  48. package/src/lexicon/types/com/atproto/admin/defs.ts +2 -0
  49. package/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts +13 -0
  50. package/src/lexicon/types/{app/bsky/unspecced/getTimelineSkeleton.ts → com/atproto/temp/checkSignupQueue.ts} +4 -8
  51. package/src/logger.ts +32 -0
  52. package/tests/auto-moderator/labeler.test.ts +2 -0
  53. package/tests/feed-generation.test.ts +0 -6
  54. package/tests/views/notifications.test.ts +9 -0
  55. package/tests/views/timeline.test.ts +8 -0
  56. package/dist/api/app/bsky/feed/describeFeedGenerator.d.ts +0 -3
  57. package/dist/api/app/bsky/feed/getFeedSkeleton.d.ts +0 -3
  58. package/dist/api/app/bsky/unspecced/getTimelineSkeleton.d.ts +0 -3
  59. package/dist/auto-moderator/abyss.d.ts +0 -48
  60. package/dist/auto-moderator/fuzzy-matcher.d.ts +0 -14
  61. package/dist/feed-gen/bsky-team.d.ts +0 -3
  62. package/dist/feed-gen/hot-classic.d.ts +0 -3
  63. package/dist/feed-gen/index.d.ts +0 -2
  64. package/dist/feed-gen/mutuals.d.ts +0 -3
  65. package/dist/feed-gen/types.d.ts +0 -15
  66. package/src/api/app/bsky/feed/describeFeedGenerator.ts +0 -21
  67. package/src/api/app/bsky/feed/getFeedSkeleton.ts +0 -30
  68. package/src/api/app/bsky/unspecced/getTimelineSkeleton.ts +0 -26
  69. package/src/auto-moderator/abyss.ts +0 -114
  70. package/src/auto-moderator/fuzzy-matcher.ts +0 -126
  71. package/src/feed-gen/bsky-team.ts +0 -42
  72. package/src/feed-gen/hot-classic.ts +0 -55
  73. package/src/feed-gen/index.ts +0 -17
  74. package/src/feed-gen/mutuals.ts +0 -57
  75. package/src/feed-gen/types.ts +0 -32
  76. package/tests/algos/hot-classic.test.ts +0 -87
  77. package/tests/auto-moderator/fuzzy-matcher.test.ts +0 -163
  78. package/tests/auto-moderator/takedowns.test.ts +0 -202
@@ -7,18 +7,15 @@ import { lexicons } from '../../../../lexicons'
7
7
  import { isObj, hasProp } from '../../../../util'
8
8
  import { CID } from 'multiformats/cid'
9
9
  import { HandlerAuth } from '@atproto/xrpc-server'
10
- import * as AppBskyFeedDefs from '../feed/defs'
11
10
 
12
- export interface QueryParams {
13
- limit: number
14
- cursor?: string
15
- }
11
+ export interface QueryParams {}
16
12
 
17
13
  export type InputSchema = undefined
18
14
 
19
15
  export interface OutputSchema {
20
- cursor?: string
21
- feed: AppBskyFeedDefs.SkeletonFeedPost[]
16
+ activated: boolean
17
+ placeInQueue?: number
18
+ estimatedTimeMs?: number
22
19
  [k: string]: unknown
23
20
  }
24
21
 
@@ -33,7 +30,6 @@ export interface HandlerSuccess {
33
30
  export interface HandlerError {
34
31
  status: number
35
32
  message?: string
36
- error?: 'UnknownFeed'
37
33
  }
38
34
 
39
35
  export type HandlerOutput = HandlerError | HandlerSuccess
package/src/logger.ts CHANGED
@@ -1,5 +1,8 @@
1
+ import pino from 'pino'
1
2
  import pinoHttp from 'pino-http'
3
+ import * as jose from 'jose'
2
4
  import { subsystemLogger } from '@atproto/common'
5
+ import { parseBasicAuth } from './auth-verifier'
3
6
 
4
7
  export const dbLogger: ReturnType<typeof subsystemLogger> =
5
8
  subsystemLogger('bsky:db')
@@ -21,5 +24,34 @@ export const loggerMiddleware = pinoHttp({
21
24
  message: err?.message,
22
25
  }
23
26
  },
27
+ req: (req) => {
28
+ const serialized = pino.stdSerializers.req(req)
29
+ const authHeader = serialized.headers.authorization || ''
30
+ let auth: string | undefined = undefined
31
+ if (authHeader.startsWith('Bearer ')) {
32
+ const token = authHeader.slice('Bearer '.length)
33
+ const { iss } = jose.decodeJwt(token)
34
+ if (iss) {
35
+ auth = 'Bearer ' + iss
36
+ } else {
37
+ auth = 'Bearer Invalid'
38
+ }
39
+ }
40
+ if (authHeader.startsWith('Basic ')) {
41
+ const parsed = parseBasicAuth(authHeader)
42
+ if (!parsed) {
43
+ auth = 'Basic Invalid'
44
+ } else {
45
+ auth = 'Basic ' + parsed.username
46
+ }
47
+ }
48
+ return {
49
+ ...serialized,
50
+ headers: {
51
+ ...serialized.headers,
52
+ authorization: auth,
53
+ },
54
+ }
55
+ },
24
56
  },
25
57
  })
@@ -103,6 +103,8 @@ describe('labeler', () => {
103
103
  subject: uri.toString(),
104
104
  limit: 10,
105
105
  types: [],
106
+ addedLabels: [],
107
+ removedLabels: [],
106
108
  })
107
109
  expect(events.length).toBe(1)
108
110
  expect(events[0]).toMatchObject({
@@ -86,12 +86,6 @@ describe('feed generation', () => {
86
86
  await network.close()
87
87
  })
88
88
 
89
- // @TODO enable once getFeed is implemented
90
- it('describes the feed generator', async () => {
91
- const res = await agent.api.app.bsky.feed.describeFeedGenerator()
92
- expect(res.data.did).toBe(network.bsky.ctx.cfg.feedGenDid)
93
- })
94
-
95
89
  it('feed gen records can be created.', async () => {
96
90
  const all = await pdsAgent.api.app.bsky.feed.generator.create(
97
91
  { repo: alice, rkey: 'all' },
@@ -296,4 +296,13 @@ describe('notification views', () => {
296
296
  ),
297
297
  )
298
298
  })
299
+
300
+ it('fails open on clearly bad cursor.', async () => {
301
+ const { data: notifs } =
302
+ await agent.api.app.bsky.notification.listNotifications(
303
+ { cursor: 'bad' },
304
+ { headers: await network.serviceHeaders(alice) },
305
+ )
306
+ expect(notifs).toEqual({ notifications: [] })
307
+ })
299
308
  })
@@ -298,4 +298,12 @@ describe('timeline views', () => {
298
298
  ),
299
299
  )
300
300
  })
301
+
302
+ it('fails open on clearly bad cursor.', async () => {
303
+ const { data: timeline } = await agent.api.app.bsky.feed.getTimeline(
304
+ { cursor: 'bad' },
305
+ { headers: await network.serviceHeaders(alice) },
306
+ )
307
+ expect(timeline).toEqual({ feed: [] })
308
+ })
301
309
  })
@@ -1,3 +0,0 @@
1
- import { Server } from '../../../../lexicon';
2
- import AppContext from '../../../../context';
3
- export default function (server: Server, ctx: AppContext): void;
@@ -1,3 +0,0 @@
1
- import { Server } from '../../../../lexicon';
2
- import AppContext from '../../../../context';
3
- export default function (server: Server, ctx: AppContext): void;
@@ -1,3 +0,0 @@
1
- import { Server } from '../../../../lexicon';
2
- import AppContext from '../../../../context';
3
- export default function (server: Server, ctx: AppContext): void;
@@ -1,48 +0,0 @@
1
- import { CID } from 'multiformats/cid';
2
- import { AtUri } from '@atproto/syntax';
3
- import { PrimaryDatabase } from '../db';
4
- import { IdResolver } from '@atproto/identity';
5
- export interface ImageFlagger {
6
- scanImage(did: string, cid: CID, uri: AtUri): Promise<string[]>;
7
- }
8
- export declare class Abyss implements ImageFlagger {
9
- endpoint: string;
10
- protected password: string;
11
- ctx: {
12
- db: PrimaryDatabase;
13
- idResolver: IdResolver;
14
- };
15
- protected auth: string;
16
- constructor(endpoint: string, password: string, ctx: {
17
- db: PrimaryDatabase;
18
- idResolver: IdResolver;
19
- });
20
- scanImage(did: string, cid: CID, uri: AtUri): Promise<string[]>;
21
- makeReq(did: string, cid: CID, uri: AtUri): Promise<ScannerResp>;
22
- parseRes(res: ScannerResp): string[];
23
- getReqUrl(params: {
24
- did: string;
25
- uri: string;
26
- }): string;
27
- }
28
- type ScannerResp = {
29
- blob: unknown;
30
- match?: {
31
- status: string;
32
- hits: ScannerHit[];
33
- };
34
- classify?: {
35
- hits?: unknown[];
36
- };
37
- review?: {
38
- state?: string;
39
- ticketId?: string;
40
- };
41
- };
42
- type ScannerHit = {
43
- hashType: string;
44
- hashValue: string;
45
- label: string;
46
- corpus: string;
47
- };
48
- export {};
@@ -1,14 +0,0 @@
1
- export interface TextFlagger {
2
- getMatches(string: string): string[];
3
- }
4
- export declare class FuzzyMatcher implements TextFlagger {
5
- private bannedWords;
6
- private falsePositives;
7
- constructor(bannedWords: string[], falsePositives?: string[]);
8
- static fromB64(bannedB64: string, falsePositivesB64?: string): FuzzyMatcher;
9
- private normalize;
10
- private generatePermutations;
11
- getMatches(domain: string): string[];
12
- }
13
- export declare const decode: (encoded: string) => string[];
14
- export declare const encode: (words: string[]) => string;
@@ -1,3 +0,0 @@
1
- import { AlgoHandler } from './types';
2
- declare const handler: AlgoHandler;
3
- export default handler;
@@ -1,3 +0,0 @@
1
- import { AlgoHandler } from './types';
2
- declare const handler: AlgoHandler;
3
- export default handler;
@@ -1,2 +0,0 @@
1
- import { MountedAlgos } from './types';
2
- export declare const makeAlgos: (did: string) => MountedAlgos;
@@ -1,3 +0,0 @@
1
- import { AlgoHandler } from './types';
2
- declare const handler: AlgoHandler;
3
- export default handler;
@@ -1,15 +0,0 @@
1
- import AppContext from '../context';
2
- import { SkeletonFeedPost } from '../lexicon/types/app/bsky/feed/defs';
3
- import { QueryParams as SkeletonParams } from '../lexicon/types/app/bsky/feed/getFeedSkeleton';
4
- import { FeedRow } from '../services/feed';
5
- export type AlgoResponse = {
6
- feedItems: FeedRow[];
7
- resHeaders?: Record<string, string>;
8
- cursor?: string;
9
- };
10
- export type AlgoHandler = (ctx: AppContext, params: SkeletonParams, viewer: string | null) => Promise<AlgoResponse>;
11
- export type MountedAlgos = Record<string, AlgoHandler>;
12
- export declare const toSkeletonItem: (feedItem: {
13
- uri: string;
14
- postUri: string;
15
- }) => SkeletonFeedPost;
@@ -1,21 +0,0 @@
1
- import { MethodNotImplementedError } from '@atproto/xrpc-server'
2
- import { Server } from '../../../../lexicon'
3
- import AppContext from '../../../../context'
4
-
5
- export default function (server: Server, ctx: AppContext) {
6
- server.app.bsky.feed.describeFeedGenerator(async () => {
7
- if (!ctx.cfg.feedGenDid) {
8
- throw new MethodNotImplementedError()
9
- }
10
-
11
- const feeds = Object.keys(ctx.algos).map((uri) => ({ uri }))
12
-
13
- return {
14
- encoding: 'application/json',
15
- body: {
16
- did: ctx.cfg.feedGenDid,
17
- feeds,
18
- },
19
- }
20
- })
21
- }
@@ -1,30 +0,0 @@
1
- import { InvalidRequestError } from '@atproto/xrpc-server'
2
- import { Server } from '../../../../lexicon'
3
- import AppContext from '../../../../context'
4
- import { toSkeletonItem } from '../../../../feed-gen/types'
5
-
6
- export default function (server: Server, ctx: AppContext) {
7
- server.app.bsky.feed.getFeedSkeleton({
8
- auth: ctx.authVerifier.standardOptional,
9
- handler: async ({ params, auth }) => {
10
- const { feed } = params
11
- const viewer = auth.credentials.iss
12
- const localAlgo = ctx.algos[feed]
13
-
14
- if (!localAlgo) {
15
- throw new InvalidRequestError('Unknown feed', 'UnknownFeed')
16
- }
17
-
18
- const result = await localAlgo(ctx, params, viewer)
19
-
20
- return {
21
- encoding: 'application/json',
22
- body: {
23
- // @TODO should we proactively filter blocks/mutes from the skeleton, or treat this similar to other custom feeds?
24
- feed: result.feedItems.map(toSkeletonItem),
25
- cursor: result.cursor,
26
- },
27
- }
28
- },
29
- })
30
- }
@@ -1,26 +0,0 @@
1
- import { Server } from '../../../../lexicon'
2
- import AppContext from '../../../../context'
3
- import { skeleton } from '../feed/getTimeline'
4
- import { toSkeletonItem } from '../../../../feed-gen/types'
5
-
6
- // THIS IS A TEMPORARY UNSPECCED ROUTE
7
- export default function (server: Server, ctx: AppContext) {
8
- server.app.bsky.unspecced.getTimelineSkeleton({
9
- auth: ctx.authVerifier.standard,
10
- handler: async ({ auth, params }) => {
11
- const db = ctx.db.getReplica('timeline')
12
- const feedService = ctx.services.feed(db)
13
- const viewer = auth.credentials.iss
14
-
15
- const result = await skeleton({ ...params, viewer }, { db, feedService })
16
-
17
- return {
18
- encoding: 'application/json',
19
- body: {
20
- feed: result.feedItems.map(toSkeletonItem),
21
- cursor: result.cursor,
22
- },
23
- }
24
- },
25
- })
26
- }
@@ -1,114 +0,0 @@
1
- import axios from 'axios'
2
- import { CID } from 'multiformats/cid'
3
- import { AtUri } from '@atproto/syntax'
4
- import * as ui8 from 'uint8arrays'
5
- import { resolveBlob } from '../api/blob-resolver'
6
- import { retryHttp } from '../util/retry'
7
- import { PrimaryDatabase } from '../db'
8
- import { IdResolver } from '@atproto/identity'
9
- import { labelerLogger as log } from '../logger'
10
-
11
- export interface ImageFlagger {
12
- scanImage(did: string, cid: CID, uri: AtUri): Promise<string[]>
13
- }
14
-
15
- export class Abyss implements ImageFlagger {
16
- protected auth: string
17
-
18
- constructor(
19
- public endpoint: string,
20
- protected password: string,
21
- public ctx: { db: PrimaryDatabase; idResolver: IdResolver },
22
- ) {
23
- this.auth = basicAuth(this.password)
24
- }
25
-
26
- async scanImage(did: string, cid: CID, uri: AtUri): Promise<string[]> {
27
- const start = Date.now()
28
- const res = await retryHttp(async () => {
29
- try {
30
- return await this.makeReq(did, cid, uri)
31
- } catch (err) {
32
- log.warn({ err, did, cid: cid.toString() }, 'abyss request failed')
33
- throw err
34
- }
35
- })
36
- log.info(
37
- { res, did, cid: cid.toString(), duration: Date.now() - start },
38
- 'abyss response',
39
- )
40
- return this.parseRes(res)
41
- }
42
-
43
- async makeReq(did: string, cid: CID, uri: AtUri): Promise<ScannerResp> {
44
- const { stream, contentType } = await resolveBlob(
45
- did,
46
- cid,
47
- this.ctx.db,
48
- this.ctx.idResolver,
49
- )
50
- const { data } = await axios.post(
51
- this.getReqUrl({ did, uri: uri.toString() }),
52
- stream,
53
- {
54
- headers: {
55
- 'Content-Type': contentType,
56
- authorization: this.auth,
57
- },
58
- timeout: 10000,
59
- },
60
- )
61
- return data
62
- }
63
-
64
- parseRes(res: ScannerResp): string[] {
65
- if (!res.match || res.match.status !== 'success') {
66
- return []
67
- }
68
- const labels: string[] = []
69
- for (const hit of res.match.hits) {
70
- if (TAKEDOWN_LABELS.includes(hit.label)) {
71
- labels.push(hit.label)
72
- }
73
- }
74
- return labels
75
- }
76
-
77
- getReqUrl(params: { did: string; uri: string }) {
78
- const search = new URLSearchParams(params)
79
- return `${
80
- this.endpoint
81
- }/xrpc/com.atproto.unspecced.scanBlob?${search.toString()}`
82
- }
83
- }
84
-
85
- const TAKEDOWN_LABELS = ['csam', 'csem']
86
-
87
- type ScannerResp = {
88
- blob: unknown
89
- match?: {
90
- status: string
91
- hits: ScannerHit[]
92
- }
93
- classify?: {
94
- hits?: unknown[]
95
- }
96
- review?: {
97
- state?: string
98
- ticketId?: string
99
- }
100
- }
101
-
102
- type ScannerHit = {
103
- hashType: string
104
- hashValue: string
105
- label: string
106
- corpus: string
107
- }
108
-
109
- const basicAuth = (password: string) => {
110
- return (
111
- 'Basic ' +
112
- ui8.toString(ui8.fromString(`admin:${password}`, 'utf8'), 'base64pad')
113
- )
114
- }
@@ -1,126 +0,0 @@
1
- import { dedupeStrs } from '@atproto/common'
2
- import * as ui8 from 'uint8arrays'
3
-
4
- export interface TextFlagger {
5
- getMatches(string: string): string[]
6
- }
7
-
8
- export class FuzzyMatcher implements TextFlagger {
9
- private bannedWords: Set<string>
10
- private falsePositives: Set<string>
11
-
12
- constructor(bannedWords: string[], falsePositives: string[] = []) {
13
- this.bannedWords = new Set(bannedWords.map((word) => word.toLowerCase()))
14
- this.falsePositives = new Set(
15
- falsePositives.map((word) => word.toLowerCase()),
16
- )
17
- }
18
-
19
- static fromB64(bannedB64: string, falsePositivesB64?: string) {
20
- return new FuzzyMatcher(
21
- decode(bannedB64),
22
- falsePositivesB64 ? decode(falsePositivesB64) : undefined,
23
- )
24
- }
25
-
26
- private normalize(domain: string): string[] {
27
- const withoutSymbols = domain.replace(/[\W_]+/g, '') // Remove non-alphanumeric characters
28
- const lowercase = withoutSymbols.toLowerCase()
29
-
30
- // Replace common leetspeak characters
31
- const leetSpeakReplacements: { [key: string]: string[] } = {
32
- '0': ['o'],
33
- '8': ['b'],
34
- '3': ['e'],
35
- '4': ['a'],
36
- '6': ['g'],
37
- '1': ['i', 'l'],
38
- '5': ['s'],
39
- '7': ['t'],
40
- }
41
-
42
- return this.generatePermutations(lowercase, leetSpeakReplacements)
43
- }
44
-
45
- private generatePermutations(
46
- domain: string,
47
- leetSpeakReplacements: { [key: string]: string[] },
48
- ): string[] {
49
- const results: string[] = []
50
-
51
- const leetChars = Object.keys(leetSpeakReplacements)
52
- const firstLeetCharIndex = [...domain].findIndex((char) =>
53
- leetChars.includes(char),
54
- )
55
-
56
- if (firstLeetCharIndex === -1) {
57
- // No leetspeak characters left in the string
58
- results.push(domain)
59
- } else {
60
- const char = domain[firstLeetCharIndex]
61
- const beforeChar = domain.slice(0, firstLeetCharIndex)
62
- const afterChar = domain.slice(firstLeetCharIndex + 1)
63
-
64
- // For each replacement, generate all possible combinations
65
- for (const replacement of leetSpeakReplacements[char]) {
66
- const replaced = beforeChar + replacement + afterChar
67
-
68
- // Recursively generate all permutations for the rest of the string
69
- const otherPermutations = this.generatePermutations(
70
- replaced,
71
- leetSpeakReplacements,
72
- )
73
-
74
- // Add these permutations to the results
75
- results.push(...otherPermutations)
76
- }
77
- }
78
-
79
- return dedupeStrs(results)
80
- }
81
-
82
- public getMatches(domain: string): string[] {
83
- const normalizedDomains = this.normalize(domain)
84
-
85
- const foundUnacceptableWords: string[] = []
86
-
87
- for (const normalizedDomain of normalizedDomains) {
88
- for (const word of this.bannedWords) {
89
- const match = normalizedDomain.indexOf(word)
90
- if (match > -1) {
91
- let isFalsePositive = false
92
- for (const falsePositive of this.falsePositives) {
93
- const s_fp = falsePositive.indexOf(word)
94
- const s_nd = match - s_fp
95
- const wordToMatch = normalizedDomain.slice(
96
- s_nd,
97
- s_nd + falsePositive.length,
98
- )
99
- if (wordToMatch === falsePositive) {
100
- isFalsePositive = true
101
- break
102
- }
103
- }
104
-
105
- if (!isFalsePositive) {
106
- foundUnacceptableWords.push(word)
107
- }
108
- }
109
- }
110
- }
111
-
112
- if (foundUnacceptableWords.length > 0) {
113
- return foundUnacceptableWords
114
- }
115
-
116
- return []
117
- }
118
- }
119
-
120
- export const decode = (encoded: string): string[] => {
121
- return ui8.toString(ui8.fromString(encoded, 'base64'), 'utf8').split(',')
122
- }
123
-
124
- export const encode = (words: string[]): string => {
125
- return ui8.toString(ui8.fromString(words.join(','), 'utf8'), 'base64')
126
- }
@@ -1,42 +0,0 @@
1
- import { NotEmptyArray } from '@atproto/common'
2
- import { QueryParams as SkeletonParams } from '../lexicon/types/app/bsky/feed/getFeedSkeleton'
3
- import AppContext from '../context'
4
- import { paginate } from '../db/pagination'
5
- import { AlgoHandler, AlgoResponse } from './types'
6
- import { FeedKeyset } from '../api/app/bsky/util/feed'
7
-
8
- const BSKY_TEAM: NotEmptyArray<string> = [
9
- 'did:plc:z72i7hdynmk6r22z27h6tvur', // @bsky.app
10
- 'did:plc:ewvi7nxzyoun6zhxrhs64oiz', // @atproto.com
11
- 'did:plc:eon2iu7v3x2ukgxkqaf7e5np', // @safety.bsky.app
12
- ]
13
-
14
- const handler: AlgoHandler = async (
15
- ctx: AppContext,
16
- params: SkeletonParams,
17
- _viewer: string | null,
18
- ): Promise<AlgoResponse> => {
19
- const { limit = 50, cursor } = params
20
- const db = ctx.db.getReplica('feed')
21
- const feedService = ctx.services.feed(db)
22
-
23
- const { ref } = db.db.dynamic
24
-
25
- const postsQb = feedService
26
- .selectPostQb()
27
- .where('post.creator', 'in', BSKY_TEAM)
28
-
29
- const keyset = new FeedKeyset(ref('sortAt'), ref('cid'))
30
-
31
- let feedQb = db.db.selectFrom(postsQb.as('feed_items')).selectAll()
32
- feedQb = paginate(feedQb, { limit, cursor, keyset })
33
-
34
- const feedItems = await feedQb.execute()
35
-
36
- return {
37
- feedItems,
38
- cursor: keyset.packFromResult(feedItems),
39
- }
40
- }
41
-
42
- export default handler
@@ -1,55 +0,0 @@
1
- import AppContext from '../context'
2
- import { NotEmptyArray } from '@atproto/common'
3
- import { QueryParams as SkeletonParams } from '../lexicon/types/app/bsky/feed/getFeedSkeleton'
4
- import { paginate } from '../db/pagination'
5
- import { AlgoHandler, AlgoResponse } from './types'
6
- import { FeedKeyset } from '../api/app/bsky/util/feed'
7
- import { valuesList } from '../db/util'
8
-
9
- const NO_WHATS_HOT_LABELS: NotEmptyArray<string> = ['!no-promote']
10
-
11
- const handler: AlgoHandler = async (
12
- ctx: AppContext,
13
- params: SkeletonParams,
14
- _viewer: string | null,
15
- ): Promise<AlgoResponse> => {
16
- const { limit = 50, cursor } = params
17
- const db = ctx.db.getReplica('feed')
18
- const feedService = ctx.services.feed(db)
19
-
20
- const { ref } = db.db.dynamic
21
-
22
- const postsQb = feedService
23
- .selectPostQb()
24
- .leftJoin('post_agg', 'post_agg.uri', 'post.uri')
25
- .leftJoin('post_embed_record', 'post_embed_record.postUri', 'post.uri')
26
- .where('post_agg.likeCount', '>=', 12)
27
- .where('post.replyParent', 'is', null)
28
- .whereNotExists((qb) =>
29
- qb
30
- .selectFrom('label')
31
- .selectAll()
32
- .whereRef('val', 'in', valuesList(NO_WHATS_HOT_LABELS))
33
- .where('neg', '=', false)
34
- .where((clause) =>
35
- clause
36
- .whereRef('label.uri', '=', ref('post.creator'))
37
- .orWhereRef('label.uri', '=', ref('post.uri'))
38
- .orWhereRef('label.uri', '=', ref('post_embed_record.embedUri')),
39
- ),
40
- )
41
-
42
- const keyset = new FeedKeyset(ref('sortAt'), ref('cid'))
43
-
44
- let feedQb = db.db.selectFrom(postsQb.as('feed_items')).selectAll()
45
- feedQb = paginate(feedQb, { limit, cursor, keyset })
46
-
47
- const feedItems = await feedQb.execute()
48
-
49
- return {
50
- feedItems,
51
- cursor: keyset.packFromResult(feedItems),
52
- }
53
- }
54
-
55
- export default handler
@@ -1,17 +0,0 @@
1
- import { AtUri } from '@atproto/syntax'
2
- import { ids } from '../lexicon/lexicons'
3
- import bskyTeam from './bsky-team'
4
- import hotClassic from './hot-classic'
5
- import mutuals from './mutuals'
6
- import { MountedAlgos } from './types'
7
-
8
- const feedgenUri = (did, name) =>
9
- AtUri.make(did, ids.AppBskyFeedGenerator, name).toString()
10
-
11
- // These are custom algorithms that will be mounted directly onto an AppView
12
- // Feel free to remove, update to your own, or serve the following logic at a record that you control
13
- export const makeAlgos = (did: string): MountedAlgos => ({
14
- [feedgenUri(did, 'bsky-team')]: bskyTeam,
15
- [feedgenUri(did, 'hot-classic')]: hotClassic,
16
- [feedgenUri(did, 'mutuals')]: mutuals,
17
- })