- Added `previewConfigKey` metadata to multiple scripts including fetch-binary, fetch-html, fetch-http, fetch-rss-feed, fingerprint, get-secret, jsonata, ntfy, render-template, and send-email, improving configuration visibility. - Updated the `FieldHelp` and `ScriptCard` components to utilize the new preview configuration, enhancing the user interface for displaying script metadata. - Refactored `TriggerCard` and `WorkflowTestPanel` components to support expanded views, improving user interaction and accessibility of configuration details. - Enhanced the `WorkflowVisualEditor` to manage test panel visibility more effectively. This update improves the overall user experience by providing clearer context and configuration options for scripts and workflows.
175 lines
4.8 KiB
JavaScript
175 lines
4.8 KiB
JavaScript
import jsonata from "jsonata";
|
|
|
|
function ntfyHeaders(ctx) {
|
|
const headers = {};
|
|
|
|
if (ctx.data?.title) {
|
|
log.info("ntfy: setting title %s", ctx.data.title);
|
|
headers.Title = ctx.data.title;
|
|
}
|
|
|
|
return headers;
|
|
}
|
|
|
|
function resolveFingerprintKey(config) {
|
|
const fingerprint = config?.fingerprint;
|
|
if (fingerprint === true) return "fingerprint:ntfy";
|
|
if (typeof fingerprint === "string" && fingerprint.length > 0) return fingerprint;
|
|
return null;
|
|
}
|
|
|
|
function defaultFingerprintSource(ctx) {
|
|
return {
|
|
title: ctx.data?.title,
|
|
message: ctx.data?.message,
|
|
attach: ctx.data?.attach,
|
|
filename: ctx.data?.filename,
|
|
contentType: ctx.data?.contentType,
|
|
};
|
|
}
|
|
|
|
async function resolveFingerprintValue(ctx) {
|
|
const expr = ctx.config?.fingerprintJsonata;
|
|
if (typeof expr === "string" && expr.length > 0) {
|
|
const expression = jsonata(expr);
|
|
return await expression.evaluate(ctx);
|
|
}
|
|
return defaultFingerprintSource(ctx);
|
|
}
|
|
|
|
async function ntfy(ctx) {
|
|
const file = ctx.data?.file;
|
|
const hasFile = Buffer.isBuffer(file) || file instanceof Uint8Array;
|
|
|
|
log.info(
|
|
{
|
|
title: ctx.data?.title,
|
|
message: ctx.data?.message,
|
|
filename: ctx.data?.filename,
|
|
hasFile,
|
|
fileLength: hasFile ? file.length : 0,
|
|
attach: ctx.data?.attach,
|
|
},
|
|
"ntfy incoming context",
|
|
);
|
|
|
|
const fingerprintKey = resolveFingerprintKey(ctx.config);
|
|
/** @type {{ hash: string, previousAt: string | null, ageMs: number | null } | null} */
|
|
let fp = null;
|
|
|
|
if (fingerprintKey) {
|
|
const source = await resolveFingerprintValue(ctx);
|
|
const checked = await $fingerprint.check(fingerprintKey, source, {
|
|
maxAge: ctx.config?.fingerprintMaxAge,
|
|
});
|
|
if (!checked.changed) {
|
|
log.info(
|
|
{
|
|
key: fingerprintKey,
|
|
fingerprint: checked.hash,
|
|
ageMs: checked.ageMs,
|
|
},
|
|
"ntfy: skipped, fingerprint unchanged",
|
|
);
|
|
return {
|
|
sent: "false",
|
|
skipped: true,
|
|
fingerprint: checked.hash,
|
|
fingerprintAt: checked.previousAt,
|
|
fingerprintAge: checked.ageMs,
|
|
};
|
|
}
|
|
fp = checked;
|
|
}
|
|
|
|
const headers = ntfyHeaders(ctx);
|
|
const ntfyUrl = ctx.config?.url || "https://ntfy.sh/scrunner";
|
|
|
|
if (hasFile) {
|
|
const filename = ctx.data?.filename || "attachment";
|
|
headers.Filename = filename;
|
|
if (ctx.data?.message) {
|
|
headers.Message = ctx.data.message;
|
|
}
|
|
if (ctx.data?.contentType) {
|
|
headers["Content-Type"] = ctx.data.contentType;
|
|
}
|
|
|
|
log.info(
|
|
{ ntfyUrl, filename, length: file.length },
|
|
"ntfy uploading file",
|
|
);
|
|
await $axios.put(ntfyUrl, file, {
|
|
headers,
|
|
maxBodyLength: Infinity,
|
|
maxContentLength: Infinity,
|
|
});
|
|
} else {
|
|
if (ctx.data?.attach) {
|
|
log.info("ntfy: setting attach %s", ctx.data.attach);
|
|
headers.Attach = ctx.data.attach;
|
|
}
|
|
|
|
const truncatedMessage = ctx.data?.message?.substring(0, 100);
|
|
log.info("ntfy sending message to %s", ntfyUrl);
|
|
log.info("ntfy messsage: %s", truncatedMessage);
|
|
|
|
await $axios.post(ntfyUrl, ctx.data?.message || "Hello from scrunner", {
|
|
headers,
|
|
});
|
|
}
|
|
|
|
/** @type {Record<string, unknown>} */
|
|
const sent = { sent: "true" };
|
|
if (fingerprintKey && fp) {
|
|
const stored = await $fingerprint.remember(fingerprintKey, fp.hash);
|
|
sent.fingerprint = stored.hash;
|
|
sent.fingerprintAt = stored.at;
|
|
}
|
|
return sent;
|
|
}
|
|
|
|
ntfy.meta = {
|
|
description: "Send a message or file to an ntfy topic",
|
|
previewConfigKey: "url",
|
|
config: {
|
|
url: {
|
|
type: "string",
|
|
default: "https://ntfy.sh/scrunner",
|
|
description: "ntfy topic URL",
|
|
},
|
|
fingerprint: {
|
|
type: "string",
|
|
required: false,
|
|
description: "true or a KV key; skip send when the payload fingerprint is unchanged",
|
|
},
|
|
fingerprintJsonata: {
|
|
type: "string",
|
|
required: false,
|
|
description: "JSONata against ctx; default hashes title, message, attach, filename, contentType",
|
|
},
|
|
fingerprintMaxAge: {
|
|
type: "string",
|
|
required: false,
|
|
description: "Optional age limit (e.g. 24h, 7d, or milliseconds)",
|
|
},
|
|
},
|
|
input: {
|
|
title: { type: "string", required: false },
|
|
message: { type: "string", required: false },
|
|
attach: { type: "string", required: false, description: "Remote attachment URL" },
|
|
file: { type: "buffer", required: false, description: "Binary body to PUT" },
|
|
filename: { type: "string", required: false },
|
|
contentType: { type: "string", required: false },
|
|
},
|
|
output: {
|
|
sent: { type: "string", description: "Replaces the workflow context with { sent: \"true\" }" },
|
|
},
|
|
example: {
|
|
data: { title: "Hello", message: "Hello from scrunner" },
|
|
config: { url: "https://ntfy.sh/scrunner", fingerprint: true },
|
|
},
|
|
};
|
|
|
|
export default ntfy;
|