Add ez-assistant and kerberos service folders
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
import { resolveNextcloudTalkAccount } from "./accounts.js";
|
||||
import { getNextcloudTalkRuntime } from "./runtime.js";
|
||||
import { generateNextcloudTalkSignature } from "./signature.js";
|
||||
import type { CoreConfig, NextcloudTalkSendResult } from "./types.js";
|
||||
|
||||
type NextcloudTalkSendOpts = {
|
||||
baseUrl?: string;
|
||||
secret?: string;
|
||||
accountId?: string;
|
||||
replyTo?: string;
|
||||
verbose?: boolean;
|
||||
};
|
||||
|
||||
function resolveCredentials(
|
||||
explicit: { baseUrl?: string; secret?: string },
|
||||
account: { baseUrl: string; secret: string; accountId: string },
|
||||
): { baseUrl: string; secret: string } {
|
||||
const baseUrl = explicit.baseUrl?.trim() ?? account.baseUrl;
|
||||
const secret = explicit.secret?.trim() ?? account.secret;
|
||||
|
||||
if (!baseUrl) {
|
||||
throw new Error(
|
||||
`Nextcloud Talk baseUrl missing for account "${account.accountId}" (set channels.nextcloud-talk.baseUrl).`,
|
||||
);
|
||||
}
|
||||
if (!secret) {
|
||||
throw new Error(
|
||||
`Nextcloud Talk bot secret missing for account "${account.accountId}" (set channels.nextcloud-talk.botSecret/botSecretFile or NEXTCLOUD_TALK_BOT_SECRET for default).`,
|
||||
);
|
||||
}
|
||||
|
||||
return { baseUrl, secret };
|
||||
}
|
||||
|
||||
function normalizeRoomToken(to: string): string {
|
||||
const trimmed = to.trim();
|
||||
if (!trimmed) throw new Error("Room token is required for Nextcloud Talk sends");
|
||||
|
||||
let normalized = trimmed;
|
||||
if (normalized.startsWith("nextcloud-talk:")) {
|
||||
normalized = normalized.slice("nextcloud-talk:".length).trim();
|
||||
} else if (normalized.startsWith("nc:")) {
|
||||
normalized = normalized.slice("nc:".length).trim();
|
||||
}
|
||||
|
||||
if (normalized.startsWith("room:")) {
|
||||
normalized = normalized.slice("room:".length).trim();
|
||||
}
|
||||
|
||||
if (!normalized) throw new Error("Room token is required for Nextcloud Talk sends");
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export async function sendMessageNextcloudTalk(
|
||||
to: string,
|
||||
text: string,
|
||||
opts: NextcloudTalkSendOpts = {},
|
||||
): Promise<NextcloudTalkSendResult> {
|
||||
const cfg = getNextcloudTalkRuntime().config.loadConfig() as CoreConfig;
|
||||
const account = resolveNextcloudTalkAccount({
|
||||
cfg,
|
||||
accountId: opts.accountId,
|
||||
});
|
||||
const { baseUrl, secret } = resolveCredentials(
|
||||
{ baseUrl: opts.baseUrl, secret: opts.secret },
|
||||
account,
|
||||
);
|
||||
const roomToken = normalizeRoomToken(to);
|
||||
|
||||
if (!text?.trim()) {
|
||||
throw new Error("Message must be non-empty for Nextcloud Talk sends");
|
||||
}
|
||||
|
||||
const tableMode = getNextcloudTalkRuntime().channel.text.resolveMarkdownTableMode({
|
||||
cfg,
|
||||
channel: "nextcloud-talk",
|
||||
accountId: account.accountId,
|
||||
});
|
||||
const message = getNextcloudTalkRuntime().channel.text.convertMarkdownTables(
|
||||
text.trim(),
|
||||
tableMode,
|
||||
);
|
||||
|
||||
const body: Record<string, unknown> = {
|
||||
message,
|
||||
};
|
||||
if (opts.replyTo) {
|
||||
body.replyTo = opts.replyTo;
|
||||
}
|
||||
const bodyStr = JSON.stringify(body);
|
||||
|
||||
const { random, signature } = generateNextcloudTalkSignature({
|
||||
body: bodyStr,
|
||||
secret,
|
||||
});
|
||||
|
||||
const url = `${baseUrl}/ocs/v2.php/apps/spreed/api/v1/bot/${roomToken}/message`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"OCS-APIRequest": "true",
|
||||
"X-Nextcloud-Talk-Bot-Random": random,
|
||||
"X-Nextcloud-Talk-Bot-Signature": signature,
|
||||
},
|
||||
body: bodyStr,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorBody = await response.text().catch(() => "");
|
||||
const status = response.status;
|
||||
let errorMsg = `Nextcloud Talk send failed (${status})`;
|
||||
|
||||
if (status === 400) {
|
||||
errorMsg = `Nextcloud Talk: bad request - ${errorBody || "invalid message format"}`;
|
||||
} else if (status === 401) {
|
||||
errorMsg = "Nextcloud Talk: authentication failed - check bot secret";
|
||||
} else if (status === 403) {
|
||||
errorMsg = "Nextcloud Talk: forbidden - bot may not have permission in this room";
|
||||
} else if (status === 404) {
|
||||
errorMsg = `Nextcloud Talk: room not found (token=${roomToken})`;
|
||||
} else if (errorBody) {
|
||||
errorMsg = `Nextcloud Talk send failed: ${errorBody}`;
|
||||
}
|
||||
|
||||
throw new Error(errorMsg);
|
||||
}
|
||||
|
||||
let messageId = "unknown";
|
||||
let timestamp: number | undefined;
|
||||
try {
|
||||
const data = (await response.json()) as {
|
||||
ocs?: {
|
||||
data?: {
|
||||
id?: number | string;
|
||||
timestamp?: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (data.ocs?.data?.id != null) {
|
||||
messageId = String(data.ocs.data.id);
|
||||
}
|
||||
if (typeof data.ocs?.data?.timestamp === "number") {
|
||||
timestamp = data.ocs.data.timestamp;
|
||||
}
|
||||
} catch {
|
||||
// Response parsing failed, but message was sent.
|
||||
}
|
||||
|
||||
if (opts.verbose) {
|
||||
console.log(`[nextcloud-talk] Sent message ${messageId} to room ${roomToken}`);
|
||||
}
|
||||
|
||||
getNextcloudTalkRuntime().channel.activity.record({
|
||||
channel: "nextcloud-talk",
|
||||
accountId: account.accountId,
|
||||
direction: "outbound",
|
||||
});
|
||||
|
||||
return { messageId, roomToken, timestamp };
|
||||
}
|
||||
|
||||
export async function sendReactionNextcloudTalk(
|
||||
roomToken: string,
|
||||
messageId: string,
|
||||
reaction: string,
|
||||
opts: Omit<NextcloudTalkSendOpts, "replyTo"> = {},
|
||||
): Promise<{ ok: true }> {
|
||||
const cfg = getNextcloudTalkRuntime().config.loadConfig() as CoreConfig;
|
||||
const account = resolveNextcloudTalkAccount({
|
||||
cfg,
|
||||
accountId: opts.accountId,
|
||||
});
|
||||
const { baseUrl, secret } = resolveCredentials(
|
||||
{ baseUrl: opts.baseUrl, secret: opts.secret },
|
||||
account,
|
||||
);
|
||||
const normalizedToken = normalizeRoomToken(roomToken);
|
||||
|
||||
const body = JSON.stringify({ reaction });
|
||||
const { random, signature } = generateNextcloudTalkSignature({
|
||||
body,
|
||||
secret,
|
||||
});
|
||||
|
||||
const url = `${baseUrl}/ocs/v2.php/apps/spreed/api/v1/bot/${normalizedToken}/reaction/${messageId}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"OCS-APIRequest": "true",
|
||||
"X-Nextcloud-Talk-Bot-Random": random,
|
||||
"X-Nextcloud-Talk-Bot-Signature": signature,
|
||||
},
|
||||
body,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorBody = await response.text().catch(() => "");
|
||||
throw new Error(`Nextcloud Talk reaction failed: ${response.status} ${errorBody}`.trim());
|
||||
}
|
||||
|
||||
return { ok: true };
|
||||
}
|
||||
Reference in New Issue
Block a user