From 2cd148c792a47fea18d760b723d23569ae52d390 Mon Sep 17 00:00:00 2001 From: Aaron Parecki Date: Wed, 24 Dec 2014 16:44:44 -0800 Subject: [PATCH 1/4] Support for "likes" and adding "settings" page * Supports a bookmarklet to create "like" posts. * Beginning a "settings" page to connect silo profiles for POSSEing likes to twitter, facebook and instagram --- composer.json | 3 +- composer.lock | 67 ++++++++---- controllers/controllers.php | 172 ++++++++++++++++++++++++++---- lib/config.template.php | 7 ++ lib/helpers.php | 20 ++++ public/css/favorite.css | 17 +++ public/images/quill-logo-1024.png | Bin 0 -> 38419 bytes public/images/red-x.svg | 8 ++ public/images/star.svg | 8 ++ public/js/fav.js | 23 ++++ views/layout.php | 8 +- views/liked-js.php | 22 ++++ views/partials/fb-script.php | 20 ++++ views/settings.php | 88 +++++++++++++++ 14 files changed, 419 insertions(+), 44 deletions(-) create mode 100644 public/css/favorite.css create mode 100644 public/images/quill-logo-1024.png create mode 100644 public/images/red-x.svg create mode 100644 public/images/star.svg create mode 100644 public/js/fav.js create mode 100644 views/liked-js.php create mode 100644 views/partials/fb-script.php create mode 100644 views/settings.php diff --git a/composer.json b/composer.json index 196f5a9..a7dbea7 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,8 @@ "indieweb/date-formatter": "0.1.*", "indieauth/client": "0.1.3", "mpratt/relativetime": ">=1.0", - "firebase/php-jwt": "dev-master" + "firebase/php-jwt": "dev-master", + "ruudk/twitter-oauth": "dev-master" }, "autoload": { "files": [ diff --git a/composer.lock b/composer.lock index a4e4ad6..0bdac28 100644 --- a/composer.lock +++ b/composer.lock @@ -3,7 +3,7 @@ "This file locks the dependencies of your project to a known state", "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" ], - "hash": "3e034e0a6a692d5bbfecfdc95ee69db2", + "hash": "502847c033f5a54c69a6a1a51d26e894", "packages": [ { "name": "firebase/php-jwt", @@ -12,12 +12,12 @@ "source": { "type": "git", "url": "https://github.com/firebase/php-jwt.git", - "reference": "53669d621149e49c2a428722a62acfef3342c260" + "reference": "83b8899cb73d85d648af93f37ec0ac89f4a5bbae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/53669d621149e49c2a428722a62acfef3342c260", - "reference": "53669d621149e49c2a428722a62acfef3342c260", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/83b8899cb73d85d648af93f37ec0ac89f4a5bbae", + "reference": "83b8899cb73d85d648af93f37ec0ac89f4a5bbae", "shasum": "" }, "require": { @@ -26,7 +26,8 @@ "type": "library", "autoload": { "classmap": [ - "Authentication/" + "Authentication/", + "Exceptions/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -47,7 +48,7 @@ ], "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", "homepage": "https://github.com/firebase/php-jwt", - "time": "2013-09-03 20:55:18" + "time": "2014-11-18 17:58:25" }, { "name": "indieauth/client", @@ -367,6 +368,41 @@ ], "time": "2013-09-23 22:51:48" }, + { + "name": "ruudk/twitter-oauth", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/ruudk/twitteroauth.git", + "reference": "7f5a94eaa1572ddbc7f0a32ba3b865b8ac23712a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ruudk/twitteroauth/zipball/7f5a94eaa1572ddbc7f0a32ba3b865b8ac23712a", + "reference": "7f5a94eaa1572ddbc7f0a32ba3b865b8ac23712a", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "authors": [ + { + "name": "Ruud Kamphuis", + "email": "ruud@1plus1media.nl", + "role": "Developer" + } + ], + "description": "PHP 5.3 version of abraham/twitteroauth", + "homepage": "http://github.com/ruudk/twitteroauth", + "time": "2014-06-10 18:17:38" + }, { "name": "saltybeagle/savant3", "version": "dev-master", @@ -446,21 +482,14 @@ "time": "2012-12-13 02:15:50" } ], - "packages-dev": [ - - ], - "aliases": [ - - ], + "packages-dev": [], + "aliases": [], "minimum-stability": "stable", "stability-flags": { "saltybeagle/savant3": 20, - "firebase/php-jwt": 20 + "firebase/php-jwt": 20, + "ruudk/twitter-oauth": 20 }, - "platform": [ - - ], - "platform-dev": [ - - ] + "platform": [], + "platform-dev": [] } diff --git a/controllers/controllers.php b/controllers/controllers.php index 0de366b..18da149 100644 --- a/controllers/controllers.php +++ b/controllers/controllers.php @@ -1,6 +1,6 @@ request()->params(); if(array_key_exists('token', $params)) { try { @@ -8,16 +8,25 @@ function require_login(&$app) { $_SESSION['user_id'] = $data->user_id; $_SESSION['me'] = $data->me; } catch(DomainException $e) { - header('X-Error: DomainException'); - $app->redirect('/', 301); + if($redirect) { + header('X-Error: DomainException'); + $app->redirect('/', 301); + } else { + return false; + } } catch(UnexpectedValueException $e) { - header('X-Error: UnexpectedValueException'); - $app->redirect('/', 301); + if($redirect) { + header('X-Error: UnexpectedValueException'); + $app->redirect('/', 301); + } else { + return false; + } } } if(!array_key_exists('user_id', $_SESSION)) { - $app->redirect('/'); + if($redirect) + $app->redirect('/'); return false; } else { return ORM::for_table('users')->find_one($_SESSION['user_id']); @@ -160,6 +169,42 @@ $app->get('/add-to-home', function() use($app) { } }); +$app->get('/settings', function() use($app) { + if($user=require_login($app)) { + $html = render('settings', array('title' => 'Settings', 'include_facebook' => true)); + $app->response()->body($html); + } +}); + +$app->get('/favorite.js', function() use($app) { + $app->response()->header("Content-type", "text/javascript"); + if($user=require_login($app, false)) { + $params = $app->request()->params(); + + if(array_key_exists('url', $params)) { + $micropub_request = array( + 'like-of' => $params['url'] + ); + $r = micropub_post_for_user($user, $micropub_request); + } + + if(preg_match('/https?:\/\/(?:www\.)?facebook\.com\/(?:[^\/]+)\/posts\/(\d+)/', $params['url'], $match)) { + $facebook_id = $match[1]; + } else { + $facebook_id = false; + } + + $app->response()->body($app->render('liked-js.php', array( + 'url' => $params['url'], + 'like_url' => $r['location'], + 'error' => $r['error'], + 'facebook_id' => $facebook_id + ))); + } else { + $app->response()->body('alert("invalid token");'); + } +}); + $app->get('/micropub/syndications', function() use($app) { if($user=require_login($app)) { $data = get_syndication_targets($user); @@ -179,31 +224,112 @@ $app->post('/micropub/post', function() use($app) { return $v !== ''; }); - // Now send to the micropub endpoint - $r = micropub_post($user->micropub_endpoint, $params, $user->micropub_access_token); - $request = $r['request']; - $response = $r['response']; + $r = micropub_post_for_user($user, $params); + + $app->response()->body(json_encode(array( + 'request' => htmlspecialchars($r['request']), + 'response' => htmlspecialchars($r['response']), + 'location' => $r['location'], + 'error' => $r['error'], + 'curlinfo' => $r['curlinfo'] + ))); + } +}); + +$app->post('/auth/facebook', function() use($app) { + if($user=require_login($app, false)) { + $params = $app->request()->params(); + // User just auth'd with facebook, store the access token + $user->facebook_access_token = $params['fb_token']; + $user->save(); + + $app->response()->body(json_encode(array( + 'result' => 'ok' + ))); + } else { + $app->response()->body(json_encode(array( + 'result' => 'error' + ))); + } +}); + +$app->post('/auth/twitter', function() use($app) { + if($user=require_login($app, false)) { + $params = $app->request()->params(); + // User just auth'd with facebook, store the access token + $user->twitter_access_token = $params['twitter_token']; + $user->twitter_token_secret = $params['twitter_secret']; + $user->save(); + + $app->response()->body(json_encode(array( + 'result' => 'ok' + ))); + } else { + $app->response()->body(json_encode(array( + 'result' => 'error' + ))); + } +}); + +function getTwitterLoginURL(&$twitter) { + $request_token = $twitter->getRequestToken(Config::$base_url . 'auth/twitter/callback'); + $_SESSION['twitter_auth'] = $request_token; + return $twitter->getAuthorizeURL($request_token['oauth_token']); +} + +$app->get('/auth/twitter', function() use($app) { + $params = $app->request()->params(); + if($user=require_login($app, false)) { - $user->last_micropub_response = json_encode($r); - $user->last_micropub_response_date = date('Y-m-d H:i:s'); + // If there is an existing Twitter token, check if it is valid + // Otherwise, generate a Twitter login link + $twitter_login_url = false; + $twitter = new \TwitterOAuth\Api(Config::$twitterClientID, Config::$twitterClientSecret, + $user->twitter_access_token, $user->twitter_token_secret); - // Check the response and look for a "Location" header containing the URL - if($response && preg_match('/Location: (.+)/', $response, $match)) { - $location = $match[1]; - $user->micropub_success = 1; + if(array_key_exists('login', $params)) { + $twitter = new \TwitterOAuth\Api(Config::$twitterClientID, Config::$twitterClientSecret); + $twitter_login_url = getTwitterLoginURL($twitter); } else { - $location = false; + if($user->twitter_access_token) { + if ($twitter->get('account/verify_credentials')) { + $app->response()->body(json_encode(array( + 'result' => 'ok' + ))); + return; + } else { + // If the existing twitter token is not valid, generate a login link + $twitter_login_url = getTwitterLoginURL($twitter); + } + } else { + $twitter_login_url = getTwitterLoginURL($twitter); + } } - $user->save(); + $app->response()->body(json_encode(array( + 'url' => $twitter_login_url + ))); + } else { $app->response()->body(json_encode(array( - 'request' => htmlspecialchars($request), - 'response' => htmlspecialchars($response), - 'location' => $location, - 'error' => $r['error'], - 'curlinfo' => $r['curlinfo'] + 'result' => 'error' ))); } }); +$app->get('/auth/twitter/callback', function() use($app) { + if($user=require_login($app)) { + $params = $app->request()->params(); + + $twitter = new \TwitterOAuth\Api(Config::$twitterClientID, Config::$twitterClientSecret, + $_SESSION['twitter_auth']['oauth_token'], $_SESSION['twitter_auth']['oauth_token_secret']); + $credentials = $twitter->getAccessToken($params['oauth_verifier']); + + $user->twitter_access_token = $credentials['oauth_token']; + $user->twitter_token_secret = $credentials['oauth_token_secret']; + $user->twitter_username = $credentials['screen_name']; + $user->save(); + + $app->redirect('/settings'); + } +}); diff --git a/lib/config.template.php b/lib/config.template.php index df80efa..dae8968 100644 --- a/lib/config.template.php +++ b/lib/config.template.php @@ -10,5 +10,12 @@ class Config { public static $dbPassword = ''; public static $jwtSecret = 'xxx'; + + public static $fbClientID = ''; + public static $fbClientSecret = ''; + public static $twitterClientID = ''; + public static $twitterClientSecret = ''; + public static $instagramClientID = ''; + public static $instagramClientSecret = ''; } diff --git a/lib/helpers.php b/lib/helpers.php index cf751c6..010bd91 100644 --- a/lib/helpers.php +++ b/lib/helpers.php @@ -70,6 +70,26 @@ function get_timezone($lat, $lng) { return null; } +function micropub_post_for_user(&$user, $params) { + // Now send to the micropub endpoint + $r = micropub_post($user->micropub_endpoint, $params, $user->micropub_access_token); + + $user->last_micropub_response = json_encode($r); + $user->last_micropub_response_date = date('Y-m-d H:i:s'); + + // Check the response and look for a "Location" header containing the URL + if($r['response'] && preg_match('/Location: (.+)/', $r['response'], $match)) { + $r['location'] = $match[1]; + $user->micropub_success = 1; + } else { + $r['location'] = false; + } + + $user->save(); + + return $r; +} + function micropub_post($endpoint, $params, $access_token) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $endpoint); diff --git a/public/css/favorite.css b/public/css/favorite.css new file mode 100644 index 0000000..a80bc3a --- /dev/null +++ b/public/css/favorite.css @@ -0,0 +1,17 @@ + +#quill-star { + position: absolute; + + top: 50%; + left: 50%; + margin-top: -100px; + margin-left: -100px; + + width: 200px; + height: 200px; + +} + +#quill-star.hidden { + display: none; +} diff --git a/public/images/quill-logo-1024.png b/public/images/quill-logo-1024.png new file mode 100644 index 0000000000000000000000000000000000000000..b6bc5138e8b92aab900c585b6319f5ce9adba49f GIT binary patch literal 38419 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 literal 0 HcmV?d00001 diff --git a/public/images/red-x.svg b/public/images/red-x.svg new file mode 100644 index 0000000..041364d --- /dev/null +++ b/public/images/red-x.svg @@ -0,0 +1,8 @@ + + + + + + + diff --git a/public/images/star.svg b/public/images/star.svg new file mode 100644 index 0000000..5970e33 --- /dev/null +++ b/public/images/star.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/public/js/fav.js b/public/js/fav.js new file mode 100644 index 0000000..4288329 --- /dev/null +++ b/public/js/fav.js @@ -0,0 +1,23 @@ +console.log("Favoriting with token: " + quill_token); + +var http = new XMLHttpRequest(); +var params = "like-of=" + encodeURIComponent(window.location) + "&token=" + quill_token; + +http.open("POST", "http://quill.dev/favorite", true); +http.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + +http.onreadystatechange = function() {//Call a function when the state changes. + console.log(http); + if(http.readyState == 4 && http.status == 200) { + alert(http.responseText); + } +} +http.send(params); + +/* + +(function(){var el=document.createElement('input'); el.type="hidden"; el.id="quill_token"; el.value="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiMjciLCJtZSI6Imh0dHA6XC9cL2Fhcm9ucGFyZWNraS5jb20iLCJjcmVhdGVkX2F0IjoxNDEwMTE3NTM5fQ.ifp1VIgCTz9NPtMTlTLPBXAGSxHwpGS5tLPhXGxrjNk"; document.body.appendChild(el); document.body.appendChild(document.createElement('script')).src='http://quill.dev/js/fav.js';})(); + +(function(){document.body.appendChild(document.createElement('script')).src='http://quill.dev/favorite.js?url='+encodeURIComponent(window.location)+'&token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiMSIsIm1lIjoiaHR0cDpcL1wvcGsuZGV2XC8iLCJjcmVhdGVkX2F0IjoxNDE5MDM2NzAzfQ.AgJ5xyviiBzWOvQO0je0Bdi3BUpKJ4CLJnx8GIm-0OI';})(); + +*/ \ No newline at end of file diff --git a/views/layout.php b/views/layout.php index 531dec9..0c78aa7 100644 --- a/views/layout.php +++ b/views/layout.php @@ -31,6 +31,12 @@ + + \ No newline at end of file diff --git a/views/settings.php b/views/settings.php new file mode 100644 index 0000000..61feefc --- /dev/null +++ b/views/settings.php @@ -0,0 +1,88 @@ +
+ + +

Signed In As

+ + +

Facebook

+ + +

Twitter

+ + + + + +
+ From 430609b9005bb93ce2c6b21405cea0ede2b17a9d Mon Sep 17 00:00:00 2001 From: Aaron Parecki Date: Thu, 25 Dec 2014 18:50:34 -0800 Subject: [PATCH 2/4] adds instagram auth --- composer.json | 3 ++- composer.lock | 45 ++++++++++++++++++++++++++++++++++++- controllers/controllers.php | 40 +++++++++++++++++++++++++++++++++ lib/helpers.php | 11 +++++++++ views/settings.php | 31 ++++++++++++++++++++----- 5 files changed, 122 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index a7dbea7..cf2f57e 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,8 @@ "indieauth/client": "0.1.3", "mpratt/relativetime": ">=1.0", "firebase/php-jwt": "dev-master", - "ruudk/twitter-oauth": "dev-master" + "ruudk/twitter-oauth": "dev-master", + "andreyco/instagram": "3.*" }, "autoload": { "files": [ diff --git a/composer.lock b/composer.lock index 0bdac28..eb3dfc1 100644 --- a/composer.lock +++ b/composer.lock @@ -3,8 +3,51 @@ "This file locks the dependencies of your project to a known state", "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" ], - "hash": "502847c033f5a54c69a6a1a51d26e894", + "hash": "f2f8fdb671b52ce22dc0a5e133f9a13d", "packages": [ + { + "name": "andreyco/instagram", + "version": "v3.2", + "source": { + "type": "git", + "url": "https://github.com/Andreyco/Instagram-for-PHP.git", + "reference": "726e724c51301410873d9bae9c659a0597ca47e4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Andreyco/Instagram-for-PHP/zipball/726e724c51301410873d9bae9c659a0597ca47e4", + "reference": "726e724c51301410873d9bae9c659a0597ca47e4", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Andreyco\\Instagram\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-4-Clause" + ], + "authors": [ + { + "name": "Andrej Badin", + "email": "contact@andrejbadin.com", + "homepage": "http://andrejbadin.com" + } + ], + "description": "An easy-to-use PHP Class for accessing Instagram's API.", + "homepage": "https://github.com/Andreyco/Instagram", + "keywords": [ + "api", + "instagram" + ], + "time": "2014-07-14 19:53:19" + }, { "name": "firebase/php-jwt", "version": "dev-master", diff --git a/controllers/controllers.php b/controllers/controllers.php index 18da149..47fd614 100644 --- a/controllers/controllers.php +++ b/controllers/controllers.php @@ -333,3 +333,43 @@ $app->get('/auth/twitter/callback', function() use($app) { $app->redirect('/settings'); } }); + +$app->get('/auth/instagram', function() use($app) { + if($user=require_login($app, false)) { + + $instagram = instagram_client(); + + // If there is an existing Instagram auth token, check if it's valid + if($user->instagram_access_token) { + $instagram->setAccessToken($user->instagram_access_token); + $igUser = $instagram->getUser(); + + if($igUser && $igUser->meta->code == 200) { + $app->response()->body(json_encode(array( + 'result' => 'ok', + 'username' => $igUser->data->username + ))); + return; + } + } + + $app->response()->body(json_encode(array( + 'result' => 'error', + 'url' => $instagram->getLoginUrl(array('basic','likes')) + ))); + } +}); + +$app->get('/auth/instagram/callback', function() use($app) { + if($user=require_login($app)) { + $params = $app->request()->params(); + + $instagram = instagram_client(); + $data = $instagram->getOAuthToken($params['code']); + $user->instagram_access_token = $data->access_token; + $user->save(); + + $app->redirect('/settings'); + } +}); + diff --git a/lib/helpers.php b/lib/helpers.php index 010bd91..456fcfd 100644 --- a/lib/helpers.php +++ b/lib/helpers.php @@ -195,3 +195,14 @@ function relative_time($date) { } return $rel->timeAgo($date); } + +function instagram_client() { + return new Andreyco\Instagram\Client(array( + 'apiKey' => Config::$instagramClientID, + 'apiSecret' => Config::$instagramClientSecret, + 'apiCallback' => Config::$base_url . 'auth/instagram/callback', + 'scope' => array('basic','likes'), + )); +} + + diff --git a/views/settings.php b/views/settings.php index 61feefc..9860a91 100644 --- a/views/settings.php +++ b/views/settings.php @@ -10,11 +10,8 @@

Twitter

- - + From 8be498a324954f9d5792ca921b7364f53ff4085f Mon Sep 17 00:00:00 2001 From: Aaron Parecki Date: Fri, 26 Dec 2014 14:57:31 -0800 Subject: [PATCH 3/4] new "favorite" form with bookmarklet * disable facebook liking for now since it was getting complicated * new page for creating a favorite, can pass in a URL parameter too * bookmarklet code added to the "favorite" page --- controllers/controllers.php | 109 ++++++++++++++++++++---- lib/helpers.php | 2 +- public/css/favorite.css | 2 +- views/favorite-js.php | 33 +++++++ views/favorite-popup.php | 50 +++++++++++ views/layout.php | 4 +- views/liked-js.php | 22 ----- views/new-bookmark.php | 4 +- views/new-favorite.php | 74 ++++++++++++++++ views/new-post.php | 4 + views/partials/bookmark-bookmarklet.php | 2 +- views/partials/favorite-bookmarklet.php | 10 +++ views/settings.php | 8 +- 13 files changed, 277 insertions(+), 47 deletions(-) create mode 100644 views/favorite-js.php create mode 100644 views/favorite-popup.php delete mode 100644 views/liked-js.php create mode 100644 views/new-favorite.php create mode 100644 views/partials/favorite-bookmarklet.php diff --git a/controllers/controllers.php b/controllers/controllers.php index 47fd614..05b6c06 100644 --- a/controllers/controllers.php +++ b/controllers/controllers.php @@ -104,6 +104,24 @@ $app->get('/bookmark', function() use($app) { } }); +$app->get('/favorite', function() use($app) { + if($user=require_login($app)) { + $params = $app->request()->params(); + + $url = ''; + + if(array_key_exists('url', $params)) + $url = $params['url']; + + $html = render('new-favorite', array( + 'title' => 'New Favorite', + 'url' => $url, + 'token' => generate_login_token() + )); + $app->response()->body($html); + } +}); + $app->post('/prefs', function() use($app) { if($user=require_login($app)) { $params = $app->request()->params(); @@ -176,30 +194,84 @@ $app->get('/settings', function() use($app) { } }); +$app->get('/favorite-popup', function() use($app) { + if($user=require_login($app)) { + $params = $app->request()->params(); + + $html = $app->render('favorite-popup.php', array( + 'url' => $params['url'], + 'token' => $params['token'] + )); + $app->response()->body($html); + } +}); + +function create_favorite(&$user, $url) { + $micropub_request = array( + 'like-of' => $url + ); + $r = micropub_post_for_user($user, $micropub_request); + + $facebook_id = false; + $instagram_id = false; + $tweet_id = false; + + /* + // Facebook likes are posted via Javascript, so pass the FB ID to the javascript code + if(preg_match('/https?:\/\/(?:www\.)?facebook\.com\/(?:[^\/]+)\/posts\/(\d+)/', $url, $match)) { + $facebook_id = $match[1]; + } + + if(preg_match('/https?:\/\/(?:www\.)?facebook\.com\/photo\.php\?fbid=(\d+)/', $url, $match)) { + $facebook_id = $match[1]; + } + */ + + if(preg_match('/https?:\/\/(?:www\.)?instagram\.com\/p\/([^\/]+)/', $url, $match)) { + $instagram_id = $match[1]; + if($user->instagram_access_token) { + $instagram = instagram_client(); + $instagram->setAccessToken($user->instagram_access_token); + $ch = curl_init('https://api.instagram.com/v1/media/shortcode/' . $instagram_id . '?access_token=' . $user->instagram_access_token); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $result = json_decode(curl_exec($ch)); + + $result = $instagram->likeMedia($result->data->id); + } else { + // TODO: indicate that the instagram post couldn't be liked because no access token was available + } + } + + if(preg_match('/https?:\/\/(?:www\.)?twitter\.com\/[^\/]+\/status(?:es)?\/(\d+)/', $url, $match)) { + $tweet_id = $match[1]; + $twitter = new \TwitterOAuth\Api(Config::$twitterClientID, Config::$twitterClientSecret, + $user->twitter_access_token, $user->twitter_token_secret); + $result = $twitter->post('favorites/create', array( + 'id' => $tweet_id + )); + } + + return $r; +} + $app->get('/favorite.js', function() use($app) { $app->response()->header("Content-type", "text/javascript"); if($user=require_login($app, false)) { $params = $app->request()->params(); if(array_key_exists('url', $params)) { - $micropub_request = array( - 'like-of' => $params['url'] - ); - $r = micropub_post_for_user($user, $micropub_request); - } - - if(preg_match('/https?:\/\/(?:www\.)?facebook\.com\/(?:[^\/]+)\/posts\/(\d+)/', $params['url'], $match)) { - $facebook_id = $match[1]; + $r = create_favorite($user, $params['url']); + + $app->response()->body($app->render('favorite-js.php', array( + 'url' => $params['url'], + 'like_url' => $r['location'], + 'error' => $r['error'], + // 'facebook_id' => $facebook_id + ))); } else { - $facebook_id = false; + $app->response()->body('alert("no url");'); } - $app->response()->body($app->render('liked-js.php', array( - 'url' => $params['url'], - 'like_url' => $r['location'], - 'error' => $r['error'], - 'facebook_id' => $facebook_id - ))); } else { $app->response()->body('alert("invalid token");'); } @@ -236,6 +308,7 @@ $app->post('/micropub/post', function() use($app) { } }); +/* $app->post('/auth/facebook', function() use($app) { if($user=require_login($app, false)) { $params = $app->request()->params(); @@ -252,11 +325,12 @@ $app->post('/auth/facebook', function() use($app) { ))); } }); +*/ $app->post('/auth/twitter', function() use($app) { if($user=require_login($app, false)) { $params = $app->request()->params(); - // User just auth'd with facebook, store the access token + // User just auth'd with twitter, store the access token $user->twitter_access_token = $params['twitter_token']; $user->twitter_token_secret = $params['twitter_secret']; $user->save(); @@ -347,7 +421,8 @@ $app->get('/auth/instagram', function() use($app) { if($igUser && $igUser->meta->code == 200) { $app->response()->body(json_encode(array( 'result' => 'ok', - 'username' => $igUser->data->username + 'username' => $igUser->data->username, + 'url' => $instagram->getLoginUrl(array('basic','likes')) ))); return; } diff --git a/lib/helpers.php b/lib/helpers.php index 456fcfd..9993231 100644 --- a/lib/helpers.php +++ b/lib/helpers.php @@ -79,7 +79,7 @@ function micropub_post_for_user(&$user, $params) { // Check the response and look for a "Location" header containing the URL if($r['response'] && preg_match('/Location: (.+)/', $r['response'], $match)) { - $r['location'] = $match[1]; + $r['location'] = trim($match[1]); $user->micropub_success = 1; } else { $r['location'] = false; diff --git a/public/css/favorite.css b/public/css/favorite.css index a80bc3a..70a8677 100644 --- a/public/css/favorite.css +++ b/public/css/favorite.css @@ -1,6 +1,6 @@ #quill-star { - position: absolute; + position: fixed; top: 50%; left: 50%; diff --git a/views/favorite-js.php b/views/favorite-js.php new file mode 100644 index 0000000..4ce6dba --- /dev/null +++ b/views/favorite-js.php @@ -0,0 +1,33 @@ + +console.log("Favoriting URL: url ?>"); + +var css = document.createElement('link'); +css.rel="stylesheet"; +css.type="text/css"; +css.href="css/favorite.css"; +document.body.appendChild(css); + +function show_star() { + var star = document.createElement('img'); + star.id="quill-star"; + star.src="images/like_url ? 'star' : 'red-x' ?>.svg"; + star.onload=function() { + setTimeout(function(){ + + document.getElementById('quill-star').classList.add('hidden'); + var el = document.getElementById('quill-star'); + el.parentNode.removeChild(el); + if(typeof favorite_finished == "function") { + favorite_finished(); + } else { + // For now, redirect the user to the URL of their favorite so they can see it posted. + // Might want to change this later. + window.location = "like_url ?>"; + } + + }, 1200); + } + document.body.appendChild(star); +} + +show_star(); diff --git a/views/favorite-popup.php b/views/favorite-popup.php new file mode 100644 index 0000000..fecc780 --- /dev/null +++ b/views/favorite-popup.php @@ -0,0 +1,50 @@ + + + + Favoriting + + + + + + + window.quillFbInit = function() { + FB.getLoginStatus(function(response) { + + if (response.status === 'connected') { + // the user is logged in and has authenticated your + // app, and response.authResponse supplies + // the user's ID, a valid access token, a signed + // request, and the time the access token + // and signed request each expire + var uid = response.authResponse.userID; + var accessToken = response.authResponse.accessToken; + console.log(accessToken); + + FB.api("/facebook_id ?>/likes", "post", function(response){ + console.log(response); + show_star(); + }); + + } else if (response.status === 'not_authorized') { + // the user is logged in to Facebook, + // but has not authenticated your app + console.log("Logged in but not authorized"); + } else { + // the user isn't logged in to Facebook. + console.log("User isn't logged in"); + } + }); + }; + + + */ ?> + + + + \ No newline at end of file diff --git a/views/layout.php b/views/layout.php index 0c78aa7..8d7607d 100644 --- a/views/layout.php +++ b/views/layout.php @@ -33,7 +33,7 @@ @@ -64,13 +64,13 @@ if(property_exists($this, 'include_facebook')) {
  • New Post
  • Bookmark
  • +
  • Favorite
  • Docs