@atproto/bsky 0.0.28 → 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.
- package/CHANGELOG.md +7 -0
- package/LICENSE.txt +1 -1
- package/dist/auto-moderator/index.d.ts +1 -14
- package/dist/context.d.ts +0 -3
- package/dist/db/index.js +34593 -3478
- package/dist/db/index.js.map +3 -3
- package/dist/db/pagination.d.ts +1 -0
- package/dist/index.d.ts +0 -4
- package/dist/index.js +1531 -1723
- package/dist/index.js.map +3 -3
- package/dist/indexer/config.d.ts +0 -8
- package/dist/lexicon/index.d.ts +0 -2
- package/dist/lexicon/lexicons.d.ts +38 -47
- package/dist/lexicon/types/com/atproto/admin/defs.d.ts +2 -2
- package/dist/lexicon/types/com/atproto/admin/queryModerationEvents.d.ts +7 -0
- package/package.json +7 -6
- package/src/api/app/bsky/actor/getSuggestions.ts +1 -1
- package/src/api/app/bsky/actor/searchActors.ts +1 -0
- package/src/api/app/bsky/feed/getActorFeeds.ts +6 -0
- package/src/api/app/bsky/feed/getActorLikes.ts +4 -0
- package/src/api/app/bsky/feed/getAuthorFeed.ts +4 -0
- package/src/api/app/bsky/feed/getFeed.ts +8 -5
- package/src/api/app/bsky/feed/getLikes.ts +4 -0
- package/src/api/app/bsky/feed/getListFeed.ts +4 -0
- package/src/api/app/bsky/feed/getRepostedBy.ts +4 -0
- package/src/api/app/bsky/feed/getSuggestedFeeds.ts +1 -1
- package/src/api/app/bsky/feed/getTimeline.ts +4 -0
- package/src/api/app/bsky/feed/searchPosts.ts +1 -0
- package/src/api/app/bsky/graph/getBlocks.ts +7 -0
- package/src/api/app/bsky/graph/getFollowers.ts +4 -0
- package/src/api/app/bsky/graph/getFollows.ts +4 -0
- package/src/api/app/bsky/graph/getList.ts +4 -0
- package/src/api/app/bsky/graph/getListBlocks.ts +4 -0
- package/src/api/app/bsky/graph/getListMutes.ts +7 -0
- package/src/api/app/bsky/graph/getLists.ts +7 -0
- package/src/api/app/bsky/graph/getMutes.ts +7 -0
- package/src/api/app/bsky/notification/listNotifications.ts +3 -0
- package/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts +7 -1
- package/src/api/index.ts +0 -6
- package/src/auto-moderator/index.ts +9 -176
- package/src/context.ts +0 -6
- package/src/db/pagination.ts +3 -0
- package/src/index.ts +1 -6
- package/src/indexer/config.ts +0 -29
- package/src/lexicon/index.ts +0 -12
- package/src/lexicon/lexicons.ts +43 -50
- package/src/lexicon/types/com/atproto/admin/defs.ts +2 -0
- package/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts +13 -0
- package/src/logger.ts +32 -0
- package/tests/auto-moderator/labeler.test.ts +2 -0
- package/tests/feed-generation.test.ts +0 -6
- package/tests/views/notifications.test.ts +9 -0
- package/tests/views/timeline.test.ts +8 -0
- package/dist/api/app/bsky/feed/describeFeedGenerator.d.ts +0 -3
- package/dist/api/app/bsky/feed/getFeedSkeleton.d.ts +0 -3
- package/dist/api/app/bsky/unspecced/getTimelineSkeleton.d.ts +0 -3
- package/dist/auto-moderator/abyss.d.ts +0 -48
- package/dist/auto-moderator/fuzzy-matcher.d.ts +0 -14
- package/dist/feed-gen/bsky-team.d.ts +0 -3
- package/dist/feed-gen/hot-classic.d.ts +0 -3
- package/dist/feed-gen/index.d.ts +0 -2
- package/dist/feed-gen/mutuals.d.ts +0 -3
- package/dist/feed-gen/types.d.ts +0 -15
- package/dist/lexicon/types/app/bsky/unspecced/getTimelineSkeleton.d.ts +0 -35
- package/src/api/app/bsky/feed/describeFeedGenerator.ts +0 -21
- package/src/api/app/bsky/feed/getFeedSkeleton.ts +0 -30
- package/src/api/app/bsky/unspecced/getTimelineSkeleton.ts +0 -26
- package/src/auto-moderator/abyss.ts +0 -114
- package/src/auto-moderator/fuzzy-matcher.ts +0 -126
- package/src/feed-gen/bsky-team.ts +0 -42
- package/src/feed-gen/hot-classic.ts +0 -55
- package/src/feed-gen/index.ts +0 -17
- package/src/feed-gen/mutuals.ts +0 -57
- package/src/feed-gen/types.ts +0 -32
- package/src/lexicon/types/app/bsky/unspecced/getTimelineSkeleton.ts +0 -49
- package/tests/algos/hot-classic.test.ts +0 -87
- package/tests/auto-moderator/fuzzy-matcher.test.ts +0 -163
- package/tests/auto-moderator/takedowns.test.ts +0 -202
|
@@ -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;
|
package/dist/feed-gen/index.d.ts
DELETED
package/dist/feed-gen/types.d.ts
DELETED
|
@@ -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,35 +0,0 @@
|
|
|
1
|
-
import express from 'express';
|
|
2
|
-
import { HandlerAuth } from '@atproto/xrpc-server';
|
|
3
|
-
import * as AppBskyFeedDefs from '../feed/defs';
|
|
4
|
-
export interface QueryParams {
|
|
5
|
-
limit: number;
|
|
6
|
-
cursor?: string;
|
|
7
|
-
}
|
|
8
|
-
export type InputSchema = undefined;
|
|
9
|
-
export interface OutputSchema {
|
|
10
|
-
cursor?: string;
|
|
11
|
-
feed: AppBskyFeedDefs.SkeletonFeedPost[];
|
|
12
|
-
[k: string]: unknown;
|
|
13
|
-
}
|
|
14
|
-
export type HandlerInput = undefined;
|
|
15
|
-
export interface HandlerSuccess {
|
|
16
|
-
encoding: 'application/json';
|
|
17
|
-
body: OutputSchema;
|
|
18
|
-
headers?: {
|
|
19
|
-
[key: string]: string;
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
export interface HandlerError {
|
|
23
|
-
status: number;
|
|
24
|
-
message?: string;
|
|
25
|
-
error?: 'UnknownFeed';
|
|
26
|
-
}
|
|
27
|
-
export type HandlerOutput = HandlerError | HandlerSuccess;
|
|
28
|
-
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
|
|
29
|
-
auth: HA;
|
|
30
|
-
params: QueryParams;
|
|
31
|
-
input: HandlerInput;
|
|
32
|
-
req: express.Request;
|
|
33
|
-
res: express.Response;
|
|
34
|
-
};
|
|
35
|
-
export type Handler<HA extends HandlerAuth = never> = (ctx: HandlerReqCtx<HA>) => Promise<HandlerOutput> | HandlerOutput;
|
|
@@ -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
|
package/src/feed-gen/index.ts
DELETED
|
@@ -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
|
-
})
|
package/src/feed-gen/mutuals.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { QueryParams as SkeletonParams } from '../lexicon/types/app/bsky/feed/getFeedSkeleton'
|
|
2
|
-
import AppContext from '../context'
|
|
3
|
-
import { paginate } from '../db/pagination'
|
|
4
|
-
import { AlgoHandler, AlgoResponse } from './types'
|
|
5
|
-
import { FeedKeyset, getFeedDateThreshold } from '../api/app/bsky/util/feed'
|
|
6
|
-
import { AuthRequiredError } from '@atproto/xrpc-server'
|
|
7
|
-
|
|
8
|
-
const handler: AlgoHandler = async (
|
|
9
|
-
ctx: AppContext,
|
|
10
|
-
params: SkeletonParams,
|
|
11
|
-
viewer: string | null,
|
|
12
|
-
): Promise<AlgoResponse> => {
|
|
13
|
-
if (!viewer) {
|
|
14
|
-
throw new AuthRequiredError('This feed requires being logged-in')
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const { limit = 50, cursor } = params
|
|
18
|
-
const db = ctx.db.getReplica('feed')
|
|
19
|
-
const feedService = ctx.services.feed(db)
|
|
20
|
-
const { ref } = db.db.dynamic
|
|
21
|
-
|
|
22
|
-
const mutualsSubquery = db.db
|
|
23
|
-
.selectFrom('follow')
|
|
24
|
-
.where('follow.creator', '=', viewer)
|
|
25
|
-
.whereExists((qb) =>
|
|
26
|
-
qb
|
|
27
|
-
.selectFrom('follow as follow_inner')
|
|
28
|
-
.whereRef('follow_inner.creator', '=', 'follow.subjectDid')
|
|
29
|
-
.where('follow_inner.subjectDid', '=', viewer)
|
|
30
|
-
.selectAll(),
|
|
31
|
-
)
|
|
32
|
-
.select('follow.subjectDid')
|
|
33
|
-
|
|
34
|
-
const keyset = new FeedKeyset(ref('feed_item.sortAt'), ref('feed_item.cid'))
|
|
35
|
-
const sortFrom = keyset.unpack(cursor)?.primary
|
|
36
|
-
|
|
37
|
-
let feedQb = feedService
|
|
38
|
-
.selectFeedItemQb()
|
|
39
|
-
.where('feed_item.type', '=', 'post') // ensures originatorDid is post.creator
|
|
40
|
-
.where((qb) =>
|
|
41
|
-
qb
|
|
42
|
-
.where('originatorDid', '=', viewer)
|
|
43
|
-
.orWhere('originatorDid', 'in', mutualsSubquery),
|
|
44
|
-
)
|
|
45
|
-
.where('feed_item.sortAt', '>', getFeedDateThreshold(sortFrom))
|
|
46
|
-
|
|
47
|
-
feedQb = paginate(feedQb, { limit, cursor, keyset })
|
|
48
|
-
|
|
49
|
-
const feedItems = await feedQb.execute()
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
feedItems,
|
|
53
|
-
cursor: keyset.packFromResult(feedItems),
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export default handler
|
package/src/feed-gen/types.ts
DELETED
|
@@ -1,32 +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
|
-
|
|
6
|
-
export type AlgoResponse = {
|
|
7
|
-
feedItems: FeedRow[]
|
|
8
|
-
resHeaders?: Record<string, string>
|
|
9
|
-
cursor?: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export type AlgoHandler = (
|
|
13
|
-
ctx: AppContext,
|
|
14
|
-
params: SkeletonParams,
|
|
15
|
-
viewer: string | null,
|
|
16
|
-
) => Promise<AlgoResponse>
|
|
17
|
-
|
|
18
|
-
export type MountedAlgos = Record<string, AlgoHandler>
|
|
19
|
-
|
|
20
|
-
export const toSkeletonItem = (feedItem: {
|
|
21
|
-
uri: string
|
|
22
|
-
postUri: string
|
|
23
|
-
}): SkeletonFeedPost => ({
|
|
24
|
-
post: feedItem.postUri,
|
|
25
|
-
reason:
|
|
26
|
-
feedItem.uri === feedItem.postUri
|
|
27
|
-
? undefined
|
|
28
|
-
: {
|
|
29
|
-
$type: 'app.bsky.feed.defs#skeletonReasonRepost',
|
|
30
|
-
repost: feedItem.uri,
|
|
31
|
-
},
|
|
32
|
-
})
|