summaryrefslogtreecommitdiff
path: root/plugins/TwitterBridge
diff options
context:
space:
mode:
authorZach Copley <zach@controlyourself.ca>2009-08-26 00:59:06 +0000
committerZach Copley <zach@controlyourself.ca>2009-08-26 00:59:06 +0000
commit5efe588174c71979fc1970353c9a556ea441f138 (patch)
treeb55c3fe906561d73f6bddb21f8ed88b712f0b1a7 /plugins/TwitterBridge
parent9b9d80cd97704426e54434d8777c5c15154014ad (diff)
Moved the rest of the Twitter stuff into the TwitterBridge plugin
Diffstat (limited to 'plugins/TwitterBridge')
-rw-r--r--plugins/TwitterBridge/TwitterBridgePlugin.php2
-rwxr-xr-xplugins/TwitterBridge/daemons/synctwitterfriends.php280
-rwxr-xr-xplugins/TwitterBridge/daemons/twitterqueuehandler.php73
-rwxr-xr-xplugins/TwitterBridge/daemons/twitterstatusfetcher.php559
-rw-r--r--plugins/TwitterBridge/twitter.php258
-rw-r--r--plugins/TwitterBridge/twitterauthorization.php2
-rw-r--r--plugins/TwitterBridge/twittersettings.php4
7 files changed, 1175 insertions, 3 deletions
diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php
index f7daa9a22..a8de1c664 100644
--- a/plugins/TwitterBridge/TwitterBridgePlugin.php
+++ b/plugins/TwitterBridge/TwitterBridgePlugin.php
@@ -90,7 +90,7 @@ class TwitterBridgePlugin extends Plugin
require_once(INSTALLDIR.'/plugins/TwitterBridge/' . strtolower(mb_substr($cls, 0, -6)) . '.php');
return false;
case 'TwitterOAuthClient':
- require_once(INSTALLDIR.'/plugins/TwitterBridge/twitteroAuthclient.php');
+ require_once(INSTALLDIR.'/plugins/TwitterBridge/twitteroauthclient.php');
return false;
default:
return true;
diff --git a/plugins/TwitterBridge/daemons/synctwitterfriends.php b/plugins/TwitterBridge/daemons/synctwitterfriends.php
new file mode 100755
index 000000000..b7be5d043
--- /dev/null
+++ b/plugins/TwitterBridge/daemons/synctwitterfriends.php
@@ -0,0 +1,280 @@
+#!/usr/bin/env php
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, Control Yourself, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..'));
+
+$shortoptions = 'di::';
+$longoptions = array('id::', 'debug');
+
+$helptext = <<<END_OF_TRIM_HELP
+Batch script for synching local friends with Twitter friends.
+ -i --id Identity (default 'generic')
+ -d --debug Debug (lots of log output)
+
+END_OF_TRIM_HELP;
+
+require_once INSTALLDIR . '/scripts/commandline.inc';
+require_once INSTALLDIR . '/lib/parallelizingdaemon.php';
+require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
+
+/**
+ * Daemon to sync local friends with Twitter friends
+ *
+ * @category Twitter
+ * @package Laconica
+ * @author Zach Copley <zach@controlyourself.ca>
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+$helptext = <<<END_OF_TWITTER_HELP
+Batch script for synching local friends with Twitter friends.
+
+END_OF_TWITTER_HELP;
+
+require_once INSTALLDIR . '/scripts/commandline.inc';
+require_once INSTALLDIR . '/lib/parallelizingdaemon.php';
+
+class SyncTwitterFriendsDaemon extends ParallelizingDaemon
+{
+ /**
+ * Constructor
+ *
+ * @param string $id the name/id of this daemon
+ * @param int $interval sleep this long before doing everything again
+ * @param int $max_children maximum number of child processes at a time
+ * @param boolean $debug debug output flag
+ *
+ * @return void
+ *
+ **/
+
+ function __construct($id = null, $interval = 60,
+ $max_children = 2, $debug = null)
+ {
+ parent::__construct($id, $interval, $max_children, $debug);
+ }
+
+ /**
+ * Name of this daemon
+ *
+ * @return string Name of the daemon.
+ */
+
+ function name()
+ {
+ return ('synctwitterfriends.' . $this->_id);
+ }
+
+ /**
+ * Find all the Twitter foreign links for users who have requested
+ * automatically subscribing to their Twitter friends locally.
+ *
+ * @return array flinks an array of Foreign_link objects
+ */
+ function getObjects()
+ {
+ $flinks = array();
+ $flink = new Foreign_link();
+
+ $conn = &$flink->getDatabaseConnection();
+
+ $flink->service = TWITTER_SERVICE;
+ $flink->orderBy('last_friendsync');
+ $flink->limit(25); // sync this many users during this run
+ $flink->find();
+
+ while ($flink->fetch()) {
+ if (($flink->friendsync & FOREIGN_FRIEND_RECV) == FOREIGN_FRIEND_RECV) {
+ $flinks[] = clone($flink);
+ }
+ }
+
+ $conn->disconnect();
+
+ global $_DB_DATAOBJECT;
+ unset($_DB_DATAOBJECT['CONNECTIONS']);
+
+ return $flinks;
+ }
+
+ function childTask($flink) {
+
+ // Each child ps needs its own DB connection
+
+ // Note: DataObject::getDatabaseConnection() creates
+ // a new connection if there isn't one already
+
+ $conn = &$flink->getDatabaseConnection();
+
+ $this->subscribeTwitterFriends($flink);
+
+ $flink->last_friendsync = common_sql_now();
+ $flink->update();
+
+ $conn->disconnect();
+
+ // XXX: Couldn't find a less brutal way to blow
+ // away a cached connection
+
+ global $_DB_DATAOBJECT;
+ unset($_DB_DATAOBJECT['CONNECTIONS']);
+ }
+
+ function fetchTwitterFriends($flink)
+ {
+ $friends = array();
+
+ $token = TwitterOAuthClient::unpackToken($flink->credentials);
+
+ $client = new TwitterOAuthClient($token->key, $token->secret);
+
+ try {
+ $friends_ids = $client->friendsIds();
+ } catch (OAuthCurlException $e) {
+ common_log(LOG_WARNING, $this->name() .
+ ' - cURL error getting friend ids ' .
+ $e->getCode() . ' - ' . $e->getMessage());
+ return $friends;
+ }
+
+ if (empty($friends_ids)) {
+ common_debug($this->name() .
+ " - Twitter user $flink->foreign_id " .
+ 'doesn\'t have any friends!');
+ return $friends;
+ }
+
+ common_debug($this->name() . ' - Twitter\'s API says Twitter user id ' .
+ "$flink->foreign_id has " .
+ count($friends_ids) . ' friends.');
+
+ // Calculate how many pages to get...
+ $pages = ceil(count($friends_ids) / 100);
+
+ if ($pages == 0) {
+ common_debug($this->name() . " - $user seems to have no friends.");
+ }
+
+ for ($i = 1; $i <= $pages; $i++) {
+
+ try {
+ $more_friends = $client->statusesFriends(null, null, null, $i);
+ } catch (OAuthCurlException $e) {
+ common_log(LOG_WARNING, $this->name() .
+ ' - cURL error getting Twitter statuses/friends ' .
+ "page $i - " . $e->getCode() . ' - ' .
+ $e->getMessage());
+ }
+
+ if (empty($more_friends)) {
+ common_log(LOG_WARNING, $this->name() .
+ " - Couldn't retrieve page $i " .
+ "of Twitter user $flink->foreign_id friends.");
+ continue;
+ } else {
+ $friends = array_merge($friends, $more_friends);
+ }
+ }
+
+ return $friends;
+ }
+
+ function subscribeTwitterFriends($flink)
+ {
+ $friends = $this->fetchTwitterFriends($flink);
+
+ if (empty($friends)) {
+ common_debug($this->name() .
+ ' - Couldn\'t get friends from Twitter for ' .
+ "Twitter user $flink->foreign_id.");
+ return false;
+ }
+
+ $user = $flink->getUser();
+
+ foreach ($friends as $friend) {
+
+ $friend_name = $friend->screen_name;
+ $friend_id = (int) $friend->id;
+
+ // Update or create the Foreign_user record for each
+ // Twitter friend
+
+ if (!save_twitter_user($friend_id, $friend_name)) {
+ common_log(LOG_WARNING, $this-name() .
+ " - Couldn't save $screen_name's friend, $friend_name.");
+ continue;
+ }
+
+ // Check to see if there's a related local user
+
+ $friend_flink = Foreign_link::getByForeignID($friend_id,
+ TWITTER_SERVICE);
+
+ if (!empty($friend_flink)) {
+
+ // Get associated user and subscribe her
+
+ $friend_user = User::staticGet('id', $friend_flink->user_id);
+
+ if (!empty($friend_user)) {
+ $result = subs_subscribe_to($user, $friend_user);
+
+ if ($result === true) {
+ common_log(LOG_INFO,
+ $this->name() . ' - Subscribed ' .
+ "$friend_user->nickname to $user->nickname.");
+ } else {
+ common_debug($this->name() .
+ ' - Tried subscribing ' .
+ "$friend_user->nickname to $user->nickname - " .
+ $result);
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+}
+
+$id = null;
+$debug = null;
+
+if (have_option('i')) {
+ $id = get_option_value('i');
+} else if (have_option('--id')) {
+ $id = get_option_value('--id');
+} else if (count($args) > 0) {
+ $id = $args[0];
+} else {
+ $id = null;
+}
+
+if (have_option('d') || have_option('debug')) {
+ $debug = true;
+}
+
+$syncer = new SyncTwitterFriendsDaemon($id, 60, 2, $debug);
+$syncer->runOnce();
+
diff --git a/plugins/TwitterBridge/daemons/twitterqueuehandler.php b/plugins/TwitterBridge/daemons/twitterqueuehandler.php
new file mode 100755
index 000000000..9aa9d1ed0
--- /dev/null
+++ b/plugins/TwitterBridge/daemons/twitterqueuehandler.php
@@ -0,0 +1,73 @@
+#!/usr/bin/env php
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, Control Yourself, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..'));
+
+$shortoptions = 'i::';
+$longoptions = array('id::');
+
+$helptext = <<<END_OF_ENJIT_HELP
+Daemon script for pushing new notices to Twitter.
+
+ -i --id Identity (default none)
+
+END_OF_ENJIT_HELP;
+
+require_once INSTALLDIR . '/scripts/commandline.inc';
+require_once INSTALLDIR . '/lib/queuehandler.php';
+require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
+
+class TwitterQueueHandler extends QueueHandler
+{
+ function transport()
+ {
+ return 'twitter';
+ }
+
+ function start()
+ {
+ $this->log(LOG_INFO, "INITIALIZE");
+ return true;
+ }
+
+ function handle_notice($notice)
+ {
+ return broadcast_twitter($notice);
+ }
+
+ function finish()
+ {
+ }
+
+}
+
+if (have_option('i')) {
+ $id = get_option_value('i');
+} else if (have_option('--id')) {
+ $id = get_option_value('--id');
+} else if (count($args) > 0) {
+ $id = $args[0];
+} else {
+ $id = null;
+}
+
+$handler = new TwitterQueueHandler($id);
+
+$handler->runOnce();
diff --git a/plugins/TwitterBridge/daemons/twitterstatusfetcher.php b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php
new file mode 100755
index 000000000..3023bf423
--- /dev/null
+++ b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php
@@ -0,0 +1,559 @@
+#!/usr/bin/env php
+<?php
+/**
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, Control Yourself, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..'));
+
+// Tune number of processes and how often to poll Twitter
+// XXX: Should these things be in config.php?
+define('MAXCHILDREN', 2);
+define('POLL_INTERVAL', 60); // in seconds
+
+$shortoptions = 'di::';
+$longoptions = array('id::', 'debug');
+
+$helptext = <<<END_OF_TRIM_HELP
+Batch script for retrieving Twitter messages from foreign service.
+
+ -i --id Identity (default 'generic')
+ -d --debug Debug (lots of log output)
+
+END_OF_TRIM_HELP;
+
+require_once INSTALLDIR . '/scripts/commandline.inc';
+require_once INSTALLDIR . '/lib/daemon.php';
+require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
+
+/**
+ * Fetcher for statuses from Twitter
+ *
+ * Fetches statuses from Twitter and inserts them as notices in local
+ * system.
+ *
+ * @category Twitter
+ * @package Laconica
+ * @author Zach Copley <zach@controlyourself.ca>
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+// NOTE: an Avatar path MUST be set in config.php for this
+// script to work: e.g.: $config['avatar']['path'] = '/laconica/avatar';
+
+class TwitterStatusFetcher extends ParallelizingDaemon
+{
+ /**
+ * Constructor
+ *
+ * @param string $id the name/id of this daemon
+ * @param int $interval sleep this long before doing everything again
+ * @param int $max_children maximum number of child processes at a time
+ * @param boolean $debug debug output flag
+ *
+ * @return void
+ *
+ **/
+ function __construct($id = null, $interval = 60,
+ $max_children = 2, $debug = null)
+ {
+ parent::__construct($id, $interval, $max_children, $debug);
+ }
+
+ /**
+ * Name of this daemon
+ *
+ * @return string Name of the daemon.
+ */
+
+ function name()
+ {
+ return ('twitterstatusfetcher.'.$this->_id);
+ }
+
+ /**
+ * Find all the Twitter foreign links for users who have requested
+ * importing of their friends' timelines
+ *
+ * @return array flinks an array of Foreign_link objects
+ */
+
+ function getObjects()
+ {
+ global $_DB_DATAOBJECT;
+
+ $flink = new Foreign_link();
+ $conn = &$flink->getDatabaseConnection();
+
+ $flink->service = TWITTER_SERVICE;
+ $flink->orderBy('last_noticesync');
+ $flink->find();
+
+ $flinks = array();
+
+ while ($flink->fetch()) {
+
+ if (($flink->noticesync & FOREIGN_NOTICE_RECV) ==
+ FOREIGN_NOTICE_RECV) {
+ $flinks[] = clone($flink);
+ }
+ }
+
+ $flink->free();
+ unset($flink);
+
+ $conn->disconnect();
+ unset($_DB_DATAOBJECT['CONNECTIONS']);
+
+ return $flinks;
+ }
+
+ function childTask($flink) {
+
+ // Each child ps needs its own DB connection
+
+ // Note: DataObject::getDatabaseConnection() creates
+ // a new connection if there isn't one already
+
+ $conn = &$flink->getDatabaseConnection();
+
+ $this->getTimeline($flink);
+
+ $flink->last_friendsync = common_sql_now();
+ $flink->update();
+
+ $conn->disconnect();
+
+ // XXX: Couldn't find a less brutal way to blow
+ // away a cached connection
+
+ global $_DB_DATAOBJECT;
+ unset($_DB_DATAOBJECT['CONNECTIONS']);
+ }
+
+ function getTimeline($flink)
+ {
+ if (empty($flink)) {
+ common_log(LOG_WARNING, $this->name() .
+ " - Can't retrieve Foreign_link for foreign ID $fid");
+ return;
+ }
+
+ common_debug($this->name() . ' - Trying to get timeline for Twitter user ' .
+ $flink->foreign_id);
+
+ // XXX: Biggest remaining issue - How do we know at which status
+ // to start importing? How many statuses? Right now I'm going
+ // with the default last 20.
+
+ $token = TwitterOAuthClient::unpackToken($flink->credentials);
+
+ $client = new TwitterOAuthClient($token->key, $token->secret);
+
+ $timeline = null;
+
+ try {
+ $timeline = $client->statusesFriendsTimeline();
+ } catch (OAuthClientCurlException $e) {
+ common_log(LOG_WARNING, $this->name() .
+ ' - OAuth client unable to get friends timeline for user ' .
+ $flink->user_id . ' - code: ' .
+ $e->getCode() . 'msg: ' . $e->getMessage());
+ }
+
+ if (empty($timeline)) {
+ common_log(LOG_WARNING, $this->name() . " - Empty timeline.");
+ return;
+ }
+
+ // Reverse to preserve order
+
+ foreach (array_reverse($timeline) as $status) {
+
+ // Hacktastic: filter out stuff coming from this Laconica
+
+ $source = mb_strtolower(common_config('integration', 'source'));
+
+ if (preg_match("/$source/", mb_strtolower($status->source))) {
+ common_debug($this->name() . ' - Skipping import of status ' .
+ $status->id . ' with source ' . $source);
+ continue;
+ }
+
+ $this->saveStatus($status, $flink);
+ }
+
+ // Okay, record the time we synced with Twitter for posterity
+
+ $flink->last_noticesync = common_sql_now();
+ $flink->update();
+ }
+
+ function saveStatus($status, $flink)
+ {
+ $id = $this->ensureProfile($status->user);
+
+ $profile = Profile::staticGet($id);
+
+ if (empty($profile)) {
+ common_log(LOG_ERR, $this->name() .
+ ' - Problem saving notice. No associated Profile.');
+ return null;
+ }
+
+ // XXX: change of screen name?
+
+ $uri = 'http://twitter.com/' . $status->user->screen_name .
+ '/status/' . $status->id;
+
+ $notice = Notice::staticGet('uri', $uri);
+
+ // check to see if we've already imported the status
+
+ if (empty($notice)) {
+
+ $notice = new Notice();
+
+ $notice->profile_id = $id;
+ $notice->uri = $uri;
+ $notice->created = strftime('%Y-%m-%d %H:%M:%S',
+ strtotime($status->created_at));
+ $notice->content = common_shorten_links($status->text); // XXX
+ $notice->rendered = common_render_content($notice->content, $notice);
+ $notice->source = 'twitter';
+ $notice->reply_to = null; // XXX: lookup reply
+ $notice->is_local = Notice::GATEWAY;
+
+ if (Event::handle('StartNoticeSave', array(&$notice))) {
+ $id = $notice->insert();
+ Event::handle('EndNoticeSave', array($notice));
+ }
+ }
+
+ if (!Notice_inbox::pkeyGet(array('notice_id' => $notice->id,
+ 'user_id' => $flink->user_id))) {
+ // Add to inbox
+ $inbox = new Notice_inbox();
+
+ $inbox->user_id = $flink->user_id;
+ $inbox->notice_id = $notice->id;
+ $inbox->created = $notice->created;
+ $inbox->source = NOTICE_INBOX_SOURCE_GATEWAY; // From a private source
+
+ $inbox->insert();
+ }
+ }
+
+ function ensureProfile($user)
+ {
+ // check to see if there's already a profile for this user
+
+ $profileurl = 'http://twitter.com/' . $user->screen_name;
+ $profile = Profile::staticGet('profileurl', $profileurl);
+
+ if (!empty($profile)) {
+ common_debug($this->name() .
+ " - Profile for $profile->nickname found.");
+
+ // Check to see if the user's Avatar has changed
+
+ $this->checkAvatar($user, $profile);
+ return $profile->id;
+
+ } else {
+ common_debug($this->name() . ' - Adding profile and remote profile ' .
+ "for Twitter user: $profileurl.");
+
+ $profile = new Profile();
+ $profile->query("BEGIN");
+
+ $profile->nickname = $user->screen_name;
+ $profile->fullname = $user->name;
+ $profile->homepage = $user->url;
+ $profile->bio = $user->description;
+ $profile->location = $user->location;
+ $profile->profileurl = $profileurl;
+ $profile->created = common_sql_now();
+
+ $id = $profile->insert();
+
+ if (empty($id)) {
+ common_log_db_error($profile, 'INSERT', __FILE__);
+ $profile->query("ROLLBACK");
+ return false;
+ }
+
+ // check for remote profile
+
+ $remote_pro = Remote_profile::staticGet('uri', $profileurl);
+
+ if (empty($remote_pro)) {
+
+ $remote_pro = new Remote_profile();
+
+ $remote_pro->id = $id;
+ $remote_pro->uri = $profileurl;
+ $remote_pro->created = common_sql_now();
+
+ $rid = $remote_pro->insert();
+
+ if (empty($rid)) {
+ common_log_db_error($profile, 'INSERT', __FILE__);
+ $profile->query("ROLLBACK");
+ return false;
+ }
+ }
+
+ $profile->query("COMMIT");
+
+ $this->saveAvatars($user, $id);
+
+ return $id;
+ }
+ }
+
+ function checkAvatar($twitter_user, $profile)
+ {
+ global $config;
+
+ $path_parts = pathinfo($twitter_user->profile_image_url);
+
+ $newname = 'Twitter_' . $twitter_user->id . '_' .
+ $path_parts['basename'];
+
+ $oldname = $profile->getAvatar(48)->filename;
+
+ if ($newname != $oldname) {
+ common_debug($this->name() . ' - Avatar for Twitter user ' .
+ "$profile->nickname has changed.");
+ common_debug($this->name() . " - old: $oldname new: $newname");
+
+ $this->updateAvatars($twitter_user, $profile);
+ }
+
+ if ($this->missingAvatarFile($profile)) {
+ common_debug($this->name() . ' - Twitter user ' .
+ $profile->nickname .
+ ' is missing one or more local avatars.');
+ common_debug($this->name() ." - old: $oldname new: $newname");
+
+ $this->updateAvatars($twitter_user, $profile);
+ }
+
+ }
+
+ function updateAvatars($twitter_user, $profile) {
+
+ global $config;
+
+ $path_parts = pathinfo($twitter_user->profile_image_url);
+
+ $img_root = substr($path_parts['basename'], 0, -11);
+ $ext = $path_parts['extension'];
+ $mediatype = $this->getMediatype($ext);
+
+ foreach (array('mini', 'normal', 'bigger') as $size) {
+ $url = $path_parts['dirname'] . '/' .
+ $img_root . '_' . $size . ".$ext";
+ $filename = 'Twitter_' . $twitter_user->id . '_' .
+ $img_root . "_$size.$ext";
+
+ $this->updateAvatar($profile->id, $size, $mediatype, $filename);
+ $this->fetchAvatar($url, $filename);
+ }
+ }
+
+ function missingAvatarFile($profile) {
+
+ foreach (array(24, 48, 73) as $size) {
+
+ $filename = $profile->getAvatar($size)->filename;
+ $avatarpath = Avatar::path($filename);
+
+ if (file_exists($avatarpath) == FALSE) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ function getMediatype($ext)
+ {
+ $mediatype = null;
+
+ switch (strtolower($ext)) {
+ case 'jpg':
+ $mediatype = 'image/jpg';
+ break;
+ case 'gif':
+ $mediatype = 'image/gif';
+ break;
+ default:
+ $mediatype = 'image/png';
+ }
+
+ return $mediatype;
+ }
+
+ function saveAvatars($user, $id)
+ {
+ global $config;
+
+ $path_parts = pathinfo($user->profile_image_url);
+ $ext = $path_parts['extension'];
+ $end = strlen('_normal' . $ext);
+ $img_root = substr($path_parts['basename'], 0, -($end+1));
+ $mediatype = $this->getMediatype($ext);
+
+ foreach (array('mini', 'normal', 'bigger') as $size) {
+ $url = $path_parts['dirname'] . '/' .
+ $img_root . '_' . $size . ".$ext";
+ $filename = 'Twitter_' . $user->id . '_' .
+ $img_root . "_$size.$ext";
+
+ if ($this->fetchAvatar($url, $filename)) {
+ $this->newAvatar($id, $size, $mediatype, $filename);
+ } else {
+ common_log(LOG_WARNING, $this->id() .
+ " - Problem fetching Avatar: $url");
+ }
+ }
+ }
+
+ function updateAvatar($profile_id, $size, $mediatype, $filename) {
+
+ common_debug($this->name() . " - Updating avatar: $size");
+
+ $profile = Profile::staticGet($profile_id);
+
+ if (empty($profile)) {
+ common_debug($this->name() . " - Couldn't get profile: $profile_id!");
+ return;
+ }
+
+ $sizes = array('mini' => 24, 'normal' => 48, 'bigger' => 73);
+ $avatar = $profile->getAvatar($sizes[$size]);
+
+ // Delete the avatar, if present
+
+ if ($avatar) {
+ $avatar->delete();
+ }
+
+ $this->newAvatar($profile->id, $size, $mediatype, $filename);
+ }
+
+ function newAvatar($profile_id, $size, $mediatype, $filename)
+ {
+ global $config;
+
+ $avatar = new Avatar();
+ $avatar->profile_id = $profile_id;
+
+ switch($size) {
+ case 'mini':
+ $avatar->width = 24;
+ $avatar->height = 24;
+ break;
+ case 'normal':
+ $avatar->width = 48;
+ $avatar->height = 48;
+ break;
+ default:
+
+ // Note: Twitter's big avatars are a different size than
+ // Laconica's (Laconica's = 96)
+
+ $avatar->width = 73;
+ $avatar->height = 73;
+ }
+
+ $avatar->original = 0; // we don't have the original
+ $avatar->mediatype = $mediatype;
+ $avatar->filename = $filename;
+ $avatar->url = Avatar::url($filename);
+
+ common_debug($this->name() . " - New filename: $avatar->url");
+
+ $avatar->created = common_sql_now();
+
+ $id = $avatar->insert();
+
+ if (empty($id)) {
+ common_log_db_error($avatar, 'INSERT', __FILE__);
+ return null;
+ }
+
+ common_debug($this->name() .
+ " - Saved new $size avatar for $profile_id.");
+
+ return $id;
+ }
+
+ function fetchAvatar($url, $filename)
+ {
+ $avatar_dir = INSTALLDIR . '/avatar/';
+
+ $avatarfile = $avatar_dir . $filename;
+
+ $out = fopen($avatarfile, 'wb');
+ if (!$out) {
+ common_log(LOG_WARNING, $this->name() .
+ " - Couldn't open file $filename");
+ return false;
+ }
+
+ common_debug($this->name() . " - Fetching Twitter avatar: $url");
+
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, $url);
+ curl_setopt($ch, CURLOPT_FILE, $out);
+ curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
+ curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0);
+ $result = curl_exec($ch);
+ curl_close($ch);
+
+ fclose($out);
+
+ return $result;
+ }
+}
+
+$id = null;
+$debug = null;
+
+if (have_option('i')) {
+ $id = get_option_value('i');
+} else if (have_option('--id')) {
+ $id = get_option_value('--id');
+} else if (count($args) > 0) {
+ $id = $args[0];
+} else {
+ $id = null;
+}
+
+if (have_option('d') || have_option('debug')) {
+ $debug = true;
+}
+
+$fetcher = new TwitterStatusFetcher($id, 60, 2, $debug);
+$fetcher->runOnce();
+
diff --git a/plugins/TwitterBridge/twitter.php b/plugins/TwitterBridge/twitter.php
new file mode 100644
index 000000000..280cdb0a3
--- /dev/null
+++ b/plugins/TwitterBridge/twitter.php
@@ -0,0 +1,258 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, Control Yourself, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+define('TWITTER_SERVICE', 1); // Twitter is foreign_service ID 1
+
+function update_twitter_user($twitter_id, $screen_name)
+{
+ $uri = 'http://twitter.com/' . $screen_name;
+ $fuser = new Foreign_user();
+
+ $fuser->query('BEGIN');
+
+ // Dropping down to SQL because regular DB_DataObject udpate stuff doesn't seem
+ // to work so good with tables that have multiple column primary keys
+
+ // Any time we update the uri for a forein user we have to make sure there
+ // are no dupe entries first -- unique constraint on the uri column
+
+ $qry = 'UPDATE foreign_user set uri = \'\' WHERE uri = ';
+ $qry .= '\'' . $uri . '\'' . ' AND service = ' . TWITTER_SERVICE;
+
+ $fuser->query($qry);
+
+ // Update the user
+
+ $qry = 'UPDATE foreign_user SET nickname = ';
+ $qry .= '\'' . $screen_name . '\'' . ', uri = \'' . $uri . '\' ';
+ $qry .= 'WHERE id = ' . $twitter_id . ' AND service = ' . TWITTER_SERVICE;
+
+ $fuser->query('COMMIT');
+
+ $fuser->free();
+ unset($fuser);
+
+ return true;
+}
+
+function add_twitter_user($twitter_id, $screen_name)
+{
+
+ $new_uri = 'http://twitter.com/' . $screen_name;
+
+ // Clear out any bad old foreign_users with the new user's legit URL
+ // This can happen when users move around or fakester accounts get
+ // repoed, and things like that.
+
+ $luser = new Foreign_user();
+ $luser->uri = $new_uri;
+ $luser->service = TWITTER_SERVICE;
+ $result = $luser->delete();
+
+ if (empty($result)) {
+ common_log(LOG_WARNING,
+ "Twitter bridge - removed invalid Twitter user squatting on uri: $new_uri");
+ }
+
+ $luser->free();
+ unset($luser);
+
+ // Otherwise, create a new Twitter user
+
+ $fuser = new Foreign_user();
+
+ $fuser->nickname = $screen_name;
+ $fuser->uri = 'http://twitter.com/' . $screen_name;
+ $fuser->id = $twitter_id;
+ $fuser->service = TWITTER_SERVICE;
+ $fuser->created = common_sql_now();
+ $result = $fuser->insert();
+
+ if (empty($result)) {
+ common_log(LOG_WARNING,
+ "Twitter bridge - failed to add new Twitter user: $twitter_id - $screen_name.");
+ common_log_db_error($fuser, 'INSERT', __FILE__);
+ } else {
+ common_debug("Twitter bridge - Added new Twitter user: $screen_name ($twitter_id).");
+ }
+
+ return $result;
+}
+
+// Creates or Updates a Twitter user
+function save_twitter_user($twitter_id, $screen_name)
+{
+
+ // Check to see whether the Twitter user is already in the system,
+ // and update its screen name and uri if so.
+
+ $fuser = Foreign_user::getForeignUser($twitter_id, TWITTER_SERVICE);
+
+ if (!empty($fuser)) {
+
+ $result = true;
+
+ // Only update if Twitter screen name has changed
+
+ if ($fuser->nickname != $screen_name) {
+ $result = update_twitter_user($twitter_id, $screen_name);
+
+ common_debug('Twitter bridge - Updated nickname (and URI) for Twitter user ' .
+ "$fuser->id to $screen_name, was $fuser->nickname");
+ }
+
+ return $result;
+
+ } else {
+ return add_twitter_user($twitter_id, $screen_name);
+ }
+
+ $fuser->free();
+ unset($fuser);
+
+ return true;
+}
+
+function is_twitter_bound($notice, $flink) {
+
+ // Check to see if notice should go to Twitter
+ if (!empty($flink) && ($flink->noticesync & FOREIGN_NOTICE_SEND)) {
+
+ // If it's not a Twitter-style reply, or if the user WANTS to send replies.
+ if (!preg_match('/^@[a-zA-Z0-9_]{1,15}\b/u', $notice->content) ||
+ ($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+function broadcast_twitter($notice)
+{
+ $flink = Foreign_link::getByUserID($notice->profile_id,
+ TWITTER_SERVICE);
+
+ if (is_twitter_bound($notice, $flink)) {
+
+ $user = $flink->getUser();
+
+ // XXX: Hack to get around PHP cURL's use of @ being a a meta character
+ $statustxt = preg_replace('/^@/', ' @', $notice->content);
+
+ $token = TwitterOAuthClient::unpackToken($flink->credentials);
+
+ $client = new TwitterOAuthClient($token->key, $token->secret);
+
+ $status = null;
+
+ try {
+ $status = $client->statusesUpdate($statustxt);
+ } catch (OAuthClientCurlException $e) {
+
+ if ($e->getMessage() == 'The requested URL returned error: 401') {
+
+ $errmsg = sprintf('User %1$s (user id: %2$s) has an invalid ' .
+ 'Twitter OAuth access token.',
+ $user->nickname, $user->id);
+ common_log(LOG_WARNING, $errmsg);
+
+ // Bad auth token! We need to delete the foreign_link
+ // to Twitter and inform the user.
+
+ remove_twitter_link($flink);
+ return true;
+
+ } else {
+
+ // Some other error happened, so we should probably
+ // try to send again later.
+
+ $errmsg = sprintf('cURL error trying to send notice to Twitter ' .
+ 'for user %1$s (user id: %2$s) - ' .
+ 'code: %3$s message: $4$s.',
+ $user->nickname, $user->id,
+ $e->getCode(), $e->getMessage());
+ common_log(LOG_WARNING, $errmsg);
+
+ return false;
+ }
+ }
+
+ if (empty($status)) {
+
+ // This could represent a failure posting,
+ // or the Twitter API might just be behaving flakey.
+
+ $errmsg = sprint('No data returned by Twitter API when ' .
+ 'trying to send update for %1$s (user id %2$s).',
+ $user->nickname, $user->id);
+ common_log(LOG_WARNING, $errmsg);
+
+ return false;
+ }
+
+ // Notice crossed the great divide
+
+ $msg = sprintf('Twitter bridge posted notice %s to Twitter.',
+ $notice->id);
+ common_log(LOG_INFO, $msg);
+ }
+
+ return true;
+}
+
+function remove_twitter_link($flink)
+{
+ $user = $flink->getUser();
+
+ common_log(LOG_INFO, 'Removing Twitter bridge Foreign link for ' .
+ "user $user->nickname (user id: $user->id).");
+
+ $result = $flink->delete();
+
+ if (empty($result)) {
+ common_log(LOG_ERR, 'Could not remove Twitter bridge ' .
+ "Foreign_link for $user->nickname (user id: $user->id)!");
+ common_log_db_error($flink, 'DELETE', __FILE__);
+ }
+
+ // Notify the user that her Twitter bridge is down
+
+ if (isset($user->email)) {
+
+ $result = mail_twitter_bridge_removed($user);
+
+ if (!$result) {
+
+ $msg = 'Unable to send email to notify ' .
+ "$user->nickname (user id: $user->id) " .
+ 'that their Twitter bridge link was ' .
+ 'removed!';
+
+ common_log(LOG_WARNING, $msg);
+ }
+ }
+
+}
+
diff --git a/plugins/TwitterBridge/twitterauthorization.php b/plugins/TwitterBridge/twitterauthorization.php
index b04f35327..a54528434 100644
--- a/plugins/TwitterBridge/twitterauthorization.php
+++ b/plugins/TwitterBridge/twitterauthorization.php
@@ -31,6 +31,8 @@ if (!defined('LACONICA')) {
exit(1);
}
+require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
+
/**
* Class for doing OAuth authentication against Twitter
*
diff --git a/plugins/TwitterBridge/twittersettings.php b/plugins/TwitterBridge/twittersettings.php
index a3e02e125..b3d4a971f 100644
--- a/plugins/TwitterBridge/twittersettings.php
+++ b/plugins/TwitterBridge/twittersettings.php
@@ -31,8 +31,8 @@ if (!defined('LACONICA')) {
exit(1);
}
-require_once INSTALLDIR.'/lib/connectsettingsaction.php';
-require_once INSTALLDIR.'/lib/twitter.php';
+require_once INSTALLDIR . '/lib/connectsettingsaction.php';
+require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
/**
* Settings for Twitter integration