diff --git a/fp-plugins/mastodon/Function-Organigram.md b/fp-plugins/mastodon/Function-Organigram.md index a3add0e5..015d5baa 100644 --- a/fp-plugins/mastodon/Function-Organigram.md +++ b/fp-plugins/mastodon/Function-Organigram.md @@ -13,7 +13,7 @@ The layout is intentionally hierarchical so responsibilities, call paths, and im - Recently added FlatPress configuration reuse, centralized plugin-state detection, FlatPress I/O helpers, explicit `FILE_PERMISSIONS` handling for Mastodon runtime files, APCu-backed last-known-good state fallback, and file-backed synchronization cooldown guards are reflected explicitly. - The companion-plugin diagnostics for BBCode, PhotoSwipe, AudioVideo, Tag, and Emoticons are covered. - The admin-side Mastodon instance-information snapshot, manual refresh button, exact-version display, and reuse of cached instance capabilities for later sync runs are covered. -- Mastodon instance-dependent URL budgeting, bounded PHP execution-budget refreshes, central per-run Mastodon API rate-limit guards with request/media-upload/delete budgets, OAuth scope discovery with a strict `profile`-scope preference and an older-instance fallback to `read:accounts`, media-type-aware asynchronous media-upload readiness polling for longer AudioVideo processing, AudioVideo optional endtag descriptions for local export and remote import, best-effort cleanup of unattached uploaded Mastodon media before a failed final posting finishes, follow-up deletion synchronization, the admin toggle that can disable deletion synchronization, comment tombstones that block stale re-imports of deleted remote replies, early protection of locally deleted exported FlatPress comments before the next content sync, local reattachment of surviving descendant replies to the synchronized entry status during deletion follow-up, targeted descendant-recheck queues with a dedicated `comment_rechecks` follow-up scope, 5-minute scheduled-sync cooldown guards, state-write failure reporting, and the separate persistence of content-sync and deletion-sync counters are reflected explicitly. +- Mastodon instance-dependent URL budgeting, bounded PHP execution-budget refreshes, central per-run Mastodon API rate-limit guards with request/media-upload/delete budgets, persistent cross-run media-upload/delete/account-status-page windows, OAuth scope discovery with a strict `profile`-scope preference and an older-instance fallback to `read:accounts`, media-type-aware asynchronous media-upload readiness polling for longer AudioVideo processing, AudioVideo optional endtag descriptions for local export and remote import, scheduled-run recent-content windows, dirty queues for changed older mapped content, optional rotating old-thread reply checks, best-effort cleanup of unattached uploaded Mastodon media before a failed final posting finishes, follow-up deletion synchronization, the admin toggle that can disable deletion synchronization, comment tombstones that block stale re-imports of deleted remote replies, early protection of locally deleted exported FlatPress comments before the next content sync, local reattachment of surviving descendant replies to the synchronized entry status during deletion follow-up, targeted descendant-recheck queues with a dedicated `comment_rechecks` follow-up scope, 5-minute scheduled-sync cooldown guards, state-write failure reporting, and the separate persistence of content-sync and deletion-sync counters are reflected explicitly. - FlatPress timeoffset-aware remote import timestamps for Mastodon statuses and replies are reflected explicitly. - FlatPress-local admin time display for the daily synchronization time, last content sync, and last deletion sync is reflected explicitly while keeping the stored synchronization time in UTC. - Friendly deletion-sync guards for normalized comment mapping metadata are reflected explicitly. @@ -48,12 +48,12 @@ Current export behavior uses these endpoints in a version-aware way: - Remote media import retries alternate direct media URLs (`url`, `remote_url`, and for images `preview_url`) and uses longer transfer timeouts for downloaded media, so temporary object-storage or CDN failures do not immediately lose AudioVideo imports. - Current Mastodon media entities can be `image`, `gifv`, `video`, or `audio`; `audio` was added after early Mastodon media APIs, so import/export code must tolerate older payloads and unknown attachment types. - Instance-provided limits from `/api/v2/instance` are authoritative when available: `statuses.max_media_attachments`, `media_attachments.supported_mime_types`, `image_size_limit`, `video_size_limit`, `audio_size_limit`, and description limits. Public defaults still commonly include 4 media per status, one video/audio per status, and server-specific size/transcoding limits. -- Official Mastodon API rate limits are protected locally by a per-run guard: the plugin reserves a conservative general request budget, a media-upload budget, and a status-delete budget, and also observes `X-RateLimit-*` / `429` responses so a single large synchronization run stops before it repeatedly hammers the instance. +- Official Mastodon API rate limits are protected locally by a per-run guard and persistent cross-run windows: the plugin reserves a conservative general request budget, a media-upload budget, and a status-delete budget for each run, counts media uploads and status deletes across a 30-minute file-backed window, counts account-status paging across a 15-minute file-backed window, and also observes `X-RateLimit-*` / `429` responses so synchronization stops cleanly with a visible state/log error before it repeatedly hammers the instance. ## Function count -The plugin file currently contains **288** callable functions/methods documented in this organigram: -- **285** top-level plugin functions +The plugin file currently contains **311** callable functions/methods documented in this organigram: +- **308** top-level plugin functions - **3** admin panel class methods (`setup()`, `main()`, `onsubmit()`) ## High-level call flow @@ -68,22 +68,24 @@ The plugin file currently contains **288** callable functions/methods documented - `plugin_mastodon_maybe_sync()` Reads the saved state for the current request and then either: - runs `plugin_mastodon_run_sync()` when the scheduled content sync is due, or - - runs `plugin_mastodon_run_deletion_sync()` in a later follow-up request when deletion work is pending. + - runs `plugin_mastodon_run_deletion_sync()` in a later follow-up request when deletion work is pending and its persisted not-before timestamp has passed. - `plugin_mastodon_run_sync()` Refreshes the shared-request PHP execution budget through `plugin_mastodon_extend_time_limit()`, protects locally deleted exported FlatPress comment mappings through `plugin_mastodon_protect_locally_deleted_exported_comments()` so stale Mastodon thread context cannot resurrect them during the same request, and then executes the two main directions in order: 1. `plugin_mastodon_sync_remote_to_local()` 2. `plugin_mastodon_sync_local_to_remote()` - Non-forced scheduled runs are gated by `plugin_mastodon_sync_guard_active()` and mark a 5-minute `content` cooldown through `plugin_mastodon_sync_guard_mark()` before network work starts. After acquiring the sync lock, the function starts a per-run Mastodon API guard through `plugin_mastodon_rate_limit_guard_start()`, so general requests, media uploads, and status deletions stay inside conservative budgets and Mastodon `X-RateLimit-*` headers can stop the run cleanly. If both directions finish successfully, the function marks a follow-up deletion pass as pending for a later web request. A completed sync is reported as failed if `plugin_mastodon_state_write()` cannot persist the updated state; the updated state is still placed in the short APCu fallback cache when APCu is available. + Non-forced scheduled runs are gated by `plugin_mastodon_sync_guard_active()` and mark a 5-minute `content` cooldown through `plugin_mastodon_sync_guard_mark()` before network work starts. After acquiring the sync lock, the function starts a per-run Mastodon API guard through `plugin_mastodon_rate_limit_guard_start()`, so general requests, media uploads, status deletes, persistent cross-run media/delete windows, persistent account-status paging windows, and Mastodon `X-RateLimit-*` headers can stop the run cleanly. If both directions finish successfully, the function marks a follow-up deletion pass as pending for a later web request and stores a `deletions_not_before` timestamp at least 5 minutes after the completed content sync. A completed sync is reported as failed if `plugin_mastodon_state_write()` cannot persist the updated state; the updated state is still placed in the short APCu fallback cache when APCu is available. - `plugin_mastodon_run_deletion_sync()` - Runs the real deletion synchronization in a separate follow-up request. It first checks `plugin_mastodon_should_run_deletion_sync()` so the user-controlled admin toggle can disable the delete pass, applies the same 5-minute cooldown guard with the `deletion` kind for non-forced scheduled runs, starts the per-run Mastodon API guard after acquiring the sync lock, resets only `deletion_stats` while preserving `content_stats` from the last content sync, gates mapped items through `plugin_mastodon_mapping_matches_sync_start()` so the delete pass stays inside the configured sync-start window, then either runs the full reconciliation pass or a targeted `comment_rechecks` follow-up scope via `plugin_mastodon_state_has_comment_recheck_scope()`. Direct descendants of newly deleted replies are queued with `plugin_mastodon_queue_comment_descendant_remote_rechecks()`, surviving direct child replies can be reattached to the synchronized entry status through `plugin_mastodon_reattach_local_comment_to_entry_status()`, and pending deep-thread cleanup is processed breadth-first through `plugin_mastodon_process_pending_comment_remote_rechecks()`. A successful delete pass is reported as failed if the resulting state cannot be persisted. + Runs the real deletion synchronization in a separate follow-up request. It first checks `plugin_mastodon_should_run_deletion_sync()` so the user-controlled admin toggle can disable the delete pass, then checks `plugin_mastodon_deletion_sync_due()` so the delete pass cannot start before the persisted 5-minute separation window has passed. It applies the same 5-minute cooldown guard with the `deletion` kind for non-forced scheduled runs, starts the per-run Mastodon API guard after acquiring the sync lock, and every remote status delete must pass both the per-run delete budget and the persistent cross-run delete window. It resets only `deletion_stats` while preserving `content_stats` from the last content sync, gates mapped items through `plugin_mastodon_mapping_matches_sync_start()` so the delete pass stays inside the configured sync-start window, then either runs the full reconciliation pass or a targeted `comment_rechecks` follow-up scope via `plugin_mastodon_state_has_comment_recheck_scope()`. Direct descendants of newly deleted replies are queued with `plugin_mastodon_queue_comment_descendant_remote_rechecks()`, surviving direct child replies can be reattached to the synchronized entry status through `plugin_mastodon_reattach_local_comment_to_entry_status()`, and pending deep-thread cleanup is processed breadth-first through `plugin_mastodon_process_pending_comment_remote_rechecks()`. A successful delete pass is reported as failed if the resulting state cannot be persisted. ### Local → remote export path - `plugin_mastodon_sync_local_to_remote()` - loads options and persisted state - retrieves export candidates with `plugin_mastodon_list_local_entries()` + - applies the permanent `sync_start_date` lower bound and, for scheduled non-forced runs, the selected 7/14/30-day recent-content window + - queues changed older mapped entries and comments through the dirty queue so edits outside the scheduled window are still sent to Mastodon without exporting unrelated old content - orders entries with `plugin_mastodon_compare_local_entries_for_export()` - builds entry text with `plugin_mastodon_build_entry_status_text()` - budgets status length via `plugin_mastodon_instance_url_reserved_length()`, `plugin_mastodon_status_text_length()`, and `plugin_mastodon_limit_status_text()` @@ -111,6 +113,7 @@ The plugin file currently contains **288** callable functions/methods documented - consumes pre-seeded tombstones from `plugin_mastodon_protect_locally_deleted_exported_comments()` so deleted exported FlatPress comments are not resurrected from later Mastodon thread-context refreshes - validates credentials with `plugin_mastodon_verify_credentials()` - fetches account statuses with `plugin_mastodon_fetch_account_statuses()` + - applies the permanent `sync_start_date` lower bound and, for scheduled non-forced runs, the selected 7/14/30-day recent-content window to newly fetched statuses - imports top-level statuses with `plugin_mastodon_import_remote_entry()` and deliberately skips reply statuses as entry imports when `import_synced_comments_as_entries` is disabled, because Mastodon account timelines may still include self-replies even with `exclude_replies=true` - fetches thread context with `plugin_mastodon_fetch_status_context()` - imports replies with `plugin_mastodon_import_remote_context_descendants()` @@ -118,12 +121,12 @@ The plugin file currently contains **288** callable functions/methods documented - imports remote audio/video descriptions as optional `[audioplayer]...[/audioplayer]` and `[videoplayer]...[/videoplayer]` text so FlatPress keeps Mastodon alt text locally - blocks stale re-imports of deleted remote replies via `plugin_mastodon_state_has_comment_tombstone()` - optionally prepends a BBCode quote of the replied-to Mastodon comment (or the previously exported FlatPress comment) so FlatPress readers can see who was answered and what was answered to - - refreshes known older threads with `plugin_mastodon_collect_known_entry_context_targets()` + - refreshes newly fetched thread contexts immediately and, when enabled in the admin panel, refreshes older known synchronized threads through `plugin_mastodon_collect_known_entry_context_targets()` in bounded rotating batches while still skipping mappings outside the configured synchronization start-date window - aborts the import direction with the rate-limit reason when the central per-run guard has blocked further API work ### Admin panel flow -- `plugin_mastodon_admin_assign()` assigns the plugin data for the Smarty view, including companion-plugin diagnostics, cached Mastodon instance-information rows, and cache-state/error metadata. +- `plugin_mastodon_admin_assign()` assigns the plugin data for the Smarty view, including companion-plugin diagnostics, the scheduled-window radio choices, the old-thread reply-check toggle, cached Mastodon instance-information rows, and cache-state/error metadata. - `setup()` registers the admin template and delegates to `plugin_mastodon_admin_assign()`. - `main()` keeps the FlatPress admin lifecycle stable. - `onsubmit()` handles: @@ -136,313 +139,336 @@ The plugin file currently contains **288** callable functions/methods documented ## A. Entry points and admin integration -- `plugin_mastodon_head()` — line 1864 — Print Mastodon profile metadata into the HTML head. -- `plugin_mastodon_maybe_sync()` — line 8513 — Run the scheduled synchronization when the current request is due. -- `plugin_mastodon_run_sync()` — line 8434 — Run a full synchronization cycle. -- `plugin_mastodon_run_deletion_sync()` — line 8192 — Run the deferred deletion synchronization in a follow-up request after content sync completed. -- `plugin_mastodon_sync_due()` — line 8412 — Determine whether the scheduled synchronization is currently due. -- `plugin_mastodon_admin_assign()` — line 8675 — Assign plugin data to Smarty for the admin panel, including cached instance-information rows. -- `setup()` — line 8720 — Register the Mastodon admin panel template and assign plugin data to Smarty. -- `main()` — line 8725 — Keep the admin panel lifecycle compatible with FlatPress without extra processing. -- `onsubmit()` — line 8729 — Process configuration saves, OAuth actions, authorization-code exchange, manual instance-information refreshes, and the manual synchronization trigger. +- `plugin_mastodon_head()` — line 2110 — Print Mastodon profile metadata into the HTML head. +- `plugin_mastodon_maybe_sync()` — line 9086 — Run the scheduled synchronization when the current request is due. +- `plugin_mastodon_run_sync()` — line 9006 — Run a full synchronization cycle. +- `plugin_mastodon_run_deletion_sync()` — line 8759 — Run the deferred deletion synchronization in a follow-up request after content sync completed. +- `plugin_mastodon_sync_due()` — line 8984 — Determine whether the scheduled synchronization is currently due. +- `plugin_mastodon_admin_assign()` — line 9248 — Assign plugin data to Smarty for the admin panel, including cached instance-information rows. +- `setup()` — line 9296 — Register the Mastodon admin panel template and assign plugin data to Smarty. +- `main()` — line 9301 — Keep the admin panel lifecycle compatible with FlatPress without extra processing. +- `onsubmit()` — line 9305 — Process configuration saves, OAuth actions, authorization-code exchange, manual instance-information refreshes, and the manual synchronization trigger. ## B. Defaults, configuration, secrets, and centralized FlatPress feature toggles -- `plugin_mastodon_default_options()` — line 77 — Return the default plugin option values. -- `plugin_mastodon_clear_saved_instance_info()` — line 107 — Remove persisted instance-information snapshots and related admin refresh errors from an option array. -- `plugin_mastodon_compact_instance_document()` — line 126 — Reduce `/api/v2/instance` responses to the stable subset reused by the plugin and admin table. -- `plugin_mastodon_saved_instance_document()` — line 331 — Decode the persisted compact instance snapshot from the saved plugin configuration. -- `plugin_mastodon_store_instance_document()` — line 376 — Persist a compact instance-information snapshot in the FlatPress plugin configuration and warm request/APCu caches. -- `plugin_mastodon_store_instance_error()` — line 422 — Persist the latest instance-information refresh failure for the admin diagnostics view. -- `plugin_mastodon_refresh_instance_information()` — line 438 — Force a live `/api/v2/instance` refresh, compact the response, and store it for later requests. -- `plugin_mastodon_default_content_stats()` — line 458 — Return the default counters for the last content synchronization. -- `plugin_mastodon_default_deletion_stats()` — line 475 — Return the default counters for the last deletion synchronization. -- `plugin_mastodon_default_state()` — line 488 — Return the default runtime state structure, including the targeted deletion-follow-up scope marker. -- `plugin_mastodon_oauth_legacy_scopes()` — line 512 — Return the legacy OAuth scope string used before scope discovery was added. -- `plugin_mastodon_oauth_profile_scopes()` — line 520 — Return the stricter OAuth scope string that uses `profile` for `verify_credentials`. -- `plugin_mastodon_oauth_server_metadata()` — line 529 — Discover and cache OAuth authorization-server metadata from `/.well-known/oauth-authorization-server`. -- `plugin_mastodon_oauth_supported_scopes()` — line 548 — Parse the discoverable OAuth scopes supported by the configured Mastodon instance. -- `plugin_mastodon_oauth_scope_supported()` — line 585 — Check whether the configured Mastodon instance advertises support for a specific OAuth scope. -- `plugin_mastodon_oauth_preferred_scopes()` — line 606 — Prefer the narrow `profile` scope on current instances and fall back to `read:accounts` on older ones. -- `plugin_mastodon_oauth_scopes()` — line 623 — Return the OAuth scopes that the currently registered app may safely request. -- `plugin_mastodon_fp_config()` — line 700 — Return the FlatPress configuration, preferring the early-loaded core cache. -- `plugin_mastodon_fp_config_value()` — line 736 — Read a nested FlatPress configuration value. -- `plugin_mastodon_fp_timeoffset_seconds()` — line 752 — Return the configured FlatPress time offset in seconds for exact local admin-time conversion. -- `plugin_mastodon_fp_timeoffset_label()` — line 772 — Format the configured FlatPress time offset as a UTC label for admin users. -- `plugin_mastodon_sync_time_to_minutes()` — line 802 — Convert a normalized `HH:MM` sync-time value into minutes after midnight. -- `plugin_mastodon_minutes_to_sync_time()` — line 813 — Convert minutes after midnight back into a normalized `HH:MM` sync-time value. -- `plugin_mastodon_sync_time_utc_to_local()` — line 829 — Convert the stored UTC synchronization time to the FlatPress-local admin time. -- `plugin_mastodon_sync_time_local_to_utc()` — line 840 — Convert the FlatPress-local admin synchronization time back to stored UTC. -- `plugin_mastodon_format_admin_datetime()` — line 851 — Format stored UTC timestamps for the admin panel using FlatPress `timeoffset`, date format, and time format. -- `plugin_mastodon_fp_timeoffset_hours()` — line 764 — Return the configured FlatPress time offset in whole hours. -- `plugin_mastodon_get_options()` — line 1517 — Load the saved plugin options and merge them with defaults. -- `plugin_mastodon_save_options()` — line 1572 — Persist plugin options, invalidate mismatching instance snapshots on URL changes, and keep matching refreshed snapshots. -- `plugin_mastodon_secret_key()` — line 1657 — Build the encryption key used for stored secrets. -- `plugin_mastodon_secret_encode()` — line 1674 — Encode a secret value before storing it in the configuration. -- `plugin_mastodon_secret_decode()` — line 1697 — Decode a previously stored secret value. -- `plugin_mastodon_normalize_instance_url()` — line 1728 — Normalize the configured Mastodon instance URL. -- `plugin_mastodon_normalize_head_username()` — line 1761 — Normalize the configured Mastodon username for HTML head metadata. -- `plugin_mastodon_instance_authority()` — line 1800 — Return the Mastodon instance authority used in fediverse creator metadata. -- `plugin_mastodon_profile_url()` — line 1830 — Build the public Mastodon profile URL used for the rel-me link. -- `plugin_mastodon_fediverse_creator_value()` — line 1846 — Build the fediverse creator meta value. -- `plugin_mastodon_normalize_status_language()` — line 1904 — Normalize a FlatPress locale string to a Mastodon-compatible ISO 639-1 code. -- `plugin_mastodon_configured_status_language()` — line 1926 — Read the configured FlatPress locale and return the Mastodon language code. -- `plugin_mastodon_normalize_sync_time()` — line 1946 — Normalize the configured daily sync time. -- `plugin_mastodon_normalize_sync_start_date()` — line 1963 — Normalize the configured sync start date. -- `plugin_mastodon_normalize_boolean_option()` — line 1989 — Normalize a boolean-like option value to the stored string representation. -- `plugin_mastodon_normalize_update_local_from_remote()` — line 2005 — Normalize the toggle that controls whether existing local content may be updated from remote Mastodon data. -- `plugin_mastodon_should_update_local_from_remote()` — line 2014 — Check whether remote Mastodon updates may overwrite already existing local FlatPress content. -- `plugin_mastodon_normalize_import_synced_comments_as_entries()` — line 2023 — Normalize the toggle that allows importing already synchronized local comments as entries. -- `plugin_mastodon_should_import_synced_comments_as_entries()` — line 2032 — Check whether a remote Mastodon status that is already mapped to a local FlatPress comment may also be imported as an entry. -- `plugin_mastodon_normalize_quote_imported_reply_parent()` — line 2041 — Normalize the toggle that controls whether imported Mastodon replies should quote the replied-to comment. -- `plugin_mastodon_should_quote_imported_reply_parent()` — line 2050 — Check whether imported Mastodon replies should include a quote of the replied-to comment. -- `plugin_mastodon_normalize_delete_sync_enabled()` — line 2059 — Normalize the toggle that enables or disables the follow-up deletion synchronization. -- `plugin_mastodon_should_run_deletion_sync()` — line 2068 — Check whether the follow-up deletion synchronization is enabled. -- `plugin_mastodon_enabled_plugin_state()` — line 3418 — Determine whether a FlatPress plugin is enabled in the centralized plugin configuration. -- `plugin_mastodon_tag_plugin_active()` — line 3483 — Determine whether the Tag plugin is active for the current FlatPress request. -- `plugin_mastodon_bbcode_plugin_active()` — line 3500 — Determine whether the BBCode plugin is active for the current FlatPress request. -- `plugin_mastodon_photoswipe_plugin_active()` — line 3517 — Determine whether the PhotoSwipe plugin is active for the current FlatPress request. -- `plugin_mastodon_audiovideo_plugin_active()` — line 3534 — Determine whether the AudioVideo plugin is active for the current FlatPress request. -- `plugin_mastodon_emoticons_plugin_active()` — line 3551 — Determine whether the Emoticons plugin is active for the current FlatPress request. -- `plugin_mastodon_companion_plugins_status()` — line 3566 — Return the status of companion FlatPress plugins used for the full Mastodon feature set. +- `plugin_mastodon_default_options()` — line 101 — Return the default plugin option values. +- `plugin_mastodon_clear_saved_instance_info()` — line 128 — Remove persisted instance-information snapshots and related admin refresh errors from an option array. +- `plugin_mastodon_compact_instance_document()` — line 147 — Reduce `/api/v2/instance` responses to the stable subset reused by the plugin and admin table. +- `plugin_mastodon_saved_instance_document()` — line 352 — Decode the persisted compact instance snapshot from the saved plugin configuration. +- `plugin_mastodon_store_instance_document()` — line 397 — Persist a compact instance-information snapshot in the FlatPress plugin configuration and warm request/APCu caches. +- `plugin_mastodon_store_instance_error()` — line 443 — Persist the latest instance-information refresh failure for the admin diagnostics view. +- `plugin_mastodon_refresh_instance_information()` — line 459 — Force a live `/api/v2/instance` refresh, compact the response, and store it for later requests. +- `plugin_mastodon_default_content_stats()` — line 479 — Return the default counters for the last content synchronization. +- `plugin_mastodon_default_deletion_stats()` — line 496 — Return the default counters for the last deletion synchronization. +- `plugin_mastodon_default_state()` — line 514 — Return the default runtime state structure, including the targeted deletion-follow-up scope marker. +- `plugin_mastodon_oauth_legacy_scopes()` — line 534 — Return the legacy OAuth scope string used before scope discovery was added. +- `plugin_mastodon_oauth_profile_scopes()` — line 542 — Return the stricter OAuth scope string that uses `profile` for `verify_credentials`. +- `plugin_mastodon_oauth_server_metadata()` — line 551 — Discover and cache OAuth authorization-server metadata from `/.well-known/oauth-authorization-server`. +- `plugin_mastodon_oauth_supported_scopes()` — line 570 — Parse the discoverable OAuth scopes supported by the configured Mastodon instance. +- `plugin_mastodon_oauth_scope_supported()` — line 607 — Check whether the configured Mastodon instance advertises support for a specific OAuth scope. +- `plugin_mastodon_oauth_preferred_scopes()` — line 628 — Prefer the narrow `profile` scope on current instances and fall back to `read:accounts` on older ones. +- `plugin_mastodon_oauth_scopes()` — line 645 — Return the OAuth scopes that the currently registered app may safely request. +- `plugin_mastodon_fp_config()` — line 722 — Return the FlatPress configuration, preferring the early-loaded core cache. +- `plugin_mastodon_fp_config_value()` — line 758 — Read a nested FlatPress configuration value. +- `plugin_mastodon_fp_timeoffset_seconds()` — line 774 — Return the configured FlatPress time offset in seconds for exact local admin-time conversion. +- `plugin_mastodon_fp_timeoffset_label()` — line 794 — Format the configured FlatPress time offset as a UTC label for admin users. +- `plugin_mastodon_sync_time_to_minutes()` — line 824 — Convert a normalized `HH:MM` sync-time value into minutes after midnight. +- `plugin_mastodon_minutes_to_sync_time()` — line 835 — Convert minutes after midnight back into a normalized `HH:MM` sync-time value. +- `plugin_mastodon_sync_time_utc_to_local()` — line 851 — Convert the stored UTC synchronization time to the FlatPress-local admin time. +- `plugin_mastodon_sync_time_local_to_utc()` — line 862 — Convert the FlatPress-local admin synchronization time back to stored UTC. +- `plugin_mastodon_format_admin_datetime()` — line 873 — Format stored UTC timestamps for the admin panel using FlatPress `timeoffset`, date format, and time format. +- `plugin_mastodon_fp_timeoffset_hours()` — line 786 — Return the configured FlatPress time offset in whole hours. +- `plugin_mastodon_get_options()` — line 1752 — Load the saved plugin options and merge them with defaults. +- `plugin_mastodon_save_options()` — line 1811 — Persist plugin options, invalidate mismatching instance snapshots on URL changes, and keep matching refreshed snapshots. +- `plugin_mastodon_secret_key()` — line 1888 — Build the encryption key used for stored secrets. +- `plugin_mastodon_secret_encode()` — line 1905 — Encode a secret value before storing it in the configuration. +- `plugin_mastodon_secret_decode()` — line 1928 — Decode a previously stored secret value. +- `plugin_mastodon_normalize_instance_url()` — line 1959 — Normalize the configured Mastodon instance URL. +- `plugin_mastodon_normalize_head_username()` — line 1992 — Normalize the configured Mastodon username for HTML head metadata. +- `plugin_mastodon_instance_authority()` — line 2031 — Return the Mastodon instance authority used in fediverse creator metadata. +- `plugin_mastodon_profile_url()` — line 2061 — Build the public Mastodon profile URL used for the rel-me link. +- `plugin_mastodon_fediverse_creator_value()` — line 2077 — Build the fediverse creator meta value. +- `plugin_mastodon_normalize_status_language()` — line 2135 — Normalize a FlatPress locale string to a Mastodon-compatible ISO 639-1 code. +- `plugin_mastodon_configured_status_language()` — line 2157 — Read the configured FlatPress locale and return the Mastodon language code. +- `plugin_mastodon_normalize_sync_time()` — line 2177 — Normalize the configured daily sync time. +- `plugin_mastodon_normalize_sync_start_date()` — line 2194 — Normalize the configured sync start date. +- `plugin_mastodon_normalize_scheduled_window_days()` — line 2235 — Normalize the configured 7/14/30-day automatic window for scheduled runs. +- `plugin_mastodon_scheduled_window_choices()` — line 2247 — Return the localized admin radio choices for the scheduled synchronization window. +- `plugin_mastodon_normalize_boolean_option()` — line 2220 — Normalize a boolean-like option value to the stored string representation. +- `plugin_mastodon_normalize_update_local_from_remote()` — line 2236 — Normalize the toggle that controls whether existing local content may be updated from remote Mastodon data. +- `plugin_mastodon_should_update_local_from_remote()` — line 2245 — Check whether remote Mastodon updates may overwrite already existing local FlatPress content. +- `plugin_mastodon_normalize_import_synced_comments_as_entries()` — line 2254 — Normalize the toggle that allows importing already synchronized local comments as entries. +- `plugin_mastodon_should_import_synced_comments_as_entries()` — line 2263 — Check whether a remote Mastodon status that is already mapped to a local FlatPress comment may also be imported as an entry. +- `plugin_mastodon_normalize_quote_imported_reply_parent()` — line 2272 — Normalize the toggle that controls whether imported Mastodon replies should quote the replied-to comment. +- `plugin_mastodon_should_quote_imported_reply_parent()` — line 2281 — Check whether imported Mastodon replies should include a quote of the replied-to comment. +- `plugin_mastodon_normalize_old_thread_reply_check()` — line 2330 — Normalize the toggle that enables rotating context checks for old synchronized Mastodon threads. +- `plugin_mastodon_should_check_old_thread_replies()` — line 2339 — Check whether old synchronized Mastodon threads should be checked for replies in rotating batches. +- `plugin_mastodon_normalize_delete_sync_enabled()` — line 2290 — Normalize the toggle that enables or disables the follow-up deletion synchronization. +- `plugin_mastodon_should_run_deletion_sync()` — line 2299 — Check whether the follow-up deletion synchronization is enabled. +- `plugin_mastodon_enabled_plugin_state()` — line 3681 — Determine whether a FlatPress plugin is enabled in the centralized plugin configuration. +- `plugin_mastodon_tag_plugin_active()` — line 3746 — Determine whether the Tag plugin is active for the current FlatPress request. +- `plugin_mastodon_bbcode_plugin_active()` — line 3763 — Determine whether the BBCode plugin is active for the current FlatPress request. +- `plugin_mastodon_photoswipe_plugin_active()` — line 3780 — Determine whether the PhotoSwipe plugin is active for the current FlatPress request. +- `plugin_mastodon_audiovideo_plugin_active()` — line 3797 — Determine whether the AudioVideo plugin is active for the current FlatPress request. +- `plugin_mastodon_emoticons_plugin_active()` — line 3814 — Determine whether the Emoticons plugin is active for the current FlatPress request. +- `plugin_mastodon_companion_plugins_status()` — line 3829 — Return the status of companion FlatPress plugins used for the full Mastodon feature set. ## C. Caching, filesystem helpers, logging, and persisted state -- `plugin_mastodon_runtime_cache_get()` — line 643 — Return a value from the request-local plugin cache. -- `plugin_mastodon_runtime_cache_set()` — line 667 — Store a value in the request-local plugin cache. -- `plugin_mastodon_runtime_cache_clear()` — line 685 — Clear one request-local plugin cache bucket or the complete cache. -- `plugin_mastodon_io_read_file()` — line 885 — Read a file through the FlatPress I/O layer when available. -- `plugin_mastodon_io_read_file_uncached()` — line 898 — Read a file without FlatPress request-local caches. -- `plugin_mastodon_file_permissions_mode()` — line 913 — Return the FlatPress `FILE_PERMISSIONS` mode used for plugin runtime files. -- `plugin_mastodon_apply_file_permissions()` — line 922 — Apply FlatPress `FILE_PERMISSIONS` to a plugin runtime file. -- `plugin_mastodon_io_write_file()` — line 935 — Write a file through the FlatPress I/O layer when available and enforce `FILE_PERMISSIONS` after successful writes. -- `plugin_mastodon_io_append_file()` — line 956 — Append to a file via the FlatPress I/O layer. -- `plugin_mastodon_apcu_enabled()` — line 972 — Check whether shared APCu caching is available for the plugin. -- `plugin_mastodon_apcu_cache_key()` — line 981 — Build the namespaced APCu key used by this plugin. -- `plugin_mastodon_apcu_fetch()` — line 995 — Fetch a value from APCu through the FlatPress namespace helper. -- `plugin_mastodon_apcu_store()` — line 1010 — Store a value in APCu through the FlatPress namespace helper. -- `plugin_mastodon_apcu_delete()` — line 1023 — Delete a value from APCu using the FlatPress namespace key builder. -- `plugin_mastodon_state_fallback_key()` — line 1035 — Return the APCu key used for the short-lived last-known-good state fallback. -- `plugin_mastodon_state_fallback_store()` — line 1044 — Store a normalized runtime state in APCu for a 5-minute emergency fallback window. -- `plugin_mastodon_state_fallback_read()` — line 1056 — Fetch the short-lived last-known-good state fallback from APCu. -- `plugin_mastodon_sync_guard_kind()` — line 1074 — Normalize the cooldown guard kind. -- `plugin_mastodon_sync_guard_apcu_key()` — line 1084 — Return the APCu key used for one sync cooldown guard. -- `plugin_mastodon_sync_guard_entry_active()` — line 1094 — Check whether one cooldown guard entry is still active. -- `plugin_mastodon_sync_guard_apcu_store()` — line 1105 — Store one cooldown guard in APCu. -- `plugin_mastodon_sync_guard_file_read()` — line 1118 — Read and prune the file-backed cooldown guard. -- `plugin_mastodon_sync_guard_file_write()` — line 1145 — Write the file-backed cooldown guard. -- `plugin_mastodon_sync_guard_active()` — line 1166 — Check whether a recent scheduled content/deletion pass is still cooling down. -- `plugin_mastodon_sync_guard_mark()` — line 1191 — Mark a 5-minute cooldown guard for a scheduled sync/deletion pass. -- `plugin_mastodon_sync_guard_clear()` — line 1212 — Clear one sync cooldown guard. -- `plugin_mastodon_rate_limit_default_budgets()` — line 1227 — Return conservative per-run Mastodon API budgets. -- `plugin_mastodon_rate_limit_guard_start()` — line 1252 — Start the per-run Mastodon API rate-limit guard. -- `plugin_mastodon_rate_limit_guard_stop()` — line 1276 — Stop the per-run Mastodon API rate-limit guard while keeping its summary inspectable. -- `plugin_mastodon_rate_limit_guard_active()` — line 1287 — Check whether the per-run Mastodon API guard is active. -- `plugin_mastodon_rate_limit_guard_summary()` — line 1295 — Return the current per-run rate-limit guard summary. -- `plugin_mastodon_rate_limit_headers()` — line 1304 — Normalize response headers for rate-limit checks. -- `plugin_mastodon_rate_limit_request_kind()` — line 1325 — Classify Mastodon API requests as general, media upload, or status deletion. -- `plugin_mastodon_rate_limit_block()` — line 1347 — Mark the current run as blocked by a rate-limit budget or Mastodon response and log the stop reason with budget counters once per run. -- `plugin_mastodon_rate_limit_acquire()` — line 1383 — Reserve request/media/delete budget before a Mastodon API request proceeds. -- `plugin_mastodon_rate_limit_observe_response()` — line 1424 — Update the guard from `X-RateLimit-*` headers and `429` responses. -- `plugin_mastodon_rate_limit_blocked_reason()` — line 1448 — Return the current rate-limit block reason. -- `plugin_mastodon_rate_limit_blocked_response()` — line 1460 — Build the synthetic `429` response used for locally blocked requests. -- `plugin_mastodon_rate_limit_state_error()` — line 1476 — Return the rate-limit reason that should be written to synchronization state. -- `plugin_mastodon_file_prestat()` — line 1486 — Read a cheap file metadata snapshot for cache validation. -- `plugin_mastodon_file_prestat_signature()` — line 1506 — Convert a file metadata snapshot into a stable cache signature. -- `plugin_mastodon_ensure_state_dir()` — line 2241 — Ensure that the plugin runtime directory exists. -- `plugin_mastodon_log()` — line 2250 — Append a line to the plugin sync log. -- `plugin_mastodon_state_read()` — line 2260 — Load the persisted runtime state from disk and fall back to the short-lived APCu state if the file is temporarily missing, empty, or invalid. -- `plugin_mastodon_state_write()` — line 2314 — Persist the runtime state to disk and always refresh the short-lived APCu last-known-good state when possible. -- `plugin_mastodon_normalize_deletions_pending_scope()` — line 2339 — Normalize the targeted deletion-follow-up scope marker. -- `plugin_mastodon_state_normalize()` — line 2352 — Normalize a runtime state array and fill in missing keys. -- `plugin_mastodon_state_comment_key()` — line 2399 — Build the compound state key used for comment mappings. -- `plugin_mastodon_state_set_entry_mapping()` — line 2414 — Store the mapping between a local entry and a remote status. -- `plugin_mastodon_state_set_comment_mapping()` — line 2452 — Store the mapping between a local comment and a remote status. -- `plugin_mastodon_state_remove_entry_mapping()` — line 2487 — Remove the mapping between a local entry and a remote status. -- `plugin_mastodon_state_remove_comment_mapping()` — line 2506 — Remove the mapping between a local comment and a remote status. -- `plugin_mastodon_state_get_entry_meta()` — line 2527 — Return mapping metadata for a local entry. -- `plugin_mastodon_state_set_entry_media_meta()` — line 2541 — Persist cached remote media IDs plus local attachment/description signatures for a synchronized entry. -- `plugin_mastodon_state_get_comment_meta()` — line 2622 — Return mapping metadata for a local comment. -- `plugin_mastodon_state_set_comment_tombstone()` — line 2636 — Store a tombstone that blocks stale re-imports of one deleted remote comment. -- `plugin_mastodon_state_has_comment_tombstone()` — line 2656 — Check whether one remote Mastodon comment status was tombstoned locally. -- `plugin_mastodon_protect_locally_deleted_exported_comments()` — line 2667 — Tombstone locally deleted exported FlatPress comment mappings before the next content sync can stale-reimport them from Mastodon thread context. -- `plugin_mastodon_reattach_local_comment_to_entry_status()` — line 2713 — Remove a local imported reply parent link and reattach the surviving reply to the synchronized entry status after its remote parent reply disappeared. -- `plugin_mastodon_state_remove_pending_comment_remote_recheck()` — line 2773 — Remove one pending descendant recheck marker. -- `plugin_mastodon_state_get_pending_comment_remote_recheck()` — line 2787 — Return one pending descendant recheck marker. -- `plugin_mastodon_state_set_pending_comment_remote_recheck()` — line 2802 — Mark one local comment for follow-up verification after an ancestor disappeared remotely. -- `plugin_mastodon_state_set_deletions_pending()` — line 2827 — Persist whether another deletion follow-up request is pending and which scope it should run. -- `plugin_mastodon_state_has_comment_recheck_scope()` — line 2839 — Check whether the next deletion follow-up request should run only the targeted descendant recheck scope. -- `plugin_mastodon_build_comment_remote_child_index()` — line 2848 — Build a direct-child index for mapped remote reply trees. -- `plugin_mastodon_queue_comment_descendant_remote_rechecks()` — line 2876 — Queue only the direct mapped local children of one deleted remote comment for additional verification passes. -- `plugin_mastodon_process_pending_comment_remote_rechecks()` — line 2915 — Process pending descendant rechecks breadth-first so deeper reply chains can converge within the same targeted follow-up request. +- `plugin_mastodon_runtime_cache_get()` — line 665 — Return a value from the request-local plugin cache. +- `plugin_mastodon_runtime_cache_set()` — line 689 — Store a value in the request-local plugin cache. +- `plugin_mastodon_runtime_cache_clear()` — line 707 — Clear one request-local plugin cache bucket or the complete cache. +- `plugin_mastodon_io_read_file()` — line 907 — Read a file through the FlatPress I/O layer when available. +- `plugin_mastodon_io_read_file_uncached()` — line 920 — Read a file without FlatPress request-local caches. +- `plugin_mastodon_file_permissions_mode()` — line 935 — Return the FlatPress `FILE_PERMISSIONS` mode used for plugin runtime files. +- `plugin_mastodon_apply_file_permissions()` — line 944 — Apply FlatPress `FILE_PERMISSIONS` to a plugin runtime file. +- `plugin_mastodon_io_write_file()` — line 957 — Write a file through the FlatPress I/O layer when available and enforce `FILE_PERMISSIONS` after successful writes. +- `plugin_mastodon_io_append_file()` — line 978 — Append to a file via the FlatPress I/O layer. +- `plugin_mastodon_apcu_enabled()` — line 994 — Check whether shared APCu caching is available for the plugin. +- `plugin_mastodon_apcu_cache_key()` — line 1003 — Build the namespaced APCu key used by this plugin. +- `plugin_mastodon_apcu_fetch()` — line 1017 — Fetch a value from APCu through the FlatPress namespace helper. +- `plugin_mastodon_apcu_store()` — line 1032 — Store a value in APCu through the FlatPress namespace helper. +- `plugin_mastodon_apcu_delete()` — line 1045 — Delete a value from APCu using the FlatPress namespace key builder. +- `plugin_mastodon_state_fallback_key()` — line 1057 — Return the APCu key used for the short-lived last-known-good state fallback. +- `plugin_mastodon_state_fallback_store()` — line 1066 — Store a normalized runtime state in APCu for a 5-minute emergency fallback window. +- `plugin_mastodon_state_fallback_read()` — line 1078 — Fetch the short-lived last-known-good state fallback from APCu. +- `plugin_mastodon_sync_guard_kind()` — line 1096 — Normalize the cooldown guard kind. +- `plugin_mastodon_sync_guard_apcu_key()` — line 1106 — Return the APCu key used for one sync cooldown guard. +- `plugin_mastodon_sync_guard_entry_active()` — line 1116 — Check whether one cooldown guard entry is still active. +- `plugin_mastodon_sync_guard_apcu_store()` — line 1127 — Store one cooldown guard in APCu. +- `plugin_mastodon_sync_guard_file_read()` — line 1140 — Read and prune the file-backed cooldown guard. +- `plugin_mastodon_sync_guard_file_write()` — line 1167 — Write the file-backed cooldown guard. +- `plugin_mastodon_sync_guard_active()` — line 1188 — Check whether a recent scheduled content/deletion pass is still cooling down. +- `plugin_mastodon_sync_guard_mark()` — line 1213 — Mark a 5-minute cooldown guard for a scheduled sync/deletion pass. +- `plugin_mastodon_sync_guard_clear()` — line 1234 — Clear one sync cooldown guard. +- `plugin_mastodon_rate_limit_default_budgets()` — line 1249 — Return conservative per-run Mastodon API budgets. +- `plugin_mastodon_rate_limit_window_budgets()` — line 1273 — Return persistent cross-run Mastodon API window budgets. +- `plugin_mastodon_rate_limit_window_config()` — line 1300 — Return the persistent window key, budget, TTL, and block reason for one request kind. +- `plugin_mastodon_rate_limit_window_entry()` — line 1320 — Normalize one persistent rate-limit window entry and drop expired values. +- `plugin_mastodon_rate_limit_window_read()` — line 1340 — Read and prune the persistent cross-run rate-limit windows. +- `plugin_mastodon_rate_limit_window_write()` — line 1372 — Persist cross-run rate-limit windows with FlatPress file permissions. +- `plugin_mastodon_rate_limit_window_acquire()` — line 1397 — Reserve one media-upload/delete/account-status-page slot from the persistent cross-run window. +- `plugin_mastodon_rate_limit_window_clear()` — line 1446 — Clear persistent rate-limit windows for tests or recovery tooling. +- `plugin_mastodon_rate_limit_guard_start()` — line 1458 — Start the per-run Mastodon API rate-limit guard. +- `plugin_mastodon_rate_limit_guard_stop()` — line 1484 — Stop the per-run Mastodon API rate-limit guard while keeping its summary inspectable. +- `plugin_mastodon_rate_limit_guard_active()` — line 1495 — Check whether the per-run Mastodon API guard is active. +- `plugin_mastodon_rate_limit_guard_summary()` — line 1503 — Return the current per-run rate-limit guard summary. +- `plugin_mastodon_rate_limit_headers()` — line 1512 — Normalize response headers for rate-limit checks. +- `plugin_mastodon_rate_limit_request_kind()` — line 1533 — Classify Mastodon API requests as general, media upload, status deletion, or account-status paging. +- `plugin_mastodon_rate_limit_block()` — line 1558 — Mark the current run as blocked by a rate-limit budget, persistent window, or Mastodon response and log the stop reason with budget counters once per run. +- `plugin_mastodon_rate_limit_acquire()` — line 1602 — Reserve per-run and persistent window budget before a Mastodon API request proceeds. +- `plugin_mastodon_rate_limit_observe_response()` — line 1651 — Update the guard from `X-RateLimit-*` headers and `429` responses. +- `plugin_mastodon_rate_limit_blocked_reason()` — line 1675 — Return the current rate-limit block reason. +- `plugin_mastodon_rate_limit_blocked_response()` — line 1687 — Build the synthetic `429` response used for locally blocked requests. +- `plugin_mastodon_rate_limit_state_error()` — line 1703 — Return the rate-limit reason that should be written to synchronization state. +- `plugin_mastodon_file_prestat()` — line 1713 — Read a cheap file metadata snapshot for cache validation. +- `plugin_mastodon_file_prestat_signature()` — line 1733 — Convert a file metadata snapshot into a stable cache signature. +- `plugin_mastodon_ensure_state_dir()` — line 2472 — Ensure that the plugin runtime directory exists. +- `plugin_mastodon_log()` — line 2481 — Append a line to the plugin sync log. +- `plugin_mastodon_state_read()` — line 2491 — Load the persisted runtime state from disk and fall back to the short-lived APCu state if the file is temporarily missing, empty, or invalid. +- `plugin_mastodon_state_write()` — line 2545 — Persist the runtime state to disk and always refresh the short-lived APCu last-known-good state when possible. +- `plugin_mastodon_normalize_deletions_pending_scope()` — line 2570 — Normalize the targeted deletion-follow-up scope marker. +- `plugin_mastodon_state_normalize()` — line 2583 — Normalize a runtime state array and fill in missing keys. +- `plugin_mastodon_state_comment_key()` — line 2631 — Build the compound state key used for comment mappings. +- `plugin_mastodon_state_set_entry_mapping()` — line 2646 — Store the mapping between a local entry and a remote status. +- `plugin_mastodon_state_set_comment_mapping()` — line 2684 — Store the mapping between a local comment and a remote status. +- `plugin_mastodon_state_remove_entry_mapping()` — line 2719 — Remove the mapping between a local entry and a remote status. +- `plugin_mastodon_state_remove_comment_mapping()` — line 2738 — Remove the mapping between a local comment and a remote status. +- `plugin_mastodon_state_set_dirty_entry()` — line 2886 — Add an older changed entry to the persistent dirty queue. +- `plugin_mastodon_state_remove_dirty_entry()` — line 2906 — Remove an entry from the dirty queue after successful synchronization or cleanup. +- `plugin_mastodon_state_has_dirty_entry()` — line 2919 — Check whether an entry is queued for synchronization outside the scheduled window. +- `plugin_mastodon_state_set_dirty_comment()` — line 2932 — Add an older changed comment to the persistent dirty queue. +- `plugin_mastodon_state_remove_dirty_comment()` — line 2955 — Remove a comment from the dirty queue after successful synchronization or cleanup. +- `plugin_mastodon_state_has_dirty_comment()` — line 2969 — Check whether a comment is queued for synchronization outside the scheduled window. +- `plugin_mastodon_state_get_entry_meta()` — line 2759 — Return mapping metadata for a local entry. +- `plugin_mastodon_state_set_entry_media_meta()` — line 2773 — Persist cached remote media IDs plus local attachment/description signatures for a synchronized entry. +- `plugin_mastodon_state_get_comment_meta()` — line 2854 — Return mapping metadata for a local comment. +- `plugin_mastodon_state_set_comment_tombstone()` — line 2868 — Store a tombstone that blocks stale re-imports of one deleted remote comment. +- `plugin_mastodon_state_has_comment_tombstone()` — line 2888 — Check whether one remote Mastodon comment status was tombstoned locally. +- `plugin_mastodon_protect_locally_deleted_exported_comments()` — line 2899 — Tombstone locally deleted exported FlatPress comment mappings before the next content sync can stale-reimport them from Mastodon thread context. +- `plugin_mastodon_reattach_local_comment_to_entry_status()` — line 2945 — Remove a local imported reply parent link and reattach the surviving reply to the synchronized entry status after its remote parent reply disappeared. +- `plugin_mastodon_state_remove_pending_comment_remote_recheck()` — line 3005 — Remove one pending descendant recheck marker. +- `plugin_mastodon_state_get_pending_comment_remote_recheck()` — line 3019 — Return one pending descendant recheck marker. +- `plugin_mastodon_state_set_pending_comment_remote_recheck()` — line 3034 — Mark one local comment for follow-up verification after an ancestor disappeared remotely. +- `plugin_mastodon_state_set_deletions_pending()` — line 3060 — Persist whether another deletion follow-up request is pending, which scope it should run, and when it may start. +- `plugin_mastodon_deletion_sync_due()` — line 3074 — Check whether the pending deletion synchronization may start after its persisted not-before timestamp. +- `plugin_mastodon_state_has_comment_recheck_scope()` — line 3102 — Check whether the next deletion follow-up request should run only the targeted descendant recheck scope. +- `plugin_mastodon_build_comment_remote_child_index()` — line 3111 — Build a direct-child index for mapped remote reply trees. +- `plugin_mastodon_queue_comment_descendant_remote_rechecks()` — line 3139 — Queue only the direct mapped local children of one deleted remote comment for additional verification passes. +- `plugin_mastodon_process_pending_comment_remote_rechecks()` — line 3178 — Process pending descendant rechecks breadth-first so deeper reply chains can converge within the same targeted follow-up request. ## D. Date, timestamp, visibility, and threading helpers -- `plugin_mastodon_timestamp_to_flatpress_time()` — line 786 — Convert a real Unix timestamp into FlatPress's offset-adjusted timestamp model. -- `plugin_mastodon_timestamp_date_key()` — line 2077 — Convert a FlatPress-adjusted timestamp into a stable date key. -- `plugin_mastodon_local_item_date_key()` — line 2091 — Determine the date key of a local FlatPress entry or comment. -- `plugin_mastodon_remote_status_date_key()` — line 2111 — Determine the date key of a remote Mastodon status. -- `plugin_mastodon_datetime_date_key()` — line 2137 — Normalize a stored date/datetime string to the sync-start date-key format. -- `plugin_mastodon_date_matches_sync_start()` — line 2161 — Determine whether a content date passes the configured sync start date. -- `plugin_mastodon_local_item_matches_sync_start()` — line 2180 — Determine whether a local FlatPress item should be synchronized. -- `plugin_mastodon_remote_status_matches_sync_start()` — line 2190 — Determine whether a remote Mastodon status should be synchronized. -- `plugin_mastodon_mapping_matches_sync_start()` — line 2201 — Determine whether a stored synchronization mapping still belongs to the active sync-start window. -- `plugin_mastodon_parse_iso_datetime()` — line 3020 — Parse an ISO date/time string into FlatPress date format. -- `plugin_mastodon_parse_iso_timestamp()` — line 3038 — Parse an ISO date/time value into a Unix timestamp. -- `plugin_mastodon_remote_status_timestamp()` — line 3060 — Resolve the best FlatPress-adjusted timestamp for a remote Mastodon status. -- `plugin_mastodon_remote_status_visibility()` — line 3079 — Return the normalized visibility of a remote Mastodon status. -- `plugin_mastodon_remote_status_is_importable()` — line 3092 — Determine whether a remote Mastodon status may be imported. -- `plugin_mastodon_comment_parent_fields()` — line 3104 — Return the comment fields that may contain a parent reference. -- `plugin_mastodon_normalize_boolean_option()` — line 1989 — Normalize a boolean-like option value to the stored string representation. -- `plugin_mastodon_normalize_comment_parent_id()` — line 3113 — Normalize a stored local comment parent identifier. -- `plugin_mastodon_detect_local_comment_parent_id()` — line 3130 — Detect the local parent comment identifier from comment data. -- `plugin_mastodon_resolve_comment_reply_target()` — line 3152 — Resolve the remote reply target for a local comment export. -- `plugin_mastodon_list_local_comment_ids()` — line 3205 — Scan the FlatPress comment directory directly so local reply export is not blocked by stale comment-list caches. +- `plugin_mastodon_timestamp_to_flatpress_time()` — line 808 — Convert a real Unix timestamp into FlatPress's offset-adjusted timestamp model. +- `plugin_mastodon_timestamp_date_key()` — line 2308 — Convert a FlatPress-adjusted timestamp into a stable date key. +- `plugin_mastodon_local_item_date_key()` — line 2322 — Determine the date key of a local FlatPress entry or comment. +- `plugin_mastodon_remote_status_date_key()` — line 2342 — Determine the date key of a remote Mastodon status. +- `plugin_mastodon_datetime_date_key()` — line 2368 — Normalize a stored date/datetime string to the sync-start date-key format. +- `plugin_mastodon_date_matches_sync_start()` — line 2392 — Determine whether a content date passes the configured sync start date. +- `plugin_mastodon_scheduled_window_start_date()` — line 2468 — Return the FlatPress-local date key that starts the automatic scheduled sync window. +- `plugin_mastodon_date_matches_content_window()` — line 2483 — Combine the durable sync-start lower bound with the scheduled-run recent-content window. +- `plugin_mastodon_local_item_matches_sync_start()` — line 2411 — Determine whether a local FlatPress item should be synchronized. +- `plugin_mastodon_local_item_matches_content_window()` — line 2510 — Determine whether a local FlatPress item is inside the active content synchronization window. +- `plugin_mastodon_remote_status_matches_sync_start()` — line 2421 — Determine whether a remote Mastodon status should be synchronized. +- `plugin_mastodon_remote_status_matches_content_window()` — line 2525 — Determine whether a remote Mastodon status is inside the active content synchronization window. +- `plugin_mastodon_mapping_matches_sync_start()` — line 2432 — Determine whether a stored synchronization mapping still belongs to the active sync-start window. +- `plugin_mastodon_parse_iso_datetime()` — line 3283 — Parse an ISO date/time string into FlatPress date format. +- `plugin_mastodon_parse_iso_timestamp()` — line 3301 — Parse an ISO date/time value into a Unix timestamp. +- `plugin_mastodon_remote_status_timestamp()` — line 3323 — Resolve the best FlatPress-adjusted timestamp for a remote Mastodon status. +- `plugin_mastodon_remote_status_visibility()` — line 3342 — Return the normalized visibility of a remote Mastodon status. +- `plugin_mastodon_remote_status_is_importable()` — line 3355 — Determine whether a remote Mastodon status may be imported. +- `plugin_mastodon_comment_parent_fields()` — line 3367 — Return the comment fields that may contain a parent reference. +- `plugin_mastodon_normalize_boolean_option()` — line 2220 — Normalize a boolean-like option value to the stored string representation. +- `plugin_mastodon_normalize_comment_parent_id()` — line 3376 — Normalize a stored local comment parent identifier. +- `plugin_mastodon_detect_local_comment_parent_id()` — line 3393 — Detect the local parent comment identifier from comment data. +- `plugin_mastodon_resolve_comment_reply_target()` — line 3415 — Resolve the remote reply target for a local comment export. +- `plugin_mastodon_list_local_comment_ids()` — line 3468 — Scan the FlatPress comment directory directly so local reply export is not blocked by stale comment-list caches. ## E. Text, URLs, language strings, tags, emojis, and BBCode/HTML conversion -- `plugin_mastodon_guess_subject()` — line 3242 — Guess a subject line from imported plain text. -- `plugin_mastodon_html_entity_decode()` — line 3284 — Decode HTML entities using the plugin defaults. -- `plugin_mastodon_blog_base_url()` — line 3293 — Return the absolute base URL of the current FlatPress installation. -- `plugin_mastodon_extract_url_token()` — line 3329 — Extract the URL token from a BBCode or attribute fragment. -- `plugin_mastodon_absolute_url()` — line 3346 — Convert a URL or path into an absolute URL when possible. -- `plugin_mastodon_lang_string()` — line 3388 — Return a localized plugin string or a provided fallback. -- `plugin_mastodon_normalize_tag_list()` — line 3614 — Normalize a list of tag labels. -- `plugin_mastodon_extend_time_limit()` — line 5842 — Refresh or raise the PHP execution time budget for long-running Mastodon work without lowering an existing higher or unlimited limit. -- `plugin_mastodon_extract_flatpress_tags()` — line 3645 — Extract FlatPress Tag plugin labels from an entry body. -- `plugin_mastodon_strip_flatpress_tag_bbcode()` — line 3670 — Remove Tag plugin BBCode blocks from entry content. -- `plugin_mastodon_mastodon_hashtag_footer()` — line 3687 — Convert FlatPress tag labels into a Mastodon hashtag footer line. -- `plugin_mastodon_remote_status_tags()` — line 3708 — Collect remote Mastodon tags from a status entity. -- `plugin_mastodon_strip_trailing_mastodon_hashtag_footer()` — line 3735 — Remove a trailing Mastodon hashtag footer from imported plain text. -- `plugin_mastodon_build_flatpress_tag_bbcode()` — line 3798 — Build Tag plugin BBCode from a list of remote Mastodon tags. -- `plugin_mastodon_emoticon_entity_to_unicode()` — line 3811 — Convert an emoticon HTML entity into a Unicode character. -- `plugin_mastodon_emoticon_map()` — line 3824 — Return the FlatPress emoticon-to-Unicode lookup map. -- `plugin_mastodon_replace_emoticon_shortcodes_with_unicode()` — line 3878 — Replace FlatPress emoticon shortcodes with Unicode glyphs. -- `plugin_mastodon_prepare_emoticons_for_mastodon()` — line 3893 — Convert active FlatPress emoticon shortcodes to standard Unicode emoji before Mastodon export. -- `plugin_mastodon_replace_unicode_emoticons_with_shortcodes()` — line 3906 — Replace Unicode emoticons with FlatPress shortcodes. -- `plugin_mastodon_is_public_host()` — line 3928 — Determine whether a host name resolves to a public endpoint. -- `plugin_mastodon_public_url_for_mastodon()` — line 3951 — Return a Mastodon-safe public URL or an empty string. -- `plugin_mastodon_plain_text_from_bbcode()` — line 3970 — Convert FlatPress BBCode into plain text for Mastodon export, removing complete AudioVideo player tags including optional description endtag content. -- `plugin_mastodon_subject_line_is_noise()` — line 4014 — Determine whether an extracted line should be ignored as a subject. -- `plugin_mastodon_domains_match()` — line 4042 — Determine whether two host names belong to the same domain family. -- `plugin_mastodon_cleanup_imported_text()` — line 4056 — Clean imported text before saving it to FlatPress. -- `plugin_mastodon_dom_children_to_flatpress()` — line 4110 — Convert DOM child nodes into FlatPress BBCode text. -- `plugin_mastodon_dom_node_to_flatpress()` — line 4128 — Convert a single DOM node into FlatPress BBCode text. -- `plugin_mastodon_public_entry_url()` — line 4237 — Return the public URL for a FlatPress entry. -- `plugin_mastodon_public_comments_url()` — line 4264 — Return the public comments URL for a FlatPress entry. -- `plugin_mastodon_public_comment_url()` — line 4292 — Return the public URL for a specific FlatPress comment. -- `plugin_mastodon_mastodon_html_to_flatpress()` — line 4307 — Convert Mastodon HTML content into FlatPress BBCode. -- `plugin_mastodon_flatpress_to_mastodon()` — line 4409 — Convert FlatPress content into Mastodon-ready plain text. -- `plugin_mastodon_limit_text()` — line 4526 — Limit text to a maximum number of characters. +- `plugin_mastodon_guess_subject()` — line 3505 — Guess a subject line from imported plain text. +- `plugin_mastodon_html_entity_decode()` — line 3547 — Decode HTML entities using the plugin defaults. +- `plugin_mastodon_blog_base_url()` — line 3556 — Return the absolute base URL of the current FlatPress installation. +- `plugin_mastodon_extract_url_token()` — line 3592 — Extract the URL token from a BBCode or attribute fragment. +- `plugin_mastodon_absolute_url()` — line 3609 — Convert a URL or path into an absolute URL when possible. +- `plugin_mastodon_lang_string()` — line 3651 — Return a localized plugin string or a provided fallback. +- `plugin_mastodon_normalize_tag_list()` — line 3877 — Normalize a list of tag labels. +- `plugin_mastodon_extend_time_limit()` — line 6105 — Refresh or raise the PHP execution time budget for long-running Mastodon work without lowering an existing higher or unlimited limit. +- `plugin_mastodon_extract_flatpress_tags()` — line 3908 — Extract FlatPress Tag plugin labels from an entry body. +- `plugin_mastodon_strip_flatpress_tag_bbcode()` — line 3933 — Remove Tag plugin BBCode blocks from entry content. +- `plugin_mastodon_mastodon_hashtag_footer()` — line 3950 — Convert FlatPress tag labels into a Mastodon hashtag footer line. +- `plugin_mastodon_remote_status_tags()` — line 3971 — Collect remote Mastodon tags from a status entity. +- `plugin_mastodon_strip_trailing_mastodon_hashtag_footer()` — line 3998 — Remove a trailing Mastodon hashtag footer from imported plain text. +- `plugin_mastodon_build_flatpress_tag_bbcode()` — line 4061 — Build Tag plugin BBCode from a list of remote Mastodon tags. +- `plugin_mastodon_emoticon_entity_to_unicode()` — line 4074 — Convert an emoticon HTML entity into a Unicode character. +- `plugin_mastodon_emoticon_map()` — line 4087 — Return the FlatPress emoticon-to-Unicode lookup map. +- `plugin_mastodon_replace_emoticon_shortcodes_with_unicode()` — line 4141 — Replace FlatPress emoticon shortcodes with Unicode glyphs. +- `plugin_mastodon_prepare_emoticons_for_mastodon()` — line 4156 — Convert active FlatPress emoticon shortcodes to standard Unicode emoji before Mastodon export. +- `plugin_mastodon_replace_unicode_emoticons_with_shortcodes()` — line 4169 — Replace Unicode emoticons with FlatPress shortcodes. +- `plugin_mastodon_is_public_host()` — line 4191 — Determine whether a host name resolves to a public endpoint. +- `plugin_mastodon_public_url_for_mastodon()` — line 4214 — Return a Mastodon-safe public URL or an empty string. +- `plugin_mastodon_plain_text_from_bbcode()` — line 4233 — Convert FlatPress BBCode into plain text for Mastodon export, removing complete AudioVideo player tags including optional description endtag content. +- `plugin_mastodon_subject_line_is_noise()` — line 4277 — Determine whether an extracted line should be ignored as a subject. +- `plugin_mastodon_domains_match()` — line 4305 — Determine whether two host names belong to the same domain family. +- `plugin_mastodon_cleanup_imported_text()` — line 4319 — Clean imported text before saving it to FlatPress. +- `plugin_mastodon_dom_children_to_flatpress()` — line 4373 — Convert DOM child nodes into FlatPress BBCode text. +- `plugin_mastodon_dom_node_to_flatpress()` — line 4391 — Convert a single DOM node into FlatPress BBCode text. +- `plugin_mastodon_public_entry_url()` — line 4500 — Return the public URL for a FlatPress entry. +- `plugin_mastodon_public_comments_url()` — line 4527 — Return the public comments URL for a FlatPress entry. +- `plugin_mastodon_public_comment_url()` — line 4555 — Return the public URL for a specific FlatPress comment. +- `plugin_mastodon_mastodon_html_to_flatpress()` — line 4570 — Convert Mastodon HTML content into FlatPress BBCode. +- `plugin_mastodon_flatpress_to_mastodon()` — line 4672 — Convert FlatPress content into Mastodon-ready plain text. +- `plugin_mastodon_limit_text()` — line 4789 — Limit text to a maximum number of characters. ## F. Local content access, media processing, hashing, and export ordering -- `plugin_mastodon_entry_hash()` — line 4548 — Build a change-detection hash for a FlatPress entry. -- `plugin_mastodon_comment_hash()` — line 4560 — Build a change-detection hash for a FlatPress comment. -- `plugin_mastodon_remote_status_author_label()` — line 7552 — Build a readable author label for quoted Mastodon replies. -- `plugin_mastodon_strip_leading_quote_block()` — line 7585 — Remove one leading BBCode quote block so imported reply quotes do not compound indefinitely. -- `plugin_mastodon_imported_reply_quote_payload()` — line 7618 — Resolve the author and body that should be quoted for an imported Mastodon reply. -- `plugin_mastodon_build_imported_reply_quote()` — line 7661 — Build the optional BBCode quote block for an imported Mastodon reply. -- `plugin_mastodon_safe_path_component()` — line 4578 — Sanitize a string so it can be used as a path component. -- `plugin_mastodon_safe_filename()` — line 4593 — Sanitize a file name for local storage. -- `plugin_mastodon_media_relative_to_absolute()` — line 4629 — Resolve a FlatPress media path to an absolute file path. -- `plugin_mastodon_media_prepare_directory()` — line 4642 — Ensure that a media directory exists. -- `plugin_mastodon_media_delete_tree()` — line 4658 — Delete a directory tree used for imported media. -- `plugin_mastodon_media_copy_tree()` — line 4685 — Copy a directory tree used for media synchronization. -- `plugin_mastodon_bbcode_attr_escape()` — line 4723 — Escape a value for safe BBCode attribute usage. -- `plugin_mastodon_bbcode_text_escape()` — line 4735 — Escape plain text embedded between BBCode tags, used for imported AudioVideo media descriptions. -- `plugin_mastodon_media_guess_mime_type()` — line 4757 — Guess the MIME type of a local media file. -- `plugin_mastodon_media_parse_tag_attributes()` — line 4979 — Parse key/value attributes from a FlatPress media tag. -- `plugin_mastodon_media_description_from_bbcode_content()` — line 5010 — Normalize optional AudioVideo BBCode content into a Mastodon media description. -- `plugin_mastodon_instance_supported_media_mime_types()` — line 4905 — Return the MIME types advertised by the configured Mastodon instance. -- `plugin_mastodon_instance_media_size_limit()` — line 4925 — Return the configured byte-size limit for an image, video/GIFV, or audio upload. -- `plugin_mastodon_validate_local_media_item()` — line 4952 — Validate one local media file against available instance MIME and byte-size limits before upload. -- `plugin_mastodon_media_extract_default_path()` — line 5036 — Extract the default path parameter from FlatPress media BBCode such as `[img=...]`, `[gallery=...]`, `[audioplayer="..."]`, and `[videoplayer="..."]`. -- `plugin_mastodon_add_local_media_item()` — line 5065 — Add one normalized local media item while deduplicating and enforcing an expected media family. -- `plugin_mastodon_collect_local_entry_media()` — line 5114 — Collect local images, galleries, audio, video, optional AudioVideo endtag descriptions, and video poster thumbnails referenced by an entry. -- `plugin_mastodon_prepare_entry_media_items()` — line 5252 — Normalize collected local media items into reusable path/description tuples. -- `plugin_mastodon_entry_media_attachment_signature_from_items()` — line 5283 — Hash only the attachment identity of normalized media items. -- `plugin_mastodon_entry_media_description_signature_from_items()` — line 5306 — Hash only the alt-text portion of normalized media items. -- `plugin_mastodon_entry_media_signature()` — line 5325 — Build a combined attachment+description signature for media references contained in entry content. -- `plugin_mastodon_remote_media_attachment_type()` — line 5342 — Normalize a Mastodon attachment type, including extension-based fallbacks for older or incomplete payloads. -- `plugin_mastodon_remote_status_media_attachments()` — line 5369 — Extract supported image, audio, video, and GIFV attachments from a remote Mastodon status. -- `plugin_mastodon_remote_status_image_attachments()` — line 5399 — Extract image attachments from a remote Mastodon status. -- `plugin_mastodon_remote_media_source_url()` — line 5408 — Resolve the best downloadable source URL for a remote attachment. -- `plugin_mastodon_remote_media_source_urls()` — line 5426 — Resolve direct-download fallback candidates for a remote attachment; audio/video/GIFV avoid `preview_url` as a file-source fallback, while images may use it. -- `plugin_mastodon_remote_media_description()` — line 5448 — Resolve the best description for a remote attachment. -- `plugin_mastodon_remote_media_focus()` — line 5462 — Normalize a Mastodon media focus string when present. -- `plugin_mastodon_remote_media_descriptors_from_status()` — line 5479 — Extract reusable media descriptors (`id`, `description`, `focus`) from a Mastodon status payload. -- `plugin_mastodon_remote_media_descriptors_from_media_ids()` — line 5510 — Build fallback reusable media descriptors from already-known IDs and local media items. -- `plugin_mastodon_media_download()` — line 5577 — Download a remote media asset with an extended media-transfer timeout. -- `plugin_mastodon_remote_download_basename()` — line 5591 — Build a safe local basename for a downloaded remote image, audio, video, GIFV, or poster. -- `plugin_mastodon_store_remote_media_url()` — line 5621 — Download and persist one remote media URL. -- `plugin_mastodon_build_imported_media_bbcode()` — line 5643 — Build FlatPress BBCode for imported remote media attachments: images become `[img]`/`[gallery]`, audio becomes `[audioplayer]`, and video/GIFV becomes `[videoplayer]` with imported optional description endtag text and an imported poster when available; alternate direct media URLs are retried before an attachment is skipped. -- `plugin_mastodon_collect_entry_files()` — line 6610 — Collect entry files recursively from the FlatPress content tree. -- `plugin_mastodon_local_item_timestamp()` — line 6637 — Resolve the best timestamp for a local FlatPress item. -- `plugin_mastodon_compare_local_entries_for_export()` — line 6663 — Compare local FlatPress entries for Mastodon export order. -- `plugin_mastodon_list_local_entries()` — line 6681 — List local FlatPress entry identifiers. +- `plugin_mastodon_entry_hash()` — line 4811 — Build a change-detection hash for a FlatPress entry. +- `plugin_mastodon_comment_hash()` — line 4823 — Build a change-detection hash for a FlatPress comment. +- `plugin_mastodon_remote_status_author_label()` — line 7815 — Build a readable author label for quoted Mastodon replies. +- `plugin_mastodon_strip_leading_quote_block()` — line 7848 — Remove one leading BBCode quote block so imported reply quotes do not compound indefinitely. +- `plugin_mastodon_imported_reply_quote_payload()` — line 7881 — Resolve the author and body that should be quoted for an imported Mastodon reply. +- `plugin_mastodon_build_imported_reply_quote()` — line 7924 — Build the optional BBCode quote block for an imported Mastodon reply. +- `plugin_mastodon_safe_path_component()` — line 4841 — Sanitize a string so it can be used as a path component. +- `plugin_mastodon_safe_filename()` — line 4856 — Sanitize a file name for local storage. +- `plugin_mastodon_media_relative_to_absolute()` — line 4892 — Resolve a FlatPress media path to an absolute file path. +- `plugin_mastodon_media_prepare_directory()` — line 4905 — Ensure that a media directory exists. +- `plugin_mastodon_media_delete_tree()` — line 4921 — Delete a directory tree used for imported media. +- `plugin_mastodon_media_copy_tree()` — line 4948 — Copy a directory tree used for media synchronization. +- `plugin_mastodon_bbcode_attr_escape()` — line 4986 — Escape a value for safe BBCode attribute usage. +- `plugin_mastodon_bbcode_text_escape()` — line 4998 — Escape plain text embedded between BBCode tags, used for imported AudioVideo media descriptions. +- `plugin_mastodon_media_guess_mime_type()` — line 5020 — Guess the MIME type of a local media file. +- `plugin_mastodon_media_parse_tag_attributes()` — line 5242 — Parse key/value attributes from a FlatPress media tag. +- `plugin_mastodon_media_description_from_bbcode_content()` — line 5273 — Normalize optional AudioVideo BBCode content into a Mastodon media description. +- `plugin_mastodon_instance_supported_media_mime_types()` — line 5168 — Return the MIME types advertised by the configured Mastodon instance. +- `plugin_mastodon_instance_media_size_limit()` — line 5188 — Return the configured byte-size limit for an image, video/GIFV, or audio upload. +- `plugin_mastodon_validate_local_media_item()` — line 5215 — Validate one local media file against available instance MIME and byte-size limits before upload. +- `plugin_mastodon_media_extract_default_path()` — line 5299 — Extract the default path parameter from FlatPress media BBCode such as `[img=...]`, `[gallery=...]`, `[audioplayer="..."]`, and `[videoplayer="..."]`. +- `plugin_mastodon_add_local_media_item()` — line 5328 — Add one normalized local media item while deduplicating and enforcing an expected media family. +- `plugin_mastodon_collect_local_entry_media()` — line 5377 — Collect local images, galleries, audio, video, optional AudioVideo endtag descriptions, and video poster thumbnails referenced by an entry. +- `plugin_mastodon_prepare_entry_media_items()` — line 5515 — Normalize collected local media items into reusable path/description tuples. +- `plugin_mastodon_entry_media_attachment_signature_from_items()` — line 5546 — Hash only the attachment identity of normalized media items. +- `plugin_mastodon_entry_media_description_signature_from_items()` — line 5569 — Hash only the alt-text portion of normalized media items. +- `plugin_mastodon_entry_media_signature()` — line 5588 — Build a combined attachment+description signature for media references contained in entry content. +- `plugin_mastodon_remote_media_attachment_type()` — line 5605 — Normalize a Mastodon attachment type, including extension-based fallbacks for older or incomplete payloads. +- `plugin_mastodon_remote_status_media_attachments()` — line 5632 — Extract supported image, audio, video, and GIFV attachments from a remote Mastodon status. +- `plugin_mastodon_remote_status_image_attachments()` — line 5662 — Extract image attachments from a remote Mastodon status. +- `plugin_mastodon_remote_media_source_url()` — line 5671 — Resolve the best downloadable source URL for a remote attachment. +- `plugin_mastodon_remote_media_source_urls()` — line 5689 — Resolve direct-download fallback candidates for a remote attachment; audio/video/GIFV avoid `preview_url` as a file-source fallback, while images may use it. +- `plugin_mastodon_remote_media_description()` — line 5711 — Resolve the best description for a remote attachment. +- `plugin_mastodon_remote_media_focus()` — line 5725 — Normalize a Mastodon media focus string when present. +- `plugin_mastodon_remote_media_descriptors_from_status()` — line 5742 — Extract reusable media descriptors (`id`, `description`, `focus`) from a Mastodon status payload. +- `plugin_mastodon_remote_media_descriptors_from_media_ids()` — line 5773 — Build fallback reusable media descriptors from already-known IDs and local media items. +- `plugin_mastodon_media_download()` — line 5840 — Download a remote media asset with an extended media-transfer timeout. +- `plugin_mastodon_remote_download_basename()` — line 5854 — Build a safe local basename for a downloaded remote image, audio, video, GIFV, or poster. +- `plugin_mastodon_store_remote_media_url()` — line 5884 — Download and persist one remote media URL. +- `plugin_mastodon_build_imported_media_bbcode()` — line 5906 — Build FlatPress BBCode for imported remote media attachments: images become `[img]`/`[gallery]`, audio becomes `[audioplayer]`, and video/GIFV becomes `[videoplayer]` with imported optional description endtag text and an imported poster when available; alternate direct media URLs are retried before an attachment is skipped. +- `plugin_mastodon_collect_entry_files()` — line 6873 — Collect entry files recursively from the FlatPress content tree. +- `plugin_mastodon_local_item_timestamp()` — line 6900 — Resolve the best timestamp for a local FlatPress item. +- `plugin_mastodon_compare_local_entries_for_export()` — line 6926 — Compare local FlatPress entries for Mastodon export order. +- `plugin_mastodon_list_local_entries()` — line 6944 — List local FlatPress entry identifiers. ## G. HTTP transport, PHP timeout budgeting, instance capability lookup, status-length budgeting, OAuth, Mastodon API calls, and media upload -- `plugin_mastodon_extend_time_limit()` — line 5842 — Refresh or raise the PHP execution time budget for long-running Mastodon work without lowering an existing higher or unlimited limit. -- `plugin_mastodon_instance_document()` — line 5873 — Load and cache the compact Mastodon instance document, preferring the saved FlatPress snapshot before APCu and live network fetches. -- `plugin_mastodon_instance_version()` — line 5917 — Extract the human-readable Mastodon server version from the cached instance document. -- `plugin_mastodon_instance_supports_status_media_attributes()` — line 5935 — Decide whether `PUT /api/v1/statuses/:id` may safely use `media_attributes` for in-place alt-text edits. -- `plugin_mastodon_instance_configuration()` — line 5952 — Return the normalized `configuration` subtree from the cached Mastodon instance document. -- `plugin_mastodon_instance_media_limit()` — line 5962 — Return the media attachment limit of the configured instance. -- `plugin_mastodon_instance_media_description_limit()` — line 5975 — Return the media description length limit of the configured instance. -- `plugin_mastodon_instance_url_reserved_length()` — line 5988 — Return the reserved Mastodon character budget used for each URL. -- `plugin_mastodon_instance_registration_summary()` — line 8577 — Summarize the cached registration policy advertised by the instance for the admin diagnostics table. -- `plugin_mastodon_admin_instance_info_rows()` — line 8603 — Build the localized admin-table rows from the cached instance-information snapshot without forcing another live request. -- `plugin_mastodon_status_text_length()` — line 6002 — Calculate the Mastodon-visible status length with instance URL budgeting. -- `plugin_mastodon_limit_status_text()` — line 6034 — Truncate status text using Mastodon URL-budget rules. -- `plugin_mastodon_http_request_multipart()` — line 6113 — Perform a multipart HTTP request. -- `plugin_mastodon_fetch_media_attachment()` — line 6246 — Fetch a single Mastodon media attachment by ID. -- `plugin_mastodon_media_processing_attempts()` — line 6311 — Calculate media-type- and size-aware polling attempts for asynchronous Mastodon media processing. -- `plugin_mastodon_media_transfer_timeout()` — line 6335 — Calculate longer upload transfer timeouts for audio/video/GIFV while keeping image uploads lighter. -- `plugin_mastodon_wait_for_media_attachment()` — line 6355 — Poll an asynchronously processed Mastodon media attachment until it is ready or times out, including pending audio/video responses without `preview_url`. -- `plugin_mastodon_upload_media_items()` — line 6404 — Upload local media items to Mastodon and collect the created media IDs; AudioVideo posters are sent as Mastodon `thumbnail` multipart fields for video uploads. -- `plugin_mastodon_parse_http_response_headers()` — line 6718 — Parse raw HTTP response headers. -- `plugin_mastodon_stream_context_request()` — line 6748 — Perform an HTTP request through a stream context fallback. -- `plugin_mastodon_status_media_attributes()` — line 5544 — Build the `media_attributes` array used for in-place status edits of already attached media. -- `plugin_mastodon_prepare_entry_media_sync_plan()` — line 6521 — Decide whether an entry should upload fresh media, reuse stored IDs, or reuse IDs plus `media_attributes`. -- `plugin_mastodon_array_is_list()` — line 6788 — Detect whether a PHP array is a zero-based list that should use `[]` form-field notation. -- `plugin_mastodon_array_contains_only_form_scalars()` — line 6808 — Detect whether a list can be serialized as repeated scalar `[]` fields. -- `plugin_mastodon_http_build_query()` — line 6826 — Build an application/x-www-form-urlencoded query string, emitting Rack-compatible Mastodon array fields such as `media_ids[]` and nested `media_attributes[][description]`. -- `plugin_mastodon_http_request()` — line 6882 — Perform an HTTP request using cURL or the stream fallback. -- `plugin_mastodon_mastodon_api()` — line 7000 — Call the Mastodon API and return the raw HTTP response. -- `plugin_mastodon_mastodon_json()` — line 7049 — Call the Mastodon API and decode a JSON response. -- `plugin_mastodon_response_error_message()` — line 7064 — Extract the most useful error message from an API response. -- `plugin_mastodon_oauth_legacy_scopes()` — line 512 — Return the legacy OAuth scope string used by older registrations. -- `plugin_mastodon_oauth_profile_scopes()` — line 520 — Return the stricter scope string that uses `profile` for `verify_credentials`. -- `plugin_mastodon_oauth_server_metadata()` — line 529 — Discover OAuth server metadata from `/.well-known/oauth-authorization-server`. -- `plugin_mastodon_oauth_supported_scopes()` — line 548 — Extract the discoverable scope list from OAuth server metadata. -- `plugin_mastodon_oauth_scope_supported()` — line 585 — Check whether the configured Mastodon instance supports a given OAuth scope. -- `plugin_mastodon_oauth_preferred_scopes()` — line 606 — Prefer `profile` on current instances and fall back to `read:accounts` on older ones. -- `plugin_mastodon_register_app()` — line 7093 — Register the FlatPress application on the configured Mastodon instance with the preferred discoverable scope set. -- `plugin_mastodon_build_authorize_url()` — line 7116 — Build the OAuth authorization URL using the scopes that the registered app may safely request. -- `plugin_mastodon_exchange_code_for_token()` — line 7136 — Exchange an OAuth authorization code for an access token using the same negotiated scope string. -- `plugin_mastodon_verify_credentials()` — line 7166 — Verify the currently configured access token. -- `plugin_mastodon_instance_character_limit()` — line 7183 — Return the status character limit of the configured instance. -- `plugin_mastodon_fetch_account_statuses()` — line 7198 — Fetch statuses for the authenticated Mastodon account. -- `plugin_mastodon_fetch_status_context()` — line 7245 — Fetch the conversation context for a Mastodon status. -- `plugin_mastodon_fetch_status()` — line 7256 — Fetch a single Mastodon status. -- `plugin_mastodon_delete_status()` — line 7267 — Delete a Mastodon status, including media when requested. -- `plugin_mastodon_status_missing_response()` — line 7280 — Check whether an API response means that the referenced Mastodon status no longer exists. -- `plugin_mastodon_create_status()` — line 7293 — Create a Mastodon status. -- `plugin_mastodon_update_status()` — line 7320 — Update an existing Mastodon status. +- `plugin_mastodon_extend_time_limit()` — line 6105 — Refresh or raise the PHP execution time budget for long-running Mastodon work without lowering an existing higher or unlimited limit. +- `plugin_mastodon_instance_document()` — line 6136 — Load and cache the compact Mastodon instance document, preferring the saved FlatPress snapshot before APCu and live network fetches. +- `plugin_mastodon_instance_version()` — line 6180 — Extract the human-readable Mastodon server version from the cached instance document. +- `plugin_mastodon_instance_supports_status_media_attributes()` — line 6198 — Decide whether `PUT /api/v1/statuses/:id` may safely use `media_attributes` for in-place alt-text edits. +- `plugin_mastodon_instance_configuration()` — line 6215 — Return the normalized `configuration` subtree from the cached Mastodon instance document. +- `plugin_mastodon_instance_media_limit()` — line 6225 — Return the media attachment limit of the configured instance. +- `plugin_mastodon_instance_media_description_limit()` — line 6238 — Return the media description length limit of the configured instance. +- `plugin_mastodon_instance_url_reserved_length()` — line 6251 — Return the reserved Mastodon character budget used for each URL. +- `plugin_mastodon_instance_registration_summary()` — line 8851 — Summarize the cached registration policy advertised by the instance for the admin diagnostics table. +- `plugin_mastodon_admin_instance_info_rows()` — line 8877 — Build the localized admin-table rows from the cached instance-information snapshot without forcing another live request. +- `plugin_mastodon_status_text_length()` — line 6265 — Calculate the Mastodon-visible status length with instance URL budgeting. +- `plugin_mastodon_limit_status_text()` — line 6297 — Truncate status text using Mastodon URL-budget rules. +- `plugin_mastodon_http_request_multipart()` — line 6376 — Perform a multipart HTTP request. +- `plugin_mastodon_fetch_media_attachment()` — line 6509 — Fetch a single Mastodon media attachment by ID. +- `plugin_mastodon_media_processing_attempts()` — line 6574 — Calculate media-type- and size-aware polling attempts for asynchronous Mastodon media processing. +- `plugin_mastodon_media_transfer_timeout()` — line 6598 — Calculate longer upload transfer timeouts for audio/video/GIFV while keeping image uploads lighter. +- `plugin_mastodon_wait_for_media_attachment()` — line 6618 — Poll an asynchronously processed Mastodon media attachment until it is ready or times out, including pending audio/video responses without `preview_url`. +- `plugin_mastodon_upload_media_items()` — line 6667 — Upload local media items to Mastodon and collect the created media IDs; AudioVideo posters are sent as Mastodon `thumbnail` multipart fields for video uploads. +- `plugin_mastodon_parse_http_response_headers()` — line 6981 — Parse raw HTTP response headers. +- `plugin_mastodon_stream_context_request()` — line 7011 — Perform an HTTP request through a stream context fallback. +- `plugin_mastodon_status_media_attributes()` — line 5807 — Build the `media_attributes` array used for in-place status edits of already attached media. +- `plugin_mastodon_prepare_entry_media_sync_plan()` — line 6784 — Decide whether an entry should upload fresh media, reuse stored IDs, or reuse IDs plus `media_attributes`. +- `plugin_mastodon_array_is_list()` — line 7051 — Detect whether a PHP array is a zero-based list that should use `[]` form-field notation. +- `plugin_mastodon_array_contains_only_form_scalars()` — line 7071 — Detect whether a list can be serialized as repeated scalar `[]` fields. +- `plugin_mastodon_http_build_query()` — line 7089 — Build an application/x-www-form-urlencoded query string, emitting Rack-compatible Mastodon array fields such as `media_ids[]` and nested `media_attributes[][description]`. +- `plugin_mastodon_http_request()` — line 7145 — Perform an HTTP request using cURL or the stream fallback. +- `plugin_mastodon_mastodon_api()` — line 7263 — Call the Mastodon API and return the raw HTTP response. +- `plugin_mastodon_mastodon_json()` — line 7312 — Call the Mastodon API and decode a JSON response. +- `plugin_mastodon_response_error_message()` — line 7327 — Extract the most useful error message from an API response. +- `plugin_mastodon_oauth_legacy_scopes()` — line 534 — Return the legacy OAuth scope string used by older registrations. +- `plugin_mastodon_oauth_profile_scopes()` — line 542 — Return the stricter scope string that uses `profile` for `verify_credentials`. +- `plugin_mastodon_oauth_server_metadata()` — line 551 — Discover OAuth server metadata from `/.well-known/oauth-authorization-server`. +- `plugin_mastodon_oauth_supported_scopes()` — line 570 — Extract the discoverable scope list from OAuth server metadata. +- `plugin_mastodon_oauth_scope_supported()` — line 607 — Check whether the configured Mastodon instance supports a given OAuth scope. +- `plugin_mastodon_oauth_preferred_scopes()` — line 628 — Prefer `profile` on current instances and fall back to `read:accounts` on older ones. +- `plugin_mastodon_register_app()` — line 7356 — Register the FlatPress application on the configured Mastodon instance with the preferred discoverable scope set. +- `plugin_mastodon_build_authorize_url()` — line 7379 — Build the OAuth authorization URL using the scopes that the registered app may safely request. +- `plugin_mastodon_exchange_code_for_token()` — line 7399 — Exchange an OAuth authorization code for an access token using the same negotiated scope string. +- `plugin_mastodon_verify_credentials()` — line 7429 — Verify the currently configured access token. +- `plugin_mastodon_instance_character_limit()` — line 7446 — Return the status character limit of the configured instance. +- `plugin_mastodon_fetch_account_statuses()` — line 7461 — Fetch statuses for the authenticated Mastodon account. +- `plugin_mastodon_fetch_status_context()` — line 7508 — Fetch the conversation context for a Mastodon status. +- `plugin_mastodon_fetch_status()` — line 7519 — Fetch a single Mastodon status. +- `plugin_mastodon_delete_status()` — line 7530 — Delete a Mastodon status, including media when requested. +- `plugin_mastodon_status_missing_response()` — line 7543 — Check whether an API response means that the referenced Mastodon status no longer exists. +- `plugin_mastodon_create_status()` — line 7556 — Create a Mastodon status. +- `plugin_mastodon_update_status()` — line 7583 — Update an existing Mastodon status. ## H. Import/export builders and synchronization orchestration -- `plugin_mastodon_build_entry_status_text()` — line 7346 — Build the status body used when exporting a FlatPress entry. -- `plugin_mastodon_build_comment_status_text()` — line 7414 — Build the status body used when exporting a FlatPress comment. -- `plugin_mastodon_import_remote_entry()` — line 7452 — Import a remote Mastodon status into FlatPress as an entry. -- `plugin_mastodon_import_remote_comment()` — line 7703 — Import a remote Mastodon reply into FlatPress as a comment while respecting comment tombstones, including early tombstones for locally deleted exported comments. -- `plugin_mastodon_import_remote_context_descendants()` — line 7787 — Import remote Mastodon replies from a fetched thread context while blocking tombstoned parent/child replies. -- `plugin_mastodon_collect_known_entry_context_targets()` — line 7890 — Collect known synchronized entry threads that should have their Mastodon reply context refreshed. -- `plugin_mastodon_sync_remote_to_local()` — line 7924 — Synchronize remote Mastodon content into FlatPress. -- `plugin_mastodon_sync_local_to_remote()` — line 7989 — Synchronize local FlatPress content to Mastodon, including remote-sourced entry comment export, media-plan reuse of stored `media_ids`, and version-aware in-place alt-text updates. -- `plugin_mastodon_run_deletion_sync()` — line 8192 — Reconcile mapped deletions between FlatPress and Mastodon in a separate follow-up request. +- `plugin_mastodon_build_entry_status_text()` — line 7609 — Build the status body used when exporting a FlatPress entry. +- `plugin_mastodon_build_comment_status_text()` — line 7677 — Build the status body used when exporting a FlatPress comment. +- `plugin_mastodon_import_remote_entry()` — line 7715 — Import a remote Mastodon status into FlatPress as an entry. +- `plugin_mastodon_import_remote_comment()` — line 7966 — Import a remote Mastodon reply into FlatPress as a comment while respecting comment tombstones, including early tombstones for locally deleted exported comments. +- `plugin_mastodon_import_remote_context_descendants()` — line 8050 — Import remote Mastodon replies from a fetched thread context while blocking tombstoned parent/child replies. +- `plugin_mastodon_old_thread_context_rotation_limit()` — line 8372 — Return the maximum number of older known threads checked for replies per content sync run. +- `plugin_mastodon_collect_known_entry_context_targets()` — line 8388 — Collect known synchronized entry threads for optional rotating reply-context refreshes while respecting the synchronization start-date window. +- `plugin_mastodon_sync_remote_to_local()` — line 8461 — Synchronize remote Mastodon content into FlatPress with the durable start-date lower bound, scheduled-run window, and optional old-thread rotation. +- `plugin_mastodon_sync_local_to_remote()` — line 8528 — Synchronize local FlatPress content to Mastodon, including remote-sourced entry comment export, scheduled-run window filtering, dirty-queue processing, media-plan reuse of stored `media_ids`, and version-aware in-place alt-text updates. +- `plugin_mastodon_run_deletion_sync()` — line 8460 — Reconcile mapped deletions between FlatPress and Mastodon in a separate follow-up request. ## Recommended reading order for new developers @@ -476,7 +502,9 @@ The current plugin version includes dedicated function groups for: - centralized FlatPress plugin-state detection for companion-plugin diagnostics - FlatPress-native file I/O wrappers for plugin state, logs, and downloaded media - encrypted storage of sensitive configuration values -- sync start date filtering +- sync start date filtering plus a configurable 7/14/30-day automatic window for scheduled runs +- persistent dirty queues that still synchronize changed older mapped entries and comments outside the scheduled window +- optional rotating reply checks for older synchronized Mastodon threads - status language export derived from the FlatPress locale configuration - optional remote overwrite of existing local content - optional import of already synchronized comments as entries @@ -510,240 +538,263 @@ When changing the plugin, these clusters usually need to stay in sync: A change in one of these areas often requires corresponding updates in the simulation script. ## Alphabetical appendix -- `main()` — line 8725 — Keep the admin panel lifecycle compatible with FlatPress without extra processing. -- `onsubmit()` — line 8729 — Process configuration saves, OAuth actions, including app registration and authorization-code exchange, and the manual synchronization trigger. -- `plugin_mastodon_absolute_url()` — line 3346 — Convert a URL or path into an absolute URL when possible. -- `plugin_mastodon_admin_assign()` — line 8675 — Assign plugin data to Smarty for the admin panel. -- `plugin_mastodon_apcu_cache_key()` — line 981 — Build the namespaced APCu key used by this plugin. -- `plugin_mastodon_apcu_delete()` — line 1023 — Delete a value from APCu using the FlatPress namespace key builder. -- `plugin_mastodon_apcu_enabled()` — line 972 — Check whether shared APCu caching is available for the plugin. -- `plugin_mastodon_apcu_fetch()` — line 995 — Fetch a value from APCu through the FlatPress namespace helper. -- `plugin_mastodon_apcu_store()` — line 1010 — Store a value in APCu through the FlatPress namespace helper. -- `plugin_mastodon_state_fallback_key()` — line 1035 — Return the APCu key used for the short-lived last-known-good state fallback. -- `plugin_mastodon_state_fallback_store()` — line 1044 — Store a normalized runtime state in APCu for a 5-minute emergency fallback window. -- `plugin_mastodon_state_fallback_read()` — line 1056 — Fetch the short-lived last-known-good state fallback from APCu. -- `plugin_mastodon_sync_guard_kind()` — line 1074 — Normalize the cooldown guard kind. -- `plugin_mastodon_sync_guard_apcu_key()` — line 1084 — Return the APCu key used for one sync cooldown guard. -- `plugin_mastodon_sync_guard_entry_active()` — line 1094 — Check whether one cooldown guard entry is still active. -- `plugin_mastodon_sync_guard_apcu_store()` — line 1105 — Store one cooldown guard in APCu. -- `plugin_mastodon_sync_guard_file_read()` — line 1118 — Read and prune the file-backed cooldown guard. -- `plugin_mastodon_sync_guard_file_write()` — line 1145 — Write the file-backed cooldown guard. -- `plugin_mastodon_sync_guard_active()` — line 1166 — Check whether a recent scheduled content/deletion pass is still cooling down. -- `plugin_mastodon_sync_guard_mark()` — line 1191 — Mark a 5-minute cooldown guard for a scheduled sync/deletion pass. -- `plugin_mastodon_sync_guard_clear()` — line 1212 — Clear one sync cooldown guard. -- `plugin_mastodon_rate_limit_default_budgets()` — line 1227 — Return conservative per-run Mastodon API budgets. -- `plugin_mastodon_rate_limit_guard_start()` — line 1252 — Start the per-run Mastodon API rate-limit guard. -- `plugin_mastodon_rate_limit_guard_stop()` — line 1276 — Stop the per-run Mastodon API rate-limit guard while keeping its summary inspectable. -- `plugin_mastodon_rate_limit_guard_active()` — line 1287 — Check whether the per-run Mastodon API guard is active. -- `plugin_mastodon_rate_limit_guard_summary()` — line 1295 — Return the current per-run rate-limit guard summary. -- `plugin_mastodon_rate_limit_headers()` — line 1304 — Normalize response headers for rate-limit checks. -- `plugin_mastodon_rate_limit_request_kind()` — line 1325 — Classify Mastodon API requests as general, media upload, or status deletion. -- `plugin_mastodon_rate_limit_block()` — line 1347 — Mark the current run as blocked by a rate-limit budget or Mastodon response and log the stop reason with budget counters once per run. -- `plugin_mastodon_rate_limit_acquire()` — line 1383 — Reserve request/media/delete budget before a Mastodon API request proceeds. -- `plugin_mastodon_rate_limit_observe_response()` — line 1424 — Update the guard from `X-RateLimit-*` headers and `429` responses. -- `plugin_mastodon_rate_limit_blocked_reason()` — line 1448 — Return the current rate-limit block reason. -- `plugin_mastodon_rate_limit_blocked_response()` — line 1460 — Build the synthetic `429` response used for locally blocked requests. -- `plugin_mastodon_rate_limit_state_error()` — line 1476 — Return the rate-limit reason that should be written to synchronization state. -- `plugin_mastodon_bbcode_attr_escape()` — line 4723 — Escape a value for safe BBCode attribute usage. -- `plugin_mastodon_bbcode_text_escape()` — line 4735 — Escape plain text embedded between BBCode tags. -- `plugin_mastodon_bbcode_plugin_active()` — line 3500 — Determine whether the BBCode plugin is active for the current FlatPress request. -- `plugin_mastodon_blog_base_url()` — line 3293 — Return the absolute base URL of the current FlatPress installation. -- `plugin_mastodon_build_authorize_url()` — line 7116 — Build the OAuth authorization URL using the scopes that the registered app may safely request. -- `plugin_mastodon_build_comment_status_text()` — line 7414 — Build the status body used when exporting a FlatPress comment. -- `plugin_mastodon_build_entry_status_text()` — line 7346 — Build the status body used when exporting a FlatPress entry. -- `plugin_mastodon_build_flatpress_tag_bbcode()` — line 3798 — Build Tag plugin BBCode from a list of remote Mastodon tags. -- `plugin_mastodon_build_imported_media_bbcode()` — line 5643 — Build FlatPress BBCode for imported remote media attachments, including AudioVideo optional description endtag text. -- `plugin_mastodon_cleanup_imported_text()` — line 4056 — Clean imported text before saving it to FlatPress. -- `plugin_mastodon_cleanup_uploaded_media()` — line 6275 — Best-effort cleanup for uploaded Mastodon media that never reached a final status request. -- `plugin_mastodon_collect_entry_files()` — line 6610 — Collect entry files recursively from the FlatPress content tree. -- `plugin_mastodon_collect_known_entry_context_targets()` — line 7890 — Collect known synchronized entry threads that should have their Mastodon reply context refreshed. -- `plugin_mastodon_collect_local_entry_media()` — line 5114 — Collect local images, galleries, AudioVideo media, optional AudioVideo endtag descriptions, and video poster thumbnails referenced by an entry. -- `plugin_mastodon_comment_hash()` — line 4560 — Build a change-detection hash for a FlatPress comment. -- `plugin_mastodon_comment_parent_fields()` — line 3104 — Return the comment fields that may contain a parent reference. -- `plugin_mastodon_companion_plugins_status()` — line 3566 — Return the status of companion FlatPress plugins used for the full Mastodon feature set. -- `plugin_mastodon_compare_local_entries_for_export()` — line 6663 — Compare local FlatPress entries for Mastodon export order. -- `plugin_mastodon_configured_status_language()` — line 1926 — Read the configured FlatPress locale and return the Mastodon language code. -- `plugin_mastodon_create_status()` — line 7293 — Create a Mastodon status. -- `plugin_mastodon_date_matches_sync_start()` — line 2161 — Determine whether a content date passes the configured sync start date. -- `plugin_mastodon_datetime_date_key()` — line 2137 — Normalize a stored date/datetime string to the sync-start date-key format. -- `plugin_mastodon_default_content_stats()` — line 458 — Return the default counters for the last content synchronization. -- `plugin_mastodon_default_deletion_stats()` — line 475 — Return the default counters for the last deletion synchronization. -- `plugin_mastodon_default_options()` — line 77 — Return the default plugin option values. -- `plugin_mastodon_default_state()` — line 488 — Return the default runtime state structure, including the targeted deletion-follow-up scope marker. -- `plugin_mastodon_delete_media_attachment()` — line 6256 — Delete an uploaded Mastodon media attachment before it is attached to a final status. -- `plugin_mastodon_delete_status()` — line 7267 — Delete a Mastodon status, including media when requested. -- `plugin_mastodon_detect_local_comment_parent_id()` — line 3130 — Detect the local parent comment identifier from comment data. -- `plugin_mastodon_dom_children_to_flatpress()` — line 4110 — Convert DOM child nodes into FlatPress BBCode text. -- `plugin_mastodon_dom_node_to_flatpress()` — line 4128 — Convert a single DOM node into FlatPress BBCode text. -- `plugin_mastodon_domains_match()` — line 4042 — Determine whether two host names belong to the same domain family. -- `plugin_mastodon_emoticon_entity_to_unicode()` — line 3811 — Convert an emoticon HTML entity into a Unicode character. -- `plugin_mastodon_emoticon_map()` — line 3824 — Return the FlatPress emoticon-to-Unicode lookup map. -- `plugin_mastodon_emoticons_plugin_active()` — line 3551 — Determine whether the Emoticons plugin is active for the current FlatPress request. -- `plugin_mastodon_enabled_plugin_state()` — line 3418 — Determine whether a FlatPress plugin is enabled in the centralized plugin configuration. -- `plugin_mastodon_ensure_state_dir()` — line 2241 — Ensure that the plugin runtime directory exists. -- `plugin_mastodon_entry_hash()` — line 4548 — Build a change-detection hash for a FlatPress entry. -- `plugin_mastodon_entry_media_signature()` — line 5325 — Build a signature for media references contained in entry content. -- `plugin_mastodon_exchange_code_for_token()` — line 7136 — Exchange an OAuth authorization code for an access token using the same negotiated scope string. -- `plugin_mastodon_extend_time_limit()` — line 5842 — Refresh or raise the PHP execution time budget for long-running Mastodon work without lowering an existing higher or unlimited limit. -- `plugin_mastodon_extract_flatpress_tags()` — line 3645 — Extract FlatPress Tag plugin labels from an entry body. -- `plugin_mastodon_extract_url_token()` — line 3329 — Extract the URL token from a BBCode or attribute fragment. -- `plugin_mastodon_fediverse_creator_value()` — line 1846 — Build the fediverse creator meta value. -- `plugin_mastodon_fetch_account_statuses()` — line 7198 — Fetch statuses for the authenticated Mastodon account. -- `plugin_mastodon_fetch_media_attachment()` — line 6246 — Fetch a single Mastodon media attachment by ID. -- `plugin_mastodon_fetch_status()` — line 7256 — Fetch a single Mastodon status. -- `plugin_mastodon_fetch_status_context()` — line 7245 — Fetch the conversation context for a Mastodon status. -- `plugin_mastodon_file_prestat()` — line 1486 — Read a cheap file metadata snapshot for cache validation. -- `plugin_mastodon_file_prestat_signature()` — line 1506 — Convert a file metadata snapshot into a stable cache signature. -- `plugin_mastodon_flatpress_to_mastodon()` — line 4409 — Convert FlatPress content into Mastodon-ready plain text. -- `plugin_mastodon_fp_config()` — line 700 — Return the FlatPress configuration, preferring the early-loaded core cache. -- `plugin_mastodon_fp_config_value()` — line 736 — Read a nested FlatPress configuration value. -- `plugin_mastodon_fp_timeoffset_seconds()` — line 752 — Return the configured FlatPress time offset in seconds for exact local admin-time conversion. -- `plugin_mastodon_fp_timeoffset_label()` — line 772 — Format the configured FlatPress time offset as a UTC label for admin users. -- `plugin_mastodon_sync_time_to_minutes()` — line 802 — Convert a normalized `HH:MM` sync-time value into minutes after midnight. -- `plugin_mastodon_minutes_to_sync_time()` — line 813 — Convert minutes after midnight back into a normalized `HH:MM` sync-time value. -- `plugin_mastodon_sync_time_utc_to_local()` — line 829 — Convert the stored UTC synchronization time to the FlatPress-local admin time. -- `plugin_mastodon_sync_time_local_to_utc()` — line 840 — Convert the FlatPress-local admin synchronization time back to stored UTC. -- `plugin_mastodon_format_admin_datetime()` — line 851 — Format stored UTC timestamps for the admin panel using FlatPress `timeoffset`, date format, and time format. -- `plugin_mastodon_get_options()` — line 1517 — Load the saved plugin options and merge them with defaults. -- `plugin_mastodon_guess_subject()` — line 3242 — Guess a subject line from imported plain text. -- `plugin_mastodon_head()` — line 1864 — Print Mastodon profile metadata into the HTML head. -- `plugin_mastodon_html_entity_decode()` — line 3284 — Decode HTML entities using the plugin defaults. -- `plugin_mastodon_http_build_query()` — line 6826 — Build an application/x-www-form-urlencoded query string. -- `plugin_mastodon_http_request()` — line 6882 — Perform an HTTP request using cURL or the stream fallback. -- `plugin_mastodon_http_request_multipart()` — line 6113 — Perform a multipart HTTP request. -- `plugin_mastodon_import_remote_comment()` — line 7703 — Import a remote Mastodon reply into FlatPress as a comment while respecting comment tombstones, including early tombstones for locally deleted exported comments. -- `plugin_mastodon_import_remote_context_descendants()` — line 7787 — Import remote Mastodon replies from a fetched thread context while blocking tombstoned parent/child replies. -- `plugin_mastodon_import_remote_entry()` — line 7452 — Import a remote Mastodon status into FlatPress as an entry. -- `plugin_mastodon_instance_authority()` — line 1800 — Return the Mastodon instance authority used in fediverse creator metadata. -- `plugin_mastodon_instance_character_limit()` — line 7183 — Return the status character limit of the configured instance. -- `plugin_mastodon_instance_configuration()` — line 5952 — Load and cache the Mastodon instance configuration document. -- `plugin_mastodon_instance_media_description_limit()` — line 5975 — Return the media description length limit of the configured instance. -- `plugin_mastodon_instance_media_limit()` — line 5962 — Return the media attachment limit of the configured instance. -- `plugin_mastodon_instance_url_reserved_length()` — line 5988 — Return the reserved Mastodon character budget used for each URL. -- `plugin_mastodon_io_append_file()` — line 956 — Append to a file via the FlatPress I/O layer. -- `plugin_mastodon_io_read_file()` — line 885 — Read a file through the FlatPress I/O layer when available. -- `plugin_mastodon_io_read_file_uncached()` — line 898 — Read a file without FlatPress request-local caches. -- `plugin_mastodon_file_permissions_mode()` — line 913 — Return the FlatPress `FILE_PERMISSIONS` mode used for plugin runtime files. -- `plugin_mastodon_apply_file_permissions()` — line 922 — Apply FlatPress `FILE_PERMISSIONS` to a plugin runtime file. -- `plugin_mastodon_io_write_file()` — line 935 — Write a file through the FlatPress I/O layer when available and enforce `FILE_PERMISSIONS` after successful writes. -- `plugin_mastodon_is_public_host()` — line 3928 — Determine whether a host name resolves to a public endpoint. -- `plugin_mastodon_lang_string()` — line 3388 — Return a localized plugin string or a provided fallback. -- `plugin_mastodon_limit_status_text()` — line 6034 — Truncate status text using Mastodon URL-budget rules. -- `plugin_mastodon_limit_text()` — line 4526 — Limit text to a maximum number of characters. -- `plugin_mastodon_list_local_entries()` — line 6681 — List local FlatPress entry identifiers. -- `plugin_mastodon_local_item_date_key()` — line 2091 — Determine the date key of a local FlatPress entry or comment. -- `plugin_mastodon_local_item_matches_sync_start()` — line 2180 — Determine whether a local FlatPress item should be synchronized. -- `plugin_mastodon_local_item_timestamp()` — line 6637 — Resolve the best timestamp for a local FlatPress item. -- `plugin_mastodon_log()` — line 2250 — Append a line to the plugin sync log. -- `plugin_mastodon_mapping_matches_sync_start()` — line 2201 — Determine whether a stored synchronization mapping still belongs to the active sync-start window. -- `plugin_mastodon_mastodon_api()` — line 7000 — Call the Mastodon API and return the raw HTTP response. -- `plugin_mastodon_mastodon_hashtag_footer()` — line 3687 — Convert FlatPress tag labels into a Mastodon hashtag footer line. -- `plugin_mastodon_mastodon_html_to_flatpress()` — line 4307 — Convert Mastodon HTML content into FlatPress BBCode. -- `plugin_mastodon_mastodon_json()` — line 7049 — Call the Mastodon API and decode a JSON response. -- `plugin_mastodon_maybe_sync()` — line 8513 — Run the scheduled synchronization when the current request is due. -- `plugin_mastodon_media_copy_tree()` — line 4685 — Copy a directory tree used for media synchronization. -- `plugin_mastodon_media_delete_tree()` — line 4658 — Delete a directory tree used for imported media. -- `plugin_mastodon_media_download()` — line 5577 — Download a remote media asset. -- `plugin_mastodon_media_guess_mime_type()` — line 4757 — Guess the MIME type of a local media file. -- `plugin_mastodon_media_description_from_bbcode_content()` — line 5010 — Normalize optional AudioVideo BBCode content into a Mastodon media description. -- `plugin_mastodon_media_parse_tag_attributes()` — line 4979 — Parse key/value attributes from a FlatPress media tag. -- `plugin_mastodon_media_prepare_directory()` — line 4642 — Ensure that a media directory exists. -- `plugin_mastodon_media_relative_to_absolute()` — line 4629 — Resolve a FlatPress media path to an absolute file path. -- `plugin_mastodon_media_processing_attempts()` — line 6311 — Calculate media-type- and size-aware polling attempts for asynchronous Mastodon media processing. -- `plugin_mastodon_media_transfer_timeout()` — line 6335 — Calculate media-type- and size-aware HTTP transfer timeouts for uploads. -- `plugin_mastodon_normalize_comment_parent_id()` — line 3113 — Normalize a stored local comment parent identifier. -- `plugin_mastodon_normalize_delete_sync_enabled()` — line 2059 — Normalize the toggle that enables or disables the follow-up deletion synchronization. -- `plugin_mastodon_normalize_head_username()` — line 1761 — Normalize the configured Mastodon username for HTML head metadata. -- `plugin_mastodon_normalize_import_synced_comments_as_entries()` — line 2023 — Normalize the toggle that allows importing already synchronized local comments as entries. -- `plugin_mastodon_normalize_instance_url()` — line 1728 — Normalize the configured Mastodon instance URL. -- `plugin_mastodon_normalize_status_language()` — line 1904 — Normalize a FlatPress locale string to a Mastodon-compatible ISO 639-1 code. -- `plugin_mastodon_normalize_sync_start_date()` — line 1963 — Normalize the configured sync start date. -- `plugin_mastodon_normalize_sync_time()` — line 1946 — Normalize the configured daily sync time. -- `plugin_mastodon_normalize_tag_list()` — line 3614 — Normalize a list of tag labels. -- `plugin_mastodon_normalize_update_local_from_remote()` — line 2005 — Normalize the toggle that controls whether existing local content may be updated from remote Mastodon data. -- `plugin_mastodon_oauth_legacy_scopes()` — line 512 — Return the legacy OAuth scope string used before scope discovery was added. -- `plugin_mastodon_oauth_preferred_scopes()` — line 606 — Prefer the narrow `profile` scope on current instances and fall back to `read:accounts` on older ones. -- `plugin_mastodon_oauth_profile_scopes()` — line 520 — Return the stricter OAuth scope string that uses `profile` for `verify_credentials`. -- `plugin_mastodon_oauth_scope_supported()` — line 585 — Check whether the configured Mastodon instance advertises support for a specific OAuth scope. -- `plugin_mastodon_oauth_scopes()` — line 623 — Return the OAuth scopes that the currently registered app may safely request. -- `plugin_mastodon_oauth_server_metadata()` — line 529 — Discover and cache OAuth authorization-server metadata from `/.well-known/oauth-authorization-server`. -- `plugin_mastodon_oauth_supported_scopes()` — line 548 — Parse the discoverable OAuth scopes supported by the configured Mastodon instance. -- `plugin_mastodon_parse_http_response_headers()` — line 6718 — Parse raw HTTP response headers. -- `plugin_mastodon_parse_iso_datetime()` — line 3020 — Parse an ISO date/time string into FlatPress date format. -- `plugin_mastodon_parse_iso_timestamp()` — line 3038 — Parse an ISO date/time value into a Unix timestamp. -- `plugin_mastodon_photoswipe_plugin_active()` — line 3517 — Determine whether the PhotoSwipe plugin is active for the current FlatPress request. -- `plugin_mastodon_plain_text_from_bbcode()` — line 3970 — Convert FlatPress BBCode into plain text for Mastodon export, removing complete AudioVideo player tags including optional description content. -- `plugin_mastodon_profile_url()` — line 1830 — Build the public Mastodon profile URL used for the rel-me link. -- `plugin_mastodon_public_comment_url()` — line 4292 — Return the public URL for a specific FlatPress comment. -- `plugin_mastodon_public_comments_url()` — line 4264 — Return the public comments URL for a FlatPress entry. -- `plugin_mastodon_public_entry_url()` — line 4237 — Return the public URL for a FlatPress entry. -- `plugin_mastodon_public_url_for_mastodon()` — line 3951 — Return a Mastodon-safe public URL or an empty string. -- `plugin_mastodon_register_app()` — line 7093 — Register the FlatPress application on the configured Mastodon instance with the preferred discoverable scope set. -- `plugin_mastodon_remote_media_description()` — line 5448 — Resolve the best description for a remote attachment. -- `plugin_mastodon_remote_media_source_url()` — line 5408 — Resolve the best downloadable source URL for a remote attachment. -- `plugin_mastodon_remote_media_source_urls()` — line 5426 — Resolve direct-download fallback candidates for a remote attachment. -- `plugin_mastodon_remote_status_date_key()` — line 2111 — Determine the date key of a remote Mastodon status. -- `plugin_mastodon_remote_status_image_attachments()` — line 5399 — Extract image attachments from a remote Mastodon status. -- `plugin_mastodon_remote_status_is_importable()` — line 3092 — Determine whether a remote Mastodon status may be imported. -- `plugin_mastodon_remote_status_matches_sync_start()` — line 2190 — Determine whether a remote Mastodon status should be synchronized. -- `plugin_mastodon_remote_status_tags()` — line 3708 — Collect remote Mastodon tags from a status entity. -- `plugin_mastodon_remote_status_timestamp()` — line 3060 — Resolve the best FlatPress-adjusted timestamp for a remote Mastodon status. -- `plugin_mastodon_remote_status_visibility()` — line 3079 — Return the normalized visibility of a remote Mastodon status. -- `plugin_mastodon_replace_emoticon_shortcodes_with_unicode()` — line 3878 — Replace FlatPress emoticon shortcodes with Unicode glyphs. -- `plugin_mastodon_replace_unicode_emoticons_with_shortcodes()` — line 3906 — Replace Unicode emoticons with FlatPress shortcodes. -- `plugin_mastodon_resolve_comment_reply_target()` — line 3152 — Resolve the remote reply target for a local comment export. -- `plugin_mastodon_list_local_comment_ids()` — line 3205 — Scan the FlatPress comment directory directly so local reply export is not blocked by stale comment-list caches. -- `plugin_mastodon_response_error_message()` — line 7064 — Extract the most useful error message from an API response. -- `plugin_mastodon_run_deletion_sync()` — line 8192 — Run the deferred deletion synchronization in a follow-up request after content sync completed. -- `plugin_mastodon_run_sync()` — line 8434 — Run a full synchronization cycle. -- `plugin_mastodon_runtime_cache_clear()` — line 685 — Clear one request-local plugin cache bucket or the complete cache. -- `plugin_mastodon_runtime_cache_get()` — line 643 — Return a value from the request-local plugin cache. -- `plugin_mastodon_runtime_cache_set()` — line 667 — Store a value in the request-local plugin cache. -- `plugin_mastodon_safe_filename()` — line 4593 — Sanitize a file name for local storage. -- `plugin_mastodon_safe_path_component()` — line 4578 — Sanitize a string so it can be used as a path component. -- `plugin_mastodon_save_options()` — line 1572 — Persist plugin options. -- `plugin_mastodon_secret_decode()` — line 1697 — Decode a previously stored secret value. -- `plugin_mastodon_secret_encode()` — line 1674 — Encode a secret value before storing it in the configuration. -- `plugin_mastodon_secret_key()` — line 1657 — Build the encryption key used for stored secrets. -- `plugin_mastodon_should_import_synced_comments_as_entries()` — line 2032 — Check whether a remote Mastodon status that is already mapped to a local FlatPress comment may also be imported as an entry. -- `plugin_mastodon_should_run_deletion_sync()` — line 2068 — Check whether the follow-up deletion synchronization is enabled. -- `plugin_mastodon_should_update_local_from_remote()` — line 2014 — Check whether remote Mastodon updates may overwrite already existing local FlatPress content. -- `plugin_mastodon_state_comment_key()` — line 2399 — Build the compound state key used for comment mappings. -- `plugin_mastodon_state_get_comment_meta()` — line 2622 — Return mapping metadata for a local comment. -- `plugin_mastodon_state_set_comment_tombstone()` — line 2636 — Store a tombstone that blocks stale re-imports of one deleted remote comment. -- `plugin_mastodon_state_has_comment_tombstone()` — line 2656 — Check whether one remote Mastodon comment status was tombstoned locally. -- `plugin_mastodon_protect_locally_deleted_exported_comments()` — line 2667 — Tombstone locally deleted exported FlatPress comment mappings before the next content sync can stale-reimport them from Mastodon thread context. -- `plugin_mastodon_reattach_local_comment_to_entry_status()` — line 2713 — Remove a local imported reply parent link and reattach the surviving reply to the synchronized entry status after its remote parent reply disappeared. -- `plugin_mastodon_state_remove_pending_comment_remote_recheck()` — line 2773 — Remove one pending descendant recheck marker. -- `plugin_mastodon_state_get_pending_comment_remote_recheck()` — line 2787 — Return one pending descendant recheck marker. -- `plugin_mastodon_state_set_pending_comment_remote_recheck()` — line 2802 — Mark one local comment for follow-up verification after an ancestor disappeared remotely. -- `plugin_mastodon_state_set_deletions_pending()` — line 2827 — Persist whether another deletion follow-up request is pending and which scope it should run. -- `plugin_mastodon_state_has_comment_recheck_scope()` — line 2839 — Check whether the next deletion follow-up request should run only the targeted descendant recheck scope. -- `plugin_mastodon_build_comment_remote_child_index()` — line 2848 — Build a direct-child index for mapped remote reply trees. -- `plugin_mastodon_queue_comment_descendant_remote_rechecks()` — line 2876 — Queue only the direct mapped local children of one deleted remote comment for additional verification passes. -- `plugin_mastodon_process_pending_comment_remote_rechecks()` — line 2915 — Process pending descendant rechecks breadth-first so deeper reply chains can converge within the same targeted follow-up request. -- `plugin_mastodon_state_get_entry_meta()` — line 2527 — Return mapping metadata for a local entry. -- `plugin_mastodon_normalize_deletions_pending_scope()` — line 2339 — Normalize the targeted deletion-follow-up scope marker. -- `plugin_mastodon_state_normalize()` — line 2352 — Normalize a runtime state array and fill in missing keys. -- `plugin_mastodon_state_read()` — line 2260 — Load the persisted runtime state from disk and fall back to the short-lived APCu state if the file is temporarily missing, empty, or invalid. -- `plugin_mastodon_state_remove_comment_mapping()` — line 2506 — Remove the mapping between a local comment and a remote status. -- `plugin_mastodon_state_remove_entry_mapping()` — line 2487 — Remove the mapping between a local entry and a remote status. -- `plugin_mastodon_state_set_comment_mapping()` — line 2452 — Store the mapping between a local comment and a remote status. -- `plugin_mastodon_state_set_entry_mapping()` — line 2414 — Store the mapping between a local entry and a remote status. -- `plugin_mastodon_state_write()` — line 2314 — Persist the runtime state to disk and always refresh the short-lived APCu last-known-good state when possible. -- `plugin_mastodon_status_missing_response()` — line 7280 — Check whether an API response means that the referenced Mastodon status no longer exists. -- `plugin_mastodon_status_text_length()` — line 6002 — Calculate the Mastodon-visible status length with instance URL budgeting. -- `plugin_mastodon_stream_context_request()` — line 6748 — Perform an HTTP request through a stream context fallback. -- `plugin_mastodon_strip_flatpress_tag_bbcode()` — line 3670 — Remove Tag plugin BBCode blocks from entry content. -- `plugin_mastodon_strip_trailing_mastodon_hashtag_footer()` — line 3735 — Remove a trailing Mastodon hashtag footer from imported plain text. -- `plugin_mastodon_subject_line_is_noise()` — line 4014 — Determine whether an extracted line should be ignored as a subject. -- `plugin_mastodon_sync_due()` — line 8412 — Determine whether the scheduled synchronization is currently due. -- `plugin_mastodon_sync_local_to_remote()` — line 7989 — Synchronize local FlatPress content to Mastodon. -- `plugin_mastodon_sync_remote_to_local()` — line 7924 — Synchronize remote Mastodon content into FlatPress. -- `plugin_mastodon_tag_plugin_active()` — line 3483 — Determine whether the Tag plugin is active for the current FlatPress request. -- `plugin_mastodon_timestamp_date_key()` — line 2077 — Convert a FlatPress-adjusted timestamp into a stable date key. -- `plugin_mastodon_update_status()` — line 7320 — Update an existing Mastodon status. -- `plugin_mastodon_upload_media_items()` — line 6404 — Upload local media items to Mastodon and collect the created media IDs. -- `plugin_mastodon_verify_credentials()` — line 7166 — Verify the currently configured access token. -- `plugin_mastodon_wait_for_media_attachment()` — line 6355 — Poll an asynchronously processed Mastodon media attachment until it is ready or times out. -- `setup()` — line 8720 — Register the Mastodon admin panel template and assign plugin data to Smarty. +- `main()` — line 8999 — Keep the admin panel lifecycle compatible with FlatPress without extra processing. +- `onsubmit()` — line 9003 — Process configuration saves, OAuth actions, including app registration and authorization-code exchange, and the manual synchronization trigger. +- `plugin_mastodon_absolute_url()` — line 3609 — Convert a URL or path into an absolute URL when possible. +- `plugin_mastodon_admin_assign()` — line 8949 — Assign plugin data to Smarty for the admin panel. +- `plugin_mastodon_apcu_cache_key()` — line 1003 — Build the namespaced APCu key used by this plugin. +- `plugin_mastodon_apcu_delete()` — line 1045 — Delete a value from APCu using the FlatPress namespace key builder. +- `plugin_mastodon_apcu_enabled()` — line 994 — Check whether shared APCu caching is available for the plugin. +- `plugin_mastodon_apcu_fetch()` — line 1017 — Fetch a value from APCu through the FlatPress namespace helper. +- `plugin_mastodon_apcu_store()` — line 1032 — Store a value in APCu through the FlatPress namespace helper. +- `plugin_mastodon_state_fallback_key()` — line 1057 — Return the APCu key used for the short-lived last-known-good state fallback. +- `plugin_mastodon_state_fallback_store()` — line 1066 — Store a normalized runtime state in APCu for a 5-minute emergency fallback window. +- `plugin_mastodon_state_fallback_read()` — line 1078 — Fetch the short-lived last-known-good state fallback from APCu. +- `plugin_mastodon_sync_guard_kind()` — line 1096 — Normalize the cooldown guard kind. +- `plugin_mastodon_sync_guard_apcu_key()` — line 1106 — Return the APCu key used for one sync cooldown guard. +- `plugin_mastodon_sync_guard_entry_active()` — line 1116 — Check whether one cooldown guard entry is still active. +- `plugin_mastodon_sync_guard_apcu_store()` — line 1127 — Store one cooldown guard in APCu. +- `plugin_mastodon_sync_guard_file_read()` — line 1140 — Read and prune the file-backed cooldown guard. +- `plugin_mastodon_sync_guard_file_write()` — line 1167 — Write the file-backed cooldown guard. +- `plugin_mastodon_sync_guard_active()` — line 1188 — Check whether a recent scheduled content/deletion pass is still cooling down. +- `plugin_mastodon_sync_guard_mark()` — line 1213 — Mark a 5-minute cooldown guard for a scheduled sync/deletion pass. +- `plugin_mastodon_sync_guard_clear()` — line 1234 — Clear one sync cooldown guard. +- `plugin_mastodon_rate_limit_default_budgets()` — line 1249 — Return conservative per-run Mastodon API budgets. +- `plugin_mastodon_rate_limit_window_budgets()` — line 1273 — Return persistent cross-run Mastodon API window budgets. +- `plugin_mastodon_rate_limit_window_config()` — line 1300 — Return the persistent window key, budget, TTL, and block reason for one request kind. +- `plugin_mastodon_rate_limit_window_entry()` — line 1320 — Normalize one persistent rate-limit window entry and drop expired values. +- `plugin_mastodon_rate_limit_window_read()` — line 1340 — Read and prune the persistent cross-run rate-limit windows. +- `plugin_mastodon_rate_limit_window_write()` — line 1372 — Persist cross-run rate-limit windows with FlatPress file permissions. +- `plugin_mastodon_rate_limit_window_acquire()` — line 1397 — Reserve one media-upload/delete/account-status-page slot from the persistent cross-run window. +- `plugin_mastodon_rate_limit_window_clear()` — line 1446 — Clear persistent rate-limit windows for tests or recovery tooling. +- `plugin_mastodon_rate_limit_guard_start()` — line 1458 — Start the per-run Mastodon API rate-limit guard. +- `plugin_mastodon_rate_limit_guard_stop()` — line 1484 — Stop the per-run Mastodon API rate-limit guard while keeping its summary inspectable. +- `plugin_mastodon_rate_limit_guard_active()` — line 1495 — Check whether the per-run Mastodon API guard is active. +- `plugin_mastodon_rate_limit_guard_summary()` — line 1503 — Return the current per-run rate-limit guard summary. +- `plugin_mastodon_rate_limit_headers()` — line 1512 — Normalize response headers for rate-limit checks. +- `plugin_mastodon_rate_limit_request_kind()` — line 1533 — Classify Mastodon API requests as general, media upload, status deletion, or account-status paging. +- `plugin_mastodon_rate_limit_block()` — line 1558 — Mark the current run as blocked by a rate-limit budget, persistent window, or Mastodon response and log the stop reason with budget counters once per run. +- `plugin_mastodon_rate_limit_acquire()` — line 1602 — Reserve per-run and persistent window budget before a Mastodon API request proceeds. +- `plugin_mastodon_rate_limit_observe_response()` — line 1651 — Update the guard from `X-RateLimit-*` headers and `429` responses. +- `plugin_mastodon_rate_limit_blocked_reason()` — line 1675 — Return the current rate-limit block reason. +- `plugin_mastodon_rate_limit_blocked_response()` — line 1687 — Build the synthetic `429` response used for locally blocked requests. +- `plugin_mastodon_rate_limit_state_error()` — line 1703 — Return the rate-limit reason that should be written to synchronization state. +- `plugin_mastodon_bbcode_attr_escape()` — line 4986 — Escape a value for safe BBCode attribute usage. +- `plugin_mastodon_bbcode_text_escape()` — line 4998 — Escape plain text embedded between BBCode tags. +- `plugin_mastodon_bbcode_plugin_active()` — line 3763 — Determine whether the BBCode plugin is active for the current FlatPress request. +- `plugin_mastodon_blog_base_url()` — line 3556 — Return the absolute base URL of the current FlatPress installation. +- `plugin_mastodon_build_authorize_url()` — line 7379 — Build the OAuth authorization URL using the scopes that the registered app may safely request. +- `plugin_mastodon_build_comment_status_text()` — line 7677 — Build the status body used when exporting a FlatPress comment. +- `plugin_mastodon_build_entry_status_text()` — line 7609 — Build the status body used when exporting a FlatPress entry. +- `plugin_mastodon_build_flatpress_tag_bbcode()` — line 4061 — Build Tag plugin BBCode from a list of remote Mastodon tags. +- `plugin_mastodon_build_imported_media_bbcode()` — line 5906 — Build FlatPress BBCode for imported remote media attachments, including AudioVideo optional description endtag text. +- `plugin_mastodon_cleanup_imported_text()` — line 4319 — Clean imported text before saving it to FlatPress. +- `plugin_mastodon_cleanup_uploaded_media()` — line 6538 — Best-effort cleanup for uploaded Mastodon media that never reached a final status request. +- `plugin_mastodon_collect_entry_files()` — line 6873 — Collect entry files recursively from the FlatPress content tree. +- `plugin_mastodon_collect_known_entry_context_targets()` — line 8154 — Collect known synchronized entry threads that should have their Mastodon reply context refreshed while respecting the synchronization start-date window. +- `plugin_mastodon_collect_local_entry_media()` — line 5377 — Collect local images, galleries, AudioVideo media, optional AudioVideo endtag descriptions, and video poster thumbnails referenced by an entry. +- `plugin_mastodon_comment_hash()` — line 4823 — Build a change-detection hash for a FlatPress comment. +- `plugin_mastodon_comment_parent_fields()` — line 3367 — Return the comment fields that may contain a parent reference. +- `plugin_mastodon_companion_plugins_status()` — line 3829 — Return the status of companion FlatPress plugins used for the full Mastodon feature set. +- `plugin_mastodon_compare_local_entries_for_export()` — line 6926 — Compare local FlatPress entries for Mastodon export order. +- `plugin_mastodon_configured_status_language()` — line 2157 — Read the configured FlatPress locale and return the Mastodon language code. +- `plugin_mastodon_create_status()` — line 7556 — Create a Mastodon status. +- `plugin_mastodon_date_matches_content_window()` — line 2485 — Combine the durable sync-start lower bound with the scheduled-run recent-content window. +- `plugin_mastodon_date_matches_sync_start()` — line 2392 — Determine whether a content date passes the configured sync start date. +- `plugin_mastodon_datetime_date_key()` — line 2368 — Normalize a stored date/datetime string to the sync-start date-key format. +- `plugin_mastodon_default_content_stats()` — line 479 — Return the default counters for the last content synchronization. +- `plugin_mastodon_default_deletion_stats()` — line 496 — Return the default counters for the last deletion synchronization. +- `plugin_mastodon_default_options()` — line 98 — Return the default plugin option values. +- `plugin_mastodon_default_state()` — line 509 — Return the default runtime state structure, including the targeted deletion-follow-up scope marker. +- `plugin_mastodon_delete_media_attachment()` — line 6519 — Delete an uploaded Mastodon media attachment before it is attached to a final status. +- `plugin_mastodon_delete_status()` — line 7530 — Delete a Mastodon status, including media when requested. +- `plugin_mastodon_detect_local_comment_parent_id()` — line 3393 — Detect the local parent comment identifier from comment data. +- `plugin_mastodon_dom_children_to_flatpress()` — line 4373 — Convert DOM child nodes into FlatPress BBCode text. +- `plugin_mastodon_dom_node_to_flatpress()` — line 4391 — Convert a single DOM node into FlatPress BBCode text. +- `plugin_mastodon_domains_match()` — line 4305 — Determine whether two host names belong to the same domain family. +- `plugin_mastodon_emoticon_entity_to_unicode()` — line 4074 — Convert an emoticon HTML entity into a Unicode character. +- `plugin_mastodon_emoticon_map()` — line 4087 — Return the FlatPress emoticon-to-Unicode lookup map. +- `plugin_mastodon_emoticons_plugin_active()` — line 3814 — Determine whether the Emoticons plugin is active for the current FlatPress request. +- `plugin_mastodon_enabled_plugin_state()` — line 3681 — Determine whether a FlatPress plugin is enabled in the centralized plugin configuration. +- `plugin_mastodon_ensure_state_dir()` — line 2472 — Ensure that the plugin runtime directory exists. +- `plugin_mastodon_entry_hash()` — line 4811 — Build a change-detection hash for a FlatPress entry. +- `plugin_mastodon_entry_media_signature()` — line 5588 — Build a signature for media references contained in entry content. +- `plugin_mastodon_exchange_code_for_token()` — line 7399 — Exchange an OAuth authorization code for an access token using the same negotiated scope string. +- `plugin_mastodon_extend_time_limit()` — line 6105 — Refresh or raise the PHP execution time budget for long-running Mastodon work without lowering an existing higher or unlimited limit. +- `plugin_mastodon_extract_flatpress_tags()` — line 3908 — Extract FlatPress Tag plugin labels from an entry body. +- `plugin_mastodon_extract_url_token()` — line 3592 — Extract the URL token from a BBCode or attribute fragment. +- `plugin_mastodon_fediverse_creator_value()` — line 2077 — Build the fediverse creator meta value. +- `plugin_mastodon_fetch_account_statuses()` — line 7461 — Fetch statuses for the authenticated Mastodon account. +- `plugin_mastodon_fetch_media_attachment()` — line 6509 — Fetch a single Mastodon media attachment by ID. +- `plugin_mastodon_fetch_status()` — line 7519 — Fetch a single Mastodon status. +- `plugin_mastodon_fetch_status_context()` — line 7508 — Fetch the conversation context for a Mastodon status. +- `plugin_mastodon_file_prestat()` — line 1713 — Read a cheap file metadata snapshot for cache validation. +- `plugin_mastodon_file_prestat_signature()` — line 1733 — Convert a file metadata snapshot into a stable cache signature. +- `plugin_mastodon_flatpress_to_mastodon()` — line 4672 — Convert FlatPress content into Mastodon-ready plain text. +- `plugin_mastodon_fp_config()` — line 722 — Return the FlatPress configuration, preferring the early-loaded core cache. +- `plugin_mastodon_fp_config_value()` — line 758 — Read a nested FlatPress configuration value. +- `plugin_mastodon_fp_timeoffset_seconds()` — line 774 — Return the configured FlatPress time offset in seconds for exact local admin-time conversion. +- `plugin_mastodon_fp_timeoffset_label()` — line 794 — Format the configured FlatPress time offset as a UTC label for admin users. +- `plugin_mastodon_sync_time_to_minutes()` — line 824 — Convert a normalized `HH:MM` sync-time value into minutes after midnight. +- `plugin_mastodon_minutes_to_sync_time()` — line 835 — Convert minutes after midnight back into a normalized `HH:MM` sync-time value. +- `plugin_mastodon_sync_time_utc_to_local()` — line 851 — Convert the stored UTC synchronization time to the FlatPress-local admin time. +- `plugin_mastodon_sync_time_local_to_utc()` — line 862 — Convert the FlatPress-local admin synchronization time back to stored UTC. +- `plugin_mastodon_format_admin_datetime()` — line 873 — Format stored UTC timestamps for the admin panel using FlatPress `timeoffset`, date format, and time format. +- `plugin_mastodon_get_options()` — line 1744 — Load the saved plugin options and merge them with defaults. +- `plugin_mastodon_guess_subject()` — line 3505 — Guess a subject line from imported plain text. +- `plugin_mastodon_head()` — line 2095 — Print Mastodon profile metadata into the HTML head. +- `plugin_mastodon_html_entity_decode()` — line 3547 — Decode HTML entities using the plugin defaults. +- `plugin_mastodon_http_build_query()` — line 7089 — Build an application/x-www-form-urlencoded query string. +- `plugin_mastodon_http_request()` — line 7145 — Perform an HTTP request using cURL or the stream fallback. +- `plugin_mastodon_http_request_multipart()` — line 6376 — Perform a multipart HTTP request. +- `plugin_mastodon_import_remote_comment()` — line 7966 — Import a remote Mastodon reply into FlatPress as a comment while respecting comment tombstones, including early tombstones for locally deleted exported comments. +- `plugin_mastodon_import_remote_context_descendants()` — line 8050 — Import remote Mastodon replies from a fetched thread context while blocking tombstoned parent/child replies. +- `plugin_mastodon_import_remote_entry()` — line 7715 — Import a remote Mastodon status into FlatPress as an entry. +- `plugin_mastodon_instance_authority()` — line 2031 — Return the Mastodon instance authority used in fediverse creator metadata. +- `plugin_mastodon_instance_character_limit()` — line 7446 — Return the status character limit of the configured instance. +- `plugin_mastodon_instance_configuration()` — line 6215 — Load and cache the Mastodon instance configuration document. +- `plugin_mastodon_instance_media_description_limit()` — line 6238 — Return the media description length limit of the configured instance. +- `plugin_mastodon_instance_media_limit()` — line 6225 — Return the media attachment limit of the configured instance. +- `plugin_mastodon_instance_url_reserved_length()` — line 6251 — Return the reserved Mastodon character budget used for each URL. +- `plugin_mastodon_io_append_file()` — line 978 — Append to a file via the FlatPress I/O layer. +- `plugin_mastodon_io_read_file()` — line 907 — Read a file through the FlatPress I/O layer when available. +- `plugin_mastodon_io_read_file_uncached()` — line 920 — Read a file without FlatPress request-local caches. +- `plugin_mastodon_file_permissions_mode()` — line 935 — Return the FlatPress `FILE_PERMISSIONS` mode used for plugin runtime files. +- `plugin_mastodon_apply_file_permissions()` — line 944 — Apply FlatPress `FILE_PERMISSIONS` to a plugin runtime file. +- `plugin_mastodon_io_write_file()` — line 957 — Write a file through the FlatPress I/O layer when available and enforce `FILE_PERMISSIONS` after successful writes. +- `plugin_mastodon_is_public_host()` — line 4191 — Determine whether a host name resolves to a public endpoint. +- `plugin_mastodon_lang_string()` — line 3651 — Return a localized plugin string or a provided fallback. +- `plugin_mastodon_limit_status_text()` — line 6297 — Truncate status text using Mastodon URL-budget rules. +- `plugin_mastodon_limit_text()` — line 4789 — Limit text to a maximum number of characters. +- `plugin_mastodon_list_local_entries()` — line 6944 — List local FlatPress entry identifiers. +- `plugin_mastodon_local_item_date_key()` — line 2322 — Determine the date key of a local FlatPress entry or comment. +- `plugin_mastodon_local_item_matches_content_window()` — line 2519 — Determine whether a local FlatPress item is inside the active content synchronization window. +- `plugin_mastodon_local_item_matches_sync_start()` — line 2411 — Determine whether a local FlatPress item should be synchronized. +- `plugin_mastodon_local_item_timestamp()` — line 6900 — Resolve the best timestamp for a local FlatPress item. +- `plugin_mastodon_log()` — line 2481 — Append a line to the plugin sync log. +- `plugin_mastodon_mapping_matches_sync_start()` — line 2432 — Determine whether a stored synchronization mapping still belongs to the active sync-start window. +- `plugin_mastodon_mastodon_api()` — line 7263 — Call the Mastodon API and return the raw HTTP response. +- `plugin_mastodon_mastodon_hashtag_footer()` — line 3950 — Convert FlatPress tag labels into a Mastodon hashtag footer line. +- `plugin_mastodon_mastodon_html_to_flatpress()` — line 4570 — Convert Mastodon HTML content into FlatPress BBCode. +- `plugin_mastodon_mastodon_json()` — line 7312 — Call the Mastodon API and decode a JSON response. +- `plugin_mastodon_maybe_sync()` — line 8787 — Run the scheduled synchronization when the current request is due. +- `plugin_mastodon_media_copy_tree()` — line 4948 — Copy a directory tree used for media synchronization. +- `plugin_mastodon_media_delete_tree()` — line 4921 — Delete a directory tree used for imported media. +- `plugin_mastodon_media_download()` — line 5840 — Download a remote media asset. +- `plugin_mastodon_media_guess_mime_type()` — line 5020 — Guess the MIME type of a local media file. +- `plugin_mastodon_media_description_from_bbcode_content()` — line 5273 — Normalize optional AudioVideo BBCode content into a Mastodon media description. +- `plugin_mastodon_media_parse_tag_attributes()` — line 5242 — Parse key/value attributes from a FlatPress media tag. +- `plugin_mastodon_media_prepare_directory()` — line 4905 — Ensure that a media directory exists. +- `plugin_mastodon_media_relative_to_absolute()` — line 4892 — Resolve a FlatPress media path to an absolute file path. +- `plugin_mastodon_media_processing_attempts()` — line 6574 — Calculate media-type- and size-aware polling attempts for asynchronous Mastodon media processing. +- `plugin_mastodon_media_transfer_timeout()` — line 6598 — Calculate media-type- and size-aware HTTP transfer timeouts for uploads. +- `plugin_mastodon_normalize_comment_parent_id()` — line 3376 — Normalize a stored local comment parent identifier. +- `plugin_mastodon_normalize_delete_sync_enabled()` — line 2290 — Normalize the toggle that enables or disables the follow-up deletion synchronization. +- `plugin_mastodon_normalize_head_username()` — line 1992 — Normalize the configured Mastodon username for HTML head metadata. +- `plugin_mastodon_normalize_import_synced_comments_as_entries()` — line 2254 — Normalize the toggle that allows importing already synchronized local comments as entries. +- `plugin_mastodon_normalize_instance_url()` — line 1959 — Normalize the configured Mastodon instance URL. +- `plugin_mastodon_normalize_old_thread_reply_check()` — line 2330 — Normalize the toggle that enables rotating context checks for old synchronized Mastodon threads. +- `plugin_mastodon_normalize_scheduled_window_days()` — line 2235 — Normalize the configured 7/14/30-day automatic window for scheduled runs. +- `plugin_mastodon_normalize_status_language()` — line 2135 — Normalize a FlatPress locale string to a Mastodon-compatible ISO 639-1 code. +- `plugin_mastodon_normalize_sync_start_date()` — line 2194 — Normalize the configured sync start date. +- `plugin_mastodon_normalize_sync_time()` — line 2177 — Normalize the configured daily sync time. +- `plugin_mastodon_normalize_tag_list()` — line 3877 — Normalize a list of tag labels. +- `plugin_mastodon_normalize_update_local_from_remote()` — line 2236 — Normalize the toggle that controls whether existing local content may be updated from remote Mastodon data. +- `plugin_mastodon_oauth_legacy_scopes()` — line 534 — Return the legacy OAuth scope string used before scope discovery was added. +- `plugin_mastodon_oauth_preferred_scopes()` — line 628 — Prefer the narrow `profile` scope on current instances and fall back to `read:accounts` on older ones. +- `plugin_mastodon_oauth_profile_scopes()` — line 542 — Return the stricter OAuth scope string that uses `profile` for `verify_credentials`. +- `plugin_mastodon_oauth_scope_supported()` — line 607 — Check whether the configured Mastodon instance advertises support for a specific OAuth scope. +- `plugin_mastodon_oauth_scopes()` — line 645 — Return the OAuth scopes that the currently registered app may safely request. +- `plugin_mastodon_oauth_server_metadata()` — line 551 — Discover and cache OAuth authorization-server metadata from `/.well-known/oauth-authorization-server`. +- `plugin_mastodon_oauth_supported_scopes()` — line 570 — Parse the discoverable OAuth scopes supported by the configured Mastodon instance. +- `plugin_mastodon_old_thread_context_rotation_limit()` — line 8372 — Return the maximum number of older known threads checked for replies per content sync run. +- `plugin_mastodon_parse_http_response_headers()` — line 6981 — Parse raw HTTP response headers. +- `plugin_mastodon_parse_iso_datetime()` — line 3283 — Parse an ISO date/time string into FlatPress date format. +- `plugin_mastodon_parse_iso_timestamp()` — line 3301 — Parse an ISO date/time value into a Unix timestamp. +- `plugin_mastodon_photoswipe_plugin_active()` — line 3780 — Determine whether the PhotoSwipe plugin is active for the current FlatPress request. +- `plugin_mastodon_plain_text_from_bbcode()` — line 4233 — Convert FlatPress BBCode into plain text for Mastodon export, removing complete AudioVideo player tags including optional description content. +- `plugin_mastodon_profile_url()` — line 2061 — Build the public Mastodon profile URL used for the rel-me link. +- `plugin_mastodon_public_comment_url()` — line 4555 — Return the public URL for a specific FlatPress comment. +- `plugin_mastodon_public_comments_url()` — line 4527 — Return the public comments URL for a FlatPress entry. +- `plugin_mastodon_public_entry_url()` — line 4500 — Return the public URL for a FlatPress entry. +- `plugin_mastodon_public_url_for_mastodon()` — line 4214 — Return a Mastodon-safe public URL or an empty string. +- `plugin_mastodon_register_app()` — line 7356 — Register the FlatPress application on the configured Mastodon instance with the preferred discoverable scope set. +- `plugin_mastodon_remote_media_description()` — line 5711 — Resolve the best description for a remote attachment. +- `plugin_mastodon_remote_media_source_url()` — line 5671 — Resolve the best downloadable source URL for a remote attachment. +- `plugin_mastodon_remote_media_source_urls()` — line 5689 — Resolve direct-download fallback candidates for a remote attachment. +- `plugin_mastodon_remote_status_date_key()` — line 2342 — Determine the date key of a remote Mastodon status. +- `plugin_mastodon_remote_status_image_attachments()` — line 5662 — Extract image attachments from a remote Mastodon status. +- `plugin_mastodon_remote_status_is_importable()` — line 3355 — Determine whether a remote Mastodon status may be imported. +- `plugin_mastodon_remote_status_matches_content_window()` — line 2540 — Determine whether a remote Mastodon status is inside the active content synchronization window. +- `plugin_mastodon_remote_status_matches_sync_start()` — line 2421 — Determine whether a remote Mastodon status should be synchronized. +- `plugin_mastodon_remote_status_tags()` — line 3971 — Collect remote Mastodon tags from a status entity. +- `plugin_mastodon_remote_status_timestamp()` — line 3323 — Resolve the best FlatPress-adjusted timestamp for a remote Mastodon status. +- `plugin_mastodon_remote_status_visibility()` — line 3342 — Return the normalized visibility of a remote Mastodon status. +- `plugin_mastodon_replace_emoticon_shortcodes_with_unicode()` — line 4141 — Replace FlatPress emoticon shortcodes with Unicode glyphs. +- `plugin_mastodon_replace_unicode_emoticons_with_shortcodes()` — line 4169 — Replace Unicode emoticons with FlatPress shortcodes. +- `plugin_mastodon_resolve_comment_reply_target()` — line 3415 — Resolve the remote reply target for a local comment export. +- `plugin_mastodon_list_local_comment_ids()` — line 3468 — Scan the FlatPress comment directory directly so local reply export is not blocked by stale comment-list caches. +- `plugin_mastodon_response_error_message()` — line 7327 — Extract the most useful error message from an API response. +- `plugin_mastodon_run_deletion_sync()` — line 8460 — Run the deferred deletion synchronization in a follow-up request after content sync completed. +- `plugin_mastodon_run_sync()` — line 8707 — Run a full synchronization cycle. +- `plugin_mastodon_runtime_cache_clear()` — line 707 — Clear one request-local plugin cache bucket or the complete cache. +- `plugin_mastodon_runtime_cache_get()` — line 665 — Return a value from the request-local plugin cache. +- `plugin_mastodon_runtime_cache_set()` — line 689 — Store a value in the request-local plugin cache. +- `plugin_mastodon_safe_filename()` — line 4856 — Sanitize a file name for local storage. +- `plugin_mastodon_safe_path_component()` — line 4841 — Sanitize a string so it can be used as a path component. +- `plugin_mastodon_save_options()` — line 1799 — Persist plugin options. +- `plugin_mastodon_scheduled_window_choices()` — line 2247 — Return the localized admin radio choices for the scheduled synchronization window. +- `plugin_mastodon_scheduled_window_start_date()` — line 2468 — Return the FlatPress-local date key that starts the automatic scheduled sync window. +- `plugin_mastodon_secret_decode()` — line 1928 — Decode a previously stored secret value. +- `plugin_mastodon_secret_encode()` — line 1905 — Encode a secret value before storing it in the configuration. +- `plugin_mastodon_secret_key()` — line 1888 — Build the encryption key used for stored secrets. +- `plugin_mastodon_should_import_synced_comments_as_entries()` — line 2263 — Check whether a remote Mastodon status that is already mapped to a local FlatPress comment may also be imported as an entry. +- `plugin_mastodon_should_check_old_thread_replies()` — line 2339 — Check whether old synchronized Mastodon threads should be checked for replies in rotating batches. +- `plugin_mastodon_should_run_deletion_sync()` — line 2299 — Check whether the follow-up deletion synchronization is enabled. +- `plugin_mastodon_should_update_local_from_remote()` — line 2245 — Check whether remote Mastodon updates may overwrite already existing local FlatPress content. +- `plugin_mastodon_state_comment_key()` — line 2631 — Build the compound state key used for comment mappings. +- `plugin_mastodon_state_get_comment_meta()` — line 2854 — Return mapping metadata for a local comment. +- `plugin_mastodon_state_has_dirty_comment()` — line 2969 — Check whether a comment is queued for synchronization outside the scheduled window. +- `plugin_mastodon_state_has_dirty_entry()` — line 2919 — Check whether an entry is queued for synchronization outside the scheduled window. +- `plugin_mastodon_state_set_comment_tombstone()` — line 2868 — Store a tombstone that blocks stale re-imports of one deleted remote comment. +- `plugin_mastodon_state_has_comment_tombstone()` — line 2888 — Check whether one remote Mastodon comment status was tombstoned locally. +- `plugin_mastodon_protect_locally_deleted_exported_comments()` — line 2899 — Tombstone locally deleted exported FlatPress comment mappings before the next content sync can stale-reimport them from Mastodon thread context. +- `plugin_mastodon_reattach_local_comment_to_entry_status()` — line 2945 — Remove a local imported reply parent link and reattach the surviving reply to the synchronized entry status after its remote parent reply disappeared. +- `plugin_mastodon_state_remove_pending_comment_remote_recheck()` — line 3005 — Remove one pending descendant recheck marker. +- `plugin_mastodon_state_get_pending_comment_remote_recheck()` — line 3019 — Return one pending descendant recheck marker. +- `plugin_mastodon_state_set_pending_comment_remote_recheck()` — line 3034 — Mark one local comment for follow-up verification after an ancestor disappeared remotely. +- `plugin_mastodon_state_set_deletions_pending()` — line 3060 — Persist whether another deletion follow-up request is pending, which scope it should run, and when it may start. +- `plugin_mastodon_deletion_sync_due()` — line 3074 — Check whether the pending deletion synchronization may start after its persisted not-before timestamp. +- `plugin_mastodon_state_has_comment_recheck_scope()` — line 3102 — Check whether the next deletion follow-up request should run only the targeted descendant recheck scope. +- `plugin_mastodon_build_comment_remote_child_index()` — line 3111 — Build a direct-child index for mapped remote reply trees. +- `plugin_mastodon_queue_comment_descendant_remote_rechecks()` — line 3139 — Queue only the direct mapped local children of one deleted remote comment for additional verification passes. +- `plugin_mastodon_process_pending_comment_remote_rechecks()` — line 3178 — Process pending descendant rechecks breadth-first so deeper reply chains can converge within the same targeted follow-up request. +- `plugin_mastodon_state_get_entry_meta()` — line 2759 — Return mapping metadata for a local entry. +- `plugin_mastodon_normalize_deletions_pending_scope()` — line 2570 — Normalize the targeted deletion-follow-up scope marker. +- `plugin_mastodon_state_normalize()` — line 2583 — Normalize a runtime state array and fill in missing keys. +- `plugin_mastodon_state_read()` — line 2491 — Load the persisted runtime state from disk and fall back to the short-lived APCu state if the file is temporarily missing, empty, or invalid. +- `plugin_mastodon_state_remove_dirty_comment()` — line 2955 — Remove a comment from the dirty queue. +- `plugin_mastodon_state_remove_dirty_entry()` — line 2906 — Remove an entry from the dirty queue. +- `plugin_mastodon_state_remove_comment_mapping()` — line 2738 — Remove the mapping between a local comment and a remote status. +- `plugin_mastodon_state_remove_entry_mapping()` — line 2719 — Remove the mapping between a local entry and a remote status. +- `plugin_mastodon_state_set_comment_mapping()` — line 2684 — Store the mapping between a local comment and a remote status. +- `plugin_mastodon_state_set_dirty_comment()` — line 2932 — Add an older changed comment to the persistent dirty queue. +- `plugin_mastodon_state_set_dirty_entry()` — line 2886 — Add an older changed entry to the persistent dirty queue. +- `plugin_mastodon_state_set_entry_mapping()` — line 2646 — Store the mapping between a local entry and a remote status. +- `plugin_mastodon_state_write()` — line 2545 — Persist the runtime state to disk and always refresh the short-lived APCu last-known-good state when possible. +- `plugin_mastodon_status_missing_response()` — line 7543 — Check whether an API response means that the referenced Mastodon status no longer exists. +- `plugin_mastodon_status_text_length()` — line 6265 — Calculate the Mastodon-visible status length with instance URL budgeting. +- `plugin_mastodon_stream_context_request()` — line 7011 — Perform an HTTP request through a stream context fallback. +- `plugin_mastodon_strip_flatpress_tag_bbcode()` — line 3933 — Remove Tag plugin BBCode blocks from entry content. +- `plugin_mastodon_strip_trailing_mastodon_hashtag_footer()` — line 3998 — Remove a trailing Mastodon hashtag footer from imported plain text. +- `plugin_mastodon_subject_line_is_noise()` — line 4277 — Determine whether an extracted line should be ignored as a subject. +- `plugin_mastodon_sync_due()` — line 8685 — Determine whether the scheduled synchronization is currently due. +- `plugin_mastodon_sync_local_to_remote()` — line 8257 — Synchronize local FlatPress content to Mastodon. +- `plugin_mastodon_sync_remote_to_local()` — line 8192 — Synchronize remote Mastodon content into FlatPress. +- `plugin_mastodon_tag_plugin_active()` — line 3746 — Determine whether the Tag plugin is active for the current FlatPress request. +- `plugin_mastodon_timestamp_date_key()` — line 2308 — Convert a FlatPress-adjusted timestamp into a stable date key. +- `plugin_mastodon_update_status()` — line 7583 — Update an existing Mastodon status. +- `plugin_mastodon_upload_media_items()` — line 6667 — Upload local media items to Mastodon and collect the created media IDs. +- `plugin_mastodon_verify_credentials()` — line 7429 — Verify the currently configured access token. +- `plugin_mastodon_wait_for_media_attachment()` — line 6618 — Poll an asynchronously processed Mastodon media attachment until it is ready or times out. +- `setup()` — line 8994 — Register the Mastodon admin panel template and assign plugin data to Smarty. diff --git a/fp-plugins/mastodon/lang/lang.cs-cz.php b/fp-plugins/mastodon/lang/lang.cs-cz.php index afc1a561..95a07552 100644 --- a/fp-plugins/mastodon/lang/lang.cs-cz.php +++ b/fp-plugins/mastodon/lang/lang.cs-cz.php @@ -10,6 +10,11 @@ 'password' => 'Heslo Mastodonu', 'sync_time' => 'Denní čas synchronizace', 'sync_start_date' => 'Synchronizovat od data', + 'sync_scheduled_window_days' => 'Automatické časové okno pro naplánované spouštění', + 'sync_scheduled_window_days_desc' => 'Naplánované denní spouštění synchronizuje pouze toto nejnovější časové okno, ale výše uvedené datum zůstává trvalou dolní hranicí. Ruční spuštění stále používají pouze výše uvedené datum.', + 'sync_scheduled_window_7_days' => '7 dní', + 'sync_scheduled_window_14_days' => '14 dní', + 'sync_scheduled_window_30_days' => '30 dní', 'more_options' => 'Další možnosti', 'update_local_from_remote' => 'Aktualizovat existující místní obsah z Mastodonu', 'update_local_from_remote_desc' => 'Je-li plugin aktivován, mohou být stávající příspěvky a komentáře z FlatPressu přepsány změnami z Mastodonu. Příspěvky a komentáře z FlatPressu budou poté zkráceny na 500 znaků.', @@ -19,6 +24,8 @@ 'import_synced_comments_as_entries_desc' => 'Pokud je tato funkce aktivována, mohou být statusy z Mastodonu, které jsou již přiřazeny k místnímu komentáři ve FlatPress, navíc importovány jako příspěvky ve FlatPress. Ve výchozím nastavení je tato funkce deaktivována, aby se zabránilo duplicitnímu obsahu.', 'quote_imported_reply_parent' => 'Při importu citovat komentář Mastodonu, na který se odpovídá', 'quote_imported_reply_parent_desc' => 'Je-li zapnuto, importované odpovědi z Mastodonu na jiný komentář začínají blokem citace, který zobrazuje autora původního komentáře a jeho text.', + 'old_thread_reply_check' => 'Zkontrolovat staré vlákna na nové odpovědi', + 'old_thread_reply_check_desc' => 'Je-li tato funkce zapnutá, plugin kontroluje starší synchronizovaná vlákna Mastodonu na nové odpovědi v malých rotujících dávkách.', 'reply_quote_author_format' => '%s napsal(a):', 'save' => 'Uložit nastavení', 'oauth_head' => 'Pomocník OAuth', diff --git a/fp-plugins/mastodon/lang/lang.da-dk.php b/fp-plugins/mastodon/lang/lang.da-dk.php index 45fb2fc0..baf3a8c8 100644 --- a/fp-plugins/mastodon/lang/lang.da-dk.php +++ b/fp-plugins/mastodon/lang/lang.da-dk.php @@ -10,6 +10,11 @@ 'password' => 'Mastodon-adgangskode', 'sync_time' => 'Dagligt synkroniseringstidspunkt', 'sync_start_date' => 'Synkroniser fra dato', + 'sync_scheduled_window_days' => 'Automatisk tidsvindue for planlagte kørsler', + 'sync_scheduled_window_days_desc' => 'Planlagte daglige kørsler synkroniserer kun dette seneste tidsvindue, men datoen ovenfor forbliver den faste nedre grænse. Manuelle kørsler bruger stadig kun datoen ovenfor.', + 'sync_scheduled_window_7_days' => '7 dage', + 'sync_scheduled_window_14_days' => '14 dage', + 'sync_scheduled_window_30_days' => '30 dage', 'more_options' => 'Yderligere indstillinger', 'update_local_from_remote' => 'Opdater eksisterende lokalt indhold fra Mastodon', 'update_local_from_remote_desc' => 'Når dette er aktiveret, kan ændringer fra Mastodon overskrive allerede eksisterende lokale FlatPress-indlæg og kommentarer. Indlæg og kommentarer på FlatPress bliver derefter forkortet til 500 tegn.', @@ -19,6 +24,8 @@ 'import_synced_comments_as_entries_desc' => 'Når dette er aktiveret, kan Mastodon-statusser, der allerede er knyttet til en lokal FlatPress-kommentar, også importeres som FlatPress-indlæg. Dette er som standard deaktiveret for at undgå duplikeret indhold.', 'quote_imported_reply_parent' => 'Citér den Mastodon-kommentar, der svares på, ved import', 'quote_imported_reply_parent_desc' => 'Når dette er aktiveret, begynder importerede Mastodon-svar til en anden kommentar med en citatblok, der viser den bruger, der svares til, og kommentarteksten.', + 'old_thread_reply_check' => 'Tjek gamle tråde for svar', + 'old_thread_reply_check_desc' => 'Når denne funktion er aktiveret, tjekker plugin\'et ældre synkroniserede Mastodon-tråde for nye svar i små roterende batches.', 'reply_quote_author_format' => '%s skrev:', 'save' => 'Gem indstillinger', 'oauth_head' => 'OAuth-hjælp', diff --git a/fp-plugins/mastodon/lang/lang.de-de.php b/fp-plugins/mastodon/lang/lang.de-de.php index 875b846b..579920ed 100644 --- a/fp-plugins/mastodon/lang/lang.de-de.php +++ b/fp-plugins/mastodon/lang/lang.de-de.php @@ -10,6 +10,11 @@ 'password' => 'Mastodon-Passwort', 'sync_time' => 'Tägliche Synchronisationszeit', 'sync_start_date' => 'Synchronisierung ab Datum', + 'sync_scheduled_window_days' => 'Automatisches Fenster für geplante Läufe', + 'sync_scheduled_window_days_desc' => 'Geplante tägliche Läufe synchronisieren nur dieses jüngere Fenster; das Datum darüber bleibt die dauerhafte Untergrenze. Manuelle Läufe verwenden weiterhin nur das Datum darüber.', + 'sync_scheduled_window_7_days' => '7 Tage', + 'sync_scheduled_window_14_days' => '14 Tage', + 'sync_scheduled_window_30_days' => '30 Tage', 'more_options' => 'Weitere Optionen', 'update_local_from_remote' => 'Vorhandene FlatPress-Inhalte aus Mastodon aktualisieren', 'update_local_from_remote_desc' => 'Wenn aktiviert, dürfen bereits vorhandene FlatPress-Beiträge und -Kommentare durch Änderungen aus Mastodon überschrieben werden. FlatPress Eintäge und Kommentare werden dann auf 500 Zeichen gekürzt.', @@ -19,6 +24,8 @@ 'import_synced_comments_as_entries_desc' => 'Wenn aktiviert, dürfen Mastodon-Statusmeldungen, die bereits einem lokalen FlatPress-Kommentar zugeordnet sind, zusätzlich als FlatPress-Beitrag importiert werden. Standardmäßig ist dies deaktiviert, um doppelte Inhalte zu vermeiden.', 'quote_imported_reply_parent' => 'Beantworteten Mastodon-Kommentar beim Import zitieren', 'quote_imported_reply_parent_desc' => 'Wenn aktiviert, beginnen importierte Mastodon-Antworten auf einen anderen Kommentar mit einem Zitatblock, der den beantworteten Benutzer und den Kommentartext zeigt.', + 'old_thread_reply_check' => 'Alte Threads auf Antworten prüfen', + 'old_thread_reply_check_desc' => 'Wenn aktiviert, prüft das Plugin ältere synchronisierte Mastodon-Threads in kleinen rotierenden Gruppen auf neue Antworten.', 'reply_quote_author_format' => '%s schrieb:', 'save' => 'Einstellungen speichern', 'oauth_head' => 'OAuth-Helfer', diff --git a/fp-plugins/mastodon/lang/lang.el-gr.php b/fp-plugins/mastodon/lang/lang.el-gr.php index ae5880a4..31c452f1 100644 --- a/fp-plugins/mastodon/lang/lang.el-gr.php +++ b/fp-plugins/mastodon/lang/lang.el-gr.php @@ -10,6 +10,11 @@ 'password' => 'Κωδικός Mastodon', 'sync_time' => 'Ημερήσια ώρα συγχρονισμού', 'sync_start_date' => 'Συγχρονισμός από ημερομηνία', + 'sync_scheduled_window_days' => 'Αυτόματο χρονικό παράθυρο για προγραμματισμένες εκτελέσεις', + 'sync_scheduled_window_days_desc' => 'Οι προγραμματισμένες καθημερινές εκτελέσεις συγχρονίζουν μόνο αυτό το πρόσφατο χρονικό παράθυρο, αλλά η παραπάνω ημερομηνία παραμένει το μόνιμο κατώτατο όριο. Οι χειροκίνητες εκτελέσεις εξακολουθούν να χρησιμοποιούν μόνο την παραπάνω ημερομηνία.', + 'sync_scheduled_window_7_days' => '7 ημέρες', + 'sync_scheduled_window_14_days' => '14 ημέρες', + 'sync_scheduled_window_30_days' => '30 ημέρες', 'more_options' => 'Πρόσθετες επιλογές', 'update_local_from_remote' => 'Ενημέρωση υπαρχόμενου τοπικού περιεχομένου από το Mastodon', 'update_local_from_remote_desc' => 'Όταν είναι ενεργοποιημένο, οι αλλαγές από το Mastodon μπορούν να αντικαταστήσουν ήδη υπάρχουσες τοπικές εγγραφές και σχόλια του FlatPress. Οι αναρτήσεις και τα σχόλια στο FlatPress περιορίζονται τότε στα 500 χαρακτήρες.', @@ -19,6 +24,8 @@ 'import_synced_comments_as_entries_desc' => 'Όταν είναι ενεργοποιημένο, καταστάσεις Mastodon που έχουν ήδη αντιστοιχιστεί σε τοπικό σχόλιο FlatPress μπορούν επίσης να εισαχθούν ως εγγραφές FlatPress. Από προεπιλογή είναι απενεργοποιημένο για να αποφεύγεται το διπλό περιεχόμενο.', 'quote_imported_reply_parent' => 'Παράθεση του σχολίου Mastodon στο οποίο γίνεται απάντηση κατά την εισαγωγή', 'quote_imported_reply_parent_desc' => 'Όταν είναι ενεργό, οι εισαγόμενες απαντήσεις Mastodon σε άλλο σχόλιο ξεκινούν με ένα μπλοκ παράθεσης που δείχνει τον χρήστη και το κείμενο του σχολίου στο οποίο έγινε απάντηση.', + 'old_thread_reply_check' => 'Έλεγχος παλαιών νημάτων για απαντήσεις', + 'old_thread_reply_check_desc' => 'Όταν είναι ενεργοποιημένο, το πρόσθετο ελέγχει παλαιότερα συγχρονισμένα νήματα Mastodon για νέες απαντήσεις σε μικρές εναλλασσόμενες παρτίδες.', 'reply_quote_author_format' => 'Ο/Η %s έγραψε:', 'save' => 'Αποθήκευση ρυθμίσεων', 'oauth_head' => 'Βοηθός OAuth', diff --git a/fp-plugins/mastodon/lang/lang.en-us.php b/fp-plugins/mastodon/lang/lang.en-us.php index a0b824eb..f2b0efff 100644 --- a/fp-plugins/mastodon/lang/lang.en-us.php +++ b/fp-plugins/mastodon/lang/lang.en-us.php @@ -10,6 +10,11 @@ 'password' => 'Mastodon password', 'sync_time' => 'Daily sync time', 'sync_start_date' => 'Synchronize from date', + 'sync_scheduled_window_days' => 'Automatic window for scheduled runs', + 'sync_scheduled_window_days_desc' => 'Scheduled daily runs synchronize only this recent window, but the date above remains the permanent lower bound. Manual runs still use only the date above.', + 'sync_scheduled_window_7_days' => '7 days', + 'sync_scheduled_window_14_days' => '14 days', + 'sync_scheduled_window_30_days' => '30 days', 'more_options' => 'Additional options', 'update_local_from_remote' => 'Update existing local content from Mastodon', 'update_local_from_remote_desc' => 'When enabled, changes from Mastodon may overwrite already existing local FlatPress entries and comments. FlatPress posts and comments are then truncated to 500 characters.', @@ -19,6 +24,8 @@ 'import_synced_comments_as_entries_desc' => 'When enabled, Mastodon statuses that are already mapped to a local FlatPress comment may also be imported as FlatPress entries. This is disabled by default to avoid duplicate content.', 'quote_imported_reply_parent' => 'Quote replied-to Mastodon comment on import', 'quote_imported_reply_parent_desc' => 'When enabled, imported Mastodon replies to another comment begin with a quote block that shows the replied-to user and comment text.', + 'old_thread_reply_check' => 'Check old threads for replies', + 'old_thread_reply_check_desc' => 'When enabled, the plugin checks older synchronized Mastodon threads for new replies in small rotating batches.', 'reply_quote_author_format' => '%s wrote:', 'save' => 'Save settings', 'oauth_head' => 'OAuth helper', diff --git a/fp-plugins/mastodon/lang/lang.es-es.php b/fp-plugins/mastodon/lang/lang.es-es.php index df2c42f2..cf5424df 100644 --- a/fp-plugins/mastodon/lang/lang.es-es.php +++ b/fp-plugins/mastodon/lang/lang.es-es.php @@ -10,6 +10,11 @@ 'password' => 'Contraseña de Mastodon', 'sync_time' => 'Hora diaria de sincronización', 'sync_start_date' => 'Sincronizar desde la fecha', + 'sync_scheduled_window_days' => 'Ventana automática para ejecuciones programadas', + 'sync_scheduled_window_days_desc' => 'Las ejecuciones diarias programadas solo sincronizan esta ventana reciente, pero la fecha indicada anteriormente sigue siendo el límite inferior permanente. Las ejecuciones manuales siguen utilizando únicamente la fecha anterior.', + 'sync_scheduled_window_7_days' => '7 días', + 'sync_scheduled_window_14_days' => '14 días', + 'sync_scheduled_window_30_days' => '30 días', 'more_options' => 'Opciones adicionales', 'update_local_from_remote' => 'Actualizar el contenido local existente desde Mastodon', 'update_local_from_remote_desc' => 'Cuando está activado, los cambios de Mastodon pueden sobrescribir entradas y comentarios locales de FlatPress ya existentes. Las entradas y los comentarios de FlatPress se acortarán entonces a 500 caracteres.', @@ -19,6 +24,8 @@ 'import_synced_comments_as_entries_desc' => 'Cuando está activado, los estados de Mastodon que ya están asociados a un comentario local de FlatPress también pueden importarse como entradas de FlatPress. Esta opción está desactivada por defecto para evitar contenido duplicado.', 'quote_imported_reply_parent' => 'Citar al importar el comentario de Mastodon al que se responde', 'quote_imported_reply_parent_desc' => 'Cuando está activado, las respuestas de Mastodon importadas a otro comentario comienzan con un bloque de cita que muestra el usuario citado y el texto del comentario.', + 'old_thread_reply_check' => 'Comprobar respuestas en hilos antiguos', + 'old_thread_reply_check_desc' => 'Cuando está habilitado, el complemento comprueba los hilos antiguos sincronizados de Mastodon en busca de nuevas respuestas en pequeños lotes rotativos.', 'reply_quote_author_format' => '%s escribió:', 'save' => 'Guardar ajustes', 'oauth_head' => 'Asistente de OAuth', diff --git a/fp-plugins/mastodon/lang/lang.eu-es.php b/fp-plugins/mastodon/lang/lang.eu-es.php index c23b72e6..4ca05fde 100644 --- a/fp-plugins/mastodon/lang/lang.eu-es.php +++ b/fp-plugins/mastodon/lang/lang.eu-es.php @@ -10,6 +10,11 @@ 'password' => 'Mastodon pasahitza', 'sync_time' => 'Eguneroko sinkronizazio-ordua', 'sync_start_date' => 'Data honetatik aurrera sinkronizatu', + 'sync_scheduled_window_days' => 'Ezarritako exekuzioetarako leiho automatikoa', + 'sync_scheduled_window_days_desc' => 'Ezarritako eguneroko exekuzioek leiho berri hau bakarrik sinkronizatzen dute, baina goiko datak beheko muga iraunkorra izaten jarraitzen du. Eskuzko exekuzioek oraindik ere goiko datari bakarrik egiten diote erreferentzia.', + 'sync_scheduled_window_7_days' => '7 egun', + 'sync_scheduled_window_14_days' => '14 egun', + 'sync_scheduled_window_30_days' => '30 egun', 'more_options' => 'Aukera gehigarriak', 'update_local_from_remote' => 'Lehendik dagoen tokiko edukia Mastodontik eguneratu', 'update_local_from_remote_desc' => 'Gaituta dagoenean, Mastodoneko aldaketek lehendik dauden FlatPress-eko sarrera eta iruzkin lokalak gainidatz ditzakete. Ondoren, FlatPresseko mezuak eta iruzkinak 500 karaktereetara laburbiltzen dira.', @@ -19,6 +24,8 @@ 'import_synced_comments_as_entries_desc' => 'Gaituta dagoenean, dagoeneko FlatPress-eko tokiko iruzkin bati lotutako Mastodon egoerak FlatPress-eko sarrera gisa ere inporta daitezke. Berez desgaituta dago, eduki bikoiztuak saihesteko.', 'quote_imported_reply_parent' => 'Erantzuten zaion Mastodon iruzkina inportatzean aipatzea', 'quote_imported_reply_parent_desc' => 'Gaituta dagoenean, beste iruzkin bati emandako Mastodon erantzun inportatuek erantzundako erabiltzailea eta iruzkinaren testua erakusten dituen aipamen-bloke batekin hasten dira.', + 'old_thread_reply_check' => 'Hitz zaharrak erantzun berrien bila egiaztatu', + 'old_thread_reply_check_desc' => 'Gaituenean, pluginak sinkronizatutako Mastodon hari zaharragoak erantzun berri bila egiaztatzen ditu, txandaka eta tamaina txikiko multzoetan.', 'reply_quote_author_format' => '%s-k idatzi zuen:', 'save' => 'Gorde ezarpenak', 'oauth_head' => 'OAuth laguntzailea', diff --git a/fp-plugins/mastodon/lang/lang.fr-fr.php b/fp-plugins/mastodon/lang/lang.fr-fr.php index e91a9c29..70184590 100644 --- a/fp-plugins/mastodon/lang/lang.fr-fr.php +++ b/fp-plugins/mastodon/lang/lang.fr-fr.php @@ -10,6 +10,11 @@ 'password' => 'Mot de passe Mastodon', 'sync_time' => 'Heure de synchronisation quotidienne', 'sync_start_date' => 'Synchroniser à partir de la date', + 'sync_scheduled_window_days' => 'Fenêtre automatique pour les exécutions planifiées', + 'sync_scheduled_window_days_desc' => 'Les exécutions quotidiennes planifiées ne synchronisent que cette fenêtre récente, mais la date ci-dessus reste la limite inférieure permanente. Les exécutions manuelles utilisent toujours uniquement la date ci-dessus.', + 'sync_scheduled_window_7_days' => '7 jours', + 'sync_scheduled_window_14_days' => '14 jours', + 'sync_scheduled_window_30_days' => '30 jours', 'more_options' => 'Options supplémentaires', 'update_local_from_remote' => 'Mettre à jour le contenu local existant depuis Mastodon', 'update_local_from_remote_desc' => 'Lorsqu’elle est activée, les modifications de Mastodon peuvent écraser des entrées et commentaires FlatPress déjà existants localement. Les publications et les commentaires sur FlatPress sont alors limités à 500 caractères.', @@ -19,6 +24,8 @@ 'import_synced_comments_as_entries_desc' => 'Lorsqu’elle est activée, les statuts Mastodon déjà associés à un commentaire local FlatPress peuvent également être importés comme entrées FlatPress. Cette option est désactivée par défaut afin d’éviter les contenus dupliqués.', 'quote_imported_reply_parent' => 'Citer à l’importation le commentaire Mastodon auquel il est répondu', 'quote_imported_reply_parent_desc' => 'Lorsque cette option est activée, les réponses Mastodon importées à un autre commentaire commencent par un bloc de citation indiquant l’utilisateur cité et le texte du commentaire.', + 'old_thread_reply_check' => 'Vérifier les réponses dans les anciens fils de discussion', + 'old_thread_reply_check_desc' => 'Lorsque cette option est activée, le plugin vérifie les anciens fils de discussion Mastodon synchronisés à la recherche de nouvelles réponses par petits lots tournants.', 'reply_quote_author_format' => '%s a écrit :', 'save' => 'Enregistrer les réglages', 'oauth_head' => 'Assistant OAuth', diff --git a/fp-plugins/mastodon/lang/lang.it-it.php b/fp-plugins/mastodon/lang/lang.it-it.php index 84fb2f4a..ecc5f633 100644 --- a/fp-plugins/mastodon/lang/lang.it-it.php +++ b/fp-plugins/mastodon/lang/lang.it-it.php @@ -10,6 +10,11 @@ 'password' => 'Password Mastodon', 'sync_time' => 'Ora di sincronizzazione giornaliera', 'sync_start_date' => 'Sincronizza dalla data', + 'sync_scheduled_window_days' => 'Finestra automatica per le esecuzioni pianificate', + 'sync_scheduled_window_days_desc' => 'Le esecuzioni giornaliere pianificate sincronizzano solo questa finestra recente, ma la data sopra indicata rimane il limite inferiore permanente. Le esecuzioni manuali utilizzano ancora solo la data sopra indicata.', + 'sync_scheduled_window_7_days' => '7 giorni', + 'sync_scheduled_window_14_days' => '14 giorni', + 'sync_scheduled_window_30_days' => '30 giorni', 'more_options' => 'Opzioni aggiuntive', 'update_local_from_remote' => 'Aggiorna il contenuto locale esistente da Mastodon', 'update_local_from_remote_desc' => 'Quando è abilitato, le modifiche provenienti da Mastodon possono sovrascrivere post e commenti FlatPress locali già esistenti. I post e i commenti su FlatPress vengono quindi ridotti a 500 caratteri.', @@ -19,6 +24,8 @@ 'import_synced_comments_as_entries_desc' => 'Quando è abilitato, gli stati Mastodon già associati a un commento locale di FlatPress possono essere importati anche come post di FlatPress. Questa opzione è disattivata per impostazione predefinita per evitare contenuti duplicati.', 'quote_imported_reply_parent' => 'Citare durante l’importazione il commento Mastodon a cui si risponde', 'quote_imported_reply_parent_desc' => 'Quando è attivo, le risposte Mastodon importate a un altro commento iniziano con un blocco di citazione che mostra l’utente citato e il testo del commento.', + 'old_thread_reply_check' => 'Controlla i thread vecchi per le risposte', + 'old_thread_reply_check_desc' => 'Quando è abilitato, il plugin controlla i thread Mastodon sincronizzati più vecchi alla ricerca di nuove risposte in piccoli lotti a rotazione.', 'reply_quote_author_format' => '%s ha scritto:', 'save' => 'Salva impostazioni', 'oauth_head' => 'Assistente OAuth', diff --git a/fp-plugins/mastodon/lang/lang.ja-jp.php b/fp-plugins/mastodon/lang/lang.ja-jp.php index 1c115185..80eadade 100644 --- a/fp-plugins/mastodon/lang/lang.ja-jp.php +++ b/fp-plugins/mastodon/lang/lang.ja-jp.php @@ -10,6 +10,11 @@ 'password' => 'Mastodon パスワード', 'sync_time' => '毎日の同期時刻', 'sync_start_date' => 'この日付以降を同期', + 'sync_scheduled_window_days' => 'スケジュールされた実行の自動同期期間', + 'sync_scheduled_window_days_desc' => '毎日スケジュールされた実行では、この直近の期間のみが同期されますが、上記の日付が恒久的な下限となります。手動実行では、引き続き上記の日付のみが使用されます。', + 'sync_scheduled_window_7_days' => '7 日間', + 'sync_scheduled_window_14_days' => '14 日間', + 'sync_scheduled_window_30_days' => '30 日間', 'more_options' => '追加オプション', 'update_local_from_remote' => 'Mastodon から既存のローカル内容を更新する', 'update_local_from_remote_desc' => '有効にすると、Mastodon からの変更によって既存の FlatPress のローカルエントリーやコメントが上書きされる場合があります。 FlatPressの投稿やコメントは、500文字に短縮されます。', @@ -19,6 +24,8 @@ 'import_synced_comments_as_entries_desc' => '有効にすると、すでにローカルの FlatPress コメントに対応付けられている Mastodon ステータスも FlatPress エントリーとして取り込めます。重複コンテンツを避けるため、既定では無効です。', 'quote_imported_reply_parent' => 'インポート時に返信先のMastodonコメントを引用する', 'quote_imported_reply_parent_desc' => '有効にすると、別のコメントへのMastodon返信をインポートした際に、返信先のユーザー名とコメント本文を示す引用ブロックを先頭に追加します。', + 'old_thread_reply_check' => '古いスレッドの返信を確認する', + 'old_thread_reply_check_desc' => '有効にすると、プラグインは同期済みの古いMastodonスレッドを、小さなローテーションバッチで新しい返信がないか確認します。', 'reply_quote_author_format' => '%s さんが書きました:', 'save' => '設定を保存', 'oauth_head' => 'OAuth ヘルパー', diff --git a/fp-plugins/mastodon/lang/lang.nl-nl.php b/fp-plugins/mastodon/lang/lang.nl-nl.php index 272e650f..36738e29 100644 --- a/fp-plugins/mastodon/lang/lang.nl-nl.php +++ b/fp-plugins/mastodon/lang/lang.nl-nl.php @@ -10,6 +10,11 @@ 'password' => 'Mastodon-wachtwoord', 'sync_time' => 'Dagelijks synchronisatietijdstip', 'sync_start_date' => 'Synchroniseren vanaf datum', + 'sync_scheduled_window_days' => 'Automatisch venster voor geplande uitvoeringen', + 'sync_scheduled_window_days_desc' => 'Bij geplande dagelijkse uitvoeringen wordt alleen dit recente venster gesynchroniseerd, maar de bovenstaande datum blijft de permanente ondergrens. Handmatige runs gebruiken nog steeds alleen de bovenstaande datum', + 'sync_scheduled_window_7_days' => '7 dagen', + 'sync_scheduled_window_14_days' => '14 dagen', + 'sync_scheduled_window_30_days' => '30 dagen', 'more_options' => 'Aanvullende opties', 'update_local_from_remote' => 'Bestaande lokale inhoud vanuit Mastodon bijwerken', 'update_local_from_remote_desc' => 'Wanneer ingeschakeld, mogen wijzigingen uit Mastodon reeds bestaande lokale FlatPress-berichten en reacties overschrijven. Berichten en reacties op FlatPress worden vervolgens ingekort tot 500 tekens.', @@ -19,6 +24,8 @@ 'import_synced_comments_as_entries_desc' => 'Wanneer ingeschakeld, mogen Mastodon-statussen die al aan een lokale FlatPress-reactie zijn gekoppeld ook als FlatPress-bericht worden geïmporteerd. Dit staat standaard uit om dubbele inhoud te voorkomen.', 'quote_imported_reply_parent' => 'Beantwoorde Mastodon-opmerking bij import citeren', 'quote_imported_reply_parent_desc' => 'Wanneer ingeschakeld, beginnen geïmporteerde Mastodon-antwoorden op een andere opmerking met een citaatblok dat de beantwoorde gebruiker en de tekst van de opmerking toont.', + 'old_thread_reply_check' => 'Controleer oude threads op reacties', + 'old_thread_reply_check_desc' => 'Wanneer ingeschakeld, controleert de plug-in oudere gesynchroniseerde Mastodon-threads op nieuwe reacties in kleine, roterende batches.', 'reply_quote_author_format' => '%s schreef:', 'save' => 'Instellingen opslaan', 'oauth_head' => 'OAuth-helper', diff --git a/fp-plugins/mastodon/lang/lang.pt-br.php b/fp-plugins/mastodon/lang/lang.pt-br.php index 8b344408..f5241b9f 100644 --- a/fp-plugins/mastodon/lang/lang.pt-br.php +++ b/fp-plugins/mastodon/lang/lang.pt-br.php @@ -10,6 +10,11 @@ 'password' => 'Senha do Mastodon', 'sync_time' => 'Horário diário de sincronização', 'sync_start_date' => 'Sincronizar a partir da data', + 'sync_scheduled_window_days' => 'Janela automática para execuções programadas', + 'sync_scheduled_window_days_desc' => 'As execuções diárias programadas sincronizam apenas esta janela mais recente, mas a data acima continua sendo o limite inferior permanente. As execuções manuais continuam usando apenas a data acima', + 'sync_scheduled_window_7_days' => '7 dias', + 'sync_scheduled_window_14_days' => '14 dias', + 'sync_scheduled_window_30_days' => '30 dias', 'more_options' => 'Opções adicionais', 'update_local_from_remote' => 'Atualizar conteúdo local existente a partir do Mastodon', 'update_local_from_remote_desc' => 'Quando ativado, alterações do Mastodon podem sobrescrever entradas e comentários locais do FlatPress que já existam. As postagens e comentários no FlatPress serão então reduzidos para 500 caracteres.', @@ -19,6 +24,8 @@ 'import_synced_comments_as_entries_desc' => 'Quando ativado, estados do Mastodon que já estejam associados a um comentário local do FlatPress também podem ser importados como entradas do FlatPress. Isso vem desativado por padrão para evitar conteúdo duplicado.', 'quote_imported_reply_parent' => 'Citar na importação o comentário do Mastodon ao qual a resposta se refere', 'quote_imported_reply_parent_desc' => 'Quando ativado, respostas do Mastodon importadas para outro comentário começam com um bloco de citação que mostra o usuário citado e o texto do comentário.', + 'old_thread_reply_check' => 'Verificar respostas em tópicos antigos', + 'old_thread_reply_check_desc' => 'Quando ativado, o plugin verifica tópicos antigos sincronizados do Mastodon em busca de novas respostas em pequenos lotes rotativos.', 'reply_quote_author_format' => '%s escreveu:', 'save' => 'Salvar configurações', 'oauth_head' => 'Assistente OAuth', diff --git a/fp-plugins/mastodon/lang/lang.ru-ru.php b/fp-plugins/mastodon/lang/lang.ru-ru.php index de0fb9b9..eb65fc8b 100644 --- a/fp-plugins/mastodon/lang/lang.ru-ru.php +++ b/fp-plugins/mastodon/lang/lang.ru-ru.php @@ -10,6 +10,11 @@ 'password' => 'Пароль Mastodon', 'sync_time' => 'Ежедневное время синхронизации', 'sync_start_date' => 'Синхронизировать начиная с даты', + 'sync_scheduled_window_days' => 'Автоматическое окно для запланированных запусков', + 'sync_scheduled_window_days_desc' => 'Ежедневные запланированные запуски синхронизируют только это последнее окно, но указанная выше дата остается постоянной нижней границей. При ручном запуске по-прежнему используется только указанная выше дата.', + 'sync_scheduled_window_7_days' => '7 дней', + 'sync_scheduled_window_14_days' => '14 дней', + 'sync_scheduled_window_30_days' => '30 дней', 'more_options' => 'Дополнительные параметры', 'update_local_from_remote' => 'Обновлять существующее локальное содержимое из Mastodon', 'update_local_from_remote_desc' => 'Если включено, изменения из Mastodon могут перезаписывать уже существующие локальные записи и комментарии FlatPress. Сообщения и комментарии в FlatPress будут сокращены до 500 символов.', @@ -19,6 +24,8 @@ 'import_synced_comments_as_entries_desc' => 'Если включено, статусы Mastodon, уже сопоставленные с локальным комментарием FlatPress, также могут импортироваться как записи FlatPress. По умолчанию эта возможность отключена, чтобы избежать дублирования содержимого.', 'quote_imported_reply_parent' => 'Цитировать при импорте комментарий Mastodon, на который дан ответ', 'quote_imported_reply_parent_desc' => 'Если включено, импортированные ответы Mastodon на другой комментарий начинаются с блока цитаты, который показывает пользователя, которому ответили, и текст комментария.', + 'old_thread_reply_check' => 'Проверять старые ветки на наличие ответов', + 'old_thread_reply_check_desc' => 'Если включено, плагин проверяет старые синхронизированные ветки Mastodon на наличие новых ответов небольшими поочередными партиями.', 'reply_quote_author_format' => '%s написал(а):', 'save' => 'Сохранить настройки', 'oauth_head' => 'Помощник OAuth', diff --git a/fp-plugins/mastodon/lang/lang.sl-si.php b/fp-plugins/mastodon/lang/lang.sl-si.php index 35db56e9..ee1a629c 100644 --- a/fp-plugins/mastodon/lang/lang.sl-si.php +++ b/fp-plugins/mastodon/lang/lang.sl-si.php @@ -10,6 +10,11 @@ 'password' => 'Geslo za Mastodon', 'sync_time' => 'Dnevni čas sinhronizacije', 'sync_start_date' => 'Sinhroniziraj od datuma', + 'sync_scheduled_window_days' => 'Avtomatsko časovno okno za načrtovana izvajanja', + 'sync_scheduled_window_days_desc' => 'Načrtovana dnevna izvajanja sinhronizirajo le to zadnje časovno okno, vendar zgornji datum ostaja trajna spodnja meja. Ročni izvajanji še vedno uporabljata le zgornji datum', + 'sync_scheduled_window_7_days' => '7 dni', + 'sync_scheduled_window_14_days' => '14 dni', + 'sync_scheduled_window_30_days' => '30 dni', 'more_options' => 'Dodatne možnosti', 'update_local_from_remote' => 'Posodobi obstoječo lokalno vsebino iz Mastodona', 'update_local_from_remote_desc' => 'Ko je omogočeno, lahko spremembe iz Mastodona prepišejo že obstoječe lokalne vnose in komentarje FlatPressa. Objave in komentarji na FlatPressu se nato skrajšajo na 500 znakov.', @@ -19,6 +24,8 @@ 'import_synced_comments_as_entries_desc' => 'Ko je omogočeno, se lahko statusi Mastodona, ki so že preslikani na lokalni komentar FlatPressa, uvozijo tudi kot vnosi FlatPressa. To je privzeto onemogočeno, da se prepreči podvojena vsebina.', 'quote_imported_reply_parent' => 'Ob uvozu citiraj komentar Mastodona, na katerega je odgovorjeno', 'quote_imported_reply_parent_desc' => 'Ko je omogočeno, se uvoženi odgovori Mastodona na drug komentar začnejo z blokom citata, ki prikaže uporabnika, kateremu je bilo odgovorjeno, in besedilo komentarja.', + 'old_thread_reply_check' => 'Preveri stare niti za odgovore', + 'old_thread_reply_check_desc' => 'Ko je omogočeno, vtičnik preveri starejše sinhronizirane niti Mastodona za nove odgovore v majhnih krožnih serijah.', 'reply_quote_author_format' => '%s je napisal/-a:', 'save' => 'Shrani nastavitve', 'oauth_head' => 'Pomočnik OAuth', diff --git a/fp-plugins/mastodon/lang/lang.tr-tr.php b/fp-plugins/mastodon/lang/lang.tr-tr.php index 708cada0..27f7f492 100644 --- a/fp-plugins/mastodon/lang/lang.tr-tr.php +++ b/fp-plugins/mastodon/lang/lang.tr-tr.php @@ -10,6 +10,11 @@ 'password' => 'Mastodon parolası', 'sync_time' => 'Günlük senkronizasyon saati', 'sync_start_date' => 'Şu tarihten itibaren senkronize et', + 'sync_scheduled_window_days' => 'Zamanlanmış çalıştırmalar için otomatik zaman aralığı', + 'sync_scheduled_window_days_desc' => 'Zamanlanmış günlük çalıştırmalar yalnızca bu son zaman aralığını senkronize eder, ancak yukarıdaki tarih kalıcı alt sınır olarak kalır. Manuel çalıştırmalarda ise yine yalnızca yukarıdaki tarih kullanılır.', + 'sync_scheduled_window_7_days' => '7 gün', + 'sync_scheduled_window_14_days' => '14 gün', + 'sync_scheduled_window_30_days' => '30 gün', 'more_options' => 'Ek seçenekler', 'update_local_from_remote' => 'Mevcut yerel içeriği Mastodon’dan güncelle', 'update_local_from_remote_desc' => 'Etkinleştirildiğinde, Mastodon’daki değişiklikler mevcut yerel FlatPress girdilerinin ve yorumlarının üzerine yazabilir. FlatPress’teki gönderiler ve yorumlar 500 karaktere kısaltılır.', @@ -19,6 +24,8 @@ 'import_synced_comments_as_entries_desc' => 'Etkinleştirildiğinde, zaten yerel bir FlatPress yorumuyla eşlenmiş olan Mastodon durumları FlatPress girdisi olarak da içe aktarılabilir. Yinelenen içerikten kaçınmak için bu seçenek varsayılan olarak devre dışıdır.', 'quote_imported_reply_parent' => 'İçe aktarımda yanıt verilen Mastodon yorumunu alıntıla', 'quote_imported_reply_parent_desc' => 'Etkinleştirildiğinde, başka bir yoruma verilen içe aktarılmış Mastodon yanıtları, yanıt verilen kullanıcıyı ve yorum metnini gösteren bir alıntı bloğuyla başlar.', + 'old_thread_reply_check' => 'Eski konuları yanıtlar için kontrol et', + 'old_thread_reply_check_desc' => 'Etkinleştirildiğinde, eklenti senkronize edilmiş eski Mastodon konuları küçük dönen gruplar halinde yeni yanıtlar için kontrol eder.', 'reply_quote_author_format' => '%s yazdı:', 'save' => 'Ayarları kaydet', 'oauth_head' => 'OAuth yardımcısı', diff --git a/fp-plugins/mastodon/plugin.mastodon.php b/fp-plugins/mastodon/plugin.mastodon.php index 4a4a7d5f..d5b678b8 100644 --- a/fp-plugins/mastodon/plugin.mastodon.php +++ b/fp-plugins/mastodon/plugin.mastodon.php @@ -31,6 +31,9 @@ if (!defined('PLUGIN_MASTODON_GUARD_FILE')) { define('PLUGIN_MASTODON_GUARD_FILE', PLUGIN_MASTODON_STATE_DIR . 'sync.guard.json'); } +if (!defined('PLUGIN_MASTODON_RATE_LIMIT_WINDOW_FILE')) { + define('PLUGIN_MASTODON_RATE_LIMIT_WINDOW_FILE', PLUGIN_MASTODON_STATE_DIR . 'rate-limit-windows.json'); +} if (!defined('PLUGIN_MASTODON_LOG_FILE')) { define('PLUGIN_MASTODON_LOG_FILE', PLUGIN_MASTODON_STATE_DIR . 'sync.log'); } @@ -49,6 +52,9 @@ if (!defined('PLUGIN_MASTODON_PENDING_COMMENT_RECHECK_LIMIT')) { define('PLUGIN_MASTODON_PENDING_COMMENT_RECHECK_LIMIT', 3); } +if (!defined('PLUGIN_MASTODON_OLD_THREAD_CONTEXT_ROTATION_LIMIT')) { + define('PLUGIN_MASTODON_OLD_THREAD_CONTEXT_ROTATION_LIMIT', 3); +} // ttl for sync guards if (!defined('PLUGIN_MASTODON_STATE_FALLBACK_TTL')) { define('PLUGIN_MASTODON_STATE_FALLBACK_TTL', 300); @@ -66,6 +72,24 @@ if (!defined('PLUGIN_MASTODON_RUN_DELETE_BUDGET')) { define('PLUGIN_MASTODON_RUN_DELETE_BUDGET', 24); } +if (!defined('PLUGIN_MASTODON_WINDOW_MEDIA_UPLOAD_BUDGET')) { + define('PLUGIN_MASTODON_WINDOW_MEDIA_UPLOAD_BUDGET', PLUGIN_MASTODON_RUN_MEDIA_UPLOAD_BUDGET); +} +if (!defined('PLUGIN_MASTODON_WINDOW_MEDIA_UPLOAD_TTL')) { + define('PLUGIN_MASTODON_WINDOW_MEDIA_UPLOAD_TTL', 1800); +} +if (!defined('PLUGIN_MASTODON_WINDOW_DELETE_BUDGET')) { + define('PLUGIN_MASTODON_WINDOW_DELETE_BUDGET', PLUGIN_MASTODON_RUN_DELETE_BUDGET); +} +if (!defined('PLUGIN_MASTODON_WINDOW_DELETE_TTL')) { + define('PLUGIN_MASTODON_WINDOW_DELETE_TTL', 1800); +} +if (!defined('PLUGIN_MASTODON_WINDOW_STATUS_PAGE_BUDGET')) { + define('PLUGIN_MASTODON_WINDOW_STATUS_PAGE_BUDGET', 300); +} +if (!defined('PLUGIN_MASTODON_WINDOW_STATUS_PAGE_TTL')) { + define('PLUGIN_MASTODON_WINDOW_STATUS_PAGE_TTL', 900); +} if (!defined('PLUGIN_MASTODON_RATE_LIMIT_REMAINING_FLOOR')) { define('PLUGIN_MASTODON_RATE_LIMIT_REMAINING_FLOOR', 10); } @@ -81,9 +105,11 @@ function plugin_mastodon_default_options() { 'password' => '', 'sync_time' => PLUGIN_MASTODON_DEFAULT_SYNC_TIME, 'sync_start_date' => '', + 'sync_scheduled_window_days' => '14', 'update_local_from_remote' => '0', 'import_synced_comments_as_entries' => '0', 'quote_imported_reply_parent' => '1', + 'old_thread_reply_check' => '0', 'delete_sync_enabled' => '1', 'client_id' => '', 'client_secret' => '', @@ -492,14 +518,18 @@ function plugin_mastodon_default_state() { 'last_deletion_run' => '', 'deletions_pending' => 0, 'deletions_pending_scope' => 'full', + 'deletions_not_before' => '', 'last_error' => '', 'last_remote_status_id' => '', 'entries' => array(), 'entries_remote' => array(), 'comments' => array(), 'comments_remote' => array(), + 'dirty_entries' => array(), + 'dirty_comments' => array(), 'comment_tombstones' => array(), 'pending_comment_remote_rechecks' => array(), + 'old_thread_context_cursor' => '', 'content_stats' => plugin_mastodon_default_content_stats(), 'deletion_stats' => plugin_mastodon_default_deletion_stats() ); @@ -1244,12 +1274,198 @@ function plugin_mastodon_rate_limit_default_budgets() { return $budgets; } +/** + * Return the persistent Mastodon API window budgets across synchronization runs. + * @return array + */ +function plugin_mastodon_rate_limit_window_budgets() { + $budgets = array( + 'media_uploads' => (int) PLUGIN_MASTODON_WINDOW_MEDIA_UPLOAD_BUDGET, + 'media_uploads_ttl' => (int) PLUGIN_MASTODON_WINDOW_MEDIA_UPLOAD_TTL, + 'deletes' => (int) PLUGIN_MASTODON_WINDOW_DELETE_BUDGET, + 'deletes_ttl' => (int) PLUGIN_MASTODON_WINDOW_DELETE_TTL, + 'status_pages' => (int) PLUGIN_MASTODON_WINDOW_STATUS_PAGE_BUDGET, + 'status_pages_ttl' => (int) PLUGIN_MASTODON_WINDOW_STATUS_PAGE_TTL + ); + if (isset($GLOBALS ['plugin_mastodon_test_rate_limit_window_budgets']) && is_array($GLOBALS ['plugin_mastodon_test_rate_limit_window_budgets'])) { + foreach ($budgets as $key => $value) { + if (isset($GLOBALS ['plugin_mastodon_test_rate_limit_window_budgets'] [$key]) && is_numeric($GLOBALS ['plugin_mastodon_test_rate_limit_window_budgets'] [$key])) { + $budgets [$key] = (int) $GLOBALS ['plugin_mastodon_test_rate_limit_window_budgets'] [$key]; + } + } + } + foreach ($budgets as $key => $value) { + $budgets [$key] = max(0, (int) $value); + } + return $budgets; +} + +/** + * Return persistent window configuration for one request kind. + * @param string $kind + * @return array{key:string,budget:int,ttl:int,reason:string} + */ +function plugin_mastodon_rate_limit_window_config($kind) { + $budgets = plugin_mastodon_rate_limit_window_budgets(); + if ($kind === 'media_upload') { + return array('key' => 'media_uploads', 'budget' => $budgets ['media_uploads'], 'ttl' => $budgets ['media_uploads_ttl'], 'reason' => 'rate_limit_window_media_upload_budget_exhausted'); + } + if ($kind === 'status_delete') { + return array('key' => 'deletes', 'budget' => $budgets ['deletes'], 'ttl' => $budgets ['deletes_ttl'], 'reason' => 'rate_limit_window_delete_budget_exhausted'); + } + if ($kind === 'status_page') { + return array('key' => 'status_pages', 'budget' => $budgets ['status_pages'], 'ttl' => $budgets ['status_pages_ttl'], 'reason' => 'rate_limit_window_status_page_budget_exhausted'); + } + return array('key' => '', 'budget' => 0, 'ttl' => 0, 'reason' => ''); +} + +/** + * Normalize one persistent rate-limit window entry. + * @param mixed $entry + * @param int $now + * @return array + */ +function plugin_mastodon_rate_limit_window_entry($entry, $now) { + $entry = is_array($entry) ? $entry : array(); + $startedAt = isset($entry ['started_at']) && is_numeric($entry ['started_at']) ? (int) $entry ['started_at'] : 0; + $expiresAt = isset($entry ['expires_at']) && is_numeric($entry ['expires_at']) ? (int) $entry ['expires_at'] : 0; + $used = isset($entry ['used']) && is_numeric($entry ['used']) ? (int) $entry ['used'] : 0; + if ($startedAt <= 0 || $expiresAt <= $now || $used < 0) { + return array(); + } + return array( + 'started_at' => $startedAt, + 'expires_at' => $expiresAt, + 'used' => $used + ); +} + +/** + * Read the persistent rate-limit windows. + * @param int|null $now + * @return array> + */ +function plugin_mastodon_rate_limit_window_read($now = null) { + $now = $now === null ? time() : (int) $now; + $prestat = plugin_mastodon_file_prestat(PLUGIN_MASTODON_RATE_LIMIT_WINDOW_FILE); + if (empty($prestat ['exists'])) { + return array(); + } + $json = plugin_mastodon_io_read_file_uncached(PLUGIN_MASTODON_RATE_LIMIT_WINDOW_FILE, true); + if (!is_string($json) || trim($json) === '') { + return array(); + } + $data = json_decode($json, true); + if (!is_array($data)) { + return array(); + } + $source = isset($data ['windows']) && is_array($data ['windows']) ? $data ['windows'] : $data; + $windows = array(); + foreach (array('media_uploads', 'deletes', 'status_pages') as $key) { + if (isset($source [$key])) { + $entry = plugin_mastodon_rate_limit_window_entry($source [$key], $now); + if ($entry !== array()) { + $windows [$key] = $entry; + } + } + } + return $windows; +} + +/** + * Persist the current rate-limit windows. + * @param array> $windows + * @return bool + */ +function plugin_mastodon_rate_limit_window_write($windows) { + plugin_mastodon_ensure_state_dir(); + $payload = array('version' => 1, 'windows' => array()); + foreach (array('media_uploads', 'deletes', 'status_pages') as $key) { + if (isset($windows [$key]) && is_array($windows [$key])) { + $payload ['windows'] [$key] = array( + 'started_at' => isset($windows [$key] ['started_at']) ? (int) $windows [$key] ['started_at'] : 0, + 'expires_at' => isset($windows [$key] ['expires_at']) ? (int) $windows [$key] ['expires_at'] : 0, + 'used' => isset($windows [$key] ['used']) ? (int) $windows [$key] ['used'] : 0 + ); + } + } + $json = json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + if (!is_string($json)) { + return false; + } + return plugin_mastodon_io_write_file(PLUGIN_MASTODON_RATE_LIMIT_WINDOW_FILE, $json . PHP_EOL); +} + +/** + * Reserve one item from a persistent cross-run Mastodon rate-limit window. + * @param string $kind + * @param int|null $now + * @return string Empty string means the request may proceed. + */ +function plugin_mastodon_rate_limit_window_acquire($kind, $now = null) { + $config = plugin_mastodon_rate_limit_window_config($kind); + if ($config ['key'] === '' || $config ['budget'] <= 0 || $config ['ttl'] <= 0) { + return ''; + } + $now = $now === null ? time() : (int) $now; + $windows = plugin_mastodon_rate_limit_window_read($now); + $key = $config ['key']; + $entry = isset($windows [$key]) ? plugin_mastodon_rate_limit_window_entry($windows [$key], $now) : array(); + if ($entry === array()) { + $entry = array( + 'started_at' => $now, + 'expires_at' => $now + $config ['ttl'], + 'used' => 0 + ); + } + if ((int) $entry ['used'] >= (int) $config ['budget']) { + $GLOBALS ['plugin_mastodon_rate_limit_window_block'] = array( + 'key' => $key, + 'used' => (int) $entry ['used'], + 'budget' => (int) $config ['budget'], + 'expires_at' => (int) $entry ['expires_at'] + ); + return $config ['reason']; + } + $entry ['used'] = ((int) $entry ['used']) + 1; + $windows [$key] = $entry; + if (!plugin_mastodon_rate_limit_window_write($windows)) { + $GLOBALS ['plugin_mastodon_rate_limit_window_block'] = array( + 'key' => $key, + 'used' => (int) $entry ['used'], + 'budget' => (int) $config ['budget'], + 'expires_at' => (int) $entry ['expires_at'] + ); + return 'rate_limit_window_write_failed'; + } + $GLOBALS ['plugin_mastodon_rate_limit_window_last'] = array( + 'key' => $key, + 'used' => (int) $entry ['used'], + 'budget' => (int) $config ['budget'], + 'expires_at' => (int) $entry ['expires_at'] + ); + return ''; +} + +/** + * Clear persistent rate-limit windows, mainly for tests and recovery tooling. + * @return bool + */ +function plugin_mastodon_rate_limit_window_clear() { + if (@file_exists(PLUGIN_MASTODON_RATE_LIMIT_WINDOW_FILE)) { + return @unlink(PLUGIN_MASTODON_RATE_LIMIT_WINDOW_FILE); + } + return true; +} + /** * Start a per-run Mastodon API rate-limit guard. * @param string $scope * @return void */ function plugin_mastodon_rate_limit_guard_start($scope) { + unset($GLOBALS ['plugin_mastodon_rate_limit_window_block']); + unset($GLOBALS ['plugin_mastodon_rate_limit_window_last']); $budgets = plugin_mastodon_rate_limit_default_budgets(); $GLOBALS ['plugin_mastodon_rate_limit_guard'] = array( 'active' => true, @@ -1336,6 +1552,9 @@ function plugin_mastodon_rate_limit_request_kind($method, $target) { if (($method === 'DELETE' && preg_match('#/api/v1/statuses/[^/?]+#', $path)) || ($method === 'POST' && preg_match('#/api/v1/statuses/[^/?]+/unreblog#', $path))) { return 'status_delete'; } + if ($method === 'GET' && preg_match('#/api/v1/accounts/[^/]+/statuses/?$#', $path)) { + return 'status_page'; + } return 'request'; } @@ -1365,6 +1584,14 @@ function plugin_mastodon_rate_limit_block($reason) { if (!empty($summary ['remote_reset'])) { $message .= ', remote_reset=' . (string) $summary ['remote_reset']; } + if (isset($GLOBALS ['plugin_mastodon_rate_limit_window_block']) && is_array($GLOBALS ['plugin_mastodon_rate_limit_window_block']) && !empty($GLOBALS ['plugin_mastodon_rate_limit_window_block'] ['key'])) { + $window = $GLOBALS ['plugin_mastodon_rate_limit_window_block']; + $message .= ', window=' . (string) $window ['key'] + . ' ' . (isset($window ['used']) ? (int) $window ['used'] : 0) . '/' . (isset($window ['budget']) ? (int) $window ['budget'] : 0); + if (!empty($window ['expires_at'])) { + $message .= ' reset=' . gmdate('Y-m-d H:i:s', (int) $window ['expires_at']) . ' UTC'; + } + } $message .= ')'; } plugin_mastodon_log($message); @@ -1388,7 +1615,7 @@ function plugin_mastodon_rate_limit_acquire($method, $target) { $kind = plugin_mastodon_rate_limit_request_kind($method, $target); if (!empty($guard ['blocked_reason'])) { $blockedReason = (string) $guard ['blocked_reason']; - if (!(($blockedReason === 'rate_limit_media_upload_budget_exhausted' && $kind !== 'media_upload') || ($blockedReason === 'rate_limit_delete_budget_exhausted' && $kind !== 'status_delete'))) { + if (!(($blockedReason === 'rate_limit_media_upload_budget_exhausted' && $kind !== 'media_upload') || ($blockedReason === 'rate_limit_window_media_upload_budget_exhausted' && $kind !== 'media_upload') || ($blockedReason === 'rate_limit_delete_budget_exhausted' && $kind !== 'status_delete') || ($blockedReason === 'rate_limit_window_delete_budget_exhausted' && $kind !== 'status_delete') || ($blockedReason === 'rate_limit_window_status_page_budget_exhausted' && $kind !== 'status_page'))) { return $blockedReason; } } @@ -1406,6 +1633,10 @@ function plugin_mastodon_rate_limit_acquire($method, $target) { if ($kind === 'status_delete' && isset($guard ['deletes_budget']) && (int) $guard ['deletes_budget'] > 0 && (int) $guard ['deletes_used'] >= (int) $guard ['deletes_budget']) { return plugin_mastodon_rate_limit_block('rate_limit_delete_budget_exhausted'); } + $persistentReason = plugin_mastodon_rate_limit_window_acquire($kind); + if ($persistentReason !== '') { + return plugin_mastodon_rate_limit_block($persistentReason); + } $guard ['requests_used'] = isset($guard ['requests_used']) ? ((int) $guard ['requests_used']) + 1 : 1; if ($kind === 'media_upload') { $guard ['media_uploads_used'] = isset($guard ['media_uploads_used']) ? ((int) $guard ['media_uploads_used']) + 1 : 1; @@ -1525,9 +1756,11 @@ function plugin_mastodon_get_options() { $options ['instance_url'] = plugin_mastodon_normalize_instance_url($options ['instance_url']); $options ['sync_time'] = plugin_mastodon_normalize_sync_time($options ['sync_time']); $options ['sync_start_date'] = plugin_mastodon_normalize_sync_start_date($options ['sync_start_date']); + $options ['sync_scheduled_window_days'] = plugin_mastodon_normalize_scheduled_window_days($options ['sync_scheduled_window_days']); $options ['update_local_from_remote'] = plugin_mastodon_normalize_update_local_from_remote($options ['update_local_from_remote']); $options ['import_synced_comments_as_entries'] = plugin_mastodon_normalize_import_synced_comments_as_entries($options ['import_synced_comments_as_entries']); $options ['quote_imported_reply_parent'] = plugin_mastodon_normalize_quote_imported_reply_parent($options ['quote_imported_reply_parent']); + $options ['old_thread_reply_check'] = plugin_mastodon_normalize_old_thread_reply_check($options ['old_thread_reply_check']); $options ['delete_sync_enabled'] = plugin_mastodon_normalize_delete_sync_enabled($options ['delete_sync_enabled']); $options ['oauth_registered_scopes'] = trim((string) $options ['oauth_registered_scopes']); return $options; @@ -1555,9 +1788,11 @@ function plugin_mastodon_get_options() { $options ['instance_url'] = plugin_mastodon_normalize_instance_url($options ['instance_url']); $options ['sync_time'] = plugin_mastodon_normalize_sync_time($options ['sync_time']); $options ['sync_start_date'] = plugin_mastodon_normalize_sync_start_date($options ['sync_start_date']); + $options ['sync_scheduled_window_days'] = plugin_mastodon_normalize_scheduled_window_days($options ['sync_scheduled_window_days']); $options ['update_local_from_remote'] = plugin_mastodon_normalize_update_local_from_remote($options ['update_local_from_remote']); $options ['import_synced_comments_as_entries'] = plugin_mastodon_normalize_import_synced_comments_as_entries($options ['import_synced_comments_as_entries']); $options ['quote_imported_reply_parent'] = plugin_mastodon_normalize_quote_imported_reply_parent($options ['quote_imported_reply_parent']); + $options ['old_thread_reply_check'] = plugin_mastodon_normalize_old_thread_reply_check($options ['old_thread_reply_check']); $options ['delete_sync_enabled'] = plugin_mastodon_normalize_delete_sync_enabled($options ['delete_sync_enabled']); $options ['oauth_registered_scopes'] = trim((string) $options ['oauth_registered_scopes']); plugin_mastodon_runtime_cache_set('options', 'normalized', $options); @@ -1581,7 +1816,10 @@ function plugin_mastodon_save_options($options) { $merged = plugin_mastodon_clear_saved_instance_info($merged); } - foreach (array('instance_url', 'username', 'sync_time', 'sync_start_date', 'update_local_from_remote', 'import_synced_comments_as_entries', 'quote_imported_reply_parent', 'delete_sync_enabled', 'last_authorize_url', 'oauth_registered_scopes', 'instance_info_url', 'instance_info_json', 'instance_info_fetched_at', 'instance_info_error', 'instance_info_error_at') as $plainKey) { + $merged ['sync_scheduled_window_days'] = plugin_mastodon_normalize_scheduled_window_days(isset($merged ['sync_scheduled_window_days']) ? $merged ['sync_scheduled_window_days'] : ''); + $merged ['old_thread_reply_check'] = plugin_mastodon_normalize_old_thread_reply_check(isset($merged ['old_thread_reply_check']) ? $merged ['old_thread_reply_check'] : ''); + + foreach (array('instance_url', 'username', 'sync_time', 'sync_start_date', 'sync_scheduled_window_days', 'update_local_from_remote', 'import_synced_comments_as_entries', 'quote_imported_reply_parent', 'old_thread_reply_check', 'delete_sync_enabled', 'last_authorize_url', 'oauth_registered_scopes', 'instance_info_url', 'instance_info_json', 'instance_info_fetched_at', 'instance_info_error', 'instance_info_error_at') as $plainKey) { plugin_addoption('mastodon', $plainKey, (string) $merged [$plainKey]); } @@ -1642,6 +1880,10 @@ function plugin_mastodon_save_options($options) { $state ['deletions_pending_scope'] = 'full'; $stateChanged = true; } + if (!empty($state ['deletions_not_before'])) { + $state ['deletions_not_before'] = ''; + $stateChanged = true; + } if ($stateChanged) { plugin_mastodon_state_write($state); } @@ -1981,6 +2223,31 @@ function plugin_mastodon_normalize_sync_start_date($value) { return sprintf('%04d-%02d-%02d', $year, $month, $day); } +/** + * Normalize the automatic scheduled synchronization window. + * @param mixed $value + * @return string + */ +function plugin_mastodon_normalize_scheduled_window_days($value) { + $value = trim((string) $value); + if (in_array($value, array('7', '14', '30'), true)) { + return $value; + } + return '14'; +} + +/** + * Return the admin radio choices for the scheduled synchronization window. + * @return array> + */ +function plugin_mastodon_scheduled_window_choices() { + return array( + array('value' => '7', 'label' => plugin_mastodon_lang_string('sync_scheduled_window_7_days', '7 days')), + array('value' => '14', 'label' => plugin_mastodon_lang_string('sync_scheduled_window_14_days', '14 days')), + array('value' => '30', 'label' => plugin_mastodon_lang_string('sync_scheduled_window_30_days', '30 days')) + ); +} + /** * Normalize a boolean-like option value to the stored string representation. * @param mixed $value @@ -2051,6 +2318,24 @@ function plugin_mastodon_should_quote_imported_reply_parent($options) { return plugin_mastodon_normalize_quote_imported_reply_parent(isset($options ['quote_imported_reply_parent']) ? $options ['quote_imported_reply_parent'] : '') === '1'; } +/** + * Normalize the toggle that enables rotating context checks for old synchronized Mastodon threads. + * @param mixed $value + * @return string + */ +function plugin_mastodon_normalize_old_thread_reply_check($value) { + return plugin_mastodon_normalize_boolean_option($value); +} + +/** + * Check whether old synchronized Mastodon threads should be checked for replies in rotating batches. + * @param array $options + * @return bool + */ +function plugin_mastodon_should_check_old_thread_replies($options) { + return plugin_mastodon_normalize_old_thread_reply_check(isset($options ['old_thread_reply_check']) ? $options ['old_thread_reply_check'] : '') === '1'; +} + /** * Normalize the toggle that enables the follow-up deletion synchronization. * @param mixed $value @@ -2170,6 +2455,44 @@ function plugin_mastodon_date_matches_sync_start($options, $dateKey) { return strcmp($dateKey, $startDate) >= 0; } +/** + * Return the FlatPress-local date key that starts the automatic scheduled sync window. + * @param array $options + * @param int|null $timestamp + * @return string + */ +function plugin_mastodon_scheduled_window_start_date($options, $timestamp = null) { + $days = (int) plugin_mastodon_normalize_scheduled_window_days(isset($options ['sync_scheduled_window_days']) ? $options ['sync_scheduled_window_days'] : '14'); + $timestamp = $timestamp === null ? time() : (int) $timestamp; + if (isset($GLOBALS ['plugin_mastodon_test_now']) && is_numeric($GLOBALS ['plugin_mastodon_test_now'])) { + $timestamp = (int) $GLOBALS ['plugin_mastodon_test_now']; + } + $localTimestamp = $timestamp + plugin_mastodon_fp_timeoffset_seconds(); + return gmdate('Y-m-d', $localTimestamp - ($days * 86400)); +} + +/** + * Determine whether a date is inside the manual lower bound and, for scheduled runs, the automatic window. + * @param array $options + * @param string $dateKey + * @param bool $force + * @return bool + */ +function plugin_mastodon_date_matches_content_window($options, $dateKey, $force) { + if (!plugin_mastodon_date_matches_sync_start($options, $dateKey)) { + return false; + } + if ($force) { + return true; + } + $dateKey = plugin_mastodon_normalize_sync_start_date($dateKey); + if ($dateKey === '') { + return false; + } + $windowStart = plugin_mastodon_scheduled_window_start_date($options); + return $windowStart === '' || strcmp($dateKey, $windowStart) >= 0; +} + /** * Determine whether a local FlatPress item should be synchronized. * @param array $options @@ -2181,6 +2504,18 @@ function plugin_mastodon_local_item_matches_sync_start($options, $item, $fallbac return plugin_mastodon_date_matches_sync_start($options, plugin_mastodon_local_item_date_key($item, $fallbackId)); } +/** + * Determine whether a local FlatPress item is inside the active content synchronization window. + * @param array $options + * @param array $item + * @param string $fallbackId + * @param bool $force + * @return bool + */ +function plugin_mastodon_local_item_matches_content_window($options, $item, $fallbackId, $force) { + return plugin_mastodon_date_matches_content_window($options, plugin_mastodon_local_item_date_key($item, $fallbackId), $force); +} + /** * Determine whether a remote Mastodon status should be synchronized. * @param array $options @@ -2191,6 +2526,17 @@ function plugin_mastodon_remote_status_matches_sync_start($options, $remoteStatu return plugin_mastodon_date_matches_sync_start($options, plugin_mastodon_remote_status_date_key($remoteStatus)); } +/** + * Determine whether a remote Mastodon status is inside the active content synchronization window. + * @param array $options + * @param array $remoteStatus + * @param bool $force + * @return bool + */ +function plugin_mastodon_remote_status_matches_content_window($options, $remoteStatus, $force) { + return plugin_mastodon_date_matches_content_window($options, plugin_mastodon_remote_status_date_key($remoteStatus), $force); +} + /** * Determine whether a synchronized mapping should participate in the deletion follow-up for the current sync start date. * @param array $options @@ -2356,7 +2702,7 @@ function plugin_mastodon_state_normalize($state) { $hasContentStats = isset($input ['content_stats']) && is_array($input ['content_stats']); $hasDeletionStats = isset($input ['deletion_stats']) && is_array($input ['deletion_stats']); $state = array_merge($defaults, $input); - foreach (array('entries', 'entries_remote', 'comments', 'comments_remote', 'comment_tombstones', 'pending_comment_remote_rechecks') as $key) { + foreach (array('entries', 'entries_remote', 'comments', 'comments_remote', 'dirty_entries', 'dirty_comments', 'comment_tombstones', 'pending_comment_remote_rechecks') as $key) { if (!isset($state [$key]) || !is_array($state [$key])) { $state [$key] = $defaults [$key]; } @@ -2366,8 +2712,10 @@ function plugin_mastodon_state_normalize($state) { $state ['last_deletion_run'] = isset($state ['last_deletion_run']) ? (string) $state ['last_deletion_run'] : ''; $state ['deletions_pending'] = !empty($state ['deletions_pending']) ? 1 : 0; $state ['deletions_pending_scope'] = plugin_mastodon_normalize_deletions_pending_scope(isset($state ['deletions_pending_scope']) ? $state ['deletions_pending_scope'] : $defaults ['deletions_pending_scope']); + $state ['deletions_not_before'] = plugin_mastodon_parse_iso_datetime(isset($state ['deletions_not_before']) ? (string) $state ['deletions_not_before'] : ''); $state ['last_error'] = isset($state ['last_error']) ? (string) $state ['last_error'] : ''; $state ['last_remote_status_id'] = isset($state ['last_remote_status_id']) ? (string) $state ['last_remote_status_id'] : ''; + $state ['old_thread_context_cursor'] = isset($state ['old_thread_context_cursor']) ? (string) $state ['old_thread_context_cursor'] : ''; $legacyContentStats = array(); foreach (array_keys($defaults ['content_stats']) as $key) { if (isset($legacyStats [$key])) { @@ -2494,6 +2842,9 @@ function plugin_mastodon_state_remove_entry_mapping(&$state, $localId) { if ($remoteId !== '' && isset($state ['entries_remote'] [$remoteId]) && (string) $state ['entries_remote'] [$remoteId] === $localId) { unset($state ['entries_remote'] [$remoteId]); } + if (isset($state ['dirty_entries'] [$localId])) { + unset($state ['dirty_entries'] [$localId]); + } } /** @@ -2516,6 +2867,104 @@ function plugin_mastodon_state_remove_comment_mapping(&$state, $entryId, $commen unset($state ['comments_remote'] [$remoteId]); } } + if (isset($state ['dirty_comments'] [$key])) { + unset($state ['dirty_comments'] [$key]); + } +} + +/** + * Add an older changed entry to the persistent dirty queue. + * @param array $state + * @param string $entryId + * @param string $hash + * @return void + */ +function plugin_mastodon_state_set_dirty_entry(&$state, $entryId, $hash) { + $entryId = trim((string) $entryId); + if ($entryId === '') { + return; + } + if (!isset($state ['dirty_entries']) || !is_array($state ['dirty_entries'])) { + $state ['dirty_entries'] = array(); + } + $state ['dirty_entries'] [$entryId] = array( + 'hash' => (string) $hash, + 'queued_at' => gmdate('Y-m-d H:i:s') + ); +} + +/** + * Remove an entry from the dirty queue. + * @param array $state + * @param string $entryId + * @return void + */ +function plugin_mastodon_state_remove_dirty_entry(&$state, $entryId) { + $entryId = trim((string) $entryId); + if ($entryId !== '' && isset($state ['dirty_entries'] [$entryId])) { + unset($state ['dirty_entries'] [$entryId]); + } +} + +/** + * Check whether an entry is queued for synchronization although it is outside the scheduled window. + * @param array $state + * @param string $entryId + * @return bool + */ +function plugin_mastodon_state_has_dirty_entry($state, $entryId) { + $entryId = trim((string) $entryId); + return $entryId !== '' && isset($state ['dirty_entries']) && is_array($state ['dirty_entries']) && isset($state ['dirty_entries'] [$entryId]); +} + +/** + * Add an older changed comment to the persistent dirty queue. + * @param array $state + * @param string $entryId + * @param string $commentId + * @param string $hash + * @return void + */ +function plugin_mastodon_state_set_dirty_comment(&$state, $entryId, $commentId, $hash) { + $key = plugin_mastodon_state_comment_key($entryId, $commentId); + if ($key === ':') { + return; + } + if (!isset($state ['dirty_comments']) || !is_array($state ['dirty_comments'])) { + $state ['dirty_comments'] = array(); + } + $state ['dirty_comments'] [$key] = array( + 'entry_id' => (string) $entryId, + 'comment_id' => (string) $commentId, + 'hash' => (string) $hash, + 'queued_at' => gmdate('Y-m-d H:i:s') + ); +} + +/** + * Remove a comment from the dirty queue. + * @param array $state + * @param string $entryId + * @param string $commentId + * @return void + */ +function plugin_mastodon_state_remove_dirty_comment(&$state, $entryId, $commentId) { + $key = plugin_mastodon_state_comment_key($entryId, $commentId); + if (isset($state ['dirty_comments'] [$key])) { + unset($state ['dirty_comments'] [$key]); + } +} + +/** + * Check whether a comment is queued for synchronization although it is outside the scheduled window. + * @param array $state + * @param string $entryId + * @param string $commentId + * @return bool + */ +function plugin_mastodon_state_has_dirty_comment($state, $entryId, $commentId) { + $key = plugin_mastodon_state_comment_key($entryId, $commentId); + return isset($state ['dirty_comments']) && is_array($state ['dirty_comments']) && isset($state ['dirty_comments'] [$key]); } /** @@ -2822,13 +3271,44 @@ function plugin_mastodon_state_set_pending_comment_remote_recheck(&$state, $entr * @param array $state * @param bool $pending * @param string $scope + * @param string $notBefore * @return void */ -function plugin_mastodon_state_set_deletions_pending(&$state, $pending, $scope) { +function plugin_mastodon_state_set_deletions_pending(&$state, $pending, $scope, $notBefore = '') { $pending = (bool) $pending; $scope = plugin_mastodon_normalize_deletions_pending_scope($scope); $state ['deletions_pending'] = $pending ? 1 : 0; $state ['deletions_pending_scope'] = $pending ? $scope : 'full'; + $state ['deletions_not_before'] = $pending ? plugin_mastodon_parse_iso_datetime($notBefore) : ''; +} + +/** + * Determine whether a pending deletion synchronization may start now. + * @param array $state + * @param int|null $timestamp + * @return bool + */ +function plugin_mastodon_deletion_sync_due($state, $timestamp = null) { + $state = is_array($state) ? $state : array(); + if (empty($state ['deletions_pending'])) { + return false; + } + $timestamp = $timestamp === null ? time() : (int) $timestamp; + $notBefore = plugin_mastodon_parse_iso_datetime(isset($state ['deletions_not_before']) ? (string) $state ['deletions_not_before'] : ''); + if ($notBefore === '' && !empty($state ['last_run'])) { + $lastRun = strtotime((string) $state ['last_run']); + if ($lastRun !== false) { + $notBefore = date('Y-m-d H:i:s', $lastRun + PLUGIN_MASTODON_COOLDOWN_TTL); + } + } + if ($notBefore === '') { + return true; + } + $notBeforeTimestamp = strtotime($notBefore); + if ($notBeforeTimestamp === false) { + return true; + } + return $timestamp >= $notBeforeTimestamp; } /** @@ -7881,14 +8361,35 @@ function plugin_mastodon_import_remote_context_descendants(&$options, &$state, $ } } +/** + * Return the maximum number of older known threads checked for replies per content sync run. + * @return int + */ +function plugin_mastodon_old_thread_context_rotation_limit() { + $limit = (int) PLUGIN_MASTODON_OLD_THREAD_CONTEXT_ROTATION_LIMIT; + if (isset($GLOBALS ['plugin_mastodon_test_old_thread_context_rotation_limit']) && is_numeric($GLOBALS ['plugin_mastodon_test_old_thread_context_rotation_limit'])) { + $limit = (int) $GLOBALS ['plugin_mastodon_test_old_thread_context_rotation_limit']; + } + return max(0, $limit); +} + /** * Collect known synchronized entry threads that should have their Mastodon reply context refreshed. * @param array $state * @param array $skipRemoteIds + * @param array $options + * @param bool $force * @return array */ -function plugin_mastodon_collect_known_entry_context_targets($state, $skipRemoteIds = array()) { +function plugin_mastodon_collect_known_entry_context_targets(&$state, $skipRemoteIds = array(), $options = array(), $force = false) { $targets = array(); + if (!plugin_mastodon_should_check_old_thread_replies($options)) { + return $targets; + } + $limit = plugin_mastodon_old_thread_context_rotation_limit(); + if ($limit <= 0) { + return $targets; + } $skipLookup = array(); if (is_array($skipRemoteIds)) { foreach ($skipRemoteIds as $skipRemoteId) { @@ -7901,6 +8402,7 @@ function plugin_mastodon_collect_known_entry_context_targets($state, $skipRemote if (empty($state ['entries']) || !is_array($state ['entries'])) { return $targets; } + $candidates = array(); foreach ($state ['entries'] as $localEntryId => $meta) { $localEntryId = (string) $localEntryId; if ($localEntryId === '' || !is_array($meta) || empty($meta ['remote_id'])) { @@ -7910,7 +8412,37 @@ function plugin_mastodon_collect_known_entry_context_targets($state, $skipRemote if ($remoteId === '' || isset($skipLookup [$remoteId]) || !entry_exists($localEntryId)) { continue; } - $targets [$remoteId] = $localEntryId; + if (!plugin_mastodon_mapping_matches_sync_start($options, $meta, $localEntryId)) { + plugin_mastodon_log('Skipping context refresh for synchronized entry ' . $localEntryId . ' because its mapping is older than the configured sync start date'); + continue; + } + $candidates [$remoteId] = $localEntryId; + } + if (empty($candidates)) { + return $targets; + } + ksort($candidates, SORT_STRING); + $remoteIds = array_keys($candidates); + $cursor = isset($state ['old_thread_context_cursor']) ? (string) $state ['old_thread_context_cursor'] : ''; + $start = 0; + if ($cursor !== '') { + foreach ($remoteIds as $index => $remoteId) { + if (strcmp((string) $remoteId, $cursor) > 0) { + $start = (int) $index; + break; + } + if ($index === count($remoteIds) - 1) { + $start = 0; + } + } + } + $count = count($remoteIds); + $checked = 0; + for ($offset = 0; $offset < $count && $checked < $limit; $offset++) { + $remoteId = (string) $remoteIds [($start + $offset) % $count]; + $targets [$remoteId] = $candidates [$remoteId]; + $state ['old_thread_context_cursor'] = $remoteId; + $checked++; } return $targets; } @@ -7919,10 +8451,12 @@ function plugin_mastodon_collect_known_entry_context_targets($state, $skipRemote * Synchronize remote Mastodon content into FlatPress. * @param array $options * @param array $state + * @param bool $force * @return bool */ -function plugin_mastodon_sync_remote_to_local(&$options, &$state) { +function plugin_mastodon_sync_remote_to_local(&$options, &$state, $force = true) { plugin_mastodon_extend_time_limit(180); + $force = (bool) $force; $verify = plugin_mastodon_verify_credentials($options); if (!$verify ['ok'] || empty($verify ['json'] ['id'])) { $rateLimitError = plugin_mastodon_rate_limit_state_error(); @@ -7949,8 +8483,8 @@ function plugin_mastodon_sync_remote_to_local(&$options, &$state) { plugin_mastodon_log('Skipping non-public remote status ' . $statusId . ' with visibility ' . plugin_mastodon_remote_status_visibility($status)); continue; } - if (!plugin_mastodon_remote_status_matches_sync_start($options, $status)) { - plugin_mastodon_log('Skipping remote status ' . $statusId . ' because it is older than the configured sync start date'); + if (!plugin_mastodon_remote_status_matches_content_window($options, $status, $force)) { + plugin_mastodon_log('Skipping remote status ' . $statusId . ' because it is outside the active synchronization date window'); continue; } $entryId = plugin_mastodon_import_remote_entry($options, $state, $status); @@ -7962,7 +8496,7 @@ function plugin_mastodon_sync_remote_to_local(&$options, &$state) { $refreshedContextIds [$statusId] = true; } - $contextTargets = plugin_mastodon_collect_known_entry_context_targets($state, array_keys($refreshedContextIds)); + $contextTargets = plugin_mastodon_collect_known_entry_context_targets($state, array_keys($refreshedContextIds), $options, $force); foreach ($contextTargets as $remoteEntryId => $localEntryId) { plugin_mastodon_extend_time_limit(120); $context = plugin_mastodon_fetch_status_context($options, $remoteEntryId); @@ -7984,10 +8518,12 @@ function plugin_mastodon_sync_remote_to_local(&$options, &$state) { * Synchronize local FlatPress content to Mastodon. * @param array $options * @param array $state + * @param bool $force * @return bool */ -function plugin_mastodon_sync_local_to_remote(&$options, &$state) { +function plugin_mastodon_sync_local_to_remote(&$options, &$state, $force = true) { plugin_mastodon_extend_time_limit(180); + $force = (bool) $force; $charLimit = plugin_mastodon_instance_character_limit($options); $mediaLimit = plugin_mastodon_instance_media_limit($options); $entries = plugin_mastodon_list_local_entries(); @@ -7998,12 +8534,21 @@ function plugin_mastodon_sync_local_to_remote(&$options, &$state) { $entrySource = !empty($meta ['source']) ? strtolower((string) $meta ['source']) : 'local'; $skipEntryStatusSync = ($entrySource === 'remote'); $entryMatchesSyncStart = plugin_mastodon_local_item_matches_sync_start($options, $entry, $entryId); + $entryMatchesContentWindow = plugin_mastodon_local_item_matches_content_window($options, $entry, $entryId, $force); if ($skipEntryStatusSync) { plugin_mastodon_log('Skipping local entry status export for remote-sourced entry ' . $entryId . ' while still evaluating local FlatPress comments for reply export'); - } elseif ($entryMatchesSyncStart) { + } elseif ($entryMatchesSyncStart && (!$entryMatchesContentWindow && !empty($meta ['remote_id']) && (!empty($meta ['hash']) && $meta ['hash'] !== plugin_mastodon_entry_hash($entry)))) { + plugin_mastodon_state_set_dirty_entry($state, $entryId, plugin_mastodon_entry_hash($entry)); + plugin_mastodon_log('Queued older changed local entry ' . $entryId . ' for synchronization outside the scheduled window'); + } + $entryQueuedDirty = plugin_mastodon_state_has_dirty_entry($state, $entryId); + if ($skipEntryStatusSync) { + // comments are still handled below + } elseif ($entryMatchesSyncStart && ($entryMatchesContentWindow || $entryQueuedDirty)) { $hash = plugin_mastodon_entry_hash($entry); $text = plugin_mastodon_build_entry_status_text($entryId, $entry, $charLimit); if ($text === '') { + plugin_mastodon_state_remove_dirty_entry($state, $entryId); continue; } @@ -8035,7 +8580,7 @@ function plugin_mastodon_sync_local_to_remote(&$options, &$state) { if (!empty($meta ['remote_id'])) { if (!empty($meta ['hash']) && $meta ['hash'] === $hash) { - // no change + plugin_mastodon_state_remove_dirty_entry($state, $entryId); } else { $updated = plugin_mastodon_update_status($options, $meta ['remote_id'], $text, $mediaIds, $mediaAttributes); if ($updated ['ok'] && !empty($updated ['json'] ['id'])) { @@ -8045,6 +8590,7 @@ function plugin_mastodon_sync_local_to_remote(&$options, &$state) { $resolvedRemoteMedia = $updatedRemoteMedia; } plugin_mastodon_state_set_entry_media_meta($state, $entryId, $resolvedRemoteMedia, $mediaAttachmentSignature, $mediaDescriptionSignature); + plugin_mastodon_state_remove_dirty_entry($state, $entryId); $state ['content_stats'] ['updated_remote_entries']++; } else { if ($uploadedNewMedia && !empty($mediaIds)) { @@ -8065,6 +8611,7 @@ function plugin_mastodon_sync_local_to_remote(&$options, &$state) { $resolvedRemoteMedia = $createdRemoteMedia; } plugin_mastodon_state_set_entry_media_meta($state, $entryId, $resolvedRemoteMedia, $mediaAttachmentSignature, $mediaDescriptionSignature); + plugin_mastodon_state_remove_dirty_entry($state, $entryId); $state ['content_stats'] ['exported_entries']++; $meta = plugin_mastodon_state_get_entry_meta($state, $entryId); } else { @@ -8078,7 +8625,7 @@ function plugin_mastodon_sync_local_to_remote(&$options, &$state) { } } } else { - plugin_mastodon_log('Skipping local entry ' . $entryId . ' because it is older than the configured sync start date'); + plugin_mastodon_log('Skipping local entry ' . $entryId . ' because it is outside the active synchronization date window'); } $entryMeta = plugin_mastodon_state_get_entry_meta($state, $entryId); @@ -8105,6 +8652,19 @@ function plugin_mastodon_sync_local_to_remote(&$options, &$state) { plugin_mastodon_log('Skipping local comment ' . $entryId . '/' . $commentId . ' because it is older than the configured sync start date'); continue; } + $commentHash = plugin_mastodon_comment_hash($comment); + $commentQueuedDirty = plugin_mastodon_state_has_dirty_comment($state, $entryId, $commentId); + if (!plugin_mastodon_local_item_matches_content_window($options, $comment, $commentId, $force)) { + if (!empty($commentMeta ['remote_id']) && !empty($commentMeta ['hash']) && $commentMeta ['hash'] !== $commentHash) { + plugin_mastodon_state_set_dirty_comment($state, $entryId, $commentId, $commentHash); + $commentQueuedDirty = true; + plugin_mastodon_log('Queued older changed local comment ' . $entryId . '/' . $commentId . ' for synchronization outside the scheduled window'); + } + if (!$commentQueuedDirty) { + plugin_mastodon_log('Skipping local comment ' . $entryId . '/' . $commentId . ' because it is outside the active synchronization date window'); + continue; + } + } $pendingComments [] = array( 'comment_id' => (string) $commentId, 'comment' => $comment @@ -8140,11 +8700,13 @@ function plugin_mastodon_sync_local_to_remote(&$options, &$state) { $parentCommentId = !empty($replyTarget ['parent_comment_id']) ? (string) $replyTarget ['parent_comment_id'] : ''; if (!empty($commentMeta ['remote_id'])) { if (!empty($commentMeta ['hash']) && $commentMeta ['hash'] === $commentHash && ((isset($commentMeta ['parent_comment_id']) ? (string) $commentMeta ['parent_comment_id'] : '') === $parentCommentId) && ((isset($commentMeta ['in_reply_to_remote_id']) ? (string) $commentMeta ['in_reply_to_remote_id'] : '') === $replyToRemoteId)) { + plugin_mastodon_state_remove_dirty_comment($state, $entryId, $commentId); continue; } $updated = plugin_mastodon_update_status($options, $commentMeta ['remote_id'], $text, array()); if ($updated ['ok'] && !empty($updated ['json'] ['id'])) { plugin_mastodon_state_set_comment_mapping($state, $entryId, $commentId, $commentMeta ['remote_id'], 'local', $commentHash, isset($updated ['json'] ['url']) ? $updated ['json'] ['url'] : '', plugin_mastodon_parse_iso_datetime(isset($updated ['json'] ['edited_at']) ? $updated ['json'] ['edited_at'] : ''), $parentCommentId, $replyToRemoteId, plugin_mastodon_local_item_date_key($comment, $commentId), plugin_mastodon_remote_status_date_key(isset($updated ['json']) && is_array($updated ['json']) ? $updated ['json'] : array())); + plugin_mastodon_state_remove_dirty_comment($state, $entryId, $commentId); $state ['content_stats'] ['updated_remote_comments']++; } else { $hadFailure = true; @@ -8155,6 +8717,7 @@ function plugin_mastodon_sync_local_to_remote(&$options, &$state) { $created = plugin_mastodon_create_status($options, $text, $replyToRemoteId, array()); if ($created ['ok'] && !empty($created ['json'] ['id'])) { plugin_mastodon_state_set_comment_mapping($state, $entryId, $commentId, $created ['json'] ['id'], 'local', $commentHash, isset($created ['json'] ['url']) ? $created ['json'] ['url'] : '', plugin_mastodon_parse_iso_datetime(isset($created ['json'] ['created_at']) ? $created ['json'] ['created_at'] : ''), $parentCommentId, $replyToRemoteId, plugin_mastodon_local_item_date_key($comment, $commentId), plugin_mastodon_remote_status_date_key(isset($created ['json']) && is_array($created ['json']) ? $created ['json'] : array())); + plugin_mastodon_state_remove_dirty_comment($state, $entryId, $commentId); $state ['content_stats'] ['exported_comments']++; } else { $hadFailure = true; @@ -8214,6 +8777,9 @@ function plugin_mastodon_run_deletion_sync($force) { if (!$force && empty($state ['deletions_pending'])) { return array('ok' => true, 'state' => $state, 'message' => 'no_deletions_pending'); } + if (!plugin_mastodon_deletion_sync_due($state, time())) { + return array('ok' => true, 'state' => $state, 'message' => 'deletion_sync_wait'); + } if (!$force && plugin_mastodon_sync_guard_active('deletion')) { return array('ok' => true, 'state' => $state, 'message' => 'deletion_sync_cooldown'); } @@ -8368,8 +8934,9 @@ function plugin_mastodon_run_deletion_sync($force) { } if (!$hadFailure) { - $state ['last_deletion_run'] = date('Y-m-d H:i:s'); - plugin_mastodon_state_set_deletions_pending($state, $pendingCommentRechecksRemaining, $pendingCommentRechecksRemaining ? 'comment_rechecks' : 'full'); + $deletionCompletionTimestamp = time(); + $state ['last_deletion_run'] = date('Y-m-d H:i:s', $deletionCompletionTimestamp); + plugin_mastodon_state_set_deletions_pending($state, $pendingCommentRechecksRemaining, $pendingCommentRechecksRemaining ? 'comment_rechecks' : 'full', date('Y-m-d H:i:s', $deletionCompletionTimestamp + PLUGIN_MASTODON_COOLDOWN_TTL)); $state ['last_error'] = ''; if ($pendingCommentRechecksRemaining) { plugin_mastodon_log('Deletion synchronization completed with pending descendant reply rechecks'); @@ -8377,10 +8944,11 @@ function plugin_mastodon_run_deletion_sync($force) { plugin_mastodon_log('Deletion synchronization completed successfully'); } } else { + $deletionRetryNotBefore = date('Y-m-d H:i:s', time() + PLUGIN_MASTODON_COOLDOWN_TTL); if ($pendingCommentRechecksRemaining) { - plugin_mastodon_state_set_deletions_pending($state, true, 'comment_rechecks'); + plugin_mastodon_state_set_deletions_pending($state, true, 'comment_rechecks', $deletionRetryNotBefore); } else { - plugin_mastodon_state_set_deletions_pending($state, true, $commentRecheckOnly ? 'comment_rechecks' : 'full'); + plugin_mastodon_state_set_deletions_pending($state, true, $commentRecheckOnly ? 'comment_rechecks' : 'full', $deletionRetryNotBefore); } if ($state ['last_error'] === '') { $state ['last_error'] = 'deletion_sync_failed'; @@ -8477,15 +9045,16 @@ function plugin_mastodon_run_sync($force) { plugin_mastodon_log('Protected ' . $protectedDeletedExportedComments . ' locally deleted exported FlatPress comment mapping(s) from stale Mastodon context re-imports before content synchronization'); } - $okRemote = plugin_mastodon_sync_remote_to_local($options, $state); + $okRemote = plugin_mastodon_sync_remote_to_local($options, $state, $force); $okLocal = false; if ($okRemote) { - $okLocal = plugin_mastodon_sync_local_to_remote($options, $state); + $okLocal = plugin_mastodon_sync_local_to_remote($options, $state, $force); } if ($okRemote && $okLocal) { - $state ['last_run'] = date('Y-m-d H:i:s'); - plugin_mastodon_state_set_deletions_pending($state, plugin_mastodon_should_run_deletion_sync($options), 'full'); + $completionTimestamp = time(); + $state ['last_run'] = date('Y-m-d H:i:s', $completionTimestamp); + plugin_mastodon_state_set_deletions_pending($state, plugin_mastodon_should_run_deletion_sync($options), 'full', date('Y-m-d H:i:s', $completionTimestamp + PLUGIN_MASTODON_COOLDOWN_TTL)); $state ['last_error'] = ''; plugin_mastodon_log('Synchronization completed successfully'); } elseif ($state ['last_error'] === '') { @@ -8527,7 +9096,7 @@ function plugin_mastodon_maybe_sync() { plugin_mastodon_run_sync(false); return; } - if (!empty($state ['deletions_pending']) && plugin_mastodon_should_run_deletion_sync($options)) { + if (!empty($state ['deletions_pending']) && plugin_mastodon_should_run_deletion_sync($options) && plugin_mastodon_deletion_sync_due($state, time())) { plugin_mastodon_run_deletion_sync(false); } } @@ -8688,9 +9257,11 @@ function plugin_mastodon_admin_assign(&$smarty) { 'sync_time_utc' => $options ['sync_time'], 'sync_time_offset_label' => plugin_mastodon_fp_timeoffset_label(), 'sync_start_date' => $options ['sync_start_date'], + 'sync_scheduled_window_days' => $options ['sync_scheduled_window_days'], 'update_local_from_remote' => $options ['update_local_from_remote'], 'import_synced_comments_as_entries' => $options ['import_synced_comments_as_entries'], 'quote_imported_reply_parent' => $options ['quote_imported_reply_parent'], + 'old_thread_reply_check' => $options ['old_thread_reply_check'], 'delete_sync_enabled' => $options ['delete_sync_enabled'], 'client_id' => $options ['client_id'], 'client_secret' => $options ['client_secret'] !== '' ? '••••••••' : '', @@ -8702,6 +9273,7 @@ function plugin_mastodon_admin_assign(&$smarty) { $adminState ['last_deletion_run_local'] = plugin_mastodon_format_admin_datetime(isset($state ['last_deletion_run']) ? $state ['last_deletion_run'] : ''); $smarty->assign('mastodon_state', $adminState); + $smarty->assign('mastodon_scheduled_window_choices', plugin_mastodon_scheduled_window_choices()); $smarty->assign('mastodon_authorize_url', $authorizeUrl); $smarty->assign('mastodon_temp_dir', PLUGIN_MASTODON_STATE_DIR); $smarty->assign('mastodon_instance_info_rows', plugin_mastodon_admin_instance_info_rows($options)); @@ -8735,9 +9307,11 @@ function onsubmit($data = null) { $options ['password'] = trim(isset($_POST ['password']) ? (string) $_POST ['password'] : ''); $options ['sync_time'] = plugin_mastodon_sync_time_local_to_utc(isset($_POST ['sync_time']) ? (string) $_POST ['sync_time'] : ''); $options ['sync_start_date'] = plugin_mastodon_normalize_sync_start_date(isset($_POST ['sync_start_date']) ? $_POST ['sync_start_date'] : ''); + $options ['sync_scheduled_window_days'] = plugin_mastodon_normalize_scheduled_window_days(isset($_POST ['sync_scheduled_window_days']) ? $_POST ['sync_scheduled_window_days'] : ''); $options ['update_local_from_remote'] = plugin_mastodon_normalize_update_local_from_remote(isset($_POST ['update_local_from_remote']) ? $_POST ['update_local_from_remote'] : ''); $options ['import_synced_comments_as_entries'] = plugin_mastodon_normalize_import_synced_comments_as_entries(isset($_POST ['import_synced_comments_as_entries']) ? $_POST ['import_synced_comments_as_entries'] : ''); $options ['quote_imported_reply_parent'] = plugin_mastodon_normalize_quote_imported_reply_parent(isset($_POST ['quote_imported_reply_parent']) ? $_POST ['quote_imported_reply_parent'] : ''); + $options ['old_thread_reply_check'] = plugin_mastodon_normalize_old_thread_reply_check(isset($_POST ['old_thread_reply_check']) ? $_POST ['old_thread_reply_check'] : ''); $options ['delete_sync_enabled'] = plugin_mastodon_normalize_delete_sync_enabled(isset($_POST ['delete_sync_enabled']) ? $_POST ['delete_sync_enabled'] : ''); $options ['authorization_code'] = trim(isset($_POST ['authorization_code']) ? (string) $_POST ['authorization_code'] : ''); plugin_mastodon_save_options($options); diff --git a/fp-plugins/mastodon/tpls/admin.plugin.mastodon.tpl b/fp-plugins/mastodon/tpls/admin.plugin.mastodon.tpl index 6f3c05c8..847d3ddc 100644 --- a/fp-plugins/mastodon/tpls/admin.plugin.mastodon.tpl +++ b/fp-plugins/mastodon/tpls/admin.plugin.mastodon.tpl @@ -33,6 +33,17 @@
+ +
{$plang.sync_scheduled_window_days|escape}
+
+ {foreach from=$mastodon_scheduled_window_choices item=window_choice} + + {/foreach} +
{$plang.sync_scheduled_window_days_desc|escape} +
@@ -66,6 +77,14 @@
+
+
+ +
+