Self-hosted multi-platform broadcast newsroom for activist collectives: publishing pipeline with group approval and DST-proof scheduling, TweetDeck-style column wall with hostile-content-safe rendering, live control room with Auto Director, bridged chat governance with polls and quorum, distributed sandboxed transcode workers, encrypted content-addressed archive, monthly analytics. MIT licensed. See README.md and docs/.
13 lines
785 B
TypeScript
13 lines
785 B
TypeScript
import { createHash } from "node:crypto";
|
|
function canonical(value: unknown): unknown {
|
|
if (Array.isArray(value)) return value.map(canonical);
|
|
if (value !== null && typeof value === "object") return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, item]) => [key, canonical(item)]));
|
|
return value;
|
|
}
|
|
/** The target lives in providerMetadata.connectionId; execution keys are excluded. */
|
|
export function canonicalPostHash<T extends { providerMetadata: Record<string, unknown> }>(post: T): string {
|
|
const metadata = { ...post.providerMetadata };
|
|
delete metadata.idempotencyKey;
|
|
delete metadata.draftId;
|
|
return createHash("sha256").update(JSON.stringify(canonical({ ...post, providerMetadata: metadata }))).digest("hex");
|
|
}
|