? .DS_Store
? twitter_views_and_cron.patch
Index: twitter.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/twitter/twitter.inc,v
retrieving revision 1.2
diff -u -p -r1.2 twitter.inc
--- twitter.inc	8 Jun 2008 04:56:45 -0000	1.2
+++ twitter.inc	8 Jun 2008 22:35:07 -0000
@@ -2,18 +2,30 @@
 // $Id: twitter.inc,v 1.2 2008/06/08 04:56:45 walkah Exp $
 
 /**
+ * @file
+ * A wrapper API for the Twitter microblogging service.
+ *
+ * Provides functions for retrieving public and authenticated messages from
+ * Twitter, helper code for locally caching Twitter data and account
+ * information, and utility functions for associating Drupal site users with
+ * their Twitter accounts.
+ */
+
+
+/**
  * Generate a twitter posting form for the given user.
  *
- * @param object $account user account object.
+ * @param $account
+ *   A Drupal user object.
  */
 function twitter_form($account = NULL) {
   drupal_add_js(drupal_get_path('module', 'twitter') .'/twitter.js', 'module');
-  
+
   if (empty($account)) {
     global $user;
     $account = $user;
   }
-  
+
   $twitter_accounts = drupal_map_assoc(array_keys(twitter_get_user_accounts($account->uid)));
   if (count($twitter_accounts)) {
     $form = array();
@@ -22,7 +34,7 @@ function twitter_form($account = NULL) {
       '#rows' => 1,
       '#id' => 'twitter-textarea',
     );
-    
+
     if (count($twitter_accounts) > 1) {
       $form['account'] = array(
         '#type' => 'select',
@@ -42,23 +54,35 @@ function twitter_form($account = NULL) {
   }
 }
 
+
+
 /**
  * Twitter API functions
  */
-function twitter_set_status($screen_name, $password, $text = '', $source = NULL) {
-  $url = "http://twitter.com/statuses/update.xml";
-
-  $headers = array('Authorization' => 'Basic '. base64_encode($screen_name .':'. $password),
-                   'Content-type' => 'application/x-www-form-urlencoded');
-  $data = 'status='. urlencode($text);
-  if (!empty($source)) {
-    $data .= 'source='. urlencode($source);
-  }
 
-  return drupal_http_request($url, $headers, 'POST', $data);
-}
-
-function twitter_fetch_user_timeline($screen_name, $filter_since = TRUE, $cache = TRUE) {
+/**
+ * Fetch the public timeline for a Twitter.com account.
+ *
+ * Note that this function only requires a screen name, and not a password. As
+ * such, it can only retrieve statuses for publically visible Twitter accounts.
+ * Because it doesn't require authentication, it is also easier on the Twitter
+ * servers. Be kind, use this version whenever you can.
+ *
+ * @param $screen_name
+ *   The screen name of a Twitter.com user.
+ * @param $filter_since
+ *   A boolean indicating that Twitter should only return statuses that have not
+ *   been locally cached. This incurs an extra database hit, to retrieve the date
+ *   of the most recent locally cached twitter message for the screen name.
+ * @param $cache
+ *   A boolean indicating whether the statuses should be cached in the local
+ *   site's database after they're retrieved.
+ * @return
+ *   An array of Twitter statuses.
+ *
+ * @see twitter_fetch_statuses()
+ */
+function twitter_fetch_timeline($screen_name, $filter_since = TRUE, $cache = TRUE) {
   if ($filter_since) {
     $sql  = "SELECT t.created_at FROM {twitter} t WHERE t.screen_name = '%s' ORDER BY t.created_at DESC";
     $since = db_result(db_query($sql, $screen_name));
@@ -71,7 +95,7 @@ function twitter_fetch_user_timeline($sc
   }
 
   $results = drupal_http_request($url, array(), 'GET');
-  if ($results->code == 304) {
+  if (_twitter_request_failure($results)) {
     return array();
   }
   else {
@@ -80,45 +104,177 @@ function twitter_fetch_user_timeline($sc
       foreach($results as $status) {
         twitter_cache_status($status);
       }
+      twitter_touch_account($screen_name);
     }
     return $results;
   }
 }
 
-function twitter_fetch_user_friends($screen_name, $cache = TRUE) {
-  $url = "http://twitter.com/statuses/friends/$screen_name.xml";
-  $results = drupal_http_request($url, array(), 'GET');
-  return _twitter_convert_xml_to_array($results->data);
+
+/**
+ * Post a message to a Twitter.com account.
+ *
+ * @param $screen_name
+ *   The screen name of a Twitter.com user.
+ * @param $password
+ *   The password of a Twitter.com user.
+ * @param $text
+ *   The text to post. Strings longer than 140 characters will be truncated by
+ *   Twitter.
+ * @param $source
+ *   A string indicating the program or site used to post the message. This is
+ *   not cleanly supported yet by Twitter, and should probably be ignored.
+ * @return
+ *   The full results of the Drupal HTTP request, including the HTTP response
+ *   code returned by Twitter.com.
+ */
+function twitter_set_status($screen_name, $password, $text, $source = NULL) {
+  $url = "http://twitter.com/statuses/update.xml";
+
+  $headers = array('Authorization' => 'Basic '. base64_encode($screen_name .':'. $password),
+                   'Content-type' => 'application/x-www-form-urlencoded');
+  $data = 'status='. urlencode($text);
+  if (!empty($source)) {
+    $data .= 'source='. urlencode($source);
+  }
+
+  return drupal_http_request($url, $headers, 'POST', $data);
 }
 
-function twitter_fetch_user_followers($screen_name, $password, $cache = TRUE) {
-  $url = "http://twitter.com/statuses/followers/$screen_name.xml";
+/**
+ * Fetch the full information for a Twitter.com account.
+ *
+ * This function requires an authenticated connection for the account in
+ * question.
+ *
+ * @param $screen_name
+ *   The screen name of a Twitter.com user.
+ * @param $password
+ *   The password of a Twitter.com user.
+ * @param $cache
+ *   A boolean indicating whether the account info should be cached in the local
+ *   site's database after it's retrieved.
+ * @return
+ *   An single Twitter account.
+ */
+function twitter_fetch_account_info($screen_name, $password, $cache = TRUE) {
+  $url = "http://twitter.com/users/show/$screen_name.xml";
   $headers = array('Authorization' => 'Basic '. base64_encode($screen_name .':'. $password),
                    'Content-type' => 'application/x-www-form-urlencoded');
   $results = drupal_http_request($url, $headers, 'GET');
+
+  if (_twitter_request_failure($results)) {
+    return array();
+  }
+
   $results = _twitter_convert_xml_to_array($results->data);
 
   if ($cache) {
-    foreach($results as $status) {
-      twitter_cache_status($status);
+    foreach($results as $user) {
+      twitter_cache_account($user);
     }
   }
   return $results;
 }
 
-function twitter_fetch_status($screen_name, $cache = TRUE) {
+/**
+ * Fetch the latest statuses for a Twitter.com account, regardless of privacy.
+ *
+ * This function is the authenticated version of twitter_fetch_timeline(), and
+ * is the only way to retrieve statuses for a 'private' account.
+ *
+ * @param $screen_name
+ *   The screen name of a Twitter.com user.
+ * @param $password
+ *   The password of a Twitter.com user.
+ * @param $cache
+ *   A boolean indicating whether the statuses should be cached in the local
+ *   site's database after they're retrieved.
+ * @return
+ *   An array of Twitter statuses.
+ *
+ * @see twitter_fetch_timeline()
+ */
+function twitter_fetch_statuses($screen_name, $password, $cache = TRUE) {
   $url = "http://twitter.com/statuses/$screen_name.xml";
-  $results = drupal_http_request($url, array(), 'GET');
+  $headers = array('Authorization' => 'Basic '. base64_encode($screen_name .':'. $password),
+                   'Content-type' => 'application/x-www-form-urlencoded');
+
+  $results = drupal_http_request($url, $headers, 'GET');
+  if (_twitter_request_failure($results)) {
+    return array();
+  }
   $results = _twitter_convert_xml_to_array($results->data);
 
   if ($cache && !empty($results)) {
     foreach($results as $status) {
       twitter_cache_status($status);
-      return $status;
     }
+    twitter_touch_account($screen_name);
   }
+  return $results;
 }
 
+/**
+ * Fetch information about Twitter.com accounts followed by a given user.
+ *
+ * This function does not require authentication. It is mostly useful for mining
+ * information about connections, and locating existing Twitter friends who have
+ * signed up for the same Drupal site.
+ *
+ * @param $screen_name
+ *   The screen name of a Twitter.com user.
+ * @return
+ *   An array of Twitter accounts.
+ *
+ * @see twitter_fetch_followers()
+ */
+function twitter_fetch_friends($screen_name) {
+  $url = "http://twitter.com/statuses/friends/$screen_name.xml";
+  $results = drupal_http_request($url, array(), 'GET');
+  if (_twitter_request_failure($results)) {
+    return array();
+  }
+  return _twitter_convert_xml_to_array($results->data);
+}
+
+/**
+ * Fetch information about users following a given Twitter.com account.
+ *
+ * This function is mostly useful for mining information about connections, and
+ * locating existing Twitter friends who have also signed up for the same Drupal
+ * site.
+ *
+ * @param $screen_name
+ *   The screen name of a Twitter.com user.
+ * @param $password
+ *   The password of a Twitter.com user.
+ * @return
+ *   An array of Twitter accounts.
+ *
+ * @see twitter_fetch_friends()
+ */
+function twitter_fetch_followers($screen_name, $password) {
+  $url = "http://twitter.com/statuses/followers/$screen_name.xml";
+  $headers = array('Authorization' => 'Basic '. base64_encode($screen_name .':'. $password),
+                   'Content-type' => 'application/x-www-form-urlencoded');
+  $results = drupal_http_request($url, $headers, 'GET');
+  if (_twitter_request_failure($results)) {
+    return array();
+  }
+  return _twitter_convert_xml_to_array($results->data);
+}
+
+/**
+ * Attempts to authenticate a username/password on Twitter.com.
+ *
+ * @param $screen_name
+ *   The screen name of a Twitter.com user.
+ * @param $password
+ *   The password of a Twitter.com user.
+ * @return
+ *   A boolean indicating success or failure.
+ */
 function twitter_authenticate($screen_name, $password) {
   $url = "http://twitter.com/account/verify_credentials.xml";
   $headers = array('Authorization' => 'Basic '. base64_encode($screen_name .':'. $password),
@@ -128,33 +284,82 @@ function twitter_authenticate($screen_na
   return ($results->code == '200');
 }
 
-function twitter_fetch_user_details($screen_name, $password, $cache = TRUE) {
-  $url = "http://twitter.com/users/show/$screen_name.xml";
-  $headers = array('Authorization' => 'Basic '. base64_encode($screen_name .':'. $password),
-                   'Content-type' => 'application/x-www-form-urlencoded');
-  $results = drupal_http_request($url, $headers, 'GET');
-  if ($results->code == 401) {
-    return array();
-  }
-  $results = _twitter_convert_xml_to_array($results->data);
+/**
+ * Internal helper function to deal cleanly with various HTTP response codes.
+ */
+function _twitter_request_failure($results) {
+  switch ($results->code) {
+    case '304':
+      // Not modified, nothing to do.
+      return TRUE;
+    case 401:
+    case 403:
+      // Twitter returns both of these for different classes of auth failure.
+      watchdog('twitter', 'Twitter account %screen_name could not be authenticated.', array('%screen_name' => $screen_name));
+      return TRUE;
+    case 404:
+      // Probably a bogus username.
+      watchdog('twitter', 'Twitter account %screen_name could not be retrieved.', array('%screen_name' => $screen_name));
+      return TRUE;
 
-  if ($cache) {
-    foreach($results as $user) {
-      twitter_cache_user($user);
-    }
   }
-  return $results;
+  return FALSE;
 }
 
 
+
+
+/**
+ * Caching functions
+ */
+
+
 /**
- * Helper functions
+ * Saves Twitter account information to the database.
+ *
+ * @param $twitter_account
+ *   A Twitter user account in array form.
+ *
+ * @see twitter_touch_account()
+ * @see twitter_cache_status()
  */
-function twitter_cache_user($twitter_user = array()) {
-  db_query("DELETE FROM {twitter_account} WHERE twitter_uid = %d", $twitter_user['twitter_uid']);
-  drupal_write_record('twitter_account', $twitter_user);
+function twitter_cache_account($twitter_account = array()) {
+  db_query("DELETE FROM {twitter_account} WHERE twitter_uid = %d", $twitter_account['twitter_uid']);
+  drupal_write_record('twitter_account', $twitter_account);
 }
 
+
+/**
+ * Updates the 'last refreshed on' timestamp of a given locally cached Twitter
+ * account.
+ *
+ * @param $screen_name
+ *   A Twitter screen name..
+ *
+ * @see twitter_cache_account()
+ * @see twitter_cache_status()
+ */
+function twitter_touch_account($screen_name = '') {
+  db_query("UPDATE {twitter_account} SET last_refresh = %d WHERE screen_name = '%s'", time(), $screen_name);
+}
+
+
+/**
+ * Saves Twitter status message to the database.
+ *
+ * If the $silent parameter is set to TRUE, this function will also notify other
+ * modules via hook_twitter_status_update() that a new stauts has been retrieved
+ * and saved. This is normally set to FALSE, but may be useful when integrating
+ * Twitter into complex workflows.
+ *
+ * @param $status
+ *   A Twitter status updated in array form.
+ * @param $silent
+ *   A boolean indicating whether hook_twitter_status_update should be fired.
+ *
+ * @see twitter_touch_account()
+ * @see twitter_cache_status()
+ */
 function twitter_cache_status($status = array(), $silent = FALSE) {
   db_query("DELETE FROM {twitter} WHERE twitter_id = %d", $status['twitter_id']);
   drupal_write_record('twitter', $status);
@@ -164,6 +369,12 @@ function twitter_cache_status($status = 
 }
 
 
+
+
+/**
+ * User/account relationship code
+ */
+
 function twitter_get_user_accounts($uid) {
   $sql = "SELECT *, tu.screen_name AS screen_name FROM {twitter_user} tu LEFT JOIN {twitter_account} ta ON (tu.screen_name = ta.screen_name) WHERE tu.uid = %d";
   $args = array($uid);
@@ -186,7 +397,7 @@ function twitter_user_save($user = array
     'announce_types'        => array(),
     'announce_message'      => '',
   );
-  
+
   $user['announce_types'] = serialize($user['announce_types']);
   if (db_result(db_query("SELECT 1 FROM {twitter_user} WHERE uid = %d AND screen_name = '%s'", $user['uid'], $user['screen_name']))) {
     drupal_write_record('twitter_user', $user, array('uid', 'screen_name'));
@@ -196,7 +407,7 @@ function twitter_user_save($user = array
   }
   $user['announce_types'] = unserialize($user['announce_types']);
 
-  twitter_fetch_user_details($user['screen_name'], $user['password']);
+  twitter_fetch_account_info($user['screen_name'], $user['password']);
 }
 
 function twitter_user_delete($uid, $screen_name = NULL) {
@@ -209,6 +420,7 @@ function twitter_user_delete($uid, $scre
   db_query($sql, $args);
 }
 
+
 /**
  * Internal XML munging code
  */
@@ -254,9 +466,6 @@ function _twitter_convert_user($user) {
   if (!empty($result['status']) && is_object($result['status'])) {
     $result['status'] = _twitter_convert_status($result['status']);
   }
-  else {
-    $result['twitter_uid'] = NULL;
-  }
   return $result;
 }
 
Index: twitter.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/twitter/twitter.install,v
retrieving revision 1.1
diff -u -p -r1.1 twitter.install
--- twitter.install	8 Jun 2008 04:48:33 -0000	1.1
+++ twitter.install	8 Jun 2008 22:35:07 -0000
@@ -55,23 +55,34 @@ function twitter_schema() {
         'type' => 'int',
         'not null' => TRUE
       ),
-      'name' => array(
+      'screen_name' => array(
         'description' => t("The unique login name of the {twitter_account} user."),
         'type' => 'varchar',
+        'length' => 255
+      ),
+      'name' => array(
+        'description' => t("The full name of the {twitter_account} user."),
+        'type' => 'varchar',
         'length' => 64,
         'not null' => TRUE,
         'default' => ''
       ),
-      'screen_name' => array(
-        'description' => t("The full name of the {twitter_account} user."),
+      'description' => array(
+        'description' => t("The description/biography associated with the {twitter_account}."),
         'type' => 'varchar',
         'length' => 255
       ),
-      'description' => array(
-        'description' => t("The description/biography associated with the {twitter_account}."),
+      'location' => array(
+        'description' => t("The location of the {twitter_account}'s owner."),
         'type' => 'varchar',
         'length' => 255
       ),
+      'followers_count' => array(
+        'description' => t("The number of users following this {twitter_account}."),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0
+      ),
       'profile_image_url' => array(
         'description' => t("The url of the {twitter_account}'s profile image."),
         'type' => 'varchar',
@@ -92,7 +103,7 @@ function twitter_schema() {
         'description' => t("A UNIX timestamp marking the date Twitter statuses were last fetched on."),
         'type' => 'int',
         'not null' => TRUE
-      ),      
+      ),
     ),
     'indexes' => array('screen_name' => array('screen_name')),
     'primary key' => array('twitter_uid'),
@@ -114,9 +125,16 @@ function twitter_schema() {
         'description' => t("The password for the Twitter account."),
         'type' => 'varchar',
         'length' => 64
-      ),            
+      ),
+      'import' => array(
+        'description' => t("Boolean flag indicating whether the {twitter_user}'s posts should be pulled in by the site."),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 1
+      ),
     ),
     'primary key' => array('uid', 'screen_name'),
+    'indexes' => array('screen_name' => array('screen_name'), 'uid' => array('uid'), 'update' => array('update')),
   );
 
   return $schema;
@@ -134,10 +152,34 @@ function twitter_install() {
  * Previous versions of the Twitter module had no database schema.
  * We're safe just running the basic install for update_1.
  */
-function twitter_update_1() {
+function twitter_update_6000() {
   twitter_install();
 }
 
+function twitter_update_6001() {
+  $ret = array();
+  $attributes = array(
+    'description' => t("Boolean flag indicating whether the {twitter_user}'s posts should be pulled in by the site."),
+    'not null' => TRUE,
+    'default' => 1,
+  );
+  db_add_column($ret, 'twitter_user', 'import', 'int', $attributes);
+
+  $attributes = array(
+    'description' => t("The location of the {twitter_account}'s owner."),
+    'length' => 255
+  );
+  db_add_column($ret, 'twitter_account', 'location', 'varchar(255)', $attributes);
+
+  $attributes = array(
+    'description' => t("The number of users following this {twitter_account}."),
+    'default' => 0
+  );
+  db_add_column($ret, 'twitter_account', 'followers_count', 'int', $attributes);
+
+  return $ret;
+}
+
 function twitter_uninstall() {
   // Remove tables.
   drupal_uninstall_schema('twitter');
Index: twitter.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/twitter/twitter.module,v
retrieving revision 1.2
diff -u -p -r1.2 twitter.module
--- twitter.module	8 Jun 2008 04:48:33 -0000	1.2
+++ twitter.module	8 Jun 2008 22:35:07 -0000
@@ -25,7 +25,7 @@ function twitter_menu() {
     'file' => 'twitter.pages.inc',
     'type' => MENU_LOCAL_TASK,
   );
-  
+
   return $items;
 }
 
@@ -65,12 +65,12 @@ function twitter_form_alter(&$form, $for
     $form['twitter'] += $twitter_form;
     $form['twitter']['status']['#default_value'] = variable_get('twitter_default_format', 'New post: !title (!url)');
     $form['twitter']['status']['#description'] = t('The given text will be posted to twitter.com. You can use !url, !title and !user as replacement text.');
-  } 
+  }
 }
 
 function twitter_nodeapi(&$node, $op, $a1) {
   module_load_include('inc', 'twitter');
-  
+
   switch ($op) {
     case 'insert':
     case 'update':
@@ -86,4 +86,50 @@ function twitter_nodeapi(&$node, $op, $a
       }
       break;
   }
-}
\ No newline at end of file
+}
+
+/**
+ * Implementation of hook_cron()
+ *
+ * Imports new Twitter statuses for site users, and deletes expired tweets.
+ */
+function twitter_cron() {
+  if (!variable_get('twitter_import', TRUE)) {
+    return;
+  }
+
+  module_load_include('inc', 'twitter');
+
+  // Pull up a list of Twitter accounts that are flagged for updating,
+  // sorted by how long it's been since we last updated them. This ensures
+  // that the most out-of-date accounts get updated first.
+
+  $sql  = "SELECT tu.screen_name, tu.password, ta.protected FROM {twitter_user} tu ";
+  $sql .= "LEFT JOIN {twitter_account} ta ON tu.screen_name = ta.screen_name ";
+  $sql .= "WHERE tu.import = 1 ORDER BY ta.last_refresh ASC";
+
+  $results = db_query_range($sql, 0, 20);
+  while ($account = db_fetch_array($results)) {
+    // Use the 'cheaper' unauthenticated version if the account isn't protected.
+    if (empty($account['protected'])) {
+      $statuses = twitter_fetch_timeline($account['screen_name']);
+    }
+    else {
+      $statuses = twitter_fetch_statuses($account['screen_name'], $account['password']);
+    }
+
+    // If we got results back, update the account information with the latest
+    // follower count, location, user picture, etc. Also, touch the account's
+    // last_refreshed timestamp so it won't get thrashed before other, staler
+    // accounts.
+    if (!empty($statuses)) {
+      twitter_cache_account($statuses[0]['account']);
+    }
+    twitter_touch_account($account['screen_name']);
+  }
+
+  // Nuke old statuses.
+  if ($age = variable_get('twitter_expire', 0)) {
+    db_query('DELETE {twitter} WHERE created_time < %d', time() - $age);
+  }
+}
Index: twitter.pages.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/twitter/twitter.pages.inc,v
retrieving revision 1.1
diff -u -p -r1.1 twitter.pages.inc
--- twitter.pages.inc	8 Jun 2008 04:48:33 -0000	1.1
+++ twitter.pages.inc	8 Jun 2008 22:35:07 -0000
@@ -6,34 +6,66 @@
  */
 function twitter_admin_form() {
   $form = array();
-  
-  $form['twitter_default_format'] = array(
+
+  $form['import'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Twitter import'),
+    '#description' => t('Import and display the Twitter statuses of site users who have entered their Twitter account information.'),
+  );
+
+  $form['import']['twitter_import'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Import Twitter statuses'),
+    '#default_value' => variable_get('twitter_import', TRUE),
+  );
+
+  $periods = array(0 => t('Never'));
+  $periods += drupal_map_assoc(array(604800, 2419200, 7257600, 31449600), 'format_interval');
+  $form['import']['twitter_expire'] = array(
+    '#type' => 'select',
+    '#title' => t('Delete old statuses'),
+    '#default_value' => variable_get('amazon_refresh_schedule', 0),
+    '#options' => $periods
+  );
+
+  $form['posting'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Twitter posting'),
+    '#description' => t('Users with proper permissions will be given the option to post announcements to their Twitter accounts when they create new content.'),
+  );
+
+  $form['posting']['twitter_types'] = array(
+    '#type' => 'checkboxes',
+    '#title' => t('Node types'),
+    '#options' => node_get_types('names'),
+    '#default_value' => variable_get('twitter_types', array('story' => 'story', 'blog' => 'blog')),
+  );
+
+  $form['posting']['twitter_default_format'] = array(
     '#type' => 'textfield',
     '#title' => t('Default format string'),
     '#maxlength' => 140,
-    '#description' => t('The given text will be posted to twitter.com. You can use !url, !title and !user as replacement text.')
+    '#description' => t('The given text will be posted to twitter.com. You can use !url, !title and !user as replacement text.'),
+    '#default_value' => variable_get('twitter_default_format', 'New post: !title (!url)'),
   );
-  
-  $form['twitter_types'] = array(
-    
-  );
- 
-  return system_settings_form($form); 
+
+  return system_settings_form($form);
 }
 
 function twitter_user_settings($account) {
   module_load_include('inc', 'twitter');
-  
+
   $twitter_accounts = twitter_get_user_accounts($account->uid);
-  
-  $header = array('', t('Name'), t('Description'));
+
+  $header = array('', t('Name'), t('Description'), t('Import'));
   $rows = array();
-  
+
   foreach ($twitter_accounts as $twitter_account) {
     $rows[] = array(
-      '<img src="'.$twitter_account['profile_image_url'].'" />',
+      theme('image', $twitter_account['profile_image_url'], '', '', array(), FALSE),
       $twitter_account['screen_name'],
       $twitter_account['description'],
+      empty($twitter_account['import']) ? t('No') : t('Yes'),
     );
   }
 
@@ -47,7 +79,7 @@ function twitter_add_account($form_state
     global $user;
     $account = $user;
   }
-  
+
   $form['uid'] = array(
     '#type' => 'value',
     '#value' => $account->uid,
@@ -63,17 +95,23 @@ function twitter_add_account($form_state
     '#title' => t('Password'),
   );
 
+  $form['import'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Import statuses from this account'),
+    '#default_value' => TRUE,
+  );
+
   $form['submit'] = array(
     '#type' => 'submit',
     '#value' => t('Add account'),
   );
 
-  return $form;  
+  return $form;
 }
 
 function twitter_add_account_validate($form, &$form_state) {
   module_load_include('inc', 'twitter');
-  
+
   $verify = FALSE;
 
   $pass = $form_state['values']['password'];
@@ -93,7 +131,7 @@ function twitter_add_account_validate($f
 
 function twitter_add_account_submit($form, &$form_state) {
   module_load_include('inc', 'twitter');
-   
+
   if (!empty($form_state['values']['screen_name'])) {
     twitter_user_save($form_state['values']);
   }
Index: twitter.views.inc
===================================================================
RCS file: twitter.views.inc
diff -N twitter.views.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ twitter.views.inc	8 Jun 2008 22:35:07 -0000
@@ -0,0 +1,340 @@
+<?php
+// $Id$
+/**
+ * @file
+ * Provide views data and handlers for twitter.module
+ */
+
+/**
+ * @defgroup views_twitter_module twitter.module handlers
+ *
+ * Includes the ability to create views of just the twitter table.
+ * @{
+ */
+
+/**
+ * Implementation of hook_views_data()
+ */
+function twitter_views_data() {
+  // Basic table information.
+
+  $data['twitter']['table']['group']  = t('Twitter');
+
+  // Advertise this table as a possible base table
+  $data['twitter']['table']['base'] = array(
+    'field' => 'twitter_id',
+    'title' => t('Twitter message'),
+    'help' => t('Stores Twitter status messages.'),
+    'weight' => 10,
+  );
+
+  // Twitter screen name
+  $data['twitter']['screen_name'] = array(
+    'title' => t('Login name'),
+    'help' => t('The login account of the Twitter user.'),
+    'field' => array(
+      'handler' => 'views_handler_field',
+      'click sortable' => TRUE,
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_string',
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+    ),
+    'argument' => array(
+      'handler' => 'views_handler_argument_string',
+    ),
+  );
+
+  // Twitter message timestamp
+  $data['twitter']['created_time'] = array(
+    'title' => t('Created time'),
+    'help' => t('The time the Twitter message was posted.'),
+    'field' => array(
+      'handler' => 'views_handler_field_date',
+      'click sortable' => TRUE,
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_date',
+    ),
+  );
+
+  // Twitter text
+  $data['twitter']['text'] = array(
+    'title' => t('Message text'),
+    'help' => t('The text of the Twitter message.'),
+    'field' => array(
+      'handler' => 'views_handler_field_xss',
+      'click sortable' => TRUE,
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_string',
+    ),
+  );
+
+  // Twitter source
+  $data['twitter']['source'] = array(
+    'title' => t('Source'),
+    'help' => t('The name of the application that posted the Twitter message.'),
+    'field' => array(
+      'handler' => 'views_handler_field_xss',
+      'click sortable' => TRUE,
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_string',
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+    ),
+  );
+
+
+
+
+  $data['twitter_account']['table']['group']  = t('Twitter');
+
+  $data['twitter_account']['table']['join'] = array(
+    'twitter' => array(
+      'left_field' => 'screen_name',
+      'field' => 'screen_name',
+    ),
+    'users' => array(
+      'field' => 'screen_name',
+      'left_table' => 'twitter_user',
+      'left_field' => 'screen_name',
+    ),
+  );
+
+  // Twitter screen name
+  $data['twitter_account']['screen_name'] = array(
+    'title' => t('Login name'),
+    'help' => t('The login account of the Twitter user.'),
+    'field' => array(
+      'handler' => 'views_handler_field',
+      'click sortable' => TRUE,
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_string',
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+    ),
+    'argument' => array(
+      'handler' => 'views_handler_argument_string',
+    ),
+  );
+
+
+  // Twitter account full name
+  $data['twitter_account']['name'] = array(
+    'title' => t('Full name'),
+    'help' => t('The full name Twitter account user.'),
+    'field' => array(
+      'handler' => 'views_handler_field',
+      'click sortable' => TRUE,
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_string',
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+    ),
+    'argument' => array(
+      'handler' => 'views_handler_argument_string',
+    ),
+  );
+
+  // Twitter account description
+  $data['twitter_account']['description'] = array(
+    'title' => t('Description'),
+    'help' => t('The description of the Twitter account.'),
+    'field' => array(
+      'handler' => 'views_handler_field',
+      'click sortable' => TRUE,
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_field_xss',
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+    ),
+  );
+
+  // Twitter account location
+  $data['twitter_account']['location'] = array(
+    'title' => t('Location'),
+    'help' => t('The location of the Twitter account.'),
+    'field' => array(
+      'handler' => 'views_handler_field',
+      'click sortable' => TRUE,
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_field_xss',
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+    ),
+  );
+
+  // Twitter account description
+  $data['twitter_account']['followers_count'] = array(
+    'title' => t('Location'),
+    'help' => t('The number of users following this Twitter account.'),
+    'field' => array(
+      'handler' => 'views_handler_field_numeric',
+      'click sortable' => TRUE,
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_numeric',
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+    ),
+  );
+
+  // Twitter account profile image
+  $data['twitter_account']['profile_image_url'] = array(
+    'title' => t('Profile image'),
+    'help' => t('The image used by the Twitter account.'),
+    'field' => array(
+      'handler' => 'twitter_views_handler_profile_image',
+      'click sortable' => TRUE,
+    ),
+  );
+
+  // Twitter account url
+  $data['twitter_account']['url'] = array(
+    'title' => t('URL'),
+    'help' => t('The URL given by the Twitter account user.'),
+    'field' => array(
+      'handler' => 'views_handler_field_url',
+      'click sortable' => TRUE,
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_string',
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+    ),
+  );
+
+  // Twitter account protected
+  $data['twitter_account']['protected'] = array(
+    'title' => t('Protected status'),
+    'help' => t('Whether posts from this Twitter account should be visible to the general public.'),
+    'field' => array(
+      'handler' => 'views_handler_field_boolean',
+      'click sortable' => TRUE,
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_boolean_operator',
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+    ),
+  );
+
+  // Twitter message timestamp
+  $data['twitter_account']['last_refresh'] = array(
+    'title' => t('Last refresh'),
+    'help' => t('The time the Twitter account statuses were retrieved.'),
+    'field' => array(
+      'handler' => 'views_handler_field_date',
+      'click sortable' => TRUE,
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_date',
+    ),
+  );
+
+
+  $data['twitter_user']['table']['group']  = t('Twitter');
+
+  $data['twitter_user']['table']['join'] = array(
+    'users' => array(
+      'left_field' => 'uid',
+      'field' => 'uid',
+    ),
+    'twitter_account' => array(
+      'left_field' => 'screen_name',
+      'field' => 'screen_name',
+    ),
+    'twitter' => array(
+      'left_table' => 'twitter_account',
+      'left_field' => 'screen_name',
+      'field' => 'screen_name',
+    ),
+  );
+
+  // Twitter account description
+  $data['twitter_user']['uid'] = array(
+    'title' => t('User ID'),
+    'help' => t('The UID of the Twitter account.'),
+  );
+
+  // Twitter account description
+  $data['twitter_user']['screen_name'] = array(
+    'title' => t('Screen name'),
+    'help' => t('The screen name of the Twitter account.'),
+  );
+
+  // Twitter account protected
+  $data['twitter_user']['import'] = array(
+    'title' => t('Import status'),
+    'help' => t('Whether posts from this Twitter account should be imported automatically.'),
+    'field' => array(
+      'handler' => 'views_handler_field_boolean',
+      'click sortable' => TRUE,
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_boolean_operator',
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+    ),
+  );
+
+
+  return $data;
+}
+
+
+function twitter_views_data_alter(&$data) {
+  $data['users']['table']['join']['twitter'] = array(
+    'left_table' => 'twitter_user',
+    'left_field' => 'uid',
+    'field' => 'uid',
+  );
+  $data['users']['table']['join']['twitter_account'] = array(
+    'left_table' => 'twitter_user',
+    'left_field' => 'uid',
+    'field' => 'uid',
+  );
+  $data['users']['table']['join']['twitter_user'] = array(
+    'left_field' => 'uid',
+    'field' => 'uid',
+  );
+}
+
+
+/**
+ * Field handler to provide simple renderer that turns a URL into a clickable link.
+ */
+class twitter_views_handler_profile_image extends views_handler_field {
+  function render($values) {
+    $value = $values->{$this->field_alias};
+    return theme('image', $value, '', '', array(), FALSE);
+  }
+}
+
+
+/**
+ * @}
+ */
Index: twitter.views_default.inc
===================================================================
RCS file: twitter.views_default.inc
diff -N twitter.views_default.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ twitter.views_default.inc	8 Jun 2008 22:35:07 -0000
@@ -0,0 +1,211 @@
+<?php
+// $Id$
+
+function twitter_views_default_views() {
+  $view = new view;
+  $view->name = 'tweets';
+  $view->description = 'Displays Twitter.com status messages for users who have associated Twitter accounts.';
+  $view->tag = '';
+  $view->view_php = '';
+  $view->base_table = 'twitter';
+  $view->is_cacheable = '0';
+  $view->api_version = 2;
+  $view->disabled = FALSE; // Edit this to true to make a default view disabled initially
+  $view->display = array();
+    $display = new views_display;
+    $display->id = 'default';
+    $display->display_title = 'Tweets';
+    $display->display_plugin = 'default';
+    $display->position = '1';
+    $display->display_options = array(
+    'sorts' => array(
+      'created_time' => array(
+        'order' => 'DESC',
+        'id' => 'created_time',
+        'table' => 'twitter',
+        'field' => 'created_time',
+        'relationship' => 'none',
+      ),
+    ),
+    'fields' => array(
+      'profile_image_url' => array(
+        'label' => '',
+        'exclude' => 0,
+        'id' => 'profile_image_url',
+        'table' => 'twitter_account',
+        'field' => 'profile_image_url',
+        'relationship' => 'none',
+      ),
+      'text' => array(
+        'label' => '',
+        'exclude' => 0,
+        'id' => 'text',
+        'table' => 'twitter',
+        'field' => 'text',
+        'relationship' => 'none',
+      ),
+      'created_time' => array(
+        'label' => '',
+        'date_format' => 'time ago',
+        'custom_date_format' => '',
+        'exclude' => 0,
+        'id' => 'created_time',
+        'table' => 'twitter',
+        'field' => 'created_time',
+        'relationship' => 'none',
+      ),
+    ),
+    'filters' => array(
+      'protected' => array(
+        'operator' => '=',
+        'value' => 0,
+        'group' => '0',
+        'exposed' => FALSE,
+        'expose' => array(
+          'operator' => FALSE,
+          'label' => '',
+        ),
+        'id' => 'protected',
+        'table' => 'twitter_account',
+        'field' => 'protected',
+        'relationship' => 'none',
+      ),
+    ),
+    'style_plugin' => 'table',
+    'style_options' => array(
+      'grouping' => '',
+      'override' => 1,
+      'sticky' => 0,
+      'order' => 'asc',
+      'columns' => array(
+        'profile_image_url' => 'profile_image_url',
+        'text' => 'text',
+        'created_time' => 'text',
+      ),
+      'info' => array(
+        'profile_image_url' => array(
+          'sortable' => 0,
+          'separator' => '',
+        ),
+        'text' => array(
+          'sortable' => 0,
+          'separator' => ' &#8212; ',
+        ),
+        'created_time' => array(
+          'sortable' => 0,
+          'separator' => '',
+        ),
+      ),
+      'default' => '-1',
+    ),
+    'use_pager' => 'mini',
+    'pager_element' => 0,
+    'arguments' => array(
+      'uid' => array(
+        'default_action' => 'default',
+        'style_plugin' => 'default_summary',
+        'style_options' => array(),
+        'wildcard' => 'all',
+        'wildcard_substitution' => 'All author',
+        'title' => '%1\'s tweets',
+        'default_argument_type' => 'user',
+        'default_argument' => '',
+        'validate_type' => 'none',
+        'validate_fail' => 'not found',
+        'break_phrase' => 0,
+        'not' => 0,
+        'id' => 'uid',
+        'table' => 'users',
+        'field' => 'uid',
+        'relationship' => 'none',
+        'default_argument_user' => 1,
+        'default_argument_fixed' => '',
+        'default_argument_php' => '',
+        'validate_argument_node_type' => array(
+          'poll' => 0,
+          'page' => 0,
+          'review' => 0,
+          'story' => 0,
+        ),
+        'validate_argument_node_access' => 0,
+        'validate_argument_nid_type' => 'nid',
+        'validate_argument_vocabulary' => array(),
+        'validate_argument_type' => 'tid',
+        'validate_argument_php' => '',
+      ),
+    ),
+  );
+  $view->display['default'] = $display;
+    $display = new views_display;
+    $display->id = 'page';
+    $display->display_title = 'Page';
+    $display->display_plugin = 'page';
+    $display->position = '2';
+    $display->display_options = array(
+    'menu' => array(
+      'type' => 'tab',
+      'title' => 'Twitter',
+      'weight' => '1',
+    ),
+    'path' => 'user/%/tweets',
+  );
+  $view->display['page'] = $display;
+    $display = new views_display;
+    $display->id = 'block';
+    $display->display_title = 'Block';
+    $display->display_plugin = 'block';
+    $display->position = '3';
+    $display->display_options = array(
+    'fields' => array(
+      'text' => array(
+        'label' => '',
+        'exclude' => 0,
+        'id' => 'text',
+        'table' => 'twitter',
+        'field' => 'text',
+        'relationship' => 'none',
+      ),
+      'created_time' => array(
+        'label' => '',
+        'date_format' => 'time ago',
+        'custom_date_format' => '',
+        'exclude' => 0,
+        'id' => 'created_time',
+        'table' => 'twitter',
+        'field' => 'created_time',
+        'relationship' => 'none',
+      ),
+    ),
+    'defaults' => array(
+      'fields' => FALSE,
+      'style_plugin' => FALSE,
+      'style_options' => FALSE,
+      'row_plugin' => FALSE,
+      'row_options' => FALSE,
+      'items_per_page' => FALSE,
+      'offset' => FALSE,
+      'use_pager' => FALSE,
+      'pager_element' => FALSE,
+      'use_more' => FALSE,
+    ),
+    'style_plugin' => 'list',
+    'style_options' => array(),
+    'row_plugin' => 'fields',
+    'row_options' => array(
+      'inline' => array(
+        'text' => 'text',
+        'created_time' => 'created_time',
+      ),
+      'separator' => ' &#8212; ',
+    ),
+    'items_per_page' => 5,
+    'offset' => 0,
+    'use_pager' => '0',
+    'pager_element' => 0,
+    'use_more' => 1,
+    'block_description' => 'User Tweets',
+  );
+  $view->display['block'] = $display;
+
+  return array('tweets' => $view);
+}
