@videocensor/sdk 1.0.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/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ # Changelog
2
+
3
+ Все значимые изменения SDK документируются здесь. Формат — [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
4
+ Версионирование — [SemVer](https://semver.org/):
5
+
6
+ - **MAJOR** — breaking changes: удаление endpoint, удаление или переименование поля, изменение типа поля
7
+ - **MINOR** — новые endpoints, новые опциональные поля
8
+ - **PATCH** — багфиксы клиента, улучшения retry/backoff, docs
9
+
10
+ Breaking changes сопровождаются deprecation period не менее 3 месяцев в [Deprecated] секции до релиза MAJOR.
11
+
12
+ ## [1.0.0] — 2026-04-08
13
+
14
+ Первый публичный релиз.
15
+
16
+ ### Added
17
+ - `VideoCensor` client с методами: `analyzeText`, `censorText`, `analyzeMedia`, `censorMedia`, `analyzeMediaUrl`, `censorMediaUrl`, `listJobs`, `getJob`, `getJobResult`, `cancelJob`, `downloadJob`, `getTranscript`, `waitForJob`, `createBatch`, `getBatch`, `listWebhooks`, `createWebhook`, `deleteWebhook`, `getAccount`
18
+ - Типизированные ошибки: `AuthenticationError`, `RateLimitError`, `BadRequestError`, `NotFoundError`, `VideoCensorError`
19
+ - Exponential backoff retry (429/5xx) с Retry-After
20
+ - Поддержка `vc_live_` и `vc_test_` API-ключей
21
+ - Модели `JobStatus`, `JobResult` синхронизированы с публичным контрактом API (16 и 9 полей соответственно)
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 VideoCensor
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,136 @@
1
+ # @videocensor/sdk
2
+
3
+ Node.js SDK for [VideoCensor](https://videocensor.ru) content moderation API.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @videocensor/sdk
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { VideoCensor } from '@videocensor/sdk';
15
+
16
+ const client = new VideoCensor({ apiKey: 'vc_live_...' });
17
+
18
+ // Analyze text
19
+ const result = await client.analyzeText('some text to check');
20
+ console.log(result.flaggedCount, result.categories);
21
+
22
+ // Censor text
23
+ const censored = await client.censorText('some text to censor');
24
+ console.log(censored.censored);
25
+
26
+ // Analyze media file
27
+ const job = await client.analyzeMedia('/path/to/video.mp4');
28
+ const completed = await client.waitForJob(job.id);
29
+ const analysis = await client.getJobResult(completed.id);
30
+
31
+ // Censor media from URL
32
+ const censorJob = await client.censorMediaUrl('https://youtube.com/watch?v=...');
33
+ const done = await client.waitForJob(censorJob.id);
34
+ await client.downloadJob(done.id, './censored.mp4');
35
+ ```
36
+
37
+ ## Configuration
38
+
39
+ ```typescript
40
+ const client = new VideoCensor({
41
+ apiKey: 'vc_live_...', // Required. Use vc_test_ for sandbox.
42
+ baseUrl: 'https://...', // Optional. Default: https://videocensor.ru/api/v1
43
+ timeout: 30_000, // Optional. Request timeout in ms.
44
+ maxRetries: 3, // Optional. Retries on 429/5xx.
45
+ });
46
+ ```
47
+
48
+ ## API
49
+
50
+ ### Text
51
+
52
+ | Method | Description |
53
+ |--------|-------------|
54
+ | `analyzeText(text, options?)` | Analyze text for flagged content |
55
+ | `censorText(text, options?)` | Replace flagged words with `***` |
56
+
57
+ Options: `language`, `categories`, `preset`, `replacement` (censor only).
58
+
59
+ ### Media
60
+
61
+ | Method | Description |
62
+ |--------|-------------|
63
+ | `analyzeMedia(filePath, options?)` | Upload and analyze media file |
64
+ | `censorMedia(filePath, options?)` | Upload and censor media file |
65
+ | `analyzeMediaUrl(url, options?)` | Analyze media from URL |
66
+ | `censorMediaUrl(url, options?)` | Censor media from URL |
67
+
68
+ Options: `language`, `mode`, `categories`, `preset`, `wayOfBlocking`, `censorStrength`.
69
+
70
+ ### Jobs
71
+
72
+ | Method | Description |
73
+ |--------|-------------|
74
+ | `listJobs(options?)` | List jobs with pagination |
75
+ | `getJob(jobId)` | Get job status |
76
+ | `getJobResult(jobId)` | Get full analysis result |
77
+ | `cancelJob(jobId)` | Cancel a job |
78
+ | `downloadJob(jobId, outputPath)` | Download processed file |
79
+ | `getTranscript(jobId, format?)` | Get transcription (srt/txt/json) |
80
+ | `waitForJob(jobId, timeoutMs?)` | Poll until job completes |
81
+
82
+ ### Batch
83
+
84
+ | Method | Description |
85
+ |--------|-------------|
86
+ | `createBatch(requests)` | Submit batch of text operations |
87
+ | `getBatch(batchId)` | Get batch status and results |
88
+
89
+ ### Webhooks
90
+
91
+ | Method | Description |
92
+ |--------|-------------|
93
+ | `listWebhooks()` | List webhook endpoints |
94
+ | `createWebhook(url, events?)` | Create webhook endpoint |
95
+ | `deleteWebhook(webhookId)` | Delete webhook endpoint |
96
+
97
+ ### Account
98
+
99
+ | Method | Description |
100
+ |--------|-------------|
101
+ | `getAccount()` | Get account info and usage |
102
+
103
+ ## Error Handling
104
+
105
+ ```typescript
106
+ import { VideoCensorError, RateLimitError, AuthenticationError } from '@videocensor/sdk';
107
+
108
+ try {
109
+ await client.analyzeText('test');
110
+ } catch (err) {
111
+ if (err instanceof RateLimitError) {
112
+ console.log(`Retry after ${err.retryAfter}s`);
113
+ }
114
+ if (err instanceof AuthenticationError) {
115
+ console.log('Invalid API key');
116
+ }
117
+ }
118
+ ```
119
+
120
+ ## Content Categories
121
+
122
+ `profanity`, `hate_speech`, `extremism`, `drugs`, `sexual`, `insults`
123
+
124
+ ## Requirements
125
+
126
+ Node.js >= 18
127
+
128
+ ## Versioning
129
+
130
+ This SDK follows [SemVer](https://semver.org/):
131
+
132
+ - **MAJOR** — breaking changes (removed endpoints/fields, type changes). Deprecation notice at least 3 months in advance.
133
+ - **MINOR** — new endpoints or optional fields.
134
+ - **PATCH** — bugfixes, retry/backoff improvements, docs.
135
+
136
+ See [CHANGELOG.md](./CHANGELOG.md) for release notes.
@@ -0,0 +1,48 @@
1
+ import type { TextAnalysisResult, TextCensorResult, JobStatus, JobResult, JobList, WebhookEndpoint, AccountInfo, BatchResult, AnalyzeTextOptions, CensorTextOptions, MediaOptions, CensorMediaOptions } from './models';
2
+ export interface VideoCensorConfig {
3
+ apiKey: string;
4
+ baseUrl?: string;
5
+ timeout?: number;
6
+ maxRetries?: number;
7
+ }
8
+ export declare class VideoCensor {
9
+ private readonly baseUrl;
10
+ private readonly apiKey;
11
+ private readonly timeout;
12
+ private readonly maxRetries;
13
+ constructor(config: VideoCensorConfig);
14
+ analyzeText(text: string, options?: AnalyzeTextOptions): Promise<TextAnalysisResult>;
15
+ censorText(text: string, options?: CensorTextOptions): Promise<TextCensorResult>;
16
+ analyzeMedia(filePath: string, options?: MediaOptions): Promise<JobStatus>;
17
+ censorMedia(filePath: string, options?: CensorMediaOptions): Promise<JobStatus>;
18
+ analyzeMediaUrl(url: string, options?: MediaOptions): Promise<JobStatus>;
19
+ censorMediaUrl(url: string, options?: CensorMediaOptions): Promise<JobStatus>;
20
+ listJobs(options?: {
21
+ cursor?: string;
22
+ page?: number;
23
+ limit?: number;
24
+ status?: string;
25
+ }): Promise<JobList>;
26
+ getJob(jobId: string): Promise<JobStatus>;
27
+ getJobResult(jobId: string): Promise<JobResult>;
28
+ cancelJob(jobId: string): Promise<void>;
29
+ downloadJob(jobId: string, outputPath: string): Promise<void>;
30
+ getTranscript(jobId: string, format?: 'srt' | 'txt' | 'json'): Promise<unknown>;
31
+ waitForJob(jobId: string, timeoutMs?: number): Promise<JobStatus>;
32
+ createBatch(requests: Array<{
33
+ method: string;
34
+ params: Record<string, unknown>;
35
+ }>): Promise<BatchResult>;
36
+ getBatch(batchId: string): Promise<BatchResult>;
37
+ listWebhooks(): Promise<WebhookEndpoint[]>;
38
+ createWebhook(url: string, events?: string[]): Promise<WebhookEndpoint>;
39
+ deleteWebhook(webhookId: string): Promise<void>;
40
+ getAccount(): Promise<AccountInfo>;
41
+ private get;
42
+ private post;
43
+ private postMedia;
44
+ private fetchWithRetry;
45
+ private throwResponseError;
46
+ private retryDelay;
47
+ private sleep;
48
+ }
package/dist/client.js ADDED
@@ -0,0 +1,234 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VideoCensor = void 0;
4
+ const promises_1 = require("node:fs/promises");
5
+ const node_fs_1 = require("node:fs");
6
+ const node_path_1 = require("node:path");
7
+ const errors_1 = require("./errors");
8
+ const DEFAULT_BASE_URL = 'https://videocensor.ru/api/v1';
9
+ const POLL_INTERVAL_MS = 3_000;
10
+ const MAX_POLL_ATTEMPTS = 200;
11
+ const MAX_RETRIES = 3;
12
+ const RETRYABLE_STATUSES = new Set([429, 500, 502, 503, 504]);
13
+ class VideoCensor {
14
+ baseUrl;
15
+ apiKey;
16
+ timeout;
17
+ maxRetries;
18
+ constructor(config) {
19
+ if (!config.apiKey?.startsWith('vc_live_') && !config.apiKey?.startsWith('vc_test_')) {
20
+ throw new errors_1.AuthenticationError("API key must start with 'vc_live_' or 'vc_test_'");
21
+ }
22
+ this.baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
23
+ this.apiKey = config.apiKey;
24
+ this.timeout = config.timeout ?? 30_000;
25
+ this.maxRetries = config.maxRetries ?? MAX_RETRIES;
26
+ }
27
+ // ── Text ──
28
+ async analyzeText(text, options = {}) {
29
+ return this.post('/analyze/text', {
30
+ text,
31
+ language: options.language ?? 'ru',
32
+ categories: options.categories,
33
+ preset: options.preset ?? 'standard',
34
+ });
35
+ }
36
+ async censorText(text, options = {}) {
37
+ return this.post('/censor/text', {
38
+ text,
39
+ language: options.language ?? 'ru',
40
+ replacement: options.replacement ?? '***',
41
+ categories: options.categories,
42
+ preset: options.preset ?? 'standard',
43
+ });
44
+ }
45
+ // ── Media ──
46
+ async analyzeMedia(filePath, options = {}) {
47
+ return this.postMedia('/analyze/media', filePath, options);
48
+ }
49
+ async censorMedia(filePath, options = {}) {
50
+ return this.postMedia('/censor/media', filePath, options);
51
+ }
52
+ async analyzeMediaUrl(url, options = {}) {
53
+ return this.post('/analyze/media-url', {
54
+ url, language: options.language ?? 'ru', mode: options.mode ?? 'standard',
55
+ categories: options.categories, preset: options.preset ?? 'standard',
56
+ });
57
+ }
58
+ async censorMediaUrl(url, options = {}) {
59
+ return this.post('/censor/media-url', {
60
+ url, language: options.language ?? 'ru', mode: options.mode ?? 'standard',
61
+ categories: options.categories, preset: options.preset ?? 'standard',
62
+ wayOfBlocking: options.wayOfBlocking ?? 'beep',
63
+ censorStrength: options.censorStrength ?? 100,
64
+ });
65
+ }
66
+ // ── Jobs ──
67
+ async listJobs(options = {}) {
68
+ const params = new URLSearchParams();
69
+ if (options.cursor)
70
+ params.set('cursor', options.cursor);
71
+ else if (options.page)
72
+ params.set('page', String(options.page));
73
+ if (options.limit)
74
+ params.set('limit', String(options.limit));
75
+ if (options.status)
76
+ params.set('status', options.status);
77
+ const qs = params.toString();
78
+ return this.get(`/jobs${qs ? `?${qs}` : ''}`);
79
+ }
80
+ async getJob(jobId) {
81
+ return this.get(`/jobs/${jobId}`);
82
+ }
83
+ async getJobResult(jobId) {
84
+ return this.get(`/jobs/${jobId}/result`);
85
+ }
86
+ async cancelJob(jobId) {
87
+ await this.post(`/jobs/${jobId}/cancel`);
88
+ }
89
+ async downloadJob(jobId, outputPath) {
90
+ const response = await this.fetchWithRetry(`/jobs/${jobId}/download`, { method: 'GET' });
91
+ const buffer = Buffer.from(await response.arrayBuffer());
92
+ await (0, promises_1.writeFile)(outputPath, buffer);
93
+ }
94
+ async getTranscript(jobId, format = 'json') {
95
+ const response = await this.fetchWithRetry(`/jobs/${jobId}/transcript?format=${format}`, { method: 'GET' });
96
+ if (format === 'json')
97
+ return response.json();
98
+ return response.text();
99
+ }
100
+ async waitForJob(jobId, timeoutMs = 600_000) {
101
+ const deadline = Date.now() + timeoutMs;
102
+ for (let i = 0; i < MAX_POLL_ATTEMPTS; i++) {
103
+ if (Date.now() > deadline) {
104
+ throw new errors_1.VideoCensorError(`Job ${jobId} did not complete within ${timeoutMs}ms`);
105
+ }
106
+ const status = await this.getJob(jobId);
107
+ if (['done', 'error', 'cancelled'].includes(status.status))
108
+ return status;
109
+ await this.sleep(POLL_INTERVAL_MS);
110
+ }
111
+ throw new errors_1.VideoCensorError(`Job ${jobId} polling exceeded ${MAX_POLL_ATTEMPTS} attempts`);
112
+ }
113
+ // ── Batch ──
114
+ async createBatch(requests) {
115
+ return this.post('/batch', { requests });
116
+ }
117
+ async getBatch(batchId) {
118
+ return this.get(`/batch/${batchId}`);
119
+ }
120
+ // ── Webhooks ──
121
+ async listWebhooks() {
122
+ const data = await this.get('/webhooks');
123
+ return data.data;
124
+ }
125
+ async createWebhook(url, events) {
126
+ return this.post('/webhooks', { url, events });
127
+ }
128
+ async deleteWebhook(webhookId) {
129
+ await this.fetchWithRetry(`/webhooks/${webhookId}`, { method: 'DELETE' });
130
+ }
131
+ // ── Account ──
132
+ async getAccount() {
133
+ return this.get('/account');
134
+ }
135
+ // ── Internal ──
136
+ async get(path) {
137
+ const response = await this.fetchWithRetry(path, { method: 'GET' });
138
+ return response.json();
139
+ }
140
+ async post(path, body) {
141
+ const response = await this.fetchWithRetry(path, {
142
+ method: 'POST',
143
+ headers: { 'Content-Type': 'application/json' },
144
+ body: body ? JSON.stringify(body) : undefined,
145
+ });
146
+ return response.json();
147
+ }
148
+ async postMedia(path, filePath, options) {
149
+ const blob = typeof node_fs_1.openAsBlob === 'function'
150
+ ? await (0, node_fs_1.openAsBlob)(filePath)
151
+ : new Blob([await (0, promises_1.readFile)(filePath)]);
152
+ const form = new FormData();
153
+ form.append('file', blob, (0, node_path_1.basename)(filePath));
154
+ form.append('language', options.language ?? 'ru');
155
+ form.append('mode', options.mode ?? 'standard');
156
+ form.append('preset', options.preset ?? 'standard');
157
+ if (options.categories?.length) {
158
+ for (const cat of options.categories)
159
+ form.append('categories', cat);
160
+ }
161
+ if (options.wayOfBlocking)
162
+ form.append('wayOfBlocking', options.wayOfBlocking);
163
+ if (options.censorStrength !== undefined)
164
+ form.append('censorStrength', String(options.censorStrength));
165
+ const response = await this.fetchWithRetry(path, { method: 'POST', body: form });
166
+ return response.json();
167
+ }
168
+ async fetchWithRetry(path, init) {
169
+ let lastError;
170
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
171
+ if (attempt > 0) {
172
+ const delay = this.retryDelay(attempt, lastError);
173
+ await this.sleep(delay);
174
+ }
175
+ try {
176
+ const response = await globalThis.fetch(`${this.baseUrl}${path}`, {
177
+ ...init,
178
+ headers: {
179
+ Authorization: `Bearer ${this.apiKey}`,
180
+ ...init.headers,
181
+ },
182
+ signal: AbortSignal.timeout(this.timeout),
183
+ });
184
+ if (response.ok)
185
+ return response;
186
+ if (!RETRYABLE_STATUSES.has(response.status) || attempt === this.maxRetries) {
187
+ await this.throwResponseError(response);
188
+ }
189
+ lastError = new errors_1.VideoCensorError(`HTTP ${response.status}`, response.status);
190
+ }
191
+ catch (err) {
192
+ if (err instanceof errors_1.VideoCensorError)
193
+ throw err;
194
+ if (attempt === this.maxRetries)
195
+ throw err;
196
+ lastError = err;
197
+ }
198
+ }
199
+ throw lastError ?? new errors_1.VideoCensorError('Request failed after retries');
200
+ }
201
+ async throwResponseError(response) {
202
+ const status = response.status;
203
+ let message = `Request failed with status ${status}`;
204
+ try {
205
+ const body = await response.json();
206
+ if (body.detail)
207
+ message = body.detail;
208
+ else if (body.message)
209
+ message = body.message;
210
+ }
211
+ catch { /* body not JSON */ }
212
+ if (status === 401)
213
+ throw new errors_1.AuthenticationError(message);
214
+ if (status === 429) {
215
+ const retryAfter = parseInt(response.headers.get('retry-after') ?? '60', 10);
216
+ throw new errors_1.RateLimitError(message, retryAfter);
217
+ }
218
+ if (status === 400)
219
+ throw new errors_1.BadRequestError(message);
220
+ if (status === 404)
221
+ throw new errors_1.NotFoundError(message);
222
+ throw new errors_1.VideoCensorError(message, status);
223
+ }
224
+ retryDelay(attempt, lastError) {
225
+ if (lastError instanceof errors_1.RateLimitError && lastError.retryAfter) {
226
+ return lastError.retryAfter * 1000;
227
+ }
228
+ return Math.min(1000 * 2 ** (attempt - 1), 30_000);
229
+ }
230
+ sleep(ms) {
231
+ return new Promise((resolve) => setTimeout(resolve, ms));
232
+ }
233
+ }
234
+ exports.VideoCensor = VideoCensor;
@@ -0,0 +1,17 @@
1
+ export declare class VideoCensorError extends Error {
2
+ readonly statusCode?: number | undefined;
3
+ constructor(message: string, statusCode?: number | undefined);
4
+ }
5
+ export declare class AuthenticationError extends VideoCensorError {
6
+ constructor(message: string);
7
+ }
8
+ export declare class RateLimitError extends VideoCensorError {
9
+ readonly retryAfter?: number | undefined;
10
+ constructor(message: string, retryAfter?: number | undefined);
11
+ }
12
+ export declare class BadRequestError extends VideoCensorError {
13
+ constructor(message: string);
14
+ }
15
+ export declare class NotFoundError extends VideoCensorError {
16
+ constructor(message: string);
17
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NotFoundError = exports.BadRequestError = exports.RateLimitError = exports.AuthenticationError = exports.VideoCensorError = void 0;
4
+ class VideoCensorError extends Error {
5
+ statusCode;
6
+ constructor(message, statusCode) {
7
+ super(message);
8
+ this.statusCode = statusCode;
9
+ this.name = 'VideoCensorError';
10
+ }
11
+ }
12
+ exports.VideoCensorError = VideoCensorError;
13
+ class AuthenticationError extends VideoCensorError {
14
+ constructor(message) {
15
+ super(message, 401);
16
+ this.name = 'AuthenticationError';
17
+ }
18
+ }
19
+ exports.AuthenticationError = AuthenticationError;
20
+ class RateLimitError extends VideoCensorError {
21
+ retryAfter;
22
+ constructor(message, retryAfter) {
23
+ super(message, 429);
24
+ this.retryAfter = retryAfter;
25
+ this.name = 'RateLimitError';
26
+ }
27
+ }
28
+ exports.RateLimitError = RateLimitError;
29
+ class BadRequestError extends VideoCensorError {
30
+ constructor(message) {
31
+ super(message, 400);
32
+ this.name = 'BadRequestError';
33
+ }
34
+ }
35
+ exports.BadRequestError = BadRequestError;
36
+ class NotFoundError extends VideoCensorError {
37
+ constructor(message) {
38
+ super(message, 404);
39
+ this.name = 'NotFoundError';
40
+ }
41
+ }
42
+ exports.NotFoundError = NotFoundError;
@@ -0,0 +1,4 @@
1
+ export { VideoCensor } from './client';
2
+ export type { VideoCensorConfig } from './client';
3
+ export { VideoCensorError, AuthenticationError, RateLimitError, BadRequestError, NotFoundError, } from './errors';
4
+ export type { TextAnalysisResult, TextCensorResult, JobStatus, JobResult, JobList, WebhookEndpoint, AccountInfo, BatchResult, AnalyzeTextOptions, CensorTextOptions, MediaOptions, CensorMediaOptions, ContentFlag, FlaggedWord, CategorySummary, } from './models';
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NotFoundError = exports.BadRequestError = exports.RateLimitError = exports.AuthenticationError = exports.VideoCensorError = exports.VideoCensor = void 0;
4
+ var client_1 = require("./client");
5
+ Object.defineProperty(exports, "VideoCensor", { enumerable: true, get: function () { return client_1.VideoCensor; } });
6
+ var errors_1 = require("./errors");
7
+ Object.defineProperty(exports, "VideoCensorError", { enumerable: true, get: function () { return errors_1.VideoCensorError; } });
8
+ Object.defineProperty(exports, "AuthenticationError", { enumerable: true, get: function () { return errors_1.AuthenticationError; } });
9
+ Object.defineProperty(exports, "RateLimitError", { enumerable: true, get: function () { return errors_1.RateLimitError; } });
10
+ Object.defineProperty(exports, "BadRequestError", { enumerable: true, get: function () { return errors_1.BadRequestError; } });
11
+ Object.defineProperty(exports, "NotFoundError", { enumerable: true, get: function () { return errors_1.NotFoundError; } });
@@ -0,0 +1,118 @@
1
+ export interface ContentFlag {
2
+ category: string;
3
+ severity: string;
4
+ }
5
+ export interface FlaggedWord {
6
+ text: string;
7
+ position: number;
8
+ flagged: boolean;
9
+ flags: ContentFlag[];
10
+ }
11
+ export interface CategorySummary {
12
+ count: number;
13
+ words: string[];
14
+ }
15
+ export interface TextAnalysisResult {
16
+ totalWords: number;
17
+ flaggedCount: number;
18
+ categories: Record<string, CategorySummary>;
19
+ words: FlaggedWord[];
20
+ cleanText: string;
21
+ }
22
+ export interface TextCensorResult {
23
+ censored: string;
24
+ replacements: number;
25
+ flags: Array<{
26
+ text: string;
27
+ position: number;
28
+ flags: ContentFlag[];
29
+ }>;
30
+ }
31
+ export interface JobStatus {
32
+ id: string;
33
+ status: string;
34
+ stage: string | null;
35
+ progress: number;
36
+ message: string | null;
37
+ censoredCount: number;
38
+ censoredWords: string[];
39
+ sourceOriginalName: string | null;
40
+ language: string | null;
41
+ transcriptionMode: string | null;
42
+ durationSec: number | null;
43
+ errorMessage: string | null;
44
+ errorCode: string | null;
45
+ downloadUrl: string | null;
46
+ createdAt: string;
47
+ updatedAt: string;
48
+ }
49
+ export interface WordTimestamp {
50
+ text: string;
51
+ start: number;
52
+ end: number;
53
+ confidence: number;
54
+ }
55
+ export interface CensoredWordTimestamp extends WordTimestamp {
56
+ flags: ContentFlag[];
57
+ }
58
+ export interface JobResult {
59
+ id: string;
60
+ status: string;
61
+ fullText: string | null;
62
+ censoredCount: number;
63
+ censoredWords: string[];
64
+ wordTimestamps: WordTimestamp[];
65
+ censoredTimestamps: CensoredWordTimestamp[];
66
+ chapters: unknown[] | null;
67
+ settings: Record<string, unknown> | null;
68
+ }
69
+ export interface JobList {
70
+ data: JobStatus[];
71
+ page?: number;
72
+ limit: number;
73
+ total?: number;
74
+ hasMore?: boolean;
75
+ nextCursor?: string;
76
+ }
77
+ export interface WebhookEndpoint {
78
+ id: string;
79
+ url: string;
80
+ events: string[];
81
+ active: boolean;
82
+ secret?: string;
83
+ }
84
+ export interface AccountInfo {
85
+ email?: string;
86
+ apiTier: string;
87
+ usage: Record<string, number>;
88
+ tierLimits: Record<string, unknown>;
89
+ }
90
+ export interface BatchResult {
91
+ batchId: string;
92
+ total: number;
93
+ completed: number;
94
+ failed: number;
95
+ status: string;
96
+ results: unknown[];
97
+ }
98
+ export interface AnalyzeTextOptions {
99
+ language?: string;
100
+ categories?: string[];
101
+ preset?: string;
102
+ }
103
+ export interface CensorTextOptions {
104
+ language?: string;
105
+ replacement?: string;
106
+ categories?: string[];
107
+ preset?: string;
108
+ }
109
+ export interface MediaOptions {
110
+ language?: string;
111
+ mode?: string;
112
+ categories?: string[];
113
+ preset?: string;
114
+ }
115
+ export interface CensorMediaOptions extends MediaOptions {
116
+ wayOfBlocking?: string;
117
+ censorStrength?: number;
118
+ }
package/dist/models.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@videocensor/sdk",
3
+ "version": "1.0.0",
4
+ "description": "Official Node.js SDK for VideoCensor content moderation API — profanity detection, hate speech, censoring for text, audio and video.",
5
+ "keywords": [
6
+ "videocensor",
7
+ "content-moderation",
8
+ "profanity",
9
+ "profanity-filter",
10
+ "censor",
11
+ "moderation",
12
+ "speech-to-text",
13
+ "audio",
14
+ "video",
15
+ "api-client",
16
+ "sdk"
17
+ ],
18
+ "homepage": "https://videocensor.ru/developers",
19
+ "bugs": {
20
+ "url": "https://videocensor.ru/developers",
21
+ "email": "support@videocensor.ru"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/dzhumaevn/videocensor.git",
26
+ "directory": "sdk/nodejs"
27
+ },
28
+ "license": "MIT",
29
+ "author": {
30
+ "name": "VideoCensor",
31
+ "email": "support@videocensor.ru",
32
+ "url": "https://videocensor.ru"
33
+ },
34
+ "main": "dist/index.js",
35
+ "types": "dist/index.d.ts",
36
+ "files": [
37
+ "dist",
38
+ "README.md",
39
+ "LICENSE",
40
+ "CHANGELOG.md"
41
+ ],
42
+ "engines": {
43
+ "node": ">=18"
44
+ },
45
+ "scripts": {
46
+ "build": "tsc",
47
+ "prepublishOnly": "npm run build"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "devDependencies": {
53
+ "typescript": "^5.0.0"
54
+ }
55
+ }