@geekity/plugin-wordpress 0.2.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 +249 -163
- package/dist/bundle/index.js +4960 -4195
- package/dist/bundle/plugin.json +2 -2
- package/dist/comments-import.d.ts.map +1 -1
- package/dist/comments-import.js +102 -14
- package/dist/comments-import.js.map +1 -1
- package/dist/content-import.d.ts +2 -0
- package/dist/content-import.d.ts.map +1 -1
- package/dist/content-import.js +63 -21
- 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 +36 -44
- 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,105 +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
|
-
|
|
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
|
|
143
|
+
non-ASCII slug in lower case, as in `/2026/07/i-%e2%99%a5-rss/`. The site
|
|
144
|
+
prints them in upper case, as in `/2026/07/i-%E2%99%A5-rss/`, in the RSS,
|
|
145
|
+
Atom and JSON feeds, the comments feeds, `rel="canonical"`, `og:url`, the
|
|
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.
|
|
149
|
+
|
|
150
|
+
A post in the status format has no title, so it becomes a note. The page
|
|
139
151
|
WordPress served at the home URL becomes `homepage` in `_data/site.json`. The
|
|
140
152
|
import sets that key only while the site has not set it, or still holds what
|
|
141
153
|
the import last set.
|
|
142
154
|
|
|
143
|
-
The body becomes Markdown
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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.
|
|
150
166
|
|
|
151
167
|
### Media
|
|
152
168
|
|
|
153
169
|
Each attachment's original is copied from `--uploads` into `content/uploads/`
|
|
154
170
|
at the path WordPress kept it under, such as `uploads/2024/03/photo.png`.
|
|
155
171
|
WordPress's size variants (`photo-1024x575.png`, `photo-scaled.png`) are not
|
|
156
|
-
copied
|
|
157
|
-
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.
|
|
158
174
|
|
|
159
|
-
Every media URL in a body
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
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.
|
|
163
180
|
|
|
164
|
-
Each file
|
|
165
|
-
allowed types and size limits
|
|
166
|
-
file the site would refuse is not copied. The report names it, and names
|
|
167
|
-
|
|
168
|
-
|
|
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.
|
|
169
186
|
|
|
170
187
|
An attachment's alt text (`_wp_attachment_image_alt`) goes to
|
|
171
|
-
`_data/media.json` under its upload path
|
|
172
|
-
|
|
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.
|
|
173
190
|
|
|
174
191
|
While the plugin is enabled, a request for `/wp-content/uploads/<path>`
|
|
175
|
-
answers 301 at `/uploads/<path>`, so links from elsewhere still
|
|
176
|
-
|
|
177
|
-
`import.json` records it
|
|
178
|
-
entry
|
|
179
|
-
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.
|
|
180
197
|
|
|
181
198
|
### Comments and reactions
|
|
182
199
|
|
|
183
|
-
|
|
184
|
-
post's comment file, `_data/comments/<slug>.json
|
|
185
|
-
comments go to.
|
|
186
|
-
mentions, bookmarks and pingbacks the ActivityPub and
|
|
187
|
-
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:
|
|
188
206
|
|
|
189
207
|
| WordPress | Here |
|
|
190
208
|
| --------------------------------------------------------- | -------------------------------------------------------------- |
|
|
@@ -194,24 +212,78 @@ as comments. Each lands under the post as one the site received itself would:
|
|
|
194
212
|
| `pingback`, `trackback` | A mention, linked to the page that pinged. |
|
|
195
213
|
| `comment` with no protocol | A comment left on the page. |
|
|
196
214
|
|
|
197
|
-
A
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
|
221
|
+
out and named in the report. A commenter's email goes only to
|
|
222
|
+
`data/comments/`. The import announces nothing. It emails no moderator and
|
|
223
|
+
delivers nothing.
|
|
224
|
+
|
|
225
|
+
Every imported comment is `migrated: true`. It shows on its post and in its
|
|
226
|
+
thread like any other comment, and replies to it thread under it. It is in no
|
|
227
|
+
feed: not `/comments/feed/`, not a post's `{permalink}feed/`, not a
|
|
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.
|
|
230
|
+
|
|
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.
|
|
233
|
+
A key is the first 16 hex digits of a SHA-256.
|
|
234
|
+
|
|
235
|
+
| Comment | `origin` | Id, the key of |
|
|
236
|
+
| ----------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------- |
|
|
237
|
+
| A fediverse reply | The note, `source_id` | The origin |
|
|
238
|
+
| A fediverse like or boost | The activity, `source_id` | The origin |
|
|
239
|
+
| A webmention, pingback or trackback | The page that linked here: `webmention_source_url`, or a pingback's author URL | The origin and the post's permalink |
|
|
240
|
+
| A like or repost with no protocol | `source_id`, such as a Bluesky `at://` URI | The origin and the post's permalink |
|
|
241
|
+
| A comment left on the page | None | `https://example.com/?p=ID#comment-N` |
|
|
242
|
+
|
|
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}/`.
|
|
248
|
+
|
|
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
|
|
252
|
+
`actor`, the actor's id, and its `author.avatar`, the actor's icon. The page
|
|
253
|
+
shows the icon through the site's avatar cache, so a like or a boost shows a
|
|
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`.
|
|
257
|
+
|
|
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.
|
|
203
264
|
|
|
204
265
|
The import merges by comment id. A rerun adds the comments WordPress received
|
|
205
|
-
since and picks up
|
|
206
|
-
received itself
|
|
207
|
-
edited or deleted.
|
|
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.
|
|
271
|
+
|
|
272
|
+
**Upgrading from 0.2.0.** Version 0.2.0 used each comment's WordPress URL as
|
|
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
|
|
278
|
+
import's record of what it wrote, `import.json`, moves to the new keys, so the
|
|
279
|
+
rerun reports no clash.
|
|
208
280
|
|
|
209
281
|
### Redirects
|
|
210
282
|
|
|
211
|
-
Links from elsewhere use URL forms WordPress answered and the site has
|
|
212
|
-
document at. The import declares a redirect for each in
|
|
213
|
-
`_data/redirects/wordpress.json
|
|
214
|
-
`_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:
|
|
215
287
|
|
|
216
288
|
| URL | Answers 301 at |
|
|
217
289
|
| ------------------------------------------- | --------------------------- |
|
|
@@ -220,26 +292,35 @@ document at. The import declares a redirect for each in
|
|
|
220
292
|
| An attachment's page, such as `/photo-jpg/` | Its file under `/uploads/`. |
|
|
221
293
|
| `/?attachment_id=ID` and `/?p=ID`, likewise | Its file under `/uploads/`. |
|
|
222
294
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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.
|
|
298
|
+
|
|
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.
|
|
229
305
|
|
|
230
|
-
The import owns each key it set in `wordpress.json`, never the file
|
|
231
|
-
touches `_data/redirects.json`.
|
|
232
|
-
|
|
233
|
-
A link to a comment, `#comment-N`, reaches the post
|
|
234
|
-
|
|
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.
|
|
235
312
|
|
|
236
313
|
## Bringing a person across
|
|
237
314
|
|
|
238
|
-
`geekity import wordpress-actor`
|
|
239
|
-
things the site cannot
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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.
|
|
243
324
|
|
|
244
325
|
```sh
|
|
245
326
|
geekity import wordpress-actor ada \
|
|
@@ -248,23 +329,26 @@ geekity import wordpress-actor ada \
|
|
|
248
329
|
--keypair ada.keypair.json
|
|
249
330
|
```
|
|
250
331
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
`data/
|
|
254
|
-
|
|
255
|
-
`
|
|
256
|
-
|
|
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.
|
|
257
341
|
|
|
258
342
|
| Option | What it is |
|
|
259
343
|
| --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
260
|
-
| `--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. |
|
|
261
345
|
| `--wordpress-id <n>` | The WordPress user id, which is the number in the plugin's paths. |
|
|
262
346
|
| `--keypair <file>` | The JSON the plugin's option holds: `{"private_key": …, "public_key": …}`. |
|
|
263
|
-
| `--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
|
|
264
|
-
| `--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. |
|
|
265
349
|
| `--force` | Import over a key pair the user already has. |
|
|
266
350
|
|
|
267
|
-
**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
|
|
268
352
|
pair in an option named after their login:
|
|
269
353
|
|
|
270
354
|
```sh
|
|
@@ -272,43 +356,45 @@ pair in an option named after their login:
|
|
|
272
356
|
wp option get activitypub_keypair_for_ada --format=json > ada.keypair.json
|
|
273
357
|
```
|
|
274
358
|
|
|
275
|
-
|
|
276
|
-
|
|
359
|
+
An older site still on the plugin's legacy storage has the pair in user meta
|
|
360
|
+
instead, one PEM per key:
|
|
277
361
|
|
|
278
362
|
```sh
|
|
279
363
|
wp user meta get 2 magic_sig_private_key > ada.private.pem
|
|
280
364
|
wp user meta get 2 magic_sig_public_key > ada.public.pem
|
|
281
365
|
```
|
|
282
366
|
|
|
283
|
-
|
|
284
|
-
KEY-----` (PKCS#8) or `-----BEGIN RSA PRIVATE KEY-----`
|
|
285
|
-
|
|
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.
|
|
286
371
|
|
|
287
|
-
|
|
288
|
-
|
|
372
|
+
You can save the followers before you shut down the old site, and pass the
|
|
373
|
+
saved file to `--followers`:
|
|
289
374
|
|
|
290
375
|
```sh
|
|
291
376
|
curl -H 'Accept: application/activity+json' \
|
|
292
377
|
'https://example.com/wp-json/activitypub/1.0/actors/2/followers?page=1' > followers.json
|
|
293
378
|
```
|
|
294
379
|
|
|
295
|
-
**Running it twice changes nothing**, and says so
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
answer is listed and skipped
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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.
|
|
305
391
|
|
|
306
|
-
The cutover, end to end, is in
|
|
307
|
-
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).
|
|
308
394
|
|
|
309
395
|
## The bundle
|
|
310
396
|
|
|
311
397
|
The package's build also writes `dist/bundle/index.js`, one module with its
|
|
312
|
-
dependencies inlined and the plugin as its default export.
|
|
313
|
-
|
|
314
|
-
|
|
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).
|