Sync your WordPress posts to standard.site records on your PDS
7
fork

Configure Feed

Select the types of activity you want to include in your feed.

backfill posts

+298 -2
+2 -2
README.md
··· 33 33 - **Name** — from WordPress site title, Yoast SEO, or a custom value 34 34 - **Description** — from WordPress tagline, Yoast SEO, or a custom value 35 35 - **Icon** — from WordPress site icon or a custom upload 36 - - **Theme colors** — background, foreground, accent, and accent foreground 37 - - **Discoverability** — opt in or out of discovery feeds 36 + - **Theme colors** — background, foreground, accent, and accent foreground. NOTE: these are used by other ATProto platforms to style your content, not on your WordPress site. 37 + - **Discoverability** — opt in or out of discovery feeds. NOTE: these are used by other ATProto platforms to show your publication in algorithmic feeds, not on your WordPress site. 38 38 39 39 The plugin also serves a `.well-known/site.standard.publication` endpoint that returns the AT-URI of your publication record. 40 40
+144
assets/js/settings.js
··· 99 99 }); 100 100 } 101 101 102 + function initBackfill() { 103 + var $button = $("#wireservice-backfill-start"); 104 + var $progress = $("#wireservice-backfill-progress"); 105 + var $fill = $progress.find(".wireservice-progress-bar-fill"); 106 + var $status = $("#wireservice-backfill-status"); 107 + var $results = $("#wireservice-backfill-results"); 108 + var batchSize = 5; 109 + 110 + if (!$button.length) { 111 + return; 112 + } 113 + 114 + $button.on("click", function () { 115 + $button.prop("disabled", true); 116 + $progress.show(); 117 + $results.hide().empty(); 118 + $fill.css("width", "0%"); 119 + $status.text("Counting posts..."); 120 + 121 + $.post(wireserviceBackfill.ajaxUrl, { 122 + action: "wireservice_backfill_count", 123 + nonce: wireserviceBackfill.nonce, 124 + }) 125 + .done(function (response) { 126 + if (!response.success) { 127 + $status.text("Failed to count posts."); 128 + $button.prop("disabled", false); 129 + return; 130 + } 131 + 132 + var postIds = response.data.post_ids; 133 + var total = postIds.length; 134 + 135 + if (total === 0) { 136 + $status.text("All posts are already synced."); 137 + $progress.hide(); 138 + $button.prop("disabled", false); 139 + return; 140 + } 141 + 142 + var processed = 0; 143 + var succeeded = 0; 144 + var errors = []; 145 + 146 + // Chunk post IDs into batches. 147 + var batches = []; 148 + for (var i = 0; i < postIds.length; i += batchSize) { 149 + batches.push(postIds.slice(i, i + batchSize)); 150 + } 151 + 152 + function processBatch(index) { 153 + if (index >= batches.length) { 154 + // Done. 155 + var pct = "100%"; 156 + $fill.css("width", pct); 157 + 158 + var summary = succeeded + " of " + total + " posts synced."; 159 + $status.text(summary); 160 + 161 + if (errors.length > 0) { 162 + var html = 163 + '<details class="wireservice-backfill-errors"><summary>' + 164 + errors.length + 165 + " failed</summary><ul>"; 166 + for (var e = 0; e < errors.length; e++) { 167 + html += 168 + "<li><strong>" + 169 + $("<span>").text(errors[e].title).html() + 170 + "</strong>: " + 171 + $("<span>").text(errors[e].error).html() + 172 + "</li>"; 173 + } 174 + html += "</ul></details>"; 175 + $results.html(html).show(); 176 + } 177 + 178 + $button.prop("disabled", false); 179 + return; 180 + } 181 + 182 + $status.text( 183 + "Syncing posts... (" + processed + " of " + total + ")" 184 + ); 185 + 186 + $.post(wireserviceBackfill.ajaxUrl, { 187 + action: "wireservice_backfill_batch", 188 + nonce: wireserviceBackfill.nonce, 189 + post_ids: batches[index], 190 + }) 191 + .done(function (response) { 192 + if (response.success && response.data.results) { 193 + for (var r = 0; r < response.data.results.length; r++) { 194 + var result = response.data.results[r]; 195 + processed++; 196 + if (result.success) { 197 + succeeded++; 198 + } else { 199 + errors.push(result); 200 + } 201 + } 202 + } else { 203 + // Count the entire batch as failed. 204 + processed += batches[index].length; 205 + for (var f = 0; f < batches[index].length; f++) { 206 + errors.push({ 207 + title: "Post #" + batches[index][f], 208 + error: "Batch request failed.", 209 + }); 210 + } 211 + } 212 + 213 + var pct = Math.round((processed / total) * 100) + "%"; 214 + $fill.css("width", pct); 215 + 216 + processBatch(index + 1); 217 + }) 218 + .fail(function () { 219 + processed += batches[index].length; 220 + for (var f = 0; f < batches[index].length; f++) { 221 + errors.push({ 222 + title: "Post #" + batches[index][f], 223 + error: "Request failed.", 224 + }); 225 + } 226 + 227 + var pct = Math.round((processed / total) * 100) + "%"; 228 + $fill.css("width", pct); 229 + 230 + processBatch(index + 1); 231 + }); 232 + } 233 + 234 + processBatch(0); 235 + }) 236 + .fail(function () { 237 + $status.text("Failed to start backfill."); 238 + $button.prop("disabled", false); 239 + }); 240 + }); 241 + } 242 + 102 243 $(document).ready(function () { 103 244 toggleCustomField( 104 245 "wireservice_pub_name_source", ··· 116 257 117 258 // Initialize color pickers. 118 259 $(".wireservice-color-picker").wpColorPicker(); 260 + 261 + // Initialize backfill. 262 + initBackfill(); 119 263 }); 120 264 })(jQuery);
+106
includes/Admin.php
··· 17 17 private ConnectionsManager $connections_manager, 18 18 private API $api, 19 19 private Publication $publication, 20 + private Document $document, 20 21 ) {} 21 22 22 23 /** ··· 41 42 $this, 42 43 "handle_save_doc_settings", 43 44 ]); 45 + add_action("wp_ajax_wireservice_backfill_count", [ 46 + $this, 47 + "handle_backfill_count", 48 + ]); 49 + add_action("wp_ajax_wireservice_backfill_batch", [ 50 + $this, 51 + "handle_backfill_batch", 52 + ]); 44 53 add_filter("plugin_action_links_wireservice/wireservice.php", [ 45 54 $this, 46 55 "add_settings_link", ··· 69 78 WIRESERVICE_VERSION, 70 79 true, 71 80 ); 81 + 82 + wp_localize_script("wireservice-settings", "wireserviceBackfill", [ 83 + "ajaxUrl" => admin_url("admin-ajax.php"), 84 + "nonce" => wp_create_nonce("wireservice_backfill"), 85 + ]); 72 86 } 73 87 74 88 /** ··· 626 640 "custom" => $custom_icon_id, 627 641 default => 0, 628 642 }; 643 + } 644 + 645 + /** 646 + * Handle AJAX request to count unsynced posts for backfill. 647 + * 648 + * @return void 649 + */ 650 + public function handle_backfill_count(): void 651 + { 652 + if (!current_user_can("manage_options")) { 653 + wp_send_json_error("Unauthorized.", 403); 654 + } 655 + 656 + check_ajax_referer("wireservice_backfill", "nonce"); 657 + 658 + $post_types = apply_filters("wireservice_syncable_post_types", [ 659 + "post", 660 + "page", 661 + ]); 662 + 663 + $query = new \WP_Query([ 664 + "post_type" => $post_types, 665 + "post_status" => "publish", 666 + "posts_per_page" => -1, 667 + "fields" => "ids", 668 + "meta_query" => [ 669 + [ 670 + "key" => Document::META_KEY_URI, 671 + "compare" => "NOT EXISTS", 672 + ], 673 + ], 674 + ]); 675 + 676 + wp_send_json_success([ 677 + "total" => $query->found_posts, 678 + "post_ids" => $query->posts, 679 + ]); 680 + } 681 + 682 + /** 683 + * Handle AJAX request to sync a batch of posts for backfill. 684 + * 685 + * @return void 686 + */ 687 + public function handle_backfill_batch(): void 688 + { 689 + if (!current_user_can("manage_options")) { 690 + wp_send_json_error("Unauthorized.", 403); 691 + } 692 + 693 + check_ajax_referer("wireservice_backfill", "nonce"); 694 + 695 + $post_ids = isset($_POST["post_ids"]) 696 + ? array_map("absint", (array) $_POST["post_ids"]) 697 + : []; 698 + 699 + if (empty($post_ids)) { 700 + wp_send_json_error("No post IDs provided."); 701 + } 702 + 703 + $results = []; 704 + foreach ($post_ids as $post_id) { 705 + $post = get_post($post_id); 706 + 707 + if (!$post || !$this->document->should_sync($post)) { 708 + $results[] = [ 709 + "id" => $post_id, 710 + "success" => false, 711 + "error" => __("Post not eligible for sync.", "wireservice"), 712 + ]; 713 + continue; 714 + } 715 + 716 + $response = $this->document->sync_to_atproto($post); 717 + 718 + if (is_wp_error($response)) { 719 + $results[] = [ 720 + "id" => $post_id, 721 + "title" => get_the_title($post), 722 + "success" => false, 723 + "error" => $response->get_error_message(), 724 + ]; 725 + } else { 726 + $results[] = [ 727 + "id" => $post_id, 728 + "title" => get_the_title($post), 729 + "success" => true, 730 + ]; 731 + } 732 + } 733 + 734 + wp_send_json_success(["results" => $results]); 629 735 } 630 736 631 737 /**
+1
includes/Setup.php
··· 40 40 $this->connections_manager, 41 41 $this->api, 42 42 $this->publication, 43 + $this->document, 43 44 ); 44 45 $admin->init(); 45 46
+45
templates/settings-page.php
··· 363 363 </table> 364 364 <?php submit_button(__("Save Document Settings", "wireservice")); ?> 365 365 </form> 366 + 367 + <?php if ($pub_uri): ?> 368 + <hr> 369 + 370 + <h2><?php esc_html_e("Backfill Documents", "wireservice"); ?></h2> 371 + <p class="description"><?php esc_html_e("Sync all existing published posts that haven't been published to AT Protocol yet.", "wireservice"); ?></p> 372 + 373 + <div id="wireservice-backfill"> 374 + <p> 375 + <button type="button" class="button button-secondary" id="wireservice-backfill-start"> 376 + <?php esc_html_e("Backfill Posts", "wireservice"); ?> 377 + </button> 378 + </p> 379 + 380 + <div id="wireservice-backfill-progress" style="display: none;"> 381 + <div class="wireservice-progress-bar"> 382 + <div class="wireservice-progress-bar-fill" style="width: 0%;"></div> 383 + </div> 384 + <p id="wireservice-backfill-status"></p> 385 + </div> 386 + 387 + <div id="wireservice-backfill-results" style="display: none;"></div> 388 + </div> 389 + <?php endif; ?> 366 390 <?php endif; ?> 367 391 368 392 <hr> ··· 469 493 } 470 494 .wireservice-advanced-settings summary:hover { 471 495 color: #1d2327; 496 + } 497 + .wireservice-progress-bar { 498 + width: 100%; 499 + height: 20px; 500 + background: #f0f0f1; 501 + border-radius: 3px; 502 + overflow: hidden; 503 + margin: 10px 0; 504 + } 505 + .wireservice-progress-bar-fill { 506 + height: 100%; 507 + background: #2271b1; 508 + transition: width 0.3s ease; 509 + } 510 + .wireservice-backfill-errors { 511 + margin-top: 10px; 512 + } 513 + .wireservice-backfill-errors summary { 514 + cursor: pointer; 515 + color: #d63638; 516 + font-weight: 500; 472 517 } 473 518 </style>