diff --git a/README.md b/README.md index a4412a2..67dde34 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ XRay will parse content in the following formats. First the URL is checked again * GitHub * XKCD * Hackernews +* Facebook (public events) If the contents of the URL is XML or JSON, then XRay will parse the Atom, RSS or JSONFeed formats. @@ -165,7 +166,7 @@ url=https://aaronparecki.com/2016/01/16/11/ &body=.... ``` -or for Twitter/GitHub where you might have JSON, +or for Twitter/GitHub/Facebook where you might have JSON, ``` POST /parse @@ -199,11 +200,17 @@ url=https://aaronparecki.com/2016/01/16/11/ &token=12341234123412341234 ``` -### Twitter Authentication -XRay uses the Twitter API to fetch posts, and the Twitter API requires authentication. In order to keep XRay stateless, it is required that you pass in Twitter credentials to the parse call. You can register an application on the Twitter developer website, and generate an access token for your account without writing any code, and then use those credentials when making an API request to XRay. +### API Authentication -You should only send Twitter credentials when the URL you are trying to parse is a Twitter URL, so you'll want to check for whether the hostname is `twitter.com` before you include credentials in this call. +XRay uses the Twitter, Github and Facebook APIs to fetch posts, and those API require authentication. In order to keep XRay stateless, it is required that you pass in the credentials to the parse call. + +You should only send the credentials when the URL you are trying to parse is a Twitter URL, a GitHub URL or a Facebook URL, so you'll want to check for whether the hostname is `twitter.com`, `github.com`, etc. before you include credentials in this call. + + +#### Twitter Authentication + +XRay uses the Twitter API to fetch Twitter URLs. You can register an application on the Twitter developer website, and generate an access token for your account without writing any code, and then use those credentials when making an API request to XRay. * `twitter_api_key` - Your application's API key * `twitter_api_secret` - Your application's API secret @@ -211,13 +218,23 @@ You should only send Twitter credentials when the URL you are trying to parse is * `twitter_access_token_secret` - Your Twitter secret access token -### GitHub Authentication +#### GitHub Authentication XRay uses the GitHub API to fetch GitHub URLs, which provides higher rate limits when used with authentication. You can pass a GitHub access token along with the request and XRay will use it when making requests to the API. * `github_access_token` - A GitHub access token +#### Facebook Authentication + +XRay uses the Facebook API to fetch Facebook URLs. You can create a Facebook App on Facebooks developer website. + +* facebook_app_id - Your application's App ID +* facebook_app_secret - Your application's App Secret + +At this moment, XRay is able to get it's own access token from those credentials. + + ### Error Response ```json @@ -273,7 +290,7 @@ Possible errors are listed below: } ``` -#### Primary Data +#### Primary Data The primary object on the page is returned in the `data` property. This will indicate the type of object (e.g. `entry`), and will contain the vocabulary's properties that it was able to parse from the page. diff --git a/composer.json b/composer.json index 4643f83..8f9cf85 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,8 @@ "p3k/timezone": "*", "p3k/http": "0.1.*", "cebe/markdown": "1.1.*", - "miniflux/picofeed": "^0.1.37" + "miniflux/picofeed": "^0.1.37", + "facebook/graph-sdk": "^5.5" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 146a69a..19d658b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "4cacb7d73963baec7b3fbc3248e43833", + "content-hash": "a1330f39bf5204a5cc6bdd1222915639", "packages": [ { "name": "cebe/markdown", - "version": "1.1.1", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/cebe/markdown.git", - "reference": "c30eb5e01fe021cc5bba2f9ee0eeef96d4931166" + "reference": "25b28bae8a6f185b5030673af77b32e1163d5c6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cebe/markdown/zipball/c30eb5e01fe021cc5bba2f9ee0eeef96d4931166", - "reference": "c30eb5e01fe021cc5bba2f9ee0eeef96d4931166", + "url": "https://api.github.com/repos/cebe/markdown/zipball/25b28bae8a6f185b5030673af77b32e1163d5c6e", + "reference": "25b28bae8a6f185b5030673af77b32e1163d5c6e", "shasum": "" }, "require": { @@ -64,7 +64,7 @@ "markdown", "markdown-extra" ], - "time": "2016-09-14T20:40:20+00:00" + "time": "2017-07-16T21:13:23+00:00" }, { "name": "dg/twitter-php", @@ -110,16 +110,16 @@ }, { "name": "ezyang/htmlpurifier", - "version": "v4.9.2", + "version": "v4.9.3", "source": { "type": "git", "url": "https://github.com/ezyang/htmlpurifier.git", - "reference": "6d50e5282afdfdfc3e0ff6d192aff56c5629b3d4" + "reference": "95e1bae3182efc0f3422896a3236e991049dac69" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/6d50e5282afdfdfc3e0ff6d192aff56c5629b3d4", - "reference": "6d50e5282afdfdfc3e0ff6d192aff56c5629b3d4", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/95e1bae3182efc0f3422896a3236e991049dac69", + "reference": "95e1bae3182efc0f3422896a3236e991049dac69", "shasum": "" }, "require": { @@ -153,7 +153,65 @@ "keywords": [ "html" ], - "time": "2017-03-13T06:30:53+00:00" + "time": "2017-06-03T02:28:16+00:00" + }, + { + "name": "facebook/graph-sdk", + "version": "5.6.1", + "source": { + "type": "git", + "url": "https://github.com/facebook/php-graph-sdk.git", + "reference": "2f9639c15ae043911f40ffe44080b32bac2c5280" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/facebook/php-graph-sdk/zipball/2f9639c15ae043911f40ffe44080b32bac2c5280", + "reference": "2f9639c15ae043911f40ffe44080b32bac2c5280", + "shasum": "" + }, + "require": { + "php": "^5.4|^7.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "~5.0", + "mockery/mockery": "~0.8", + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "guzzlehttp/guzzle": "Allows for implementation of the Guzzle HTTP client", + "paragonie/random_compat": "Provides a better CSPRNG option in PHP 5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "Facebook\\": "src/Facebook/" + }, + "files": [ + "src/Facebook/polyfills.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Facebook Platform" + ], + "authors": [ + { + "name": "Facebook", + "homepage": "https://github.com/facebook/php-graph-sdk/contributors" + } + ], + "description": "Facebook SDK for PHP", + "homepage": "https://github.com/facebook/php-graph-sdk", + "keywords": [ + "facebook", + "sdk" + ], + "time": "2017-08-16T17:28:07+00:00" }, { "name": "indieweb/link-rel-parser", @@ -741,16 +799,16 @@ }, { "name": "phpdocumentor/reflection-common", - "version": "1.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", - "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", "shasum": "" }, "require": { @@ -791,26 +849,26 @@ "reflection", "static analysis" ], - "time": "2015-12-27T11:43:31+00:00" + "time": "2017-09-11T18:02:19+00:00" }, { "name": "phpdocumentor/reflection-docblock", - "version": "3.1.1", + "version": "3.2.2", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e" + "reference": "4aada1f93c72c35e22fb1383b47fee43b8f1d157" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e", - "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/4aada1f93c72c35e22fb1383b47fee43b8f1d157", + "reference": "4aada1f93c72c35e22fb1383b47fee43b8f1d157", "shasum": "" }, "require": { "php": ">=5.5", "phpdocumentor/reflection-common": "^1.0@dev", - "phpdocumentor/type-resolver": "^0.2.0", + "phpdocumentor/type-resolver": "^0.3.0", "webmozart/assert": "^1.0" }, "require-dev": { @@ -836,24 +894,24 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2016-09-30T07:12:33+00:00" + "time": "2017-08-08T06:39:58+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "0.2.1", + "version": "0.3.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb" + "reference": "fb3933512008d8162b3cdf9e18dba9309b7c3773" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", - "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/fb3933512008d8162b3cdf9e18dba9309b7c3773", + "reference": "fb3933512008d8162b3cdf9e18dba9309b7c3773", "shasum": "" }, "require": { - "php": ">=5.5", + "php": "^5.5 || ^7.0", "phpdocumentor/reflection-common": "^1.0" }, "require-dev": { @@ -883,26 +941,26 @@ "email": "me@mikevanriel.com" } ], - "time": "2016-11-25T06:54:22+00:00" + "time": "2017-06-03T08:32:36+00:00" }, { "name": "phpspec/prophecy", - "version": "v1.7.0", + "version": "v1.7.2", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "93d39f1f7f9326d746203c7c056f300f7f126073" + "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/93d39f1f7f9326d746203c7c056f300f7f126073", - "reference": "93d39f1f7f9326d746203c7c056f300f7f126073", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6", + "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", "sebastian/comparator": "^1.1|^2.0", "sebastian/recursion-context": "^1.0|^2.0|^3.0" }, @@ -913,7 +971,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.6.x-dev" + "dev-master": "1.7.x-dev" } }, "autoload": { @@ -946,7 +1004,7 @@ "spy", "stub" ], - "time": "2017-03-02T20:05:34+00:00" + "time": "2017-09-04T11:05:03+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1198,16 +1256,16 @@ }, { "name": "phpunit/phpunit", - "version": "4.8.35", + "version": "4.8.36", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "791b1a67c25af50e230f841ee7a9c6eba507dc87" + "reference": "46023de9a91eec7dfb06cc56cb4e260017298517" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/791b1a67c25af50e230f841ee7a9c6eba507dc87", - "reference": "791b1a67c25af50e230f841ee7a9c6eba507dc87", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/46023de9a91eec7dfb06cc56cb4e260017298517", + "reference": "46023de9a91eec7dfb06cc56cb4e260017298517", "shasum": "" }, "require": { @@ -1266,7 +1324,7 @@ "testing", "xunit" ], - "time": "2017-02-06T05:18:07+00:00" + "time": "2017-06-21T08:07:12+00:00" }, { "name": "phpunit/phpunit-mock-objects", @@ -1698,16 +1756,16 @@ }, { "name": "symfony/http-foundation", - "version": "v2.8.20", + "version": "v2.8.29", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "cfa9013809ad18514855144d14bb321cf4673561" + "reference": "3aae75693835239d3a640efe3b5572dcd2d2abf3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/cfa9013809ad18514855144d14bb321cf4673561", - "reference": "cfa9013809ad18514855144d14bb321cf4673561", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/3aae75693835239d3a640efe3b5572dcd2d2abf3", + "reference": "3aae75693835239d3a640efe3b5572dcd2d2abf3", "shasum": "" }, "require": { @@ -1749,20 +1807,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2017-05-01T14:31:55+00:00" + "time": "2017-11-05T19:06:07+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.3.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4" + "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4", - "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296", + "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296", "shasum": "" }, "require": { @@ -1774,7 +1832,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3-dev" + "dev-master": "1.6-dev" } }, "autoload": { @@ -1808,20 +1866,20 @@ "portable", "shim" ], - "time": "2016-11-14T01:06:16+00:00" + "time": "2017-10-11T12:05:26+00:00" }, { "name": "symfony/polyfill-php54", - "version": "v1.3.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php54.git", - "reference": "90e085822963fdcc9d1c5b73deb3d2e5783b16a0" + "reference": "d7810a14b2c6c1aff415e1bb755f611b3d5327bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php54/zipball/90e085822963fdcc9d1c5b73deb3d2e5783b16a0", - "reference": "90e085822963fdcc9d1c5b73deb3d2e5783b16a0", + "url": "https://api.github.com/repos/symfony/polyfill-php54/zipball/d7810a14b2c6c1aff415e1bb755f611b3d5327bc", + "reference": "d7810a14b2c6c1aff415e1bb755f611b3d5327bc", "shasum": "" }, "require": { @@ -1830,7 +1888,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3-dev" + "dev-master": "1.6-dev" } }, "autoload": { @@ -1866,20 +1924,20 @@ "portable", "shim" ], - "time": "2016-11-14T01:06:16+00:00" + "time": "2017-10-11T12:05:26+00:00" }, { "name": "symfony/polyfill-php55", - "version": "v1.3.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php55.git", - "reference": "03e3f0350bca2220e3623a0e340eef194405fc67" + "reference": "b64e7f0c37ecf144ecc16668936eef94e628fbfd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php55/zipball/03e3f0350bca2220e3623a0e340eef194405fc67", - "reference": "03e3f0350bca2220e3623a0e340eef194405fc67", + "url": "https://api.github.com/repos/symfony/polyfill-php55/zipball/b64e7f0c37ecf144ecc16668936eef94e628fbfd", + "reference": "b64e7f0c37ecf144ecc16668936eef94e628fbfd", "shasum": "" }, "require": { @@ -1889,7 +1947,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3-dev" + "dev-master": "1.6-dev" } }, "autoload": { @@ -1922,24 +1980,24 @@ "portable", "shim" ], - "time": "2016-11-14T01:06:16+00:00" + "time": "2017-10-11T12:05:26+00:00" }, { "name": "symfony/yaml", - "version": "v3.2.8", + "version": "v3.3.11", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "acec26fcf7f3031e094e910b94b002fa53d4e4d6" + "reference": "0938408c4faa518d95230deabb5f595bf0de31b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/acec26fcf7f3031e094e910b94b002fa53d4e4d6", - "reference": "acec26fcf7f3031e094e910b94b002fa53d4e4d6", + "url": "https://api.github.com/repos/symfony/yaml/zipball/0938408c4faa518d95230deabb5f595bf0de31b9", + "reference": "0938408c4faa518d95230deabb5f595bf0de31b9", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^5.5.9|>=7.0.8" }, "require-dev": { "symfony/console": "~2.8|~3.0" @@ -1950,7 +2008,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "3.3-dev" } }, "autoload": { @@ -1977,7 +2035,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2017-05-01T14:55:58+00:00" + "time": "2017-11-10T18:26:04+00:00" }, { "name": "webmozart/assert", diff --git a/controllers/Parse.php b/controllers/Parse.php index fc18e07..d20e11e 100644 --- a/controllers/Parse.php +++ b/controllers/Parse.php @@ -87,6 +87,7 @@ class Parse { $fields = [ 'twitter_api_key','twitter_api_secret','twitter_access_token','twitter_access_token_secret', + 'facebook_app_id', 'facebook_app_secret', 'github_access_token', 'token' ]; diff --git a/lib/XRay/Fetcher.php b/lib/XRay/Fetcher.php index 3e37789..dd9e861 100644 --- a/lib/XRay/Fetcher.php +++ b/lib/XRay/Fetcher.php @@ -43,6 +43,11 @@ class Fetcher { return $this->_fetch_tweet($url, $opts); } + // Check if this is a Facebook URL and use the API + if(Formats\Facebook::matches_host($url)) { + return $this->_fetch_facebook($url, $opts); + } + // Transform the HTML GitHub URL into an GitHub API request and fetch the API response if(Formats\GitHub::matches_host($url)) { return $this->_fetch_github($url, $opts); @@ -148,6 +153,26 @@ class Fetcher { return Formats\Twitter::fetch($url, $creds); } + private function _fetch_facebook($url, $opts) { + $fields = ['facebook_app_id','facebook_app_secret']; + $creds = []; + foreach($fields as $f) { + if(isset($opts[$f])) + $creds[$f] = $opts[$f]; + } + + if(count($creds) < 2) { + return [ + 'error_code' => 400, + 'error' => 'missing_parameters', + 'error_description' => 'Both Facebook credentials must be included in the request' + ]; + } + + // TODO: Question, should I do this like Twitter or like Github? + return Formats\Facebook::fetch($url, $creds); + } + private function _fetch_github($url, $opts) { $fields = ['github_access_token']; $creds = []; diff --git a/lib/XRay/Formats/Facebook.php b/lib/XRay/Formats/Facebook.php new file mode 100644 index 0000000..330b20e --- /dev/null +++ b/lib/XRay/Formats/Facebook.php @@ -0,0 +1,135 @@ + 'event', + 'url' => $url, + 'name' => $fbObject['name'], + 'start' => $fbObject['start_time'] + ); + + if(isset($fbObject['end_time'])) $event['end'] = $fbObject['end_time']; + if(isset($fbObject['description'])) $event['summary'] = $fbObject['description']; + + // Is the event linked to a Page? + if(isset($fbObject['place']['id'])) { + + $card = array( + 'type' => 'card', + 'url' => 'https://facebook.com/'.$fbObject['place']['id'], + 'name' => $fbObject['place']['name'] + ); + + if(isset($fbObject['place']['location'])) { + + $location = $fbObject['place']['location']; + + if(isset($location['zip'])) $card['postal-code'] = $location['zip']; + if(isset($location['city'])) $card['locality'] = $location['city']; + if(isset($location['state'])) $card['region'] = $location['state']; + if(isset($location['street'])) $card['street-address'] = $location['street']; + if(isset($location['country'])) $card['country'] = $location['country']; + if(isset($location['latitude'])) $card['latitude'] = (string)$location['latitude']; + if(isset($location['longitude'])) $card['longitude'] = (string)$location['longitude']; + + } + + $event['location'] = $card['url']; + $event['refs'] = array($card); + + // If we only have a name, use that + } elseif(isset($fbObject['place']['name'])) { + $event['location'] = $fbObject['place']['name']; + } + + return [ + 'data' => $event, + 'original' => $fbObject + ]; + } + } + + public static function fetch($url, $creds) { + + $parts = self::extract_url_parts($url); + + if(!$parts or $parts['api_uri'] == false) { + return [ + 'error' => 'unsupported_url', + 'error_description' => 'This Facebook URL is not supported', + 'error_code' => 400, + ]; + } + + $fb = new \Facebook\Facebook(array( + 'app_id' => $creds['facebook_app_id'], + 'app_secret' => $creds['facebook_app_secret'], + 'default_graph_version' => 'v2.9', + )); + + $fbApp = new \Facebook\FacebookApp($creds['facebook_app_id'], $creds['facebook_app_secret']); + $token = $fbApp->getAccessToken(); + + $request = new \Facebook\FacebookRequest($fbApp, $token, 'GET', $parts['api_uri']); + + try { + $response = $fb->getClient()->sendRequest($request); + } catch(\Facebook\Exceptions\FacebookResponseException $e) { + return [ + 'error' => 'facebook_graph_error', + 'error_description' => 'Graph returned an error: ' . $e->getMessage(), + 'error_code' => 400, + ]; + } catch(\Facebook\Exceptions\FacebookSDKException $e) { + return [ + 'error' => 'facebook_sdk_error', + 'error_description' => 'Facebook SDK returned an error: ' . $e->getMessage(), + 'error_code' => 400, + ]; + } + + return [ + 'code' => 200, + 'body' => $response->getDecodedBody(), + 'url' => $url + ]; + } + + private static function extract_url_parts($url) { + $response = false; + + if(preg_match('~https://(.*?).?facebook.com/([^/]+)/posts/(\d+)/?$~', $url, $match)) { + // TODO: how do we get these? + // $response['type'] = 'entry'; + // $response['api_uri'] = false; + + } elseif(preg_match('~https://(.*?).?facebook.com/events/(\d+)/?$~', $url, $match)) { + $response['type'] = 'event'; + $response['api_uri'] = '/'.$match[2]; + } + + return $response; + } +} diff --git a/lib/XRay/Parser.php b/lib/XRay/Parser.php index 2d8f919..539bd77 100644 --- a/lib/XRay/Parser.php +++ b/lib/XRay/Parser.php @@ -30,6 +30,10 @@ class Parser { return Formats\Twitter::parse($body, $url); } + if(Formats\Facebook::matches($url)) { + return Formats\Facebook::parse($body, $url); + } + if(Formats\XKCD::matches($url)) { return Formats\XKCD::parse($body, $url); } diff --git a/tests/FacebookTest.php b/tests/FacebookTest.php new file mode 100644 index 0000000..002a5b8 --- /dev/null +++ b/tests/FacebookTest.php @@ -0,0 +1,67 @@ +client = new Parse(); + $this->client->mc = null; + } + + private function parse($params) { + $request = new Request($params); + $response = new Response(); + + $result = $this->client->parse($request, $response); + $body = $result->getContent(); + $this->assertEquals(200, $result->getStatusCode()); + return json_decode($body, true); + } + + private function loadFbObject($id) { + return file_get_contents(dirname(__FILE__).'/data/graph.facebook.com/'.$id.'.json'); + } + + public function testFacebookEventWithHCard() { + $url = 'https://www.facebook.com/events/446197069049722/'; + $json = $this->loadFbObject('446197069049722'); + + $data = $this->parse(['url' => $url, 'body' => $json]); + + $this->assertEquals('event', $data['data']['type']); + $this->assertEquals('IndieWeb Summit', $data['data']['name']); + $this->assertEquals('2017-06-24T09:00:00-0700', $data['data']['start']); + $this->assertEquals('2017-06-25T18:00:00-0700', $data['data']['end']); + $this->assertContains('The seventh annual gathering for independent web creators of all kinds,', $data['data']['summary']); + $this->assertEquals('https://facebook.com/332204056925945', $data['data']['location']); + + $card = $data['data']['refs'][0]; + $this->assertEquals('card', $card['type']); + $this->assertEquals('https://facebook.com/332204056925945', $card['url']); + $this->assertEquals('Mozilla PDX', $card['name']); + $this->assertEquals('97209', $card['postal-code']); + $this->assertEquals('Portland', $card['locality']); + $this->assertEquals('OR', $card['region']); + $this->assertEquals('1120 NW Couch St, Ste 320', $card['street-address']); + $this->assertEquals('United States', $card['country']); + $this->assertEquals('45.5233192', $card['latitude']); + $this->assertEquals('-122.6824722', $card['longitude']); + } + + public function testFacebookEvent() { + $url = 'https://www.facebook.com/events/1596554663924436/'; + $json = $this->loadFbObject('1596554663924436'); + + $data = $this->parse(['url' => $url, 'body' => $json]); + + $this->assertEquals('event', $data['data']['type']); + $this->assertEquals('Homebrew Website Club', $data['data']['name']); + $this->assertEquals('2015-04-22T19:00:00-0400', $data['data']['start']); + $this->assertContains('Are you building your own website? Indie reader?', $data['data']['summary']); + $this->assertEquals('Charging Bull - WeeWork - 25 Broadway, New York, NY 10004', $data['data']['location']); + } + +} diff --git a/tests/data/graph.facebook.com/1596554663924436.json b/tests/data/graph.facebook.com/1596554663924436.json new file mode 100644 index 0000000..aeb5f61 --- /dev/null +++ b/tests/data/graph.facebook.com/1596554663924436.json @@ -0,0 +1 @@ +{"description":"Are you building your own website? Indie reader? Personal publishing web app? Or some other digital magic-cloud proxy? If so, come on by and join a gathering of people with like-minded interests. Bring your friends that want to start a personal web site. Exchange information, swap ideas, talk shop, help work on a project. \n\n Contact (914) 414-1775 when you arrive to be admitted. \n\n\nMore information: \u0040[NjQyMTgzOTU5MjA4MTA3Omh0dHBzXGEvL2luZGlld2ViY2FtcC5jb20vZXZlbnRzLzIwMTUtMDQtMjItaG9tZWJyZXctd2Vic2l0ZS1jbHViOjo=:\u0040[NjQyMTgzOTU5MjA4MTA3Omh0dHBzXGEvL2luZGlld2ViY2FtcC5jb20vZXZlbnRzLzIwMTUtMDQtMjItaG9tZWJyZXctd2Vic2l0ZS1jbHViOjo=:https:\/\/indiewebcamp.com\/events\/2015-04-22-homebrew-website-club]]","name":"Homebrew Website Club","place":{"name":"Charging Bull - WeeWork - 25 Broadway, New York, NY 10004"},"start_time":"2015-04-22T19:00:00-0400","id":"1596554663924436"} \ No newline at end of file diff --git a/tests/data/graph.facebook.com/446197069049722.json b/tests/data/graph.facebook.com/446197069049722.json new file mode 100644 index 0000000..7818a4d --- /dev/null +++ b/tests/data/graph.facebook.com/446197069049722.json @@ -0,0 +1 @@ +{"description":"The seventh annual gathering for independent web creators of all kinds, from graphic artists, to designers, UX engineers, coders, hackers, to share ideas, actively work on creating for their own personal websites, and build upon each others creations.\n\nIMPORTANT: Please register for a ticket from https:\/\/2017.indieweb.org\/ to attend! RSVPing on Facebook is not enough. Your registration will ensure that we get enough food for everyone!\n\nOriginally posted at https:\/\/aaronparecki.com\/2017\/06\/24\/1\/indieweb-summit","end_time":"2017-06-25T18:00:00-0700","name":"IndieWeb Summit","place":{"name":"Mozilla PDX","location":{"city":"Portland","country":"United States","latitude":45.5233192,"longitude":-122.6824722,"state":"OR","street":"1120 NW Couch St, Ste 320","zip":"97209"},"id":"332204056925945"},"start_time":"2017-06-24T09:00:00-0700","id":"446197069049722"} \ No newline at end of file