@geekity/plugin-wordpress 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +220 -195
- package/dist/bundle/index.js +5452 -4793
- package/dist/bundle/plugin.json +2 -2
- package/dist/comments-import.js +3 -6
- package/dist/comments-import.js.map +1 -1
- package/dist/content-import.js +5 -11
- package/dist/content-import.js.map +1 -1
- package/dist/import.d.ts.map +1 -1
- package/dist/import.js +1 -6
- package/dist/import.js.map +1 -1
- package/dist/media.d.ts.map +1 -1
- package/dist/media.js +2 -1
- package/dist/media.js.map +1 -1
- package/dist/posts-import.d.ts.map +1 -1
- package/dist/posts-import.js +32 -43
- package/dist/posts-import.js.map +1 -1
- package/dist/records.d.ts.map +1 -1
- package/dist/records.js +1 -3
- package/dist/records.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/wxr.d.ts.map +1 -1
- package/dist/wxr.js +12 -4
- package/dist/wxr.js.map +1 -1
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
# @geekity/plugin-wordpress
|
|
2
2
|
|
|
3
|
-
A [Geekity CMS](
|
|
4
|
-
WordPress ActivityPub plugin. It
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
3
|
+
A [Geekity CMS](https://github.com/geekitycom/cms/blob/main/packages/cms/README.md) plugin for a site that moved from the
|
|
4
|
+
WordPress ActivityPub plugin. It does three things:
|
|
5
|
+
|
|
6
|
+
- It answers the old plugin's inbox and collection paths until every
|
|
7
|
+
follower's server has refetched the actor.
|
|
8
|
+
- It records when each path was last requested.
|
|
9
|
+
- It adds two imports. `geekity import wordpress` imports the content of a
|
|
10
|
+
WordPress export. `geekity import wordpress-actor` imports each person's key
|
|
11
|
+
pair, actor id and followers.
|
|
12
|
+
|
|
13
|
+
A site that started on the CMS never needs it. Stored actor ids, post object
|
|
14
|
+
ids, WebFinger aliases and the feed and archive layout belong to every site and
|
|
15
|
+
are in `@geekity/cms` (decision-14).
|
|
14
16
|
|
|
15
17
|
## Installing it
|
|
16
18
|
|
|
17
19
|
It works with every 0.x release of `@geekity/cms` from the one it was built
|
|
18
20
|
against, which its peer dependency names.
|
|
19
21
|
|
|
20
|
-
Add the package to the site and to `plugins` in
|
|
22
|
+
Add the package to the site and to `plugins` in the site's config:
|
|
21
23
|
|
|
22
24
|
```sh
|
|
23
25
|
pnpm add @geekity/plugin-wordpress
|
|
@@ -39,46 +41,47 @@ under `/admin/plugins`:
|
|
|
39
41
|
docker compose exec geekity geekity plugin add @geekity/plugin-wordpress
|
|
40
42
|
```
|
|
41
43
|
|
|
42
|
-
Both imports work as soon as the plugin is installed. The
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
Both imports work as soon as the plugin is installed. The old paths, and the
|
|
45
|
+
redirects from WordPress media URLs, are served only while the plugin is
|
|
46
|
+
enabled under `/admin/plugins`. If you disable it, they stop on the next
|
|
47
|
+
request, with no restart.
|
|
46
48
|
|
|
47
|
-
The plugin keeps
|
|
49
|
+
The plugin keeps three files in `data/plugins/@geekity/plugin-wordpress/`:
|
|
48
50
|
|
|
49
|
-
| File | What it holds
|
|
50
|
-
| --------------- |
|
|
51
|
-
| `actors.json` | Each username and the number WordPress gave that person's actor
|
|
52
|
-
| `requests.json` | When each old path was last
|
|
53
|
-
| `import.json` | A hash of every file `geekity import wordpress` wrote, by path, and each `site.json`, `media.json` and redirect key it set.
|
|
51
|
+
| File | What it holds |
|
|
52
|
+
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
53
|
+
| `actors.json` | Each username and the number WordPress gave that person's actor. The old paths are built from the number. The import writes this file. |
|
|
54
|
+
| `requests.json` | When each old path was last requested, per user, and when the shared inbox was. If you lose it, the screen's times reset. |
|
|
55
|
+
| `import.json` | A hash of every file `geekity import wordpress` wrote, by path, and each `site.json`, `media.json` and redirect key it set. If you lose it, every imported file is a clash on the next run. |
|
|
54
56
|
|
|
55
57
|
## The old paths
|
|
56
58
|
|
|
57
59
|
A follower's server still holds the WordPress plugin's endpoints, such as
|
|
58
60
|
`/wp-json/activitypub/1.0/actors/2/inbox`, the shared
|
|
59
|
-
`/wp-json/activitypub/1.0/inbox`, and the collections beside them.
|
|
60
|
-
|
|
61
|
-
refetches the actor.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
plugin's screen under
|
|
75
|
-
lists each path with that
|
|
76
|
-
|
|
61
|
+
`/wp-json/activitypub/1.0/inbox`, and the collections beside them. These
|
|
62
|
+
paths are cached data, not identity. A follower's server replaces them the next
|
|
63
|
+
time it refetches the actor. The site serves them for a while, then stops.
|
|
64
|
+
|
|
65
|
+
While the plugin is enabled, the plugin serves:
|
|
66
|
+
|
|
67
|
+
- real inbox routes on those paths. They verify signatures the same way the
|
|
68
|
+
site's own inboxes do, so an unsigned or badly signed delivery is refused.
|
|
69
|
+
- GET routes for the actor, its outbox, its followers and its following
|
|
70
|
+
|
|
71
|
+
A peer always reads back the canonical document: the same `id`, the same key,
|
|
72
|
+
and the _canonical_ inbox and collections. A follower that refetches the actor
|
|
73
|
+
at the old URL learns the new endpoints. A user with no number in
|
|
74
|
+
`actors.json` is not reachable through any of these paths.
|
|
75
|
+
|
|
76
|
+
Each path records the time it was last requested. The plugin's screen under
|
|
77
|
+
Plugins, `/admin/plugins/@geekity/plugin-wordpress`, lists each path with that
|
|
78
|
+
time, or _Never_. Once every follower's server has refetched the actor, no
|
|
79
|
+
more requests arrive, and you can disable the plugin.
|
|
77
80
|
|
|
78
81
|
## Bringing the content across
|
|
79
82
|
|
|
80
83
|
`geekity import wordpress` reads the WXR file WordPress writes under Tools >
|
|
81
|
-
Export (or `wp export`) and writes
|
|
84
|
+
Export (or `wp export`) and writes its contents into the content directory:
|
|
82
85
|
|
|
83
86
|
```sh
|
|
84
87
|
geekity import wordpress example.WordPress.2026-10-08.xml \
|
|
@@ -86,113 +89,120 @@ geekity import wordpress example.WordPress.2026-10-08.xml \
|
|
|
86
89
|
--origins https://staging.example.com
|
|
87
90
|
```
|
|
88
91
|
|
|
89
|
-
`--uploads` is a copy of the site's `wp-content/uploads
|
|
90
|
-
no files
|
|
91
|
-
|
|
92
|
-
optional
|
|
92
|
+
`--uploads` is a copy of the site's `wp-content/uploads`. An export carries
|
|
93
|
+
no files. `--origins` is a comma-separated list of any other hosts in the
|
|
94
|
+
posts' media URLs, such as the staging host the site was built on. Both are
|
|
95
|
+
optional. See [Media](#media).
|
|
93
96
|
|
|
94
97
|
A file that is not a WordPress export is refused before anything is written,
|
|
95
98
|
with each problem named, and the command exits 1.
|
|
96
99
|
|
|
97
100
|
The command prints the totals, then one tab-separated row per item: its
|
|
98
|
-
outcome, post type, WordPress id, title, the file it went to, and
|
|
101
|
+
outcome, post type, WordPress id, title, the file it went to, and the
|
|
102
|
+
reason.
|
|
99
103
|
|
|
100
104
|
| Outcome | What it means |
|
|
101
105
|
| ----------- | -------------------------------------------------------------------------------------------------------- |
|
|
102
106
|
| `written` | The file is new, was changed on WordPress since the last run, or was removed here and is written again. |
|
|
103
107
|
| `unchanged` | The file already holds what WordPress has. |
|
|
104
108
|
| `kept` | The file was edited on this site and WordPress has not changed it since. The edit stays. |
|
|
105
|
-
| `conflict` | The file was edited on this site and changed on WordPress too. The edit stays
|
|
109
|
+
| `conflict` | The file was edited on this site and changed on WordPress too. The edit stays. The row gives both dates. |
|
|
106
110
|
| `clash` | A file the import never wrote is at the path. It is left alone. |
|
|
107
111
|
| `skipped` | The item has no Geekity counterpart, such as a revision, a menu or the ActivityPub plugin's records. |
|
|
108
112
|
| `warned` | The item was imported, with something the operator should check. |
|
|
109
113
|
|
|
110
|
-
**
|
|
111
|
-
directory byte for byte as one run
|
|
112
|
-
what was added or edited on WordPress since. The import never
|
|
113
|
-
it did not write, and never deletes
|
|
114
|
-
|
|
115
|
-
run the import again.
|
|
114
|
+
**You can run it any number of times.** Two runs over one export leave the
|
|
115
|
+
content directory byte for byte the same as one run. A run over a newer export
|
|
116
|
+
picks up what was added or edited on WordPress since. The import never
|
|
117
|
+
overwrites a file it did not write, and never deletes a file, so you can edit
|
|
118
|
+
imported files and keep the edits. To take WordPress's side of a conflict,
|
|
119
|
+
remove the file and run the import again.
|
|
116
120
|
|
|
117
121
|
### Posts and pages
|
|
118
122
|
|
|
119
123
|
Each post becomes `posts/YYYY-MM-DD-slug.md`, dated by its local publish date,
|
|
120
|
-
and each page becomes `pages/slug.md`. The front matter keeps
|
|
121
|
-
feed readers and followers already know
|
|
122
|
-
|
|
123
|
-
| Key | Where it comes from
|
|
124
|
-
| --------------- |
|
|
125
|
-
| `permalink` | The URL WordPress served, from the export's link. A draft has none yet, so it gets the one the published posts' structure gives it
|
|
126
|
-
| `redirect_from` | The URLs the post had before WordPress renamed or redated it: its permalink with each `_wp_old_slug` in place of its slug, and each `_wp_old_date` in place of its date.
|
|
127
|
-
| `date` | `post_date_gmt`. A draft WordPress never dated has none.
|
|
128
|
-
| `updated` | `post_modified_gmt`, when it differs from the date.
|
|
129
|
-
| `tags` | The post's tags, by name.
|
|
130
|
-
| `categories` | The post's categories, by name.
|
|
131
|
-
| `draft` | `true` for a draft, a pending post, and a private or password-protected one, which the report names. A scheduled post keeps its date and publishes then.
|
|
132
|
-
| `author` | The WordPress login, when the site has a user by that name. A post by any other login has no author, and the report names it.
|
|
133
|
-
| `in-reply-to` | The URL an ActivityPub plugin reply block answers.
|
|
134
|
-
| `activitypub` | On a published post, `id` is `https://example.com/?p=ID`, the object id the ActivityPub plugin served. A post the plugin federated (`activitypub_status` is `federated`) also has `published`.
|
|
135
|
-
| `guid` | The WordPress guid, when it is not the post's `?p=` address, so feed readers see no old post as new.
|
|
136
|
-
| `migrated` | `true` on everything but a scheduled post, so
|
|
137
|
-
|
|
138
|
-
**Expect one difference
|
|
124
|
+
and each page becomes `pages/slug.md`. The front matter keeps the URLs and ids
|
|
125
|
+
that visitors, feed readers and followers already know:
|
|
126
|
+
|
|
127
|
+
| Key | Where it comes from |
|
|
128
|
+
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
129
|
+
| `permalink` | The URL WordPress served, from the export's link. A draft has none yet, so it gets the one the published posts' structure gives it. A page gets its parents' slugs. |
|
|
130
|
+
| `redirect_from` | The URLs the post had before WordPress renamed or redated it: its permalink with each `_wp_old_slug` in place of its slug, and each `_wp_old_date` in place of its date. |
|
|
131
|
+
| `date` | `post_date_gmt`. A draft WordPress never dated has none. |
|
|
132
|
+
| `updated` | `post_modified_gmt`, when it differs from the date. |
|
|
133
|
+
| `tags` | The post's tags, by name. |
|
|
134
|
+
| `categories` | The post's categories, by name. |
|
|
135
|
+
| `draft` | `true` for a draft, a pending post, and a private or password-protected one, which the report names. A scheduled post keeps its date and publishes then. |
|
|
136
|
+
| `author` | The WordPress login, when the site has a user by that name. A post by any other login has no author, and the report names it. |
|
|
137
|
+
| `in-reply-to` | The URL an ActivityPub plugin reply block answers. |
|
|
138
|
+
| `activitypub` | On a published post, `id` is `https://example.com/?p=ID`, the object id the ActivityPub plugin served. A post the plugin federated (`activitypub_status` is `federated`) also has `published`. |
|
|
139
|
+
| `guid` | The WordPress guid, when it is not the post's `?p=` address, so feed readers see no old post as new. |
|
|
140
|
+
| `migrated` | `true` on everything but a scheduled post, so importing it, or publishing a draft later, sends nothing to anyone. A scheduled post publishes here as news, as it would have on WordPress. |
|
|
141
|
+
|
|
142
|
+
**Expect one difference when you diff the feeds.** WordPress prints the escapes in a
|
|
139
143
|
non-ASCII slug in lower case, as in `/2026/07/i-%e2%99%a5-rss/`. The site
|
|
140
144
|
prints them in upper case, as in `/2026/07/i-%E2%99%A5-rss/`, in the RSS,
|
|
141
145
|
Atom and JSON feeds, the comments feeds, `rel="canonical"`, `og:url`, the
|
|
142
|
-
sitemap and the ActivityStreams `url`. RFC 3986
|
|
143
|
-
same URL, and both answer `200` with the post. A feed reader keys an item
|
|
144
|
-
its `guid`, which the import keeps, so the post does not show as new.
|
|
146
|
+
sitemap and the ActivityStreams `url`. Under RFC 3986 the two spellings are
|
|
147
|
+
the same URL, and both answer `200` with the post. A feed reader keys an item
|
|
148
|
+
on its `guid`, which the import keeps, so the post does not show as new.
|
|
145
149
|
|
|
146
|
-
A post in the status format has no title, so it
|
|
150
|
+
A post in the status format has no title, so it becomes a note. The page
|
|
147
151
|
WordPress served at the home URL becomes `homepage` in `_data/site.json`. The
|
|
148
152
|
import sets that key only while the site has not set it, or still holds what
|
|
149
153
|
the import last set.
|
|
150
154
|
|
|
151
|
-
The body becomes Markdown
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
155
|
+
The body becomes Markdown:
|
|
156
|
+
|
|
157
|
+
- Block comments are removed.
|
|
158
|
+
- A classic post's line breaks become paragraphs, as WordPress showed them.
|
|
159
|
+
- A YouTube or Vimeo iframe or embed becomes the video's URL on a line of its
|
|
160
|
+
own. The site plays it.
|
|
161
|
+
- HTML that Markdown cannot express stays HTML: other iframes, a figure with a
|
|
162
|
+
caption, a table with spans, and an element that carries a `style` or
|
|
163
|
+
microformats class.
|
|
164
|
+
`<!--more-->` stays where it was. Media URLs point at the files under
|
|
165
|
+
`/uploads/`, as [Media](#media) describes.
|
|
158
166
|
|
|
159
167
|
### Media
|
|
160
168
|
|
|
161
169
|
Each attachment's original is copied from `--uploads` into `content/uploads/`
|
|
162
170
|
at the path WordPress kept it under, such as `uploads/2024/03/photo.png`.
|
|
163
171
|
WordPress's size variants (`photo-1024x575.png`, `photo-scaled.png`) are not
|
|
164
|
-
copied
|
|
165
|
-
attachment is copied too, for that post.
|
|
172
|
+
copied, because the site makes its own. A file that a post or page shows but
|
|
173
|
+
that is not an attachment is copied too, for that post.
|
|
166
174
|
|
|
167
|
-
Every media URL in a body
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
points at its original. An upload URL on any other host is left
|
|
175
|
+
Every media URL in a body becomes a root-relative `/uploads/` URL of the
|
|
176
|
+
original. This applies in Markdown and in the HTML the import keeps, for URLs
|
|
177
|
+
on the export's own origin, on an `--origins` host, or root-relative. A URL of
|
|
178
|
+
a size variant points at its original. An upload URL on any other host is left
|
|
179
|
+
unchanged.
|
|
171
180
|
|
|
172
|
-
Each file
|
|
173
|
-
allowed types and size limits
|
|
174
|
-
file the site would refuse is not copied. The report names it, and names
|
|
175
|
-
|
|
176
|
-
|
|
181
|
+
Each file must pass the same rules as an upload through the editor: the site's
|
|
182
|
+
allowed types and size limits. The import removes location and camera details.
|
|
183
|
+
A file the site would refuse is not copied. The report names it, and names each
|
|
184
|
+
file missing from `--uploads`. Without `--uploads`, nothing is copied and the
|
|
185
|
+
report names each attachment.
|
|
177
186
|
|
|
178
187
|
An attachment's alt text (`_wp_attachment_image_alt`) goes to
|
|
179
|
-
`_data/media.json` under its upload path
|
|
180
|
-
|
|
188
|
+
`_data/media.json` under its upload path. The editor offers alt text from that
|
|
189
|
+
file. The import owns each key it set there, never the file.
|
|
181
190
|
|
|
182
191
|
While the plugin is enabled, a request for `/wp-content/uploads/<path>`
|
|
183
|
-
answers 301 at `/uploads/<path>`, so links from elsewhere still
|
|
184
|
-
|
|
185
|
-
`import.json` records it
|
|
186
|
-
entry
|
|
187
|
-
old media URLs matter.
|
|
192
|
+
answers 301 at `/uploads/<path>`, so links from elsewhere still reach the file.
|
|
193
|
+
A request for a size variant the import did not copy redirects to its original,
|
|
194
|
+
as `import.json` records it. The query string is kept. The import writes no
|
|
195
|
+
`redirects.json` entry per file. Keep the plugin enabled for as long as links to
|
|
196
|
+
the old media URLs matter.
|
|
188
197
|
|
|
189
198
|
### Comments and reactions
|
|
190
199
|
|
|
191
|
-
|
|
192
|
-
post's comment file, `_data/comments/<slug>.json
|
|
193
|
-
comments go to.
|
|
194
|
-
mentions, bookmarks and pingbacks the ActivityPub and
|
|
195
|
-
as comments. Each
|
|
200
|
+
For each post or page the import writes, every comment WordPress kept on it
|
|
201
|
+
goes into the post's comment file, `_data/comments/<slug>.json`. The site's own
|
|
202
|
+
comments go to the same file. This covers form comments, and the likes,
|
|
203
|
+
reposts, replies, mentions, bookmarks and pingbacks that the ActivityPub and
|
|
204
|
+
Webmention plugins stored as comments. Each one appears under the post the
|
|
205
|
+
same way as a comment the site received itself:
|
|
196
206
|
|
|
197
207
|
| WordPress | Here |
|
|
198
208
|
| --------------------------------------------------------- | -------------------------------------------------------------- |
|
|
@@ -202,20 +212,24 @@ as comments. Each lands under the post as one the site received itself would:
|
|
|
202
212
|
| `pingback`, `trackback` | A mention, linked to the page that pinged. |
|
|
203
213
|
| `comment` with no protocol | A comment left on the page. |
|
|
204
214
|
|
|
205
|
-
A
|
|
206
|
-
|
|
215
|
+
A commenter's name is the text WordPress showed. WordPress stores a name as
|
|
216
|
+
HTML, so `Andy Sylvester's Web` arrives as `Andy Sylvester's Web`. A
|
|
217
|
+
post's title and its tag and category names are decoded the same way.
|
|
218
|
+
|
|
219
|
+
A reply keeps the comment it answers. An approved comment is approved here. A
|
|
220
|
+
comment WordPress held for moderation is held here too. Spam and trash are left
|
|
207
221
|
out and named in the report. A commenter's email goes only to
|
|
208
|
-
`data/comments/`.
|
|
209
|
-
|
|
222
|
+
`data/comments/`. The import announces nothing. It emails no moderator and
|
|
223
|
+
delivers nothing.
|
|
210
224
|
|
|
211
225
|
Every imported comment is `migrated: true`. It shows on its post and in its
|
|
212
226
|
thread like any other comment, and replies to it thread under it. It is in no
|
|
213
227
|
feed: not `/comments/feed/`, not a post's `{permalink}feed/`, not a
|
|
214
|
-
`/replies/{key}/` feed, and not in the reply counts the feeds give. A
|
|
215
|
-
|
|
228
|
+
`/replies/{key}/` feed, and not in the reply counts the feeds give. A
|
|
229
|
+
subscriber to WordPress's comments feed sees nothing new when the site moves.
|
|
216
230
|
|
|
217
|
-
Each comment keeps its `origin`, the name it was first published under
|
|
218
|
-
|
|
231
|
+
Each comment keeps its `origin`, the name it was first published under. Its id
|
|
232
|
+
is the key the site gives the same comment when the site receives it directly.
|
|
219
233
|
A key is the first 16 hex digits of a SHA-256.
|
|
220
234
|
|
|
221
235
|
| Comment | `origin` | Id, the key of |
|
|
@@ -226,48 +240,50 @@ A key is the first 16 hex digits of a SHA-256.
|
|
|
226
240
|
| A like or repost with no protocol | `source_id`, such as a Bluesky `at://` URI | The origin and the post's permalink |
|
|
227
241
|
| A comment left on the page | None | `https://example.com/?p=ID#comment-N` |
|
|
228
242
|
|
|
229
|
-
|
|
230
|
-
a fediverse reply or reaction delivered again or read by the
|
|
231
|
-
backfill
|
|
232
|
-
thread under it. A fediverse reply's `url` stays the note's web page.
|
|
233
|
-
has a page of its own, `/comment/{key}/`.
|
|
243
|
+
A webmention sent again therefore matches the comment the site already
|
|
244
|
+
holds. So does a fediverse reply or reaction delivered again or read by the
|
|
245
|
+
site's reply backfill. It shows once, as the imported comment, and replies to
|
|
246
|
+
the note thread under it. A fediverse reply's `url` stays the note's web page.
|
|
247
|
+
A comment left on the page has a page of its own, `/comment/{key}/`.
|
|
234
248
|
|
|
235
|
-
A fediverse comment names its actor in `_activitypub_remote_actor_id
|
|
236
|
-
the export's `ap_actor` items, the ActivityPub plugin's cached copy of
|
|
237
|
-
actor's profile. The import reads that profile and gives the comment its
|
|
249
|
+
A fediverse comment names its actor in `_activitypub_remote_actor_id`. That is
|
|
250
|
+
one of the export's `ap_actor` items, the ActivityPub plugin's cached copy of
|
|
251
|
+
the actor's profile. The import reads that profile and gives the comment its
|
|
238
252
|
`actor`, the actor's id, and its `author.avatar`, the actor's icon. The page
|
|
239
253
|
shows the icon through the site's avatar cache, so a like or a boost shows a
|
|
240
|
-
face
|
|
241
|
-
imported one. A comment whose actor is not in the export imports without
|
|
242
|
-
|
|
254
|
+
face. A like from the same actor delivered here after the move is counted with
|
|
255
|
+
the imported one. A comment whose actor is not in the export imports without
|
|
256
|
+
`actor` or `author.avatar`.
|
|
243
257
|
|
|
244
|
-
A comment written signed in to WordPress names its writer in
|
|
245
|
-
`comment_user_id`.
|
|
246
|
-
rule a post's author follows, the comment keeps that user as its
|
|
247
|
-
thread then shows
|
|
248
|
-
name and avatar, and a link to their author
|
|
249
|
-
ugc"`. It stays an imported comment.
|
|
258
|
+
A comment written while signed in to WordPress names its writer in
|
|
259
|
+
`comment_user_id`. If that WordPress user's login matches a user on this site
|
|
260
|
+
(the same rule a post's author follows), the comment keeps that user as its
|
|
261
|
+
`user`. The thread then shows the comment as that user's, the same way it shows
|
|
262
|
+
their reply posts: their display name and avatar, and a link to their author
|
|
263
|
+
archive with no `rel="nofollow ugc"`. It stays an imported comment.
|
|
250
264
|
|
|
251
265
|
The import merges by comment id. A rerun adds the comments WordPress received
|
|
252
|
-
since and picks up
|
|
253
|
-
received itself
|
|
254
|
-
approved, filed as spam, edited or deleted.
|
|
255
|
-
migrated and gives it its origin, its user and
|
|
266
|
+
since and picks up comments edited there. It leaves unchanged a comment this
|
|
267
|
+
site received itself. It also leaves unchanged the words and status of an
|
|
268
|
+
imported comment that a moderator approved, filed as spam, edited or deleted.
|
|
269
|
+
It still marks such a comment migrated and gives it its origin, its user and
|
|
270
|
+
its actor.
|
|
256
271
|
|
|
257
272
|
**Upgrading from 0.2.0.** Version 0.2.0 used each comment's WordPress URL as
|
|
258
|
-
its id. The site
|
|
259
|
-
or when it next runs a plugin command. A rerun of the import then moves
|
|
260
|
-
fediverse comment and webmention to the key of its origin
|
|
261
|
-
|
|
273
|
+
its id. The site replaces every such id with the key of that URL when it next
|
|
274
|
+
boots, or when it next runs a plugin command. A rerun of the import then moves
|
|
275
|
+
each fediverse comment and webmention, in place, to the key of its origin. Any
|
|
276
|
+
reply here that names the comment moves with it. Each comment stays one
|
|
277
|
+
record. The
|
|
262
278
|
import's record of what it wrote, `import.json`, moves to the new keys, so the
|
|
263
279
|
rerun reports no clash.
|
|
264
280
|
|
|
265
281
|
### Redirects
|
|
266
282
|
|
|
267
|
-
Links from elsewhere use URL forms WordPress answered and the site has
|
|
268
|
-
document at. The import declares a redirect for each in
|
|
269
|
-
`_data/redirects/wordpress.json
|
|
270
|
-
`_data/redirects.json
|
|
283
|
+
Links from elsewhere use URL forms that WordPress answered and the site has
|
|
284
|
+
no document at. The import declares a redirect for each in its own file,
|
|
285
|
+
`_data/redirects/wordpress.json`. The site reads that file beside
|
|
286
|
+
`_data/redirects.json`, with or without the plugin enabled:
|
|
271
287
|
|
|
272
288
|
| URL | Answers 301 at |
|
|
273
289
|
| ------------------------------------------- | --------------------------- |
|
|
@@ -276,31 +292,35 @@ document at. The import declares a redirect for each in
|
|
|
276
292
|
| An attachment's page, such as `/photo-jpg/` | Its file under `/uploads/`. |
|
|
277
293
|
| `/?attachment_id=ID` and `/?p=ID`, likewise | Its file under `/uploads/`. |
|
|
278
294
|
|
|
279
|
-
The page WordPress used as its static front page is the exception
|
|
280
|
-
`?p=` and `?page_id=` answer at `/`, where the site serves its homepage
|
|
281
|
-
|
|
295
|
+
The page WordPress used as its static front page is the exception. Its
|
|
296
|
+
`?p=` and `?page_id=` answer at `/`, where the site serves its homepage. The
|
|
297
|
+
page's own permalink redirects there too.
|
|
282
298
|
|
|
283
|
-
A draft, a pending, private, password-protected or scheduled post or page
|
|
284
|
-
no redirect
|
|
285
|
-
whose file was not copied gets
|
|
286
|
-
old slugs and dates go into its own `redirect_from`,
|
|
287
|
-
[Posts and pages](#posts-and-pages) says. A published post's `?p=` is also
|
|
288
|
-
`activitypub.id
|
|
299
|
+
A draft, or a pending, private, password-protected or scheduled post or page,
|
|
300
|
+
gets no redirect. Its `?p=` answers the same as an id WordPress never had. An
|
|
301
|
+
attachment whose file was not copied gets no redirect either, until a run
|
|
302
|
+
copies the file. A post's old slugs and dates go into its own `redirect_from`,
|
|
303
|
+
as [Posts and pages](#posts-and-pages) says. A published post's `?p=` is also
|
|
304
|
+
its `activitypub.id`. The site answers that first, at the same permalink.
|
|
289
305
|
|
|
290
|
-
The import owns each key it set in `wordpress.json`, never the file
|
|
291
|
-
touches `_data/redirects.json`.
|
|
292
|
-
|
|
293
|
-
A link to a comment, `#comment-N`, reaches the post
|
|
294
|
-
|
|
295
|
-
opens the post at the top.
|
|
306
|
+
The import owns each key it set in `wordpress.json`, never the file. It never
|
|
307
|
+
touches `_data/redirects.json`. The import leaves a key that the site added or
|
|
308
|
+
changed unchanged on every rerun. If both files declare one URL,
|
|
309
|
+
`redirects.json` wins. A link to a comment, `#comment-N`, reaches the post,
|
|
310
|
+
because the browser never sends the fragment to the server. The post's anchors
|
|
311
|
+
are `#comment-{key}`, so the browser opens the post at the top.
|
|
296
312
|
|
|
297
313
|
## Bringing a person across
|
|
298
314
|
|
|
299
|
-
`geekity import wordpress-actor`
|
|
300
|
-
things the site cannot
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
315
|
+
`geekity import wordpress-actor` imports one person. It writes the three
|
|
316
|
+
things the site cannot create for itself:
|
|
317
|
+
|
|
318
|
+
- the RSA key pair that the followers have cached
|
|
319
|
+
- the actor id that the followers use to identify the account
|
|
320
|
+
- the followers
|
|
321
|
+
|
|
322
|
+
It is a command and not a screen, because a stored actor id is the account's
|
|
323
|
+
identity for the life of the account.
|
|
304
324
|
|
|
305
325
|
```sh
|
|
306
326
|
geekity import wordpress-actor ada \
|
|
@@ -309,23 +329,26 @@ geekity import wordpress-actor ada \
|
|
|
309
329
|
--keypair ada.keypair.json
|
|
310
330
|
```
|
|
311
331
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
`data/
|
|
315
|
-
|
|
316
|
-
`
|
|
317
|
-
|
|
332
|
+
The command:
|
|
333
|
+
|
|
334
|
+
1. writes `data/keys/ada.rsassa-pkcs1-v1_5.jwk`, and creates the Ed25519 pair
|
|
335
|
+
beside it, which WordPress never had
|
|
336
|
+
2. puts `actorId` on the user in `data/users.json` and the number in the
|
|
337
|
+
plugin's `actors.json`
|
|
338
|
+
3. fetches the WordPress plugin's public followers collection into
|
|
339
|
+
`content/_data/federation/ada/followers.json`. It dereferences each follower
|
|
340
|
+
for its inbox, shared inbox, handle, name, avatar and profile URL.
|
|
318
341
|
|
|
319
342
|
| Option | What it is |
|
|
320
343
|
| --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
321
|
-
| `--actor-id <url>` | The id WordPress published, query string
|
|
344
|
+
| `--actor-id <url>` | The id WordPress published, including the query string. The import cannot derive it. |
|
|
322
345
|
| `--wordpress-id <n>` | The WordPress user id, which is the number in the plugin's paths. |
|
|
323
346
|
| `--keypair <file>` | The JSON the plugin's option holds: `{"private_key": …, "public_key": …}`. |
|
|
324
|
-
| `--private-key <file>`, `--public-key <file>` | The two PEMs instead, for a site still on the legacy user meta. The public key is only checked against the private half
|
|
325
|
-
| `--followers <url\|file\|none>` | Where the followers come from.
|
|
347
|
+
| `--private-key <file>`, `--public-key <file>` | The two PEMs instead, for a site still on the legacy user meta. The public key is only checked against the private half. It is derived, never stored. |
|
|
348
|
+
| `--followers <url\|file\|none>` | Where the followers come from. The default is the plugin's own collection on the actor id's origin. |
|
|
326
349
|
| `--force` | Import over a key pair the user already has. |
|
|
327
350
|
|
|
328
|
-
**Exporting the key pair with wp-cli.** The WordPress plugin
|
|
351
|
+
**Exporting the key pair with wp-cli.** The WordPress plugin stores a user's
|
|
329
352
|
pair in an option named after their login:
|
|
330
353
|
|
|
331
354
|
```sh
|
|
@@ -333,43 +356,45 @@ pair in an option named after their login:
|
|
|
333
356
|
wp option get activitypub_keypair_for_ada --format=json > ada.keypair.json
|
|
334
357
|
```
|
|
335
358
|
|
|
336
|
-
|
|
337
|
-
|
|
359
|
+
An older site still on the plugin's legacy storage has the pair in user meta
|
|
360
|
+
instead, one PEM per key:
|
|
338
361
|
|
|
339
362
|
```sh
|
|
340
363
|
wp user meta get 2 magic_sig_private_key > ada.private.pem
|
|
341
364
|
wp user meta get 2 magic_sig_public_key > ada.public.pem
|
|
342
365
|
```
|
|
343
366
|
|
|
344
|
-
|
|
345
|
-
KEY-----` (PKCS#8) or `-----BEGIN RSA PRIVATE KEY-----`
|
|
346
|
-
|
|
367
|
+
The command accepts either export, and either PEM encoding:
|
|
368
|
+
`-----BEGIN PRIVATE KEY-----` (PKCS#8) or `-----BEGIN RSA PRIVATE KEY-----`
|
|
369
|
+
(PKCS#1). Treat the files like a password. Whoever holds the private key
|
|
370
|
+
controls the account.
|
|
347
371
|
|
|
348
|
-
|
|
349
|
-
|
|
372
|
+
You can save the followers before you shut down the old site, and pass the
|
|
373
|
+
saved file to `--followers`:
|
|
350
374
|
|
|
351
375
|
```sh
|
|
352
376
|
curl -H 'Accept: application/activity+json' \
|
|
353
377
|
'https://example.com/wp-json/activitypub/1.0/actors/2/followers?page=1' > followers.json
|
|
354
378
|
```
|
|
355
379
|
|
|
356
|
-
**Running it twice changes nothing**, and says so
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
answer is listed and skipped
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
380
|
+
**Running it twice changes nothing**, and the command says so. You can run it
|
|
381
|
+
again after an instance that was down comes back. A follower already in the
|
|
382
|
+
file keeps its place and its follow time. A follower whose server does not
|
|
383
|
+
answer is listed and skipped. The import does not fail.
|
|
384
|
+
|
|
385
|
+
The command refuses a user who already has a _different_ key pair. Importing
|
|
386
|
+
over it would change that person's identity, and every follower has cached the
|
|
387
|
+
public half of the existing key. `--force` overrides the refusal. Use it only
|
|
388
|
+
when you are certain the pair you import is the one the followers hold. The
|
|
389
|
+
command also refuses an actor id or a WordPress number that another user
|
|
390
|
+
already has, before it writes anything.
|
|
366
391
|
|
|
367
|
-
The cutover, end to end, is in
|
|
368
|
-
from the WordPress ActivityPub
|
|
392
|
+
The cutover, end to end, is in
|
|
393
|
+
[Moving a site from the WordPress ActivityPub plugin](https://github.com/geekitycom/cms/blob/main/docs/federation.md#moving-a-site-from-the-wordpress-activitypub-plugin).
|
|
369
394
|
|
|
370
395
|
## The bundle
|
|
371
396
|
|
|
372
397
|
The package's build also writes `dist/bundle/index.js`, one module with its
|
|
373
|
-
dependencies inlined and the plugin as its default export.
|
|
374
|
-
|
|
375
|
-
|
|
398
|
+
dependencies inlined and the plugin as its default export. A site that runs
|
|
399
|
+
the Docker image has no `node_modules` of its own, so its plugins folder holds
|
|
400
|
+
this file (decision-33).
|