@robojs/social — Social Alerts Plugin (Twitch, YouTube, RSS/Atom)
Summary
A Robo.js plugin that alerts Discord servers when creators go live or publish new content.
@robojs/social lets server admins subscribe to creators and feeds on Twitch, YouTube, and RSS/Atom and automatically post rich announcements into Discord. It supports slash commands for setup, cron-based polling (MVP), and optional webhooks (Twitch EventSub) when @robojs/server is available. It stores per‑guild settings durably and exposes both a slash and an imperative API so teams can build dashboards later.
Goals
- Simple “add a creator → get alerts” flow for Twitch live, YouTube uploads/lives, and RSS/Atom updates.
- Per‑guild configuration (target channel, optional role mention, templates), durable storage, and safe defaults.
- MVP uses polling via
@robojs/cron. Optional: Twitch EventSub webhooks via@robojs/server. - Clean, readable embeds that respect Discord limits and optionally publish in Announcement channels when permitted.
- Encouraged but not required: tests, i18n, analytics hooks.
Non‑Goals
- Music streaming or content scraping.
- A full web dashboard in this PR (we expose APIs so others can build one later).
User Stories
- As a server admin, I can run
/social add twitch <channel>and get a message when the streamer goes live. - As a content team, I can subscribe to a YouTube channel and post a templated embed on publish (title, thumbnail, link).
- As a community manager, I can follow RSS/Atom feeds and post trimmed summaries with links.
- As a server owner, I can set a default post channel and an optional role mention.
- As a developer, I can programmatically add/remove subscriptions and update templates via an exported API (for a custom dashboard).
High‑Level Design
Providers (MVP)
-
Twitch
- Webhook (optional): EventSub for
stream.online/stream.offlinewhen@robojs/serveris present. - Polling (fallback): Helix endpoints using an app token to detect live status.
- Webhook (optional): EventSub for
-
YouTube
- Polling: YouTube Data / Live Streaming API to detect new uploads or ongoing live broadcasts. Be mindful of quota (e.g.,
search.listcost) and cache aggressively.
- Polling: YouTube Data / Live Streaming API to detect new uploads or ongoing live broadcasts. Be mindful of quota (e.g.,
-
RSS/Atom
- Polling: Fetch and parse feeds; dedupe by GUID or link+timestamp using a Node parser (e.g.,
rss-parser).
- Polling: Fetch and parse feeds; dedupe by GUID or link+timestamp using a Node parser (e.g.,
Announcement channels: if posting into an Announcement channel, try to Publish (crosspost) when permissions allow; otherwise, post normally.
Slash Command API (root: social)
All commands live under /social …:
/social add twitch <channelNameOrId> [#channel]/social add youtube <channelUrlOrId> [#channel]/social add feed <url> [#channel]/social remove <subscriptionId>/social list [provider](paginated)/social set-channel <subscriptionId|default> <#channel>/social set-role <subscriptionId|default> <@role|none>/social set-template <subscriptionId|provider|default> <preset|json|reset>/social test <subscriptionId|provider>(posts a sample embed)/social settings(ephemeral summary for the guild)
Behavior notes
- Mentions: store an optional role ID to ping on posts.
- Channel overrides: per‑subscription override or guild default.
- Templates: see “Templating” below (presets + validated JSON).
- Anti‑spam: per‑guild throttling/cooldown to avoid bursts.
Imperative API (for dashboards & advanced usage)
// ESM export from @robojs/social
import { Social } from '@robojs/social'
export type Provider = 'twitch' | 'youtube' | 'feed'
export interface AddOptions {
guildId: string
provider: Provider
target: string // twitch channel id/name, youtube channel id/url, feed URL
channelId?: string // where to post; falls back to guild default
roleId?: string | null // role to mention (optional)
templateKey?: string // 'default' | 'compact' | 'minimal' | custom key
}
export interface TemplateUpsert {
scope: { guildId: string, provider?: Provider, subscriptionId?: string }
template: Record<string, unknown> // validated JSON-ish embed model
}
await Social.addSubscription(options: AddOptions): Promise<string /* subscriptionId */>
await Social.removeSubscription(subscriptionId: string): Promise<void>
await Social.setDefaultChannel(guildId: string, channelId: string): Promise<void>
await Social.setDefaultRole(guildId: string, roleId: string | null): Promise<void>
await Social.upsertTemplate(input: TemplateUpsert): Promise<void>
await Social.getSettings(guildId: string): Promise<GuildSettings>
await Social.listSubscriptions(
guildId: string,
filter?: { provider?: Provider }
): Promise<Subscription[]>
- All setters persist via Flashcore (see “Persistence & Data Model”).
- If
@robojs/serveris installed, the plugin can also expose REST routes (see “Optional server integration”).
Templating & Embeds
Presets (built‑in): default, compact, minimal — per provider (Twitch/YouTube/feed).
Custom templates (JSON example)
{
"title": "${broadcasterName} is LIVE!",
"description": "${title}",
"url": "${url}",
"thumbnail": "${thumbnail}",
"footer": "Started at ${startedAt}"
}
{
"title": "New video: ${title}",
"description": "Watch now → ${url}",
"thumbnail": "${thumbnail}",
"footer": "Channel: ${channelTitle} • ${publishedAt}"
}
{
"title": "${title}",
"description": "${summary}",
"url": "${url}",
"footer": "Published ${publishedAt} • ${sourceTitle}"
}
Validation & guardrails
- Validate custom JSON and truncate fields to Discord embed limits (title, description, total length, etc.).
- Replace unknown template variables with empty strings and log a warning.
- Always include a plain link fallback if the embed fails validation.
Features
- Subscriptions: Add/remove/list Twitch, YouTube, and RSS/Atom sources.
- Posting: Send embeds to a configured or default channel; optional role mention.
- Deduplication: Track
lastSeenIdper subscription to avoid reposts. - Throttling: Per‑guild cooldown windows (e.g., max 1 alert per X seconds per provider).
- Templating: Switch presets or provide raw JSON (validated & sanitized) with fallbacks.
- Testing:
/social testto preview message appearance. - Persistence: All settings stored in Flashcore, namespaced by guild.
- Scheduling: Register periodic jobs via
@robojs/cron. - (Optional) Webhooks: Twitch EventSub when
@robojs/serveris present for near‑real‑time delivery and fewer API calls. - (Optional) Portal & Analytics: Add Portal toggles; fire custom analytics events if an analytics plugin is installed.
- (Optional) i18n: Wire copy through
@robojs/i18n.
Persistence & Data Model
Storage: Flashcore (durable KV). Consider Keyv adapters later. Namespace = guildId.
// KV shapes
export interface GuildSettings {
defaultChannelId?: string
defaultRoleId?: string | null
throttleMs?: number
templates: {
default?: Template
twitch?: Template
youtube?: Template
feed?: Template
bySubscriptionId: Record<string, Template>
}
}
export interface Subscription {
id: string
guildId: string
provider: 'twitch' | 'youtube' | 'feed'
target: string // e.g., twitch broadcaster_id, youtube channelId, feed URL
channelId?: string
roleId?: string | null
isActive: boolean
lastSeen: { id?: string; at?: number }
meta?: Record<string, unknown> // provider‑specific
}
Architecture
File Layout (plugin)
@robojs/social/
package.json
src/
commands/social/
add.ts
remove.ts
list.ts
set-channel.ts
set-role.ts
set-template.ts
test.ts
settings.ts
events/
ready.ts // boot polling / webhooks
providers/
twitch.ts
youtube.ts
feed.ts
types.ts
lib/
storage.ts // Flashcore wrappers (ns = guildId)
templates.ts // presets, sanitization, embed builders
posting.ts // send + optional crosspost
dedupe.ts // lastSeen helpers
throttle.ts // per‑guild provider throttle
cron.ts // register jobs via @robojs/cron
server.ts // (optional) webhook routes via @robojs/server
validate.ts // zod (or similar) schemas for inputs
index.ts // imperative API exports
Provider Adapter Interface
export interface ProviderAdapter {
id: 'twitch' | 'youtube' | 'feed'
displayName: string
init(ctx: InitContext): Promise<void>
poll(sub: Subscription): Promise<Alert[]>
webhook?: {
subscribe(sub: Subscription): Promise<void>
unsubscribe(sub: Subscription): Promise<void>
handler(req: Request, body: unknown): Promise<Alert[]>
}
dedupeKey(a: Alert): string
}
Ready Event & Scheduling
- On
ready, detect installed plugins and register cron jobs (e.g., Twitch/YouTube every 2–5 min; feeds every 10–15 min; all configurable). - If
@robojs/serverexists, mount webhook routes (e.g.,/api/social/twitch/eventsub) and validate signatures before enqueuing alerts.
Posting & Crossposting
lib/posting.tsbuilds embeds from templates (with truncation), posts to the target channel, and attempts crosspost when the channel is an Announcement channel and the bot has permission.
Provider Details
Twitch
- Webhook: EventSub for
stream.online/stream.offline; requires public callback. - Polling: Use Helix endpoints with app access token; batch requests sensibly.
- Normalized fields:
{ type: 'live', title, url, thumbnail, startedAt, broadcasterName, game }.
YouTube
- Polling: Use YouTube Data / Live Streaming API to detect uploads/live; cache results and be mindful of quota.
- Normalized fields:
{ type: 'upload' | 'live', title, url, thumbnail, publishedAt, channelTitle }.
RSS/Atom
- Polling: Fetch and parse feeds (RSS 2.0/Atom); dedupe using GUID or link+timestamp.
- Normalized fields:
{ type: 'post', title, url, summary, publishedAt, sourceTitle }.
Security & Permissions
- Discord: The bot needs permissions to send messages, embed links, and optionally Publish messages in Announcement channels.
- Embeds: Enforce size limits before sending (title, description, fields, total length). Fallback to plain link if necessary.
- Tokens: Provider API keys (YouTube, Twitch) come from environment variables.
- Webhooks (Twitch): Verify signatures per EventSub guidance.
Robo.js Integration Notes (for contributors)
- Scaffold a plugin and add it to a dev Robo project with CLI commands; follow Robo’s file‑based structure for commands/events.
- Use Flashcore for durable KV with guild‑scoped namespaces.
- Schedule polling via
@robojs/cronif available; add small, per‑provider intervals with jitter. - Consider Portal toggles to enable/disable the module at runtime, and analytics hooks if present.
Error Handling & Resilience
- Provider errors: backoff & retry (exponential, capped).
- Quota: YouTube’s searches are costly; cache by channel, use
publishedAfter/ETags where applicable. - Webhook lifecycle: auto re‑subscribe Twitch EventSub before expiration; keep an “active subs” map.
- Discord rate limits: obey backoff; coalesce bursts (multiple alerts → consolidated embed when appropriate).
- Validation: reject malformed templates with a helpful error; always keep a safe default template.
Testing (Encouraged, not required)
- Unit tests for template truncation, dedupe, throttle, and provider normalizers.
- Optional integration tests using mocked provider responses.
- CI is welcome but not required for first release.
Developer Setup
- Requirements: Node 18+, Discord bot token & client ID, provider API keys (Twitch, YouTube).
- Scaffold: create a plugin workspace and link it locally to a test Robo project.
- Run:
npx robo devin the test project, then try/social add …commands.
References
- Discord: Message/Embed limits; Announcement channel publishing rights.
- Twitch: EventSub (webhooks), Helix reference.
- YouTube: Data API v3 & Live Streaming API; quotas & best practices.
- RSS/Atom: RSS 2.0 and Atom specs; common Node parsers (e.g.,
rss-parser).
If anything above feels unclear or too large for a single PR, feel free to propose a slice (e.g., “core + RSS only”) and we’ll iterate together. Happy Hacktoberfest! 🎉
Hi. Can you assign this to me?
@bug-author Sure, task has been assigned! Feel free to reach out either here or on our Discord if you have any questions.