{"id":49371839,"url":"https://github.com/arediss/oscarr-plugin-communication","last_synced_at":"2026-04-28T00:02:12.564Z","repository":{"id":352110327,"uuid":"1213890419","full_name":"arediss/Oscarr-Plugin-Communication","owner":"arediss","description":null,"archived":false,"fork":false,"pushed_at":"2026-04-17T22:08:42.000Z","size":28,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-18T00:28:28.706Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/arediss.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-17T21:51:12.000Z","updated_at":"2026-04-17T22:08:46.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/arediss/Oscarr-Plugin-Communication","commit_stats":null,"previous_names":["arediss/oscarr-plugin-communication"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/arediss/Oscarr-Plugin-Communication","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arediss%2FOscarr-Plugin-Communication","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arediss%2FOscarr-Plugin-Communication/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arediss%2FOscarr-Plugin-Communication/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arediss%2FOscarr-Plugin-Communication/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/arediss","download_url":"https://codeload.github.com/arediss/Oscarr-Plugin-Communication/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arediss%2FOscarr-Plugin-Communication/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32360116,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-27T20:07:02.737Z","status":"ssl_error","status_checked_at":"2026-04-27T20:07:00.910Z","response_time":128,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-28T00:02:09.320Z","updated_at":"2026-04-28T00:02:12.537Z","avatar_url":"https://github.com/arediss.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Oscarr Plugin — Communication\n\nA zero-dependency-on-external-services announcement broadcaster for [Oscarr](https://github.com/arediss/Oscarr). Write markdown announcements in the admin panel, target specific roles (or everyone), schedule them for later, and users see a megaphone icon with an unread badge in the header — click, read, done.\n\nNo email, no Slack, no push provider. All data lives in a single JSON file under the Oscarr data directory.\n\n## Features\n\n- **Markdown** — headings, bold, italic, lists, links, code. HTML and `\u003cimg\u003e` are stripped; output sanitized with DOMPurify and external links auto-rewritten with `target=\"_blank\" rel=\"noopener noreferrer\"` via a DOMPurify hook.\n- **Targeting** — broadcast to every authenticated user, or restrict to one or more roles.\n- **Severity** — three levels: `info` / `warning` / `critical`. Each announcement gets a colored gradient hero header (sky / amber / rose) with the title centered.\n- **Scheduled publish** — set `publishedAt` in the future; the announcement only becomes visible once the date passes. No cron needed — it's a live filter.\n- **Optional expiration** — set `expiresAt` to auto-hide an announcement after a given date. Leave empty to never expire.\n- **Unread tracking** — purely client-side: a per-announcement read state (keyed by `id` + `updatedAt`) is stored in `localStorage`. New = never seen on this browser. Edited = `updatedAt` changes, becomes unread again. Deleted = entry pruned automatically.\n- **Admin UI** — one list view with create / edit / delete. Live markdown preview in the editor with severity / audience / scheduling controls.\n- **No server-side read state** — zero per-user DB rows, zero RGPD footprint. The user-facing list endpoint also strips admin-only fields (`targetRoles`, `createdAt`) before serving.\n\n## Requirements\n\n- **Oscarr core** with plugin API `v1` exposing these ctx methods: `log`, `getPluginDataDir`, `registerPluginPermission`, `registerRoutePermission`.\n- **Node 20+** with native `fetch` and ESM support.\n\n### Core endpoint consumed by the frontend\n\n- `GET /api/admin/roles` — list RBAC roles for the audience picker in the editor.\n\n## Install\n\n1. Clone anywhere you like:\n   ```bash\n   git clone https://github.com/arediss/Oscarr-Plugin-Communication.git\n   cd Oscarr-Plugin-Communication\n   npm install\n   npm run build\n   ```\n\n2. Symlink (or copy) the folder into your Oscarr instance's plugins directory:\n   ```bash\n   ln -s /absolute/path/to/Oscarr-Plugin-Communication /absolute/path/to/oscarr/packages/plugins/communication\n   ```\n\n3. Restart the Oscarr backend — the loader discovers the new plugin and mounts its routes at `/api/plugins/communication`.\n\n4. Visit **Admin → Plugins**, confirm the plugin is enabled.\n\n5. Visit **Admin → Communication** and create your first announcement.\n\n## Data \u0026 uninstall\n\nAll data is stored at `\u003coscarr\u003e/packages/backend/data/plugins/communication/data.json`.\n\nTo uninstall:\n1. Remove the symlink from `packages/plugins/`.\n2. Restart the Oscarr backend.\n3. Optional: delete `\u003coscarr\u003e/packages/backend/data/plugins/communication/` to drop historical data, and delete the `PluginState` row for `communication` to drop plugin enable state.\n\nNothing else is persisted — no Prisma tables, no schema migrations to roll back.\n\n## Data model\n\n`data.json`:\n\n```jsonc\n{\n  \"version\": 1,\n  \"announcements\": [\n    {\n      \"id\": \"ann_xxxx\",\n      \"title\": \"Scheduled maintenance\",\n      \"body\": \"# Downtime on Sunday\\n\\nWe'll be offline **03:00 → 04:00 UTC**.\",\n      \"severity\": \"warning\",\n      \"targetRoles\": null,\n      \"publishedAt\": \"2026-04-17T09:00:00.000Z\",\n      \"expiresAt\": \"2026-04-18T00:00:00.000Z\",\n      \"createdAt\": \"2026-04-16T12:00:00.000Z\",\n      \"updatedAt\": \"2026-04-16T12:00:00.000Z\"\n    }\n  ]\n}\n```\n\n- `severity`: `\"info\" | \"warning\" | \"critical\"`.\n- `targetRoles`: `null` = everyone. Otherwise a list of RBAC role names.\n- `publishedAt`: ISO string. If it's in the future, the announcement is **scheduled** — admins see it, users don't.\n- `expiresAt`: ISO string or `null`. Past this date, users don't see it anymore.\n\nWrites are serialized in-memory and persisted via `tmp` + `rename()` so a crash mid-write cannot leave a truncated file. A corrupted `data.json` is renamed to `data.json.corrupt-\u003cts\u003e` on the next load and the store is reinitialized empty (with a loud log).\n\n## Permissions\n\nDeclared via `ctx.registerPluginPermission`:\n\n| Permission | Scope |\n|---|---|\n| `communication.announcements.manage` | Create / edit / delete announcements |\n\n`/api/plugins/communication/announcements` inherits the default `/api/plugins/*` rule (any authenticated user can read announcements visible to their role).\n\n## Unread badge\n\nPer-browser, per-user:\n\n- Storage key: `oscarr:plugin-communication:readMap:\u003cuserId\u003e` (`localStorage`)\n- Value: `{ [announcementId]: updatedAt }` — JSON map\n- Unread = any announcement whose id is missing from the map, or whose `updatedAt` differs from the stored value (i.e. it was edited since last view)\n\nImplications:\n- Users on a new device see all currently-visible announcements as unread.\n- Deleting an announcement immediately removes it from everyone's list (entry is pruned from the map on next sync).\n- An admin who edits an announcement (typo fix, content update) will re-trigger the unread badge for everyone.\n- No server-side per-user read state, so no privacy implications and no DB bloat.\n\n## Validation limits\n\n- Title: ≤ 200 characters.\n- Body: ≤ 50 000 characters (markdown source).\n- Severity: must be one of `info` / `warning` / `critical`.\n- `expiresAt` must be after `publishedAt` if set.\n\n## Known limitations\n\n- Unread state is per-browser, not per-account — switching device resets it.\n- The list endpoint is fetched on mount and every 5 minutes (skipped when the tab isn't visible) — not real-time push.\n- Markdown rendering uses `marked` + `DOMPurify`. HTML tags and `\u003cimg\u003e` are stripped (admins can't smuggle a tracking pixel) — only the standard markdown subset + links is rendered.\n- No i18n for admin UI copy — English only.\n\n## Development\n\n```bash\nnpm install\nnpm run dev   # esbuild watch mode\n```\n\nSources in `src/` (backend) and `frontend/` (admin + header hook). `npm run build` produces:\n- `dist/index.js` — backend entry\n- `dist/frontend/index.js` — admin tab bundle\n- `dist/frontend/hooks/header.actions.js` — header megaphone bundle\n\n## License\n\nMIT.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farediss%2Foscarr-plugin-communication","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farediss%2Foscarr-plugin-communication","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farediss%2Foscarr-plugin-communication/lists"}