Official Addons for the Friendica Communications Platform. (please note that this is a clone of the repository at github, issues are handled there)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2037 lines
67 KiB

9 years ago
9 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  1. <?php
  2. /**
  3. * Name: Twitter Connector
  4. * Description: Bidirectional (posting, relaying and reading) connector for Twitter.
  5. * Version: 1.1.0
  6. * Author: Tobias Diekershoff <https://f.diekershoff.de/profile/tobias>
  7. * Author: Michael Vogel <https://pirati.ca/profile/heluecht>
  8. * Maintainer: Hypolite Petovan <https://friendica.mrpetovan.com/profile/hypolite>
  9. *
  10. * Copyright (c) 2011-2013 Tobias Diekershoff, Michael Vogel, Hypolite Petovan
  11. * All rights reserved.
  12. *
  13. * Redistribution and use in source and binary forms, with or without
  14. * modification, are permitted provided that the following conditions are met:
  15. * * Redistributions of source code must retain the above copyright notice,
  16. * this list of conditions and the following disclaimer.
  17. * * Redistributions in binary form must reproduce the above
  18. * * copyright notice, this list of conditions and the following disclaimer in
  19. * the documentation and/or other materials provided with the distribution.
  20. * * Neither the name of the <organization> nor the names of its contributors
  21. * may be used to endorse or promote products derived from this software
  22. * without specific prior written permission.
  23. *
  24. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  25. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  26. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  27. * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT,
  28. * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  29. * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  30. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  31. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
  32. * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  33. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  34. *
  35. */
  36. /* Twitter Addon for Friendica
  37. *
  38. * Author: Tobias Diekershoff
  39. * tobias.diekershoff@gmx.net
  40. *
  41. * License:3-clause BSD license
  42. *
  43. * Configuration:
  44. * To use this addon you need a OAuth Consumer key pair (key & secret)
  45. * you can get it from Twitter at https://twitter.com/apps
  46. *
  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.
  50. *
  51. * Add this key pair to your global config/addon.config.php or use the admin panel.
  52. *
  53. * 'twitter' => [
  54. * 'consumerkey' => '',
  55. * 'consumersecret' => '',
  56. * ],
  57. *
  58. * To activate the addon itself add it to the system.addon
  59. * setting. After this, your user can configure their Twitter account settings
  60. * from "Settings -> Addon Settings".
  61. *
  62. * Requirements: PHP5, curl
  63. */
  64. use Abraham\TwitterOAuth\TwitterOAuth;
  65. use Abraham\TwitterOAuth\TwitterOAuthException;
  66. use Codebird\Codebird;
  67. use Friendica\App;
  68. use Friendica\Content\OEmbed;
  69. use Friendica\Content\PageInfo;
  70. use Friendica\Content\Text\BBCode;
  71. use Friendica\Content\Text\Plaintext;
  72. use Friendica\Core\Hook;
  73. use Friendica\Core\Logger;
  74. use Friendica\Core\Protocol;
  75. use Friendica\Core\Renderer;
  76. use Friendica\Core\Worker;
  77. use Friendica\Database\DBA;
  78. use Friendica\DI;
  79. use Friendica\Model\Contact;
  80. use Friendica\Model\Conversation;
  81. use Friendica\Model\Group;
  82. use Friendica\Model\Item;
  83. use Friendica\Model\ItemContent;
  84. use Friendica\Model\ItemURI;
  85. use Friendica\Model\Tag;
  86. use Friendica\Model\User;
  87. use Friendica\Protocol\Activity;
  88. use Friendica\Util\ConfigFileLoader;
  89. use Friendica\Util\DateTimeFormat;
  90. use Friendica\Util\Images;
  91. use Friendica\Util\Network;
  92. use Friendica\Util\Strings;
  93. require_once __DIR__ . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
  94. define('TWITTER_DEFAULT_POLL_INTERVAL', 5); // given in minutes
  95. function twitter_install()
  96. {
  97. // we need some hooks, for the configuration and for sending tweets
  98. Hook::register('load_config' , __FILE__, 'twitter_load_config');
  99. Hook::register('connector_settings' , __FILE__, 'twitter_settings');
  100. Hook::register('connector_settings_post', __FILE__, 'twitter_settings_post');
  101. Hook::register('hook_fork' , __FILE__, 'twitter_hook_fork');
  102. Hook::register('post_local' , __FILE__, 'twitter_post_local');
  103. Hook::register('notifier_normal' , __FILE__, 'twitter_post_hook');
  104. Hook::register('jot_networks' , __FILE__, 'twitter_jot_nets');
  105. Hook::register('cron' , __FILE__, 'twitter_cron');
  106. Hook::register('follow' , __FILE__, 'twitter_follow');
  107. Hook::register('expire' , __FILE__, 'twitter_expire');
  108. Hook::register('prepare_body' , __FILE__, 'twitter_prepare_body');
  109. Hook::register('check_item_notification', __FILE__, 'twitter_check_item_notification');
  110. Hook::register('probe_detect' , __FILE__, 'twitter_probe_detect');
  111. Logger::info("installed twitter");
  112. }
  113. // Hook functions
  114. function twitter_load_config(App $a, ConfigFileLoader $loader)
  115. {
  116. $a->getConfigCache()->load($loader->loadAddonConfig('twitter'));
  117. }
  118. function twitter_check_item_notification(App $a, array &$notification_data)
  119. {
  120. $own_id = DI::pConfig()->get($notification_data["uid"], 'twitter', 'own_id');
  121. $own_user = q("SELECT `url` FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
  122. intval($notification_data["uid"]),
  123. DBA::escape("twitter::".$own_id)
  124. );
  125. if ($own_user) {
  126. $notification_data["profiles"][] = $own_user[0]["url"];
  127. }
  128. }
  129. function twitter_follow(App $a, array &$contact)
  130. {
  131. Logger::info('Check if contact is twitter contact', ['url' => $contact["url"]]);
  132. if (!strstr($contact["url"], "://twitter.com") && !strstr($contact["url"], "@twitter.com")) {
  133. return;
  134. }
  135. // contact seems to be a twitter contact, so continue
  136. $nickname = preg_replace("=https?://twitter.com/(.*)=ism", "$1", $contact["url"]);
  137. $nickname = str_replace("@twitter.com", "", $nickname);
  138. $uid = $a->user["uid"];
  139. $ckey = DI::config()->get('twitter', 'consumerkey');
  140. $csecret = DI::config()->get('twitter', 'consumersecret');
  141. $otoken = DI::pConfig()->get($uid, 'twitter', 'oauthtoken');
  142. $osecret = DI::pConfig()->get($uid, 'twitter', 'oauthsecret');
  143. // If the addon is not configured (general or for this user) quit here
  144. if (empty($ckey) || empty($csecret) || empty($otoken) || empty($osecret)) {
  145. $contact = false;
  146. return;
  147. }
  148. $connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
  149. $connection->post('friendships/create', ['screen_name' => $nickname]);
  150. $user = twitter_fetchuser($nickname);
  151. $contact_id = twitter_fetch_contact($uid, $user, true);
  152. $contact = Contact::getById($contact_id, ['name', 'nick', 'url', 'addr', 'batch', 'notify', 'poll', 'request', 'confirm', 'poco', 'photo', 'priority', 'network', 'alias', 'pubkey']);
  153. if (DBA::isResult($contact)) {
  154. $contact["contact"] = $contact;
  155. }
  156. }
  157. function twitter_jot_nets(App $a, array &$jotnets_fields)
  158. {
  159. if (!local_user()) {
  160. return;
  161. }
  162. if (DI::pConfig()->get(local_user(), 'twitter', 'post')) {
  163. $jotnets_fields[] = [
  164. 'type' => 'checkbox',
  165. 'field' => [
  166. 'twitter_enable',
  167. DI::l10n()->t('Post to Twitter'),
  168. DI::pConfig()->get(local_user(), 'twitter', 'post_by_default')
  169. ]
  170. ];
  171. }
  172. }
  173. function twitter_settings_post(App $a)
  174. {
  175. if (!local_user()) {
  176. return;
  177. }
  178. // don't check twitter settings if twitter submit button is not clicked
  179. if (empty($_POST['twitter-disconnect']) && empty($_POST['twitter-submit'])) {
  180. return;
  181. }
  182. if (!empty($_POST['twitter-disconnect'])) {
  183. /* * *
  184. * if the twitter-disconnect checkbox is set, clear the OAuth key/secret pair
  185. * from the user configuration
  186. */
  187. DI::pConfig()->delete(local_user(), 'twitter', 'consumerkey');
  188. DI::pConfig()->delete(local_user(), 'twitter', 'consumersecret');
  189. DI::pConfig()->delete(local_user(), 'twitter', 'oauthtoken');
  190. DI::pConfig()->delete(local_user(), 'twitter', 'oauthsecret');
  191. DI::pConfig()->delete(local_user(), 'twitter', 'post');
  192. DI::pConfig()->delete(local_user(), 'twitter', 'post_by_default');
  193. DI::pConfig()->delete(local_user(), 'twitter', 'lastid');
  194. DI::pConfig()->delete(local_user(), 'twitter', 'mirror_posts');
  195. DI::pConfig()->delete(local_user(), 'twitter', 'import');
  196. DI::pConfig()->delete(local_user(), 'twitter', 'create_user');
  197. DI::pConfig()->delete(local_user(), 'twitter', 'own_id');
  198. } else {
  199. if (isset($_POST['twitter-pin'])) {
  200. // if the user supplied us with a PIN from Twitter, let the magic of OAuth happen
  201. Logger::notice('got a Twitter PIN');
  202. $ckey = DI::config()->get('twitter', 'consumerkey');
  203. $csecret = DI::config()->get('twitter', 'consumersecret');
  204. // the token and secret for which the PIN was generated were hidden in the settings
  205. // form as token and token2, we need a new connection to Twitter using these token
  206. // and secret to request a Access Token with the PIN
  207. try {
  208. if (empty($_POST['twitter-pin'])) {
  209. throw new Exception(DI::l10n()->t('You submitted an empty PIN, please Sign In with Twitter again to get a new one.'));
  210. }
  211. $connection = new TwitterOAuth($ckey, $csecret, $_POST['twitter-token'], $_POST['twitter-token2']);
  212. $token = $connection->oauth("oauth/access_token", ["oauth_verifier" => $_POST['twitter-pin']]);
  213. // ok, now that we have the Access Token, save them in the user config
  214. DI::pConfig()->set(local_user(), 'twitter', 'oauthtoken', $token['oauth_token']);
  215. DI::pConfig()->set(local_user(), 'twitter', 'oauthsecret', $token['oauth_token_secret']);
  216. DI::pConfig()->set(local_user(), 'twitter', 'post', 1);
  217. } catch(Exception $e) {
  218. info($e->getMessage());
  219. } catch(TwitterOAuthException $e) {
  220. info($e->getMessage());
  221. }
  222. // reload the Addon Settings page, if we don't do it see Bug #42
  223. DI::baseUrl()->redirect('settings/connectors');
  224. } else {
  225. // if no PIN is supplied in the POST variables, the user has changed the setting
  226. // to post a tweet for every new __public__ posting to the wall
  227. DI::pConfig()->set(local_user(), 'twitter', 'post', intval($_POST['twitter-enable']));
  228. DI::pConfig()->set(local_user(), 'twitter', 'post_by_default', intval($_POST['twitter-default']));
  229. DI::pConfig()->set(local_user(), 'twitter', 'mirror_posts', intval($_POST['twitter-mirror']));
  230. DI::pConfig()->set(local_user(), 'twitter', 'import', intval($_POST['twitter-import']));
  231. DI::pConfig()->set(local_user(), 'twitter', 'create_user', intval($_POST['twitter-create_user']));
  232. if (!intval($_POST['twitter-mirror'])) {
  233. DI::pConfig()->delete(local_user(), 'twitter', 'lastid');
  234. }
  235. info(DI::l10n()->t('Twitter settings updated.') . EOL);
  236. }
  237. }
  238. }
  239. function twitter_settings(App $a, &$s)
  240. {
  241. if (!local_user()) {
  242. return;
  243. }
  244. DI::page()['htmlhead'] .= '<link rel="stylesheet" type="text/css" href="' . DI::baseUrl()->get() . '/addon/twitter/twitter.css' . '" media="all" />' . "\r\n";
  245. /* * *
  246. * 1) Check that we have global consumer key & secret
  247. * 2) If no OAuthtoken & stuff is present, generate button to get some
  248. * 3) Checkbox for "Send public notices (280 chars only)
  249. */
  250. $ckey = DI::config()->get('twitter', 'consumerkey');
  251. $csecret = DI::config()->get('twitter', 'consumersecret');
  252. $otoken = DI::pConfig()->get(local_user(), 'twitter', 'oauthtoken');
  253. $osecret = DI::pConfig()->get(local_user(), 'twitter', 'oauthsecret');
  254. $enabled = intval(DI::pConfig()->get(local_user(), 'twitter', 'post'));
  255. $defenabled = intval(DI::pConfig()->get(local_user(), 'twitter', 'post_by_default'));
  256. $mirrorenabled = intval(DI::pConfig()->get(local_user(), 'twitter', 'mirror_posts'));
  257. $importenabled = intval(DI::pConfig()->get(local_user(), 'twitter', 'import'));
  258. $create_userenabled = intval(DI::pConfig()->get(local_user(), 'twitter', 'create_user'));
  259. $css = (($enabled) ? '' : '-disabled');
  260. $s .= '<span id="settings_twitter_inflated" class="settings-block fakelink" style="display: block;" onclick="openClose(\'settings_twitter_expanded\'); openClose(\'settings_twitter_inflated\');">';
  261. $s .= '<img class="connector' . $css . '" src="images/twitter.png" /><h3 class="connector">' . DI::l10n()->t('Twitter Import/Export/Mirror') . '</h3>';
  262. $s .= '</span>';
  263. $s .= '<div id="settings_twitter_expanded" class="settings-block" style="display: none;">';
  264. $s .= '<span class="fakelink" onclick="openClose(\'settings_twitter_expanded\'); openClose(\'settings_twitter_inflated\');">';
  265. $s .= '<img class="connector' . $css . '" src="images/twitter.png" /><h3 class="connector">' . DI::l10n()->t('Twitter Import/Export/Mirror') . '</h3>';
  266. $s .= '</span>';
  267. if ((!$ckey) && (!$csecret)) {
  268. /* no global consumer keys
  269. * display warning and skip personal config
  270. */
  271. $s .= '<p>' . DI::l10n()->t('No consumer key pair for Twitter found. Please contact your site administrator.') . '</p>';
  272. } else {
  273. // ok we have a consumer key pair now look into the OAuth stuff
  274. if ((!$otoken) && (!$osecret)) {
  275. /* the user has not yet connected the account to twitter...
  276. * get a temporary OAuth key/secret pair and display a button with
  277. * which the user can request a PIN to connect the account to a
  278. * account at Twitter.
  279. */
  280. $connection = new TwitterOAuth($ckey, $csecret);
  281. try {
  282. $result = $connection->oauth('oauth/request_token', ['oauth_callback' => 'oob']);
  283. $s .= '<p>' . DI::l10n()->t('At this Friendica instance the Twitter addon 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>';
  284. $s .= '<a href="' . $connection->url('oauth/authorize', ['oauth_token' => $result['oauth_token']]) . '" target="_twitter"><img src="addon/twitter/lighter.png" alt="' . DI::l10n()->t('Log in with Twitter') . '"></a>';
  285. $s .= '<div id="twitter-pin-wrapper">';
  286. $s .= '<label id="twitter-pin-label" for="twitter-pin">' . DI::l10n()->t('Copy the PIN from Twitter here') . '</label>';
  287. $s .= '<input id="twitter-pin" type="text" name="twitter-pin" />';
  288. $s .= '<input id="twitter-token" type="hidden" name="twitter-token" value="' . $result['oauth_token'] . '" />';
  289. $s .= '<input id="twitter-token2" type="hidden" name="twitter-token2" value="' . $result['oauth_token_secret'] . '" />';
  290. $s .= '</div><div class="clear"></div>';
  291. $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="twitter-submit" class="settings-submit" value="' . DI::l10n()->t('Save Settings') . '" /></div>';
  292. } catch (TwitterOAuthException $e) {
  293. $s .= '<p>' . DI::l10n()->t('An error occured: ') . $e->getMessage() . '</p>';
  294. }
  295. } else {
  296. /* * *
  297. * we have an OAuth key / secret pair for the user
  298. * so let's give a chance to disable the postings to Twitter
  299. */
  300. $connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
  301. try {
  302. $details = $connection->get('account/verify_credentials');
  303. $field_checkbox = Renderer::getMarkupTemplate('field_checkbox.tpl');
  304. if (property_exists($details, 'screen_name') &&
  305. property_exists($details, 'description') &&
  306. property_exists($details, 'profile_image_url')) {
  307. $s .= '<div id="twitter-info" >
  308. <p>' . DI::l10n()->t('Currently connected to: ') . '<a href="https://twitter.com/' . $details->screen_name . '" target="_twitter">' . $details->screen_name . '</a>
  309. <button type="submit" name="twitter-disconnect" value="1">' . DI::l10n()->t('Disconnect') . '</button>
  310. </p>
  311. <p id="twitter-info-block">
  312. <a href="https://twitter.com/' . $details->screen_name . '" target="_twitter"><img id="twitter-avatar" src="' . $details->profile_image_url . '" /></a>
  313. <em>' . $details->description . '</em>
  314. </p>
  315. </div>';
  316. } else {
  317. $s .= '<div id="twitter-info" >
  318. <p>Invalid Twitter info</p>
  319. </div>';
  320. Logger::info('Invalid twitter info (verify credentials).', ['auth' => TwitterOAuth::class]);
  321. }
  322. $s .= '<div class="clear"></div>';
  323. $s .= Renderer::replaceMacros($field_checkbox, [
  324. '$field' => ['twitter-enable', DI::l10n()->t('Allow posting to Twitter'), $enabled, DI::l10n()->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.')]
  325. ]);
  326. if ($a->user['hidewall']) {
  327. $s .= '<p>' . DI::l10n()->t('<strong>Note</strong>: Due to 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>';
  328. }
  329. $s .= Renderer::replaceMacros($field_checkbox, [
  330. '$field' => ['twitter-default', DI::l10n()->t('Send public postings to Twitter by default'), $defenabled,