openreport-sdk 0.0.0-stage → 0.2.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 +116 -2
- package/dist/bot-client.d.ts +12 -0
- package/dist/bot-client.js +37 -0
- package/dist/client.d.ts +9 -0
- package/dist/client.js +37 -0
- package/dist/errors.d.ts +14 -0
- package/dist/errors.js +54 -0
- package/dist/http.d.ts +11 -0
- package/dist/http.js +72 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +27 -0
- package/dist/mapping.d.ts +51 -0
- package/dist/mapping.js +64 -0
- package/dist/types.d.ts +78 -0
- package/dist/types.js +8 -0
- package/package.json +19 -5
package/README.md
CHANGED
|
@@ -1,3 +1,117 @@
|
|
|
1
|
-
#
|
|
1
|
+
# openreport-sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TypeScript and JavaScript clients for OpenReport. Node.js >= 20, with no runtime
|
|
4
|
+
dependencies. Keep application and bot keys server side.
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
npm install openreport-sdk
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Application client
|
|
11
|
+
|
|
12
|
+
Third-party integrations use an application API key and the `/v1` routes.
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { OpenReport, levelAtLeast } from 'openreport-sdk';
|
|
16
|
+
|
|
17
|
+
const api = new OpenReport(process.env.OPENREPORT_API_KEY!);
|
|
18
|
+
const status = await api.lookup('123456789012345678');
|
|
19
|
+
if (status.blacklisted && status.level && levelAtLeast(status.level, 'medium')) {
|
|
20
|
+
console.log(status.level, status.since);
|
|
21
|
+
}
|
|
22
|
+
console.log(status.staffRole); // 'jury', 'admin' or null, even if not listed.
|
|
23
|
+
const members = await api.lookupMany(['123456789012345678', '987654321098765432']);
|
|
24
|
+
console.log(members.get('123456789012345678'));
|
|
25
|
+
|
|
26
|
+
const receipt = await api.report({
|
|
27
|
+
userIds: ['123456789012345678'],
|
|
28
|
+
reason: 'Describe what happened with enough context for the jury.',
|
|
29
|
+
evidence: [{ name: 'capture.png', data: screenshotBytes, type: 'image/png' }],
|
|
30
|
+
});
|
|
31
|
+
console.log(receipt.reference);
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`screenshotBytes` is a `Uint8Array`, `ArrayBuffer` or `Blob` containing the evidence.
|
|
35
|
+
The API validates report contents and evidence. `lookupMany` deduplicates IDs,
|
|
36
|
+
requests batches of at most 100, and returns a `Map<string, Status>`. An empty
|
|
37
|
+
list makes no request. Lookup and report IDs must have 17 to 20 digits or the
|
|
38
|
+
SDK rejects with `TypeError`. Levels are `low`, `medium`, `high`, mildest first.
|
|
39
|
+
Staff roles identify active staff; they do not attest to NDA acceptance.
|
|
40
|
+
|
|
41
|
+
CommonJS consumers can use `const { OpenReport } = require('openreport-sdk')`.
|
|
42
|
+
The same compiled CommonJS package supports named ESM imports and includes types.
|
|
43
|
+
|
|
44
|
+
## Official bot client
|
|
45
|
+
|
|
46
|
+
`OpenReportBot` is reserved to the official OpenReport bot. Third-party bots must
|
|
47
|
+
use `OpenReport`, and cannot act on behalf of users with an application key.
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { OpenReportBot } from 'openreport-sdk';
|
|
51
|
+
|
|
52
|
+
const bot = new OpenReportBot(process.env.OPENREPORT_BOT_KEY!);
|
|
53
|
+
const actingUserId = '123456789012345678';
|
|
54
|
+
const cases = await bot.cases('open'); // Or 'recent'.
|
|
55
|
+
if (cases[0]) {
|
|
56
|
+
const detail = await bot.caseDetail(actingUserId, cases[0].id);
|
|
57
|
+
const tally = await bot.vote(actingUserId, detail.id, {
|
|
58
|
+
approve: true,
|
|
59
|
+
level: 'medium',
|
|
60
|
+
});
|
|
61
|
+
console.log(tally.quorumReached);
|
|
62
|
+
}
|
|
63
|
+
const record = await bot.record(actingUserId);
|
|
64
|
+
const status = await bot.lookup(actingUserId);
|
|
65
|
+
// bot.report(actingUserId, input) accepts the same ReportInput as api.report().
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The bot authenticates with `Authorization: Bot <key>` and sends the acting user
|
|
69
|
+
in `X-OpenReport-User` for report, detail, vote and record calls. The backend
|
|
70
|
+
checks juror/admin permissions and NDA acceptance for details and votes. Show
|
|
71
|
+
case reasons only to authorized jurors in ephemeral messages. Evidence must
|
|
72
|
+
never pass through Discord.
|
|
73
|
+
|
|
74
|
+
## Options and errors
|
|
75
|
+
|
|
76
|
+
Both constructors accept `{ baseUrl, timeoutMs, fetch, userAgent }`. The default
|
|
77
|
+
base URL is `https://api-openreport.chell.fr`; the default timeout is 10 seconds.
|
|
78
|
+
Supply `fetch` to inject a compatible transport for tests. Requests use
|
|
79
|
+
`AbortSignal.timeout`; timeout errors retain the native `TimeoutError` name.
|
|
80
|
+
Network errors propagate, with no automatic retry of reports or votes.
|
|
81
|
+
|
|
82
|
+
Wire fields are snake_case; SDK fields are camelCase. Dates become `Date`
|
|
83
|
+
objects, including lookup timestamps supplied as Unix seconds. Absent lookup
|
|
84
|
+
role, level and date fields become `null` for compatibility with older servers.
|
|
85
|
+
|
|
86
|
+
API errors extend `OpenReportError` with `status` and `detail`:
|
|
87
|
+
|
|
88
|
+
| Error | Status | Extra |
|
|
89
|
+
| --- | --- | --- |
|
|
90
|
+
| `Unauthorized` | 401, 403 | Invalid key or acting user not authorized |
|
|
91
|
+
| `NotFound` | 404 | Unknown resource |
|
|
92
|
+
| `RateLimited` | 429 | `retryAfter`: seconds, or `null` if missing/invalid |
|
|
93
|
+
| `OpenReportError` | Other failures | Original error detail |
|
|
94
|
+
|
|
95
|
+
`Retry-After` accepts seconds or an HTTP date. Error detail retains structured
|
|
96
|
+
validation errors; plain text gateway responses are also preserved.
|
|
97
|
+
|
|
98
|
+
## Notice obligation
|
|
99
|
+
|
|
100
|
+
GDPR article 14 requires your bot to inform a person when it uses registry data
|
|
101
|
+
to restrict them in a server, at the latest at their first interaction. Explain
|
|
102
|
+
the restriction and link to `https://openreport.chell.fr/notice` and their record
|
|
103
|
+
on the OpenReport site. Do not sanction someone for a pending accusation.
|
|
104
|
+
|
|
105
|
+
## Development
|
|
106
|
+
|
|
107
|
+
```sh
|
|
108
|
+
npm install
|
|
109
|
+
npm run build
|
|
110
|
+
npm test
|
|
111
|
+
npm pack --dry-run
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`build` compiles strict TypeScript to CommonJS and declarations in `dist/`.
|
|
115
|
+
`test` runs `node:test` through `tsx` with injected fake fetch implementations.
|
|
116
|
+
`prepublishOnly` runs the build and tests. Only `dist/`, this README and package
|
|
117
|
+
metadata are published. License: MIT.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CaseDetail, CaseSummary, ClientOptions, ReportInput, ReportReceipt, SelfRecord, Status, Tally, VoteInput } from './types';
|
|
2
|
+
/** Reserved for the official OpenReport bot and its single bot key. */
|
|
3
|
+
export declare class OpenReportBot {
|
|
4
|
+
private readonly http;
|
|
5
|
+
constructor(botKey: string, options?: ClientOptions);
|
|
6
|
+
lookup(userId: string): Promise<Status>;
|
|
7
|
+
report(actingUserId: string, input: ReportInput): Promise<ReportReceipt>;
|
|
8
|
+
cases(state: 'open' | 'recent'): Promise<CaseSummary[]>;
|
|
9
|
+
caseDetail(actingUserId: string, caseId: string): Promise<CaseDetail>;
|
|
10
|
+
vote(actingUserId: string, caseId: string, vote: VoteInput): Promise<Tally>;
|
|
11
|
+
record(actingUserId: string): Promise<SelfRecord>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OpenReportBot = void 0;
|
|
4
|
+
const http_1 = require("./http");
|
|
5
|
+
const mapping_1 = require("./mapping");
|
|
6
|
+
/** Reserved for the official OpenReport bot and its single bot key. */
|
|
7
|
+
class OpenReportBot {
|
|
8
|
+
http;
|
|
9
|
+
constructor(botKey, options) {
|
|
10
|
+
this.http = new http_1.Http(`Bot ${botKey}`, options);
|
|
11
|
+
}
|
|
12
|
+
async lookup(userId) {
|
|
13
|
+
(0, http_1.validateUserId)(userId);
|
|
14
|
+
return (0, mapping_1.status)(userId, await this.http.request(`/bot/lookup/${userId}`));
|
|
15
|
+
}
|
|
16
|
+
async report(actingUserId, input) {
|
|
17
|
+
return this.http.request('/bot/reports', 'POST', (0, http_1.reportForm)(input), actingUserId);
|
|
18
|
+
}
|
|
19
|
+
async cases(state) {
|
|
20
|
+
const cases = await this.http.request(`/bot/cases?state=${encodeURIComponent(state)}`);
|
|
21
|
+
return cases.map(mapping_1.caseSummary);
|
|
22
|
+
}
|
|
23
|
+
async caseDetail(actingUserId, caseId) {
|
|
24
|
+
return (0, mapping_1.caseDetail)(await this.http.request(`/bot/cases/${encodeURIComponent(caseId)}`, 'GET', undefined, actingUserId));
|
|
25
|
+
}
|
|
26
|
+
async vote(actingUserId, caseId, vote) {
|
|
27
|
+
return (0, mapping_1.tally)(await this.http.request(`/bot/cases/${encodeURIComponent(caseId)}/vote`, 'POST', {
|
|
28
|
+
approve: vote.approve,
|
|
29
|
+
danger_level: vote.level,
|
|
30
|
+
note: vote.note,
|
|
31
|
+
}, actingUserId));
|
|
32
|
+
}
|
|
33
|
+
async record(actingUserId) {
|
|
34
|
+
return (0, mapping_1.record)(await this.http.request('/bot/me/record', 'GET', undefined, actingUserId));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.OpenReportBot = OpenReportBot;
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ClientOptions, ReportInput, ReportReceipt, Status } from './types';
|
|
2
|
+
/** Application API key client for third-party integrations. */
|
|
3
|
+
export declare class OpenReport {
|
|
4
|
+
private readonly http;
|
|
5
|
+
constructor(apiKey: string, options?: ClientOptions);
|
|
6
|
+
lookup(userId: string): Promise<Status>;
|
|
7
|
+
lookupMany(userIds: string[]): Promise<Map<string, Status>>;
|
|
8
|
+
report(input: ReportInput): Promise<ReportReceipt>;
|
|
9
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OpenReport = void 0;
|
|
4
|
+
const http_1 = require("./http");
|
|
5
|
+
const mapping_1 = require("./mapping");
|
|
6
|
+
/** Application API key client for third-party integrations. */
|
|
7
|
+
class OpenReport {
|
|
8
|
+
http;
|
|
9
|
+
constructor(apiKey, options) {
|
|
10
|
+
this.http = new http_1.Http(`Bearer ${apiKey}`, options);
|
|
11
|
+
}
|
|
12
|
+
async lookup(userId) {
|
|
13
|
+
(0, http_1.validateUserId)(userId);
|
|
14
|
+
return (0, mapping_1.status)(userId, await this.http.request(`/v1/lookup/${userId}`));
|
|
15
|
+
}
|
|
16
|
+
async lookupMany(userIds) {
|
|
17
|
+
const ids = [...new Set(userIds)];
|
|
18
|
+
ids.forEach(http_1.validateUserId);
|
|
19
|
+
const result = new Map();
|
|
20
|
+
for (let start = 0; start < ids.length; start += 100) {
|
|
21
|
+
const chunk = ids.slice(start, start + 100);
|
|
22
|
+
const entries = await this.http.request('/v1/lookup', 'POST', { ids: chunk });
|
|
23
|
+
for (const userId of chunk) {
|
|
24
|
+
const entry = entries[userId];
|
|
25
|
+
if (entry === undefined) {
|
|
26
|
+
throw new TypeError(`Lookup response missing ${userId}`);
|
|
27
|
+
}
|
|
28
|
+
result.set(userId, (0, mapping_1.status)(userId, entry));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
async report(input) {
|
|
34
|
+
return this.http.request('/v1/reports', 'POST', (0, http_1.reportForm)(input));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.OpenReport = OpenReport;
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare class OpenReportError extends Error {
|
|
2
|
+
readonly status: number;
|
|
3
|
+
readonly detail: unknown;
|
|
4
|
+
constructor(status: number, detail: unknown);
|
|
5
|
+
}
|
|
6
|
+
export declare class Unauthorized extends OpenReportError {
|
|
7
|
+
}
|
|
8
|
+
export declare class NotFound extends OpenReportError {
|
|
9
|
+
}
|
|
10
|
+
export declare class RateLimited extends OpenReportError {
|
|
11
|
+
readonly retryAfter: number | null;
|
|
12
|
+
constructor(detail: unknown, retryAfter: number | null);
|
|
13
|
+
}
|
|
14
|
+
export declare function responseError(response: Response, body: unknown): OpenReportError;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RateLimited = exports.NotFound = exports.Unauthorized = exports.OpenReportError = void 0;
|
|
4
|
+
exports.responseError = responseError;
|
|
5
|
+
class OpenReportError extends Error {
|
|
6
|
+
status;
|
|
7
|
+
detail;
|
|
8
|
+
constructor(status, detail) {
|
|
9
|
+
super(typeof detail === 'string' ? detail : `OpenReport request failed (${status})`);
|
|
10
|
+
this.status = status;
|
|
11
|
+
this.detail = detail;
|
|
12
|
+
this.name = new.target.name;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.OpenReportError = OpenReportError;
|
|
16
|
+
class Unauthorized extends OpenReportError {
|
|
17
|
+
}
|
|
18
|
+
exports.Unauthorized = Unauthorized;
|
|
19
|
+
class NotFound extends OpenReportError {
|
|
20
|
+
}
|
|
21
|
+
exports.NotFound = NotFound;
|
|
22
|
+
class RateLimited extends OpenReportError {
|
|
23
|
+
retryAfter;
|
|
24
|
+
constructor(detail, retryAfter) {
|
|
25
|
+
super(429, detail);
|
|
26
|
+
this.retryAfter = retryAfter;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.RateLimited = RateLimited;
|
|
30
|
+
function responseError(response, body) {
|
|
31
|
+
const detail = typeof body === 'object' && body !== null && 'detail' in body
|
|
32
|
+
? body.detail : body;
|
|
33
|
+
if (response.status === 401 || response.status === 403) {
|
|
34
|
+
return new Unauthorized(response.status, detail);
|
|
35
|
+
}
|
|
36
|
+
if (response.status === 404) {
|
|
37
|
+
return new NotFound(response.status, detail);
|
|
38
|
+
}
|
|
39
|
+
if (response.status === 429) {
|
|
40
|
+
const header = response.headers.get('Retry-After');
|
|
41
|
+
let retryAfter = null;
|
|
42
|
+
if (header !== null && /^\d+(?:\.\d+)?$/.test(header.trim())) {
|
|
43
|
+
retryAfter = Number(header);
|
|
44
|
+
}
|
|
45
|
+
else if (header !== null) {
|
|
46
|
+
const date = Date.parse(header);
|
|
47
|
+
if (Number.isFinite(date)) {
|
|
48
|
+
retryAfter = Math.max(0, Math.ceil((date - Date.now()) / 1000));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return new RateLimited(detail, retryAfter);
|
|
52
|
+
}
|
|
53
|
+
return new OpenReportError(response.status, detail);
|
|
54
|
+
}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ClientOptions, ReportInput } from './types';
|
|
2
|
+
export declare function validateUserId(userId: string): void;
|
|
3
|
+
export declare function reportForm(input: ReportInput): FormData;
|
|
4
|
+
export declare class Http {
|
|
5
|
+
private readonly baseUrl;
|
|
6
|
+
private readonly timeoutMs;
|
|
7
|
+
private readonly fetcher;
|
|
8
|
+
private readonly headers;
|
|
9
|
+
constructor(authorization: string, options?: ClientOptions);
|
|
10
|
+
request<T>(path: string, method?: string, body?: FormData | object, actingUserId?: string): Promise<T>;
|
|
11
|
+
}
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Http = void 0;
|
|
4
|
+
exports.validateUserId = validateUserId;
|
|
5
|
+
exports.reportForm = reportForm;
|
|
6
|
+
const errors_1 = require("./errors");
|
|
7
|
+
function validateUserId(userId) {
|
|
8
|
+
if (typeof userId !== 'string' || !/^\d{17,20}$/.test(userId)) {
|
|
9
|
+
throw new TypeError('Expected a Discord snowflake with 17 to 20 digits');
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function reportForm(input) {
|
|
13
|
+
input.userIds.forEach(validateUserId);
|
|
14
|
+
const form = new FormData();
|
|
15
|
+
form.append('reason', input.reason);
|
|
16
|
+
for (const userId of input.userIds) {
|
|
17
|
+
form.append('discord_ids', userId);
|
|
18
|
+
}
|
|
19
|
+
for (const evidence of input.evidence) {
|
|
20
|
+
const data = evidence.data instanceof Uint8Array
|
|
21
|
+
? new Uint8Array(evidence.data).buffer : evidence.data;
|
|
22
|
+
const blob = data instanceof Blob && evidence.type === undefined
|
|
23
|
+
? data : new Blob([data], { type: evidence.type });
|
|
24
|
+
form.append('files', blob, evidence.name);
|
|
25
|
+
}
|
|
26
|
+
return form;
|
|
27
|
+
}
|
|
28
|
+
class Http {
|
|
29
|
+
baseUrl;
|
|
30
|
+
timeoutMs;
|
|
31
|
+
fetcher;
|
|
32
|
+
headers;
|
|
33
|
+
constructor(authorization, options = {}) {
|
|
34
|
+
this.baseUrl = (options.baseUrl ?? 'https://api-openreport.chell.fr').replace(/\/+$/, '');
|
|
35
|
+
this.timeoutMs = options.timeoutMs ?? 10_000;
|
|
36
|
+
this.fetcher = options.fetch ?? globalThis.fetch;
|
|
37
|
+
this.headers = new Headers({ Authorization: authorization });
|
|
38
|
+
if (options.userAgent !== undefined) {
|
|
39
|
+
this.headers.set('User-Agent', options.userAgent);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async request(path, method = 'GET', body, actingUserId) {
|
|
43
|
+
const headers = new Headers(this.headers);
|
|
44
|
+
if (actingUserId !== undefined) {
|
|
45
|
+
validateUserId(actingUserId);
|
|
46
|
+
headers.set('X-OpenReport-User', actingUserId);
|
|
47
|
+
}
|
|
48
|
+
const multipart = body instanceof FormData;
|
|
49
|
+
if (body !== undefined && !multipart) {
|
|
50
|
+
headers.set('Content-Type', 'application/json');
|
|
51
|
+
}
|
|
52
|
+
const response = await this.fetcher(`${this.baseUrl}${path}`, {
|
|
53
|
+
method,
|
|
54
|
+
headers,
|
|
55
|
+
body: multipart ? body : body === undefined ? undefined : JSON.stringify(body),
|
|
56
|
+
signal: AbortSignal.timeout(this.timeoutMs),
|
|
57
|
+
});
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
const text = await response.text();
|
|
60
|
+
let detail = text;
|
|
61
|
+
try {
|
|
62
|
+
detail = JSON.parse(text);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Gateways can return plain text instead of the API's JSON errors.
|
|
66
|
+
}
|
|
67
|
+
throw (0, errors_1.responseError)(response, detail);
|
|
68
|
+
}
|
|
69
|
+
return await response.json();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
exports.Http = Http;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.RateLimited = exports.NotFound = exports.Unauthorized = exports.OpenReportError = exports.OpenReportBot = exports.OpenReport = void 0;
|
|
18
|
+
var client_1 = require("./client");
|
|
19
|
+
Object.defineProperty(exports, "OpenReport", { enumerable: true, get: function () { return client_1.OpenReport; } });
|
|
20
|
+
var bot_client_1 = require("./bot-client");
|
|
21
|
+
Object.defineProperty(exports, "OpenReportBot", { enumerable: true, get: function () { return bot_client_1.OpenReportBot; } });
|
|
22
|
+
var errors_1 = require("./errors");
|
|
23
|
+
Object.defineProperty(exports, "OpenReportError", { enumerable: true, get: function () { return errors_1.OpenReportError; } });
|
|
24
|
+
Object.defineProperty(exports, "Unauthorized", { enumerable: true, get: function () { return errors_1.Unauthorized; } });
|
|
25
|
+
Object.defineProperty(exports, "NotFound", { enumerable: true, get: function () { return errors_1.NotFound; } });
|
|
26
|
+
Object.defineProperty(exports, "RateLimited", { enumerable: true, get: function () { return errors_1.RateLimited; } });
|
|
27
|
+
__exportStar(require("./types"), exports);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { CaseDetail, CaseSummary, SelfRecord, Status, Tally } from './types';
|
|
2
|
+
export interface WireStatus {
|
|
3
|
+
blacklisted: boolean;
|
|
4
|
+
blacklistlevel?: Status['level'];
|
|
5
|
+
blacklistdate?: number | null;
|
|
6
|
+
staff_role?: Status['staffRole'];
|
|
7
|
+
}
|
|
8
|
+
export declare function status(userId: string, wire: WireStatus): Status;
|
|
9
|
+
export interface WireTally {
|
|
10
|
+
jury_size: number;
|
|
11
|
+
cast: number;
|
|
12
|
+
approvals: number;
|
|
13
|
+
refusals: number;
|
|
14
|
+
quorum_needed: number;
|
|
15
|
+
quorum_reached: boolean;
|
|
16
|
+
approval_ratio: number;
|
|
17
|
+
admin_required: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function tally(wire: WireTally): Tally;
|
|
20
|
+
export interface WireCase {
|
|
21
|
+
id: string;
|
|
22
|
+
reference: string;
|
|
23
|
+
kind: CaseSummary['kind'];
|
|
24
|
+
status: CaseSummary['status'];
|
|
25
|
+
opened_at: string;
|
|
26
|
+
expires_at: string | null;
|
|
27
|
+
subject_discord_id: string;
|
|
28
|
+
subject_display: string;
|
|
29
|
+
danger_level: CaseSummary['level'];
|
|
30
|
+
tally: WireTally;
|
|
31
|
+
}
|
|
32
|
+
export declare function caseSummary(wire: WireCase): CaseSummary;
|
|
33
|
+
export interface WireCaseDetail extends WireCase {
|
|
34
|
+
reason: string | null;
|
|
35
|
+
evidence_count: number;
|
|
36
|
+
my_vote: boolean | null;
|
|
37
|
+
}
|
|
38
|
+
export declare function caseDetail(wire: WireCaseDetail): CaseDetail;
|
|
39
|
+
export interface WireRecord {
|
|
40
|
+
discord_id: string;
|
|
41
|
+
blacklisted: boolean;
|
|
42
|
+
level: SelfRecord['level'];
|
|
43
|
+
blacklisted_at: string | null;
|
|
44
|
+
expires_at: string | null;
|
|
45
|
+
grounds_count: number;
|
|
46
|
+
open_cases: number;
|
|
47
|
+
can_appeal: boolean;
|
|
48
|
+
appeal_status: SelfRecord['appealStatus'];
|
|
49
|
+
informed_at: string | null;
|
|
50
|
+
}
|
|
51
|
+
export declare function record(wire: WireRecord): SelfRecord;
|
package/dist/mapping.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.status = status;
|
|
4
|
+
exports.tally = tally;
|
|
5
|
+
exports.caseSummary = caseSummary;
|
|
6
|
+
exports.caseDetail = caseDetail;
|
|
7
|
+
exports.record = record;
|
|
8
|
+
function status(userId, wire) {
|
|
9
|
+
return {
|
|
10
|
+
userId,
|
|
11
|
+
blacklisted: wire.blacklisted,
|
|
12
|
+
level: wire.blacklistlevel ?? null,
|
|
13
|
+
since: wire.blacklistdate == null ? null : new Date(wire.blacklistdate * 1000),
|
|
14
|
+
staffRole: wire.staff_role ?? null,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function tally(wire) {
|
|
18
|
+
return {
|
|
19
|
+
jurySize: wire.jury_size,
|
|
20
|
+
cast: wire.cast,
|
|
21
|
+
approvals: wire.approvals,
|
|
22
|
+
refusals: wire.refusals,
|
|
23
|
+
quorumNeeded: wire.quorum_needed,
|
|
24
|
+
quorumReached: wire.quorum_reached,
|
|
25
|
+
approvalRatio: wire.approval_ratio,
|
|
26
|
+
adminRequired: wire.admin_required,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function caseSummary(wire) {
|
|
30
|
+
return {
|
|
31
|
+
id: wire.id,
|
|
32
|
+
reference: wire.reference,
|
|
33
|
+
kind: wire.kind,
|
|
34
|
+
status: wire.status,
|
|
35
|
+
openedAt: new Date(wire.opened_at),
|
|
36
|
+
expiresAt: wire.expires_at === null ? null : new Date(wire.expires_at),
|
|
37
|
+
subjectId: wire.subject_discord_id,
|
|
38
|
+
subjectDisplay: wire.subject_display,
|
|
39
|
+
level: wire.danger_level,
|
|
40
|
+
tally: tally(wire.tally),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function caseDetail(wire) {
|
|
44
|
+
return {
|
|
45
|
+
...caseSummary(wire),
|
|
46
|
+
reason: wire.reason,
|
|
47
|
+
evidenceCount: wire.evidence_count,
|
|
48
|
+
myVote: wire.my_vote,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function record(wire) {
|
|
52
|
+
return {
|
|
53
|
+
userId: wire.discord_id,
|
|
54
|
+
blacklisted: wire.blacklisted,
|
|
55
|
+
level: wire.level,
|
|
56
|
+
blacklistedAt: wire.blacklisted_at === null ? null : new Date(wire.blacklisted_at),
|
|
57
|
+
expiresAt: wire.expires_at === null ? null : new Date(wire.expires_at),
|
|
58
|
+
groundsCount: wire.grounds_count,
|
|
59
|
+
openCases: wire.open_cases,
|
|
60
|
+
canAppeal: wire.can_appeal,
|
|
61
|
+
appealStatus: wire.appeal_status,
|
|
62
|
+
informedAt: wire.informed_at === null ? null : new Date(wire.informed_at),
|
|
63
|
+
};
|
|
64
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export type Level = 'low' | 'medium' | 'high';
|
|
2
|
+
export declare const LEVELS: readonly Level[];
|
|
3
|
+
export declare function levelAtLeast(level: Level, minimum: Level): boolean;
|
|
4
|
+
export type StaffRole = 'jury' | 'admin';
|
|
5
|
+
export interface Status {
|
|
6
|
+
userId: string;
|
|
7
|
+
blacklisted: boolean;
|
|
8
|
+
level: Level | null;
|
|
9
|
+
since: Date | null;
|
|
10
|
+
staffRole: StaffRole | null;
|
|
11
|
+
}
|
|
12
|
+
export interface Evidence {
|
|
13
|
+
name: string;
|
|
14
|
+
data: Uint8Array | ArrayBuffer | Blob;
|
|
15
|
+
type?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ReportInput {
|
|
18
|
+
userIds: string[];
|
|
19
|
+
reason: string;
|
|
20
|
+
evidence: Evidence[];
|
|
21
|
+
}
|
|
22
|
+
export interface ReportReceipt {
|
|
23
|
+
reference: string;
|
|
24
|
+
targets: number;
|
|
25
|
+
message: string;
|
|
26
|
+
}
|
|
27
|
+
export interface ClientOptions {
|
|
28
|
+
baseUrl?: string;
|
|
29
|
+
timeoutMs?: number;
|
|
30
|
+
fetch?: typeof fetch;
|
|
31
|
+
userAgent?: string;
|
|
32
|
+
}
|
|
33
|
+
export type CaseKind = 'blacklist' | 'unblacklist';
|
|
34
|
+
export type CaseStatus = 'open' | 'accepted' | 'rejected' | 'escalated' | 'expired' | 'withdrawn' | 'ignored';
|
|
35
|
+
export interface Tally {
|
|
36
|
+
jurySize: number;
|
|
37
|
+
cast: number;
|
|
38
|
+
approvals: number;
|
|
39
|
+
refusals: number;
|
|
40
|
+
quorumNeeded: number;
|
|
41
|
+
quorumReached: boolean;
|
|
42
|
+
approvalRatio: number;
|
|
43
|
+
adminRequired: boolean;
|
|
44
|
+
}
|
|
45
|
+
export interface CaseSummary {
|
|
46
|
+
id: string;
|
|
47
|
+
reference: string;
|
|
48
|
+
kind: CaseKind;
|
|
49
|
+
status: CaseStatus;
|
|
50
|
+
openedAt: Date;
|
|
51
|
+
expiresAt: Date | null;
|
|
52
|
+
subjectId: string;
|
|
53
|
+
subjectDisplay: string;
|
|
54
|
+
level: Level | null;
|
|
55
|
+
tally: Tally;
|
|
56
|
+
}
|
|
57
|
+
export interface CaseDetail extends CaseSummary {
|
|
58
|
+
reason: string | null;
|
|
59
|
+
evidenceCount: number;
|
|
60
|
+
myVote: boolean | null;
|
|
61
|
+
}
|
|
62
|
+
export interface VoteInput {
|
|
63
|
+
approve: boolean;
|
|
64
|
+
level?: Level;
|
|
65
|
+
note?: string;
|
|
66
|
+
}
|
|
67
|
+
export interface SelfRecord {
|
|
68
|
+
userId: string;
|
|
69
|
+
blacklisted: boolean;
|
|
70
|
+
level: Level | null;
|
|
71
|
+
blacklistedAt: Date | null;
|
|
72
|
+
expiresAt: Date | null;
|
|
73
|
+
groundsCount: number;
|
|
74
|
+
openCases: number;
|
|
75
|
+
canAppeal: boolean;
|
|
76
|
+
appealStatus: 'open' | 'accepted' | 'rejected' | null;
|
|
77
|
+
informedAt: Date | null;
|
|
78
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LEVELS = void 0;
|
|
4
|
+
exports.levelAtLeast = levelAtLeast;
|
|
5
|
+
exports.LEVELS = Object.freeze(['low', 'medium', 'high']);
|
|
6
|
+
function levelAtLeast(level, minimum) {
|
|
7
|
+
return exports.LEVELS.indexOf(level) >= exports.LEVELS.indexOf(minimum);
|
|
8
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
"name": "openreport-sdk",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "OpenReport application and official bot clients",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": ["dist", "README.md"],
|
|
9
|
+
"engines": { "node": ">=20" },
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"test": "node --import tsx --test test/*.test.ts",
|
|
13
|
+
"prepublishOnly": "npm run build && npm test"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@types/node": "^22.0.0",
|
|
17
|
+
"tsx": "^4.0.0",
|
|
18
|
+
"typescript": "^5.0.0"
|
|
19
|
+
}
|
|
20
|
+
}
|