From 8b0b38ea0c7d114846ba9f10073f4978b9d9b1fc Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Sat, 7 Jun 2014 10:27:48 +0200 Subject: [PATCH] App.net: New connector that will be expanded to a bidirectional connector in the future. --- appnet/AppDotNet.php | 1647 ++++++++++++++++++++++ appnet/DigiCertHighAssuranceEVRootCA.pem | 23 + appnet/appnet.css | 29 + appnet/appnet.php | 331 +++++ appnetpost/appnetpost.css | 0 5 files changed, 2030 insertions(+) create mode 100644 appnet/AppDotNet.php create mode 100644 appnet/DigiCertHighAssuranceEVRootCA.pem create mode 100755 appnet/appnet.css create mode 100644 appnet/appnet.php mode change 100755 => 100644 appnetpost/appnetpost.css diff --git a/appnet/AppDotNet.php b/appnet/AppDotNet.php new file mode 100644 index 000000000..323613145 --- /dev/null +++ b/appnet/AppDotNet.php @@ -0,0 +1,1647 @@ +_clientId = $client_id; + $this->_clientSecret = $client_secret; + + // if the digicert certificate exists in the same folder as this file, + // remember that fact for later + if (file_exists(dirname(__FILE__).'/DigiCertHighAssuranceEVRootCA.pem')) { + $this->_sslCA = dirname(__FILE__).'/DigiCertHighAssuranceEVRootCA.pem'; + } + } + + /** + * Set whether or not to strip Envelope Response (meta) information + * This option will be deprecated in the future. Is it to allow + * a stepped migration path between code expecting the old behavior + * and new behavior. When not stripped, you still can use the proper + * method to pull the meta information. Please start converting your code ASAP + */ + public function includeResponseEnvelope() { + $this->_stripResponseEnvelope=false; + } + + /** + * Construct the proper Auth URL for the user to visit and either grant + * or not access to your app. Usually you would place this as a link for + * the user to client, or a redirect to send them to the auth URL. + * Also can be called after authentication for additional scopes + * @param string $callbackUri Where you want the user to be directed + * after authenticating with App.net. This must be one of the URIs + * allowed by your App.net application settings. + * @param array $scope An array of scopes (permissions) you wish to obtain + * from the user. Currently options are stream, email, write_post, follow, + * messages, and export. If you don't specify anything, you'll only receive + * access to the user's basic profile (the default). + */ + public function getAuthUrl($callback_uri,$scope=null) { + + // construct an authorization url based on our client id and other data + $data = array( + 'client_id'=>$this->_clientId, + 'response_type'=>'code', + 'redirect_uri'=>$callback_uri, + ); + + $url = $this->_authUrl; + if ($this->_accessToken) { + $url .= 'authorize?'; + } else { + $url .= 'authenticate?'; + } + $url .= $this->buildQueryString($data); + + if ($scope) { + $url .= '&scope='.implode('+',$scope); + } + + // return the constructed url + return $url; + } + + /** + * Call this after they return from the auth page, or anytime you need the + * token. For example, you could store it in a database and use + * setAccessToken() later on to return on behalf of the user. + */ + public function getAccessToken($callback_uri) { + // if there's no access token set, and they're returning from + // the auth page with a code, use the code to get a token + if (!$this->_accessToken && isset($_GET['code']) && $_GET['code']) { + + // construct the necessary elements to get a token + $data = array( + 'client_id'=>$this->_clientId, + 'client_secret'=>$this->_clientSecret, + 'grant_type'=>'authorization_code', + 'redirect_uri'=>$callback_uri, + 'code'=>$_GET['code'] + ); + + // try and fetch the token with the above data + $res = $this->httpReq('post',$this->_authUrl.'access_token', $data); + + // store it for later + $this->_accessToken = $res['access_token']; + $this->_username = $res['username']; + $this->_user_id = $res['user_id']; + } + + // return what we have (this may be a token, or it may be nothing) + return $this->_accessToken; + } + + /** + * Check the scope of current token to see if it has required scopes + * has to be done after a check + */ + public function checkScopes($app_scopes) { + if (!count($this->_scopes)) { + return -1; // _scope is empty + } + $missing=array(); + foreach($app_scopes as $scope) { + if (!in_array($scope,$this->_scopes)) { + if ($scope=='public_messages') { + // messages works for public_messages + if (in_array('messages',$this->_scopes)) { + // if we have messages in our scopes + continue; + } + } + $missing[]=$scope; + } + } + // identify the ones missing + if (count($missing)) { + // do something + return $missing; + } + return 0; // 0 missing + } + + /** + * Set the access token (eg: after retrieving it from offline storage) + * @param string $token A valid access token you're previously received + * from calling getAccessToken(). + */ + public function setAccessToken($token) { + $this->_accessToken = $token; + } + + /** + * Deauthorize the current token (delete your authorization from the API) + * Generally this is useful for logging users out from a web app, so they + * don't get automatically logged back in the next time you redirect them + * to the authorization URL. + */ + public function deauthorizeToken() { + return $this->httpReq('delete',$this->_baseUrl.'token'); + } + + /** + * Retrieve an app access token from the app.net API. This allows you + * to access the API without going through the user access flow if you + * just want to (eg) consume global. App access tokens are required for + * some actions (like streaming global). DO NOT share the return value + * of this function with any user (or save it in a cookie, etc). This + * is considered secret info for your app only. + * @return string The app access token + */ + public function getAppAccessToken() { + + // construct the necessary elements to get a token + $data = array( + 'client_id'=>$this->_clientId, + 'client_secret'=>$this->_clientSecret, + 'grant_type'=>'client_credentials', + ); + + // try and fetch the token with the above data + $res = $this->httpReq('post',$this->_authUrl.'access_token', $data); + + // store it for later + $this->_appAccessToken = $res['access_token']; + $this->_accessToken = $res['access_token']; + $this->_username = null; + $this->_user_id = null; + + return $this->_accessToken; + } + + /** + * Returns the total number of requests you're allowed within the + * alloted time period. + * @see getRateLimitReset() + */ + public function getRateLimit() { + return $this->_rateLimit; + } + + /** + * The number of requests you have remaining within the alloted time period + * @see getRateLimitReset() + */ + public function getRateLimitRemaining() { + return $this->_rateLimitRemaining; + } + + /** + * The number of seconds remaining in the alloted time period. + * When this time is up you'll have getRateLimit() available again. + */ + public function getRateLimitReset() { + return $this->_rateLimitReset; + } + + /** + * The scope the user has + */ + public function getScope() { + return $this->_scope; + } + + /** + * Internal function, parses out important information App.net adds + * to the headers. + */ + protected function parseHeaders($response) { + // take out the headers + // set internal variables + // return the body/content + $this->_rateLimit = null; + $this->_rateLimitRemaining = null; + $this->_rateLimitReset = null; + $this->_scope = null; + + $response = explode("\r\n\r\n",$response,2); + $headers = $response[0]; + + if($headers == 'HTTP/1.1 100 Continue') { + $response = explode("\r\n\r\n",$response[1],2); + $headers = $response[0]; + } + + if (isset($response[1])) { + $content = $response[1]; + } + else { + $content = null; + } + + // this is not a good way to parse http headers + // it will not (for example) take into account multiline headers + // but what we're looking for is pretty basic, so we can ignore those shortcomings + $headers = explode("\r\n",$headers); + foreach ($headers as $header) { + $header = explode(': ',$header,2); + if (count($header)<2) { + continue; + } + list($k,$v) = $header; + switch ($k) { + case 'X-RateLimit-Remaining': + $this->_rateLimitRemaining = $v; + break; + case 'X-RateLimit-Limit': + $this->_rateLimit = $v; + break; + case 'X-RateLimit-Reset': + $this->_rateLimitReset = $v; + break; + case 'X-OAuth-Scopes': + $this->_scope = $v; + $this->_scopes=explode(',',$v); + break; + } + } + return $content; + } + + /** + * Internal function. Used to turn things like TRUE into 1, and then + * calls http_build_query. + */ + protected function buildQueryString($array) { + foreach ($array as $k=>&$v) { + if ($v===true) { + $v = '1'; + } + elseif ($v===false) { + $v = '0'; + } + unset($v); + } + return http_build_query($array); + } + + + /** + * Internal function to handle all + * HTTP requests (POST,PUT,GET,DELETE) + */ + protected function httpReq($act, $req, $params=array(),$contentType='application/x-www-form-urlencoded') { + $ch = curl_init($req); + $headers = array(); + if($act != 'get') { + curl_setopt($ch, CURLOPT_POST, true); + // if they passed an array, build a list of parameters from it + if (is_array($params) && $act != 'post-raw') { + $params = $this->buildQueryString($params); + } + curl_setopt($ch, CURLOPT_POSTFIELDS, $params); + $headers[] = "Content-Type: ".$contentType; + } + if($act != 'post' && $act != 'post-raw') { + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, strtoupper($act)); + } + if($act == 'get' && isset($params['access_token'])) { + $headers[] = 'Authorization: Bearer '.$params['access_token']; + } + else if ($this->_accessToken) { + $headers[] = 'Authorization: Bearer '.$this->_accessToken; + } + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLINFO_HEADER_OUT, true); + curl_setopt($ch, CURLOPT_HEADER, true); + if ($this->_sslCA) { + curl_setopt($ch, CURLOPT_CAINFO, $this->_sslCA); + } + $this->_last_response = curl_exec($ch); + $this->_last_request = curl_getinfo($ch,CURLINFO_HEADER_OUT); + $http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + if ($http_status==0) { + throw new AppDotNetException('Unable to connect to '.$req); + } + if ($http_status<200 || $http_status>=300) { + throw new AppDotNetException('HTTP error '.$this->_last_response); + } + if ($this->_last_request===false) { + if (!curl_getinfo($ch,CURLINFO_SSL_VERIFYRESULT)) { + throw new AppDotNetException('SSL verification failed, connection terminated.'); + } + } + $response = $this->parseHeaders($this->_last_response); + $response = json_decode($response,true); + + if (isset($response['meta'])) { + if (isset($response['meta']['max_id'])) { + $this->_maxid=$response['meta']['max_id']; + $this->_minid=$response['meta']['min_id']; + } + if (isset($response['meta']['more'])) { + $this->_more=$response['meta']['more']; + } + if (isset($response['meta']['marker'])) { + $this->_last_marker=$response['meta']['marker']; + } + } + + // look for errors + if (isset($response['error'])) { + if (is_array($response['error'])) { + throw new AppDotNetException($response['error']['message'], + $response['error']['code']); + } + else { + throw new AppDotNetException($response['error']); + } + } + + // look for response migration errors + elseif (isset($response['meta']) && isset($response['meta']['error_message'])) { + throw new AppDotNetException($response['meta']['error_message'],$response['meta']['code']); + } + + // if we've received a migration response, handle it and return data only + elseif ($this->_stripResponseEnvelope && isset($response['meta']) && isset($response['data'])) { + return $response['data']; + } + + // else non response migration response, just return it + else { + return $response; + } + } + + + /** + * Get max_id from last meta response data envelope + */ + public function getResponseMaxID() { + return $this->_maxid; + } + + /** + * Get min_id from last meta response data envelope + */ + public function getResponseMinID() { + return $this->_minid; + } + + /** + * Get more from last meta response data envelope + */ + public function getResponseMore() { + return $this->_more; + } + + /** + * Get marker from last meta response data envelope + */ + public function getResponseMarker() { + return $this->_last_marker; + } + + /** + * Fetch API configuration object + */ + public function getConfig() { + return $this->httpReq('get',$this->_baseUrl.'config'); + } + + /** + * Return the Filters for the current user. + */ + public function getAllFilters() { + return $this->httpReq('get',$this->_baseUrl.'filters'); + } + + /** + * Create a Filter for the current user. + * @param string $name The name of the new filter + * @param array $filters An associative array of filters to be applied. + * This may change as the API evolves, as of this writing possible + * values are: user_ids, hashtags, link_domains, and mention_user_ids. + * You will need to provide at least one filter name=>value pair. + */ + public function createFilter($name='New filter', $filters=array()) { + $filters['name'] = $name; + return $this->httpReq('post',$this->_baseUrl.'filters',$filters); + } + + /** + * Returns a specific Filter object. + * @param integer $filter_id The ID of the filter you wish to retrieve. + */ + public function getFilter($filter_id=null) { + return $this->httpReq('get',$this->_baseUrl.'filters/'.urlencode($filter_id)); + } + + /** + * Delete a Filter. The Filter must belong to the current User. + * @return object Returns the deleted Filter on success. + */ + public function deleteFilter($filter_id=null) { + return $this->httpReq('delete',$this->_baseUrl.'filters/'.urlencode($filter_id)); + } + + /** + * Process user description, message or post text. + * Mentions and hashtags will be parsed out of the + * text, as will bare URLs. To create a link in the text without using a + * bare URL, include the anchor text in the object text and include a link + * entity in the function call. + * @param string $text The text of the description/message/post + * @param array $data An associative array of optional post data. This + * will likely change as the API evolves, as of this writing allowed keys are: + * reply_to, and annotations. "annotations" may be a complex object represented + * by an associative array. + * @param array $params An associative array of optional data to be included + * in the URL (such as 'include_annotations' and 'include_machine') + * @return array An associative array representing the post. + */ + public function processText($text=null, $data = array(), $params = array()) { + $data['text'] = $text; + $json = json_encode($data); + $qs = ''; + if (!empty($params)) { + $qs = '?'.$this->buildQueryString($params); + } + return $this->httpReq('post',$this->_baseUrl.'text/process'.$qs, $json, 'application/json'); + } + + /** + * Create a new Post object. Mentions and hashtags will be parsed out of the + * post text, as will bare URLs. To create a link in a post without using a + * bare URL, include the anchor text in the post's text and include a link + * entity in the post creation call. + * @param string $text The text of the post + * @param array $data An associative array of optional post data. This + * will likely change as the API evolves, as of this writing allowed keys are: + * reply_to, and annotations. "annotations" may be a complex object represented + * by an associative array. + * @param array $params An associative array of optional data to be included + * in the URL (such as 'include_annotations' and 'include_machine') + * @return array An associative array representing the post. + */ + public function createPost($text=null, $data = array(), $params = array()) { + $data['text'] = $text; + + $json = json_encode($data); + $qs = ''; + if (!empty($params)) { + $qs = '?'.$this->buildQueryString($params); + } + return $this->httpReq('post',$this->_baseUrl.'posts'.$qs, $json, 'application/json'); + } + + /** + * Returns a specific Post. + * @param integer $post_id The ID of the post to retrieve + * @param array $params An associative array of optional general parameters. + * This will likely change as the API evolves, as of this writing allowed keys + * are: include_annotations. + * @return array An associative array representing the post + */ + public function getPost($post_id=null,$params = array()) { + return $this->httpReq('get',$this->_baseUrl.'posts/'.urlencode($post_id) + .'?'.$this->buildQueryString($params)); + } + + /** + * Delete a Post. The current user must be the same user who created the Post. + * It returns the deleted Post on success. + * @param integer $post_id The ID of the post to delete + * @param array An associative array representing the post that was deleted + */ + public function deletePost($post_id=null) { + return $this->httpReq('delete',$this->_baseUrl.'posts/'.urlencode($post_id)); + } + + /** + * Retrieve the Posts that are 'in reply to' a specific Post. + * @param integer $post_id The ID of the post you want to retrieve replies for. + * @param array $params An associative array of optional general parameters. + * This will likely change as the API evolves, as of this writing allowed keys + * are: count, before_id, since_id, include_muted, include_deleted, + * include_directed_posts, and include_annotations. + * @return An array of associative arrays, each representing a single post. + */ + public function getPostReplies($post_id=null,$params = array()) { + return $this->httpReq('get',$this->_baseUrl.'posts/'.urlencode($post_id) + .'/replies?'.$this->buildQueryString($params)); + } + + /** + * Get the most recent Posts created by a specific User in reverse + * chronological order (most recent first). + * @param mixed $user_id Either the ID of the user you wish to retrieve posts by, + * or the string "me", which will retrieve posts for the user you're authenticated + * as. + * @param array $params An associative array of optional general parameters. + * This will likely change as the API evolves, as of this writing allowed keys + * are: count, before_id, since_id, include_muted, include_deleted, + * include_directed_posts, and include_annotations. + * @return An array of associative arrays, each representing a single post. + */ + public function getUserPosts($user_id='me', $params = array()) { + return $this->httpReq('get',$this->_baseUrl.'users/'.urlencode($user_id) + .'/posts?'.$this->buildQueryString($params)); + } + + /** + * Get the most recent Posts mentioning by a specific User in reverse + * chronological order (newest first). + * @param mixed $user_id Either the ID of the user who is being mentioned, or + * the string "me", which will retrieve posts for the user you're authenticated + * as. + * @param array $params An associative array of optional general parameters. + * This will likely change as the API evolves, as of this writing allowed keys + * are: count, before_id, since_id, include_muted, include_deleted, + * include_directed_posts, and include_annotations. + * @return An array of associative arrays, each representing a single post. + */ + public function getUserMentions($user_id='me',$params = array()) { + return $this->httpReq('get',$this->_baseUrl.'users/' + .urlencode($user_id).'/mentions?'.$this->buildQueryString($params)); + } + + /** + * Return the 20 most recent posts from the current User and + * the Users they follow. + * @param array $params An associative array of optional general parameters. + * This will likely change as the API evolves, as of this writing allowed keys + * are: count, before_id, since_id, include_muted, include_deleted, + * include_directed_posts, and include_annotations. + * @return An array of associative arrays, each representing a single post. + */ + public function getUserStream($params = array()) { + return $this->httpReq('get',$this->_baseUrl.'posts/stream?'.$this->buildQueryString($params)); + } + + /** + * Returns a specific user object. + * @param mixed $user_id The ID of the user you want to retrieve, or the string + * "me" to retrieve data for the users you're currently authenticated as. + * @param array $params An associative array of optional general parameters. + * This will likely change as the API evolves, as of this writing allowed keys + * are: include_annotations|include_user_annotations. + * @return array An associative array representing the user data. + */ + public function getUser($user_id='me', $params = array()) { + return $this->httpReq('get',$this->_baseUrl.'users/'.urlencode($user_id) + .'?'.$this->buildQueryString($params)); + } + + /** + * Returns multiple users request by an array of user ids + * @param array $params An associative array of optional general parameters. + * This will likely change as the API evolves, as of this writing allowed keys + * are: include_annotations|include_user_annotations. + * @return array An associative array representing the users data. + */ + public function getUsers($user_arr, $params = array()) { + return $this->httpReq('get',$this->_baseUrl.'users?ids='.join(',',$user_arr) + .'&'.$this->buildQueryString($params)); + } + + /** + * Add the specified user ID to the list of users followed. + * Returns the User object of the user being followed. + * @param integer $user_id The user ID of the user to follow. + * @return array An associative array representing the user you just followed. + */ + public function followUser($user_id=null) { + return $this->httpReq('post',$this->_baseUrl.'users/'.urlencode($user_id).'/follow'); + } + + /** + * Removes the specified user ID to the list of users followed. + * Returns the User object of the user being unfollowed. + * @param integer $user_id The user ID of the user to unfollow. + * @return array An associative array representing the user you just unfollowed. + */ + public function unfollowUser($user_id=null) { + return $this->httpReq('delete',$this->_baseUrl.'users/'.urlencode($user_id).'/follow'); + } + + /** + * Returns an array of User objects the specified user is following. + * @param mixed $user_id Either the ID of the user being followed, or + * the string "me", which will retrieve posts for the user you're authenticated + * as. + * @return array An array of associative arrays, each representing a single + * user following $user_id + */ + public function getFollowing($user_id='me') { + return $this->httpReq('get',$this->_baseUrl.'users/'.$user_id.'/following'); + } + + /** + * Returns an array of User objects for users following the specified user. + * @param mixed $user_id Either the ID of the user being followed, or + * the string "me", which will retrieve posts for the user you're authenticated + * as. + * @return array An array of associative arrays, each representing a single + * user following $user_id + */ + public function getFollowers($user_id='me') { + return $this->httpReq('get',$this->_baseUrl.'users/'.$user_id.'/followers'); + } + + /** + * Return Posts matching a specific #hashtag. + * @param string $hashtag The hashtag you're looking for. + * @param array $params An associative array of optional general parameters. + * This will likely change as the API evolves, as of this writing allowed keys + * are: count, before_id, since_id, include_muted, include_deleted, + * include_directed_posts, and include_annotations. + * @return An array of associative arrays, each representing a single post. + */ + public function searchHashtags($hashtag=null, $params = array()) { + return $this->httpReq('get',$this->_baseUrl.'posts/tag/' + .urlencode($hashtag).'?'.$this->buildQueryString($params)); + } + + /** + * Retrieve a list of all public Posts on App.net, often referred to as the + * global stream. + * @param array $params An associative array of optional general parameters. + * This will likely change as the API evolves, as of this writing allowed keys + * are: count, before_id, since_id, include_muted, include_deleted, + * include_directed_posts, and include_annotations. + * @return An array of associative arrays, each representing a single post. + */ + public function getPublicPosts($params = array()) { + return $this->httpReq('get',$this->_baseUrl.'posts/stream/global?'.$this->buildQueryString($params)); + } + + /** + * List User interactions + */ + public function getMyInteractions($params = array()) { + return $this->httpReq('get',$this->_baseUrl.'users/me/interactions?'.$this->buildQueryString($params)); + } + + /** + * Retrieve a user's user ID by specifying their username. + * Now supported by the API. We use the API if we have a token + * Otherwise we scrape the alpha.app.net site for the info. + * @param string $username The username of the user you want the ID of, without + * an @ symbol at the beginning. + * @return integer The user's user ID + */ + public function getIdByUsername($username=null) { + if ($this->_accessToken) { + $res=$this->httpReq('get',$this->_baseUrl.'users/@'.$username); + $user_id=$res['data']['id']; + } else { + $ch = curl_init('https://alpha.app.net/'.urlencode(strtolower($username))); + curl_setopt($ch, CURLOPT_POST, false); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch,CURLOPT_USERAGENT, + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:7.0.1) Gecko/20100101 Firefox/7.0.1'); + $response = curl_exec($ch); + curl_close($ch); + $temp = explode('title="User Id ',$response); + $temp2 = explode('"',$temp[1]); + $user_id = $temp2[0]; + } + return $user_id; + } + + /** + * Mute a user + * @param integer $user_id The user ID to mute + */ + public function muteUser($user_id=null) { + return $this->httpReq('post',$this->_baseUrl.'users/'.urlencode($user_id).'/mute'); + } + + /** + * Unmute a user + * @param integer $user_id The user ID to unmute + */ + public function unmuteUser($user_id=null) { + return $this->httpReq('delete',$this->_baseUrl.'users/'.urlencode($user_id).'/mute'); + } + + /** + * List the users muted by the current user + * @return array An array of associative arrays, each representing one muted user. + */ + public function getMuted() { + return $this->httpReq('get',$this->_baseUrl.'users/me/muted'); + } + + /** + * Star a post + * @param integer $post_id The post ID to star + */ + public function starPost($post_id=null) { + return $this->httpReq('post',$this->_baseUrl.'posts/'.urlencode($post_id).'/star'); + } + + /** + * Unstar a post + * @param integer $post_id The post ID to unstar + */ + public function unstarPost($post_id=null) { + return $this->httpReq('delete',$this->_baseUrl.'posts/'.urlencode($post_id).'/star'); + } + + /** + * List the posts starred by the current user + * @param array $params An associative array of optional general parameters. + * This will likely change as the API evolves, as of this writing allowed keys + * are: count, before_id, since_id, include_muted, include_deleted, + * include_directed_posts, and include_annotations. + * See https://github.com/appdotnet/api-spec/blob/master/resources/posts.md#general-parameters + * @return array An array of associative arrays, each representing a single + * user who has starred a post + */ + public function getStarred($user_id='me', $params = array()) { + return $this->httpReq('get',$this->_baseUrl.'users/'.urlencode($user_id).'/stars' + .'?'.$this->buildQueryString($params)); + } + + /** + * List the users who have starred a post + * @param integer $post_id the post ID to get stars from + * @return array An array of associative arrays, each representing one user. + */ + public function getStars($post_id=null) { + return $this->httpReq('get',$this->_baseUrl.'posts/'.urlencode($post_id).'/stars'); + } + + /** + * Returns an array of User objects of users who reposted the specified post. + * @param integer $post_id the post ID to + * @return array An array of associative arrays, each representing a single + * user who reposted $post_id + */ + public function getReposters($post_id){ + return $this->httpReq('get',$this->_baseUrl.'posts/'.urlencode($post_id).'/reposters'); + } + + /** + * Repost an existing Post object. + * @param integer $post_id The id of the post + * @return not a clue + */ + public function repost($post_id){ + return $this->httpReq('post',$this->_baseUrl.'posts/'.urlencode($post_id).'/repost'); + } + + /** + * Delete a post that the user has reposted. + * @param integer $post_id The id of the post + * @return not a clue + */ + public function deleteRepost($post_id){ + return $this->httpReq('delete',$this->_baseUrl.'posts/'.urlencode($post_id).'/repost'); + } + + /** + * List the posts who match a specific search term + * @param array $params a list of filter, search query, and general Post parameters + * see: https://developers.app.net/reference/resources/post/search/ + * @param string $query The search query. Supports + * normal search terms. Searches post text. + * @return array An array of associative arrays, each representing one post. + * or false on error + */ + public function searchPosts($params = array(), $query='', $order='default') { + if (!is_array($params)) { + return false; + } + if (!empty($query)) { + $params['query']=$query; + } + if ($order=='default') { + if (!empty($query)) { + $params['order']='score'; + } else { + $params['order']='id'; + } + } + return $this->httpReq('get',$this->_baseUrl.'posts/search?'.$this->buildQueryString($params)); + } + + + /** + * List the users who match a specific search term + * @param string $search The search query. Supports @username or #tag searches as + * well as normal search terms. Searches username, display name, bio information. + * Does not search posts. + * @return array An array of associative arrays, each representing one user. + */ + public function searchUsers($search="") { + return $this->httpReq('get',$this->_baseUrl.'users/search?q='.urlencode($search)); + } + + /** + * Return the 20 most recent posts for a stream using a valid Token + * @param array $params An associative array of optional general parameters. + * This will likely change as the API evolves, as of this writing allowed keys + * are: count, before_id, since_id, include_muted, include_deleted, + * include_directed_posts, and include_annotations. + * @return An array of associative arrays, each representing a single post. + */ + public function getTokenStream($params = array()) { + if ($params['access_token']) { + return $this->httpReq('get',$this->_baseUrl.'posts/stream?'.$this->buildQueryString($params),$params); + } else { + return $this->httpReq('get',$this->_baseUrl.'posts/stream?'.$this->buildQueryString($params)); + } + } + + /** + * Get a user object by username + * @param string $name the @name to get + * @return array representing one user + */ + public function getUserByName($name=null) { + return $this->httpReq('get',$this->_baseUrl.'users/@'.$name); + } + + /** + * Return the 20 most recent Posts from the current User's personalized stream + * and mentions stream merged into one stream. + * @param array $params An associative array of optional general parameters. + * This will likely change as the API evolves, as of this writing allowed keys + * are: count, before_id, since_id, include_muted, include_deleted, + * include_directed_posts, and include_annotations. + * @return An array of associative arrays, each representing a single post. + */ + public function getUserUnifiedStream($params = array()) { + return $this->httpReq('get',$this->_baseUrl.'posts/stream/unified?'.$this->buildQueryString($params)); + } + + /** + * Update Profile Data via JSON + * @data array containing user descriptors + */ + public function updateUserData($data = array(), $params = array()) { + $json = json_encode($data); + return $this->httpReq('put',$this->_baseUrl.'users/me'.'?'. + $this->buildQueryString($params), $json, 'application/json'); + } + + /** + * Update a user image + * @which avatar|cover + * @image path reference to image + */ + protected function updateUserImage($which = 'avatar', $image = null) { + $data = array($which=>"@$image"); + return $this->httpReq('post-raw',$this->_baseUrl.'users/me/'.$which, $data, 'multipart/form-data'); + } + + public function updateUserAvatar($avatar = null) { + if($avatar != null) + return $this->updateUserImage('avatar', $avatar); + } + + public function updateUserCover($cover = null) { + if($cover != null) + return $this->updateUserImage('cover', $cover); + } + + /** + * update stream marker + */ + public function updateStreamMarker($data = array()) { + $json = json_encode($data); + return $this->httpReq('post',$this->_baseUrl.'posts/marker', $json, 'application/json'); + } + + /** + * get a page of current user subscribed channels + */ + public function getUserSubscriptions($params = array()) { + return $this->httpReq('get',$this->_baseUrl.'channels?'.$this->buildQueryString($params)); + } + + /** + * get user channels + */ + public function getMyChannels($params = array()) { + return $this->httpReq('get',$this->_baseUrl.'channels/me?'.$this->buildQueryString($params)); + } + + /** + * create a channel + * note: you cannot create a channel with type=net.app.core.pm (see createMessage) + */ + public function createChannel($data = array()) { + $json = json_encode($data); + return $this->httpReq('post',$this->_baseUrl.'channels'.($pm?'/pm/messsages':''), $json, 'application/json'); + } + + /** + * get channelid info + */ + public function getChannel($channelid, $params = array()) { + return $this->httpReq('get',$this->_baseUrl.'channels/'.$channelid.'?'.$this->buildQueryString($params)); + } + + /** + * get multiple channels' info by an array of channelids + */ + public function getChannels($channels, $params = array()) { + return $this->httpReq('get',$this->_baseUrl.'channels?ids='.join(',',$channels).'&'.$this->buildQueryString($params)); + } + + /** + * update channelid + */ + public function updateChannel($channelid, $data = array()) { + $json = json_encode($data); + return $this->httpReq('put',$this->_baseUrl.'channels/'.$channelid, $json, 'application/json'); + } + + /** + * subscribe from channelid + */ + public function channelSubscribe($channelid) { + return $this->httpReq('post',$this->_baseUrl.'channels/'.$channelid.'/subscribe'); + } + + /** + * unsubscribe from channelid + */ + public function channelUnsubscribe($channelid) { + return $this->httpReq('delete',$this->_baseUrl.'channels/'.$channelid.'/subscribe'); + } + + /** + * get all user objects subscribed to channelid + */ + public function getChannelSubscriptions($channelid, $params = array()) { + return $this->httpReq('get',$this->_baseUrl.'channel/'.$channelid.'/subscribers?'.$this->buildQueryString($params)); + } + + /** + * get all user IDs subscribed to channelid + */ + public function getChannelSubscriptionsById($channelid) { + return $this->httpReq('get',$this->_baseUrl.'channel/'.$channelid.'/subscribers/ids'); + } + + + /** + * get a page of messages in channelid + */ + public function getMessages($channelid, $params = array()) { + return $this->httpReq('get',$this->_baseUrl.'channels/'.$channelid.'/messages?'.$this->buildQueryString($params)); + } + + /** + * create message + * @param $channelid numeric or "pm" for auto-chanenl (type=net.app.core.pm) + * @param $data array('text'=>'YOUR_MESSAGE') If a type=net.app.core.pm, then "destinations" key can be set to address as an array of people to send this PM too + */ + public function createMessage($channelid,$data) { + $json = json_encode($data); + return $this->httpReq('post',$this->_baseUrl.'channels/'.$channelid.'/messages', $json, 'application/json'); + } + + /** + * get message + */ + public function getMessage($channelid,$messageid) { + return $this->httpReq('get',$this->_baseUrl.'channels/'.$channelid.'/messages/'.$messageid); + } + + /** + * delete messsage + */ + public function deleteMessage($channelid,$messageid) { + return $this->httpReq('delete',$this->_baseUrl.'channels/'.$channelid.'/messages/'.$messageid); + } + + + /** + * Get Application Information + */ + public function getAppTokenInfo() { + // requires appAccessToken + if (!$this->_appAccessToken) { + $this->getAppAccessToken(); + } + // ensure request is made with our appAccessToken + $params['access_token']=$this->_appAccessToken; + return $this->httpReq('get',$this->_baseUrl.'token',$params); + } + + /** + * Get User Information + */ + public function getUserTokenInfo() { + return $this->httpReq('get',$this->_baseUrl.'token'); + } + + /** + * Get Application Authorized User IDs + */ + public function getAppUserIDs() { + // requires appAccessToken + if (!$this->_appAccessToken) { + $this->getAppAccessToken(); + } + // ensure request is made with our appAccessToken + $params['access_token']=$this->_appAccessToken; + return $this->httpReq('get',$this->_baseUrl.'apps/me/tokens/user_ids',$params); + } + + /** + * Get Application Authorized User Tokens + */ + public function getAppUserTokens() { + // requires appAccessToken + if (!$this->_appAccessToken) { + $this->getAppAccessToken(); + } + // ensure request is made with our appAccessToken + $params['access_token']=$this->_appAccessToken; + return $this->httpReq('get',$this->_baseUrl.'apps/me/tokens',$params); + } + + public function getLastRequest() { + return $this->_last_request; + } + public function getLastResponse() { + return $this->_last_response; + } + + /** + * Registers your function (or an array of object and method) to be called + * whenever an event is received via an open app.net stream. Your function + * will receive a single parameter, which is the object wrapper containing + * the meta and data. + * @param mixed A PHP callback (either a string containing the function name, + * or an array where the first element is the class/object and the second + * is the method). + */ + public function registerStreamFunction($function) { + $this->_streamCallback = $function; + } + + /** + * Opens a stream that's been created for this user/app and starts sending + * events/objects to your defined callback functions. You must define at + * least one callback function before opening a stream. + * @param mixed $stream Either a stream ID or the endpoint of a stream + * you've already created. This stream must exist and must be valid for + * your current access token. If you pass a stream ID, the library will + * make an API call to get the endpoint. + * + * This function will return immediately, but your callback functions + * will continue to receive events until you call closeStream() or until + * App.net terminates the stream from their end with an error. + * + * If you're disconnected due to a network error, the library will + * automatically attempt to reconnect you to the same stream, no action + * on your part is necessary for this. However if the app.net API returns + * an error, a reconnection attempt will not be made. + * + * Note there is no closeStream, because once you open a stream you + * can't stop it (unless you exit() or die() or throw an uncaught + * exception, or something else that terminates the script). + * @return boolean True + * @see createStream() + */ + public function openStream($stream) { + // if there's already a stream running, don't allow another + if ($this->_currentStream) { + throw new AppDotNetException('There is already a stream being consumed, only one stream can be consumed per AppDotNetStream instance'); + } + // must register a callback (or the exercise is pointless) + if (!$this->_streamCallback) { + throw new AppDotNetException('You must define your callback function using registerStreamFunction() before calling openStream'); + } + // if the stream is a numeric value, get the stream info from the api + if (is_numeric($stream)) { + $stream = $this->getStream($stream); + $this->_streamUrl = $stream['endpoint']; + } + else { + $this->_streamUrl = $stream; + } + // continue doing this until we get an error back or something...? + $this->httpStream('get',$this->_streamUrl); + + return true; + } + + /** + * Close the currently open stream. + * @return true; + */ + public function closeStream() { + if (!$this->_lastStreamActivity) { + // never opened + return; + } + if (!$this->_multiStream) { + throw new AppDotNetException('You must open a stream before calling closeStream()'); + } + curl_close($this->_currentStream); + curl_multi_remove_handle($this->_multiStream,$this->_currentStream); + curl_multi_close($this->_multiStream); + $this->_currentStream = null; + $this->_multiStream = null; + } + + /** + * Retrieve all streams for the current access token. + * @return array An array of stream definitions. + */ + public function getAllStreams() { + return $this->httpReq('get',$this->_baseUrl.'streams'); + } + + /** + * Returns a single stream specified by a stream ID. The stream must have been + * created with the current access token. + * @return array A stream definition + */ + public function getStream($streamId) { + return $this->httpReq('get',$this->_baseUrl.'streams/'.urlencode($streamId)); + } + + /** + * Creates a stream for the current app access token. + * + * @param array $objectTypes The objects you want to retrieve data for from the + * stream. At time of writing these can be 'post', 'star', and/or 'user_follow'. + * If you don't specify, all events will be retrieved. + */ + public function createStream($objectTypes=null) { + // default object types to everything + if (is_null($objectTypes)) { + $objectTypes = array('post','star','user_follow'); + } + $data = array( + 'object_types'=>$objectTypes, + 'type'=>'long_poll', + ); + $data = json_encode($data); + $response = $this->httpReq('post',$this->_baseUrl.'streams',$data,'application/json'); + return $response; + } + + /** + * Update stream for the current app access token + * + * @param integer $streamId The stream ID to update. This stream must have been + * created by the current access token. + * @param array $data allows object_types, type, filter_id and key to be updated. filter_id/key can be omitted + */ + public function updateStream($streamId,$data) { + // objectTypes is likely required + if (is_null($data['object_types'])) { + $data['object_types'] = array('post','star','user_follow'); + } + // type can still only be long_poll + if (is_null($data['type'])) { + $data['type']='long_poll'; + } + $data = json_encode($data); + $response = $this->httpReq('put',$this->_baseUrl.'streams/'.urlencode($streamId),$data,'application/json'); + return $response; + } + + /** + * Deletes a stream if you no longer need it. + * + * @param integer $streamId The stream ID to delete. This stream must have been + * created by the current access token. + */ + public function deleteStream($streamId) { + return $this->httpReq('delete',$this->_baseUrl.'streams/'.urlencode($streamId)); + } + + /** + * Deletes all streams created by the current access token. + */ + public function deleteAllStreams() { + return $this->httpReq('delete',$this->_baseUrl.'streams'); + } + + /** + * Internal function used to process incoming chunks from the stream. This is only + * public because it needs to be accessed by CURL. Do not call or use this function + * in your own code. + * @ignore + */ + public function httpStreamReceive($ch,$data) { + $this->_lastStreamActivity = time(); + $this->_streamBuffer .= $data; + if (!$this->_streamHeaders) { + $pos = strpos($this->_streamBuffer,"\r\n\r\n"); + if ($pos!==false) { + $this->_streamHeaders = substr($this->_streamBuffer,0,$pos); + $this->_streamBuffer = substr($this->_streamBuffer,$pos+4); + } + } + else { + $pos = strpos($this->_streamBuffer,"\r\n"); + while ($pos!==false) { + $command = substr($this->_streamBuffer,0,$pos); + $this->_streamBuffer = substr($this->_streamBuffer,$pos+2); + $command = json_decode($command,true); + if ($command) { + call_user_func($this->_streamCallback,$command); + } + $pos = strpos($this->_streamBuffer,"\r\n"); + } + } + return strlen($data); + } + + /** + * Opens a long lived HTTP connection to the app.net servers, and sends data + * received to the httpStreamReceive function. As a general rule you should not + * directly call this method, it's used by openStream(). + */ + protected function httpStream($act, $req, $params=array(),$contentType='application/x-www-form-urlencoded') { + if ($this->_currentStream) { + throw new AppDotNetException('There is already an open stream, you must close the existing one before opening a new one'); + } + $headers = array(); + $this->_streamBuffer = ''; + if ($this->_accessToken) { + $headers[] = 'Authorization: Bearer '.$this->_accessToken; + } + $this->_currentStream = curl_init($req); + curl_setopt($this->_currentStream, CURLOPT_HTTPHEADER, $headers); + curl_setopt($this->_currentStream, CURLOPT_RETURNTRANSFER, true); + curl_setopt($this->_currentStream, CURLINFO_HEADER_OUT, true); + curl_setopt($this->_currentStream, CURLOPT_HEADER, true); + if ($this->_sslCA) { + curl_setopt($this->_currentStream, CURLOPT_CAINFO, $this->_sslCA); + } + // every time we receive a chunk of data, forward it to httpStreamReceive + curl_setopt($this->_currentStream, CURLOPT_WRITEFUNCTION, array($this, "httpStreamReceive")); + + // curl_exec($ch); + // return; + + $this->_multiStream = curl_multi_init(); + $this->_lastStreamActivity = time(); + curl_multi_add_handle($this->_multiStream,$this->_currentStream); + } + + public function reconnectStream() { + $this->closeStream(); + $this->_connectFailCounter++; + // if we've failed a few times, back off + if ($this->_connectFailCounter>1) { + $sleepTime = pow(2,$this->_connectFailCounter); + // don't sleep more than 60 seconds + if ($sleepTime>60) { + $sleepTime = 60; + } + sleep($sleepTime); + } + $this->httpStream('get',$this->_streamUrl); + } + + /** + * Process an open stream for x microseconds, then return. This is useful if you want + * to be doing other things while processing the stream. If you just want to + * consume the stream without other actions, you can call processForever() instead. + * @param float @microseconds The number of microseconds to process for before + * returning. There are 1,000,000 microseconds in a second. + * + * @return void + */ + public function processStream($microseconds=null) { + if (!$this->_multiStream) { + throw new AppDotNetException('You must open a stream before calling processStream()'); + } + $start = microtime(true); + $active = null; + $inQueue = null; + $sleepFor = 0; + do { + // if we haven't received anything within 5.5 minutes, reconnect + // keepalives are sent every 5 minutes (measured on 2013-3-12 by @ryantharp) + if (time()-$this->_lastStreamActivity>=330) { + $this->reconnectStream(); + } + curl_multi_exec($this->_multiStream, $active); + if (!$active) { + $httpCode = curl_getinfo($this->_currentStream,CURLINFO_HTTP_CODE); + // don't reconnect on 400 errors + if ($httpCode>=400 && $httpCode<=499) { + throw new AppDotNetException('Received HTTP error '.$httpCode.' check your URL and credentials before reconnecting'); + } + $this->reconnectStream(); + } + // sleep for a max of 2/10 of a second + $timeSoFar = (microtime(true)-$start)*1000000; + $sleepFor = $this->streamingSleepFor; + if ($timeSoFar+$sleepFor>$microseconds) { + $sleepFor = $microseconds - $timeSoFar; + } + + if ($sleepFor>0) { + usleep($sleepFor); + } + } while ($timeSoFar+$sleepFor<$microseconds); + } + + /** + * Process an open stream forever. This function will never return, if you + * want to perform other actions while consuming the stream, you should use + * processFor() instead. + * @return void This function will never return + * @see processFor(); + */ + public function processStreamForever() { + while (true) { + $this->processStream(600); + } + } + + + /** + * Upload a file to a user's file store + * @param string $file A string containing the path of the file to upload. + * @param array $data Additional data about the file you're uploading. At the + * moment accepted keys are: mime-type, kind, type, name, public and annotations. + * - If you don't specify mime-type, ADNPHP will attempt to guess the mime type + * based on the file, however this isn't always reliable. + * - If you don't specify kind ADNPHP will attempt to determine if the file is + * an image or not. + * - If you don't specify name, ADNPHP will use the filename of the first + * parameter. + * - If you don't specify public, your file will be uploaded as a private file. + * - Type is REQUIRED. + * @param array $params An associative array of optional general parameters. + * This will likely change as the API evolves, as of this writing allowed keys + * are: include_annotations|include_file_annotations. + * @return array An associative array representing the file + */ + public function createFile($file, $data, $params=array()) { + if (!$file) { + throw new AppDotNetException('You must specify a path to a file'); + } + if (!file_exists($file)) { + throw new AppDotNetException('File path specified does not exist'); + } + if (!is_readable($file)) { + throw new AppDotNetException('File path specified is not readable'); + } + + if (!$data) { + $data = array(); + } + + if (!array_key_exists('type',$data) || !$data['type']) { + throw new AppDotNetException('Type is required when creating a file'); + } + + if (!array_key_exists('name',$data)) { + $data['name'] = basename($file); + } + + if (array_key_exists('mime-type',$data)) { + $mimeType = $data['mime-type']; + unset($data['mime-type']); + } + else { + $mimeType = null; + } + if (!array_key_exists('kind',$data)) { + $test = @getimagesize($path); + if ($test && array_key_exists('mime',$test)) { + $data['kind'] = 'image'; + if (!$mimeType) { + $mimeType = $test['mime']; + } + } + else { + $data['kind'] = 'other'; + } + } + if (!$mimeType) { + $finfo = finfo_open(FILEINFO_MIME_TYPE); + $mimeType = finfo_file($finfo, $file); + finfo_close($finfo); + } + if (!$mimeType) { + throw new AppDotNetException('Unable to determine mime type of file, try specifying it explicitly'); + } + if (!array_key_exists('public',$data) || !$data['public']) { + $public = false; + } + else { + $public = true; + } + + $data['content'] = "@$file;type=$mimeType"; + return $this->httpReq('post-raw',$this->_baseUrl.'files', $data, 'multipart/form-data'); + } + + + public function createFilePlaceholder($file = null, $params=array()) { + $name = basename($file); + $data = array('annotations' => $params['annotations'], 'kind' => $params['kind'], + 'name' => $name, 'type' => $params['metadata']); + $json = json_encode($data); + return $this->httpReq('post',$this->_baseUrl.'files', $json, 'application/json'); + } + + public function updateFileContent($fileid, $file) { + + $data = file_get_contents($file); + $finfo = finfo_open(FILEINFO_MIME_TYPE); + $mime = finfo_file($finfo, $file); + finfo_close($finfo); + + return $this->httpReq('put',$this->_baseUrl.'files/' . $fileid + .'/content', $data, $mime); + } + + /** + * Allows for file rename and annotation changes. + * @param integer $file_id The ID of the file to update + * @param array $params An associative array of file parameters. + * @return array An associative array representing the updated file + */ + public function updateFile($file_id=null, $params=array()) { + $data = array('annotations' => $params['annotations'] , 'name' => $params['name']); + $json = json_encode($data); + return $this->httpReq('put',$this->_baseUrl.'files/'.urlencode($file_id), $json, 'application/json'); + } + + /** + * Returns a specific File. + * @param integer $file_id The ID of the file to retrieve + * @param array $params An associative array of optional general parameters. + * This will likely change as the API evolves, as of this writing allowed keys + * are: include_annotations|include_file_annotations. + * @return array An associative array representing the file + */ + public function getFile($file_id=null,$params = array()) { + return $this->httpReq('get',$this->_baseUrl.'files/'.urlencode($file_id) + .'?'.$this->buildQueryString($params)); + } + + public function getFileContent($file_id=null,$params = array()) { + return $this->httpReq('get',$this->_baseUrl.'files/'.urlencode($file_id) + .'/content?'.$this->buildQueryString($params)); + } + + /** $file_key : derived_file_key */ + public function getDerivedFileContent($file_id=null,$file_key=null,$params = array()) { + return $this->httpReq('get',$this->_baseUrl.'files/'.urlencode($file_id) + .'/content/'.urlencode($file_key) + .'?'.$this->buildQueryString($params)); + } + + /** + * Returns file objects. + * @param array $file_ids The IDs of the files to retrieve + * @param array $params An associative array of optional general parameters. + * This will likely change as the API evolves, as of this writing allowed keys + * are: include_annotations|include_file_annotations. + * @return array An associative array representing the file data. + */ + public function getFiles($file_ids=array(), $params = array()) { + $ids = ''; + foreach($file_ids as $id) { + $ids .= $id . ','; + } + $params['ids'] = substr($ids, 0, -1); + return $this->httpReq('get',$this->_baseUrl.'files' + .'?'.$this->buildQueryString($params)); + } + + /** + * Returns a user's file objects. + * @param array $params An associative array of optional general parameters. + * This will likely change as the API evolves, as of this writing allowed keys + * are: include_annotations|include_file_annotations|include_user_annotations. + * @return array An associative array representing the file data. + */ + public function getUserFiles($params = array()) { + return $this->httpReq('get',$this->_baseUrl.'users/me/files' + .'?'.$this->buildQueryString($params)); + } + + /** + * Delete a File. The current user must be the same user who created the File. + * It returns the deleted File on success. + * @param integer $file_id The ID of the file to delete + * @return array An associative array representing the file that was deleted + */ + public function deleteFile($file_id=null) { + return $this->httpReq('delete',$this->_baseUrl.'files/'.urlencode($file_id)); + } + +} + +class AppDotNetException extends Exception {} diff --git a/appnet/DigiCertHighAssuranceEVRootCA.pem b/appnet/DigiCertHighAssuranceEVRootCA.pem new file mode 100644 index 000000000..9e6810ab7 --- /dev/null +++ b/appnet/DigiCertHighAssuranceEVRootCA.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug +RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm ++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW +PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM +xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB +Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 +hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg +EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA +FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec +nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z +eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF +hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 +Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep ++OkuE6N36B9K +-----END CERTIFICATE----- diff --git a/appnet/appnet.css b/appnet/appnet.css new file mode 100755 index 000000000..231f4306a --- /dev/null +++ b/appnet/appnet.css @@ -0,0 +1,29 @@ +#appnet-disconnect-label, #appnet-token-label, +#appnet-enable-label, #appnet-bydefault-label, +#appnet-clientid-label, #appnet-clientsecret-label { + float: left; + width: 200px; + margin-top: 10px; +} + +#appnet-disconnect, #appnet-token, +#appnet-checkbox, #appnet-bydefault, +#appnet-clientid, #appnet-clientsecret { + float: left; + margin-top: 10px; +} + +#appnet-submit { + margin-top: 15px; +} + +#appnet-avatar { + float: left; + width: 48px; + height: 48px; + padding: 2px; +} +#appnet-info-block { + height: 52px; + vertical-align: middle; +} diff --git a/appnet/appnet.php b/appnet/appnet.php new file mode 100644 index 000000000..e30ecdca4 --- /dev/null +++ b/appnet/appnet.php @@ -0,0 +1,331 @@ + + */ + +function appnet_install() { + register_hook('post_local', 'addon/appnet/appnet.php', 'appnet_post_local'); + register_hook('notifier_normal', 'addon/appnet/appnet.php', 'appnet_send'); + register_hook('jot_networks', 'addon/appnet/appnet.php', 'appnet_jot_nets'); + register_hook('connector_settings', 'addon/appnet/appnet.php', 'appnet_settings'); + register_hook('connector_settings_post', 'addon/appnet/appnet.php', 'appnet_settings_post'); +} + + +function appnet_uninstall() { + unregister_hook('post_local', 'addon/appnet/appnet.php', 'appnet_post_local'); + unregister_hook('notifier_normal', 'addon/appnet/appnet.php', 'appnet_send'); + unregister_hook('jot_networks', 'addon/appnet/appnet.php', 'appnet_jot_nets'); + unregister_hook('connector_settings', 'addon/appnet/appnet.php', 'appnet_settings'); + unregister_hook('connector_settings_post', 'addon/appnet/appnet.php', 'appnet_settings_post'); +} + +function appnet_module() {} + +function appnet_content(&$a) { + if(! local_user()) { + notice( t('Permission denied.') . EOL); + return ''; + } + + require_once("mod/settings.php"); + settings_init($a); + + if (isset($a->argv[1])) + switch ($a->argv[1]) { + case "connect": + $o = appnet_connect($a); + break; + default: + $o = print_r($a->argv, true); + break; + } + else + $o = appnet_connect($a); + + return $o; +} + +function appnet_connect(&$a) { + require_once 'addon/appnet/AppDotNet.php'; + + $clientId = get_pconfig(local_user(),'appnet','clientid'); + $clientSecret = get_pconfig(local_user(),'appnet','clientsecret'); + + $app = new AppDotNet($clientId, $clientSecret); + + try { + $token = $app->getAccessToken($a->get_baseurl().'/appnet/connect'); + + logger("appnet_connect: authenticated"); + $o .= t("You are now authenticated to app.net. "); + set_pconfig(local_user(),'appnet','token', $token); + } + catch (AppDotNetException $e) { + $o .= t("

Error fetching token. Please try again.

"); + } + + $o .= '
'.t("return to the connector page").''; + + return($o); +} + +function appnet_jot_nets(&$a,&$b) { + if(! local_user()) + return; + + $post = get_pconfig(local_user(),'appnet','post'); + if(intval($post) == 1) { + $defpost = get_pconfig(local_user(),'appnet','post_by_default'); + $selected = ((intval($defpost) == 1) ? ' checked="checked" ' : ''); + $b .= '
' + . t('Post to app.net') . '
'; + } +} + +function appnet_settings(&$a,&$s) { + require_once 'addon/appnet/AppDotNet.php'; + + if(! local_user()) + return; + + $token = get_pconfig(local_user(),'appnet','token'); + $app_clientId = get_pconfig(local_user(),'appnet','clientid'); + $app_clientSecret = get_pconfig(local_user(),'appnet','clientsecret'); + + /* Add our stylesheet to the page so we can make our settings look nice */ + $a->page['htmlhead'] .= '' . "\r\n"; + + $enabled = get_pconfig(local_user(),'appnet','post'); + $checked = (($enabled) ? ' checked="checked" ' : ''); + + $css = (($enabled) ? '' : '-disabled'); + + $def_enabled = get_pconfig(local_user(),'appnet','post_by_default'); + $def_checked = (($def_enabled) ? ' checked="checked" ' : ''); + + $s .= ''; + $s .= '

'. t('App.net Export').'

'; + $s .= '
'; + $s .= ''; +} + +function appnet_settings_post(&$a,&$b) { + + if(x($_POST,'appnet-submit')) { + + if (isset($_POST['appnet-disconnect'])) { + del_pconfig(local_user(), 'appnet', 'clientsecret'); + del_pconfig(local_user(), 'appnet', 'clientid'); + del_pconfig(local_user(), 'appnet', 'token'); + } + + if (isset($_POST["clientsecret"])) + set_pconfig(local_user(),'appnet','clientsecret', $_POST['clientsecret']); + + if (isset($_POST["clientid"])) + set_pconfig(local_user(),'appnet','clientid', $_POST['clientid']); + + if (isset($_POST["token"]) AND ($_POST["token"] != "")) + set_pconfig(local_user(),'appnet','token', $_POST['token']); + + set_pconfig(local_user(),'appnet','post',intval($_POST['appnet'])); + set_pconfig(local_user(),'appnet','post_by_default',intval($_POST['appnet_bydefault'])); + } +} + +function appnet_post_local(&$a,&$b) { + + if($b['edit']) + return; + + if((! local_user()) || (local_user() != $b['uid'])) + return; + + if($b['private'] || $b['parent']) + return; + + $post = intval(get_pconfig(local_user(),'appnet','post')); + + $enable = (($post && x($_REQUEST,'appnet_enable')) ? intval($_REQUEST['appnet_enable']) : 0); + + if($_REQUEST['api_source'] && intval(get_pconfig(local_user(),'appnet','post_by_default'))) + $enable = 1; + + if(!$enable) + return; + + if(strlen($b['postopts'])) + $b['postopts'] .= ','; + + $b['postopts'] .= 'appnet'; +} + +function appnet_send(&$a,&$b) { + + logger('appnet_send: invoked for post '.$b['id']." ".$b['app']); + + if($b['deleted'] || $b['private'] || ($b['created'] !== $b['edited'])) + return; + + if(! strstr($b['postopts'],'appnet')) + return; + + if($b['parent'] != $b['id']) + return; + + $token = get_pconfig($b['uid'],'appnet','token'); + + if($token) { + require_once 'addon/appnet/AppDotNet.php'; + + $clientId = get_pconfig(local_user(),'appnet','clientid'); + $clientSecret = get_pconfig(local_user(),'appnet','clientsecret'); + + $app = new AppDotNet($clientId, $clientSecret); + $app->setAccessToken($token); + + $data = array(); + + require_once("include/plaintext.php"); + require_once("include/network.php"); + + $post = plaintext($a, $b, 256, false); + logger("appnet_send: converted message ".$b["id"]." result: ".print_r($post, true), LOGGER_DEBUG); + + if (isset($post["image"])) { + $img_str = fetch_url($post['image'],true, $redirects, 10); + $tempfile = tempnam(get_config("system","temppath"), "cache"); + file_put_contents($tempfile, $img_str); + + try { + $photoFile = $app->createFile($tempfile, array(type => "com.github.jdolitsky.appdotnetphp.photo")); + + $data["annotations"][] = array( + "type" => "net.app.core.oembed", + "value" => array( + "+net.app.core.file" => array( + "file_id" => $photoFile["id"], + "file_token" => $photoFile["file_token"], + "format" => "oembed") + ) + ); + } + catch (AppDotNetException $e) { + logger("appnet_send: Error creating file"); + } + + unlink($tempfile); + } + + // To-Do + // Alle Links verkürzen + + if (isset($post["url"]) AND !isset($post["title"])) { + $display_url = str_replace(array("http://www.", "https://www."), array("", ""), $post["url"]); + $display_url = str_replace(array("http://", "https://"), array("", ""), $display_url); + + if (strlen($display_url) > 26) + $display_url = substr($display_url, 0, 25)."…"; + + $post["title"] = $display_url; + } + + if (isset($post["url"]) AND isset($post["title"])) { + $post["title"] = shortenmsg($post["title"], 90); + $post["text"] = shortenmsg($post["text"], 256 - strlen($post["title"])); + $post["text"] .= "\n[".$post["title"]."](".$post["url"].")"; + } elseif (isset($post["url"])) { + $post["url"] = short_link($post["url"]); + $post["text"] = shortenmsg($post["text"], 240); + $post["text"] .= " ".$post["url"]; + } + + //print_r($post); + $data["entities"]["parse_links"] = true; + $data["entities"]["parse_markdown_links"] = true; + + try { + $ret = $app->createPost($post["text"], $data); + logger("appnet_send: send message ".$b["id"]." result: ".print_r($ret, true), LOGGER_DEBUG); + } + catch (AppDotNetException $e) { + logger("appnet_send: Error sending message ".$b["id"]); + } + } +} diff --git a/appnetpost/appnetpost.css b/appnetpost/appnetpost.css old mode 100755 new mode 100644