From 4aa06023f0c25a10d5eaeafaeb30034e0a4f2e95 Mon Sep 17 00:00:00 2001 From: Aaron Parecki Date: Mon, 19 Dec 2016 10:39:23 -0800 Subject: clean up note UI, show reply context * shows reply context of the URL you're replying to * autocomplete nicknames from the post when replying * moved debug info to the settings screen to clean up the UI --- composer.json | 5 +- composer.lock | 160 ++++++++++++++++++++++++++---------------- controllers/auth.php | 20 +++--- controllers/controllers.php | 71 ++++++++++++++++++- lib/helpers.php | 43 ++++++++++++ public/css/style.css | 29 +++++++- views/new-post.php | 164 +++++++++++++++++++++++++++++++------------- views/settings.php | 28 ++++++-- 8 files changed, 397 insertions(+), 123 deletions(-) diff --git a/composer.json b/composer.json index 1a61c65..fc6ad33 100644 --- a/composer.json +++ b/composer.json @@ -9,9 +9,10 @@ "indieauth/client": ">=0.1.11", "mpratt/relativetime": ">=1.0", "firebase/php-jwt": "2.*", - "ruudk/twitter-oauth": "dev-master", + "abraham/twitteroauth": "*", "andreyco/instagram": "3.*", - "p3k/multipart": "*" + "p3k/multipart": "*", + "tantek/cassis": "*" }, "autoload": { "files": [ diff --git a/composer.lock b/composer.lock index fef94a8..169e3ff 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,75 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "66741248756ed56d19ea2afd34809fe2", + "hash": "4756e7ef0035b8f768f07d252adbdc17", + "content-hash": "f4709cc6bd166e3759fbedbfbaa7752e", "packages": [ + { + "name": "abraham/twitteroauth", + "version": "0.7.2", + "source": { + "type": "git", + "url": "https://github.com/abraham/twitteroauth.git", + "reference": "119d5a83478a2d21c09cd27980ab67eba11c8fe1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/abraham/twitteroauth/zipball/119d5a83478a2d21c09cd27980ab67eba11c8fe1", + "reference": "119d5a83478a2d21c09cd27980ab67eba11c8fe1", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpmd/phpmd": "~2.4", + "phpunit/phpunit": "~5.6", + "squizlabs/php_codesniffer": "~2.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Abraham\\TwitterOAuth\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Abraham Williams", + "email": "abraham@abrah.am", + "homepage": "https://abrah.am", + "role": "Developer" + } + ], + "description": "The most popular PHP library for use with the Twitter OAuth REST API.", + "homepage": "https://twitteroauth.com", + "keywords": [ + "Twitter API", + "Twitter oAuth", + "api", + "oauth", + "rest", + "social", + "twitter" + ], + "time": "2016-12-12 17:42:13" + }, { "name": "andreyco/instagram", - "version": "v3.2", + "version": "3.4.0", "source": { "type": "git", "url": "https://github.com/Andreyco/Instagram-for-PHP.git", - "reference": "726e724c51301410873d9bae9c659a0597ca47e4" + "reference": "8c1b98f601a68142095461c0b8a9498375145e0d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Andreyco/Instagram-for-PHP/zipball/726e724c51301410873d9bae9c659a0597ca47e4", - "reference": "726e724c51301410873d9bae9c659a0597ca47e4", + "url": "https://api.github.com/repos/Andreyco/Instagram-for-PHP/zipball/8c1b98f601a68142095461c0b8a9498375145e0d", + "reference": "8c1b98f601a68142095461c0b8a9498375145e0d", "shasum": "" }, "require": { @@ -42,12 +97,12 @@ } ], "description": "An easy-to-use PHP Class for accessing Instagram's API.", - "homepage": "https://github.com/Andreyco/Instagram", + "homepage": "https://github.com/Andreyco/Instagram-for-PHP", "keywords": [ "api", "instagram" ], - "time": "2014-07-14 19:53:19" + "time": "2016-07-17 23:42:10" }, { "name": "barnabywalters/mf-cleaner", @@ -135,16 +190,16 @@ }, { "name": "indieauth/client", - "version": "0.1.11", + "version": "0.1.13", "source": { "type": "git", "url": "https://github.com/indieweb/indieauth-client-php.git", - "reference": "6504ed0d4714084e9955f639d6e5cf4e976f9038" + "reference": "d438bb03db15b4ccc6c63228be16de7870b6ab99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/indieweb/indieauth-client-php/zipball/6504ed0d4714084e9955f639d6e5cf4e976f9038", - "reference": "6504ed0d4714084e9955f639d6e5cf4e976f9038", + "url": "https://api.github.com/repos/indieweb/indieauth-client-php/zipball/d438bb03db15b4ccc6c63228be16de7870b6ab99", + "reference": "d438bb03db15b4ccc6c63228be16de7870b6ab99", "shasum": "" }, "require": { @@ -170,20 +225,20 @@ } ], "description": "IndieAuth Client Library", - "time": "2015-08-30 22:29:40" + "time": "2016-02-08 23:56:31" }, { "name": "indieweb/date-formatter", - "version": "0.1.5", + "version": "0.1.6", "source": { "type": "git", "url": "https://github.com/indieweb/date-formatter-php.git", - "reference": "f0dc028ba53da4da2718d2a263300396b1c14203" + "reference": "9c12e0fda95f4b3119fcaf271d141305870c4350" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/indieweb/date-formatter-php/zipball/f0dc028ba53da4da2718d2a263300396b1c14203", - "reference": "f0dc028ba53da4da2718d2a263300396b1c14203", + "url": "https://api.github.com/repos/indieweb/date-formatter-php/zipball/9c12e0fda95f4b3119fcaf271d141305870c4350", + "reference": "9c12e0fda95f4b3119fcaf271d141305870c4350", "shasum": "" }, "require": { @@ -213,7 +268,7 @@ "microformats", "microformats2" ], - "time": "2013-10-27 23:46:11" + "time": "2015-10-28 00:32:39" }, { "name": "indieweb/link-rel-parser", @@ -413,16 +468,16 @@ }, { "name": "mpratt/relativetime", - "version": "1.5.1", + "version": "1.5.4", "source": { "type": "git", "url": "https://github.com/mpratt/RelativeTime.git", - "reference": "219e6568fa3e7b181244f93be493fbab4c89c056" + "reference": "3dc1efd96c8edbd0fe9e5cdb423ca31428b9dbb7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mpratt/RelativeTime/zipball/219e6568fa3e7b181244f93be493fbab4c89c056", - "reference": "219e6568fa3e7b181244f93be493fbab4c89c056", + "url": "https://api.github.com/repos/mpratt/RelativeTime/zipball/3dc1efd96c8edbd0fe9e5cdb423ca31428b9dbb7", + "reference": "3dc1efd96c8edbd0fe9e5cdb423ca31428b9dbb7", "shasum": "" }, "require": { @@ -457,7 +512,7 @@ "time", "time-ago" ], - "time": "2015-05-28 14:13:23" + "time": "2015-12-24 12:43:04" }, { "name": "p3k/multipart", @@ -495,41 +550,6 @@ "description": "Multipart Encoding Library", "time": "2015-07-16 19:28:02" }, - { - "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", @@ -607,14 +627,36 @@ "router" ], "time": "2012-12-13 02:15:50" + }, + { + "name": "tantek/cassis", + "version": "v0.2.16895", + "source": { + "type": "git", + "url": "https://github.com/tantek/cassis.git", + "reference": "357e30ad12017b4d67cac5f4400e8f371cd2f504" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tantek/cassis/zipball/357e30ad12017b4d67cac5f4400e8f371cd2f504", + "reference": "357e30ad12017b4d67cac5f4400e8f371cd2f504", + "shasum": "" + }, + "type": "library", + "autoload": { + "files": [ + "cassis.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "time": "2016-04-04 15:31:04" } ], "packages-dev": [], "aliases": [], "minimum-stability": "stable", "stability-flags": { - "saltybeagle/savant3": 20, - "ruudk/twitter-oauth": 20 + "saltybeagle/savant3": 20 }, "prefer-stable": false, "prefer-lowest": false, diff --git a/controllers/auth.php b/controllers/auth.php index fb8b6da..baf5c2f 100644 --- a/controllers/auth.php +++ b/controllers/auth.php @@ -1,4 +1,5 @@ post('/auth/twitter', function() use($app) { }); function getTwitterLoginURL(&$twitter) { - $request_token = $twitter->getRequestToken(Config::$base_url . 'auth/twitter/callback'); + $request_token = $twitter->oauth('oauth/request_token', [ + 'oauth_callback' => Config::$base_url . 'auth/twitter/callback' + ]); $_SESSION['twitter_auth'] = $request_token; - return $twitter->getAuthorizeURL($request_token['oauth_token']); + return $twitter->url('oauth/authorize', ['oauth_token' => $request_token['oauth_token']]); } $app->get('/auth/twitter', function() use($app) { @@ -272,15 +275,16 @@ $app->get('/auth/twitter', function() use($app) { // 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); if(array_key_exists('login', $params)) { - $twitter = new \TwitterOAuth\Api(Config::$twitterClientID, Config::$twitterClientSecret); + $twitter = new TwitterOAuth(Config::$twitterClientID, Config::$twitterClientSecret); $twitter_login_url = getTwitterLoginURL($twitter); } else { + $twitter = new TwitterOAuth(Config::$twitterClientID, Config::$twitterClientSecret, + $user->twitter_access_token, $user->twitter_token_secret); + if($user->twitter_access_token) { - if ($twitter->get('account/verify_credentials')) { + if($twitter->get('account/verify_credentials')) { $app->response()['Content-type'] = 'application/json'; $app->response()->body(json_encode(array( 'result' => 'ok' @@ -312,9 +316,9 @@ $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, + $twitter = new TwitterOAuth(Config::$twitterClientID, Config::$twitterClientSecret, $_SESSION['twitter_auth']['oauth_token'], $_SESSION['twitter_auth']['oauth_token_secret']); - $credentials = $twitter->getAccessToken($params['oauth_verifier']); + $credentials = $twitter->oauth('oauth/access_token', ['oauth_verifier' => $params['oauth_verifier']]); $user->twitter_access_token = $credentials['oauth_token']; $user->twitter_token_secret = $credentials['oauth_token_secret']; diff --git a/controllers/controllers.php b/controllers/controllers.php index 09133cf..a9bc0ab 100644 --- a/controllers/controllers.php +++ b/controllers/controllers.php @@ -1,4 +1,5 @@ request()->params(); @@ -336,7 +337,7 @@ function create_favorite(&$user, $url) { // POSSE favorites to Twitter if($user->twitter_access_token && preg_match('/https?:\/\/(?:www\.)?twitter\.com\/[^\/]+\/status(?:es)?\/(\d+)/', $url, $match)) { $tweet_id = $match[1]; - $twitter = new \TwitterOAuth\Api(Config::$twitterClientID, Config::$twitterClientSecret, + $twitter = new TwitterOAuth(Config::$twitterClientID, Config::$twitterClientSecret, $user->twitter_access_token, $user->twitter_token_secret); $result = $twitter->post('favorites/create', array( 'id' => $tweet_id @@ -356,7 +357,7 @@ function create_repost(&$user, $url) { if($user->twitter_access_token && preg_match('/https?:\/\/(?:www\.)?twitter\.com\/[^\/]+\/status(?:es)?\/(\d+)/', $url, $match)) { $tweet_id = $match[1]; - $twitter = new \TwitterOAuth\Api(Config::$twitterClientID, Config::$twitterClientSecret, + $twitter = new TwitterOAuth(Config::$twitterClientID, Config::$twitterClientSecret, $user->twitter_access_token, $user->twitter_token_secret); $result = $twitter->post('statuses/retweet/'.$tweet_id); } @@ -392,6 +393,72 @@ $app->post('/repost', function() use($app) { } }); +$app->get('/reply/preview', function() use($app) { + if($user=require_login($app)) { + $params = $app->request()->params(); + + $reply_url = trim($params['url']); + + if(preg_match('/twtr\.io\/([0-9a-z]+)/i', $reply_url, $match)) { + $twtr = 'https://twitter.com/_/status/' . sxg_to_num($match[1]); + $ch = curl_init($twtr); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_exec($ch); + $expanded_url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL); + if($expanded_url) $reply_url = $expanded_url; + } + + $entry = false; + // Convert Tweets to h-entry + if(preg_match('/twitter\.com\/(?:[^\/]+)\/statuse?s?\/(.+)/', $reply_url, $match)) { + $tweet_id = $match[1]; + if($user->twitter_access_token) { + $twitter = new TwitterOAuth(Config::$twitterClientID, Config::$twitterClientSecret, + $user->twitter_access_token, $user->twitter_token_secret); + } else { + $twitter = new TwitterOAuth(Config::$twitterClientID, Config::$twitterClientSecret); + } + $tweet = $twitter->get('statuses/show/'.$tweet_id); + $entry = tweet_to_h_entry($tweet); + } else { + // Pass to X-Ray to see if it can expand the entry + $ch = curl_init('https://xray.p3k.io/parse?url='.urlencode($reply_url)); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $response = curl_exec($ch); + $data = @json_decode($response, true); + if($data && $data['data']['type'] == 'entry') { + $entry = $data['data']; + // Create a nickname based on the author URL + if($entry['author']['url']) { + $entry['author']['nickname'] = display_url($entry['author']['url']); + } + } + } + + $mentions = []; + if($entry) { + // Find all @-names in the post, as well as the author name + $mentions[] = $entry['author']['nickname']; + + if(preg_match_all('/(^|(?<=[\s\/]))@([a-z0-9_]+([a-z0-9_\.]*)?)/i', $entry['content']['text'], $matches)) { + foreach($matches[0] as $nick) { + if(trim($nick,'@') != $user->twitter_username && trim($nick,'@') != display_url($user->url)) + $mentions[] = trim($nick,'@'); + } + } + + } + + $app->response()['Content-type'] = 'application/json'; + $app->response()->body(json_encode([ + 'canonical_reply_url' => $reply_url, + 'entry' => $entry, + 'mentions' => $mentions + ])); + } +}); + $app->get('/micropub/syndications', function() use($app) { if($user=require_login($app)) { $data = get_micropub_config($user, ['q'=>'syndicate-to']); diff --git a/lib/helpers.php b/lib/helpers.php index 3a71d90..8948f92 100644 --- a/lib/helpers.php +++ b/lib/helpers.php @@ -396,3 +396,46 @@ function correct_photo_rotation($filename) { } } +function tweet_to_h_entry($tweet) { + // Converts to XRay's h-entry format + + $entry = [ + 'type' => 'entry', + 'url' => 'https://twitter.com/'.$tweet->user->screen_name.'/status/'.$tweet->id_str, + ]; + + $published = strtotime($tweet->created_at); + $entry['published'] = date('c', $published); + + $entry['content'] = [ + 'text' => $tweet->text + ]; + + if($tweet->entities->urls) { + foreach($tweet->entities->urls as $url) { + $entry['content']['text'] = str_replace($url->url, $url->expanded_url, $entry['content']['text']); + } + } + + $entry['author'] = [ + 'type' => 'card', + 'url' => 'https://twitter.com/'.$tweet->user->screen_name, + 'name' => $tweet->user->name, + 'nickname' => $tweet->user->screen_name, + 'photo' => $tweet->user->profile_image_url_https + ]; + + if($tweet->user->url) { + $entry['author']['url'] = $tweet->user->entities->url->urls[0]->expanded_url; + } + + if($tweet->entities->hashtags) { + $entry['category'] = []; + foreach($tweet->entities->hashtags as $tag) { + $entry['category'][] = $tag->text; + } + } + + return $entry; +} + diff --git a/public/css/style.css b/public/css/style.css index 5e6556c..7984fe5 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -200,4 +200,31 @@ body { .notice-pad { margin-top: 20px; - } \ No newline at end of file + } + + + +.glyphicon-spin { + -webkit-animation: spin 1000ms infinite linear; + animation: spin 1000ms infinite linear; +} +@-webkit-keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} +@keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} diff --git a/views/new-post.php b/views/new-post.php index 21571ec..68460d0 100644 --- a/views/new-post.php +++ b/views/new-post.php @@ -3,29 +3,45 @@
-
-
140
- - +
+ + Reply
- - +
140
+ +
-
- +
+
-
- +
+
- + enter photo url @@ -39,7 +55,7 @@
- +
syndication_targets) { @@ -62,7 +78,7 @@
- + @@ -93,36 +109,6 @@
test_response) ?>
- -
-

Clicking "Post" will post this note to your Micropub endpoint. Below is some information about the request that will be made.

- - - - - - - - - - - - - - - media_endpoint): ?> - - - - - - - - - -
me (should be your URL)
scopemicropub_scope ?> (should be a space-separated list of permissions including "post")
micropub endpointmicropub_endpoint ?> (should be a URL)
media endpointmedia_endpoint ?> (should be a URL)
access tokenString of length micropub_access_token) ?>micropub_access_token) > 0) ? (', ending in ' . substr($this->micropub_access_token, -7) . '') : '' ?> (should be greater than length 0)
-
-
Add to Home Screen @@ -131,6 +117,10 @@