Skip to content

Releases: dta121/moodle-local_aireader

AI Reader 1.3.1

04 Jun 17:50

Choose a tag to compare

AI Reader 1.3.1 fixes the Moodle completion setup for listening-based completion on Page and Book activities.

  • Saves AI Reader listening completion as automatic Moodle completion while clearing Moodle's native view-completion rule.
  • Prevents viewing a Page or Book from satisfying the requirement before the configured listening percentage is reached.
  • Allows teachers to leave Moodle's native completion option at None or choose Add requirements while enabling the AI Reader listening rule; AI Reader normalizes the stored completion mode on save.

Moodle Plugin CI passed on main: https://github.com/dta121/moodle-local_aireader/actions/runs/26969266262

AI Reader 1.3.0

04 Jun 17:03

Choose a tag to compare

AI Reader 1.3.0 Release Notes

Released: 2026-06-04

AI Reader 1.3.0 focuses on playback presentation, offline-friendly downloads,
and the first version of listening-based activity completion.

Highlights

  • Admins can choose between the full player and compact banner, pill,
    accordion, or inline designs, all using the same playback controls once
    expanded.
  • A new accent colour setting themes the player controls and in-page narration
    highlights.
  • Compact players can auto-play on expand when the browser allows it.
  • Learners can download narration MP3s when downloads are enabled. Downloaded
    files use human-readable filenames and remain gated by the same activity
    visibility and listening permissions as streamed playback.
  • Teachers can require learners to listen to a configured percentage of an AI
    Reader narration before Moodle marks a Page or Book activity complete.
  • Listening completion uses distinct played ranges from the embedded Moodle
    player, so scrubbing ahead does not satisfy the rule.
  • Downloaded or otherwise offline MP3 playback does not count toward listening
    completion in this release. Syncable offline listening is retained as a
    future enhancement.
  • Generated MP3s now include ID3 metadata for easier recognition in local music
    players.

Upgrade Notes

  • Site administrators must enable Allow listening-based activity completion
    before teachers see the per-activity completion fields.
  • Moodle activity completion must also be enabled on the target Page or Book
    activity for AI Reader to mark it complete.
  • The upgrade adds local_aireader_listen for per-learner listened ranges and
    local_aireader_completion for per-activity completion settings.
  • The plugin now stores listened ranges as privacy-covered user data and
    includes them in Moodle Privacy API export/delete flows.

v1.2.1 — Karaoke highlighting accuracy

30 May 01:32

Choose a tag to compare

A focused fix release for the karaoke-style in-place highlighting. A
number of sentences were narrated but never lit up on the page. The
highlighter matched each Whisper segment as a single contiguous run inside
one element and wrapped it with range.surroundContents(), which silently
dropped three classes of segment.

Fixed

  • Segments that begin with text not in the page body. The narration
    prepends the page title, and Whisper glues it onto the first body sentence;
    the title is rendered as the page heading (excluded chrome), so the combined
    text was never found — e.g. "Read this article and consider the following
    questions"
    stayed unhighlighted. A suffix fallback now drops leading
    words and matches the longest remaining run.
  • Segments that span an element boundary (a heading into a paragraph, one
    list item into the next, an inline <em>). These were found but
    surroundContents threw on the cross-element range and dropped them — the
    bulk of the misses. Matched runs are now wrapped as one <mark> per text
    node
    , all sharing the segment index and highlighting together.
  • Sentences that appear verbatim more than once (a body line restated in
    the summary). The search always resolved to the first copy, so the
    restatement never highlighted and the body copy was re-marked. A forward
    search cursor
    now resolves each segment to the correct occurrence.

Together these lift in-place highlight coverage substantially on
mixed-structure pages (headings, lists, summaries, callouts).

Notes

  • JavaScript-only change; version.php -> 1.2.1 rolls the JS revision so
    browsers fetch the rebuilt bundle. Empty upgrade savepoint, no schema change.
  • CI-verified green on Moodle 4.5 / 5.0 / 5.1 / 5.2 x PHP 8.1-8.4 x
    PostgreSQL / MariaDB.

Full changelog

See CHANGELOG.md. Compare: v1.2.0...v1.2.1

v1.2.0 — Configurable dated pricing + cost by course

29 May 12:30

Choose a tag to compare

Added

Admin-editable, date-effective TTS pricing

A new TTS pricing setting (Site admin → Plugins → AI Reader) replaces the hard-coded cost rates. Enter one model per line as model, rate, date:

  • rate = USD per 1,000,000 narration characters.
  • date (optional, YYYY-MM-DD) = effective-from date. Each asset is priced at the rate in force when it was generated, so raising a price only affects future audio — historical cost records stay accurate. To change a price going forward, add a new dated line; don't edit old ones.
  • * works as a catch-all model.

Defaults match the previous built-in rates, so nothing changes until an admin edits them. (There is no OpenAI pricing API to pull from, so rates are maintained here, not fetched live.)

Cost-by-course report

A new page under Site administration → Reports → AI Reader cost by course lists every course that generated narration, its audio count, and estimated total spend — ordered by spend, with a grand total. Linked from the audio log report.

Changed

  • The audio log's per-asset cost and summary total now use the configured, date-effective rates.

Full changelog: v1.1.0...v1.2.0

v1.1.0 — Audio generation log report

29 May 11:57

Choose a tag to compare

Added — Audio generation log report

A new page under Site administration → Reports → AI Reader audio log that lists every narration asset:

  • Context: the course and the activity (page, or book + chapter) the audio was generated for.
  • Details: language, model and voice, status, when it was generated, and the mp3 file size.
  • Estimated cost per asset, derived from the narration character count and the model used.
  • Failed generations are listed with their failure reason (e.g. "Narration text exceeds max (58501 > 50000 chars)").

The table is sortable, paged, filterable by status, and downloadable (CSV, Excel, etc.). A summary header shows counts by status and the estimated total spend.

Notes

  • Gated by a new local/aireader:viewlog capability (granted to managers by default), so managers can view it without full site-config access.
  • Cost (cost_calculator): tts-1 ($15) and tts-1-hd ($30) use OpenAI's published per-million-character rates; gpt-4o-mini-tts uses a blended $10/million-character estimate. Translation and Whisper alignment are billed separately and excluded. Figures are budgeting estimates, not an invoice.
  • A new nullable inputchars column on local_aireader_asset (captured at generation) drives the estimate. Audio generated before this release has no character count and shows an unknown cost ("—") until regenerated.

Full changelog: v1.0.5...v1.1.0

v1.0.5 — Moodle Plugin CI fixes

29 May 11:41

Choose a tag to compare

CI

Cleared the Moodle Plugin CI failures introduced during the 1.0.2–1.0.4 work. No functional change.

  • phpcs (--max-warnings 0): wrapped an over-length CJK regex line in openai_client, capitalized the lead word of three inline comments, and dropped the unnecessary MOODLE_INTERNAL guard from the two class-only backup/restore files (moodle.Files.MoodleInternalNotNeeded).
  • Grunt (--max-lint-warnings 0): rebuilt amd/build/player.min.js and its source map from amd/src/player.js, which had gained the GPL boilerplate header in 1.0.2 without a corresponding build regeneration. Rebuilt with Moodle's grunt (node 22 / lts/jod) and verified byte-identical via SHA-256; the minified bundle now carries the license banner.

Green across the full matrix: PHP 8.1–8.4 × Moodle 4.5/5.0/5.1/5.2 × MariaDB/PostgreSQL.

Full changelog: v1.0.4...v1.0.5

v1.0.4 — token-aware TTS chunking

29 May 11:23

Choose a tag to compare

Fixed

  • TTS failed with Input of N tokens is over the maximum input limit of 2000 tokens on gpt-4o-mini-tts, especially for translated (CJK) narration. chunk_text() split purely on character count (3800), which suits the tts-1 family's 4096-character limit but ignores the newer model's 2000-token cap — 3800 dense CJK characters are ~3000 tokens.
    • Chunking is now token-aware: a new estimate_tokens() heuristic (wide/CJK characters ~1 token each, other text ~4 chars/token, rounded up as a safe ceiling) plus an 1800-token default cap applied alongside the character cap.
    • Sentence splitting now recognises CJK terminators (。.!?), which carry no trailing whitespace — so unspaced Japanese/Chinese narration splits at real sentence boundaries instead of falling through to a single oversized hard cut.
    • Added unit tests for the token cap and the estimator.

Full changelog: v1.0.3...v1.0.4

v1.0.3 — load filelib.php (generate_audio fatal)

29 May 11:17

Choose a tag to compare

Fixed

  • generate_audio still fatalled after 1.0.2 with Call to undefined function file_rewrite_pluginfile_urls(). That function is defined in lib/filelib.php, which Moodle does not auto-load in the cron / adhoc-task context the extractor runs in, so the global was genuinely undefined there — the 1.0.2 namespace qualification only changed the error text, not the outcome. content_extractor::extract() now require_onces $CFG->libdir/filelib.php before use. Verified on the dev host that this makes the function available (html_to_text and get_coursemodule_from_id were already loaded). The 1.0.2 namespace qualifications are retained.

Full changelog: v1.0.2...v1.0.3

v1.0.2 — generate_audio fatal fix + Backup/Restore API

29 May 11:06

Choose a tag to compare

Fixed

  • Fatal in the generate_audio ad hoc task. Six global Moodle functions in content_extractor (file_rewrite_pluginfile_urls, get_coursemodule_from_id, format_string, html_to_text, get_string, get_string_manager) were called unqualified from inside the local_aireader\manager namespace, so PHP resolved them as undefined namespaced functions and every generation failed with Call to undefined function local_aireader\manager\file_rewrite_pluginfile_urls(). All are now fully qualified with a leading \.

Added

  • Backup / Restore API support. New module-level backup_local_plugin / restore_local_plugin classes (backup/moodle2/) carry the per-resource narration overrides (local_aireader_override, both activity-level and per-chapter) with a backed-up mod_page / mod_book activity. Restore is deferred to after_restore_module() so book_chapter id mappings exist before chapter-level overrides are remapped; stale chapter overrides are skipped, usermodified is remapped when users are included, and existing (cmid, chapterid) rows are not duplicated.

Packaging

  • Added the standard Moodle GPL boilerplate header to templates/manager_offline.mustache, templates/player.mustache, and amd/src/player.js (the latter also gains a proper @module/@copyright/@license docblock) for plugin-directory submission.

Full changelog: v1.0.0...v1.0.2

v1.0.0 — First stable release

19 May 19:17

Choose a tag to compare

First stable release. Plugin is now MATURITY_STABLE.

This release lands eight findings from an internal security audit, adds a PHPUnit test suite covering the new gates, and expands CI to every supported Moodle version.

🔒 Security

ID Title What it does
F1 Hidden chapter gate get_status, request_regen, and the pluginfile handler now reject hidden book chapters unless the caller has mod/book:viewhiddenchapters. Previously a learner with module-level access could narrate any chapter id.
F2 Lang allowlist enforcement The lang parameter on every external function is server-validated against the admin enabled_languages allowlist. Closes an OpenAI cost-amplification DoS.
F3 Pluginfile require_login scoping Uses require_login($course, false, $cm) so enrolment, course visibility, and activity availability rules all apply.
F4 Per-asset content cap New max_narration_chars admin setting (default 50000) refuses runaway pages before OpenAI is called.
F5 Unused capability removed local/aireader:purge was declared but never enforced. Gone.
F6 HTTPS-only outbound New http_guard helper rejects non-HTTPS endpoints, loopback, RFC1918 private addresses, link-local, and the cloud metadata service. Curl is locked to CURLPROTO_HTTPS.
F7 OpenAI error sanitization Bearer tokens, sk-… keys, and 40+ char opaque identifiers are redacted before being stored on the asset row or written to cron logs.
F8 Narrower web service return types Segment text now PARAM_NOTAGS, status messages PARAM_TEXT (previously PARAM_RAW).

✅ Testing

  • 31 PHPUnit tests across 5 files covering the URL guard, error sanitizer, override resolution chain, translation cache LRU, asset_manager helpers, and the get_status security gates.

🔁 CI

  • Matrix expanded from Moodle 4.5 only to Moodle 4.5 LTS / 5.0 / 5.1 / 5.2 on PHP 8.1 / 8.2 / 8.3 / 8.4 across both PostgreSQL 16 and MariaDB 10.11.
  • actions/checkout bumped to v5 (Node.js 24) ahead of GitHub's 2026-06-02 Node 20 deprecation.
  • Grunt step no longer has continue-on-error; CSS regressions and AMD-build drift now fail loudly.
  • Stylelint clean.

📦 Migration notes

  • No schema change. The upgrade savepoint at 2026051900 registers the max_narration_chars admin default; nothing else moves.
  • If you had a custom role using local/aireader:purge, that capability is gone — but it was a no-op anyway, so role behavior is unchanged.

📜 Full changelog

See CHANGELOG.md for the complete history from 0.1.0 through 1.0.0.