Ernest Defoe
Administrators
-
Joined
-
Last visited
-
Currently
Viewing Forums Index
Everything posted by Ernest Defoe
-
Respawn — support and discussion
RespawnA gaming theme for Flarum 2: chamfered surfaces, neon accents, mono typography, hex-clipped avatars, hero with blinking cursor, level/XP sidebar widget, and a stats footer. FeaturesDark + Light mode — admin-configurable default, per-user toggle in the header (☾ / ☀) Branded hero — replaces the default IndexPage hero with a panel showing an eyebrow line, big mono headline ("Welcome to {forum}"), tagline, and an optional row of tag-shortcut chips Server-status footer — four chamfered stat cards (Posts · Discussions · Members · Online Now) rendered at the bottom of the index page, cached server-side at 60s Player Card — sidebar widget for the logged-in user with hex avatar, derived LVL N · {posts} POSTS, and a cyan→magenta XP bar Rarity Legend — sidebar panel mapping the four tag-color tiers (Legendary / Epic / Rare / Common) so newcomers learn the vocabulary Post styling — chamfered post cards, mono usernames in cyan, post-number pills, accent-bordered blockquotes, mono code blocks Composer + Scrubber — restyled to match the theme The theme is a pure CSS/LESS skin layered on top of stock Flarum — no Mithril components replaced or routes added beyond the small components above. Anything that uses Flarum's CSS variables (--primary-color, --secondary-color, etc.) is remapped, so most extensions skin for free. RequirementsFlarum 2.x PHP 8.3+ Installation php flarum cache:clear``` Then enable **Respawn** in **Admin → Extensions**. --- ## Configuration All settings are in **Admin → Extensions → Respawn**. | Setting | Description | Default | |---|---|---| | **Theme Mode** | Default colour mode for new visitors (`Dark` or `Light`). Users can toggle with the moon/sun button in the header. | `Dark` | | **Hero Tagline** | Subtitle shown under the welcome headline. Leave blank to hide. | `Drop in, level up, and join the discussion.` | | **Hero Eyebrow** | Small accent line above the headline. | `▸ Player Connected` | | **Hero Tag Chips** | Comma-separated list of tag slugs to feature as quick-jump chips (e.g. `halo, elden-ring, deals`). Leave blank to hide. | *(blank)* | The hero headline reads from Flarum's standard **Forum Title** setting (Admin → Basics). --- ## How the stats are computed `ForumStatistics` queries the `posts`, `discussions`, `users`, and `users.last_seen_at` tables and caches each count for 60 seconds. The "Online Now" tile uses a 5-minute activity window matching Flarum's stock indicator. Any DB error renders the affected tile as `—` rather than a misleading `0`. --- ## Player Card level formula Currently: `level = floor(posts / 100) + 1`, progress to next level = `posts % 100`. It's a deliberately simple linear ramp. If you want a non-linear curve (exponential, custom thresholds, Bronze/Silver/Gold ranks instead of a number), open an issue. --- ## Customising The theme exposes its tokens as CSS variables on `:root` (and `[data-respawn="light"]`), so an extension or custom CSS can override any of them without touching Respawn itself: ```:root { --r-accent: #50ff8a; /* lime instead of cyan */ --r-magenta: #ff6b35; /* orange instead of magenta */ }``` Every component reads through these variables. --- ## Support Questions, bug reports, and feature requests: * **Support forum:** https://ernestdefoe.online * **Issues:** https://github.com/ernestdefoe/respawn/issues --- ## License Respawn is released under the [MIT License](https://github.com/ernestdefoe/respawn/blob/main/LICENSE). Copyright © ernestdefoe.
-
CFB Recruiting Tracker - Live college football recruiting
recruiting 2.0.15 No changes made. View release on GitHub
-
CFB Recruiting Tracker - Live college football recruiting
recruiting v2.0.13 What's Changed No changes Full Changelog: https://github.com/ernestdefoe/recruiting/compare/2.0.12...v2.0.13 View release on GitHub
-
What are some extension ideas?
I’m thinking about releasing this one when it’s more polished but may design something similar but different.
-
What are some extension ideas?
What are some ideas that you have for a Flarum extension and if I can I will develop it. I need ideas.
-
Cross References for Flarum 2
Cross References GitHub-style cross-references between discussions and posts for Flarum 2. Type #42 in any post to link the current discussion to discussion #42 — the target picks up a backlink, the target's author can be notified, and a sidebar widget surfaces every inbound reference. Built as a more robust Flarum 2 successor to club-1/flarum-ext-cross-references: rename-safe rendering, visibility-aware backlinks, batched queries, and a references:N search filter — all wired through Flarum 2's first-party patterns. What it does, at a glance1. Inline references render as rich chips with the live discussion titleType #42 in a post and it renders as a tappable chip showing the **target discussion's current title** — pulled fresh from the database on every render, so renames flow through automatically and you never read a stale title. 2. Target discussions get a backlink as a first-class event-postWhen a post references another discussion, a small "Referenced from #X" event-post appears in the target's stream — slot-in to your existing moderation history, search index, and reply pipeline. 3. Every discussion gets an "inbound references" sidebar widgetA widget on the right-hand side of DiscussionPage lists the most recent inbound refs, with the source author and a relative timestamp. Click an item to jump to the referring discussion. 4. The target's author gets an in-app notificationThe recipient sees the alert in their Flarum notification dropdown, with the source discussion title resolved live. Self-references (when you reference your own discussion) are silently skipped to avoid noise. 5. Every post header shows a copy-to-clipboard #N chipSo you always know which post number to use when typing #42/pN. Click the chip and the canonical cross-reference (#discussionId/pN) is copied to your clipboard, ready to paste into a reply. Quick start php flarum migrate php flarum cache:clear``` Then **enable from the admin panel** under `Extensions → Cross References`, grant the `Use cross-references in posts` permission to whichever groups should be allowed to type `#42`, and you're done — references start working immediately on new and edited posts. --- ## Usage — how to reference a discussion or a specific post ### Syntax cheat-sheet | Type this in your post | What renders | Where it points | | ----------------------------------- | ---------------------------------- | ------------------------------------- | | `#42` | `#42 — <Discussion Title>` | discussion 42, top of the page | | `#42/p7` | `#42 — <Discussion Title> (post #7)` | discussion 42, post number 7 | **Tip:** every post header shows a clickable `#N` chip — click it to copy the canonical `#discussionId/pN` reference straight to your clipboard, so you never have to guess a post number. | `https://forum.example.com/d/42` | `#42 — <Discussion Title>` | discussion 42 (URL auto-shortened) | | `https://forum.example.com/d/42-some-slug/7` | `#42 — <Discussion Title> (post #7)` | discussion 42, post 7 | | `[click here](https://forum.example.com/d/42)` | a regular markdown link "click here" | preserved as-is — Markdown wins | The `#N` form is the canonical one. Pasted forum-discussion URLs get **rewritten to** `#N` at parse time before storage, so the database only ever sees a single representation. URLs you explicitly wrap in markdown brackets (`[label](https://github.com/ernestdefoe/cross-references/blob/main/url)`) are left alone — the extension assumes you meant the custom label intentionally. ### A complete example You're replying to a support discussion and want to link out to two related threads — a bug report and a specific post inside a roadmap discussion: **Write:** ```Thanks for the report! This looks like the same regression as #42, and the fix is being planned in #58/p3. I'll close this as a duplicate once #58/p3 lands.``` **Renders as:** Thanks for the report! This looks like the same regression as `#42 Login button stops responding on mobile`, and the fix is being planned in `#58 Q3 roadmap (post #3)`. I'll close this as a duplicate once `#58 Q3 roadmap (post #3)` lands. The chips are clickable — they navigate to `/d/42` and `/d/58/3` respectively. **Meanwhile, in discussions #42 and #58:** * The bug report (#42) gets a new event-post that reads `you referenced this from #137` (the support thread's ID). * The roadmap (#58) gets the same event-post pointing back to #137. * The author of #42 and the author of #58 each get an in-app notification alerting them that their discussion was referenced. * The sidebar of both discussions now lists `#137` as an inbound reference. ### Visibility — references respect who can see what References are visibility-scoped on every read path. If a viewer can't see the target (e.g., it's in a restricted tag or a private group), the chip renders as a muted `#42` placeholder with **no title leaked**. Backlink event-posts inherit the target's normal visibility, so a backlink in a private thread doesn't leak the source thread's existence to non-members. | Viewer can see target? | Chip renders as | | ---------------------- | ------------------------------------------------------- | | Yes | `#42 — <Title>` — full chip, clickable | | No (private, restricted tag) | `#42` (muted, `CrossReference--hidden` class) | The sidebar widget applies the same scope — inbound refs from sources you can't view simply don't appear. ### Renames are automatic Because **titles are never persisted in the post content** — only the discussion ID is — renaming a referenced discussion flows through to every chip on every render. No reindex, no `chore:reparse`, no cache invalidation needed. The next request gets the new title. ### Searching for references Use the `references:N` filter on the discussion list to find every discussion whose posts reference discussion #N: ```filter[references]=42``` Either as a URL parameter on `/api/discussions` or via the standard search input if you've wired it (works alongside any other filter): ```filter[references]=42&filter[tag]=support``` Negation works too: `-references:42` excludes them. --- ## Admin settings Under `Admin → Extensions → Cross References`, three toggles: | Setting | Default | Effect | | ------------------------------------------------ | ------- | --------------------------------------------------------------------------------------------------- | | **Show inline references in posts** | on | Render `#42` / pasted-URL refs as rich chips. Turn off to fall back to raw `#42` text. | | **Create backlink event-posts in target discussions** | on | Insert "Referenced from #X" event-posts in the target. Disable for forums that prefer quiet target threads. | | **Notify the target discussion's author** | on | Send an in-app alert when someone references the recipient's discussion. The actor never notifies themselves. | And one permission: * `Use cross-references in posts` — gate `#42` rendering at the group level. Useful for forums that want references to work only for trusted members. --- ## Architecture ### What gets stored A single companion table `cross_references` with a row per `(source_post, target_discussion, target_post?)` tuple. Unique constraint on that tuple gives storage-layer dedupe — re-saving a post that mentions `#42` three times produces one row. ```cross_references id bigint PK source_post_id FK posts.id ON DELETE CASCADE source_discussion_id FK discussions.id target_discussion_id FK discussions.id ON DELETE CASCADE target_post_id FK posts.id (nullable — null = whole-discussion ref) created_at timestamp UNIQUE (source_post_id, target_discussion_id, target_post_id) INDEX (target_discussion_id) -- inbound-refs lookup INDEX (source_discussion_id) -- outbound-refs lookup``` The table follows Flarum 2's **companion-table convention** — no columns added to `posts` or `discussions` (CLAUDE.md §45). ### How rendering works 1. **Parse-time**: The s9e/TextFormatter `Preg` plugin matches `\B#(\d+)\b` and `\B#(\d+)/p(\d+)\b` and emits `<CROSSREF id="42" postnum="7"/>` tags into the stored XML. Pasted forum URLs are pre-rewritten to the `#N` form before the parser sees them. 2. **Pre-render**: a single batched `Discussion::whereIn('id', $ids) ->whereVisibleTo($actor)` query resolves every referenced discussion's title in one round-trip. Titles + visibility flags get injected onto each `<CROSSREF>` tag as attributes. 3. **Render**: the XSL template uses the `title` attribute when set, falling back to the `CrossReference--hidden` placeholder when visibility denies the read. No titles are persisted in post content. No per-post N+1 query. One DB call per page render, regardless of how many cross-references are in the visible posts. ### Bidirectional backlinks + notifications The `Posted` / `Revised` event listener extracts CROSSREF tags from the post's parsed XML, diffs against the existing `cross_references` rows for that post, then: * **Inserts** rows for new references; **deletes** rows for removed ones. * For each new ref, creates a `CrossReferenceEventPost` (extending Flarum's `AbstractEventPost`) in the target discussion. Lives alongside core event-posts like "renamed" or "locked" — participates in moderation, search, and the standard reply pipeline. * Optionally dispatches a `DiscussionReferencedBlueprint` notification to the target's author via the `alert` channel. Self-references are silently dropped. The listener is wrapped in `try/catch` with PSR-3 logging so a cross-ref bug can **never** block a post save (CLAUDE.md §41). ### API endpoint ```GET /api/discussions/{id}/cross-references``` Returns the inbound references for a discussion, with each row visibility-scoped against the requesting actor and eager-loaded with the source discussion title + first-post author. Capped at 50 rows — the response includes a `meta.capped50` boolean so a future "view all" page can fall back to a paginated source. ```{ "data": [ { "id": 1, "sourceDiscussionId": 6, "sourcePostId": 4, "targetPostId": null, "createdAt": "2026-05-17T13:59:39+00:00", "source": { "discussionTitle": "Cross-ref smoke test", "discussionSlug": "cross-ref-smoke-test", "author": { "id": 2, "displayName": "tester", "username": "tester", "avatarUrl": null } } } ], "meta": { "count": 1, "capped50": false } }``` ### Search filter Registered via `Extend\SearchDriver(DatabaseSearchDriver) ->addFilter(DiscussionSearcher, ReferencesFilter)`. The value is cast to `(int)` before reaching SQL — defeats the §10 wildcard / sort-allowlist trap surface; the gambit accepts numeric ids only, never strings. --- ## Comparison vs. `club-1/flarum-ext-cross-references` | Concern | `club-1/cross-references` | `ernestdefoe/cross-references` | | ------------------------------------------- | -------------------------------- | ------------------------------------------- | | Flarum version | 1.x | 2.x | | Discussion title in chip | Baked into post content at save | Resolved live from DB on every render | | Behavior on target rename | Requires `chore:reparse` to refresh stored chips | Automatic — chips read current title | | Behavior on target delete | Plain link to nothing | Chip renders as muted `#N` placeholder | | Visibility-aware | No (leaks restricted titles) | Yes — `whereVisibleTo($actor)` everywhere | | Backlink mechanism | Event post | Event post (first-class, extends `AbstractEventPost`) | | Notifications to target author | No | Yes — `AlertableInterface` blueprint | | Inbound-refs sidebar widget | No | Yes | | Search filter (`references:N`) | No | Yes | | Per-post listener failures block post save | Possible | Caught + logged, never blocks save | | Dedupe | None | Unique index at storage layer | --- ## Architecture notes (for extension authors reading this for reference) This extension was built against the Flarum 2 security/structure playbook — relevant sections: * **§5** — `whereVisibleTo($actor)` applied at every read path (API endpoint, render-time enrichment, search filter sub-query). Inbound-refs sidebar filters source visibility per actor before payload assembly. * **§19** — `getData()` carries IDs only; titles re-resolved from the subject relation at render time so a target that turns private later doesn't leak its old title via a stale notification. * **§26** — Migration uses `Flarum\Database\Migration::createTableIfNotExists` with explicit `cascadeOnDelete` FKs and a composite unique index for storage-layer dedupe. * **§38** — Single batched query per render to resolve all titles for a post; one batched visibility check for the sidebar. No N+1. * **§41** — `Psr\Log\LoggerInterface` injected in the listener + controller; try/catch wraps both so a ref bug can never block a post save. * **§43** — `composer.json` constrained to `"flarum/core": "^2.0"`; no `^1.0` fallback, since v2-only `Endpoint`/`Schema` classes are imported. * **§45** — Companion `cross_references` table; **no** columns added to core `posts`/`discussions`/`users`. * **§46** — `DiscussionReferencedBlueprint` has a typed constructor + `TYPE` class constant + `getSubjectModel()` returns `Discussion::class`, so the polymorphic `subject` relationship resolves cleanly via `typeForModel()`. --- ## Contributing Pull requests welcome. For non-trivial changes, please open an issue first so we can discuss the approach. * `composer require ernestdefoe/cross-references:dev-main` (or path repo) in your local Flarum to develop against. * `cd js && npm install && npm run dev` watches and rebuilds the JS bundle on every change. * `php flarum cache:clear` after PHP changes. ## License [MIT](https://github.com/ernestdefoe/cross-references/blob/main/LICENSE.md) © Ernestdefoe <https://ernestdefoe.online/extension/ernestdefoe/cross-references>
- Feature request for Social Groups
-
Social Groups for Flarum — support and discussion
Social Groups for Flarum A full-featured social groups extension for Flarum 2. Members can create public or private groups, post to a Facebook-style feed, hold threaded discussions, share media, react to posts, run polls, and more — with optional real-time updates powered by flarum/realtime. FeaturesGroupsPublic & private groups — public groups are open to all; private groups require a join request or direct invitation. Group image & banner upload — each group has its own avatar and cover banner image. Featured groups — administrators can pin groups to a featured row on the group directory. Primary group badge — members designate one group as their primary group; the badge is displayed on their Flarum profile. Group analytics dashboard — overview of member count, post activity, and growth over time (visible to group admins and moderators). RSS feed — each group exposes a per-group RSS 2.0 feed at /groups/{slug}/feed.rss. FeedFacebook-style post cards with full Flarum BBCode/Markdown rendering. Emoji reactions — six reaction types: 👍 Like, ❤️ Love, 😂 Haha, 😮 Wow, 😢 Sad, 😡 Angry; one reaction per user, toggleable. Polls — create polls directly from the post composer; members vote inline with live percentage bars. Link previews — URLs are automatically expanded into rich Open Graph preview cards (title, description, thumbnail). Media attachments — attach images and files to posts via fof/upload (optional). Pin posts — group admins can pin discussions to the top of the feed. Post search — debounced search bar filters discussions by title or content in real time. Post sharing — share any discussion into another group you belong to, with an optional comment; shared posts render a quoted card. DiscussionsThreaded discussion view at /groups/{slug}/d/{discussionId} with nested comment replies. Pin discussions to the top of the discussion list. Share discussions to the group feed. Media GalleryDedicated "Media" tab on each group page aggregating all images from posts and discussions into a responsive thumbnail grid with lightbox. Member ManagementJoin requests — group admins approve or reject membership requests; direct invitations bypass the queue. Promote / demote — elevate a member to group admin or step them back down. Kick — remove a member from the group. Member badges on profiles — group membership chips appear on Flarum user profile cards forum-wide. NotificationsAlert when a new post is created in a group the user participates in. Alert when someone replies directly to a user's post. Real-time (optional — requires flarum/realtime)See the flarum/realtime Integration section below. Requirements| Dependency | Version | Required | |---|---|---| | Flarum | 2.x | Yes | | PHP | 8.3+ | Yes | | flarum/realtime | any | No — graceful no-op if absent | | fof/upload | ^2.0 | No — required for file/image attachments in posts | Installation php flarum migrate php flarum cache:clear``` Then go to **Admin → Extensions** and enable **Social Groups**. **After every update**, run the migrate and cache-clear commands to apply any new database migrations: ```php flarum migrate && php flarum cache:clear``` --- ## Configuration ### Admin panel settings Navigate to **Admin → Extensions → Social Groups**: | Setting | Options | Default | |---|---|---| | **Who can create groups** | `member` — any registered user; `admin` — forum administrators only | `member` | ### Group privacy Group creators choose privacy at creation time: | Type | Behaviour | |---|---| | **Public** | Anyone can view the feed, discussions, and gallery. Joining is instant. | | **Private** | Content is hidden from non-members. New members must submit a join request (approved by a group admin) or be invited directly. | --- ## flarum/realtime Integration The real-time features are entirely **optional**. If `flarum/realtime` is not installed the extension works normally — the typing endpoint silently returns `204` and the post-broadcast listener exits immediately without any error or warning. When `flarum/realtime` **is** installed and running: * **Live post injection** — after a member submits a reply in a group discussion thread, all other members viewing that thread see the new post card appear instantly via the `sg-post-created` WebSocket event. No page refresh is needed. * **Typing indicator** — while a member is composing a reply, an animated "Jane is typing…" indicator with bouncing dots is broadcast via the `sg-typing` event and displayed above the reply box for all other members viewing the same thread. The indicator disappears automatically when the member stops typing or submits their post. * **Deduplication** — the client compares each incoming post's ID against already-rendered posts to prevent duplicates when both the HTTP response and the WebSocket push arrive for the same post. * **No extra configuration** — the extension uses the existing public Pusher/Soketi channel that `flarum/realtime` sets up. Nothing additional needs to be configured. --- ## Updating ```composer update ernestdefoe/social-groups php flarum migrate php flarum cache:clear``` --- ## License Released under the [MIT License](https://github.com/ernestdefoe/social-groups/blob/main/LICENSE). © Ernestdefoe <https://ernestdefoe.online/extension/ernestdefoe/social-groups>
- Feature request for Social Groups
-
CFB Recruiting Tracker - Live college football recruiting
FBSFB RecruitingA Flarum 2 extension that pulls live college football recruiting rankings from the College Football Data API and displays them on a dedicated /recruiting page inside your Flarum forum. FeaturesLive data — recruiting rankings pulled directly from the CFBD API and cached server-side Player headshots — automatically sourced from On3's football rankings page; falls back to a star-tier coloured initials avatar when no photo is available Full player details — national rank, star rating, numerical rating, position, height, weight, high school, hometown, and commitment status Filters — search by name / school / city, filter by position, filter by committed vs. undecided Stats bar — total recruits displayed, average rating, committed count Responsive grid — adapts from 4–5 columns on desktop down to 2 on mobile | Mobile view | |---| | | RequirementsFlarum 2.x PHP 8.3+ A free CFBD API key from collegefootballdata.com guzzlehttp/guzzle ^7.0 (pulled in automatically via Composer) Installation php flarum migrate php flarum cache:clear``` Then enable the extension in **Admin → Extensions**. --- ## Configuration All settings are found in **Admin → Extensions → FBSFB Recruiting**. | Setting | Description | Default | |---|---|---| | **API Key** | Your CFBD bearer token | *(required)* | | **Recruiting Year** | Class year to display (e.g. `2026`) | Current calendar year | | **Team Filter** | Show only recruits committed to a specific team (e.g. `Alabama`). Leave blank for national rankings. | *(blank — national)* | | **Max Recruits** | How many recruits to display (1–100) | `25` | | **Cache Duration** | How long to cache CFBD responses in minutes | `360` (6 hours) | --- ## How it works 1. A forum member navigates to `/recruiting` (or clicks the **Recruiting** link in the sidebar nav). 2. The JS frontend calls the internal API route `GET /api/cfbd-recruits`. 3. The PHP controller reads your admin settings, checks Flarum's cache, and if needed proxies a request to `https://api.collegefootballdata.com/recruiting/players?year=…&team=…`. 4. Results are sorted by national ranking, transformed into a clean JSON shape, and cached for the configured duration. 5. Recruit records are enriched with On3 headshots (see below) and returned to the client. 6. Player cards are rendered with national rank, stars, headshot, physical measurements, high school, hometown, and commitment pill. 7. Client-side filters let users narrow by position, commitment status, or keyword search instantly without a second API call. --- ## Player headshots Headshots are sourced from [**On3**](https://www.on3.com/), which maintains photos for thousands of current and historical high-school recruits. ### How the image lookup works On3's football rankings page (`on3.com/rivals/rankings/player/football/{year}/`) is fully server-rendered HTML containing 150+ ranked recruits, each with a profile link and headshot image URL embedded directly in the markup. On the first API call after the cache is empty the extension: 1. Fetches the On3 rankings page for the configured class year (one HTTP request). 2. Parses the HTML to build a **name → image URL** map using positional matching between profile hrefs (`/rivals/jared-curtis-159433/`) and `on3static.com` image paths. 3. Caches the map for **24 hours** — subsequent requests read from cache with zero external HTTP calls. 4. Matches each CFBD recruit to the map by normalised name slug (e.g. `"Jared Curtis"` → `"jared-curtis"`). ### Fallback avatars When no On3 photo is available the card shows a coloured initials avatar whose background is coded by star rating: | Stars | Colour | |---|---| | ★★★★★ | Gold | | ★★★★ | Blue | | ★★★ | Green | | ★★ / unrated | Slate | --- ## Player card data Each card displays: * **National ranking** (`#1`, `#2`, …) * **Star rating** (★★★★★) and **numerical rating** (e.g. `0.9991`) * **Headshot** — sourced from On3; coloured initials avatar as fallback * **Name** and **position** (QB, WR, CB, OT, DE, …) * **Height · Weight** (e.g. `6'3" · 215 lbs`) * **High school** name * **Hometown** (City, State) * **Commitment pill** — `✔ Georgia` (green) or `○ Undecided` (grey) --- ## Data sources | Data | Source | |---|---| | Rankings, ratings, recruit details | [College Football Data API](https://collegefootballdata.com/) | | Player headshots | [On3](https://www.on3.com/) rankings page | CFBD provides a free API key with generous rate limits. The extension caches all external responses to minimise outbound requests. --- ## License MIT <https://ernestdefoe.online/extension/ernestdefoe/recruiting>
-
Flarum OG Image - Open Graph and Twitter meta tags
Flarum OG Image Automatically adds Open Graph and Twitter Card meta tags to every page of your Flarum 2 forum so that Facebook, Twitter/X, and other platforms generate rich link previews when discussions are shared or auto-posted. What it does| Page | Tags generated | |---|---| | Discussion | og:title, og:description, og:image, og:url, og:type, article:published_time, Twitter Card | | Forum index / other pages | og:title, og:description, og:image, og:url, og:type, Twitter Card | | All pages | og:site_name, fb:app_id (if configured) | Image selection priority for discussions: First <img> found in the first post's rendered HTML Default OG Image configured in Admin → Extensions → OG Image Requirements| Dependency | Version | |---|---| | PHP | ≥ 8.1 | | Flarum | ^2.0 | Installation php flarum cache:clear``` --- ## Configuration 1. Go to **Admin → Extensions → OG Image** 2. Set **Default OG Image URL** — an absolute URL to an image used when a discussion has no embedded images (e.g. your forum banner) * Facebook recommends **1200 × 630 px** minimum * Must be publicly accessible (no auth required) 3. Set **Facebook App ID** (optional but recommended) — removes the "missing fb:app_id" warning in the Facebook Sharing Debugger * Find your App ID at **developers.facebook.com → My Apps → your app → top-left of dashboard** 4. Click **Save Changes** --- ## How it works The extension hooks into Flarum's server-side document rendering. Before the HTML is sent to the browser, it: 1. Checks the request route — discussions are detected via Flarum's `routeName` attribute or by matching `/d/{id}` in the URL path 2. Loads the discussion and its first post from the database 3. Renders the first post to HTML, strips tags for the description excerpt, and scans for the first `<img>` to use as the OG image 4. Writes `<meta property="og:*">` and `<meta name="twitter:*">` tags into the `<head>` Because the tags are server-rendered, Facebook's scraper sees them immediately — no JavaScript execution required. --- ## Using with the Facebook Auto-Post extension Install both extensions. When the Facebook Auto-Post extension posts a discussion URL to your Facebook Page, Facebook scrapes that URL and reads the OG tags this extension provides, producing a rich preview card with the discussion title, excerpt, and image. --- ## Troubleshooting | Symptom | Likely cause | |---|---| | No preview on Facebook | Facebook cached the page before OG tags were present — use the Sharing Debugger to force a re-scrape | | Preview shows no image | Discussion has no `<img>` in the first post and no default image is configured | | Default image not showing | URL is not absolute, not publicly accessible, or returns a non-image content type | | Wrong description | The excerpt is taken from the first post only; BBcode/markdown is stripped | | "Invalid App ID" warning | You entered your Facebook Page ID instead of your App ID — they are different numbers | | Image not showing on auto-posted Facebook posts | See **HTTP 206 response** section below | --- ## HTTP 206 response — images not showing on Facebook posts The Facebook Sharing Debugger reports a **Response Code** for every URL it scrapes. If yours shows **206** instead of **200**, your web server is returning partial content to Facebook's crawler. This means Facebook may not receive the full `<head>` section where OG tags live, causing the image (and other tags) to be missing from link previews. **This is a web server configuration issue, not a Flarum or extension issue.** ### Nginx Add to your server block: ```proxy_force_ranges off;``` ### Apache Add to your `.htaccess` or virtual host config: ```BrowserMatch "facebookexternalhit" no-gzip``` ### Cloudflare If your site is proxied through Cloudflare, their edge cache may be serving partial responses. Add a Cache Rule to bypass caching for discussion pages: 1. Cloudflare dashboard → **Rules → Cache Rules** 2. Create a rule matching `fbsfb.com/d/*` 3. Set **Cache Status: Bypass** After making any of these changes, go to **developers.facebook.com/tools/debug**, paste your discussion URL, and click **Scrape Again**. Confirm the Response Code changes to **200** before testing image previews. --- ## License MIT © Ernestdefoe <https://ernestdefoe.online/extension/ernestdefoe/og-image>
-
Flarum Facebook Auto-Post
Flarum Facebook Auto-PostAutomatically publishes an excerpt and link to a Facebook Page or Group whenever a new discussion is created on your Flarum 2 forum. FeaturesPosts the discussion title, a short excerpt, and a link to your chosen Facebook destination Image always displays — posts as a photo upload so the image appears regardless of Facebook domain verification Supports both Facebook Pages (Page Access Token) and Facebook Groups (User Access Token) Toggle on/off from the Admin panel — no code changes needed Credentials stored securely in Flarum's settings store Optional tag filter — only post discussions in specific categories Works with ernestdefoe/og-image to use a default image when the post has no embedded images Logs success and errors to Flarum's application log (storage/logs/) Requirements| Dependency | Version | |---|---| | PHP | 8.3+ | | Flarum | ^2.0 | | PHP extension | curl | Installation php flarum migrate php flarum cache:clear``` --- ## Configuration Overview In the Flarum Admin panel go to **Extensions → Facebook Auto-Post** and set: | Setting | Description | |---|---| | **Enable Facebook Auto-Post** | Master on/off switch | | **Destination Type** | `Facebook Page` or `Facebook Group` | | **Facebook Page ID** | Numeric Page ID *(Page destination only)* | | **Page Access Token** | Never-expiring Page token *(Page destination only)* | | **Facebook Group ID** | Numeric Group ID *(Group destination only)* | | **Group Access Token** | Long-lived User Access Token *(Group destination only)* | Only the fields for the selected Destination Type are used — the others are ignored at runtime. --- ## Facebook Setup — Page This section covers posting to a **Facebook Page** you manage. ### Step 1 — Create a Facebook Developer Account 1. Go to **developers.facebook.com** 2. Click **Get Started** and log in with the Facebook account that manages your Page 3. Complete the free developer registration --- ### Step 2 — Create a Facebook App 1. Go to **My Apps → Create App** 2. When asked what the app will do, select **Other → Next** 3. For app type select **None → Next** 4. Give it any name (e.g. "My Forum") and click **Create App** --- ### Step 3 — Add Facebook Login to the App 1. Inside the App Dashboard click **Add Product** 2. Find **Facebook Login** and click **Set Up → Web** 3. Enter your forum URL and save This step is required before permissions can be granted to tokens. --- ### Step 4 — Get Your Page ID (Important — read carefully) The numeric ID in your Facebook Page URL (`profile.php?id=XXXXXXX`) is **not** always the correct API Page ID. To get the real API Page ID: 1. In the App Dashboard go to **Tools → Graph API Explorer** 2. Click **Generate Access Token** and add these permissions: * `pages_show_list` * `pages_read_engagement` * `pages_manage_posts` * `business_management` 3. Approve and log in 4. Set the method to **GET**, type `me/businesses` in the query box and click **Submit** 5. Note the `id` of each Business Account listed 6. For each business ID, run: `{business-id}/owned_pages?fields=id,name,access_token` 7. Find your Page — the `id` field is your **API Page ID** and `access_token` is your **Page Access Token** **Why this matters:** New Page Experience pages have a different internal API ID from the number shown in the browser URL. Using the wrong ID causes `(#100) The global id is not allowed` errors. --- ### Step 5 — Extend the Page Token The token from Step 4 may be short-lived. To get a long-lived token: 1. Copy the `access_token` from the `owned_pages` results 2. Go to **developers.facebook.com/tools/debug/accesstoken** and paste it in 3. Click **Extend Access Token** and copy the new token Page Access Tokens generated this way do not expire as long as the user who generated them remains an admin of the Page. --- ### Step 6 — Configure the Extension (Page) 1. Go to **Admin → Extensions → Facebook Auto-Post** 2. Set **Destination Type** to **Facebook Page** 3. Fill in: * **Facebook Page ID** — the `id` from the `owned_pages` results * **Page Access Token** — the extended token from Step 5 4. Toggle **Enable Facebook Auto-Post** to ON and click **Save** --- ## Facebook Setup — Group This section covers posting to a **Facebook Group** you administer. ### Step 1 — Create a Developer App Follow the same Steps 1–3 from the Page setup above. If you already have an app from the Page setup, you can reuse it. --- ### Step 2 — Connect the App to Your Group Facebook requires the app to be installed on the Group before it can post to it: 1. Go to your Facebook Group 2. Click **More → Settings → Apps** (or **Manage Group → Apps** depending on your group layout) 3. Click **Add Apps** and search for your app by name 4. Add it and confirm If you do not see an Apps section, make sure the Group type allows third-party integrations. Private groups may restrict this. --- ### Step 3 — Get Your Group ID The Group ID is the numeric segment of the Group URL: ```https://www.facebook.com/groups/123456789012345 └─────────────┘ this is your Group ID``` If your group uses a custom URL (e.g. `/groups/mygroupname`), go to the group, open the page source, and search for `"groupID"` to find the numeric value. --- ### Step 4 — Get a User Access Token with publish_to_groups 1. In the App Dashboard go to **Tools → Graph API Explorer** 2. Make sure your app is selected in the top-right dropdown 3. Click **Generate Access Token** and add the permission: * `publish_to_groups` 4. Approve and log in (you must log in as a Group admin or member) 5. Copy the token shown — this is your **User Access Token** --- ### Step 5 — Extend the User Token User Access Tokens expire after approximately 60 days even when extended. To get the maximum lifespan: 1. Copy your User Access Token 2. Go to **developers.facebook.com/tools/debug/accesstoken** and paste it in 3. Click **Extend Access Token** and copy the result **Important:** Unlike Page tokens, long-lived User Access Tokens cannot be made permanent. You will need to regenerate and update this token every \~60 days. Set a calendar reminder. --- ### Step 6 — Configure the Extension (Group) 1. Go to **Admin → Extensions → Facebook Auto-Post** 2. Set **Destination Type** to **Facebook Group** 3. Fill in: * **Facebook Group ID** — the numeric ID from Step 3 * **Group Access Token** — the extended User Access Token from Step 5 4. Toggle **Enable Facebook Auto-Post** to ON and click **Save** --- ## How It Works ```New discussion created (Flarum) │ ▼ PostDiscussionToFacebook::handle() │ ├── Is this post number 1? (only new discussions, not replies) ├── Is the extension enabled? ├── Destination Type = page or group? ├── Are the matching ID + token set? ├── Does the discussion pass the tag filter? │ ▼ Is an image available? (first <img> in post content, or ernestdefoe/og-image default image) │ ├── YES ──► POST /{id}/photos { url, caption, access_token } │ │ │ success? ──► Photo post with image on Facebook ✓ │ failure? ──► falls back to link post (see below) │ └── NO ───► POST /{id}/feed { message, link, access_token } │ Facebook Page/Group feed post with link preview ✓``` Only the **first post** of each discussion triggers a Facebook update. Replies are ignored. ### Why photo posts? Facebook suppresses link preview images for API posts from apps that have not completed Meta Business Manager domain verification. Posting via the `/photos` endpoint uploads the image directly to Facebook, so the image always appears regardless of whether your domain is verified. If the photo upload fails (e.g. the image URL is not publicly accessible), the extension automatically falls back to a standard link post and logs the reason. --- ## Troubleshooting | Symptom | Likely Cause | |---|---| | Nothing posted, no log entry | Extension not enabled or credentials not saved | | `API error (HTTP 400): Malformed access token` | Token copied incorrectly — re-copy with no extra spaces | | `API error (HTTP 400): The global id is not allowed` | Wrong Page ID — use the `id` from `owned_pages`, not the URL number | | `API error (HTTP 403): Missing permission` | Page token missing `pages_manage_posts` / `pages_read_engagement` — regenerate | | `API error (HTTP 400): #240 Requires a valid user` | Token has expired — generate and extend a new one | | `API error (HTTP 200)` but nothing appears in Group | App not connected to the Group — complete Step 2 of the Group setup | | `API error (HTTP 403): publish_to_groups not granted` | Token missing `publish_to_groups` — regenerate with that permission | | `API error (HTTP 400): User must be an admin` | The account that generated the Group token is not an admin of the Group | | `me/accounts returns empty` | Page managed via Business Suite — use `me/businesses` then `{business-id}/owned_pages` | | cURL errors | Server cannot reach `graph.facebook.com` on port 443 | | Post created but no image | Post has no embedded images and no default image is set — install `ernestdefoe/og-image` and configure a default image URL | | `Photo post API error` then `Falling back to link post` in logs | Image URL is not publicly accessible or Facebook could not fetch it — check the URL works without authentication | Logs are written to `storage/logs/flarum.log`. Search for `[FacebookPost]` to find relevant entries. --- ## Image display When a new discussion is posted the extension looks for an image in this order: 1. The first `<img>` found in the post content 2. The **Default OG Image** from the [ernestdefoe/og-image](https://github.com/ernestdefoe/og-image) extension settings If an image is found it is posted via Facebook's `/photos` endpoint, which uploads the image directly and guarantees it displays on the Page or Group post. If no image is found (or the photo upload fails) it falls back to a standard link post, which relies on Facebook scraping the OG tags from your forum URL. To ensure a fallback image is always available, install `ernestdefoe/og-image` and set a **Default OG Image URL** in its settings. --- ## License MIT © Ernestdefoe [https://ernestdefoe.online/extension/ernestdefoe/flarum-facebook-pos](https://ernestdefoe.online/extension/ernestdefoe/flarum-facebook-post)
-
ESPN CFB Ticker — Flarum 2 Extension
ESPN CFB Ticker — Flarum 2 Extension A live FBS college football scores ticker for Flarum 2 forums, powered by the free public ESPN API. Scores scroll automatically across a sticky banner at the top (or bottom) of your forum, updating on a configurable interval. Features🏈 FBS-only — filters ESPN's API to Group 80 (NCAA Division I FBS) 📡 Live scores with a pulsing green dot for in-progress games 🏆 AP/Coaches rankings — shows #N next to ranked teams (Top 25) 🖼️ Team logos pulled directly from ESPN's CDN ⚙️ Admin settings — toggle on/off, top/bottom position, refresh interval, scroll speed 📱 Responsive — logos hidden on narrow screens to keep ticker readable 🔁 Seamless loop — CSS-only marquee with no layout jank ♻️ Auto-refresh — polls ESPN API on a configurable interval (default 60s) RequirementsFlarum ^2.0 PHP ^8.3 No external PHP dependencies Installationcomposer require ernestdefoe/espn-cfb-ticker Then enable the extension in your Flarum admin panel under Extensions. Building JavaScriptIf you're contributing or modifying the extension: npm install npm run build # production build npm run dev # watch mode``` --- ## Admin Settings | Setting | Default | Description | |---|---|---| | Enable Ticker | `true` | Show/hide the ticker sitewide | | Position | `top` | Sticky top or fixed bottom | | Refresh Interval | `60` seconds | How often to poll ESPN API (min 15s) | | Scroll Speed | `40` | Per-game animation multiplier (lower = faster) | --- ## How It Works 1. On page load the extension mounts a Mithril component outside Flarum's main app node. 2. The component `fetch()`es `https://site.api.espn.com/apis/site/v2/sports/football/college-football/scoreboard?groups=80&limit=50` — the public ESPN API, no key required. 3. Games are rendered in a CSS `@keyframes` scroll animation. The track is duplicated so the loop is seamless. 4. A `setInterval` re-fetches on the configured interval, causing a Mithril redraw. --- ## ESPN API Notes The ESPN public scoreboard API (`site.api.espn.com`) is unofficial and undocumented but has been widely used by developers for years. It requires no API key and returns JSON. The `groups=80` parameter restricts results to FBS schools. --- ## License [MIT](https://github.com/ernestdefoe/espn-cfb-ticker/blob/main/LICENSE) © 2026 ernestdefoe <https://ernestdefoe.online/extension/ernestdefoe/espn-cfb-ticker>
- Feature request for Social Groups