activitystreams-xl
Advanced tools
Comparing version 1.0.3 to 2.0.0
module.exports = error; | ||
function error(msg) { | ||
throw new Error(msg); | ||
throw new Error(msg); | ||
} |
{ | ||
"name": "activitystreams-xl", | ||
"version": "1.0.3", | ||
"version": "2.0.0", | ||
"description": "ActivityStreams 1.0 & 2.0 parsing and translation", | ||
@@ -25,2 +25,3 @@ "main": "index.js", | ||
"devDependencies": { | ||
"jsonld": "^0.4.12", | ||
"tape": "^4.6.3" | ||
@@ -27,0 +28,0 @@ }, |
const tape = require('tape'); | ||
const { parse } = require('../xml'); | ||
const jsonld = require('jsonld').promises; | ||
const entry = `<?xml version="1.0"?> | ||
const follow = `<?xml version="1.0"?> | ||
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:media="http://purl.org/syndication/atommedia" xmlns:ostatus="http://ostatus.org/schema/1.0" xmlns:mastodon="http://mastodon.social/schema/1.0"> | ||
@@ -41,6 +42,59 @@ <id>tag:test2.yayforqueers.net,2017-05-13:objectId=16:objectType=Follow</id> | ||
tape.test('parse xml', t => { | ||
const obj = parse(entry); | ||
t.ok(obj); | ||
t.end(); | ||
const unfollow = `<?xml version="1.0"?> | ||
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:media="http://purl.org/syndication/atommedia" xmlns:ostatus="http://ostatus.org/schema/1.0" xmlns:mastodon="http://mastodon.social/schema/1.0"> | ||
<id>tag:test2.yayforqueers.net,2017-05-13:objectId=22:objectType=Follow</id> | ||
<title>aredridel is no longer following test2@test.yayforqueers.net</title> | ||
<content type="html">aredridel is no longer following test2@test.yayforqueers.net</content> | ||
<author> | ||
<id>http://test2.yayforqueers.net/users/aredridel</id> | ||
<activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type> | ||
<uri>http://test2.yayforqueers.net/users/aredridel</uri> | ||
<name>aredridel</name> | ||
<email>aredridel@test2.yayforqueers.net</email> | ||
<summary type="html"><p>Testing 123</p></summary> | ||
<link rel="alternate" type="text/html" href="http://test2.yayforqueers.net/@aredridel"/> | ||
<link rel="avatar" type="" media:width="120" media:height="120" href="http://test2.yayforqueers.net/avatars/original/missing.png"/> | ||
<link rel="header" type="" media:width="700" media:height="335" href="http://test2.yayforqueers.net/headers/original/missing.png"/> | ||
<poco:preferredUsername>aredridel</poco:preferredUsername> | ||
<poco:displayName>Aria</poco:displayName> | ||
<poco:note>Testing 123</poco:note> | ||
<mastodon:scope>public</mastodon:scope> | ||
</author> | ||
<activity:object-type>http://activitystrea.ms/schema/1.0/activity</activity:object-type> | ||
<activity:verb>http://ostatus.org/schema/1.0/unfollow</activity:verb> | ||
<activity:object> | ||
<id>https://test.yayforqueers.net/users/test2</id> | ||
<activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type> | ||
<uri>https://test.yayforqueers.net/users/test2</uri> | ||
<name>test2</name> | ||
<email>test2@test.yayforqueers.net</email> | ||
<link rel="alternate" type="text/html" href="https://test.yayforqueers.net/@test2"/> | ||
<link rel="avatar" type="image/png" media:width="120" media:height="120" href="http://test2.yayforqueers.net/system/accounts/avatars/000/000/002/original/media.png?1493850343"/> | ||
<link rel="header" type="" media:width="700" media:height="335" href="http://test2.yayforqueers.net/headers/original/missing.png"/> | ||
<poco:preferredUsername>test2</poco:preferredUsername> | ||
<poco:displayName>WIP2</poco:displayName> | ||
<mastodon:scope>public</mastodon:scope> | ||
</activity:object> | ||
</entry>`; | ||
tape.test('parse follow', t => { | ||
return parse(follow).then(obj => { | ||
t.ok(obj); | ||
t.equal(obj.type, 'Follow'); | ||
return jsonld.expand(obj) | ||
}).then(e => { | ||
t.equal(e[0]['https://www.w3.org/ns/activitystreams#content'][0]['@value'], 'aredridel started following test2@test.yayforqueers.net'); | ||
}) | ||
.catch(t.error) | ||
.then(t.end) | ||
}); | ||
tape.test('parse unfolllow', t => { | ||
return parse(unfollow).then(obj => { | ||
t.ok(obj); | ||
t.equal(obj.type, 'Undo'); | ||
t.equal(obj.object.type, 'Follow'); | ||
}) | ||
.catch(t.error) | ||
.then(t.end); | ||
}); |
173
xml.js
@@ -6,46 +6,139 @@ const ltx = require('ltx'); | ||
const POCONS = "http://portablecontacts.net/spec/1.0"; | ||
const MASTODONNS = "http://mastodon.social/schema/1.0"; | ||
//const MASTODONNS = "http://mastodon.social/schema/1.0"; | ||
const url = require('url'); | ||
const error = require('./error'); | ||
//const error = require('./error'); | ||
const as2tables = require('./tables'); | ||
const jsonld = require('jsonld').promises; | ||
module.exports = { | ||
parse | ||
parse | ||
}; | ||
function parse(xml) { | ||
console.warn(xml); | ||
const atom = ltx.parse(xml); | ||
if (atom.is('feed', ATOMNS)) { | ||
return {items: atom.children.map(entry2as2)}; | ||
} else if (atom.is ('entry', ATOMNS) || atom.is('object', ACTIVITYNS) || atom.is('target', ACTIVITYNS)) { | ||
return {items: [ entry2as2(atom) ] }; | ||
} else { | ||
throw new Error(`unrecognized type of element ${atom.name})`); | ||
} | ||
const atom = ltx.parse(xml); | ||
if (atom.is('feed', ATOMNS)) { | ||
return atom.children.map(entry2as2).then(entries => ({ | ||
items: entries | ||
})); | ||
} else if (atom.is('entry', ATOMNS) || atom.is('object', ACTIVITYNS) || atom.is('target', ACTIVITYNS)) { | ||
return Promise.resolve(entry2as2(atom)); | ||
} else { | ||
return Promise.reject(new Error(`unrecognized type of element ${atom.name})`)); | ||
} | ||
} | ||
function entry2as2(el) { | ||
const type = typeForIRI(getText(el, 'verb', ACTIVITYNS) || getText(el, 'object-type', ACTIVITYNS)) || 'Add'; | ||
const id = getText(el, 'id', ATOMNS); | ||
// if is implicit, return implicit2as2 | ||
// else | ||
const as1 = entry2as1(el); | ||
return jsonld.compact(addContext(as1ToAS2(as1)), 'https://www.w3.org/ns/activitystreams'); | ||
} | ||
const out = { | ||
id, | ||
title: getText(el, 'title', ATOMNS), | ||
content: getText(el, 'content', ATOMNS), | ||
actor: elementToAs2(el.getChild('author', ATOMNS)), | ||
type, | ||
object: elementToAs2(el.getChild('object', ACTIVITYNS)), | ||
target: elementToAs2(el.getChild('target', ACTIVITYNS)), | ||
}; | ||
function as1ToAS2(obj) { | ||
if (!obj) return; | ||
const type = typeForIRI(obj.verb || obj.objectType) || 'https://www.w3.org/ns/activitystreams#Add'; | ||
const object = as1ToAS2(obj.object); | ||
const actor = as1ToAS2(obj.actor); | ||
const out = Object.assign({}, obj, { | ||
type, | ||
actor, | ||
object | ||
}); | ||
delete out.verb; | ||
delete out.objectType; | ||
if (type == 'http://ostatus.org/schema/1.0/unfollow') { | ||
return synthesizeUndoRecord(Object.assign(out, { | ||
type: 'https://www.w3.org/ns/activitystreams#Follow' | ||
})); | ||
} else { | ||
return out; | ||
} | ||
} | ||
function addContext(obj) { | ||
return Object.assign({ | ||
'@context': 'https://www.w3.org/ns/activitystreams' | ||
}, obj); | ||
} | ||
function entry2as1(el) { | ||
if (!el) return; | ||
const verb = getText(el, 'verb', ACTIVITYNS); | ||
const objectType = getText(el, 'object-type', ACTIVITYNS); | ||
const out = {}; | ||
Object.assign(out, el.children.reduce((obj, c) => { | ||
if (typeof c == 'string') return obj; | ||
const prop = canonicalPropertyName(c.getNS(), c.getName()); | ||
const value = getConverter(prop)(c); | ||
obj[prop] = value; | ||
return obj; | ||
}, {})) | ||
Object.assign(out, { | ||
actor: entry2as1(el.getChild('author', ATOMNS)), | ||
verb, | ||
objectType, | ||
object: entry2as1(el.getChild('object', ACTIVITYNS)), | ||
target: entry2as1(el.getChild('target', ACTIVITYNS)), | ||
}); | ||
return out; | ||
} | ||
function canonicalPropertyName(ns, name) { | ||
if (ns == 'http://www.w3.org/2005/Atom') { | ||
if (name == 'author') { | ||
return 'actor'; | ||
} else if (name == 'title') { | ||
return 'summary'; | ||
} else { | ||
return name; | ||
} | ||
} else if (ns == ACTIVITYNS) { | ||
if (name == 'object-type') { | ||
return 'objectType'; | ||
} else { | ||
return name; | ||
} | ||
} else if (ns == POCONS) { | ||
return name; | ||
} else { | ||
return ns + name; | ||
} | ||
} | ||
function getConverter(prop) { | ||
switch (prop) { | ||
case 'actor': | ||
case 'object': | ||
case 'target': | ||
return entry2as1; | ||
case 'link': | ||
return extractLink; | ||
default: | ||
return extractText; | ||
} | ||
} | ||
function extractLink(el) { | ||
// FIXME: media attributes etc. | ||
return Object.assign({}, el.attrs, { | ||
type: 'https://www.w3.org/ns/activitystreams#Link' | ||
}); | ||
} | ||
function extractText(child) { | ||
return child.getText(); | ||
} | ||
function getText(el, name, ns) { | ||
const sub = el.getChild(name, ns) | ||
if (sub) return sub.getText(); | ||
const sub = el.getChild(name, ns) | ||
if (sub) return sub.getText(); | ||
} | ||
/*FIXME | ||
function elementToAs2(el) { | ||
@@ -57,4 +150,5 @@ // TODO: figure out if this is an implicit or explicit event | ||
return { | ||
id: getText(el, 'id', ATOMNS), | ||
const out = { | ||
'@type': objectType, | ||
'@id': getText(el, 'id', ATOMNS), | ||
uri: getText(el, 'uri', ATOMNS), | ||
@@ -68,14 +162,21 @@ name: getText(el, 'name', POCONS), | ||
'poco:preferredUsername': getText(el, 'preferredUsername', POCONS), | ||
objectType, | ||
}; | ||
} | ||
*/ | ||
function synthesizeUndoRecord(object) { | ||
return { | ||
type: 'https://www.w3.org/ns/activitystreams#Undo', | ||
object | ||
} | ||
} | ||
function typeForIRI(iri) { | ||
iri = url.resolve('http://activitystrea.ms/schema/1.0/', iri); | ||
if (as2tables.legacyTypes[iri]) iri = as2tables.legacyTypes[iri]; | ||
console.warn(iri); | ||
return as2tables.byURI[iri] ? as2tables.byURI[iri].name : null; | ||
// TODO: handle ostatus URLs. | ||
// TODO: handle as 1.0 URLs. | ||
iri = url.resolve('http://activitystrea.ms/schema/1.0/', iri); | ||
if (as2tables.legacyTypes[iri]) | ||
iri = as2tables.legacyTypes[iri]; | ||
return as2tables.byURI[iri] ? as2tables.byURI[iri].uri : iri; | ||
// TODO: handle ostatus URLs. | ||
} |
102070
2872
2