@calmlens/sdk 0.16.3 → 0.18.0
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/README.md +425 -181
- package/bin/calmlens.js +16016 -0
- package/bin/package.json +1 -0
- package/cjs/BrowserUpload-BT091eOB.d.ts +11 -0
- package/cjs/BrowserUpload-CQwXEGFv.js +10 -0
- package/cjs/CalmLensAppClient-CHQGu-Do.js +89 -0
- package/cjs/CalmLensAppClient-CV_K26Ce.d.ts +40 -0
- package/cjs/CalmLensConnection-B8Bu2j3y.js +659 -0
- package/cjs/CalmLensServerClient-C5o2A8MW.js +645 -0
- package/cjs/CalmLensServerClient-JtP687a3.d.ts +235 -0
- package/cjs/CalmLensTypes-9Vt16pc7.d.ts +40400 -0
- package/cjs/Classification-94kSYWZw-CMnQKW13.d.ts +5873 -0
- package/cjs/CryptoUtils-B_x8lMIx-YBW1-bvL.js +42 -0
- package/cjs/FileIcons-Ob003MdS.js +175 -0
- package/cjs/Jurisdiction-FusEXb4U-zVs21tDv.js +91 -0
- package/cjs/ModerationContext-9lGm-Mna-CcmbRnOy.d.ts +67 -0
- package/cjs/ModerationContextLimits-DelWfmiJ.js +1311 -0
- package/cjs/ReactUpload-CAyCsyG6.d.ts +17 -0
- package/cjs/ReactUpload-Cfg5JYUq.js +54 -0
- package/cjs/TestTriggers-By07LR31-CmK0yTyR.d.ts +271 -0
- package/cjs/TestTriggers-DTc_iMJF-BjMPq4_3.js +1327 -0
- package/cjs/UploadController-BGO1eyzb.js +618 -0
- package/{esm/index-C9Xw1aid.d.ts → cjs/WebhookPayload-2aL31v0J.d.ts} +131 -155
- package/cjs/app.d.ts +7 -44
- package/cjs/app.js +11 -89
- package/cjs/elements.d.ts +88 -0
- package/cjs/elements.js +555 -0
- package/cjs/file-icons.d.ts +67 -0
- package/cjs/file-icons.js +10 -0
- package/cjs/index-CI4BzN4W.d.ts +252 -0
- package/cjs/index.d.ts +6 -4
- package/cjs/index.js +9 -4
- package/cjs/react-native.d.ts +56 -0
- package/cjs/react-native.js +495 -0
- package/cjs/react.d.ts +30 -0
- package/cjs/react.js +370 -0
- package/cjs/server.d.ts +12 -191
- package/cjs/server.js +18 -378
- package/cjs/test-inputs.d.ts +2 -2
- package/cjs/test-inputs.js +9 -264
- package/cjs/testing.d.ts +285 -190
- package/cjs/testing.js +2393 -1203
- package/cjs/ui-CC1GOoMg.js +583 -0
- package/cjs/ui-wIiMfqMB.d.ts +526 -0
- package/cjs/ui.d.ts +2 -0
- package/cjs/ui.js +27 -0
- package/cjs/vue.d.ts +490 -0
- package/cjs/vue.js +419 -0
- package/esm/BrowserUpload-BT091eOB.d.ts +11 -0
- package/esm/BrowserUpload-D2xBWqhm.js +5 -0
- package/esm/CalmLensAppClient-CV_K26Ce.d.ts +40 -0
- package/esm/CalmLensAppClient-DuFas_dW.js +84 -0
- package/esm/CalmLensConnection-MxwwLXdl.js +582 -0
- package/esm/CalmLensServerClient-D80_mKpW.js +586 -0
- package/esm/CalmLensServerClient-JtP687a3.d.ts +235 -0
- package/esm/CalmLensTypes-9Vt16pc7.d.ts +40400 -0
- package/esm/Classification-94kSYWZw-CMnQKW13.d.ts +5873 -0
- package/esm/CryptoUtils-B_x8lMIx-DhuWGzqn.js +25 -0
- package/esm/FileIcons-M6dzjFxd.js +128 -0
- package/esm/Jurisdiction-FusEXb4U-1ov6m5yE.js +46 -0
- package/esm/ModerationContext-9lGm-Mna-CcmbRnOy.d.ts +67 -0
- package/esm/ModerationContextLimits-BaX2QnU8.js +1114 -0
- package/esm/ReactUpload-CAyCsyG6.d.ts +17 -0
- package/esm/ReactUpload-CL2XC8WS.js +37 -0
- package/esm/TestTriggers-By07LR31-CmK0yTyR.d.ts +271 -0
- package/esm/TestTriggers-DTc_iMJF-HfB533Gx.js +1267 -0
- package/esm/UploadController-qjoYGCwx.js +589 -0
- package/{cjs/index-C9Xw1aid.d.ts → esm/WebhookPayload-2aL31v0J.d.ts} +131 -155
- package/esm/app.d.ts +7 -44
- package/esm/app.js +3 -86
- package/esm/elements.d.ts +88 -0
- package/esm/elements.js +528 -0
- package/esm/file-icons.d.ts +67 -0
- package/esm/file-icons.js +1 -0
- package/esm/index-CI4BzN4W.d.ts +252 -0
- package/esm/index.d.ts +6 -4
- package/esm/index.js +1 -1
- package/esm/react-native.d.ts +56 -0
- package/esm/react-native.js +467 -0
- package/esm/react.d.ts +30 -0
- package/esm/react.js +343 -0
- package/esm/server.d.ts +12 -191
- package/esm/server.js +3 -369
- package/esm/styles.css +147 -0
- package/esm/svelte/CalmLensMedia.svelte +134 -0
- package/esm/svelte/CalmLensUploader.svelte +175 -0
- package/esm/svelte/Icon.svelte +7 -0
- package/esm/svelte/index.d.ts +37 -0
- package/esm/svelte/index.js +6 -0
- package/esm/test-inputs.d.ts +2 -2
- package/esm/test-inputs.js +1 -256
- package/esm/testing.d.ts +285 -190
- package/esm/testing.js +2329 -1122
- package/esm/ui-CCY3yXFn.js +458 -0
- package/esm/ui-wIiMfqMB.d.ts +526 -0
- package/esm/ui.d.ts +2 -0
- package/esm/ui.js +3 -0
- package/esm/vue.d.ts +490 -0
- package/esm/vue.js +392 -0
- package/package.json +118 -13
- package/cjs/CalmLensConnection-BNUHp_AQ.js +0 -234
- package/cjs/CalmLensUtils-o171ownK.js +0 -699
- package/cjs/CalmLensVersion-DwhgxQK5.js +0 -480
- package/cjs/CalmLensWebhooks-BKrq30Yp.js +0 -201
- package/cjs/Classification-BNhgwGEm-DmA9VEuE.d.ts +0 -4806
- package/cjs/TestTriggers-CRKtulyp-vrzEq2wm.d.ts +0 -44
- package/cjs/WebhookPayload-CJoIkGBD.d.ts +0 -11753
- package/esm/CalmLensConnection-XTqmlwzG.js +0 -193
- package/esm/CalmLensUtils-DsF8Aa_x.js +0 -574
- package/esm/CalmLensVersion-DcifRKsx.js +0 -415
- package/esm/CalmLensWebhooks-BvGyk5d3.js +0 -148
- package/esm/Classification-BNhgwGEm-DmA9VEuE.d.ts +0 -4806
- package/esm/TestTriggers-CRKtulyp-vrzEq2wm.d.ts +0 -44
- package/esm/WebhookPayload-CJoIkGBD.d.ts +0 -11753
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from "svelte";
|
|
3
|
+
import {
|
|
4
|
+
createUploadController,
|
|
5
|
+
describeUploader,
|
|
6
|
+
describeUploadItem,
|
|
7
|
+
resolveMessages,
|
|
8
|
+
type UploaderProps,
|
|
9
|
+
} from "../ui.js";
|
|
10
|
+
import Icon from "./Icon.svelte";
|
|
11
|
+
|
|
12
|
+
let {
|
|
13
|
+
disabled = false,
|
|
14
|
+
capture,
|
|
15
|
+
messages: overrides,
|
|
16
|
+
controller: external,
|
|
17
|
+
...options
|
|
18
|
+
}: UploaderProps = $props();
|
|
19
|
+
|
|
20
|
+
// The latest options, without restarting uploads when the parent re-renders.
|
|
21
|
+
const own = createUploadController(options);
|
|
22
|
+
$effect.pre(() => {
|
|
23
|
+
if (!external) own.setOptions({ ...options });
|
|
24
|
+
});
|
|
25
|
+
onMount(() => () => own.clear());
|
|
26
|
+
|
|
27
|
+
const queue = $derived(external ?? own);
|
|
28
|
+
const messages = $derived(resolveMessages(overrides));
|
|
29
|
+
const view = $derived(describeUploader($queue, queue.options, messages));
|
|
30
|
+
let dragging = $state(false);
|
|
31
|
+
let input: HTMLInputElement | undefined = $state();
|
|
32
|
+
|
|
33
|
+
const openPicker = () => {
|
|
34
|
+
if (!disabled) input?.click();
|
|
35
|
+
};
|
|
36
|
+
const onDragOver = (event: DragEvent) => {
|
|
37
|
+
event.preventDefault();
|
|
38
|
+
if (!disabled) dragging = true;
|
|
39
|
+
};
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<div class="calmlens-uploader">
|
|
43
|
+
{#if view.showDropzone}
|
|
44
|
+
<!-- Keyboard users open the picker with the button; the zone adds drop and click targets. -->
|
|
45
|
+
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
|
|
46
|
+
<!-- biome-ignore lint/a11y/noStaticElementInteractions: the button inside is the accessible control -->
|
|
47
|
+
<div
|
|
48
|
+
class="calmlens-dropzone"
|
|
49
|
+
data-dragging={dragging || undefined}
|
|
50
|
+
data-disabled={disabled || undefined}
|
|
51
|
+
onclick={openPicker}
|
|
52
|
+
ondragover={onDragOver}
|
|
53
|
+
ondragenter={onDragOver}
|
|
54
|
+
ondragleave={(event) => {
|
|
55
|
+
if (!event.currentTarget.contains(event.relatedTarget as Node | null)) dragging = false;
|
|
56
|
+
}}
|
|
57
|
+
ondrop={(event) => {
|
|
58
|
+
event.preventDefault();
|
|
59
|
+
dragging = false;
|
|
60
|
+
if (!disabled && event.dataTransfer) queue.addFiles(event.dataTransfer.files);
|
|
61
|
+
}}
|
|
62
|
+
onpaste={(event) => {
|
|
63
|
+
const files = event.clipboardData?.files;
|
|
64
|
+
if (!disabled && files?.length) queue.addFiles(files);
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
<span class="calmlens-dropzone__icon"><Icon name="upload" /></span>
|
|
68
|
+
<div class="calmlens-dropzone__text">
|
|
69
|
+
<p class="calmlens-dropzone__title">
|
|
70
|
+
{dragging ? messages.dropzoneTitleDragging : messages.dropzoneTitle}
|
|
71
|
+
</p>
|
|
72
|
+
{#if view.hint}<p class="calmlens-dropzone__hint">{view.hint}</p>{/if}
|
|
73
|
+
</div>
|
|
74
|
+
<button
|
|
75
|
+
type="button"
|
|
76
|
+
class="calmlens-button"
|
|
77
|
+
data-variant="outline"
|
|
78
|
+
{disabled}
|
|
79
|
+
onclick={(event) => {
|
|
80
|
+
event.stopPropagation();
|
|
81
|
+
openPicker();
|
|
82
|
+
}}
|
|
83
|
+
>
|
|
84
|
+
{view.chooseLabel}
|
|
85
|
+
</button>
|
|
86
|
+
</div>
|
|
87
|
+
{/if}
|
|
88
|
+
<input
|
|
89
|
+
bind:this={input}
|
|
90
|
+
type="file"
|
|
91
|
+
hidden
|
|
92
|
+
accept={queue.options.accept}
|
|
93
|
+
multiple={queue.options.multiple}
|
|
94
|
+
{capture}
|
|
95
|
+
{disabled}
|
|
96
|
+
onchange={(event) => {
|
|
97
|
+
if (event.currentTarget.files) queue.addFiles(event.currentTarget.files);
|
|
98
|
+
event.currentTarget.value = "";
|
|
99
|
+
}}
|
|
100
|
+
/>
|
|
101
|
+
{#if $queue.items.length > 0}
|
|
102
|
+
<ul class="calmlens-uploads">
|
|
103
|
+
{#each $queue.items as item (item.key)}
|
|
104
|
+
{@const itemView = describeUploadItem(item, messages)}
|
|
105
|
+
<li class="calmlens-upload" data-stage={item.stage} data-outcome={itemView.outcome}>
|
|
106
|
+
{#if item.previewUrl}
|
|
107
|
+
<img class="calmlens-upload__thumb" src={item.previewUrl} alt="" />
|
|
108
|
+
{:else}
|
|
109
|
+
<span class="calmlens-upload__thumb" data-kind="file" aria-hidden="true"
|
|
110
|
+
>{@html itemView.fileIcon}</span
|
|
111
|
+
>
|
|
112
|
+
{/if}
|
|
113
|
+
<div class="calmlens-upload__body">
|
|
114
|
+
<span class="calmlens-upload__name">{item.file.name}</span>
|
|
115
|
+
<span class="calmlens-upload__size">{itemView.size}</span>
|
|
116
|
+
{#if itemView.busy}
|
|
117
|
+
<div
|
|
118
|
+
class="calmlens-progress"
|
|
119
|
+
role="progressbar"
|
|
120
|
+
aria-label={itemView.message}
|
|
121
|
+
aria-valuemin={0}
|
|
122
|
+
aria-valuemax={100}
|
|
123
|
+
aria-valuenow={itemView.progressPercent}
|
|
124
|
+
>
|
|
125
|
+
<div
|
|
126
|
+
class="calmlens-progress__bar"
|
|
127
|
+
style:width={itemView.progressPercent === undefined
|
|
128
|
+
? undefined
|
|
129
|
+
: `${itemView.progressPercent}%`}
|
|
130
|
+
></div>
|
|
131
|
+
</div>
|
|
132
|
+
{/if}
|
|
133
|
+
{#if itemView.message}
|
|
134
|
+
<p class="calmlens-upload__message" role="status">{itemView.message}</p>
|
|
135
|
+
{/if}
|
|
136
|
+
</div>
|
|
137
|
+
<div class="calmlens-upload__actions">
|
|
138
|
+
{#if !view.showDropzone}
|
|
139
|
+
<button
|
|
140
|
+
type="button"
|
|
141
|
+
class="calmlens-button"
|
|
142
|
+
data-variant="outline"
|
|
143
|
+
data-size="small"
|
|
144
|
+
{disabled}
|
|
145
|
+
onclick={openPicker}
|
|
146
|
+
>
|
|
147
|
+
{messages.chooseAnother}
|
|
148
|
+
</button>
|
|
149
|
+
{/if}
|
|
150
|
+
{#if itemView.canRetry}
|
|
151
|
+
<button
|
|
152
|
+
type="button"
|
|
153
|
+
class="calmlens-icon-button"
|
|
154
|
+
aria-label={messages.retry}
|
|
155
|
+
title={messages.retry}
|
|
156
|
+
onclick={() => queue.retry(item.key)}
|
|
157
|
+
>
|
|
158
|
+
<Icon name="retry" />
|
|
159
|
+
</button>
|
|
160
|
+
{/if}
|
|
161
|
+
<button
|
|
162
|
+
type="button"
|
|
163
|
+
class="calmlens-icon-button"
|
|
164
|
+
aria-label={itemView.removeLabel}
|
|
165
|
+
title={itemView.removeLabel}
|
|
166
|
+
onclick={() => queue.remove(item.key)}
|
|
167
|
+
>
|
|
168
|
+
<Icon name="remove" />
|
|
169
|
+
</button>
|
|
170
|
+
</div>
|
|
171
|
+
</li>
|
|
172
|
+
{/each}
|
|
173
|
+
</ul>
|
|
174
|
+
{/if}
|
|
175
|
+
</div>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Component } from "svelte";
|
|
2
|
+
import type {
|
|
3
|
+
CalmLensUpload as CoreCalmLensUpload,
|
|
4
|
+
UploadControllerOptions as CoreUploadControllerOptions,
|
|
5
|
+
UploaderProps as CoreUploaderProps,
|
|
6
|
+
UploadItem as CoreUploadItem,
|
|
7
|
+
UploadSnapshot as CoreUploadSnapshot,
|
|
8
|
+
MediaProps,
|
|
9
|
+
UploadController,
|
|
10
|
+
UploadFile,
|
|
11
|
+
} from "../ui.js";
|
|
12
|
+
|
|
13
|
+
// The browser's files are `File`s, so the upload types default to `File` here.
|
|
14
|
+
export type UploadItem<F extends UploadFile = File> = CoreUploadItem<F>;
|
|
15
|
+
export type CalmLensUpload<F extends UploadFile = File> = CoreCalmLensUpload<F>;
|
|
16
|
+
export type UploadSnapshot<F extends UploadFile = File> = CoreUploadSnapshot<F>;
|
|
17
|
+
export type UploadControllerOptions<F extends UploadFile = File> = CoreUploadControllerOptions<F>;
|
|
18
|
+
export type UploaderProps<F extends UploadFile = File> = CoreUploaderProps<F>;
|
|
19
|
+
/** Creates an upload queue for the components, such as one a form starts on submit. */
|
|
20
|
+
export declare const createUploadController: <F extends UploadFile = File>(
|
|
21
|
+
options: UploadControllerOptions<F>,
|
|
22
|
+
) => UploadController<F>;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A drop zone and file list that uploads through an upload preset, shows each file's progress
|
|
26
|
+
* and moderation decision, and reports finished uploads to `onUploaded`.
|
|
27
|
+
*/
|
|
28
|
+
export declare const CalmLensUploader: Component<UploaderProps>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Shows an asset from the CalmLens CDN with responsive widths and a blurred placeholder while
|
|
32
|
+
* it loads. Age-restricted assets stay behind the placeholder and a reveal button until
|
|
33
|
+
* `ageConfirmed` is set; blocked and purged assets show an unavailable notice.
|
|
34
|
+
*/
|
|
35
|
+
export declare const CalmLensMedia: Component<MediaProps>;
|
|
36
|
+
|
|
37
|
+
export * from "../ui.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Svelte 5 components for uploads and media. The upload controller is a Svelte store, so
|
|
2
|
+
// `createUploadController(...)` and `$controller.items` build your own upload UI.
|
|
3
|
+
|
|
4
|
+
export * from "../ui.js";
|
|
5
|
+
export { default as CalmLensMedia } from "./CalmLensMedia.svelte";
|
|
6
|
+
export { default as CalmLensUploader } from "./CalmLensUploader.svelte";
|
package/esm/test-inputs.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as detectTestImage, c as getImageTestCategories,
|
|
2
|
-
export { type ClassificationTestSignal,
|
|
1
|
+
import { a as detectTestImage, c as getImageTestCategories, i as TestInput, l as paintTestImage, n as TEST_INPUT_KEY_PATTERN, o as detectTestTrigger, r as TEST_INPUT_PRESETS, s as generateTestTextCode, t as ClassificationTestSignal, u as testInputForSignal } from "./TestTriggers-By07LR31-CmK0yTyR.js";
|
|
2
|
+
export { type ClassificationTestSignal, TEST_INPUT_KEY_PATTERN, TEST_INPUT_PRESETS, type TestInput, detectTestImage, detectTestTrigger, generateTestTextCode, getImageTestCategories, paintTestImage, testInputForSignal };
|
package/esm/test-inputs.js
CHANGED
|
@@ -1,256 +1 @@
|
|
|
1
|
-
|
|
2
|
-
const TEST_IMAGE_PALETTES = {
|
|
3
|
-
sexual: [
|
|
4
|
-
[
|
|
5
|
-
255,
|
|
6
|
-
20,
|
|
7
|
-
147,
|
|
8
|
-
255
|
|
9
|
-
],
|
|
10
|
-
[
|
|
11
|
-
255,
|
|
12
|
-
105,
|
|
13
|
-
180,
|
|
14
|
-
255
|
|
15
|
-
],
|
|
16
|
-
[
|
|
17
|
-
199,
|
|
18
|
-
21,
|
|
19
|
-
133,
|
|
20
|
-
255
|
|
21
|
-
],
|
|
22
|
-
[
|
|
23
|
-
255,
|
|
24
|
-
69,
|
|
25
|
-
0,
|
|
26
|
-
255
|
|
27
|
-
]
|
|
28
|
-
],
|
|
29
|
-
violence: [
|
|
30
|
-
[
|
|
31
|
-
139,
|
|
32
|
-
0,
|
|
33
|
-
0,
|
|
34
|
-
255
|
|
35
|
-
],
|
|
36
|
-
[
|
|
37
|
-
220,
|
|
38
|
-
20,
|
|
39
|
-
60,
|
|
40
|
-
255
|
|
41
|
-
],
|
|
42
|
-
[
|
|
43
|
-
178,
|
|
44
|
-
34,
|
|
45
|
-
34,
|
|
46
|
-
255
|
|
47
|
-
],
|
|
48
|
-
[
|
|
49
|
-
255,
|
|
50
|
-
0,
|
|
51
|
-
0,
|
|
52
|
-
255
|
|
53
|
-
]
|
|
54
|
-
],
|
|
55
|
-
harassment: [
|
|
56
|
-
[
|
|
57
|
-
180,
|
|
58
|
-
83,
|
|
59
|
-
9,
|
|
60
|
-
255
|
|
61
|
-
],
|
|
62
|
-
[
|
|
63
|
-
217,
|
|
64
|
-
119,
|
|
65
|
-
6,
|
|
66
|
-
255
|
|
67
|
-
],
|
|
68
|
-
[
|
|
69
|
-
245,
|
|
70
|
-
158,
|
|
71
|
-
11,
|
|
72
|
-
255
|
|
73
|
-
],
|
|
74
|
-
[
|
|
75
|
-
251,
|
|
76
|
-
191,
|
|
77
|
-
36,
|
|
78
|
-
255
|
|
79
|
-
]
|
|
80
|
-
],
|
|
81
|
-
hate: [
|
|
82
|
-
[
|
|
83
|
-
30,
|
|
84
|
-
64,
|
|
85
|
-
175,
|
|
86
|
-
255
|
|
87
|
-
],
|
|
88
|
-
[
|
|
89
|
-
37,
|
|
90
|
-
99,
|
|
91
|
-
235,
|
|
92
|
-
255
|
|
93
|
-
],
|
|
94
|
-
[
|
|
95
|
-
59,
|
|
96
|
-
130,
|
|
97
|
-
246,
|
|
98
|
-
255
|
|
99
|
-
],
|
|
100
|
-
[
|
|
101
|
-
96,
|
|
102
|
-
165,
|
|
103
|
-
250,
|
|
104
|
-
255
|
|
105
|
-
]
|
|
106
|
-
],
|
|
107
|
-
"criminal-activity": [
|
|
108
|
-
[
|
|
109
|
-
6,
|
|
110
|
-
95,
|
|
111
|
-
70,
|
|
112
|
-
255
|
|
113
|
-
],
|
|
114
|
-
[
|
|
115
|
-
5,
|
|
116
|
-
150,
|
|
117
|
-
105,
|
|
118
|
-
255
|
|
119
|
-
],
|
|
120
|
-
[
|
|
121
|
-
16,
|
|
122
|
-
185,
|
|
123
|
-
129,
|
|
124
|
-
255
|
|
125
|
-
],
|
|
126
|
-
[
|
|
127
|
-
52,
|
|
128
|
-
211,
|
|
129
|
-
153,
|
|
130
|
-
255
|
|
131
|
-
]
|
|
132
|
-
],
|
|
133
|
-
"self-harm": [
|
|
134
|
-
[
|
|
135
|
-
75,
|
|
136
|
-
0,
|
|
137
|
-
130,
|
|
138
|
-
255
|
|
139
|
-
],
|
|
140
|
-
[
|
|
141
|
-
138,
|
|
142
|
-
43,
|
|
143
|
-
226,
|
|
144
|
-
255
|
|
145
|
-
],
|
|
146
|
-
[
|
|
147
|
-
72,
|
|
148
|
-
61,
|
|
149
|
-
139,
|
|
150
|
-
255
|
|
151
|
-
],
|
|
152
|
-
[
|
|
153
|
-
147,
|
|
154
|
-
112,
|
|
155
|
-
219,
|
|
156
|
-
255
|
|
157
|
-
]
|
|
158
|
-
]
|
|
159
|
-
};
|
|
160
|
-
/** A project's test input key: eight hexadecimal characters only its members can see. */
|
|
161
|
-
const TEST_INPUT_KEY_PATTERN = /^[a-f0-9]{8}$/;
|
|
162
|
-
const TEXT_CODE_SIGNALS = [
|
|
163
|
-
"harassment",
|
|
164
|
-
"hate",
|
|
165
|
-
"criminal-activity",
|
|
166
|
-
"sexual.involving-minors"
|
|
167
|
-
];
|
|
168
|
-
/**
|
|
169
|
-
* The text code that CalmLens classifies as `category` for the project with this test input key.
|
|
170
|
-
* @returns The code, or an empty string for a category without a text code
|
|
171
|
-
*/
|
|
172
|
-
const generateTestTextCode = (category, testInputKey) => TEXT_CODE_SIGNALS.includes(category) ? `<!calmlens-test ${testInputKey} ${category}>` : "";
|
|
173
|
-
const TEST_TRIGGER_PATTERN = /<!calmlens-test\s+([a-f0-9]{8})\s+(harassment|hate|criminal-activity|sexual\.involving-minors)\s*>/gi;
|
|
174
|
-
/**
|
|
175
|
-
* The test signals in `text` whose codes carry the project's test input key. Codes with another
|
|
176
|
-
* key are ignored, so a code can only be used by the project it belongs to.
|
|
177
|
-
*/
|
|
178
|
-
const detectTestTrigger = (text, testInputKey) => {
|
|
179
|
-
const signals = [];
|
|
180
|
-
for (const [, key, signal] of text.matchAll(TEST_TRIGGER_PATTERN)) {
|
|
181
|
-
if (key.toLowerCase() !== testInputKey.toLowerCase()) continue;
|
|
182
|
-
const testSignal = signal.toLowerCase();
|
|
183
|
-
if (!signals.includes(testSignal)) signals.push(testSignal);
|
|
184
|
-
}
|
|
185
|
-
return {
|
|
186
|
-
isTestTrigger: signals.length > 0,
|
|
187
|
-
signals
|
|
188
|
-
};
|
|
189
|
-
};
|
|
190
|
-
/** Sixteen cells encode two bits each using the category's four existing colours. */
|
|
191
|
-
const getTestImageKeyCells = (width, height) => {
|
|
192
|
-
return Array.from({ length: 16 }, (_, index) => {
|
|
193
|
-
const column = index % 4;
|
|
194
|
-
const row = Math.floor(index / 4);
|
|
195
|
-
const x = Math.floor(column * width / 4);
|
|
196
|
-
const y = Math.floor(row * height / 4);
|
|
197
|
-
return {
|
|
198
|
-
x,
|
|
199
|
-
y,
|
|
200
|
-
width: Math.floor((column + 1) * width / 4) - x,
|
|
201
|
-
height: Math.floor((row + 1) * height / 4) - y
|
|
202
|
-
};
|
|
203
|
-
});
|
|
204
|
-
};
|
|
205
|
-
const getTestImageKeyColors = (signal, testInputKey) => {
|
|
206
|
-
const colors = TEST_IMAGE_PALETTES[signal];
|
|
207
|
-
if (!colors || !TEST_INPUT_KEY_PATTERN.test(testInputKey)) throw new Error("Invalid image test signal or key");
|
|
208
|
-
return [...testInputKey].flatMap((character) => {
|
|
209
|
-
const value = Number.parseInt(character, 16);
|
|
210
|
-
return [colors[value >> 2], colors[value & 3]];
|
|
211
|
-
});
|
|
212
|
-
};
|
|
213
|
-
/**
|
|
214
|
-
* Gets all test trigger categories that support images
|
|
215
|
-
*/
|
|
216
|
-
const getImageTestCategories = () => {
|
|
217
|
-
return Object.keys(TEST_IMAGE_PALETTES);
|
|
218
|
-
};
|
|
219
|
-
/** Paint a full-image 4×4 grid as RGBA pixels; callers choose their image encoder. */
|
|
220
|
-
const paintTestImage = (signal, testInputKey, width = 512, height = width) => {
|
|
221
|
-
if (!Number.isSafeInteger(width) || !Number.isSafeInteger(height) || width < 24 || height < 24) throw new RangeError("Test image dimensions must be integers of at least 24 pixels");
|
|
222
|
-
const colors = getTestImageKeyColors(signal, testInputKey);
|
|
223
|
-
const rgba = new Uint8Array(width * height * 4);
|
|
224
|
-
getTestImageKeyCells(width, height).forEach((cell, index) => {
|
|
225
|
-
for (let y = cell.y; y < cell.y + cell.height; y++) for (let x = cell.x; x < cell.x + cell.width; x++) rgba.set(colors[index], (y * width + x) * 4);
|
|
226
|
-
});
|
|
227
|
-
return rgba;
|
|
228
|
-
};
|
|
229
|
-
/** Decode the project key from pixels, tolerating resizing and lossy compression. */
|
|
230
|
-
const detectTestImage = (image, testInputKey) => {
|
|
231
|
-
const { width, height, rgba } = image;
|
|
232
|
-
if (!Number.isSafeInteger(width) || !Number.isSafeInteger(height) || width < 24 || height < 24 || rgba.length !== width * height * 4 || !TEST_INPUT_KEY_PATTERN.test(testInputKey)) return void 0;
|
|
233
|
-
const cells = getTestImageKeyCells(width, height);
|
|
234
|
-
for (const category of getImageTestCategories()) {
|
|
235
|
-
const colors = getTestImageKeyColors(category, testInputKey);
|
|
236
|
-
if (cells.every((cell, index) => {
|
|
237
|
-
const sample = [
|
|
238
|
-
0,
|
|
239
|
-
0,
|
|
240
|
-
0
|
|
241
|
-
];
|
|
242
|
-
let count = 0;
|
|
243
|
-
for (let y = cell.y + Math.ceil(cell.height / 4); y < cell.y + cell.height * 3 / 4; y++) for (let x = cell.x + Math.ceil(cell.width / 4); x < cell.x + cell.width * 3 / 4; x++) {
|
|
244
|
-
const offset = (y * width + x) * 4;
|
|
245
|
-
if (rgba[offset + 3] < 250) return false;
|
|
246
|
-
for (let c = 0; c < 3; c++) sample[c] += rgba[offset + c];
|
|
247
|
-
count++;
|
|
248
|
-
}
|
|
249
|
-
const distances = TEST_IMAGE_PALETTES[category].map((color) => sample.reduce((sum, value, c) => sum + (value / count - color[c]) ** 2, 0));
|
|
250
|
-
const nearest = distances.indexOf(Math.min(...distances));
|
|
251
|
-
return distances[nearest] <= 900 && TEST_IMAGE_PALETTES[category][nearest] === colors[index];
|
|
252
|
-
})) return category;
|
|
253
|
-
}
|
|
254
|
-
};
|
|
255
|
-
//#endregion
|
|
256
|
-
export { TEST_IMAGE_PALETTES, TEST_INPUT_KEY_PATTERN, detectTestImage, detectTestTrigger, generateTestTextCode, getImageTestCategories, getTestImageKeyCells, getTestImageKeyColors, paintTestImage };
|
|
1
|
+
export { t as TEST_INPUT_KEY_PATTERN, n as TEST_INPUT_PRESETS, r as detectTestImage, i as detectTestTrigger, a as generateTestTextCode, o as getImageTestCategories, c as paintTestImage, l as testInputForSignal } from "./TestTriggers-DTc_iMJF-HfB533Gx.js";
|