Skip to content

Commit 25436e5

Browse files
committedOct 10, 2024
Implement exportActorProfile().
1 parent d529b6b commit 25436e5

12 files changed

+386
-32
lines changed
 

‎.eslintrc.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ module.exports = {
1010
rules: {
1111
'prettier/prettier': 'off',
1212
'arrow-body-style': 'off',
13-
'prefer-arrow-callback': 'off'
13+
'prefer-arrow-callback': 'off',
14+
'@typescript-eslint/strict-boolean-expressions': 'off'
1415
}
1516
}

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ dist
114114

115115
# Output
116116
dist
117+
out
117118

118119
# MacOS
119120
.DS_Store

‎README.md

+26-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ npm install
4646

4747
## Usage
4848

49-
Export an empty wallet.
49+
### Export an empty wallet.
5050

5151
```ts
5252
export() // no wallet passed in, generates an empty Universal Wallet Backup TAR file
@@ -71,6 +71,31 @@ meta:
7171
url: https://github.com/interop-alliance/wallet-export-ts
7272
```
7373

74+
### Export an ActivityPub Actor Profile
75+
76+
```js
77+
import * as fs from 'node:fs'
78+
import { exportActorProfile } from '@interop/wallet-export-ts'
79+
80+
const filename = 'out/test-export-2024-01-01.tar'
81+
const tarball = fs.createWriteStream(filename)
82+
83+
// Each of the arguments passed in is Optional
84+
const packStream = exportActorProfile({
85+
actorProfile, outbox, followers, followingAccounts, lists, bookmarks, likes,
86+
blockedAccounts, blockedDomains, mutedAccounts
87+
})
88+
89+
packStream.pipe(tarball)
90+
```
91+
then
92+
```
93+
cd out
94+
tar -xvf test-export-2024-01-01.tar
95+
```
96+
see https://codeberg.org/fediverse/fep/src/branch/main/fep/6fcd/fep-6fcd.md#activitypub-export-example
97+
for contents
98+
7499
## Contribute
75100

76101
PRs accepted.

‎package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,15 @@
3131
"./package.json": "./package.json"
3232
},
3333
"dependencies": {
34-
"tar-stream": "^3.1.7"
34+
"tar-stream": "^3.1.7",
35+
"yaml": "^2.5.1"
3536
},
3637
"devDependencies": {
3738
"@types/chai": "^4.3.10",
3839
"@types/mocha": "^10.0.4",
3940
"@types/node": "^20.9.1",
41+
"@types/streamx": "^2.9.5",
42+
"@types/tar-stream": "^3.1.3",
4043
"@typescript-eslint/eslint-plugin": "^6.11.0",
4144
"@typescript-eslint/parser": "^6.11.0",
4245
"chai": "^4.3.10",

‎src/Example.ts

-8
This file was deleted.

‎src/index.ts

+130-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,132 @@
11
/*!
2-
* Copyright (c) 2021 Interop Alliance and Dmitri Zagidulin. All rights reserved.
2+
* Copyright (c) 2024 Interop Alliance and Dmitri Zagidulin. All rights reserved.
33
*/
4-
export { Example } from './Example'
4+
import * as tar from 'tar-stream'
5+
import { type Pack } from 'tar-stream'
6+
import YAML from 'yaml'
7+
8+
export type ActorProfileOptions = {
9+
actorProfile?: any
10+
outbox?: any
11+
followers?: any
12+
followingAccounts?: any
13+
lists?: any
14+
bookmarks?: any
15+
likes?: any
16+
blockedAccounts?: any
17+
blockedDomains?: any
18+
mutedAccounts?: any
19+
}
20+
21+
export function exportActorProfile ({
22+
actorProfile, outbox, followers, followingAccounts, lists, bookmarks, likes,
23+
blockedAccounts, blockedDomains, mutedAccounts
24+
}: ActorProfileOptions): tar.Pack {
25+
const pack: Pack = tar.pack() // pack is a stream
26+
27+
const manifest: any = {
28+
// Universal Backup Container spec version
29+
'ubc-version': '0.1',
30+
meta: {
31+
createdBy: {
32+
client: {
33+
// URL to the client that generated this export file
34+
url: 'https://github.com/interop-alliance/wallet-export-ts'
35+
}
36+
}
37+
},
38+
contents: {
39+
'manifest.yml': {
40+
url: 'https://w3id.org/fep/6fcd#manifest-file'
41+
},
42+
// Directory with ActivityPub-relevant exports
43+
activitypub: {
44+
contents: {}
45+
}
46+
}
47+
}
48+
49+
pack.entry({ name: 'activitypub', type: 'directory' })
50+
51+
if (actorProfile) {
52+
// Serialized ActivityPub Actor profile
53+
manifest.contents.activitypub.contents['actor.json'] = {
54+
url: 'https://www.w3.org/TR/activitypub/#actor-objects'
55+
}
56+
pack.entry({ name: 'activitypub/actor.json' }, JSON.stringify(actorProfile, null, 2))
57+
}
58+
59+
if (outbox) {
60+
// ActivityStreams OrderedCollection representing the contents of the actor's Outbox
61+
manifest.contents.activitypub.contents['outbox.json'] = {
62+
url: 'https://www.w3.org/TR/activitystreams-core/#collections'
63+
}
64+
pack.entry({ name: 'activitypub/outbox.json' }, JSON.stringify(outbox, null, 2))
65+
}
66+
67+
if (followers) {
68+
// ActivityStreams OrderedCollection representing the actor's Followers
69+
manifest.contents.activitypub.contents['followers.json'] = {
70+
url: 'https://www.w3.org/TR/activitystreams-core/#collections'
71+
}
72+
pack.entry({ name: 'activitypub/followers.json' }, JSON.stringify(followers, null, 2))
73+
}
74+
75+
if (likes) {
76+
// ActivityStreams OrderedCollection representing Activities and Objects the actor liked
77+
manifest.contents.activitypub.contents['likes.json'] = {
78+
url: 'https://www.w3.org/TR/activitystreams-core/#collections'
79+
}
80+
pack.entry({ name: 'activitypub/likes.json' }, JSON.stringify(likes, null, 2))
81+
}
82+
83+
if (bookmarks) {
84+
// ActivityStreams OrderedCollection representing the actor's Bookmarks
85+
manifest.contents.activitypub.contents['bookmarks.json'] = {
86+
url: 'https://www.w3.org/TR/activitystreams-core/#collections'
87+
}
88+
pack.entry({ name: 'activitypub/bookmarks.json' }, JSON.stringify(bookmarks, null, 2))
89+
}
90+
91+
if (followingAccounts) {
92+
// CSV headers:
93+
// Account address, Show boosts, Notify on new posts, Languages
94+
manifest.contents.activitypub.contents['following_accounts.csv'] = {
95+
url: 'https://docs.joinmastodon.org/user/moving/#export'
96+
}
97+
pack.entry({ name: 'activitypub/following_accounts.csv' }, followingAccounts)
98+
}
99+
100+
if (lists) {
101+
manifest.contents.activitypub.contents['lists.csv'] = {
102+
url: 'https://docs.joinmastodon.org/user/moving/#export'
103+
}
104+
pack.entry({ name: 'activitypub/lists.csv' }, lists)
105+
}
106+
107+
if (blockedAccounts) {
108+
manifest.contents.activitypub.contents['blocked_accounts.csv'] = {
109+
url: 'https://docs.joinmastodon.org/user/moving/#export'
110+
}
111+
pack.entry({ name: 'activitypub/blocked_accounts.csv' }, blockedAccounts)
112+
}
113+
114+
if (blockedDomains) {
115+
manifest.contents.activitypub.contents['blocked_domains.csv'] = {
116+
url: 'https://docs.joinmastodon.org/user/moving/#export'
117+
}
118+
pack.entry({ name: 'activitypub/blocked_domains.csv' }, blockedDomains)
119+
}
120+
121+
if (mutedAccounts) {
122+
manifest.contents.activitypub.contents['muted_accounts.csv'] = {
123+
url: 'https://docs.joinmastodon.org/user/moving/#export'
124+
}
125+
pack.entry({ name: 'activitypub/muted_accounts.csv' }, mutedAccounts)
126+
}
127+
128+
pack.entry({ name: 'manifest.yaml' }, YAML.stringify(manifest))
129+
130+
return pack
131+
}
132+

‎test.js

-9
This file was deleted.

‎test/Example.spec.ts

-9
This file was deleted.

‎test/fixtures/actorProfile.ts

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Sample Mastodon profile circa Oct 2024
2+
export const actorProfile = {
3+
"@context": [
4+
"https://www.w3.org/ns/activitystreams",
5+
"https://w3id.org/security/v1",
6+
{
7+
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
8+
"toot": "http://joinmastodon.org/ns#",
9+
"featured": {
10+
"@id": "toot:featured",
11+
"@type": "@id"
12+
},
13+
"featuredTags": {
14+
"@id": "toot:featuredTags",
15+
"@type": "@id"
16+
},
17+
"alsoKnownAs": {
18+
"@id": "as:alsoKnownAs",
19+
"@type": "@id"
20+
},
21+
"movedTo": {
22+
"@id": "as:movedTo",
23+
"@type": "@id"
24+
},
25+
"schema": "http://schema.org#",
26+
"PropertyValue": "schema:PropertyValue",
27+
"value": "schema:value",
28+
"discoverable": "toot:discoverable",
29+
"Device": "toot:Device",
30+
"Ed25519Signature": "toot:Ed25519Signature",
31+
"Ed25519Key": "toot:Ed25519Key",
32+
"Curve25519Key": "toot:Curve25519Key",
33+
"EncryptedMessage": "toot:EncryptedMessage",
34+
"publicKeyBase64": "toot:publicKeyBase64",
35+
"deviceId": "toot:deviceId",
36+
"claim": {
37+
"@type": "@id",
38+
"@id": "toot:claim"
39+
},
40+
"fingerprintKey": {
41+
"@type": "@id",
42+
"@id": "toot:fingerprintKey"
43+
},
44+
"identityKey": {
45+
"@type": "@id",
46+
"@id": "toot:identityKey"
47+
},
48+
"devices": {
49+
"@type": "@id",
50+
"@id": "toot:devices"
51+
},
52+
"messageFranking": "toot:messageFranking",
53+
"messageType": "toot:messageType",
54+
"cipherText": "toot:cipherText",
55+
"suspended": "toot:suspended",
56+
"Hashtag": "as:Hashtag",
57+
"focalPoint": {
58+
"@container": "@list",
59+
"@id": "toot:focalPoint"
60+
}
61+
}
62+
],
63+
"id": "https://example.com/users/alice",
64+
"type": "Person",
65+
"following": "https://example.com/users/alice/following",
66+
"followers": "https://example.com/users/alice/followers",
67+
"inbox": "https://example.com/users/alice/inbox",
68+
"outbox": "outbox.json",
69+
"featured": "https://example.com/users/alice/collections/featured",
70+
"featuredTags": "https://example.com/users/alice/collections/tags",
71+
"preferredUsername": "alice",
72+
"name": "Alice",
73+
"summary": "<p>Profile description goes here.<br />(she/her) <a href=\"https://example.com/tags/nobot\" class=\"mention hashtag\" rel=\"tag\">#<span>nobot</span></a></p>",
74+
"url": "https://example.com/@alice",
75+
"manuallyApprovesFollowers": false,
76+
"discoverable": true,
77+
"published": "2022-11-24T00:00:00Z",
78+
"devices": "https://example.com/users/alice/collections/devices",
79+
"alsoKnownAs": [
80+
"https://alice.example"
81+
],
82+
"publicKey": {
83+
"id": "https://example.com/users/alice#main-key",
84+
"owner": "https://example.com/users/alice",
85+
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzPpH+K1JcB3fH7889Lt\nJIwV2nhU0TovHRsDT2SN3Ew/c03YxEIjICJUy3rrejNOyLL0cegspzRYQDrEIbh8\nsIxuNB7wdHajjW9KkF/yvKHKuXT9RXIB4HIXXzkdjEpVrEJgn5LnLZyyWb4ZXBPF\nyhVf0l3+OcQ5hcS7WinVAbcoLU5G3eMa7w4QV6+kEkoRzGUHMtlQMWtLePAKgWM1\nXsLFC3ZPNk/j4gvHPKWmN+hhLSoB4nIJ91dEDeg12OfpIgbnEPzFyXhopVn2GmJ8\n163omcPS5tpheMNxkkYXOmG+qzVFzCXACSXmual/sRP8z+44Z92ONKjg01+5aeMN\n+QIDAQAB\n-----END PUBLIC KEY-----\n"
86+
},
87+
"tag": [
88+
{
89+
"type": "Hashtag",
90+
"href": "https://example.com/tags/nobot",
91+
"name": "#nobot"
92+
}
93+
],
94+
"attachment": [],
95+
"endpoints": {
96+
"sharedInbox": "https://example.com/inbox"
97+
},
98+
"icon": {
99+
"type": "Image",
100+
"mediaType": "image/jpeg",
101+
"url": "avatar.jpg"
102+
},
103+
"likes": "likes.json",
104+
"bookmarks": "bookmarks.json"
105+
}

0 commit comments

Comments
 (0)
Please sign in to comment.