diff --git a/granary/as2.py b/granary/as2.py
index 335d1e62..f8869679 100644
--- a/granary/as2.py
+++ b/granary/as2.py
@@ -25,6 +25,7 @@ def _invert(d):
'collection': 'Collection',
'comment': 'Note',
'event': 'Event',
+ 'hashtag': 'Tag', # not in AS2 spec; needed for correct round trip conversion
'image': 'Image',
'note': 'Note',
'person': 'Person',
@@ -80,6 +81,10 @@ def all_from_as1(field, type=None):
for elem in util.pop_list(obj, field)]
images = all_from_as1('image', type='Image')
+ inner_objs = all_from_as1('object')
+ if len(inner_objs) == 1:
+ inner_objs = inner_objs[0]
+
obj.update({
'type': type,
'name': obj.pop('displayName', None),
@@ -89,7 +94,7 @@ def all_from_as1(field, type=None):
'image': images,
'inReplyTo': util.trim_nulls([orig.get('id') or orig.get('url')
for orig in obj.get('inReplyTo', [])]),
- 'object': from_as1(obj.get('object'), context=None),
+ 'object': inner_objs,
'tag': all_from_as1('tags')
})
@@ -158,6 +163,10 @@ def all_to_as1(field):
if as1_img not in images:
images.append(as1_img)
+ inner_objs = all_to_as1('object')
+ if len(inner_objs) == 1:
+ inner_objs = inner_objs[0]
+
obj.update({
'displayName': obj.pop('name', None),
'actor': to_as1(obj.get('actor')),
@@ -165,7 +174,7 @@ def all_to_as1(field):
'image': images,
'inReplyTo': [url_or_as1(orig) for orig in util.get_list(obj, 'inReplyTo')],
'location': url_or_as1(obj.get('location')),
- 'object': to_as1(obj.get('object')),
+ 'object': inner_objs,
'tags': all_to_as1('tag'),
})
diff --git a/granary/github.py b/granary/github.py
index 140acd5c..c576bf24 100644
--- a/granary/github.py
+++ b/granary/github.py
@@ -648,9 +648,9 @@ def _create(self, obj, preview=None, include_link=source.OMIT_LINK,
else:
resp = self.rest(REST_API_ISSUE_LABELS % (owner, repo, number), labels).json()
return source.creation_result({
- 'id': resp.get('id'),
'url': base_url,
'type': 'tag',
+ 'tags': labels,
})
else: # new issue
diff --git a/granary/microformats2.py b/granary/microformats2.py
index e06fa223..e328b79e 100644
--- a/granary/microformats2.py
+++ b/granary/microformats2.py
@@ -46,7 +46,7 @@
$event_times
$location
$categories
-$in_reply_tos
+$links
$children
$comments
@@ -59,7 +59,7 @@
$photos
""")
-IN_REPLY_TO = string.Template(' ')
+LINK = string.Template(' ')
AS_TO_MF2_TYPE = {
'event': ['h-event'],
'person': ['h-card'],
@@ -76,6 +76,7 @@
'reply': ('comment', None),
'repost': ('activity', 'share'),
'rsvp': ('activity', None), # json_to_object() will generate verb from rsvp
+ 'tag': ('activity', 'tag'),
}
# ISO 6709 location string. http://en.wikipedia.org/wiki/ISO_6709
ISO_6709_RE = re.compile(r'^([-+][0-9.]+)([-+][0-9.]+).*/$')
@@ -198,8 +199,7 @@ def object_to_json(obj, trim_nulls=True, entry_class='h-entry',
summary = primary.get('summary')
author = obj.get('author', obj.get('actor', {}))
- in_reply_tos = obj.get(
- 'inReplyTo', obj.get('context', {}).get('inReplyTo', []))
+ in_reply_tos = obj.get('inReplyTo', obj.get('context', {}).get('inReplyTo', []))
is_rsvp = obj_type in ('rsvp-yes', 'rsvp-no', 'rsvp-maybe')
if (is_rsvp or obj_type == 'react') and obj.get('object'):
objs = obj['object']
@@ -253,13 +253,18 @@ def object_to_json(obj, trim_nulls=True, entry_class='h-entry',
}
# hashtags and person tags
+ if obj_type == 'tag':
+ ret['properties']['tag-of'] = util.get_urls(obj, 'target')
+
tags = obj.get('tags', []) or get_first(obj, 'object', {}).get('tags', [])
+ if not tags and obj_type == 'tag':
+ tags = util.get_list(obj, 'object')
ret['properties']['category'] = []
for tag in tags:
if tag.get('objectType') == 'person':
ret['properties']['category'].append(
object_to_json(tag, entry_class='u-category h-card'))
- elif tag.get('objectType') == 'hashtag':
+ elif tag.get('objectType') == 'hashtag' or obj_type == 'tag':
name = tag.get('displayName')
if name:
ret['properties']['category'].append(name)
@@ -357,6 +362,10 @@ def fetch(url):
mf2_types = mf2.get('type') or []
if 'h-geo' in mf2_types or 'p-location' in mf2_types:
mf2_type = 'location'
+ elif 'tag-of' in props:
+ # TODO: remove once this is in mf2util
+ # https://github.com/kylewm/mf2util/issues/18
+ mf2_type = 'tag'
else:
# mf2 'photo' type is a note or article *with* a photo, but AS 'photo' type
# *is* a photo. so, special case photo type to fall through to underlying
@@ -449,6 +458,10 @@ def absolute_urls(prop):
'object': objects[0] if len(objects) == 1 else objects,
'actor': author,
})
+ if as_verb == 'tag':
+ obj['target'] = {'url': prop['tag-of']}
+ assert not obj.get('object')
+ obj['object'] = obj.pop('tags')
else:
obj.update({
'inReplyTo': [{'url': url} for url in in_reply_tos],
@@ -555,8 +568,11 @@ def json_to_html(obj, parent_props=None):
return hcard_to_html(obj, parent_props)
props = copy.copy(obj.get('properties', {}))
- in_reply_tos = '\n'.join(IN_REPLY_TO.substitute(url=url)
- for url in get_string_urls(props.get('in-reply-to', [])))
+
+ links = []
+ for prop in 'in-reply-to', 'tag-of':
+ links.extend(LINK.substitute(cls=prop, url=url)
+ for url in get_string_urls(props.get(prop, [])))
prop = first_props(props)
prop.setdefault('uid', '')
@@ -663,7 +679,7 @@ def json_to_html(obj, parent_props=None):
location=hcard_to_html(location, ['p-location']),
categories='\n'.join(people + tags),
attachments='\n'.join(attachments),
- in_reply_tos=in_reply_tos,
+ links='\n'.join(links),
invitees='\n'.join([hcard_to_html(i, ['p-invitee'])
for i in props.get('invitee', [])]),
content=content_html,
diff --git a/granary/test/test_github.py b/granary/test/test_github.py
index bfb231b4..55310464 100644
--- a/granary/test/test_github.py
+++ b/granary/test/test_github.py
@@ -972,9 +972,9 @@ def test_create_add_label(self):
result = self.gh.create(TAG_ACTIVITY)
self.assert_equals({
- 'id': 'DEF456',
'url': 'https://github.com/foo/bar/issues/456',
'type': 'tag',
+ 'tags': ['one'],
}, result.content, result)
def test_preview_add_label(self):
diff --git a/granary/test/testdata/tag_of.as.json b/granary/test/testdata/tag_of.as.json
new file mode 100644
index 00000000..f2c3d207
--- /dev/null
+++ b/granary/test/testdata/tag_of.as.json
@@ -0,0 +1,12 @@
+{
+ "objectType": "activity",
+ "verb": "tag",
+ "object": [{
+ "objectType": "hashtag",
+ "displayName": "one"
+ }, {
+ "objectType": "hashtag",
+ "displayName": "two"
+ }],
+ "target": {"url": "https://github.com/foo/bar/issues/456"}
+}
diff --git a/granary/test/testdata/tag_of.as2.json b/granary/test/testdata/tag_of.as2.json
new file mode 100644
index 00000000..82736155
--- /dev/null
+++ b/granary/test/testdata/tag_of.as2.json
@@ -0,0 +1,9 @@
+{
+ "@context": "https://www.w3.org/ns/activitystreams",
+ "type": "Add",
+ "object": [
+ {"name": "one", "type": "Tag"},
+ {"name": "two", "type": "Tag"}
+ ],
+ "target": {"url": "https://github.com/foo/bar/issues/456"}
+}
diff --git a/granary/test/testdata/tag_of.mf2.html b/granary/test/testdata/tag_of.mf2.html
new file mode 100644
index 00000000..6d4ae232
--- /dev/null
+++ b/granary/test/testdata/tag_of.mf2.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+ one
+ two
+
+
diff --git a/granary/test/testdata/tag_of.mf2.json b/granary/test/testdata/tag_of.mf2.json
new file mode 100644
index 00000000..c1a8a38a
--- /dev/null
+++ b/granary/test/testdata/tag_of.mf2.json
@@ -0,0 +1,10 @@
+{
+ "type": ["h-entry"],
+ "properties": {
+ "category": [
+ "one",
+ "two"
+ ],
+ "tag-of": ["https://github.com/foo/bar/issues/456"]
+ }
+}