Skip to content

Commit

Permalink
Merge pull request #5 from skpy/type_discovery
Browse files Browse the repository at this point in the history
Support more indieweb post types
  • Loading branch information
skpy authored May 24, 2018
2 parents f6a1ea6 + 10f717e commit 9f6d8c2
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 62 deletions.
24 changes: 24 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# CHANGELOG

* Thu May 24 2018 Scott Merrill <[email protected]> - 1.1.0
- support more indieweb post types than just reposts and replies
- better timezone handling
- support configurable token endpoints
- reposts and replies can interact with their source URLs
- perform post type discovery
- use "tweet_mode=extended" to get full tweet text
- add Twitter silo support
- handle media uploads more fully


* Fri Apr 20 2018 Scott Merrill <[email protected]> - 1.0.0
- 1.0 release
- support most of the Micropub spec
- tested against micropub.rocks
- use "name" property as article titles

* Fri Apr 13 2018 Scott Merrill <[email protected]> - 0.9
- handle all content and media actions

* Tue Mar 27 2018 Scott Merrill <[email protected]> - 0.0.1
- initial commit
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ Content you create can be syndicated to external services. Right now, only Twitt

Each syndication target is required to have configuration declared in the `syndication` array in `config.php`. Then, each syndication target should have a function `syndication_<target>`, where <target> matches the name of the array key in `config.php`. Each such function is expected to return the URL of the syndicated copy of this post, which will be added to the front matter of the post.

### Replies and Reposts
Replies and reposts are [silo](https://indieweb.org/silo)-aware. Right now, the only supported silo is Twitter. If the source of a reply or repost is a Tweet, the original tweet will be retreived, and stored in the front matter of the post. Your theme may then elect to use this as needed. In this way, we can preserve historical context of your activities, and allow you to display referenced data as you need.
### Source URLs
Replies, reposts, bookmarks, etc all define a source URL. This server can interact with those sources on a per-target basis. Right now, the only supported source is Twitter. If the source of a reply, repost, or bookmark is a Tweet, the original tweet will be retreived, and stored in the front matter of the post. Your theme may then elect to use this as needed. In this way, we can preserve historical context of your activities, and allow you to display referenced data as you need.

Additional silos can be added, much like syndication. To define a new silo, create two new functions that match the format `<silo_domain_name>_in_reply_to` or `<silo_domain_name>_repost_of`. Convert all dots in the domain name to underscores. For example, the Twitter silo uses `twitter_com_in_reply_to` and `twitter_com_repost_of`.
Additional sources can be added, much like syndication. To define a new source, create a new function that matches the format `<post_type>_<source_domain_name>`. Convert all dots in the domain name to underscores. For example, the Twitter source functions use `in_reply_to_twitter_com` and `repost_of_twitter_com` and `bookmark_of_twitter_com`.

The Twitter silo also defines `m_twitter_com_in_reply_to` and `m_twitter_repost_of`, which are simple wrappers to ensure that this functionality works when using mobile-friendly URLs.
The Twitter source also defines `<post_type>_m_twitter_com`, which are simple wrappers to ensure that this functionality works when using mobile-friendly URLs.

See `inc/twitter.php` for the implementation details.

Expand Down
6 changes: 5 additions & 1 deletion config.php.sample
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,12 @@ $config = array(
# different types of content may have different paths.
# by default, entries are in the root of the /content/ directory, so
# are not included here. Notes are in the /note/ directory.
# Reposts and replies are **usually** notes, so stick them in /note/, too.
'content_paths' => array(
'note' => 'note/' . date('Y/m/d/'),
'note' => 'note/' . date('Y/m/d/'),
'in-reply-to' => 'note/' . date('Y/m/d/'),
'repost-of' => 'note/' . date('Y/m/d/'),
'bookmark-of' => 'link/',
),

# whether or not to copy uploaded files to the source /static/ directory.
Expand Down
106 changes: 61 additions & 45 deletions inc/content.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,24 +91,47 @@ function normalize_properties($properties) {
return $props;
}

function reply_or_repost($properties, $content) {
# a post is either a reply OR a repost OR neither; but never more than one.
# so we can safely loop through each of repost and reply and update
# the properties and content as needed.
foreach ( ['repost-of', 'in-reply-to'] as $type ) {
# this function is a router to other functions that can operate on the source
# URLs of reposts, replies, bookmarks, etc.
# $type = the indieweb type (https://indieweb.org/post-type-discovery)
# $properties = array of front-matter properties for this post
# $content = the content of this post (which may be an empty string)
#
function posttype_source_function($posttype, $properties, $content) {
# replace all hyphens with underscores, for later use
$type = str_replace('-', '_', $posttype);
# get the domain of the site to which we are replying, and convert
# all dots to underscores.
$target = str_replace('.', '_', parse_url($properties[$posttype], PHP_URL_HOST));
# if a function exists for this type + target combo, call it
if (function_exists("${type}_${target}")) {
list($properties, $content) = call_user_func("${type}_${target}", $properties, $content);
}
return [$properties, $content];
}

# this function accepts the properties of a post and
# tries to perform post type discovery according to
# https://indieweb.org/post-type-discovery
# returns the MF2 post type
function post_type_discovery($properties) {
$vocab = array('rsvp',
'in-reply-to',
'repost-of',
'like-of',
'bookmark-of',
'photo');
foreach ($vocab as $type) {
if (isset($properties[$type])) {
# replace all hyphens with underscores, for later use
$t = str_replace('-', '_', $type);
# get the domain of the site to which we are replying, and convert
# all dots to underscores.
$target = str_replace('.', '_', parse_url($properties[$type], PHP_URL_HOST));
# if a function exists for this type + target combo, call it
if (function_exists("${target}_${t}")) {
list($properties, $content) = call_user_func("${target}_${t}", $properties, $content);
}
return $type;
}
}
return [$properties, $content];
# articles have titles, which Micropub defines as "name"
if (isset($properties['name'])) {
return 'article';
}
# no other match? Must be a note.
return 'note';
}

# given an array of front matter and body content, return a full post
Expand Down Expand Up @@ -197,8 +220,6 @@ function create($request, $photos = []) {
global $config;

$mf2 = $request->toMf2();
# grab the type of this content, less the "h-" prefix
$type = substr($mf2['type'][0], 2);
# make a more normal PHP array from the MF2 JSON array
$properties = normalize_properties($mf2['properties']);

Expand All @@ -217,25 +238,27 @@ function create($request, $photos = []) {
# ensure that the properties array doesn't contain 'content'
unset($properties['content']);

# https://indieweb.org/post-type-discovery describes how to discern
# what type of post this is. We'll start with the assumption that
# everything is an article, and then revise as we discover otherwise.
$properties['posttype'] = 'article';

# figure out if this is a reply or a repost. Invoke silo-specific
# methods to obtain source content, and alter this post's properties
# accordingly.
list($properties, $content) = reply_or_repost($properties, $content);

if (!empty($photos)) {
# add uploaded photos to the front matter.
if (!isset($properties['photo'])) {
$properties['photo'] = $photos;
} else {
array_merge($properties['photo'], $photos);
$properties['photo'] = array_merge($properties['photo'], $photos);
}
}

# figure out what kind of post this is.
$properties['posttype'] = post_type_discovery($properties);

# invoke any source-specific functions for this post type.
# articles, notes, and photos don't really have "sources", other than
# their own content.
# replies, reposts, likes, bookmarks, etc, should reference source URLs
# and may interact with those sources here.
if (! in_array($properties['posttype'], ['article', 'note', 'photo'])) {
list($properties, $content) = posttype_source_function($properties['posttype'], $properties, $content);
}

# all items need a date
if (!isset($properties['date'])) {
$properties['date'] = date('Y-m-d H:m:s');
Expand All @@ -253,23 +276,16 @@ function create($request, $photos = []) {
$properties['published'] = true;
}

if ($type == 'entry') {
# we need either a title, or a slug.
# NOTE: MF2 defines "name" as the title value.
if (!isset($properties['name']) && !isset($properties['slug'])) {
# entries with neither a title nor a slug are "notes".
$type = 'note';
# We will assign this a slug.
$properties['slug'] = date('Hms');
if ($properties['posttype'] == 'article' ) {
# this is not a repost or a reply, so it must be a note.
$properties['posttype'] = 'note';
}
}
# we need either a title, or a slug.
# NOTE: MF2 defines "name" as the title value.
if (!isset($properties['name']) && !isset($properties['slug'])) {
# We will assign this a slug.
$properties['slug'] = date('Hms');
}

# if we have a title but not a slug, generate a slug
if (isset($properties['name']) && !isset($properties['slug'])) {
$properties['slug'] = slugify($properties['name']);
$properties['slug'] = $properties['name'];
}
# make sure the slugs are safe.
if (isset($properties['slug'])) {
Expand All @@ -283,9 +299,9 @@ function create($request, $photos = []) {
$path = $config['source_path'] . 'content/';
$url = $config['base_url'];
# does this type of content require a specific path?
if (array_key_exists($type, $config['content_paths'])) {
$path .= $config['content_paths'][$type];
$url .= $config['content_paths'][$type];
if (array_key_exists($properties['posttype'], $config['content_paths'])) {
$path .= $config['content_paths'][$properties['posttype']];
$url .= $config['content_paths'][$properties['posttype']];
}
$filename = $path . $properties['slug'] . '.md';
$url .= $properties['slug'] . '/index.html';
Expand Down
29 changes: 17 additions & 12 deletions inc/twitter.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,34 @@ function build_tweet_url($tweet) {
return 'https://twitter.com/' . $tweet->user->screen_name . '/status/' . $tweet->id_str;
}

# Tweets are fully quotable in reply or repost context, so these are
# all just wrappers around a single function that handles both cases.
function twitter_com_in_reply_to($properties, $content) {
return twitter_reply_or_repost('in-reply-to', $properties, $content);
# Tweets are fully quotable in most contexts, so these are
# all just wrappers around a single function that handles these cases.
function in_reply_to_twitter_com($properties, $content) {
return twitter_source('in-reply-to', $properties, $content);
}
function twitter_com_repost_of($properties, $content) {
return twitter_reply_or_repost('repost-of', $properties, $content);
function repost_of_twitter_com($properties, $content) {
return twitter_source('repost-of', $properties, $content);
}
function m_twitter_com_in_reply_to($properties, $content) {
return twitter_reply_or_repost('in-reply-to', $properties, $content);
function bookmark_of_twitter_com($properties, $content) {
return twitter_source('bookmark-of', $properties, $content);
}
function m_twitter_com_repost_of($properties, $content) {
return twitter_reply_or_repost('repost-of', $properties, $content);
function in_reply_to_m_twitter_com($properties, $content) {
return twitter_source('in-reply-to', $properties, $content);
}
function repost_of_m_twitter_com($properties, $content) {
return twitter_source('repost-of', $properties, $content);
}
function bookmark_of_m_twitter_com($properties, $content) {
return twitter_source('bookmark-of', $properties, $content);
}

# replies and reposts have very similar markup, so this builds it.
function twitter_reply_or_repost( $type, $properties, $content) {
function twitter_source( $type, $properties, $content) {
global $config;
if (!isset($config['syndication']['twitter'])) {
return [$properties, $content];
}

$properties['posttype'] = $type;
$tweet = get_tweet($config['syndication']['twitter'], $properties[$type]);
if ( false !== $tweet ) {
$properties["$type-name"] = $tweet->user->name;
Expand Down

0 comments on commit 9f6d8c2

Please sign in to comment.