Skip to content

Commit

Permalink
Implement exportActorProfile().
Browse files Browse the repository at this point in the history
  • Loading branch information
dmitrizagidulin committed Oct 10, 2024
1 parent d529b6b commit 25436e5
Show file tree
Hide file tree
Showing 12 changed files with 386 additions and 32 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module.exports = {
rules: {
'prettier/prettier': 'off',
'arrow-body-style': 'off',
'prefer-arrow-callback': 'off'
'prefer-arrow-callback': 'off',
'@typescript-eslint/strict-boolean-expressions': 'off'
}
}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ dist

# Output
dist
out

# MacOS
.DS_Store
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ npm install

## Usage

Export an empty wallet.
### Export an empty wallet.

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

### Export an ActivityPub Actor Profile

```js
import * as fs from 'node:fs'
import { exportActorProfile } from '@interop/wallet-export-ts'

const filename = 'out/test-export-2024-01-01.tar'
const tarball = fs.createWriteStream(filename)

// Each of the arguments passed in is Optional
const packStream = exportActorProfile({
actorProfile, outbox, followers, followingAccounts, lists, bookmarks, likes,
blockedAccounts, blockedDomains, mutedAccounts
})

packStream.pipe(tarball)
```
then
```
cd out
tar -xvf test-export-2024-01-01.tar
```
see https://codeberg.org/fediverse/fep/src/branch/main/fep/6fcd/fep-6fcd.md#activitypub-export-example
for contents

## Contribute

PRs accepted.
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,15 @@
"./package.json": "./package.json"
},
"dependencies": {
"tar-stream": "^3.1.7"
"tar-stream": "^3.1.7",
"yaml": "^2.5.1"
},
"devDependencies": {
"@types/chai": "^4.3.10",
"@types/mocha": "^10.0.4",
"@types/node": "^20.9.1",
"@types/streamx": "^2.9.5",
"@types/tar-stream": "^3.1.3",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"chai": "^4.3.10",
Expand Down
8 changes: 0 additions & 8 deletions src/Example.ts

This file was deleted.

132 changes: 130 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,132 @@
/*!
* Copyright (c) 2021 Interop Alliance and Dmitri Zagidulin. All rights reserved.
* Copyright (c) 2024 Interop Alliance and Dmitri Zagidulin. All rights reserved.
*/
export { Example } from './Example'
import * as tar from 'tar-stream'
import { type Pack } from 'tar-stream'
import YAML from 'yaml'

export type ActorProfileOptions = {
actorProfile?: any
outbox?: any
followers?: any
followingAccounts?: any
lists?: any
bookmarks?: any
likes?: any
blockedAccounts?: any
blockedDomains?: any
mutedAccounts?: any
}

export function exportActorProfile ({
actorProfile, outbox, followers, followingAccounts, lists, bookmarks, likes,
blockedAccounts, blockedDomains, mutedAccounts
}: ActorProfileOptions): tar.Pack {
const pack: Pack = tar.pack() // pack is a stream

const manifest: any = {
// Universal Backup Container spec version
'ubc-version': '0.1',
meta: {
createdBy: {
client: {
// URL to the client that generated this export file
url: 'https://github.com/interop-alliance/wallet-export-ts'
}
}
},
contents: {
'manifest.yml': {
url: 'https://w3id.org/fep/6fcd#manifest-file'
},
// Directory with ActivityPub-relevant exports
activitypub: {
contents: {}
}
}
}

pack.entry({ name: 'activitypub', type: 'directory' })

if (actorProfile) {
// Serialized ActivityPub Actor profile
manifest.contents.activitypub.contents['actor.json'] = {
url: 'https://www.w3.org/TR/activitypub/#actor-objects'
}
pack.entry({ name: 'activitypub/actor.json' }, JSON.stringify(actorProfile, null, 2))
}

if (outbox) {
// ActivityStreams OrderedCollection representing the contents of the actor's Outbox
manifest.contents.activitypub.contents['outbox.json'] = {
url: 'https://www.w3.org/TR/activitystreams-core/#collections'
}
pack.entry({ name: 'activitypub/outbox.json' }, JSON.stringify(outbox, null, 2))
}

if (followers) {
// ActivityStreams OrderedCollection representing the actor's Followers
manifest.contents.activitypub.contents['followers.json'] = {
url: 'https://www.w3.org/TR/activitystreams-core/#collections'
}
pack.entry({ name: 'activitypub/followers.json' }, JSON.stringify(followers, null, 2))
}

if (likes) {
// ActivityStreams OrderedCollection representing Activities and Objects the actor liked
manifest.contents.activitypub.contents['likes.json'] = {
url: 'https://www.w3.org/TR/activitystreams-core/#collections'
}
pack.entry({ name: 'activitypub/likes.json' }, JSON.stringify(likes, null, 2))
}

if (bookmarks) {
// ActivityStreams OrderedCollection representing the actor's Bookmarks
manifest.contents.activitypub.contents['bookmarks.json'] = {
url: 'https://www.w3.org/TR/activitystreams-core/#collections'
}
pack.entry({ name: 'activitypub/bookmarks.json' }, JSON.stringify(bookmarks, null, 2))
}

if (followingAccounts) {
// CSV headers:
// Account address, Show boosts, Notify on new posts, Languages
manifest.contents.activitypub.contents['following_accounts.csv'] = {
url: 'https://docs.joinmastodon.org/user/moving/#export'
}
pack.entry({ name: 'activitypub/following_accounts.csv' }, followingAccounts)
}

if (lists) {
manifest.contents.activitypub.contents['lists.csv'] = {
url: 'https://docs.joinmastodon.org/user/moving/#export'
}
pack.entry({ name: 'activitypub/lists.csv' }, lists)
}

if (blockedAccounts) {
manifest.contents.activitypub.contents['blocked_accounts.csv'] = {
url: 'https://docs.joinmastodon.org/user/moving/#export'
}
pack.entry({ name: 'activitypub/blocked_accounts.csv' }, blockedAccounts)
}

if (blockedDomains) {
manifest.contents.activitypub.contents['blocked_domains.csv'] = {
url: 'https://docs.joinmastodon.org/user/moving/#export'
}
pack.entry({ name: 'activitypub/blocked_domains.csv' }, blockedDomains)
}

if (mutedAccounts) {
manifest.contents.activitypub.contents['muted_accounts.csv'] = {
url: 'https://docs.joinmastodon.org/user/moving/#export'
}
pack.entry({ name: 'activitypub/muted_accounts.csv' }, mutedAccounts)
}

pack.entry({ name: 'manifest.yaml' }, YAML.stringify(manifest))

return pack
}

9 changes: 0 additions & 9 deletions test.js

This file was deleted.

9 changes: 0 additions & 9 deletions test/Example.spec.ts

This file was deleted.

105 changes: 105 additions & 0 deletions test/fixtures/actorProfile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Sample Mastodon profile circa Oct 2024
export const actorProfile = {
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"toot": "http://joinmastodon.org/ns#",
"featured": {
"@id": "toot:featured",
"@type": "@id"
},
"featuredTags": {
"@id": "toot:featuredTags",
"@type": "@id"
},
"alsoKnownAs": {
"@id": "as:alsoKnownAs",
"@type": "@id"
},
"movedTo": {
"@id": "as:movedTo",
"@type": "@id"
},
"schema": "http://schema.org#",
"PropertyValue": "schema:PropertyValue",
"value": "schema:value",
"discoverable": "toot:discoverable",
"Device": "toot:Device",
"Ed25519Signature": "toot:Ed25519Signature",
"Ed25519Key": "toot:Ed25519Key",
"Curve25519Key": "toot:Curve25519Key",
"EncryptedMessage": "toot:EncryptedMessage",
"publicKeyBase64": "toot:publicKeyBase64",
"deviceId": "toot:deviceId",
"claim": {
"@type": "@id",
"@id": "toot:claim"
},
"fingerprintKey": {
"@type": "@id",
"@id": "toot:fingerprintKey"
},
"identityKey": {
"@type": "@id",
"@id": "toot:identityKey"
},
"devices": {
"@type": "@id",
"@id": "toot:devices"
},
"messageFranking": "toot:messageFranking",
"messageType": "toot:messageType",
"cipherText": "toot:cipherText",
"suspended": "toot:suspended",
"Hashtag": "as:Hashtag",
"focalPoint": {
"@container": "@list",
"@id": "toot:focalPoint"
}
}
],
"id": "https://example.com/users/alice",
"type": "Person",
"following": "https://example.com/users/alice/following",
"followers": "https://example.com/users/alice/followers",
"inbox": "https://example.com/users/alice/inbox",
"outbox": "outbox.json",
"featured": "https://example.com/users/alice/collections/featured",
"featuredTags": "https://example.com/users/alice/collections/tags",
"preferredUsername": "alice",
"name": "Alice",
"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>",
"url": "https://example.com/@alice",
"manuallyApprovesFollowers": false,
"discoverable": true,
"published": "2022-11-24T00:00:00Z",
"devices": "https://example.com/users/alice/collections/devices",
"alsoKnownAs": [
"https://alice.example"
],
"publicKey": {
"id": "https://example.com/users/alice#main-key",
"owner": "https://example.com/users/alice",
"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"
},
"tag": [
{
"type": "Hashtag",
"href": "https://example.com/tags/nobot",
"name": "#nobot"
}
],
"attachment": [],
"endpoints": {
"sharedInbox": "https://example.com/inbox"
},
"icon": {
"type": "Image",
"mediaType": "image/jpeg",
"url": "avatar.jpg"
},
"likes": "likes.json",
"bookmarks": "bookmarks.json"
}
Loading

0 comments on commit 25436e5

Please sign in to comment.