mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 17:54:32 +00:00
bluebubbles: gracefully handle disabled private API with action/tool filtering and fallbacks (#16002)
Merged via /review-pr -> /prepare-pr -> /merge-pr.
Prepared head SHA: 243cc0cc9a
Co-authored-by: tyler6204 <243?+tyler6204@users.noreply.github.com>
Co-authored-by: tyler6204 <64381258+tyler6204@users.noreply.github.com>
Reviewed-by: @tyler6204
This commit is contained in:
@@ -23,7 +23,7 @@ import {
|
||||
leaveBlueBubblesChat,
|
||||
} from "./chat.js";
|
||||
import { resolveBlueBubblesMessageId } from "./monitor.js";
|
||||
import { isMacOS26OrHigher } from "./probe.js";
|
||||
import { getCachedBlueBubblesPrivateApiStatus, isMacOS26OrHigher } from "./probe.js";
|
||||
import { sendBlueBubblesReaction } from "./reactions.js";
|
||||
import { resolveChatGuidForTarget, sendMessageBlueBubbles } from "./send.js";
|
||||
import { normalizeBlueBubblesHandle, parseBlueBubblesTarget } from "./targets.js";
|
||||
@@ -71,6 +71,18 @@ function readBooleanParam(params: Record<string, unknown>, key: string): boolean
|
||||
|
||||
/** Supported action names for BlueBubbles */
|
||||
const SUPPORTED_ACTIONS = new Set<ChannelMessageActionName>(BLUEBUBBLES_ACTION_NAMES);
|
||||
const PRIVATE_API_ACTIONS = new Set<ChannelMessageActionName>([
|
||||
"react",
|
||||
"edit",
|
||||
"unsend",
|
||||
"reply",
|
||||
"sendWithEffect",
|
||||
"renameGroup",
|
||||
"setGroupIcon",
|
||||
"addParticipant",
|
||||
"removeParticipant",
|
||||
"leaveGroup",
|
||||
]);
|
||||
|
||||
export const bluebubblesMessageActions: ChannelMessageActionAdapter = {
|
||||
listActions: ({ cfg }) => {
|
||||
@@ -81,11 +93,15 @@ export const bluebubblesMessageActions: ChannelMessageActionAdapter = {
|
||||
const gate = createActionGate(cfg.channels?.bluebubbles?.actions);
|
||||
const actions = new Set<ChannelMessageActionName>();
|
||||
const macOS26 = isMacOS26OrHigher(account.accountId);
|
||||
const privateApiStatus = getCachedBlueBubblesPrivateApiStatus(account.accountId);
|
||||
for (const action of BLUEBUBBLES_ACTION_NAMES) {
|
||||
const spec = BLUEBUBBLES_ACTIONS[action];
|
||||
if (!spec?.gate) {
|
||||
continue;
|
||||
}
|
||||
if (privateApiStatus === false && PRIVATE_API_ACTIONS.has(action)) {
|
||||
continue;
|
||||
}
|
||||
if ("unsupportedOnMacOS26" in spec && spec.unsupportedOnMacOS26 && macOS26) {
|
||||
continue;
|
||||
}
|
||||
@@ -116,6 +132,13 @@ export const bluebubblesMessageActions: ChannelMessageActionAdapter = {
|
||||
const baseUrl = account.config.serverUrl?.trim();
|
||||
const password = account.config.password?.trim();
|
||||
const opts = { cfg: cfg, accountId: accountId ?? undefined };
|
||||
const assertPrivateApiEnabled = () => {
|
||||
if (getCachedBlueBubblesPrivateApiStatus(account.accountId) === false) {
|
||||
throw new Error(
|
||||
`BlueBubbles ${action} requires Private API, but it is disabled on the BlueBubbles server.`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Helper to resolve chatGuid from various params or session context
|
||||
const resolveChatGuid = async (): Promise<string> => {
|
||||
@@ -159,6 +182,7 @@ export const bluebubblesMessageActions: ChannelMessageActionAdapter = {
|
||||
|
||||
// Handle react action
|
||||
if (action === "react") {
|
||||
assertPrivateApiEnabled();
|
||||
const { emoji, remove, isEmpty } = readReactionParams(params, {
|
||||
removeErrorMessage: "Emoji is required to remove a BlueBubbles reaction.",
|
||||
});
|
||||
@@ -193,6 +217,7 @@ export const bluebubblesMessageActions: ChannelMessageActionAdapter = {
|
||||
|
||||
// Handle edit action
|
||||
if (action === "edit") {
|
||||
assertPrivateApiEnabled();
|
||||
// Edit is not supported on macOS 26+
|
||||
if (isMacOS26OrHigher(accountId ?? undefined)) {
|
||||
throw new Error(
|
||||
@@ -234,6 +259,7 @@ export const bluebubblesMessageActions: ChannelMessageActionAdapter = {
|
||||
|
||||
// Handle unsend action
|
||||
if (action === "unsend") {
|
||||
assertPrivateApiEnabled();
|
||||
const rawMessageId = readStringParam(params, "messageId");
|
||||
if (!rawMessageId) {
|
||||
throw new Error(
|
||||
@@ -255,6 +281,7 @@ export const bluebubblesMessageActions: ChannelMessageActionAdapter = {
|
||||
|
||||
// Handle reply action
|
||||
if (action === "reply") {
|
||||
assertPrivateApiEnabled();
|
||||
const rawMessageId = readStringParam(params, "messageId");
|
||||
const text = readMessageText(params);
|
||||
const to = readStringParam(params, "to") ?? readStringParam(params, "target");
|
||||
@@ -289,6 +316,7 @@ export const bluebubblesMessageActions: ChannelMessageActionAdapter = {
|
||||
|
||||
// Handle sendWithEffect action
|
||||
if (action === "sendWithEffect") {
|
||||
assertPrivateApiEnabled();
|
||||
const text = readMessageText(params);
|
||||
const to = readStringParam(params, "to") ?? readStringParam(params, "target");
|
||||
const effectId = readStringParam(params, "effectId") ?? readStringParam(params, "effect");
|
||||
@@ -321,6 +349,7 @@ export const bluebubblesMessageActions: ChannelMessageActionAdapter = {
|
||||
|
||||
// Handle renameGroup action
|
||||
if (action === "renameGroup") {
|
||||
assertPrivateApiEnabled();
|
||||
const resolvedChatGuid = await resolveChatGuid();
|
||||
const displayName = readStringParam(params, "displayName") ?? readStringParam(params, "name");
|
||||
if (!displayName) {
|
||||
@@ -334,6 +363,7 @@ export const bluebubblesMessageActions: ChannelMessageActionAdapter = {
|
||||
|
||||
// Handle setGroupIcon action
|
||||
if (action === "setGroupIcon") {
|
||||
assertPrivateApiEnabled();
|
||||
const resolvedChatGuid = await resolveChatGuid();
|
||||
const base64Buffer = readStringParam(params, "buffer");
|
||||
const filename =
|
||||
@@ -361,6 +391,7 @@ export const bluebubblesMessageActions: ChannelMessageActionAdapter = {
|
||||
|
||||
// Handle addParticipant action
|
||||
if (action === "addParticipant") {
|
||||
assertPrivateApiEnabled();
|
||||
const resolvedChatGuid = await resolveChatGuid();
|
||||
const address = readStringParam(params, "address") ?? readStringParam(params, "participant");
|
||||
if (!address) {
|
||||
@@ -374,6 +405,7 @@ export const bluebubblesMessageActions: ChannelMessageActionAdapter = {
|
||||
|
||||
// Handle removeParticipant action
|
||||
if (action === "removeParticipant") {
|
||||
assertPrivateApiEnabled();
|
||||
const resolvedChatGuid = await resolveChatGuid();
|
||||
const address = readStringParam(params, "address") ?? readStringParam(params, "participant");
|
||||
if (!address) {
|
||||
@@ -387,6 +419,7 @@ export const bluebubblesMessageActions: ChannelMessageActionAdapter = {
|
||||
|
||||
// Handle leaveGroup action
|
||||
if (action === "leaveGroup") {
|
||||
assertPrivateApiEnabled();
|
||||
const resolvedChatGuid = await resolveChatGuid();
|
||||
|
||||
await leaveBlueBubblesChat(resolvedChatGuid, opts);
|
||||
|
||||
Reference in New Issue
Block a user