@bozonx/social-posting-discord 0.8.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/LICENSE +21 -0
- package/README.md +128 -0
- package/dist/capabilities.d.ts +33 -0
- package/dist/capabilities.js +211 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/discord-api.d.ts +59 -0
- package/dist/discord-api.js +81 -0
- package/dist/discord-api.js.map +1 -0
- package/dist/discord-auth.validator.d.ts +31 -0
- package/dist/discord-auth.validator.js +76 -0
- package/dist/discord-auth.validator.js.map +1 -0
- package/dist/discord-error.d.ts +25 -0
- package/dist/discord-error.js +78 -0
- package/dist/discord-error.js.map +1 -0
- package/dist/discord.platform.d.ts +80 -0
- package/dist/discord.platform.js +413 -0
- package/dist/discord.platform.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discord-auth.validator.js","sourceRoot":"","sources":["../src/discord-auth.validator.ts"],"names":[],"mappings":"AAEA,4DAA4D;AAC5D,MAAM,mBAAmB,GACvB,wGAAwG,CAAC;AAE3G,2EAA2E;AAC3E,MAAM,iBAAiB,GAAG,qCAAqC,CAAC;AAWhE;;;;;;;;GAQG;AACH,MAAM,OAAO,oBAAoB;IACtB,YAAY,GAAG,SAAS,CAAC;IAElC,QAAQ,CAAC,IAA6B;QACpC,OAAO,mBAAmB,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;CACF;AAED,+EAA+E;AAC/E,MAAM,UAAU,mBAAmB,CAAC,IAA6B;IAC/D,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,IAAoD,CAAC;IAEtF,MAAM,UAAU,GAAG,UAAU,KAAK,SAAS,IAAI,UAAU,KAAK,IAAI,IAAI,UAAU,KAAK,EAAE,CAAC;IACxF,MAAM,MAAM,GAAG,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,KAAK,EAAE,CAAC;IAE9E,IAAI,CAAC,UAAU,IAAI,CAAC,MAAM,EAAE,CAAC;QAC3B,MAAM,CAAC,IAAI,CAAC,yDAAyD,CAAC,CAAC;QACvE,OAAO,EAAE,MAAM,EAAE,CAAC;IACpB,CAAC;IAED,IAAI,UAAU,IAAI,MAAM,EAAE,CAAC;QACzB,0EAA0E;QAC1E,wEAAwE;QACxE,iBAAiB;QACjB,MAAM,CAAC,IAAI,CACT,gHAAgH,CACjH,CAAC;QACF,OAAO,EAAE,MAAM,EAAE,CAAC;IACpB,CAAC;IAED,IAAI,UAAU,EAAE,CAAC;QACf,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;YACnC,MAAM,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAC;QACrD,CAAC;aAAM,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,EAAE,CAAC;YACxC,MAAM,CAAC,IAAI,CACT,iGAAiG,CAClG,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,CAAC;IACpB,CAAC;IAED,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACjC,MAAM,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;IACnD,CAAC;SAAM,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7C,MAAM,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAC;IACrD,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,CAAC;AACpB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IACnD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC,CAAW,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAW,EAAE,CAAC;AAC/D,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,UAAU,CAAC,IAA6B;IACtD,IAAI,OAAO,IAAI,CAAC,UAAU,KAAK,QAAQ,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtE,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClE,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { PlatformError } from '@bozonx/social-posting';
|
|
2
|
+
/** The JSON body Discord returns on a failure. */
|
|
3
|
+
export interface DiscordErrorBody {
|
|
4
|
+
/** Discord's own error code, distinct from the HTTP status. */
|
|
5
|
+
code?: number;
|
|
6
|
+
message?: string;
|
|
7
|
+
/** Present on 429; seconds, fractional. */
|
|
8
|
+
retry_after?: number;
|
|
9
|
+
global?: boolean;
|
|
10
|
+
errors?: unknown;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Translate a Discord failure into the library's error contract.
|
|
14
|
+
*
|
|
15
|
+
* The only place Discord's vocabulary is read. The distinction that matters to
|
|
16
|
+
* a host is between a token it should re-authorize, a permission a human must
|
|
17
|
+
* grant in the server, and content Discord will never accept — all three are
|
|
18
|
+
* 4xx, and treating them alike fills a queue with jobs that can never drain.
|
|
19
|
+
*
|
|
20
|
+
* @param status - HTTP status of the failed response.
|
|
21
|
+
* @param body - Parsed JSON error body, when there was one.
|
|
22
|
+
* @param retryAfterHeader - The `retry-after` header, when present.
|
|
23
|
+
* @returns The classified error to throw on.
|
|
24
|
+
*/
|
|
25
|
+
export declare function toPlatformError(status: number, body: DiscordErrorBody | undefined, retryAfterHeader?: string | null): PlatformError;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { ErrorCode, PlatformError } from '@bozonx/social-posting';
|
|
2
|
+
/**
|
|
3
|
+
* Discord JSON error codes that mean the content itself was refused, rather
|
|
4
|
+
* than the request being malformed.
|
|
5
|
+
*/
|
|
6
|
+
const CONTENT_REJECTED_CODES = new Set([
|
|
7
|
+
40005, // Request entity too large
|
|
8
|
+
50006, // Cannot send an empty message
|
|
9
|
+
50035, // Invalid form body — includes attachment and embed validation
|
|
10
|
+
50045, // File uploaded exceeds the maximum size
|
|
11
|
+
50046, // Invalid file uploaded
|
|
12
|
+
]);
|
|
13
|
+
/** Discord JSON error codes that mean the bot lacks rights, not credentials. */
|
|
14
|
+
const PERMISSION_CODES = new Set([
|
|
15
|
+
50001, // Missing access
|
|
16
|
+
50013, // Missing permissions
|
|
17
|
+
]);
|
|
18
|
+
/**
|
|
19
|
+
* Translate a Discord failure into the library's error contract.
|
|
20
|
+
*
|
|
21
|
+
* The only place Discord's vocabulary is read. The distinction that matters to
|
|
22
|
+
* a host is between a token it should re-authorize, a permission a human must
|
|
23
|
+
* grant in the server, and content Discord will never accept — all three are
|
|
24
|
+
* 4xx, and treating them alike fills a queue with jobs that can never drain.
|
|
25
|
+
*
|
|
26
|
+
* @param status - HTTP status of the failed response.
|
|
27
|
+
* @param body - Parsed JSON error body, when there was one.
|
|
28
|
+
* @param retryAfterHeader - The `retry-after` header, when present.
|
|
29
|
+
* @returns The classified error to throw on.
|
|
30
|
+
*/
|
|
31
|
+
export function toPlatformError(status, body, retryAfterHeader) {
|
|
32
|
+
const message = body?.message ?? `Discord responded with ${status}`;
|
|
33
|
+
const platformCode = body?.code === undefined ? String(status) : String(body.code);
|
|
34
|
+
const shared = {
|
|
35
|
+
httpStatus: status,
|
|
36
|
+
platformCode,
|
|
37
|
+
raw: { code: body?.code, message: body?.message, errors: body?.errors },
|
|
38
|
+
};
|
|
39
|
+
if (status === 429) {
|
|
40
|
+
// Discord states the cool-down in fractional seconds, in the body and in
|
|
41
|
+
// the header. The body is authoritative when both are present.
|
|
42
|
+
const fromBody = typeof body?.retry_after === 'number' ? body.retry_after : undefined;
|
|
43
|
+
const fromHeader = retryAfterHeader === null ? undefined : Number(retryAfterHeader);
|
|
44
|
+
const seconds = fromBody ?? (Number.isFinite(fromHeader) ? fromHeader : undefined);
|
|
45
|
+
return new PlatformError(message, ErrorCode.RATE_LIMIT_ERROR, {
|
|
46
|
+
...shared,
|
|
47
|
+
retryable: true,
|
|
48
|
+
retryAfterMs: seconds === undefined ? undefined : Math.ceil(seconds * 1000),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (status === 401) {
|
|
52
|
+
// Bot tokens and webhook tokens are static: a rejected one is wrong, not
|
|
53
|
+
// stale, so re-running an OAuth refresh would achieve nothing.
|
|
54
|
+
return new PlatformError(message, ErrorCode.AUTH_ERROR, { ...shared, retryable: false });
|
|
55
|
+
}
|
|
56
|
+
if (status === 403) {
|
|
57
|
+
return new PlatformError(PERMISSION_CODES.has(body?.code ?? -1)
|
|
58
|
+
? `${message} (the bot lacks permission in this channel)`
|
|
59
|
+
: message, ErrorCode.AUTH_ERROR, { ...shared, retryable: false });
|
|
60
|
+
}
|
|
61
|
+
if (status === 404) {
|
|
62
|
+
// An unknown channel or a deleted webhook: the address is wrong, and no
|
|
63
|
+
// amount of retrying makes it right.
|
|
64
|
+
return new PlatformError(message, ErrorCode.VALIDATION_ERROR, { ...shared, retryable: false });
|
|
65
|
+
}
|
|
66
|
+
if (status === 413) {
|
|
67
|
+
return new PlatformError(message, ErrorCode.CONTENT_REJECTED, { ...shared, retryable: false });
|
|
68
|
+
}
|
|
69
|
+
if (status >= 400 && status < 500) {
|
|
70
|
+
const rejected = CONTENT_REJECTED_CODES.has(body?.code ?? -1);
|
|
71
|
+
return new PlatformError(message, rejected ? ErrorCode.CONTENT_REJECTED : ErrorCode.VALIDATION_ERROR, { ...shared, retryable: false });
|
|
72
|
+
}
|
|
73
|
+
if (status >= 500) {
|
|
74
|
+
return new PlatformError(message, ErrorCode.PLATFORM_ERROR, { ...shared, retryable: true });
|
|
75
|
+
}
|
|
76
|
+
return new PlatformError(message, ErrorCode.NETWORK_ERROR, { ...shared, retryable: true });
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=discord-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discord-error.js","sourceRoot":"","sources":["../src/discord-error.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { PostType } from '@bozonx/social-posting';
|
|
2
|
+
import type { AccountConfig, ILogger, Issue, PlatformTarget, PostRef, PostRequest } from '@bozonx/social-posting';
|
|
3
|
+
import type { DeleteOptions, DeleteOutcome, IPlatform, PlatformPublishResponse, PublishOptions, RuntimeCapabilities } from '@bozonx/social-posting/platform';
|
|
4
|
+
/** Collaborators this platform needs, passed explicitly. */
|
|
5
|
+
export interface DiscordPlatformDeps {
|
|
6
|
+
logger: ILogger;
|
|
7
|
+
fetch?: typeof fetch;
|
|
8
|
+
}
|
|
9
|
+
/** Account configuration understood by the Discord platform. */
|
|
10
|
+
export interface DiscordAccountConfig extends AccountConfig {
|
|
11
|
+
auth: AccountConfig['auth'] & {
|
|
12
|
+
/** Webhook URL: credential and destination in one secret. */
|
|
13
|
+
webhookUrl?: string;
|
|
14
|
+
/** Bot token, for an account that posts as a bot. */
|
|
15
|
+
botToken?: string;
|
|
16
|
+
};
|
|
17
|
+
/** Default channel, already normalized by the core. */
|
|
18
|
+
target?: PlatformTarget;
|
|
19
|
+
/** API request timeout in seconds. */
|
|
20
|
+
apiTimeoutSeconds?: number;
|
|
21
|
+
}
|
|
22
|
+
/** Platform-specific options a caller may pass in `request.extra`. */
|
|
23
|
+
export interface DiscordExtra {
|
|
24
|
+
embeds?: unknown[];
|
|
25
|
+
tts?: boolean;
|
|
26
|
+
flags?: number;
|
|
27
|
+
allowed_mentions?: Record<string, unknown>;
|
|
28
|
+
components?: unknown[];
|
|
29
|
+
/** Webhook only. */
|
|
30
|
+
username?: string;
|
|
31
|
+
/** Webhook only. */
|
|
32
|
+
avatar_url?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Discord, over both of its access models.
|
|
36
|
+
*
|
|
37
|
+
* A webhook URL and a bot token are not two ways of saying the same thing: a
|
|
38
|
+
* webhook cannot reply to a message and posts under its own name, while a bot
|
|
39
|
+
* needs channel permissions and can delete what it wrote. The adapter picks by
|
|
40
|
+
* what the account carries and refuses what the chosen model cannot do, rather
|
|
41
|
+
* than pretending the difference away.
|
|
42
|
+
*/
|
|
43
|
+
export declare class DiscordPlatform implements IPlatform {
|
|
44
|
+
readonly name = "discord";
|
|
45
|
+
readonly capabilities: import("@bozonx/social-posting").PlatformCapabilities;
|
|
46
|
+
private readonly logger;
|
|
47
|
+
private readonly fetch?;
|
|
48
|
+
private readonly media;
|
|
49
|
+
constructor(deps: DiscordPlatformDeps);
|
|
50
|
+
publish(request: PostRequest, accountConfig: DiscordAccountConfig, options?: PublishOptions): Promise<PlatformPublishResponse>;
|
|
51
|
+
/**
|
|
52
|
+
* Delete a message this account published.
|
|
53
|
+
*
|
|
54
|
+
* Both models can delete their own messages, by different routes: a bot by
|
|
55
|
+
* channel and message id, a webhook through its own token.
|
|
56
|
+
*/
|
|
57
|
+
delete(ref: PostRef, accountConfig: DiscordAccountConfig, options?: DeleteOptions): Promise<DeleteOutcome>;
|
|
58
|
+
/**
|
|
59
|
+
* What this server actually allows, asked at publish time.
|
|
60
|
+
*
|
|
61
|
+
* The attachment ceiling is a property of the guild's boost tier, not of
|
|
62
|
+
* Discord: the shipped descriptor states the unboosted floor, and a boosted
|
|
63
|
+
* server would otherwise have uploads refused locally that it would have
|
|
64
|
+
* accepted. Only a bot token can ask — a webhook cannot read the guild — so
|
|
65
|
+
* a webhook account keeps the floor.
|
|
66
|
+
*/
|
|
67
|
+
resolveCapabilities(accountConfig: DiscordAccountConfig, signal?: AbortSignal): Promise<RuntimeCapabilities>;
|
|
68
|
+
/** Rules the capability descriptor cannot express. */
|
|
69
|
+
validateExtra(request: PostRequest, accountConfig: DiscordAccountConfig, type: PostType): Issue[];
|
|
70
|
+
private validationHooks;
|
|
71
|
+
private apiFor;
|
|
72
|
+
/** Where the message goes, which differs entirely between the two models. */
|
|
73
|
+
private publishUrl;
|
|
74
|
+
private buildPayload;
|
|
75
|
+
/**
|
|
76
|
+
* Read every attachment into a blob, checking it against the declared limits
|
|
77
|
+
* first so an oversized file costs no download.
|
|
78
|
+
*/
|
|
79
|
+
private openAttachments;
|
|
80
|
+
}
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { ErrorCode, PlatformError, PostType, ValidationError } from '@bozonx/social-posting';
|
|
2
|
+
import { MediaFetcher, buildMultipartFormData, normalizeTarget, renderBody, resolveBodyTargetFormat, validateAgainstCapabilities, } from '@bozonx/social-posting/platform';
|
|
3
|
+
import { DiscordApi } from './discord-api.js';
|
|
4
|
+
import { authModeOf, parseWebhookUrl } from './discord-auth.validator.js';
|
|
5
|
+
import { ATTACHMENT_BYTES_BY_BOOST_TIER, DEFAULT_MAX_ATTACHMENT_BYTES, MAX_ATTACHMENTS, MAX_MESSAGE_LENGTH, discordCapabilities, } from './capabilities.js';
|
|
6
|
+
const LOG_CONTEXT = 'DiscordPlatform';
|
|
7
|
+
/** Discord's own poll default, in hours. */
|
|
8
|
+
const DEFAULT_POLL_DURATION_HOURS = 24;
|
|
9
|
+
const SECS_PER_HOUR = 3_600;
|
|
10
|
+
/**
|
|
11
|
+
* Discord, over both of its access models.
|
|
12
|
+
*
|
|
13
|
+
* A webhook URL and a bot token are not two ways of saying the same thing: a
|
|
14
|
+
* webhook cannot reply to a message and posts under its own name, while a bot
|
|
15
|
+
* needs channel permissions and can delete what it wrote. The adapter picks by
|
|
16
|
+
* what the account carries and refuses what the chosen model cannot do, rather
|
|
17
|
+
* than pretending the difference away.
|
|
18
|
+
*/
|
|
19
|
+
export class DiscordPlatform {
|
|
20
|
+
name = 'discord';
|
|
21
|
+
capabilities = discordCapabilities;
|
|
22
|
+
logger;
|
|
23
|
+
fetch;
|
|
24
|
+
media;
|
|
25
|
+
constructor(deps) {
|
|
26
|
+
this.logger = deps.logger;
|
|
27
|
+
this.fetch = deps.fetch;
|
|
28
|
+
this.media = new MediaFetcher();
|
|
29
|
+
}
|
|
30
|
+
async publish(request, accountConfig, options) {
|
|
31
|
+
const signal = options?.signal;
|
|
32
|
+
if (signal?.aborted) {
|
|
33
|
+
throw new PlatformError('Request aborted before publishing', ErrorCode.NETWORK_ERROR, {
|
|
34
|
+
retryable: false,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const capabilities = options?.capabilities ?? this.capabilities;
|
|
38
|
+
const { issues, warnings, detectedType } = validateAgainstCapabilities(request, capabilities, this.validationHooks(accountConfig, request));
|
|
39
|
+
if (issues.length > 0) {
|
|
40
|
+
throw new ValidationError(issues);
|
|
41
|
+
}
|
|
42
|
+
if (warnings.length > 0) {
|
|
43
|
+
this.logger.warn(`Warnings during publish (type: ${detectedType}): ${warnings.map(w => w.message).join('; ')}`, LOG_CONTEXT);
|
|
44
|
+
}
|
|
45
|
+
const mode = requireAuthMode(accountConfig);
|
|
46
|
+
const api = this.apiFor(accountConfig);
|
|
47
|
+
const payload = this.buildPayload(request, capabilities, mode);
|
|
48
|
+
const attachments = await this.openAttachments(request.media ?? [], capabilities, signal);
|
|
49
|
+
const url = this.publishUrl(api, request, accountConfig, mode);
|
|
50
|
+
const body = attachments.length > 0 ? toMultipart(payload, attachments) : payload;
|
|
51
|
+
this.logger.debug(`Sending to Discord via ${mode} (type: ${detectedType}, attachments: ${attachments.length})`, LOG_CONTEXT);
|
|
52
|
+
const message = await api.call({
|
|
53
|
+
url,
|
|
54
|
+
method: 'POST',
|
|
55
|
+
authorization: authorizationFor(accountConfig, mode),
|
|
56
|
+
body,
|
|
57
|
+
signal,
|
|
58
|
+
});
|
|
59
|
+
if (!message?.id) {
|
|
60
|
+
// A webhook executed without `wait=true` answers 204 and reveals nothing.
|
|
61
|
+
// We always ask for the message, so an empty body here means the call
|
|
62
|
+
// succeeded but the outcome cannot be addressed — which is not a post we
|
|
63
|
+
// can hand back a reference to.
|
|
64
|
+
throw new PlatformError('Discord accepted the message but returned no message object', ErrorCode.UNKNOWN_OUTCOME, { retryable: false });
|
|
65
|
+
}
|
|
66
|
+
const target = resolveTarget(request, accountConfig);
|
|
67
|
+
const guildId = message.guild_id ?? guildIdOf(target);
|
|
68
|
+
const postUrl = buildMessageUrl(guildId, message.channel_id, message.id);
|
|
69
|
+
const part = { id: message.id, url: postUrl };
|
|
70
|
+
return {
|
|
71
|
+
status: 'published',
|
|
72
|
+
postId: message.id,
|
|
73
|
+
url: postUrl,
|
|
74
|
+
parts: [part],
|
|
75
|
+
ref: {
|
|
76
|
+
postId: message.id,
|
|
77
|
+
parts: [part],
|
|
78
|
+
// The channel from the response, not from the request: a webhook knows
|
|
79
|
+
// its own channel and the request may never have named one.
|
|
80
|
+
target: { id: message.channel_id, ...(guildId ? { guildId } : {}) },
|
|
81
|
+
},
|
|
82
|
+
raw: message,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Delete a message this account published.
|
|
87
|
+
*
|
|
88
|
+
* Both models can delete their own messages, by different routes: a bot by
|
|
89
|
+
* channel and message id, a webhook through its own token.
|
|
90
|
+
*/
|
|
91
|
+
async delete(ref, accountConfig, options) {
|
|
92
|
+
const mode = requireAuthMode(accountConfig);
|
|
93
|
+
const api = this.apiFor(accountConfig);
|
|
94
|
+
const ids = ref.parts && ref.parts.length > 0
|
|
95
|
+
? ref.parts.map(part => part.id)
|
|
96
|
+
: ref.postId
|
|
97
|
+
? [ref.postId]
|
|
98
|
+
: [];
|
|
99
|
+
if (ids.length === 0) {
|
|
100
|
+
throw new ValidationError('PostRef must contain postId or parts to delete');
|
|
101
|
+
}
|
|
102
|
+
const channelId = mode === 'bot' ? (normalizeTarget(ref.target)?.id ?? accountConfig.target?.id) : undefined;
|
|
103
|
+
if (mode === 'bot' && !channelId) {
|
|
104
|
+
throw new ValidationError('Field "target" is required for deleting a Discord message');
|
|
105
|
+
}
|
|
106
|
+
const results = [];
|
|
107
|
+
for (const id of ids) {
|
|
108
|
+
const url = mode === 'bot'
|
|
109
|
+
? api.endpoint(`/channels/${encodeURIComponent(channelId)}/messages/${encodeURIComponent(id)}`)
|
|
110
|
+
: api.endpoint(`${webhookPath(accountConfig)}/messages/${encodeURIComponent(id)}`);
|
|
111
|
+
try {
|
|
112
|
+
await api.call({
|
|
113
|
+
url,
|
|
114
|
+
method: 'DELETE',
|
|
115
|
+
authorization: authorizationFor(accountConfig, mode),
|
|
116
|
+
signal: options?.signal,
|
|
117
|
+
});
|
|
118
|
+
results.push({ id, status: 'deleted' });
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
const platformError = error instanceof PlatformError
|
|
122
|
+
? error
|
|
123
|
+
: new PlatformError(String(error), ErrorCode.PLATFORM_ERROR, { retryable: true });
|
|
124
|
+
if (platformError.httpStatus === 404) {
|
|
125
|
+
results.push({ id, status: 'alreadyGone' });
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
results.push({
|
|
129
|
+
id,
|
|
130
|
+
status: 'failed',
|
|
131
|
+
error: {
|
|
132
|
+
code: platformError.code,
|
|
133
|
+
message: platformError.message,
|
|
134
|
+
retryable: platformError.retryable,
|
|
135
|
+
retryAfterMs: platformError.retryAfterMs,
|
|
136
|
+
httpStatus: platformError.httpStatus,
|
|
137
|
+
platformCode: platformError.platformCode,
|
|
138
|
+
requestId: crypto.randomUUID(),
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const settled = results.every(r => r.status === 'deleted' || r.status === 'alreadyGone');
|
|
144
|
+
return { status: settled ? 'deleted' : 'partial', parts: results };
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* What this server actually allows, asked at publish time.
|
|
148
|
+
*
|
|
149
|
+
* The attachment ceiling is a property of the guild's boost tier, not of
|
|
150
|
+
* Discord: the shipped descriptor states the unboosted floor, and a boosted
|
|
151
|
+
* server would otherwise have uploads refused locally that it would have
|
|
152
|
+
* accepted. Only a bot token can ask — a webhook cannot read the guild — so
|
|
153
|
+
* a webhook account keeps the floor.
|
|
154
|
+
*/
|
|
155
|
+
async resolveCapabilities(accountConfig, signal) {
|
|
156
|
+
const fetchedAt = new Date().toISOString();
|
|
157
|
+
const guildId = guildIdOf(accountConfig.target);
|
|
158
|
+
if (authModeOf(accountConfig.auth) !== 'bot' || !guildId) {
|
|
159
|
+
return { capabilities: {}, cacheableForSecs: 3600, fetchedAt };
|
|
160
|
+
}
|
|
161
|
+
const api = this.apiFor(accountConfig);
|
|
162
|
+
const guild = await api.call({
|
|
163
|
+
url: api.endpoint(`/guilds/${encodeURIComponent(guildId)}`),
|
|
164
|
+
method: 'GET',
|
|
165
|
+
authorization: authorizationFor(accountConfig, 'bot'),
|
|
166
|
+
signal,
|
|
167
|
+
});
|
|
168
|
+
const tier = guild?.premium_tier ?? 0;
|
|
169
|
+
const maxBytes = ATTACHMENT_BYTES_BY_BOOST_TIER[tier] ?? DEFAULT_MAX_ATTACHMENT_BYTES;
|
|
170
|
+
const media = {};
|
|
171
|
+
for (const kind of Object.keys(this.capabilities.media ?? {})) {
|
|
172
|
+
media[kind] = { maxBytes };
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
// A boost tier changes when a server gains or loses boosts, which is
|
|
176
|
+
// slow: an hour of caching costs nothing and saves a call per publish.
|
|
177
|
+
capabilities: { media },
|
|
178
|
+
cacheableForSecs: 3600,
|
|
179
|
+
fetchedAt,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/** Rules the capability descriptor cannot express. */
|
|
183
|
+
validateExtra(request, accountConfig, type) {
|
|
184
|
+
const issues = [];
|
|
185
|
+
const mode = authModeOf(accountConfig.auth);
|
|
186
|
+
if (!mode) {
|
|
187
|
+
issues.push({
|
|
188
|
+
code: 'AUTH_MODE_MISSING',
|
|
189
|
+
field: 'auth',
|
|
190
|
+
message: "Discord auth requires either 'webhookUrl' or 'botToken'",
|
|
191
|
+
});
|
|
192
|
+
return issues;
|
|
193
|
+
}
|
|
194
|
+
if (mode === 'bot' && !resolveTarget(request, accountConfig)) {
|
|
195
|
+
issues.push({
|
|
196
|
+
code: 'TARGET_REQUIRED',
|
|
197
|
+
field: 'target',
|
|
198
|
+
message: 'Field "target" is required for a Discord bot account (provide the channel id via request.target or account config target)',
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
if (mode === 'webhook' && request.inReplyTo?.id) {
|
|
202
|
+
issues.push({
|
|
203
|
+
code: 'REPLY_UNSUPPORTED_FOR_WEBHOOK',
|
|
204
|
+
field: 'inReplyTo',
|
|
205
|
+
message: 'A Discord webhook cannot reply to a message; use a bot token account for replies',
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
const extra = (request.extra ?? {});
|
|
209
|
+
if (mode === 'bot') {
|
|
210
|
+
const webhookOnly = ['username', 'avatar_url'].filter(key => extra[key] !== undefined);
|
|
211
|
+
if (webhookOnly.length > 0) {
|
|
212
|
+
issues.push({
|
|
213
|
+
code: 'WEBHOOK_ONLY_EXTRA_FIELD',
|
|
214
|
+
field: 'extra',
|
|
215
|
+
message: `Option(s) only a webhook accepts: ${webhookOnly.join(', ')}`,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const protectedFields = ['content', 'attachments', 'files', 'poll', 'message_reference'];
|
|
220
|
+
const forbidden = Object.keys(extra).filter(key => protectedFields.includes(key));
|
|
221
|
+
if (forbidden.length > 0) {
|
|
222
|
+
issues.push({
|
|
223
|
+
code: 'PROTECTED_EXTRA_FIELD',
|
|
224
|
+
field: 'extra',
|
|
225
|
+
message: `Cannot pass protected Discord option(s) in extra: ${forbidden.join(', ')}`,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
if ((request.media?.length ?? 0) > MAX_ATTACHMENTS) {
|
|
229
|
+
issues.push({
|
|
230
|
+
code: 'TOO_MANY_ATTACHMENTS',
|
|
231
|
+
field: 'media',
|
|
232
|
+
message: `Discord accepts at most ${MAX_ATTACHMENTS} attachments per message`,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
if (type === PostType.POLL && request.poll) {
|
|
236
|
+
const seconds = request.poll.durationSecs;
|
|
237
|
+
if (seconds !== undefined && seconds % SECS_PER_HOUR !== 0) {
|
|
238
|
+
issues.push({
|
|
239
|
+
code: 'POLL_DURATION_NOT_WHOLE_HOURS',
|
|
240
|
+
field: 'poll',
|
|
241
|
+
message: 'Discord poll durations are whole hours; durationSecs must be a multiple of 3600',
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
if (request.poll.anonymous) {
|
|
245
|
+
issues.push({
|
|
246
|
+
code: 'POLL_ANONYMOUS_UNSUPPORTED',
|
|
247
|
+
field: 'poll',
|
|
248
|
+
message: 'Discord polls always show who voted; anonymous polls are unsupported',
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return issues;
|
|
253
|
+
}
|
|
254
|
+
validationHooks(accountConfig, request) {
|
|
255
|
+
return {
|
|
256
|
+
target: request ? resolveTarget(request, accountConfig) : accountConfig.target,
|
|
257
|
+
validateExtra: (candidate, type) => this.validateExtra(candidate, accountConfig, type),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
apiFor(accountConfig) {
|
|
261
|
+
return new DiscordApi({
|
|
262
|
+
baseUrl: accountConfig.apiBaseUrl,
|
|
263
|
+
timeoutSeconds: accountConfig.apiTimeoutSeconds,
|
|
264
|
+
fetch: this.fetch,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
/** Where the message goes, which differs entirely between the two models. */
|
|
268
|
+
publishUrl(api, request, accountConfig, mode) {
|
|
269
|
+
const target = resolveTarget(request, accountConfig);
|
|
270
|
+
const threadId = stringField(target, 'threadId');
|
|
271
|
+
if (mode === 'webhook') {
|
|
272
|
+
const url = new URL(api.endpoint(webhookPath(accountConfig)));
|
|
273
|
+
// Without `wait`, Discord answers 204 and we never learn the message id —
|
|
274
|
+
// which would leave the host with a publication it cannot address.
|
|
275
|
+
url.searchParams.set('wait', 'true');
|
|
276
|
+
if (threadId) {
|
|
277
|
+
url.searchParams.set('thread_id', threadId);
|
|
278
|
+
}
|
|
279
|
+
return url.toString();
|
|
280
|
+
}
|
|
281
|
+
// A thread is a channel: posting into one means addressing it directly.
|
|
282
|
+
const channelId = threadId ?? target?.id;
|
|
283
|
+
if (!channelId) {
|
|
284
|
+
throw new ValidationError('Field "target" is required for a Discord bot account (provide the channel id via request.target or account config target)');
|
|
285
|
+
}
|
|
286
|
+
return api.endpoint(`/channels/${encodeURIComponent(channelId)}/messages`);
|
|
287
|
+
}
|
|
288
|
+
buildPayload(request, capabilities, mode) {
|
|
289
|
+
const content = renderBody(request, { ...capabilities, maxBodyLength: capabilities.maxBodyLength ?? MAX_MESSAGE_LENGTH }, resolveBodyTargetFormat(request, capabilities));
|
|
290
|
+
const extra = { ...(request.extra ?? {}) };
|
|
291
|
+
const payload = { ...extra };
|
|
292
|
+
if (content) {
|
|
293
|
+
payload.content = content;
|
|
294
|
+
}
|
|
295
|
+
if (request.poll) {
|
|
296
|
+
payload.poll = {
|
|
297
|
+
question: { text: request.title ?? request.body ?? '' },
|
|
298
|
+
answers: request.poll.options.map(text => ({ poll_media: { text } })),
|
|
299
|
+
duration: request.poll.durationSecs === undefined
|
|
300
|
+
? DEFAULT_POLL_DURATION_HOURS
|
|
301
|
+
: Math.round(request.poll.durationSecs / SECS_PER_HOUR),
|
|
302
|
+
allow_multiselect: request.poll.multiple ?? false,
|
|
303
|
+
};
|
|
304
|
+
// The question carries the text; repeating it as content posts it twice.
|
|
305
|
+
delete payload.content;
|
|
306
|
+
}
|
|
307
|
+
if (mode === 'bot' && request.inReplyTo?.id) {
|
|
308
|
+
payload.message_reference = {
|
|
309
|
+
message_id: request.inReplyTo.id,
|
|
310
|
+
// Discord refuses the whole message when the referenced one is gone;
|
|
311
|
+
// a reply losing its thread is better than a publication failing.
|
|
312
|
+
fail_if_not_exists: false,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
return payload;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Read every attachment into a blob, checking it against the declared limits
|
|
319
|
+
* first so an oversized file costs no download.
|
|
320
|
+
*/
|
|
321
|
+
async openAttachments(media, capabilities, signal) {
|
|
322
|
+
const attachments = [];
|
|
323
|
+
for (const [index, item] of media.entries()) {
|
|
324
|
+
if (item.source.kind === 'platformRef') {
|
|
325
|
+
throw new ValidationError(`Media item at index ${index} references a stored file; Discord has no re-usable file ids`);
|
|
326
|
+
}
|
|
327
|
+
const opened = await this.media.open(item.source, capabilities, signal);
|
|
328
|
+
// FormData needs a discrete part, so the stream is materialized here.
|
|
329
|
+
// The size ceiling was already enforced against the descriptor, which is
|
|
330
|
+
// what keeps this bounded.
|
|
331
|
+
const blob = await new Response(opened.stream).blob();
|
|
332
|
+
attachments.push({
|
|
333
|
+
blob: new Blob([blob], { type: opened.mimeType ?? 'application/octet-stream' }),
|
|
334
|
+
fileName: attachmentFileName(item, opened.fileName, index),
|
|
335
|
+
description: item.altText,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
return attachments;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Assemble the multipart body Discord's upload format expects: the message as
|
|
343
|
+
* `payload_json`, each file as `files[n]`, and the descriptions matched to the
|
|
344
|
+
* files by index through the `attachments` array.
|
|
345
|
+
*/
|
|
346
|
+
function toMultipart(payload, attachments) {
|
|
347
|
+
const withAttachments = {
|
|
348
|
+
...payload,
|
|
349
|
+
attachments: attachments.map((attachment, index) => ({
|
|
350
|
+
id: index,
|
|
351
|
+
filename: attachment.fileName,
|
|
352
|
+
...(attachment.description ? { description: attachment.description } : {}),
|
|
353
|
+
})),
|
|
354
|
+
};
|
|
355
|
+
const parts = [
|
|
356
|
+
{ name: 'payload_json', value: JSON.stringify(withAttachments) },
|
|
357
|
+
...attachments.map((attachment, index) => ({
|
|
358
|
+
name: `files[${index}]`,
|
|
359
|
+
content: attachment.blob,
|
|
360
|
+
fileName: attachment.fileName,
|
|
361
|
+
})),
|
|
362
|
+
];
|
|
363
|
+
return buildMultipartFormData(parts);
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* The file name Discord shows, with the spoiler convention applied.
|
|
367
|
+
*
|
|
368
|
+
* Discord has no per-attachment spoiler flag: a file is spoilered when its name
|
|
369
|
+
* begins with `SPOILER_`, which is why `sensitive` is honoured here rather than
|
|
370
|
+
* in the payload.
|
|
371
|
+
*/
|
|
372
|
+
function attachmentFileName(item, sniffed, index) {
|
|
373
|
+
const base = item.fileName ?? sniffed ?? `file-${index}`;
|
|
374
|
+
return item.sensitive && !base.startsWith('SPOILER_') ? `SPOILER_${base}` : base;
|
|
375
|
+
}
|
|
376
|
+
function resolveTarget(request, accountConfig) {
|
|
377
|
+
return normalizeTarget(request.target) ?? normalizeTarget(accountConfig.target);
|
|
378
|
+
}
|
|
379
|
+
function guildIdOf(target) {
|
|
380
|
+
return stringField(target, 'guildId');
|
|
381
|
+
}
|
|
382
|
+
function stringField(target, key) {
|
|
383
|
+
const value = target?.[key];
|
|
384
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
385
|
+
}
|
|
386
|
+
/** The webhook route, derived from the secret rather than from a target. */
|
|
387
|
+
function webhookPath(accountConfig) {
|
|
388
|
+
const parsed = parseWebhookUrl(String(accountConfig.auth.webhookUrl ?? ''));
|
|
389
|
+
if (!parsed) {
|
|
390
|
+
throw new ValidationError("Field 'webhookUrl' has invalid format for Discord auth");
|
|
391
|
+
}
|
|
392
|
+
return `/webhooks/${parsed.id}/${parsed.token}`;
|
|
393
|
+
}
|
|
394
|
+
function authorizationFor(accountConfig, mode) {
|
|
395
|
+
return mode === 'bot'
|
|
396
|
+
? { kind: 'bot', token: String(accountConfig.auth.botToken) }
|
|
397
|
+
: { kind: 'webhook' };
|
|
398
|
+
}
|
|
399
|
+
function requireAuthMode(accountConfig) {
|
|
400
|
+
const mode = authModeOf(accountConfig.auth);
|
|
401
|
+
if (!mode) {
|
|
402
|
+
throw new ValidationError("Discord auth requires either 'webhookUrl' or 'botToken'");
|
|
403
|
+
}
|
|
404
|
+
return mode;
|
|
405
|
+
}
|
|
406
|
+
/** A Discord permalink needs the guild; a DM channel has none. */
|
|
407
|
+
function buildMessageUrl(guildId, channelId, messageId) {
|
|
408
|
+
if (!channelId) {
|
|
409
|
+
return undefined;
|
|
410
|
+
}
|
|
411
|
+
return `https://discord.com/channels/${guildId ?? '@me'}/${channelId}/${messageId}`;
|
|
412
|
+
}
|
|
413
|
+
//# sourceMappingURL=discord.platform.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discord.platform.js","sourceRoot":"","sources":["../src/discord.platform.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@bozonx/social-posting-discord` — Discord support for `@bozonx/social-posting`.
|
|
3
|
+
*
|
|
4
|
+
* Register it by passing {@link discord} to `createPostingClient`.
|
|
5
|
+
*/
|
|
6
|
+
import type { PlatformModule } from '@bozonx/social-posting';
|
|
7
|
+
/** The descriptor a host registers to publish to Discord. */
|
|
8
|
+
export declare const discord: PlatformModule;
|
|
9
|
+
export { DiscordPlatform } from './discord.platform.js';
|
|
10
|
+
export type { DiscordAccountConfig, DiscordExtra, DiscordPlatformDeps, } from './discord.platform.js';
|
|
11
|
+
export { DiscordAuthValidator, validateDiscordAuth, parseWebhookUrl, authModeOf, } from './discord-auth.validator.js';
|
|
12
|
+
export type { DiscordAuthMode, DiscordWebhookParts } from './discord-auth.validator.js';
|
|
13
|
+
export { DiscordApi, API_VERSION, DEFAULT_API_BASE_URL } from './discord-api.js';
|
|
14
|
+
export type { DiscordMessage } from './discord-api.js';
|
|
15
|
+
export { toPlatformError } from './discord-error.js';
|
|
16
|
+
export { discordCapabilities, MAX_MESSAGE_LENGTH, MAX_ATTACHMENTS, MAX_ALT_TEXT_LENGTH, DEFAULT_MAX_ATTACHMENT_BYTES, ATTACHMENT_BYTES_BY_BOOST_TIER, } from './capabilities.js';
|