Skip to content

@robojs/social — Social Alerts Plugin (Twitch, YouTube, RSS/Atom) #451

Description

@Pkmmte

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.offline when @robojs/server is present.
    • Polling (fallback): Helix endpoints using an app token to detect live status.
  • YouTube

    • Polling: YouTube Data / Live Streaming API to detect new uploads or ongoing live broadcasts. Be mindful of quota (e.g., search.list cost) and cache aggressively.
  • RSS/Atom

    • Polling: Fetch and parse feeds; dedupe by GUID or link+timestamp using a Node parser (e.g., rss-parser).

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/server is 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 lastSeenId per 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 test to 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/server is 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/server exists, mount webhook routes (e.g., /api/social/twitch/eventsub) and validate signatures before enqueuing alerts.

Posting & Crossposting

  • lib/posting.ts builds 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/cron if 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 dev in 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! 🎉

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions