3 * Name: Twitter Connector
4 * Description: Relay public postings to a connected Twitter account
6 * Author: Tobias Diekershoff <https://f.diekershoff.de/profile/tobias>
7 * Author: Michael Vogel <https://pirati.ca/profile/heluecht>
9 * Copyright (c) 2011-2013 Tobias Diekershoff, Michael Vogel
10 * All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions are met:
14 * * Redistributions of source code must retain the above copyright notice,
15 * this list of conditions and the following disclaimer.
16 * * Redistributions in binary form must reproduce the above
17 * * copyright notice, this list of conditions and the following disclaimer in
18 * the documentation and/or other materials provided with the distribution.
19 * * Neither the name of the <organization> nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
25 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT,
27 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
28 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
30 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
31 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
32 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 /* Twitter Plugin for Friendica
38 * Author: Tobias Diekershoff
39 * tobias.diekershoff@gmx.net
41 * License:3-clause BSD license
44 * To use this plugin you need a OAuth Consumer key pair (key & secret)
45 * you can get it from Twitter at https://twitter.com/apps
47 * Register your Friendica site as "Client" application with "Read & Write" access
48 * we do not need "Twitter as login". When you've registered the app you get the
49 * OAuth Consumer key and secret pair for your application/site.
51 * Add this key pair to your global .htconfig.php or use the admin panel.
53 * $a->config['twitter']['consumerkey'] = 'your consumer_key here';
54 * $a->config['twitter']['consumersecret'] = 'your consumer_secret here';
56 * To activate the plugin itself add it to the $a->config['system']['addon']
57 * setting. After this, your user can configure their Twitter account settings
58 * from "Settings -> Plugin Settings".
60 * Requirements: PHP5, curl [Slinky library]
63 define('TWITTER_DEFAULT_POLL_INTERVAL', 5); // given in minutes
65 function twitter_install() {
66 // we need some hooks, for the configuration and for sending tweets
67 register_hook('connector_settings', 'addon/twitter/twitter.php', 'twitter_settings');
68 register_hook('connector_settings_post', 'addon/twitter/twitter.php', 'twitter_settings_post');
69 register_hook('post_local', 'addon/twitter/twitter.php', 'twitter_post_local');
70 register_hook('notifier_normal', 'addon/twitter/twitter.php', 'twitter_post_hook');
71 register_hook('jot_networks', 'addon/twitter/twitter.php', 'twitter_jot_nets');
72 register_hook('cron', 'addon/twitter/twitter.php', 'twitter_cron');
73 register_hook('queue_predeliver', 'addon/twitter/twitter.php', 'twitter_queue_hook');
74 register_hook('follow', 'addon/twitter/twitter.php', 'twitter_follow');
75 logger("installed twitter");
79 function twitter_uninstall() {
80 unregister_hook('connector_settings', 'addon/twitter/twitter.php', 'twitter_settings');
81 unregister_hook('connector_settings_post', 'addon/twitter/twitter.php', 'twitter_settings_post');
82 unregister_hook('post_local', 'addon/twitter/twitter.php', 'twitter_post_local');
83 unregister_hook('notifier_normal', 'addon/twitter/twitter.php', 'twitter_post_hook');
84 unregister_hook('jot_networks', 'addon/twitter/twitter.php', 'twitter_jot_nets');
85 unregister_hook('cron', 'addon/twitter/twitter.php', 'twitter_cron');
86 unregister_hook('queue_predeliver', 'addon/twitter/twitter.php', 'twitter_queue_hook');
87 unregister_hook('follow', 'addon/twitter/twitter.php', 'twitter_follow');
89 // old setting - remove only
90 unregister_hook('post_local_end', 'addon/twitter/twitter.php', 'twitter_post_hook');
91 unregister_hook('plugin_settings', 'addon/twitter/twitter.php', 'twitter_settings');
92 unregister_hook('plugin_settings_post', 'addon/twitter/twitter.php', 'twitter_settings_post');
96 function twitter_follow($a, &$contact) {
98 logger("twitter_follow: Check if contact is twitter contact. ".$contact["url"], LOGGER_DEBUG);
100 if (!strstr($contact["url"], "://twitter.com") AND !strstr($contact["url"], "@twitter.com"))
103 // contact seems to be a twitter contact, so continue
104 $nickname = preg_replace("=https?://twitter.com/(.*)=ism", "$1", $contact["url"]);
105 $nickname = str_replace("@twitter.com", "", $nickname);
107 $uid = $a->user["uid"];
109 $ckey = get_config('twitter', 'consumerkey');
110 $csecret = get_config('twitter', 'consumersecret');
111 $otoken = get_pconfig($uid, 'twitter', 'oauthtoken');
112 $osecret = get_pconfig($uid, 'twitter', 'oauthsecret');
114 require_once("addon/twitter/codebird.php");
116 $cb = \Codebird\Codebird::getInstance();
117 $cb->setConsumerKey($ckey, $csecret);
118 $cb->setToken($otoken, $osecret);
120 $parameters = array();
121 $parameters["screen_name"] = $nickname;
123 $user = $cb->friendships_create($parameters);
125 twitter_fetchuser($a, $uid, $nickname);
127 $r = q("SELECT name,nick,url,addr,batch,notify,poll,request,confirm,poco,photo,priority,network,alias,pubkey
128 FROM `contact` WHERE `uid` = %d AND `nick` = '%s'",
132 $contact["contact"] = $r[0];
135 function twitter_jot_nets(&$a,&$b) {
139 $tw_post = get_pconfig(local_user(),'twitter','post');
140 if(intval($tw_post) == 1) {
141 $tw_defpost = get_pconfig(local_user(),'twitter','post_by_default');
142 $selected = ((intval($tw_defpost) == 1) ? ' checked="checked" ' : '');
143 $b .= '<div class="profile-jot-net"><input type="checkbox" name="twitter_enable"' . $selected . ' value="1" /> '
144 . t('Post to Twitter') . '</div>';
148 function twitter_settings_post ($a,$post) {
151 // don't check twitter settings if twitter submit button is not clicked
152 if (!x($_POST,'twitter-submit'))
155 if (isset($_POST['twitter-disconnect'])) {
157 * if the twitter-disconnect checkbox is set, clear the OAuth key/secret pair
158 * from the user configuration
160 del_pconfig(local_user(), 'twitter', 'consumerkey');
161 del_pconfig(local_user(), 'twitter', 'consumersecret');
162 del_pconfig(local_user(), 'twitter', 'oauthtoken');
163 del_pconfig(local_user(), 'twitter', 'oauthsecret');
164 del_pconfig(local_user(), 'twitter', 'post');
165 del_pconfig(local_user(), 'twitter', 'post_by_default');
166 del_pconfig(local_user(), 'twitter', 'post_taglinks');
167 del_pconfig(local_user(), 'twitter', 'lastid');
168 del_pconfig(local_user(), 'twitter', 'mirror_posts');
169 del_pconfig(local_user(), 'twitter', 'intelligent_shortening');
170 del_pconfig(local_user(), 'twitter', 'import');
171 del_pconfig(local_user(), 'twitter', 'create_user');
173 if (isset($_POST['twitter-pin'])) {
174 // if the user supplied us with a PIN from Twitter, let the magic of OAuth happen
175 logger('got a Twitter PIN');
176 require_once('library/twitteroauth.php');
177 $ckey = get_config('twitter', 'consumerkey');
178 $csecret = get_config('twitter', 'consumersecret');
179 // the token and secret for which the PIN was generated were hidden in the settings
180 // form as token and token2, we need a new connection to Twitter using these token
181 // and secret to request a Access Token with the PIN
182 $connection = new TwitterOAuth($ckey, $csecret, $_POST['twitter-token'], $_POST['twitter-token2']);
183 $token = $connection->getAccessToken( $_POST['twitter-pin'] );
184 // ok, now that we have the Access Token, save them in the user config
185 set_pconfig(local_user(),'twitter', 'oauthtoken', $token['oauth_token']);
186 set_pconfig(local_user(),'twitter', 'oauthsecret', $token['oauth_token_secret']);
187 set_pconfig(local_user(),'twitter', 'post', 1);
188 set_pconfig(local_user(),'twitter', 'post_taglinks', 1);
189 // reload the Addon Settings page, if we don't do it see Bug #42
190 goaway($a->get_baseurl().'/settings/connectors');
192 // if no PIN is supplied in the POST variables, the user has changed the setting
193 // to post a tweet for every new __public__ posting to the wall
194 set_pconfig(local_user(),'twitter','post',intval($_POST['twitter-enable']));
195 set_pconfig(local_user(),'twitter','post_by_default',intval($_POST['twitter-default']));
196 set_pconfig(local_user(),'twitter','post_taglinks',intval($_POST['twitter-sendtaglinks']));
197 set_pconfig(local_user(), 'twitter', 'mirror_posts', intval($_POST['twitter-mirror']));
198 set_pconfig(local_user(), 'twitter', 'intelligent_shortening', intval($_POST['twitter-shortening']));
199 set_pconfig(local_user(), 'twitter', 'import', intval($_POST['twitter-import']));
200 set_pconfig(local_user(), 'twitter', 'create_user', intval($_POST['twitter-create_user']));
201 info( t('Twitter settings updated.') . EOL);
204 function twitter_settings(&$a,&$s) {
207 $a->page['htmlhead'] .= '<link rel="stylesheet" type="text/css" href="' . $a->get_baseurl() . '/addon/twitter/twitter.css' . '" media="all" />' . "\r\n";
209 * 1) Check that we have global consumer key & secret
210 * 2) If no OAuthtoken & stuff is present, generate button to get some
211 * 3) Checkbox for "Send public notices (140 chars only)
213 $ckey = get_config('twitter', 'consumerkey' );
214 $csecret = get_config('twitter', 'consumersecret' );
215 $otoken = get_pconfig(local_user(), 'twitter', 'oauthtoken' );
216 $osecret = get_pconfig(local_user(), 'twitter', 'oauthsecret' );
217 $enabled = get_pconfig(local_user(), 'twitter', 'post');
218 $checked = (($enabled) ? ' checked="checked" ' : '');
219 $defenabled = get_pconfig(local_user(),'twitter','post_by_default');
220 $defchecked = (($defenabled) ? ' checked="checked" ' : '');
221 $linksenabled = get_pconfig(local_user(),'twitter','post_taglinks');
222 $linkschecked = (($linksenabled) ? ' checked="checked" ' : '');
223 $mirrorenabled = get_pconfig(local_user(),'twitter','mirror_posts');
224 $mirrorchecked = (($mirrorenabled) ? ' checked="checked" ' : '');
225 $shorteningenabled = get_pconfig(local_user(),'twitter','intelligent_shortening');
226 $shorteningchecked = (($shorteningenabled) ? ' checked="checked" ' : '');
227 $importenabled = get_pconfig(local_user(),'twitter','import');
228 $importchecked = (($importenabled) ? ' checked="checked" ' : '');
229 $create_userenabled = get_pconfig(local_user(),'twitter','create_user');
230 $create_userchecked = (($create_userenabled) ? ' checked="checked" ' : '');
232 $s .= '<div class="settings-block">';
233 $s .= '<h3>'. t('Twitter Posting Settings') .'</h3>';
235 if ( (!$ckey) && (!$csecret) ) {
237 * no global consumer keys
238 * display warning and skip personal config
240 $s .= '<p>'. t('No consumer key pair for Twitter found. Please contact your site administrator.') .'</p>';
243 * ok we have a consumer key pair now look into the OAuth stuff
245 if ( (!$otoken) && (!$osecret) ) {
247 * the user has not yet connected the account to twitter...
248 * get a temporary OAuth key/secret pair and display a button with
249 * which the user can request a PIN to connect the account to a
250 * account at Twitter.
252 require_once('library/twitteroauth.php');
253 $connection = new TwitterOAuth($ckey, $csecret);
254 $request_token = $connection->getRequestToken();
255 $token = $request_token['oauth_token'];
257 * make some nice form
259 $s .= '<p>'. t('At this Friendica instance the Twitter plugin was enabled but you have not yet connected your account to your Twitter account. To do so click the button below to get a PIN from Twitter which you have to copy into the input box below and submit the form. Only your <strong>public</strong> posts will be posted to Twitter.') .'</p>';
260 $s .= '<a href="'.$connection->getAuthorizeURL($token).'" target="_twitter"><img src="addon/twitter/lighter.png" alt="'.t('Log in with Twitter').'"></a>';
261 $s .= '<div id="twitter-pin-wrapper">';
262 $s .= '<label id="twitter-pin-label" for="twitter-pin">'. t('Copy the PIN from Twitter here') .'</label>';
263 $s .= '<input id="twitter-pin" type="text" name="twitter-pin" />';
264 $s .= '<input id="twitter-token" type="hidden" name="twitter-token" value="'.$token.'" />';
265 $s .= '<input id="twitter-token2" type="hidden" name="twitter-token2" value="'.$request_token['oauth_token_secret'].'" />';
266 $s .= '</div><div class="clear"></div>';
267 $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="twitter-submit" class="settings-submit" value="' . t('Submit') . '" /></div>';
270 * we have an OAuth key / secret pair for the user
271 * so let's give a chance to disable the postings to Twitter
273 require_once('library/twitteroauth.php');
274 $connection = new TwitterOAuth($ckey,$csecret,$otoken,$osecret);
275 $details = $connection->get('account/verify_credentials');
276 $s .= '<div id="twitter-info" ><img id="twitter-avatar" src="'.$details->profile_image_url.'" /><p id="twitter-info-block">'. t('Currently connected to: ') .'<a href="https://twitter.com/'.$details->screen_name.'" target="_twitter">'.$details->screen_name.'</a><br /><em>'.$details->description.'</em></p></div>';
277 $s .= '<p>'. t('If enabled all your <strong>public</strong> postings can be posted to the associated Twitter account. You can choose to do so by default (here) or for every posting separately in the posting options when writing the entry.') .'</p>';
278 if ($a->user['hidewall']) {
279 $s .= '<p>'. t('<strong>Note</strong>: Due your privacy settings (<em>Hide your profile details from unknown viewers?</em>) the link potentially included in public postings relayed to Twitter will lead the visitor to a blank page informing the visitor that the access to your profile has been restricted.') .'</p>';
281 $s .= '<div id="twitter-enable-wrapper">';
282 $s .= '<label id="twitter-enable-label" for="twitter-checkbox">'. t('Allow posting to Twitter'). '</label>';
283 $s .= '<input id="twitter-checkbox" type="checkbox" name="twitter-enable" value="1" ' . $checked . '/>';
284 $s .= '<div class="clear"></div>';
285 $s .= '<label id="twitter-default-label" for="twitter-default">'. t('Send public postings to Twitter by default') .'</label>';
286 $s .= '<input id="twitter-default" type="checkbox" name="twitter-default" value="1" ' . $defchecked . '/>';
287 $s .= '<div class="clear"></div>';
289 $s .= '<label id="twitter-mirror-label" for="twitter-mirror">'.t('Mirror all posts from twitter that are no replies').'</label>';
290 $s .= '<input id="twitter-mirror" type="checkbox" name="twitter-mirror" value="1" '. $mirrorchecked . '/>';
291 $s .= '<div class="clear"></div>';
293 $s .= '<label id="twitter-shortening-label" for="twitter-shortening">'.t('Shortening method that optimizes the tweet').'</label>';
294 $s .= '<input id="twitter-shortening" type="checkbox" name="twitter-shortening" value="1" '. $shorteningchecked . '/>';
295 $s .= '<div class="clear"></div>';
297 $s .= '<label id="twitter-sendtaglinks-label" for="twitter-sendtaglinks">'.t('Send linked #-tags and @-names to Twitter').'</label>';
298 $s .= '<input id="twitter-sendtaglinks" type="checkbox" name="twitter-sendtaglinks" value="1" '. $linkschecked . '/>';
299 $s .= '</div><div class="clear"></div>';
301 $s .= '<label id="twitter-import-label" for="twitter-import">'.t('Import the remote timeline').'</label>';
302 $s .= '<input id="twitter-import" type="checkbox" name="twitter-import" value="1" '. $importchecked . '/>';
303 $s .= '<div class="clear"></div>';
305 $s .= '<label id="twitter-create_user-label" for="twitter-create_user">'.t('Automatically create contacts').'</label>';
306 $s .= '<input id="twitter-create_user" type="checkbox" name="twitter-create_user" value="1" '. $create_userchecked . '/>';
307 $s .= '<div class="clear"></div>';
309 $s .= '<div id="twitter-disconnect-wrapper">';
310 $s .= '<label id="twitter-disconnect-label" for="twitter-disconnect">'. t('Clear OAuth configuration') .'</label>';
311 $s .= '<input id="twitter-disconnect" type="checkbox" name="twitter-disconnect" value="1" />';
312 $s .= '</div><div class="clear"></div>';
313 $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="twitter-submit" class="settings-submit" value="' . t('Submit') . '" /></div>';
316 $s .= '</div><div class="clear"></div>';
320 function twitter_post_local(&$a,&$b) {
325 if((local_user()) && (local_user() == $b['uid']) && (! $b['private']) && (! $b['parent']) ) {
327 $twitter_post = intval(get_pconfig(local_user(),'twitter','post'));
328 $twitter_enable = (($twitter_post && x($_REQUEST,'twitter_enable')) ? intval($_REQUEST['twitter_enable']) : 0);
330 // if API is used, default to the chosen settings
331 if($_REQUEST['api_source'] && intval(get_pconfig(local_user(),'twitter','post_by_default')))
334 if(! $twitter_enable)
337 if(strlen($b['postopts']))
338 $b['postopts'] .= ',';
339 $b['postopts'] .= 'twitter';
343 if (! function_exists('short_link')) {
344 function short_link ($url) {
345 require_once('library/slinky.php');
346 $slinky = new Slinky( $url );
347 $yourls_url = get_config('yourls','url1');
349 $yourls_username = get_config('yourls','username1');
350 $yourls_password = get_config('yourls', 'password1');
351 $yourls_ssl = get_config('yourls', 'ssl1');
352 $yourls = new Slinky_YourLS();
353 $yourls->set( 'username', $yourls_username );
354 $yourls->set( 'password', $yourls_password );
355 $yourls->set( 'ssl', $yourls_ssl );
356 $yourls->set( 'yourls-url', $yourls_url );
357 $slinky->set_cascade( array( $yourls, new Slinky_UR1ca(), new Slinky_Trim(), new Slinky_IsGd(), new Slinky_TinyURL() ) );
360 // setup a cascade of shortening services
361 // try to get a short link from these services
362 // in the order ur1.ca, trim, id.gd, tinyurl
363 $slinky->set_cascade( array( new Slinky_UR1ca(), new Slinky_Trim(), new Slinky_IsGd(), new Slinky_TinyURL() ) );
365 return $slinky->short();
368 function twitter_shortenmsg($b, $shortlink = false) {
369 require_once("include/bbcode.php");
370 require_once("include/html2plain.php");
374 // Looking for the first image
376 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/is",$b['body'],$matches))
377 $image = $matches[3];
380 if(preg_match("/\[img\](.*?)\[\/img\]/is",$b['body'],$matches))
381 $image = $matches[1];
383 $multipleimages = (strpos($b['body'], "[img") != strrpos($b['body'], "[img"));
385 // When saved into the database the content is sent through htmlspecialchars
386 // That means that we have to decode all image-urls
387 $image = htmlspecialchars_decode($image);
390 if ($b["title"] != "")
391 $body = $b["title"]."\n\n".$body;
393 if (strpos($body, "[bookmark") !== false) {
394 // splitting the text in two parts:
395 // before and after the bookmark
396 $pos = strpos($body, "[bookmark");
397 $body1 = substr($body, 0, $pos);
398 $body2 = substr($body, $pos);
400 // Removing all quotes after the bookmark
401 // they are mostly only the content after the bookmark.
402 $body2 = preg_replace("/\[quote\=([^\]]*)\](.*?)\[\/quote\]/ism",'',$body2);
403 $body2 = preg_replace("/\[quote\](.*?)\[\/quote\]/ism",'',$body2);
404 $body = $body1.$body2;
407 // Add some newlines so that the message could be cut better
408 $body = str_replace(array("[quote", "[bookmark", "[/bookmark]", "[/quote]"),
409 array("\n[quote", "\n[bookmark", "[/bookmark]\n", "[/quote]\n"), $body);
411 // remove the recycle signs and the names since they aren't helpful on twitter
413 $recycle = html_entity_decode("♲ ", ENT_QUOTES, 'UTF-8');
414 $body = preg_replace( '/'.$recycle.'\[url\=(\w+.*?)\](\w+.*?)\[\/url\]/i', "\n", $body);
416 $recycle = html_entity_decode("◌ ", ENT_QUOTES, 'UTF-8');
417 $body = preg_replace( '/'.$recycle.'\[url\=(\w+.*?)\](\w+.*?)\[\/url\]/i', "\n", $body);
419 // remove the share element
420 //$body = preg_replace("/\[share(.*?)\](.*?)\[\/share\]/ism","\n\n$2\n\n",$body);
422 // At first convert the text to html
423 $html = bbcode($body, false, false, 2);
425 // Then convert it to plain text
426 $msg = trim(html2plain($html, 0, true));
427 $msg = html_entity_decode($msg,ENT_QUOTES,'UTF-8');
429 // Removing multiple newlines
430 while (strpos($msg, "\n\n\n") !== false)
431 $msg = str_replace("\n\n\n", "\n\n", $msg);
433 // Removing multiple spaces
434 while (strpos($msg, " ") !== false)
435 $msg = str_replace(" ", " ", $msg);
437 $origmsg = trim($msg);
440 $msg = preg_replace('/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/i', "", $msg);
445 // look for bookmark-bbcode and handle it with priority
446 if(preg_match("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/is",$b['body'],$matches))
449 $multiplelinks = (strpos($b['body'], "[bookmark") != strrpos($b['body'], "[bookmark"));
451 // If there is no bookmark element then take the first link
453 $links = collecturls($html);
455 foreach($links AS $singlelink) {
456 $img_str = fetch_url($singlelink);
458 $tempfile = tempnam(get_config("system","temppath"), "cache");
459 file_put_contents($tempfile, $img_str);
460 $mime = image_type_to_mime_type(exif_imagetype($tempfile));
463 if (substr($mime, 0, 6) == "image/") {
464 $image = $singlelink;
465 unset($links[$singlelink]);
469 if (sizeof($links) > 0) {
471 $link = current($links);
473 $multiplelinks = (sizeof($links) > 1);
478 $msglink = $b["plink"];
479 else if ($link != "")
481 else if ($multipleimages)
482 $msglink = $b["plink"];
483 else if ($image != "")
486 if (($msglink == "") and strlen($msg) > $max_char)
487 $msglink = $b["plink"];
489 // If the message is short enough then don't modify it.
490 if ((strlen($origmsg) <= $max_char) AND ($msglink == ""))
491 return(array("msg"=>$origmsg, "image"=>""));
493 // If the message is short enough and contains a picture then post the picture as well
494 if ((strlen($origmsg) <= ($max_char - 23)) AND strpos($origmsg, $msglink))
495 return(array("msg"=>$origmsg, "image"=>$image));
497 // If the message is short enough and the link exists in the original message don't modify it as well
498 // -3 because of the bad shortener of twitter
499 if ((strlen($origmsg) <= ($max_char - 3)) AND strpos($origmsg, $msglink))
500 return(array("msg"=>$origmsg, "image"=>""));
502 // Preserve the unshortened link
503 $orig_link = $msglink;
505 // Just replace the message link with a 22 character long string
506 // Twitter calculates with this length
507 if (trim($msglink) <> '')
508 $msglink = "1234567890123456789012";
510 if (strlen(trim($msg." ".$msglink)) > ($max_char)) {
511 $msg = substr($msg, 0, ($max_char) - (strlen($msglink)));
512 $lastchar = substr($msg, -1);
513 $msg = substr($msg, 0, -1);
514 $pos = strrpos($msg, "\n");
516 $msg = substr($msg, 0, $pos);
517 else if ($lastchar != "\n")
518 $msg = substr($msg, 0, -3)."...";
520 // if the post contains a picture and a link then the system tries to cut the post earlier.
521 // So the link and the picture can be posted.
522 if (($image != "") AND ($orig_link != $image)) {
523 $msg2 = substr($msg, 0, ($max_char - 20) - (strlen($msglink)));
524 $lastchar = substr($msg2, -1);
525 $msg2 = substr($msg2, 0, -1);
526 $pos = strrpos($msg2, "\n");
528 $msg = substr($msg2, 0, $pos);
529 else if ($lastchar == "\n")
534 // Removing multiple spaces - again
535 while (strpos($msg, " ") !== false)
536 $msg = str_replace(" ", " ", $msg);
540 // Removing multiple newlines
541 //while (strpos($msg, "\n\n") !== false)
542 // $msg = str_replace("\n\n", "\n", $msg);
544 // Looking if the link points to an image
545 $img_str = fetch_url($orig_link);
547 $tempfile = tempnam(get_config("system","temppath"), "cache");
548 file_put_contents($tempfile, $img_str);
549 $mime = image_type_to_mime_type(exif_imagetype($tempfile));
552 if (($image == $orig_link) OR (substr($mime, 0, 6) == "image/"))
553 return(array("msg"=>$msg, "image"=>$orig_link));
554 else if (($image != $orig_link) AND ($image != "") AND (strlen($msg." ".$msglink) <= ($max_char - 23))) {
556 $orig_link = short_link($orig_link);
558 return(array("msg"=>$msg." ".$orig_link, "image"=>$image));
561 $orig_link = short_link($orig_link);
563 return(array("msg"=>$msg." ".$orig_link, "image"=>""));
567 function twitter_action($a, $uid, $pid, $action) {
569 $ckey = get_config('twitter', 'consumerkey');
570 $csecret = get_config('twitter', 'consumersecret');
571 $otoken = get_pconfig($uid, 'twitter', 'oauthtoken');
572 $osecret = get_pconfig($uid, 'twitter', 'oauthsecret');
574 require_once("addon/twitter/codebird.php");
576 $cb = \Codebird\Codebird::getInstance();
577 $cb->setConsumerKey($ckey, $csecret);
578 $cb->setToken($otoken, $osecret);
580 $post = array('id' => $pid);
582 logger("twitter_action '".$action."' ID: ".$pid." data: " . print_r($post, true), LOGGER_DATA);
586 $result = $cb->statuses_destroy($post);
589 $result = $cb->favorites_create($post);
592 $result = $cb->favorites_destroy($post);
595 logger("twitter_action '".$action."' send, result: " . print_r($result, true), LOGGER_DEBUG);
598 function twitter_post_hook(&$a,&$b) {
604 if (!get_pconfig($b["uid"],'twitter','import')) {
605 if($b['deleted'] || $b['private'] || ($b['created'] !== $b['edited']))
609 if($b['parent'] != $b['id']) {
610 logger("twitter_post_hook: parameter ".print_r($b, true), LOGGER_DATA);
612 // Looking if its a reply to a twitter post
613 if ((substr($b["parent-uri"], 0, 9) != "twitter::") AND (substr($b["extid"], 0, 9) != "twitter::") AND (substr($b["thr-parent"], 0, 9) != "twitter::")) {
614 logger("twitter_post_hook: no twitter post ".$b["parent"]);
618 $r = q("SELECT * FROM item WHERE item.uri = '%s' AND item.uid = %d LIMIT 1",
619 dbesc($b["thr-parent"]),
623 logger("twitter_post_hook: no parent found ".$b["thr-parent"]);
630 // To-Do: Ab dem letzten / nehmen
631 $b["body"] = "@".substr($orig_post["author-link"], 20)." ".$b["body"];
633 logger("twitter_post_hook: parent found ".print_r($orig_post, true), LOGGER_DATA);
637 if($b['private'] OR !strstr($b['postopts'],'twitter'))
641 if (($b['verb'] == ACTIVITY_POST) AND $b['deleted'])
642 twitter_action($a, $b["uid"], substr($orig_post["uri"], 9), "delete");
644 if($b['verb'] == ACTIVITY_LIKE) {
645 logger("twitter_post_hook: parameter 2 ".substr($b["thr-parent"], 9), LOGGER_DEBUG);
647 twitter_action($a, $b["uid"], substr($b["thr-parent"], 9), "unlike");
649 twitter_action($a, $b["uid"], substr($b["thr-parent"], 9), "like");
653 if($b['deleted'] || ($b['created'] !== $b['edited']))
656 // if post comes from twitter don't send it back
657 if($b['app'] == "Twitter")
660 logger('twitter post invoked');
663 load_pconfig($b['uid'], 'twitter');
665 $ckey = get_config('twitter', 'consumerkey');
666 $csecret = get_config('twitter', 'consumersecret');
667 $otoken = get_pconfig($b['uid'], 'twitter', 'oauthtoken');
668 $osecret = get_pconfig($b['uid'], 'twitter', 'oauthsecret');
669 $intelligent_shortening = get_pconfig($b['uid'], 'twitter', 'intelligent_shortening');
671 // Global setting overrides this
672 if (get_config('twitter','intelligent_shortening'))
673 $intelligent_shortening = get_config('twitter','intelligent_shortening');
675 if($ckey && $csecret && $otoken && $osecret) {
676 logger('twitter: we have customer key and oauth stuff, going to send.', LOGGER_DEBUG);
678 require_once('library/twitteroauth.php');
679 require_once('include/bbcode.php');
680 $tweet = new TwitterOAuth($ckey,$csecret,$otoken,$osecret);
681 // in theory max char is 140 but T. uses t.co to make links
682 // longer so we give them 10 characters extra
683 if (!$intelligent_shortening) {
684 $max_char = 130; // max. length for a tweet
685 // we will only work with up to two times the length of the dent
686 // we can later send to Twitter. This way we can "gain" some
687 // information during shortening of potential links but do not
688 // shorten all the links in a 200000 character long essay.
689 if (! $b['title']=='') {
690 $tmp = $b['title'] . ' : '. $b['body'];
691 // $tmp = substr($tmp, 0, 4*$max_char);
693 $tmp = $b['body']; // substr($b['body'], 0, 3*$max_char);
695 // if [url=bla][img]blub.png[/img][/url] get blub.png
696 $tmp = preg_replace( '/\[url\=(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)\]\[img\](\\w+.*?)\\[\\/img\]\\[\\/url\]/i', '$2', $tmp);
697 // preserve links to images, videos and audios
698 $tmp = preg_replace( '/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism', '$3', $tmp);
699 $tmp = preg_replace( '/\[\\/?img(\\s+.*?\]|\])/i', '', $tmp);
700 $tmp = preg_replace( '/\[\\/?video(\\s+.*?\]|\])/i', '', $tmp);
701 $tmp = preg_replace( '/\[\\/?youtube(\\s+.*?\]|\])/i', '', $tmp);
702 $tmp = preg_replace( '/\[\\/?vimeo(\\s+.*?\]|\])/i', '', $tmp);
703 $tmp = preg_replace( '/\[\\/?audio(\\s+.*?\]|\])/i', '', $tmp);
704 $linksenabled = get_pconfig($b['uid'],'twitter','post_taglinks');
705 // if a #tag is linked, don't send the [url] over to SN
706 // that is, don't send if the option is not set in the
707 // connector settings
708 if ($linksenabled=='0') {
710 $tmp = preg_replace( '/#\[url\=(\w+.*?)\](\w+.*?)\[\/url\]/i', '#$2', $tmp);
712 $tmp = preg_replace( '/@\[url\=(\w+.*?)\](\w+.*?)\[\/url\]/i', '@$2', $tmp);
714 $recycle = html_entity_decode("♲ ", ENT_QUOTES, 'UTF-8');
715 $tmp = preg_replace( '/'.$recycle.'\[url\=(\w+.*?)\](\w+.*?)\[\/url\]/i', $recycle.'$2', $tmp);
717 $recycle = html_entity_decode("◌ ", ENT_QUOTES, 'UTF-8');
718 $tmp = preg_replace( '/'.$recycle.'\[url\=(\w+.*?)\](\w+.*?)\[\/url\]/i', $recycle.'$2', $tmp);
720 $tmp = preg_replace( '/\[url\=(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)\](\w+.*?)\[\/url\]/i', '$2 $1', $tmp);
721 $tmp = preg_replace( '/\[bookmark\=(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)\](\w+.*?)\[\/bookmark\]/i', '$2 $1', $tmp);
722 // find all http or https links in the body of the entry and
723 // apply the shortener if the link is longer then 20 characters
724 if (( strlen($tmp)>$max_char ) && ( $max_char > 0 )) {
725 preg_match_all ( '/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/i', $tmp, $allurls );
726 foreach ($allurls as $url) {
727 foreach ($url as $u) {
729 $sl = short_link($u);
730 $tmp = str_replace( $u, $sl, $tmp );
735 // ok, all the links we want to send out are save, now strip
736 // away the remaining bbcode
737 //$msg = strip_tags(bbcode($tmp, false, false));
738 $msg = bbcode($tmp, false, false, true);
739 $msg = str_replace(array('<br>','<br />'),"\n",$msg);
740 $msg = strip_tags($msg);
742 // quotes not working - let's try this
743 $msg = html_entity_decode($msg);
744 if (( strlen($msg) > $max_char) && $max_char > 0) {
745 $shortlink = short_link( $b['plink'] );
746 // the new message will be shortened such that "... $shortlink"
747 // will fit into the character limit
748 $msg = nl2br(substr($msg, 0, $max_char-strlen($shortlink)-4));
749 $msg = str_replace(array('<br>','<br />'),' ',$msg);
750 $e = explode(' ', $msg);
751 // remove the last word from the cut down message to
752 // avoid sending cut words to the MicroBlog
754 $msg = implode(' ', $e);
755 $msg .= '... ' . $shortlink;
761 $msgarr = twitter_shortenmsg($b);
762 $msg = $msgarr["msg"];
763 $image = $msgarr["image"];
765 // and now tweet it :-)
766 if(strlen($msg) and ($image != "")) {
767 $img_str = fetch_url($image);
769 $tempfile = tempnam(get_config("system","temppath"), "cache");
770 file_put_contents($tempfile, $img_str);
772 // Twitter had changed something so that the old library doesn't work anymore
773 // so we are using a new library for twitter
775 // Switching completely to this library with all functions
776 require_once("addon/twitter/codebird.php");
778 $cb = \Codebird\Codebird::getInstance();
779 $cb->setConsumerKey($ckey, $csecret);
780 $cb->setToken($otoken, $osecret);
782 $post = array('status' => $msg, 'media[]' => $tempfile);
785 $post["in_reply_to_status_id"] = substr($orig_post["uri"], 9);
787 $result = $cb->statuses_updateWithMedia($post);
792 $mime = image_type_to_mime_type(exif_imagetype($tempfile));
795 $filename = "upload";
797 $result = $tweet->post('statuses/update_with_media', array('media[]' => "{$img_str};type=".$mime.";filename={$filename}" , 'status' => $msg));
800 logger('twitter_post_with_media send, result: ' . print_r($result, true), LOGGER_DEBUG);
801 if ($result->errors OR $result->error) {
802 logger('Send to Twitter failed: "' . print_r($result->errors, true) . '"');
804 // Workaround: Remove the picture link so that the post can be reposted without it
807 } elseif ($iscomment) {
808 logger('twitter_post: Update extid '.$result->id_str." for post id ".$b['id']);
809 q("UPDATE `item` SET `extid` = '%s', `body` = '%s' WHERE `id` = %d",
810 dbesc("twitter::".$result->id_str),
811 dbesc($result->text),
817 if(strlen($msg) and ($image == "")) {
818 $url = 'statuses/update';
819 $post = array('status' => $msg);
822 $post["in_reply_to_status_id"] = substr($orig_post["uri"], 9);
824 $result = $tweet->post($url, $post);
825 logger('twitter_post send, result: ' . print_r($result, true), LOGGER_DEBUG);
826 if ($result->errors) {
827 logger('Send to Twitter failed: "' . print_r($result->errors, true) . '"');
829 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `self`", intval($b['uid']));
831 $a->contact = $r[0]["id"];
833 $s = serialize(array('url' => $url, 'item' => $b['id'], 'post' => $post));
834 require_once('include/queue_fn.php');
835 add_to_queue($a->contact,NETWORK_TWITTER,$s);
836 notice(t('Twitter post failed. Queued for retry.').EOL);
837 } elseif ($iscomment) {
838 logger('twitter_post: Update extid '.$result->id_str." for post id ".$b['id']);
839 q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d",
840 dbesc("twitter::".$result->id_str),
843 //q("UPDATE `item` SET `extid` = '%s', `body` = '%s' WHERE `id` = %d",
844 // dbesc("twitter::".$result->id_str),
845 // dbesc($result->text),
853 function twitter_plugin_admin_post(&$a){
854 $consumerkey = ((x($_POST,'consumerkey')) ? notags(trim($_POST['consumerkey'])) : '');
855 $consumersecret = ((x($_POST,'consumersecret')) ? notags(trim($_POST['consumersecret'])): '');
856 $applicationname = ((x($_POST, 'applicationname')) ? notags(trim($_POST['applicationname'])):'');
857 set_config('twitter','consumerkey',$consumerkey);
858 set_config('twitter','consumersecret',$consumersecret);
859 set_config('twitter','application_name',$applicationname);
860 info( t('Settings updated.'). EOL );
862 function twitter_plugin_admin(&$a, &$o){
863 $t = get_markup_template( "admin.tpl", "addon/twitter/" );
865 $o = replace_macros($t, array(
866 '$submit' => t('Submit'),
867 // name, label, value, help, [extra values]
868 '$consumerkey' => array('consumerkey', t('Consumer key'), get_config('twitter', 'consumerkey' ), ''),
869 '$consumersecret' => array('consumersecret', t('Consumer secret'), get_config('twitter', 'consumersecret' ), ''),
870 '$applicationname' => array('applicationname', t('Name of the Twitter Application'), get_config('twitter','application_name'),t('set this to avoid mirroring postings from ~friendica back to ~friendica'))
874 function twitter_cron($a,$b) {
875 $last = get_config('twitter','last_poll');
877 $poll_interval = intval(get_config('twitter','poll_interval'));
879 $poll_interval = TWITTER_DEFAULT_POLL_INTERVAL;
882 $next = $last + ($poll_interval * 60);
884 logger('twitter: poll intervall not reached');
888 logger('twitter: cron_start');
890 $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'twitter' AND `k` = 'mirror_posts' AND `v` = '1' ORDER BY RAND()");
893 logger('twitter: fetching for user '.$rr['uid']);
894 twitter_fetchtimeline($a, $rr['uid']);
899 $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'twitter' AND `k` = 'import' AND `v` = '1' ORDER BY RAND()");
902 logger('twitter: importing timeline from user '.$rr['uid']);
903 twitter_fetchhometimeline($a, $rr["uid"]);
907 // check for new contacts once a day
908 $last_contact_check = get_pconfig($rr['uid'],'pumpio','contact_check');
909 if($last_contact_check)
910 $next_contact_check = $last_contact_check + 86400;
912 $next_contact_check = 0;
914 if($next_contact_check <= time()) {
915 pumpio_getallusers($a, $rr["uid"]);
916 set_pconfig($rr['uid'],'pumpio','contact_check',time());
923 logger('twitter: cron_end');
925 set_config('twitter','last_poll', time());
928 function twitter_fetchtimeline($a, $uid) {
929 $ckey = get_config('twitter', 'consumerkey');
930 $csecret = get_config('twitter', 'consumersecret');
931 $otoken = get_pconfig($uid, 'twitter', 'oauthtoken');
932 $osecret = get_pconfig($uid, 'twitter', 'oauthsecret');
933 $lastid = get_pconfig($uid, 'twitter', 'lastid');
935 $application_name = get_config('twitter', 'application_name');
937 if ($application_name == "")
938 $application_name = $a->get_hostname();
940 require_once('mod/item.php');
942 require_once('library/twitteroauth.php');
943 $connection = new TwitterOAuth($ckey,$csecret,$otoken,$osecret);
945 $parameters = array("exclude_replies" => true, "trim_user" => false, "contributor_details" => true, "include_rts" => true);
947 $first_time = ($lastid == "");
950 $parameters["since_id"] = $lastid;
952 $items = $connection->get('statuses/user_timeline', $parameters);
954 if (!is_array($items))
957 $posts = array_reverse($items);
960 foreach ($posts as $post) {
961 if ($post->id_str > $lastid)
962 $lastid = $post->id_str;
967 if (!strpos($post->source, $application_name)) {
968 $_SESSION["authenticated"] = true;
969 $_SESSION["uid"] = $uid;
972 $_REQUEST["type"] = "wall";
973 $_REQUEST["api_source"] = true;
974 $_REQUEST["profile_uid"] = $uid;
975 $_REQUEST["source"] = "Twitter";
977 //$_REQUEST["date"] = $post->created_at;
979 $_REQUEST["title"] = "";
981 if (is_object($post->retweeted_status)) {
983 $_REQUEST['body'] = $post->retweeted_status->text;
986 if (is_array($post->retweeted_status->entities->media)) {
987 foreach($post->retweeted_status->entities->media AS $media) {
988 switch($media->type) {
990 $_REQUEST['body'] = str_replace($media->url, "\n\n[img]".$media->media_url_https."[/img]\n", $_REQUEST['body']);
996 $converted = twitter_convertmsg($a, $_REQUEST['body'], true);
997 $_REQUEST['body'] = $converted["body"];
999 $_REQUEST['body'] = "[share author='".$post->retweeted_status->user->name.
1000 "' profile='https://twitter.com/".$post->retweeted_status->user->screen_name.
1001 "' avatar='".$post->retweeted_status->user->profile_image_url_https.
1002 "' link='https://twitter.com/".$post->retweeted_status->user->screen_name."/status/".$post->retweeted_status->id_str."']".
1004 $_REQUEST['body'] .= "[/share]";
1006 $_REQUEST["body"] = $post->text;
1008 if (is_array($post->entities->media)) {
1009 foreach($post->entities->media AS $media) {
1010 switch($media->type) {
1012 $_REQUEST['body'] = str_replace($media->url, "\n\n[img]".$media->media_url_https."[/img]\n", $_REQUEST['body']);
1018 $converted = twitter_convertmsg($a, $_REQUEST["body"], true);
1019 $_REQUEST['body'] = $converted["body"];
1022 if (is_string($post->place->name))
1023 $_REQUEST["location"] = $post->place->name;
1025 if (is_string($post->place->full_name))
1026 $_REQUEST["location"] = $post->place->full_name;
1028 if (is_array($post->geo->coordinates))
1029 $_REQUEST["coord"] = $post->geo->coordinates[0]." ".$post->geo->coordinates[1];
1031 if (is_array($post->coordinates->coordinates))
1032 $_REQUEST["coord"] = $post->coordinates->coordinates[1]." ".$post->coordinates->coordinates[0];
1034 //print_r($_REQUEST);
1035 logger('twitter: posting for user '.$uid);
1037 // require_once('mod/item.php');
1043 set_pconfig($uid, 'twitter', 'lastid', $lastid);
1046 function twitter_queue_hook(&$a,&$b) {
1048 $qi = q("SELECT * FROM `queue` WHERE `network` = '%s'",
1049 dbesc(NETWORK_TWITTER)
1054 require_once('include/queue_fn.php');
1056 foreach($qi as $x) {
1057 if($x['network'] !== NETWORK_TWITTER)
1060 logger('twitter_queue: run');
1062 $r = q("SELECT `user`.* FROM `user` LEFT JOIN `contact` on `contact`.`uid` = `user`.`uid`
1063 WHERE `contact`.`self` = 1 AND `contact`.`id` = %d LIMIT 1",
1071 $ckey = get_config('twitter', 'consumerkey');
1072 $csecret = get_config('twitter', 'consumersecret');
1073 $otoken = get_pconfig($user['uid'], 'twitter', 'oauthtoken');
1074 $osecret = get_pconfig($user['uid'], 'twitter', 'oauthsecret');
1078 if ($ckey AND $csecret AND $otoken AND $osecret) {
1080 logger('twitter_queue: able to post');
1082 $z = unserialize($x['content']);
1084 require_once("addon/twitter/codebird.php");
1086 $cb = \Codebird\Codebird::getInstance();
1087 $cb->setConsumerKey($ckey, $csecret);
1088 $cb->setToken($otoken, $osecret);
1090 if ($z['url'] == "statuses/update")
1091 $result = $cb->statuses_update($z['post']);
1093 logger('twitter_queue: post result: ' . print_r($result, true), LOGGER_DEBUG);
1095 if ($result->errors)
1096 logger('twitter_queue: Send to Twitter failed: "' . print_r($result->errors, true) . '"');
1099 remove_queue_item($x['id']);
1102 logger("twitter_queue: Error getting tokens for user ".$user['uid']);
1105 logger('twitter_queue: delayed');
1106 update_queue_time($x['id']);
1111 function twitter_fetch_contact($uid, $contact, $create_user) {
1113 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
1114 intval($uid), dbesc("twitter::".$contact->id_str));
1116 if(!count($r) AND !$create_user)
1119 if (count($r) AND ($r[0]["readonly"] OR $r[0]["blocked"])) {
1120 logger("twitter_fetch_contact: Contact '".$r[0]["nick"]."' is blocked or readonly.", LOGGER_DEBUG);
1125 // create contact record
1126 q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `addr`, `alias`, `notify`, `poll`,
1127 `name`, `nick`, `photo`, `network`, `rel`, `priority`,
1128 `writable`, `blocked`, `readonly`, `pending` )
1129 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, 0, 0, 0 ) ",
1131 dbesc(datetime_convert()),
1132 dbesc("https://twitter.com/".$contact->screen_name),
1133 dbesc(normalise_link("https://twitter.com/".$contact->screen_name)),
1134 dbesc($contact->screen_name."@twitter.com"),
1135 dbesc("twitter::".$contact->id_str),
1137 dbesc("twitter::".$contact->id_str),
1138 dbesc($contact->name),
1139 dbesc($contact->screen_name),
1140 dbesc($contact->profile_image_url_https),
1141 dbesc(NETWORK_TWITTER),
1142 intval(CONTACT_IS_FRIEND),
1147 $r = q("SELECT * FROM `contact` WHERE `alias` = '%s' AND `uid` = %d LIMIT 1",
1148 dbesc("twitter::".$contact->id_str),
1155 $contact_id = $r[0]['id'];
1157 $g = q("SELECT def_gid FROM user WHERE uid = %d LIMIT 1",
1161 if($g && intval($g[0]['def_gid'])) {
1162 require_once('include/group.php');
1163 group_add_member($uid,'',$contact_id,$g[0]['def_gid']);
1166 require_once("Photo.php");
1168 $photos = import_profile_photo($contact->profile_image_url_https,$uid,$contact_id);
1170 q("UPDATE `contact` SET `photo` = '%s',
1175 `avatar-date` = '%s'
1180 dbesc(datetime_convert()),
1181 dbesc(datetime_convert()),
1182 dbesc(datetime_convert()),
1186 // update profile photos once every two weeks as we have no notification of when they change.
1188 //$update_photo = (($r[0]['avatar-date'] < datetime_convert('','','now -2 days')) ? true : false);
1189 $update_photo = ($r[0]['avatar-date'] < datetime_convert('','','now -12 hours'));
1191 // check that we have all the photos, this has been known to fail on occasion
1193 if((! $r[0]['photo']) || (! $r[0]['thumb']) || (! $r[0]['micro']) || ($update_photo)) {
1195 logger("twitter_fetch_contact: Updating contact ".$contact->screen_name, LOGGER_DEBUG);
1197 require_once("Photo.php");
1199 $photos = import_profile_photo($contact->profile_image_url_https, $uid, $r[0]['id']);
1201 q("UPDATE `contact` SET `photo` = '%s',
1206 `avatar-date` = '%s',
1216 dbesc(datetime_convert()),
1217 dbesc(datetime_convert()),
1218 dbesc(datetime_convert()),
1219 dbesc("https://twitter.com/".$contact->screen_name),
1220 dbesc(normalise_link("https://twitter.com/".$contact->screen_name)),
1221 dbesc($contact->screen_name."@twitter.com"),
1222 dbesc($contact->name),
1223 dbesc($contact->screen_name),
1229 return($r[0]["id"]);
1232 function twitter_fetchuser($a, $uid, $screen_name = "", $user_id = "") {
1233 $ckey = get_config('twitter', 'consumerkey');
1234 $csecret = get_config('twitter', 'consumersecret');
1235 $otoken = get_pconfig($uid, 'twitter', 'oauthtoken');
1236 $osecret = get_pconfig($uid, 'twitter', 'oauthsecret');
1238 require_once("addon/twitter/codebird.php");
1240 $cb = \Codebird\Codebird::getInstance();
1241 $cb->setConsumerKey($ckey, $csecret);
1242 $cb->setToken($otoken, $osecret);
1244 $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1252 $parameters = array();
1254 if ($screen_name != "")
1255 $parameters["screen_name"] = $screen_name;
1258 $parameters["user_id"] = $user_id;
1260 // Fetching user data
1261 $user = $cb->users_show($parameters);
1263 if (!is_object($user))
1266 $contact_id = twitter_fetch_contact($uid, $user, true);
1271 function twitter_createpost($a, $uid, $post, $self, $create_user, $only_existing_contact) {
1272 $postarray = array();
1273 $postarray['gravity'] = 0;
1274 $postarray['uid'] = $uid;
1275 $postarray['wall'] = 0;
1276 $postarray['uri'] = "twitter::".$post->id_str;
1278 $r = q("SELECT * FROM `item` WHERE `extid` = '%s' AND `uid` = %d LIMIT 1",
1279 dbesc($postarray['uri']),
1288 if ($post->in_reply_to_status_id_str != "") {
1290 $parent = "twitter::".$post->in_reply_to_status_id_str;
1292 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1297 $postarray['thr-parent'] = $r[0]["uri"];
1298 $postarray['parent-uri'] = $r[0]["parent-uri"];
1300 $r = q("SELECT * FROM `item` WHERE `extid` = '%s' AND `uid` = %d LIMIT 1",
1305 $postarray['thr-parent'] = $r[0]['uri'];
1306 $postarray['parent-uri'] = $r[0]['parent-uri'];
1308 $postarray['thr-parent'] = $postarray['uri'];
1309 $postarray['parent-uri'] = $postarray['uri'];
1314 $own_id = get_pconfig($uid, 'twitter', 'own_id');
1316 if ($post->user->id_str == $own_id) {
1317 $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1321 $contactid = $r[0]["id"];
1323 $postarray['owner-name'] = $r[0]["name"];
1324 $postarray['owner-link'] = $r[0]["url"];
1325 $postarray['owner-avatar'] = $r[0]["photo"];
1330 $postarray['parent-uri'] = $postarray['uri'];
1332 if ($contactid == 0) {
1333 $contactid = twitter_fetch_contact($uid, $post->user, $create_user);
1335 $postarray['owner-name'] = $post->user->name;
1336 $postarray['owner-link'] = "https://twitter.com/".$post->user->screen_name;
1337 $postarray['owner-avatar'] = $post->user->profile_image_url_https;
1340 if(($contactid == 0) AND !$only_existing_contact)
1341 $contactid = $self['id'];
1342 elseif ($contactid <= 0)
1345 $postarray['contact-id'] = $contactid;
1347 $postarray['verb'] = ACTIVITY_POST;
1348 $postarray['author-name'] = $postarray['owner-name'];
1349 $postarray['author-link'] = $postarray['owner-link'];
1350 $postarray['author-avatar'] = $postarray['owner-avatar'];
1351 $postarray['plink'] = "https://twitter.com/".$post->user->screen_name."/status/".$post->id_str;
1352 $postarray['app'] = strip_tags($post->source);
1354 if ($post->user->protected) {
1355 $postarray['private'] = 1;
1356 $postarray['allow_cid'] = '<' . $self['id'] . '>';
1359 $postarray['body'] = $post->text;
1362 if (is_array($post->entities->media)) {
1363 foreach($post->entities->media AS $media) {
1364 switch($media->type) {
1366 $postarray['body'] = str_replace($media->url, "\n\n[img]".$media->media_url_https."[/img]\n", $postarray['body']);
1369 $postarray['body'] .= print_r($media, true);
1374 $converted = twitter_convertmsg($a, $postarray['body']);
1375 $postarray['body'] = $converted["body"];
1376 $postarray['tag'] = $converted["tags"];
1378 $postarray['created'] = datetime_convert('UTC','UTC',$post->created_at);
1379 $postarray['edited'] = datetime_convert('UTC','UTC',$post->created_at);
1381 if (is_string($post->place->name))
1382 $postarray["location"] = $post->place->name;
1384 if (is_string($post->place->full_name))
1385 $postarray["location"] = $post->place->full_name;
1387 if (is_array($post->geo->coordinates))
1388 $postarray["coord"] = $post->geo->coordinates[0]." ".$post->geo->coordinates[1];
1390 if (is_array($post->coordinates->coordinates))
1391 $postarray["coord"] = $post->coordinates->coordinates[1]." ".$post->coordinates->coordinates[0];
1393 if (is_object($post->retweeted_status)) {
1395 $postarray['body'] = $post->retweeted_status->text;
1398 if (is_array($post->retweeted_status->entities->media)) {
1399 foreach($post->retweeted_status->entities->media AS $media) {
1400 switch($media->type) {
1402 $postarray['body'] = str_replace($media->url, "\n\n[img]".$media->media_url_https."[/img]\n", $postarray['body']);
1405 $postarray['body'] .= print_r($media, true);
1410 $converted = twitter_convertmsg($a, $postarray['body']);
1411 $postarray['body'] = $converted["body"];
1412 $postarray['tag'] = $converted["tags"];
1415 // Deactivated at the moment, since there are problems with answers to retweets
1416 if (false AND !intval(get_config('system','wall-to-wall_share'))) {
1417 $postarray['body'] = "[share author='".$post->retweeted_status->user->name.
1418 "' profile='https://twitter.com/".$post->retweeted_status->user->screen_name.
1419 "' avatar='".$post->retweeted_status->user->profile_image_url_https.
1420 "' link='https://twitter.com/".$post->retweeted_status->user->screen_name."/status/".$post->retweeted_status->id_str."']".
1422 $postarray['body'] .= "[/share]";
1424 // Let retweets look like wall-to-wall posts
1425 $postarray['author-name'] = $post->retweeted_status->user->name;
1426 $postarray['author-link'] = "https://twitter.com/".$post->retweeted_status->user->screen_name;
1427 $postarray['author-avatar'] = $post->retweeted_status->user->profile_image_url_https;
1434 function twitter_checknotification($a, $uid, $own_id, $top_item, $postarray) {
1436 $user = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` LIMIT 1",
1444 if (link_compare($user[0]["url"], $postarray['author-link']))
1447 $own_user = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
1449 dbesc("twitter::".$own_id)
1452 if(!count($own_user))
1455 // Is it me from twitter?
1456 if (link_compare($own_user[0]["url"], $postarray['author-link']))
1459 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
1460 dbesc($postarray['parent-uri']),
1464 if(count($myconv)) {
1466 foreach($myconv as $conv) {
1467 // now if we find a match, it means we're in this conversation
1469 if(!link_compare($conv['author-link'],$user[0]["url"]) AND !link_compare($conv['author-link'],$own_user[0]["url"]))
1472 require_once('include/enotify.php');
1474 $conv_parent = $conv['parent'];
1477 'type' => NOTIFY_COMMENT,
1478 'notify_flags' => $user[0]['notify-flags'],
1479 'language' => $user[0]['language'],
1480 'to_name' => $user[0]['username'],
1481 'to_email' => $user[0]['email'],
1482 'uid' => $user[0]['uid'],
1483 'item' => $postarray,
1484 'link' => $a->get_baseurl() . '/display/' . $user[0]['nickname'] . '/' . $top_item,
1485 'source_name' => $postarray['author-name'],
1486 'source_link' => $postarray['author-link'],
1487 'source_photo' => $postarray['author-avatar'],
1488 'verb' => ACTIVITY_POST,
1490 'parent' => $conv_parent,
1493 // only send one notification
1499 function twitter_fetchhometimeline($a, $uid) {
1500 $ckey = get_config('twitter', 'consumerkey');
1501 $csecret = get_config('twitter', 'consumersecret');
1502 $otoken = get_pconfig($uid, 'twitter', 'oauthtoken');
1503 $osecret = get_pconfig($uid, 'twitter', 'oauthsecret');
1504 $create_user = get_pconfig($uid, 'twitter', 'create_user');
1506 logger("twitter_fetchhometimeline: Fetching for user ".$uid, LOGGER_DEBUG);
1508 require_once('library/twitteroauth.php');
1509 require_once('include/items.php');
1511 $connection = new TwitterOAuth($ckey,$csecret,$otoken,$osecret);
1513 $own_contact = twitter_fetch_own_contact($a, $uid);
1515 $r = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1516 intval($own_contact),
1520 $own_id = $r[0]["nick"];
1522 logger("twitter_fetchhometimeline: Own twitter contact not found for user ".$uid, LOGGER_DEBUG);
1526 $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1532 logger("twitter_fetchhometimeline: Own contact not found for user ".$uid, LOGGER_DEBUG);
1536 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1539 logger("twitter_fetchhometimeline: Own user not found for user ".$uid, LOGGER_DEBUG);
1543 $parameters = array("exclude_replies" => false, "trim_user" => false, "contributor_details" => true, "include_rts" => true);
1544 //$parameters["count"] = 200;
1547 // Fetching timeline
1548 $lastid = get_pconfig($uid, 'twitter', 'lasthometimelineid');
1550 $first_time = ($lastid == "");
1553 $parameters["since_id"] = $lastid;
1555 $items = $connection->get('statuses/home_timeline', $parameters);
1557 if (!is_array($items)) {
1558 logger("twitter_fetchhometimeline: Error fetching home timeline: ".print_r($items, true), LOGGER_DEBUG);
1562 $posts = array_reverse($items);
1564 logger("twitter_fetchhometimeline: Fetching timeline for user ".$uid." ".sizeof($posts)." items", LOGGER_DEBUG);
1566 if (count($posts)) {
1567 foreach ($posts as $post) {
1568 if ($post->id_str > $lastid)
1569 $lastid = $post->id_str;
1574 $postarray = twitter_createpost($a, $uid, $post, $self, $create_user, true);
1576 if (trim($postarray['body']) == "")
1579 $item = item_store($postarray);
1581 logger('twitter_fetchhometimeline: User '.$self["nick"].' posted home timeline item '.$item);
1584 twitter_checknotification($a, $uid, $own_id, $item, $postarray);
1588 set_pconfig($uid, 'twitter', 'lasthometimelineid', $lastid);
1590 // Fetching mentions
1591 $lastid = get_pconfig($uid, 'twitter', 'lastmentionid');
1593 $first_time = ($lastid == "");
1596 $parameters["since_id"] = $lastid;
1598 $items = $connection->get('statuses/mentions_timeline', $parameters);
1600 if (!is_array($items)) {
1601 logger("twitter_fetchhometimeline: Error fetching mentions: ".print_r($items, true), LOGGER_DEBUG);
1605 $posts = array_reverse($items);
1607 logger("twitter_fetchhometimeline: Fetching mentions for user ".$uid." ".sizeof($posts)." items", LOGGER_DEBUG);
1609 if (count($posts)) {
1610 foreach ($posts as $post) {
1611 if ($post->id_str > $lastid)
1612 $lastid = $post->id_str;
1617 $postarray = twitter_createpost($a, $uid, $post, $self, false, false);
1619 if (trim($postarray['body']) == "")
1622 $item = item_store($postarray);
1624 logger('twitter_fetchhometimeline: User '.$self["nick"].' posted mention timeline item '.$item);
1627 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1628 dbesc($postarray['uri']),
1632 $item = $r[0]['id'];
1636 require_once('include/enotify.php');
1638 'type' => NOTIFY_TAGSELF,
1639 'notify_flags' => $u[0]['notify-flags'],
1640 'language' => $u[0]['language'],
1641 'to_name' => $u[0]['username'],
1642 'to_email' => $u[0]['email'],
1643 'uid' => $u[0]['uid'],
1644 'item' => $postarray,
1645 'link' => $a->get_baseurl() . '/display/' . $u[0]['nickname'] . '/' . $item,
1646 'source_name' => $postarray['author-name'],
1647 'source_link' => $postarray['author-link'],
1648 'source_photo' => $postarray['author-avatar'],
1649 'verb' => ACTIVITY_TAG,
1656 set_pconfig($uid, 'twitter', 'lastmentionid', $lastid);
1659 function twitter_original_url($url, $depth=1, $fetchbody = false) {
1663 $siteinfo = array();
1665 curl_setopt($ch, CURLOPT_URL, $url);
1666 curl_setopt($ch, CURLOPT_HEADER, 1);
1669 curl_setopt($ch, CURLOPT_NOBODY, 0);
1671 curl_setopt($ch, CURLOPT_NOBODY, 1);
1673 curl_setopt($ch, CURLOPT_TIMEOUT, 10);
1674 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
1675 curl_setopt($ch,CURLOPT_USERAGENT,'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20100101 Firefox/24.0');
1677 $header = curl_exec($ch);
1678 $curl_info = @curl_getinfo($ch);
1679 $http_code = $curl_info['http_code'];
1682 if ((($curl_info['http_code'] == "301") OR ($curl_info['http_code'] == "302"))
1683 AND (($curl_info['redirect_url'] != "") OR ($curl_info['location'] != ""))) {
1684 if ($curl_info['redirect_url'] != "")
1685 return(twitter_original_url($curl_info['redirect_url'], ++$depth, $fetchbody));
1687 return(twitter_original_url($curl_info['location'], ++$depth, $fetchbody));
1690 $pos = strpos($header, "\r\n\r\n");
1693 $body = trim(substr($header, $pos));
1697 if (trim($body) == "")
1698 return(twitter_original_url($url, ++$depth, true));
1700 $doc = new DOMDocument();
1701 @$doc->loadHTML($body);
1703 $xpath = new DomXPath($doc);
1705 $list = $xpath->query("//meta[@content]");
1706 foreach ($list as $node) {
1708 if ($node->attributes->length)
1709 foreach ($node->attributes as $attribute)
1710 $attr[$attribute->name] = $attribute->value;
1712 if (@$attr["http-equiv"] == 'refresh') {
1713 $path = $attr["content"];
1714 $pathinfo = explode(";", $path);
1716 foreach ($pathinfo AS $value)
1717 if (substr(strtolower($value), 0, 4) == "url=")
1718 return(twitter_original_url(substr($value, 4), ++$depth));
1725 function twitter_siteinfo($url) {
1726 require_once("mod/parse_url.php");
1728 $data = parseurl_getsiteinfo($url);
1730 if (!is_string($data["text"]) AND (sizeof($data["images"]) == 0) AND ($data["title"] == $url))
1733 if (is_string($data["title"]))
1734 $text .= "[bookmark=".$url."]".trim($data["title"])."[/bookmark]\n";
1736 if (sizeof($data["images"]) > 0) {
1737 $imagedata = $data["images"][0];
1738 $text .= '[img='.$imagedata["width"].'x'.$imagedata["height"].']'.$imagedata["src"].'[/img]' . "\n";
1741 if (is_string($data["text"]))
1742 $text .= "[quote]".$data["text"]."[/quote]";
1748 function twitter_convertmsg($a, $body, $no_tags = false) {
1750 $links = preg_match_all("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", $body,$matches,PREG_SET_ORDER);
1755 foreach ($matches AS $match) {
1756 $expanded_url = twitter_original_url($match[2]);
1761 if (strstr($expanded_url, "//www.youtube.com/"))
1762 $body = str_replace($match[2], "\n[youtube]".$expanded_url."[/youtube]\n", $body);
1763 elseif (strstr($expanded_url, "//player.vimeo.com/"))
1764 $body = str_replace($match[2], "\n[vimeo]".$expanded_url."[/vimeo]\n", $body);
1765 elseif (strstr($expanded_url, "//instagram.com"))
1766 $body = str_replace($match[2], "\n[url]".$expanded_url."[/url]\n", $body);
1768 $img_str = fetch_url($expanded_url, true, $redirects, 4);
1770 $tempfile = tempnam(get_config("system","temppath"), "cache");
1771 file_put_contents($tempfile, $img_str);
1772 $mime = image_type_to_mime_type(exif_imagetype($tempfile));
1775 if (substr($mime, 0, 6) == "image/")
1776 $body = str_replace($match[2], "[img]".$expanded_url."[/img]", $body);
1779 //if ($footer == "")
1780 $footer = "\n\n".twitter_siteinfo($expanded_url);
1781 $footerlink = "[url=".$expanded_url."]".$expanded_url."[/url]";
1783 $body = str_replace($match[2], $footerlink, $body);
1788 if (($footerlink != "") AND ($footer != "")) {
1789 $removedlink = trim(str_replace($footerlink, "", $body));
1791 if (strstr($body, $removedlink))
1792 $body = $removedlink;
1799 return(array("body" => $body, $tags => ""));
1803 $tags = get_tags($body);
1806 foreach($tags as $tag) {
1807 if (strstr(trim($tag), " "))
1810 if(strpos($tag,'#') === 0) {
1811 if(strpos($tag,'[url='))
1814 // don't link tags that are already embedded in links
1816 if(preg_match('/\[(.*?)' . preg_quote($tag,'/') . '(.*?)\]/',$body))
1818 if(preg_match('/\[(.*?)\]\((.*?)' . preg_quote($tag,'/') . '(.*?)\)/',$body))
1821 $basetag = str_replace('_',' ',substr($tag,1));
1822 $body = str_replace($tag,'#[url=' . $a->get_baseurl() . '/search?tag=' . rawurlencode($basetag) . ']' . $basetag . '[/url]',$body);
1823 if(strlen($str_tags))
1825 $str_tags .= '#[url=' . $a->get_baseurl() . '/search?tag=' . rawurlencode($basetag) . ']' . $basetag . '[/url]';
1827 } elseif(strpos($tag,'@') === 0) {
1828 $basetag = substr($tag,1);
1829 $body = str_replace($tag,'@[url=https://twitter.com/' . rawurlencode($basetag) . ']' . $basetag . '[/url]',$body);
1835 $cnt = preg_match_all('/@\[url=(.*?)\[\/url\]/ism',$body,$matches,PREG_SET_ORDER);
1837 foreach($matches as $mtch) {
1838 if(strlen($str_tags))
1840 $str_tags .= '@[url=' . $mtch[1] . '[/url]';
1844 return(array("body"=>$body, "tags"=>$str_tags));
1848 function twitter_fetch_own_contact($a, $uid) {
1849 $ckey = get_config('twitter', 'consumerkey');
1850 $csecret = get_config('twitter', 'consumersecret');
1851 $otoken = get_pconfig($uid, 'twitter', 'oauthtoken');
1852 $osecret = get_pconfig($uid, 'twitter', 'oauthsecret');
1854 $own_id = get_pconfig($uid, 'twitter', 'own_id');
1858 if ($own_id == "") {
1859 require_once('library/twitteroauth.php');
1861 $connection = new TwitterOAuth($ckey,$csecret,$otoken,$osecret);
1863 // Fetching user data
1864 $user = $connection->get('account/verify_credentials');
1866 set_pconfig($uid, 'twitter', 'own_id', $user->id_str);
1868 $contact_id = twitter_fetch_contact($uid, $user, true);
1871 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
1872 intval($uid), dbesc("twitter::".$own_id));
1874 $contact_id = $r[0]["id"];
1877 return($contact_id);