@atproto/bsky 0.0.173 → 0.0.174
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 +9 -0
- package/dist/api/app/bsky/unspecced/checkHandleAvailability.d.ts +4 -0
- package/dist/api/app/bsky/unspecced/checkHandleAvailability.d.ts.map +1 -0
- package/dist/api/app/bsky/unspecced/checkHandleAvailability.js +238 -0
- package/dist/api/app/bsky/unspecced/checkHandleAvailability.js.map +1 -0
- package/dist/api/app/bsky/unspecced/initAgeAssurance.d.ts.map +1 -1
- package/dist/api/app/bsky/unspecced/initAgeAssurance.js +20 -0
- package/dist/api/app/bsky/unspecced/initAgeAssurance.js.map +1 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +2 -0
- package/dist/api/index.js.map +1 -1
- package/dist/lexicon/index.d.ts +4 -2
- package/dist/lexicon/index.d.ts.map +1 -1
- package/dist/lexicon/index.js +8 -4
- package/dist/lexicon/index.js.map +1 -1
- package/dist/lexicon/lexicons.d.ts +258 -82
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +137 -42
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/app/bsky/unspecced/checkHandleAvailability.d.ts +58 -0
- package/dist/lexicon/types/app/bsky/unspecced/checkHandleAvailability.d.ts.map +1 -0
- package/dist/lexicon/types/app/bsky/unspecced/checkHandleAvailability.js +34 -0
- package/dist/lexicon/types/app/bsky/unspecced/checkHandleAvailability.js.map +1 -0
- package/dist/lexicon/types/app/bsky/unspecced/initAgeAssurance.d.ts +1 -1
- package/dist/lexicon/types/app/bsky/unspecced/initAgeAssurance.d.ts.map +1 -1
- package/package.json +7 -6
- package/src/api/app/bsky/unspecced/checkHandleAvailability.ts +291 -0
- package/src/api/app/bsky/unspecced/initAgeAssurance.ts +32 -0
- package/src/api/index.ts +2 -0
- package/src/lexicon/index.ts +24 -11
- package/src/lexicon/lexicons.ts +146 -43
- package/src/lexicon/types/app/bsky/unspecced/checkHandleAvailability.ts +99 -0
- package/src/lexicon/types/app/bsky/unspecced/initAgeAssurance.ts +1 -1
- package/tests/views/age-assurance.test.ts +44 -0
- package/tests/views/handle-availability.test.ts +294 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.tests.tsbuildinfo +1 -1
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED CODE - DO NOT MODIFY
|
|
3
|
+
*/
|
|
4
|
+
import { type ValidationResult } from '@atproto/lexicon';
|
|
5
|
+
import { type $Typed } from '../../../../util';
|
|
6
|
+
export type QueryParams = {
|
|
7
|
+
/** Tentative handle. Will be checked for availability or used to build handle suggestions. */
|
|
8
|
+
handle: string;
|
|
9
|
+
/** User-provided email. Might be used to build handle suggestions. */
|
|
10
|
+
email?: string;
|
|
11
|
+
/** User-provided birth date. Might be used to build handle suggestions. */
|
|
12
|
+
birthDate?: string;
|
|
13
|
+
};
|
|
14
|
+
export type InputSchema = undefined;
|
|
15
|
+
export interface OutputSchema {
|
|
16
|
+
/** Echo of the input handle. */
|
|
17
|
+
handle: string;
|
|
18
|
+
result: $Typed<ResultAvailable> | $Typed<ResultUnavailable> | {
|
|
19
|
+
$type: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export type HandlerInput = void;
|
|
23
|
+
export interface HandlerSuccess {
|
|
24
|
+
encoding: 'application/json';
|
|
25
|
+
body: OutputSchema;
|
|
26
|
+
headers?: {
|
|
27
|
+
[key: string]: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export interface HandlerError {
|
|
31
|
+
status: number;
|
|
32
|
+
message?: string;
|
|
33
|
+
error?: 'InvalidEmail';
|
|
34
|
+
}
|
|
35
|
+
export type HandlerOutput = HandlerError | HandlerSuccess;
|
|
36
|
+
/** Indicates the provided handle is available. */
|
|
37
|
+
export interface ResultAvailable {
|
|
38
|
+
$type?: 'app.bsky.unspecced.checkHandleAvailability#resultAvailable';
|
|
39
|
+
}
|
|
40
|
+
export declare function isResultAvailable<V>(v: V): v is import("../../../../util").$TypedObject<V, "app.bsky.unspecced.checkHandleAvailability", "resultAvailable">;
|
|
41
|
+
export declare function validateResultAvailable<V>(v: V): ValidationResult<ResultAvailable & V>;
|
|
42
|
+
/** Indicates the provided handle is unavailable and gives suggestions of available handles. */
|
|
43
|
+
export interface ResultUnavailable {
|
|
44
|
+
$type?: 'app.bsky.unspecced.checkHandleAvailability#resultUnavailable';
|
|
45
|
+
/** List of suggested handles based on the provided inputs. */
|
|
46
|
+
suggestions: Suggestion[];
|
|
47
|
+
}
|
|
48
|
+
export declare function isResultUnavailable<V>(v: V): v is import("../../../../util").$TypedObject<V, "app.bsky.unspecced.checkHandleAvailability", "resultUnavailable">;
|
|
49
|
+
export declare function validateResultUnavailable<V>(v: V): ValidationResult<ResultUnavailable & V>;
|
|
50
|
+
export interface Suggestion {
|
|
51
|
+
$type?: 'app.bsky.unspecced.checkHandleAvailability#suggestion';
|
|
52
|
+
handle: string;
|
|
53
|
+
/** Method used to build this suggestion. Should be considered opaque to clients. Can be used for metrics. */
|
|
54
|
+
method: string;
|
|
55
|
+
}
|
|
56
|
+
export declare function isSuggestion<V>(v: V): v is import("../../../../util").$TypedObject<V, "app.bsky.unspecced.checkHandleAvailability", "suggestion">;
|
|
57
|
+
export declare function validateSuggestion<V>(v: V): ValidationResult<Suggestion & V>;
|
|
58
|
+
//# sourceMappingURL=checkHandleAvailability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkHandleAvailability.d.ts","sourceRoot":"","sources":["../../../../../../src/lexicon/types/app/bsky/unspecced/checkHandleAvailability.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,KAAK,gBAAgB,EAAW,MAAM,kBAAkB,CAAA;AAGjE,OAAO,EACL,KAAK,MAAM,EAGZ,MAAM,kBAAkB,CAAA;AAMzB,MAAM,MAAM,WAAW,GAAG;IACxB,8FAA8F;IAC9F,MAAM,EAAE,MAAM,CAAA;IACd,sEAAsE;IACtE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AACD,MAAM,MAAM,WAAW,GAAG,SAAS,CAAA;AAEnC,MAAM,WAAW,YAAY;IAC3B,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EACF,MAAM,CAAC,eAAe,CAAC,GACvB,MAAM,CAAC,iBAAiB,CAAC,GACzB;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;CACtB;AAED,MAAM,MAAM,YAAY,GAAG,IAAI,CAAA;AAE/B,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,kBAAkB,CAAA;IAC5B,IAAI,EAAE,YAAY,CAAA;IAClB,OAAO,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAA;CACpC;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,cAAc,CAAA;CACvB;AAED,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,cAAc,CAAA;AAEzD,kDAAkD;AAClD,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,4DAA4D,CAAA;CACrE;AAID,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,oHAExC;AAED,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,yCAE9C;AAED,+FAA+F;AAC/F,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,8DAA8D,CAAA;IACtE,8DAA8D;IAC9D,WAAW,EAAE,UAAU,EAAE,CAAA;CAC1B;AAID,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,sHAE1C;AAED,wBAAgB,yBAAyB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,2CAEhD;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,uDAAuD,CAAA;IAC/D,MAAM,EAAE,MAAM,CAAA;IACd,6GAA6G;IAC7G,MAAM,EAAE,MAAM,CAAA;CACf;AAID,wBAAgB,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,+GAEnC;AAED,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,oCAEzC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isResultAvailable = isResultAvailable;
|
|
4
|
+
exports.validateResultAvailable = validateResultAvailable;
|
|
5
|
+
exports.isResultUnavailable = isResultUnavailable;
|
|
6
|
+
exports.validateResultUnavailable = validateResultUnavailable;
|
|
7
|
+
exports.isSuggestion = isSuggestion;
|
|
8
|
+
exports.validateSuggestion = validateSuggestion;
|
|
9
|
+
const lexicons_1 = require("../../../../lexicons");
|
|
10
|
+
const util_1 = require("../../../../util");
|
|
11
|
+
const is$typed = util_1.is$typed, validate = lexicons_1.validate;
|
|
12
|
+
const id = 'app.bsky.unspecced.checkHandleAvailability';
|
|
13
|
+
const hashResultAvailable = 'resultAvailable';
|
|
14
|
+
function isResultAvailable(v) {
|
|
15
|
+
return is$typed(v, id, hashResultAvailable);
|
|
16
|
+
}
|
|
17
|
+
function validateResultAvailable(v) {
|
|
18
|
+
return validate(v, id, hashResultAvailable);
|
|
19
|
+
}
|
|
20
|
+
const hashResultUnavailable = 'resultUnavailable';
|
|
21
|
+
function isResultUnavailable(v) {
|
|
22
|
+
return is$typed(v, id, hashResultUnavailable);
|
|
23
|
+
}
|
|
24
|
+
function validateResultUnavailable(v) {
|
|
25
|
+
return validate(v, id, hashResultUnavailable);
|
|
26
|
+
}
|
|
27
|
+
const hashSuggestion = 'suggestion';
|
|
28
|
+
function isSuggestion(v) {
|
|
29
|
+
return is$typed(v, id, hashSuggestion);
|
|
30
|
+
}
|
|
31
|
+
function validateSuggestion(v) {
|
|
32
|
+
return validate(v, id, hashSuggestion);
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=checkHandleAvailability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkHandleAvailability.js","sourceRoot":"","sources":["../../../../../../src/lexicon/types/app/bsky/unspecced/checkHandleAvailability.ts"],"names":[],"mappings":";;AA0DA,8CAEC;AAED,0DAEC;AAWD,kDAEC;AAED,8DAEC;AAWD,oCAEC;AAED,gDAEC;AA7FD,mDAA4D;AAC5D,2CAIyB;AAEzB,MAAM,QAAQ,GAAG,eAAS,EACxB,QAAQ,GAAG,mBAAS,CAAA;AACtB,MAAM,EAAE,GAAG,4CAA4C,CAAA;AA0CvD,MAAM,mBAAmB,GAAG,iBAAiB,CAAA;AAE7C,SAAgB,iBAAiB,CAAI,CAAI;IACvC,OAAO,QAAQ,CAAC,CAAC,EAAE,EAAE,EAAE,mBAAmB,CAAC,CAAA;AAC7C,CAAC;AAED,SAAgB,uBAAuB,CAAI,CAAI;IAC7C,OAAO,QAAQ,CAAsB,CAAC,EAAE,EAAE,EAAE,mBAAmB,CAAC,CAAA;AAClE,CAAC;AASD,MAAM,qBAAqB,GAAG,mBAAmB,CAAA;AAEjD,SAAgB,mBAAmB,CAAI,CAAI;IACzC,OAAO,QAAQ,CAAC,CAAC,EAAE,EAAE,EAAE,qBAAqB,CAAC,CAAA;AAC/C,CAAC;AAED,SAAgB,yBAAyB,CAAI,CAAI;IAC/C,OAAO,QAAQ,CAAwB,CAAC,EAAE,EAAE,EAAE,qBAAqB,CAAC,CAAA;AACtE,CAAC;AASD,MAAM,cAAc,GAAG,YAAY,CAAA;AAEnC,SAAgB,YAAY,CAAI,CAAI;IAClC,OAAO,QAAQ,CAAC,CAAC,EAAE,EAAE,EAAE,cAAc,CAAC,CAAA;AACxC,CAAC;AAED,SAAgB,kBAAkB,CAAI,CAAI;IACxC,OAAO,QAAQ,CAAiB,CAAC,EAAE,EAAE,EAAE,cAAc,CAAC,CAAA;AACxD,CAAC"}
|
|
@@ -23,7 +23,7 @@ export interface HandlerSuccess {
|
|
|
23
23
|
export interface HandlerError {
|
|
24
24
|
status: number;
|
|
25
25
|
message?: string;
|
|
26
|
-
error?: 'InvalidEmail' | 'DidTooLong';
|
|
26
|
+
error?: 'InvalidEmail' | 'DidTooLong' | 'InvalidInitiation';
|
|
27
27
|
}
|
|
28
28
|
export type HandlerOutput = HandlerError | HandlerSuccess;
|
|
29
29
|
//# sourceMappingURL=initAgeAssurance.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"initAgeAssurance.d.ts","sourceRoot":"","sources":["../../../../../../src/lexicon/types/app/bsky/unspecced/initAgeAssurance.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,KAAK,oBAAoB,MAAM,WAAW,CAAA;AAMtD,MAAM,MAAM,WAAW,GAAG,EAAE,CAAA;AAE5B,MAAM,WAAW,WAAW;IAC1B,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAA;IACb,oFAAoF;IACpF,QAAQ,EAAE,MAAM,CAAA;IAChB,yDAAyD;IACzD,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,MAAM,YAAY,GAAG,oBAAoB,CAAC,iBAAiB,CAAA;AAEjE,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,kBAAkB,CAAA;IAC5B,IAAI,EAAE,WAAW,CAAA;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,kBAAkB,CAAA;IAC5B,IAAI,EAAE,YAAY,CAAA;IAClB,OAAO,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAA;CACpC;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,cAAc,GAAG,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"initAgeAssurance.d.ts","sourceRoot":"","sources":["../../../../../../src/lexicon/types/app/bsky/unspecced/initAgeAssurance.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,KAAK,oBAAoB,MAAM,WAAW,CAAA;AAMtD,MAAM,MAAM,WAAW,GAAG,EAAE,CAAA;AAE5B,MAAM,WAAW,WAAW;IAC1B,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAA;IACb,oFAAoF;IACpF,QAAQ,EAAE,MAAM,CAAA;IAChB,yDAAyD;IACzD,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,MAAM,YAAY,GAAG,oBAAoB,CAAC,iBAAiB,CAAA;AAEjE,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,kBAAkB,CAAA;IAC5B,IAAI,EAAE,WAAW,CAAA;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,kBAAkB,CAAA;IAC5B,IAAI,EAAE,YAAY,CAAA;IAClB,OAAO,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAA;CACpC;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,cAAc,GAAG,YAAY,GAAG,mBAAmB,CAAA;CAC5D;AAED,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,cAAc,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto/bsky",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.174",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Reference implementation of app.bsky App View (Bluesky API)",
|
|
6
6
|
"keywords": [
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"jose": "^5.0.1",
|
|
38
38
|
"key-encoder": "^2.0.3",
|
|
39
39
|
"kysely": "^0.22.0",
|
|
40
|
+
"leo-profanity": "^1.8.0",
|
|
40
41
|
"multiformats": "^9.9.0",
|
|
41
42
|
"murmurhash": "^2.0.1",
|
|
42
43
|
"p-queue": "^6.6.2",
|
|
@@ -53,15 +54,15 @@
|
|
|
53
54
|
"@atproto-labs/fetch-node": "0.1.9",
|
|
54
55
|
"@atproto-labs/xrpc-utils": "0.0.17",
|
|
55
56
|
"@atproto/common": "^0.4.11",
|
|
56
|
-
"@atproto/api": "^0.15.26",
|
|
57
57
|
"@atproto/crypto": "^0.4.4",
|
|
58
58
|
"@atproto/did": "^0.1.5",
|
|
59
59
|
"@atproto/identity": "^0.4.8",
|
|
60
60
|
"@atproto/lexicon": "^0.4.12",
|
|
61
|
-
"@atproto/repo": "^0.8.5",
|
|
62
61
|
"@atproto/sync": "^0.1.29",
|
|
63
62
|
"@atproto/syntax": "^0.4.0",
|
|
64
|
-
"@atproto/
|
|
63
|
+
"@atproto/repo": "^0.8.5",
|
|
64
|
+
"@atproto/xrpc-server": "^0.9.0",
|
|
65
|
+
"@atproto/api": "^0.15.27"
|
|
65
66
|
},
|
|
66
67
|
"devDependencies": {
|
|
67
68
|
"@bufbuild/buf": "^1.28.1",
|
|
@@ -76,9 +77,9 @@
|
|
|
76
77
|
"jest": "^28.1.2",
|
|
77
78
|
"ts-node": "^10.8.2",
|
|
78
79
|
"typescript": "^5.6.3",
|
|
79
|
-
"@atproto/api": "^0.15.
|
|
80
|
+
"@atproto/api": "^0.15.27",
|
|
80
81
|
"@atproto/lex-cli": "^0.9.0",
|
|
81
|
-
"@atproto/pds": "^0.4.
|
|
82
|
+
"@atproto/pds": "^0.4.162",
|
|
82
83
|
"@atproto/xrpc": "^0.7.1"
|
|
83
84
|
},
|
|
84
85
|
"scripts": {
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { isEmailValid } from '@hapi/address'
|
|
2
|
+
import filter from 'leo-profanity'
|
|
3
|
+
import * as ident from '@atproto/syntax'
|
|
4
|
+
import { InvalidRequestError } from '@atproto/xrpc-server'
|
|
5
|
+
import { AppContext } from '../../../../context'
|
|
6
|
+
import { Server } from '../../../../lexicon'
|
|
7
|
+
import {
|
|
8
|
+
QueryParams,
|
|
9
|
+
Suggestion,
|
|
10
|
+
} from '../../../../lexicon/types/app/bsky/unspecced/checkHandleAvailability'
|
|
11
|
+
|
|
12
|
+
// THIS IS A TEMPORARY UNSPECCED ROUTE
|
|
13
|
+
export default function (server: Server, ctx: AppContext) {
|
|
14
|
+
server.app.bsky.unspecced.checkHandleAvailability({
|
|
15
|
+
handler: async ({ params }) => {
|
|
16
|
+
const { birthDate, email, handle } = validateParams(params)
|
|
17
|
+
|
|
18
|
+
if (isSlur(handle)) {
|
|
19
|
+
return {
|
|
20
|
+
encoding: 'application/json',
|
|
21
|
+
body: {
|
|
22
|
+
handle,
|
|
23
|
+
result: {
|
|
24
|
+
$type:
|
|
25
|
+
'app.bsky.unspecced.checkHandleAvailability#resultUnavailable',
|
|
26
|
+
suggestions: [],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const [did] = await ctx.hydrator.actor.getDids([handle], {
|
|
33
|
+
lookupUnidirectional: true,
|
|
34
|
+
})
|
|
35
|
+
if (!did) {
|
|
36
|
+
return {
|
|
37
|
+
encoding: 'application/json',
|
|
38
|
+
body: {
|
|
39
|
+
handle,
|
|
40
|
+
result: {
|
|
41
|
+
$type:
|
|
42
|
+
'app.bsky.unspecced.checkHandleAvailability#resultAvailable',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const suggestions = await getSuggestions(ctx, handle, email, birthDate)
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
encoding: 'application/json',
|
|
52
|
+
body: {
|
|
53
|
+
handle,
|
|
54
|
+
result: {
|
|
55
|
+
$type:
|
|
56
|
+
'app.bsky.unspecced.checkHandleAvailability#resultUnavailable',
|
|
57
|
+
suggestions,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const validateParams = (params: QueryParams) => {
|
|
66
|
+
const { email } = params
|
|
67
|
+
if (email && !isEmailValid(email)) {
|
|
68
|
+
throw new InvalidRequestError('Invalid email address.', 'InvalidEmail')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
birthDate: params.birthDate,
|
|
73
|
+
email,
|
|
74
|
+
handle: ident.normalizeHandle(params.handle),
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const uniqueSuggestions = (suggestions: Suggestion[]): Suggestion[] =>
|
|
79
|
+
suggestions.filter((s0, i, ss) => {
|
|
80
|
+
return ss.findIndex((s1) => s0.handle === s1.handle) === i
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
/** Gets the target number of suggestions, ensuring uniqueness and availability. */
|
|
84
|
+
const getSuggestions = async (
|
|
85
|
+
ctx: AppContext,
|
|
86
|
+
tentativeHandle: string,
|
|
87
|
+
email: string | undefined,
|
|
88
|
+
birthDate: string | undefined,
|
|
89
|
+
): Promise<Suggestion[]> => {
|
|
90
|
+
const [subdomain, ...rest] = tentativeHandle.split('.')
|
|
91
|
+
const domain = rest.join('.')
|
|
92
|
+
|
|
93
|
+
let suggestions: Suggestion[] = []
|
|
94
|
+
|
|
95
|
+
const want = 5
|
|
96
|
+
let attempt = 0
|
|
97
|
+
|
|
98
|
+
const deterministic = await availableSuggestions(
|
|
99
|
+
ctx,
|
|
100
|
+
deterministicSuggestions(subdomain, email, birthDate),
|
|
101
|
+
tentativeHandle,
|
|
102
|
+
domain,
|
|
103
|
+
)
|
|
104
|
+
suggestions.push(...deterministic)
|
|
105
|
+
|
|
106
|
+
while (suggestions.length < want && attempt < 3) {
|
|
107
|
+
const random = await availableSuggestions(
|
|
108
|
+
ctx,
|
|
109
|
+
randomSuggestions(subdomain),
|
|
110
|
+
tentativeHandle,
|
|
111
|
+
domain,
|
|
112
|
+
)
|
|
113
|
+
suggestions.push(...random)
|
|
114
|
+
|
|
115
|
+
suggestions = uniqueSuggestions([...suggestions, ...random])
|
|
116
|
+
attempt++
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return suggestions.slice(0, want)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
type IntermediateSuggestion = {
|
|
123
|
+
subdomain: string
|
|
124
|
+
method: string
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const availableSuggestions = async (
|
|
128
|
+
ctx: AppContext,
|
|
129
|
+
suggestions: IntermediateSuggestion[],
|
|
130
|
+
tentativeHandle: string,
|
|
131
|
+
domain: string,
|
|
132
|
+
): Promise<Suggestion[]> => {
|
|
133
|
+
const join = (subdomain: string, domain: string) => `${subdomain}.${domain}`
|
|
134
|
+
|
|
135
|
+
const validSuggestions = suggestions
|
|
136
|
+
.filter((s) => {
|
|
137
|
+
// @TODO: from a magic number in the PDS code.
|
|
138
|
+
if (s.subdomain.length < 3) return false
|
|
139
|
+
|
|
140
|
+
// @TODO: from a magic number in the PDS code.
|
|
141
|
+
if (s.subdomain.length > 18) return false
|
|
142
|
+
|
|
143
|
+
const handle = join(s.subdomain, domain)
|
|
144
|
+
// @TODO: from a magic number in the entryway code.
|
|
145
|
+
if (handle.length > 30) return false
|
|
146
|
+
|
|
147
|
+
// Only valid, and not the tentative one.
|
|
148
|
+
return ident.isValidHandle(handle) && handle !== tentativeHandle
|
|
149
|
+
})
|
|
150
|
+
.map(
|
|
151
|
+
(s): Suggestion => ({
|
|
152
|
+
handle: join(s.subdomain, domain),
|
|
153
|
+
method: s.method,
|
|
154
|
+
}),
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
const dids = await ctx.hydrator.actor.getDids(
|
|
158
|
+
validSuggestions.map((s) => s.handle),
|
|
159
|
+
{
|
|
160
|
+
lookupUnidirectional: true,
|
|
161
|
+
},
|
|
162
|
+
)
|
|
163
|
+
return validSuggestions.filter((_, i) => !dids[i])
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const deterministicSuggestions = (
|
|
167
|
+
subdomain: string,
|
|
168
|
+
email: string | undefined,
|
|
169
|
+
birthDate: string | undefined,
|
|
170
|
+
): IntermediateSuggestion[] => {
|
|
171
|
+
const localPart = email
|
|
172
|
+
?.split('@')[0]
|
|
173
|
+
.toLowerCase()
|
|
174
|
+
.replace('.', '-')
|
|
175
|
+
.replace(/[^a-zA-Z0-9-]/g, '')
|
|
176
|
+
const year = getYear(birthDate)
|
|
177
|
+
|
|
178
|
+
return [
|
|
179
|
+
...suggestAppendDigits('handle_yob', subdomain, year),
|
|
180
|
+
...suggestValue('email', localPart),
|
|
181
|
+
...suggestAppendDigits('email_yob', localPart, year),
|
|
182
|
+
]
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const randomSuggestions = (subdomain: string): IntermediateSuggestion[] => [
|
|
186
|
+
...suggestHyphens('hyphen', subdomain),
|
|
187
|
+
...suggestAppendRandomDigits('random_digits', subdomain),
|
|
188
|
+
]
|
|
189
|
+
|
|
190
|
+
const avoidDigits = ['69']
|
|
191
|
+
|
|
192
|
+
const getYear = (d: string | undefined): string | undefined => {
|
|
193
|
+
if (!d) return undefined
|
|
194
|
+
|
|
195
|
+
const date = new Date(d)
|
|
196
|
+
if (isNaN(date.getTime())) return undefined
|
|
197
|
+
|
|
198
|
+
const year = date.getFullYear().toString().slice(-2)
|
|
199
|
+
return avoidDigits.includes(year) ? undefined : year
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const suggestValue = (
|
|
203
|
+
method: string,
|
|
204
|
+
s: string | undefined,
|
|
205
|
+
): IntermediateSuggestion[] => {
|
|
206
|
+
if (!s) return []
|
|
207
|
+
return [{ subdomain: `${s}`, method }]
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const suggestAppendDigits = (
|
|
211
|
+
method: string,
|
|
212
|
+
s: string | undefined,
|
|
213
|
+
d: string | undefined,
|
|
214
|
+
): IntermediateSuggestion[] => {
|
|
215
|
+
if (!s || !d) return []
|
|
216
|
+
|
|
217
|
+
// If s already ends in digits, add an hyphen before appending the number.
|
|
218
|
+
const separator = /\d$/.test(s) ? '-' : ''
|
|
219
|
+
return [{ subdomain: `${s}${separator}${d}`, method }]
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const suggestAppendRandomDigits = (
|
|
223
|
+
method: string,
|
|
224
|
+
s: string,
|
|
225
|
+
): IntermediateSuggestion[] => {
|
|
226
|
+
const ss: IntermediateSuggestion[] = []
|
|
227
|
+
const want = 2
|
|
228
|
+
let got = 0
|
|
229
|
+
while (got < want) {
|
|
230
|
+
const randomDigits = Math.floor(Math.random() * 100).toString()
|
|
231
|
+
if (avoidDigits.includes(randomDigits)) continue
|
|
232
|
+
ss.push(...suggestAppendDigits(method, s, randomDigits))
|
|
233
|
+
got++
|
|
234
|
+
}
|
|
235
|
+
return ss
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const suggestHyphens = (
|
|
239
|
+
method: string,
|
|
240
|
+
s: string,
|
|
241
|
+
): IntermediateSuggestion[] => {
|
|
242
|
+
const ss: IntermediateSuggestion[] = []
|
|
243
|
+
// 2 suggestions or less, if the string is too short.
|
|
244
|
+
const want = Math.min(Math.floor(s.length / 2), 2)
|
|
245
|
+
let got = 0
|
|
246
|
+
|
|
247
|
+
while (got < want) {
|
|
248
|
+
// Exclude first and last character to avoid leading/trailing hyphens.
|
|
249
|
+
for (let i = 1; i < s.length && got < want; i++) {
|
|
250
|
+
// Randomly skip some combinations.
|
|
251
|
+
if (Math.random() > 0.5) continue
|
|
252
|
+
|
|
253
|
+
const left = s.slice(0, i)
|
|
254
|
+
const right = s.slice(i)
|
|
255
|
+
if (isSlur(left) || isSlur(right)) {
|
|
256
|
+
// Skip but count to avoid infinite loop.
|
|
257
|
+
got++
|
|
258
|
+
continue
|
|
259
|
+
}
|
|
260
|
+
ss.push({ subdomain: `${left}-${right}`, method })
|
|
261
|
+
got++
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return ss
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// regexes taken from: https://github.com/Blank-Cheque/Slurs
|
|
269
|
+
/* eslint-disable no-misleading-character-class */
|
|
270
|
+
const explicitSlurRegexes = [
|
|
271
|
+
/\b[cĆćĈĉČčĊċÇçḈḉȻȼꞒꞓꟄꞔƇƈɕ][hĤĥȞȟḦḧḢḣḨḩḤḥḪḫH̱ẖĦħⱧⱨꞪɦꞕΗНн][iÍíi̇́Ììi̇̀ĬĭÎîǏǐÏïḮḯĨĩi̇̃ĮįĮ́į̇́Į̃į̇̃ĪīĪ̀ī̀ỈỉȈȉI̋i̋ȊȋỊịꞼꞽḬḭƗɨᶖİiIıIi1lĺľļḷḹl̃ḽḻłŀƚꝉⱡɫɬꞎꬷꬸꬹᶅɭȴLl][nŃńǸǹŇňÑñṄṅŅņṆṇṊṋṈṉN̈n̈ƝɲŊŋꞐꞑꞤꞥᵰᶇɳȵꬻꬼИиПпNn][kḰḱǨǩĶķḲḳḴḵƘƙⱩⱪᶄꝀꝁꝂꝃꝄꝅꞢꞣ][sŚśṤṥŜŝŠšṦṧṠṡŞşṢṣṨṩȘșS̩s̩ꞨꞩⱾȿꟅʂᶊᵴ]?\b/,
|
|
272
|
+
/\b[cĆćĈĉČčĊċÇçḈḉȻȼꞒꞓꟄꞔƇƈɕ][ÓóÒòŎŏÔôỐốỒồỖỗỔổǑǒÖöȪȫŐőÕõṌṍṎṏȬȭȮȯO͘o͘ȰȱØøǾǿǪǫǬǭŌōṒṓṐṑỎỏȌȍȎȏƠơỚớỜờỠỡỞởỢợỌọỘộO̩o̩Ò̩ò̩Ó̩ó̩ƟɵꝊꝋꝌꝍⱺOo0]{2}[nŃńǸǹŇňÑñṄṅŅņṆṇṊṋṈṉN̈n̈ƝɲŊŋꞐꞑꞤꞥᵰᶇɳȵꬻꬼИиПпNn][sŚśṤṥŜŝŠšṦṧṠṡŞşṢṣṨṩȘșS̩s̩ꞨꞩⱾȿꟅʂᶊᵴ]?\b/,
|
|
273
|
+
/\b[fḞḟƑƒꞘꞙᵮᶂ][aÁáÀàĂăẮắẰằẴẵẲẳÂâẤấẦầẪẫẨẩǍǎÅåǺǻÄäǞǟÃãȦȧǠǡĄąĄ́ą́Ą̃ą̃ĀāĀ̀ā̀ẢảȀȁA̋a̋ȂȃẠạẶặẬậḀḁȺⱥꞺꞻᶏẚAa@4][gǴǵĞğĜĝǦǧĠġG̃g̃ĢģḠḡǤǥꞠꞡƓɠᶃꬶGgqꝖꝗꝘꝙɋʠ]{1,2}([ÓóÒòŎŏÔôỐốỒồỖỗỔổǑǒÖöȪȫŐőÕõṌṍṎṏȬȭȮȯO͘o͘ȰȱØøǾǿǪǫǬǭŌōṒṓṐṑỎỏȌȍȎȏƠơỚớỜờỠỡỞởỢợỌọỘộO̩o̩Ò̩ò̩Ó̩ó̩ƟɵꝊꝋꝌꝍⱺOo0e3ЄєЕеÉéÈèĔĕÊêẾếỀềỄễỂểÊ̄ê̄Ê̌ê̌ĚěËëẼẽĖėĖ́ė́Ė̃ė̃ȨȩḜḝĘęĘ́ę́Ę̃ę̃ĒēḖḗḔḕẺẻȄȅE̋e̋ȆȇẸẹỆệḘḙḚḛɆɇE̩e̩È̩è̩É̩é̩ᶒⱸꬴꬳEeiÍíi̇́Ììi̇̀ĬĭÎîǏǐÏïḮḯĨĩi̇̃ĮįĮ́į̇́Į̃į̇̃ĪīĪ̀ī̀ỈỉȈȉI̋i̋ȊȋỊịꞼꞽḬḭƗɨᶖİiIıIi1lĺľļḷḹl̃ḽḻłŀƚꝉⱡɫɬꞎꬷꬸꬹᶅɭȴLl][tŤťṪṫŢţṬṭȚțṰṱṮṯŦŧȾⱦƬƭƮʈT̈ẗᵵƫȶ]{1,2}([rŔŕŘřṘṙŖŗȐȑȒȓṚṛṜṝṞṟR̃r̃ɌɍꞦꞧⱤɽᵲᶉꭉ][yÝýỲỳŶŷY̊ẙŸÿỸỹẎẏȲȳỶỷỴỵɎɏƳƴỾỿ]|[rŔŕŘřṘṙŖŗȐȑȒȓṚṛṜṝṞṟR̃r̃ɌɍꞦꞧⱤɽᵲᶉꭉ][iÍíi̇́Ììi̇̀ĬĭÎîǏǐÏïḮḯĨĩi̇̃ĮįĮ́į̇́Į̃į̇̃ĪīĪ̀ī̀ỈỉȈȉI̋i̋ȊȋỊịꞼꞽḬḭƗɨᶖİiIıIi1lĺľļḷḹl̃ḽḻłŀƚꝉⱡɫɬꞎꬷꬸꬹᶅɭȴLl][e3ЄєЕеÉéÈèĔĕÊêẾếỀềỄễỂểÊ̄ê̄Ê̌ê̌ĚěËëẼẽĖėĖ́ė́Ė̃ė̃ȨȩḜḝĘęĘ́ę́Ę̃ę̃ĒēḖḗḔḕẺẻȄȅE̋e̋ȆȇẸẹỆệḘḙḚḛɆɇE̩e̩È̩è̩É̩é̩ᶒⱸꬴꬳEe])?)?[sŚśṤṥŜŝŠšṦṧṠṡŞşṢṣṨṩȘșS̩s̩ꞨꞩⱾȿꟅʂᶊᵴ]?\b/,
|
|
274
|
+
/\b[kḰḱǨǩĶķḲḳḴḵƘƙⱩⱪᶄꝀꝁꝂꝃꝄꝅꞢꞣ][iÍíi̇́Ììi̇̀ĬĭÎîǏǐÏïḮḯĨĩi̇̃ĮįĮ́į̇́Į̃į̇̃ĪīĪ̀ī̀ỈỉȈȉI̋i̋ȊȋỊịꞼꞽḬḭƗɨᶖİiIıIi1lĺľļḷḹl̃ḽḻłŀƚꝉⱡɫɬꞎꬷꬸꬹᶅɭȴLlyÝýỲỳŶŷY̊ẙŸÿỸỹẎẏȲȳỶỷỴỵɎɏƳƴỾỿ][kḰḱǨǩĶķḲḳḴḵƘƙⱩⱪᶄꝀꝁꝂꝃꝄꝅꞢꞣ][e3ЄєЕеÉéÈèĔĕÊêẾếỀềỄễỂểÊ̄ê̄Ê̌ê̌ĚěËëẼẽĖėĖ́ė́Ė̃ė̃ȨȩḜḝĘęĘ́ę́Ę̃ę̃ĒēḖḗḔḕẺẻȄȅE̋e̋ȆȇẸẹỆệḘḙḚḛɆɇE̩e̩È̩è̩É̩é̩ᶒⱸꬴꬳEe]([rŔŕŘřṘṙŖŗȐȑȒȓṚṛṜṝṞṟR̃r̃ɌɍꞦꞧⱤɽᵲᶉꭉ][yÝýỲỳŶŷY̊ẙŸÿỸỹẎẏȲȳỶỷỴỵɎɏƳƴỾỿ]|[rŔŕŘřṘṙŖŗȐȑȒȓṚṛṜṝṞṟR̃r̃ɌɍꞦꞧⱤɽᵲᶉꭉ][iÍíi̇́Ììi̇̀ĬĭÎîǏǐÏïḮḯĨĩi̇̃ĮįĮ́į̇́Į̃į̇̃ĪīĪ̀ī̀ỈỉȈȉI̋i̋ȊȋỊịꞼꞽḬḭƗɨᶖİiIıIi1lĺľļḷḹl̃ḽḻłŀƚꝉⱡɫɬꞎꬷꬸꬹᶅɭȴLl][e3ЄєЕеÉéÈèĔĕÊêẾếỀềỄễỂểÊ̄ê̄Ê̌ê̌ĚěËëẼẽĖėĖ́ė́Ė̃ė̃ȨȩḜḝĘęĘ́ę́Ę̃ę̃ĒēḖḗḔḕẺẻȄȅE̋e̋ȆȇẸẹỆệḘḙḚḛɆɇE̩e̩È̩è̩É̩é̩ᶒⱸꬴꬳEe])?[sŚśṤṥŜŝŠšṦṧṠṡŞşṢṣṨṩȘșS̩s̩ꞨꞩⱾȿꟅʂᶊᵴ]*\b/,
|
|
275
|
+
/\b[nŃńǸǹŇňÑñṄṅŅņṆṇṊṋṈṉN̈n̈ƝɲŊŋꞐꞑꞤꞥᵰᶇɳȵꬻꬼИиПпNn][iÍíi̇́Ììi̇̀ĬĭÎîǏǐÏïḮḯĨĩi̇̃ĮįĮ́į̇́Į̃į̇̃ĪīĪ̀ī̀ỈỉȈȉI̋i̋ȊȋỊịꞼꞽḬḭƗɨᶖİiIıIi1lĺľļḷḹl̃ḽḻłŀƚꝉⱡɫɬꞎꬷꬸꬹᶅɭȴLloÓóÒòŎŏÔôỐốỒồỖỗỔổǑǒÖöȪȫŐőÕõṌṍṎṏȬȭȮȯO͘o͘ȰȱØøǾǿǪǫǬǭŌōṒṓṐṑỎỏȌȍȎȏƠơỚớỜờỠỡỞởỢợỌọỘộO̩o̩Ò̩ò̩Ó̩ó̩ƟɵꝊꝋꝌꝍⱺOoІіa4ÁáÀàĂăẮắẰằẴẵẲẳÂâẤấẦầẪẫẨẩǍǎÅåǺǻÄäǞǟÃãȦȧǠǡĄąĄ́ą́Ą̃ą̃ĀāĀ̀ā̀ẢảȀȁA̋a̋ȂȃẠạẶặẬậḀḁȺⱥꞺꞻᶏẚAa][gǴǵĞğĜĝǦǧĠġG̃g̃ĢģḠḡǤǥꞠꞡƓɠᶃꬶGgqꝖꝗꝘꝙɋʠ]{2}(l[e3ЄєЕеÉéÈèĔĕÊêẾếỀềỄễỂểÊ̄ê̄Ê̌ê̌ĚěËëẼẽĖėĖ́ė́Ė̃ė̃ȨȩḜḝĘęĘ́ę́Ę̃ę̃ĒēḖḗḔḕẺẻȄȅE̋e̋ȆȇẸẹỆệḘḙḚḛɆɇE̩e̩È̩è̩É̩é̩ᶒⱸꬴꬳEe]t|[e3ЄєЕеÉéÈèĔĕÊêẾếỀềỄễỂểÊ̄ê̄Ê̌ê̌ĚěËëẼẽĖėĖ́ė́Ė̃ė̃ȨȩḜḝĘęĘ́ę́Ę̃ę̃ĒēḖḗḔḕẺẻȄȅE̋e̋ȆȇẸẹỆệḘḙḚḛɆɇE̩e̩È̩è̩É̩é̩ᶒⱸꬴꬳEeaÁáÀàĂăẮắẰằẴẵẲẳÂâẤấẦầẪẫẨẩǍǎÅåǺǻÄäǞǟÃãȦȧǠǡĄąĄ́ą́Ą̃ą̃ĀāĀ̀ā̀ẢảȀȁA̋a̋ȂȃẠạẶặẬậḀḁȺⱥꞺꞻᶏẚAa][rŔŕŘřṘṙŖŗȐȑȒȓṚṛṜṝṞṟR̃r̃ɌɍꞦꞧⱤɽᵲᶉꭉ]?|n[ÓóÒòŎŏÔôỐốỒồỖỗỔổǑǒÖöȪȫŐőÕõṌṍṎṏȬȭȮȯO͘o͘ȰȱØøǾǿǪǫǬǭŌōṒṓṐṑỎỏȌȍȎȏƠơỚớỜờỠỡỞởỢợỌọỘộO̩o̩Ò̩ò̩Ó̩ó̩ƟɵꝊꝋꝌꝍⱺOo0][gǴǵĞğĜĝǦǧĠġG̃g̃ĢģḠḡǤǥꞠꞡƓɠᶃꬶGgqꝖꝗꝘꝙɋʠ]|[a4ÁáÀàĂăẮắẰằẴẵẲẳÂâẤấẦầẪẫẨẩǍǎÅåǺǻÄäǞǟÃãȦȧǠǡĄąĄ́ą́Ą̃ą̃ĀāĀ̀ā̀ẢảȀȁA̋a̋ȂȃẠạẶặẬậḀḁȺⱥꞺꞻᶏẚAa]?)?[sŚśṤṥŜŝŠšṦṧṠṡŞşṢṣṨṩȘșS̩s̩ꞨꞩⱾȿꟅʂᶊᵴ]?\b/,
|
|
276
|
+
/[nŃńǸǹŇňÑñṄṅŅņṆṇṊṋṈṉN̈n̈ƝɲŊŋꞐꞑꞤꞥᵰᶇɳȵꬻꬼИиПпNn][iÍíi̇́Ììi̇̀ĬĭÎîǏǐÏïḮḯĨĩi̇̃ĮįĮ́į̇́Į̃į̇̃ĪīĪ̀ī̀ỈỉȈȉI̋i̋ȊȋỊịꞼꞽḬḭƗɨᶖİiIıIi1lĺľļḷḹl̃ḽḻłŀƚꝉⱡɫɬꞎꬷꬸꬹᶅɭȴLloÓóÒòŎŏÔôỐốỒồỖỗỔổǑǒÖöȪȫŐőÕõṌṍṎṏȬȭȮȯO͘o͘ȰȱØøǾǿǪǫǬǭŌōṒṓṐṑỎỏȌȍȎȏƠơỚớỜờỠỡỞởỢợỌọỘộO̩o̩Ò̩ò̩Ó̩ó̩ƟɵꝊꝋꝌꝍⱺOoІіa4ÁáÀàĂăẮắẰằẴẵẲẳÂâẤấẦầẪẫẨẩǍǎÅåǺǻÄäǞǟÃãȦȧǠǡĄąĄ́ą́Ą̃ą̃ĀāĀ̀ā̀ẢảȀȁA̋a̋ȂȃẠạẶặẬậḀḁȺⱥꞺꞻᶏẚAa][gǴǵĞğĜĝǦǧĠġG̃g̃ĢģḠḡǤǥꞠꞡƓɠᶃꬶGgqꝖꝗꝘꝙɋʠ]{2}(l[e3ЄєЕеÉéÈèĔĕÊêẾếỀềỄễỂểÊ̄ê̄Ê̌ê̌ĚěËëẼẽĖėĖ́ė́Ė̃ė̃ȨȩḜḝĘęĘ́ę́Ę̃ę̃ĒēḖḗḔḕẺẻȄȅE̋e̋ȆȇẸẹỆệḘḙḚḛɆɇE̩e̩È̩è̩É̩é̩ᶒⱸꬴꬳEe]t|[e3ЄєЕеÉéÈèĔĕÊêẾếỀềỄễỂểÊ̄ê̄Ê̌ê̌ĚěËëẼẽĖėĖ́ė́Ė̃ė̃ȨȩḜḝĘęĘ́ę́Ę̃ę̃ĒēḖḗḔḕẺẻȄȅE̋e̋ȆȇẸẹỆệḘḙḚḛɆɇE̩e̩È̩è̩É̩é̩ᶒⱸꬴꬳEe][rŔŕŘřṘṙŖŗȐȑȒȓṚṛṜṝṞṟR̃r̃ɌɍꞦꞧⱤɽᵲᶉꭉ])[sŚśṤṥŜŝŠšṦṧṠṡŞşṢṣṨṩȘșS̩s̩ꞨꞩⱾȿꟅʂᶊᵴ]?/,
|
|
277
|
+
/\b[tŤťṪṫŢţṬṭȚțṰṱṮṯŦŧȾⱦƬƭƮʈT̈ẗᵵƫȶ][rŔŕŘřṘṙŖŗȐȑȒȓṚṛṜṝṞṟR̃r̃ɌɍꞦꞧⱤɽᵲᶉꭉ][aÁáÀàĂăẮắẰằẴẵẲẳÂâẤấẦầẪẫẨẩǍǎÅåǺǻÄäǞǟÃãȦȧǠǡĄąĄ́ą́Ą̃ą̃ĀāĀ̀ā̀ẢảȀȁA̋a̋ȂȃẠạẶặẬậḀḁȺⱥꞺꞻᶏẚAa4]+[nŃńǸǹŇňÑñṄṅŅņṆṇṊṋṈṉN̈n̈ƝɲŊŋꞐꞑꞤꞥᵰᶇɳȵꬻꬼИиПпNn]{1,2}([iÍíi̇́Ììi̇̀ĬĭÎîǏǐÏïḮḯĨĩi̇̃ĮįĮ́į̇́Į̃į̇̃ĪīĪ̀ī̀ỈỉȈȉI̋i̋ȊȋỊịꞼꞽḬḭƗɨᶖİiIıIi1lĺľļḷḹl̃ḽḻłŀƚꝉⱡɫɬꞎꬷꬸꬹᶅɭȴLl][e3ЄєЕеÉéÈèĔĕÊêẾếỀềỄễỂểÊ̄ê̄Ê̌ê̌ĚěËëẼẽĖėĖ́ė́Ė̃ė̃ȨȩḜḝĘęĘ́ę́Ę̃ę̃ĒēḖḗḔḕẺẻȄȅE̋e̋ȆȇẸẹỆệḘḙḚḛɆɇE̩e̩È̩è̩É̩é̩ᶒⱸꬴꬳEe]|[yÝýỲỳŶŷY̊ẙŸÿỸỹẎẏȲȳỶỷỴỵɎɏƳƴỾỿ]|[e3ЄєЕеÉéÈèĔĕÊêẾếỀềỄễỂểÊ̄ê̄Ê̌ê̌ĚěËëẼẽĖėĖ́ė́Ė̃ė̃ȨȩḜḝĘęĘ́ę́Ę̃ę̃ĒēḖḗḔḕẺẻȄȅE̋e̋ȆȇẸẹỆệḘḙḚḛɆɇE̩e̩È̩è̩É̩é̩ᶒⱸꬴꬳEe][rŔŕŘřṘṙŖŗȐȑȒȓṚṛṜṝṞṟR̃r̃ɌɍꞦꞧⱤɽᵲᶉꭉ])[sŚśṤṥŜŝŠšṦṧṠṡŞşṢṣṨṩȘșS̩s̩ꞨꞩⱾȿꟅʂᶊᵴ]?\b/,
|
|
278
|
+
]
|
|
279
|
+
|
|
280
|
+
const isSlur = (handle: string): boolean => {
|
|
281
|
+
return (
|
|
282
|
+
filter.check(handle) ||
|
|
283
|
+
explicitSlurRegexes.some(
|
|
284
|
+
(reg) =>
|
|
285
|
+
reg.test(handle) ||
|
|
286
|
+
reg.test(
|
|
287
|
+
handle.replaceAll('.', '').replaceAll('-', '').replaceAll('_', ''),
|
|
288
|
+
),
|
|
289
|
+
)
|
|
290
|
+
)
|
|
291
|
+
}
|
|
@@ -12,6 +12,7 @@ import { KwsExternalPayloadError } from '../../../../kws'
|
|
|
12
12
|
import { Server } from '../../../../lexicon'
|
|
13
13
|
import { InputSchema } from '../../../../lexicon/types/app/bsky/unspecced/initAgeAssurance'
|
|
14
14
|
import { httpLogger as log } from '../../../../logger'
|
|
15
|
+
import { ActorInfo } from '../../../../proto/bsky_pb'
|
|
15
16
|
import { KwsExternalPayload } from '../../../kws/types'
|
|
16
17
|
import { createStashEvent, getClientUa } from '../../../kws/util'
|
|
17
18
|
|
|
@@ -33,6 +34,20 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
33
34
|
throw new ForbiddenError()
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
const actorInfo = await getAgeVerificationState(ctx, actorDid)
|
|
38
|
+
|
|
39
|
+
if (actorInfo?.ageAssuranceStatus) {
|
|
40
|
+
if (
|
|
41
|
+
actorInfo.ageAssuranceStatus.status !== 'unknown' &&
|
|
42
|
+
actorInfo.ageAssuranceStatus.status !== 'pending'
|
|
43
|
+
) {
|
|
44
|
+
throw new InvalidRequestError(
|
|
45
|
+
`Cannot initiate age assurance flow from current state: ${actorInfo.ageAssuranceStatus.status}`,
|
|
46
|
+
'InvalidInitiation',
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
36
51
|
const { countryCode, email, language } = validateInput(input.body)
|
|
37
52
|
|
|
38
53
|
const attemptId = crypto.randomUUID()
|
|
@@ -122,3 +137,20 @@ const validateInput = (input: InputSchema): InputSchema => {
|
|
|
122
137
|
language: kwsAvSupportedLanguages.includes(language) ? language : 'en',
|
|
123
138
|
}
|
|
124
139
|
}
|
|
140
|
+
|
|
141
|
+
const getAgeVerificationState = async (
|
|
142
|
+
ctx: AppContext,
|
|
143
|
+
actorDid: string,
|
|
144
|
+
): Promise<ActorInfo | undefined> => {
|
|
145
|
+
try {
|
|
146
|
+
const res = await ctx.dataplane.getActors({
|
|
147
|
+
dids: [actorDid],
|
|
148
|
+
returnAgeAssuranceForDids: [actorDid],
|
|
149
|
+
skipCacheForDids: [actorDid],
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
return res.actors[0]
|
|
153
|
+
} catch (err) {
|
|
154
|
+
return undefined
|
|
155
|
+
}
|
|
156
|
+
}
|
package/src/api/index.ts
CHANGED
|
@@ -51,6 +51,7 @@ import putPreferences from './app/bsky/notification/putPreferences'
|
|
|
51
51
|
import putPreferencesV2 from './app/bsky/notification/putPreferencesV2'
|
|
52
52
|
import registerPush from './app/bsky/notification/registerPush'
|
|
53
53
|
import updateSeen from './app/bsky/notification/updateSeen'
|
|
54
|
+
import checkHandleAvailability from './app/bsky/unspecced/checkHandleAvailability'
|
|
54
55
|
import getAgeAssuranceState from './app/bsky/unspecced/getAgeAssuranceState'
|
|
55
56
|
import getConfig from './app/bsky/unspecced/getConfig'
|
|
56
57
|
import getPopularFeedGenerators from './app/bsky/unspecced/getPopularFeedGenerators'
|
|
@@ -142,6 +143,7 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
142
143
|
getConfig(server, ctx)
|
|
143
144
|
getPopularFeedGenerators(server, ctx)
|
|
144
145
|
getTaggedSuggestions(server, ctx)
|
|
146
|
+
checkHandleAvailability(server, ctx)
|
|
145
147
|
getAgeAssuranceState(server, ctx)
|
|
146
148
|
initAgeAssurance(server, ctx)
|
|
147
149
|
// com.atproto
|
package/src/lexicon/index.ts
CHANGED
|
@@ -109,8 +109,8 @@ import * as AppBskyFeedGetFeedGenerators from './types/app/bsky/feed/getFeedGene
|
|
|
109
109
|
import * as AppBskyFeedGetFeedSkeleton from './types/app/bsky/feed/getFeedSkeleton.js'
|
|
110
110
|
import * as AppBskyFeedGetLikes from './types/app/bsky/feed/getLikes.js'
|
|
111
111
|
import * as AppBskyFeedGetListFeed from './types/app/bsky/feed/getListFeed.js'
|
|
112
|
-
import * as AppBskyFeedGetPostThread from './types/app/bsky/feed/getPostThread.js'
|
|
113
112
|
import * as AppBskyFeedGetPosts from './types/app/bsky/feed/getPosts.js'
|
|
113
|
+
import * as AppBskyFeedGetPostThread from './types/app/bsky/feed/getPostThread.js'
|
|
114
114
|
import * as AppBskyFeedGetQuotes from './types/app/bsky/feed/getQuotes.js'
|
|
115
115
|
import * as AppBskyFeedGetRepostedBy from './types/app/bsky/feed/getRepostedBy.js'
|
|
116
116
|
import * as AppBskyFeedGetSuggestedFeeds from './types/app/bsky/feed/getSuggestedFeeds.js'
|
|
@@ -149,6 +149,7 @@ import * as AppBskyNotificationPutPreferencesV2 from './types/app/bsky/notificat
|
|
|
149
149
|
import * as AppBskyNotificationRegisterPush from './types/app/bsky/notification/registerPush.js'
|
|
150
150
|
import * as AppBskyNotificationUnregisterPush from './types/app/bsky/notification/unregisterPush.js'
|
|
151
151
|
import * as AppBskyNotificationUpdateSeen from './types/app/bsky/notification/updateSeen.js'
|
|
152
|
+
import * as AppBskyUnspeccedCheckHandleAvailability from './types/app/bsky/unspecced/checkHandleAvailability.js'
|
|
152
153
|
import * as AppBskyUnspeccedGetAgeAssuranceState from './types/app/bsky/unspecced/getAgeAssuranceState.js'
|
|
153
154
|
import * as AppBskyUnspeccedGetConfig from './types/app/bsky/unspecced/getConfig.js'
|
|
154
155
|
import * as AppBskyUnspeccedGetPopularFeedGenerators from './types/app/bsky/unspecced/getPopularFeedGenerators.js'
|
|
@@ -1600,27 +1601,27 @@ export class AppBskyFeedNS {
|
|
|
1600
1601
|
return this._server.xrpc.method(nsid, cfg)
|
|
1601
1602
|
}
|
|
1602
1603
|
|
|
1603
|
-
|
|
1604
|
+
getPosts<A extends Auth = void>(
|
|
1604
1605
|
cfg: MethodConfigOrHandler<
|
|
1605
1606
|
A,
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1607
|
+
AppBskyFeedGetPosts.QueryParams,
|
|
1608
|
+
AppBskyFeedGetPosts.HandlerInput,
|
|
1609
|
+
AppBskyFeedGetPosts.HandlerOutput
|
|
1609
1610
|
>,
|
|
1610
1611
|
) {
|
|
1611
|
-
const nsid = 'app.bsky.feed.
|
|
1612
|
+
const nsid = 'app.bsky.feed.getPosts' // @ts-ignore
|
|
1612
1613
|
return this._server.xrpc.method(nsid, cfg)
|
|
1613
1614
|
}
|
|
1614
1615
|
|
|
1615
|
-
|
|
1616
|
+
getPostThread<A extends Auth = void>(
|
|
1616
1617
|
cfg: MethodConfigOrHandler<
|
|
1617
1618
|
A,
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1619
|
+
AppBskyFeedGetPostThread.QueryParams,
|
|
1620
|
+
AppBskyFeedGetPostThread.HandlerInput,
|
|
1621
|
+
AppBskyFeedGetPostThread.HandlerOutput
|
|
1621
1622
|
>,
|
|
1622
1623
|
) {
|
|
1623
|
-
const nsid = 'app.bsky.feed.
|
|
1624
|
+
const nsid = 'app.bsky.feed.getPostThread' // @ts-ignore
|
|
1624
1625
|
return this._server.xrpc.method(nsid, cfg)
|
|
1625
1626
|
}
|
|
1626
1627
|
|
|
@@ -2120,6 +2121,18 @@ export class AppBskyUnspeccedNS {
|
|
|
2120
2121
|
this._server = server
|
|
2121
2122
|
}
|
|
2122
2123
|
|
|
2124
|
+
checkHandleAvailability<A extends Auth = void>(
|
|
2125
|
+
cfg: MethodConfigOrHandler<
|
|
2126
|
+
A,
|
|
2127
|
+
AppBskyUnspeccedCheckHandleAvailability.QueryParams,
|
|
2128
|
+
AppBskyUnspeccedCheckHandleAvailability.HandlerInput,
|
|
2129
|
+
AppBskyUnspeccedCheckHandleAvailability.HandlerOutput
|
|
2130
|
+
>,
|
|
2131
|
+
) {
|
|
2132
|
+
const nsid = 'app.bsky.unspecced.checkHandleAvailability' // @ts-ignore
|
|
2133
|
+
return this._server.xrpc.method(nsid, cfg)
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2123
2136
|
getAgeAssuranceState<A extends Auth = void>(
|
|
2124
2137
|
cfg: MethodConfigOrHandler<
|
|
2125
2138
|
A,
|