? .DS_Store
? twitter_6.patch
Index: twitter.info
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/twitter/twitter.info,v
retrieving revision 1.1.2.1
diff -u -p -r1.1.2.1 twitter.info
--- twitter.info	18 Jun 2007 23:07:10 -0000	1.1.2.1
+++ twitter.info	26 Jul 2007 21:04:59 -0000
@@ -1,5 +1,6 @@
-; $Id: twitter.info,v 1.1.2.1 2007/06/18 23:07:10 dww Exp $
+; $Id$
 name = Twitter
-description = This module posts blog updates to twitter.com
-
-
+description = Exposes the twitter.com APIs to other Drupal modules
+package = Twitter
+php = 5.1
+core = 6.x
Index: twitter.install
===================================================================
RCS file: twitter.install
diff -N twitter.install
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ twitter.install	26 Jul 2007 21:04:59 -0000
@@ -0,0 +1,23 @@
+<?php
+// $Id$
+
+/**
+ * Implementation of hook_install().
+ */
+function twitter_install() {
+  // Create tables.
+  drupal_install_schema('twitter');
+}
+
+/**
+ * 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() {
+  twitter_install();
+}
+
+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.1
diff -u -p -r1.1 twitter.module
--- twitter.module	23 Jan 2007 17:05:50 -0000	1.1
+++ twitter.module	26 Jul 2007 21:04:59 -0000
@@ -1,67 +1,187 @@
 <?php
 // $Id: twitter.module,v 1.1 2007/01/23 17:05:50 walkah Exp $
 
-define('TWITTER_URL', 'http://twitter.com/statuses/update.xml');
-
 /**
- * Implementation of hook_user()
+ * @file
+ * Manages Twitter API integration for Drupal. Twitter data is stored
+ * (internal to this module, at least) as structured arrays in the following
+ * formats:
+ *
+ * $twitter_status = array(
+ *   'twitter_id'   => $unique_id_from_twitter,
+ *   'twitter_uid'  => $unique_user_id_from_twitter,
+ *   'twitter_user' => $optional_sub_array
+ *   'created_at'   => $time_of_post_in_UTC,
+ *   'created_time' => $unix_timestamp_of_post,
+ *   'text'         => $text_of_tweet,
+ *   'source'       => $source_application,
+ * );
+ *
+ * $twitter_account = array(
+ *   'twitter_uid'       => $unique_user_id_from_twitter,
+ *   'name'              => $twitter_login_email,
+ *   'screen_name'       => $twitter_screen_name,
+ *   'description'       => $user_description,
+ *   'profile_image_url' => $image_url,
+ *   'url'               => $user_home_page,
+ *   'protected'         => $boolean,
+ *   'status'            => $optional_sub_array
+ * );
  */
-function twitter_user($op, &$edit, &$account, $category = NULL) {
-  switch ($op) {
-    case 'form':
-      $form['twitter'] = array(
-        '#type' => 'fieldset',
-        '#title' => t('Twitter settings'));
-      $form['twitter']['twitter_user'] = array(
-        '#type' => 'textfield',
-        '#title' => t('Username'),
-        '#default_value' => $edit['twitter_user'],
-        '#description' => t('The username (email address) associated with your twitter.com account'));
-      $form['twitter']['twitter_pass'] = array(
-        '#type' => 'password',
-        '#title' => t('Password'));
-      $form['twitter']['twitter_text'] = array(
-        '#type' => 'textfield',
-        '#title' => t('Text format'),
-        '#default_value' => $edit['twitter_text'],
-        '#description' => t('Format of the text to submit. Use !title and !url for the post title and url respectively.'));
-      return $form;
-    case 'insert':
-    case 'update':
-      if (!empty($edit['twitter_user']) && !empty($edit['twitter_pass'])) {
-        $edit['twitter_encrypted'] = base64_encode($edit['twitter_user'] .':'. $edit['twitter_pass']);
-      }
-      unset($edit['twitter_pass']);
+
+function twitter_fetch_user_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));
+  }
+
+  $url = "http://twitter.com/statuses/user_timeline/$screen_name.xml";
+
+  if (!empty($since)) {
+    $url .= '?since='. urlencode($since);
+  }
+
+  $results = drupal_http_request($url, array(), 'GET');
+  $results = _twitter_convert_xml_to_array($results->data);
+
+  if ($cache) {
+    foreach($results as $status) {
+      twitter_cache_status($status);
+    }
   }
+  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);
 }
 
-function twitter_nodeapi(&$node, $op) {
-  switch ($op) {
-    case 'insert':
-      global $user;
-      if ($node->status == 1) {
-        twitter_post($node, $user);
-      }
+function twitter_fetch_user_followers($screen_name, $password, $cache = TRUE) {
+  $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');
+  $results = _twitter_convert_xml_to_array($results->data);
+
+  if ($cache) {
+    foreach($results as $status) {
+      twitter_cache_status($status);
+    }
   }
+  return $results;
 }
 
-/**
- * Implements the twitter posting API per:
- * http://twitter.com/help/api
- */
-function twitter_post($node, $account) {
-  if (empty($account->twitter_encrypted)) {
-    return false;
+function twitter_fetch_status($screen_name, $cache = TRUE) {
+  $url = "http://twitter.com/statuses/$screen_name.xml";
+  $results = drupal_http_request($url, array(), 'GET');
+  $results = _twitter_convert_xml_to_array($results->data);
+
+  if ($cache && !empty($results)) {
+    foreach($results as $status) {
+      twitter_cache_status($status);
+      return $status;
+    }
   }
+}
+
+function twitter_authenticate($screen_name, $password) {
+  $url = "http://twitter.com/account/verify_credentials.xml";
+  $headers = array('Authorization' => 'Basic '. base64_encode($screen_name .':'. $password),
+                   'Content-type' => 'application/x-www-form-urlencoded');
+  $results = drupal_http_request($url, $headers, 'GET');
+  drupal_http_request('http://twitter.com/account/end_session', $headers, 'GET');
+  return ($results->code == '200');
+}
 
-  $text = ($account->twitter_text) ? $account->twitter_text : 'New post: !title (!url)';
-  $text = t($text, array('!title' => $node->title,
-                         '!url' => url('node/' . $node->nid, NULL, NULL, TRUE)));
-  
-  $headers = array('Authorization' => 'Basic '. $account->twitter_encrypted,
+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');
+  $results = _twitter_convert_xml_to_array($results->data);
+
+  if ($cache) {
+    foreach($results as $user) {
+      twitter_cache_user($user);
+    }
+  }
+  return $results;
+}
+
+function twitter_cache_status($status = array(), $silent = FALSE) {
+  dsm($status);
+  db_query("DELETE FROM {twitter} WHERE twitter_id = %d", $status['twitter_id']);
+  $sql  = "INSERT INTO {twitter} (twitter_id, screen_name, created_at, created_time, `text`, source) ";
+  $sql .= "VALUES (%d, '%s', '%s', %d, '%s', '%s')";
+  db_query($sql, $status['twitter_id'], $status['screen_name'], $status['created_at'], $status['created_time'], $status['text'], $status['source']);
+  if (!$silent) {
+    module_invoke_all('twitter_status_update', $status);
+  }
+}
+
+function twitter_cache_user($twitter_user = array()) {
+  db_query("DELETE FROM {twitter_account} WHERE twitter_uid = %d", $twitter_user['twitter_uid']);
+  $sql  = "INSERT INTO {twitter_account} (twitter_uid, name, screen_name, description, profile_image_url, url, protected) ";
+  $sql .= "VALUES (%d, '%s', '%s', '%s', '%s', '%s', %d)";
+}
+
+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);
+}
+
+/**
+ * Internal XML munging code
+ */
 
-  $result = drupal_http_request(TWITTER_URL, $headers, 'POST', $data);
-  drupal_set_message(t('Posted to twitter.com'));
-}
\ No newline at end of file
+function _twitter_convert_xml_to_array($data) {
+  $results = array();
+  $xml =  new SimpleXMLElement($data);
+  if (!empty($xml->user)) {
+    foreach($xml->user as $user) {
+      $results[] = _twitter_convert_user($user);
+    }
+  }
+  elseif (!empty($xml->status)) {
+    foreach($xml->status as $status) {
+      $results[] = _twitter_convert_status($status);
+    }
+  }
+  return $results;
+}
+
+function _twitter_convert_status($status) {
+  $result = (array)$status;
+  $result['twitter_id'] = $result['id'];
+  if (!empty($result['user']) && is_object($result['user'])) {
+    $result['account'] = _twitter_convert_user($result['user']);
+    $result['screen_name'] = $result['account']['screen_name'];
+  }
+  else {
+    $result['screen_name'] = NULL;
+  }
+  $result['created_time'] = strtotime($result['created_at']);
+  return $result;
+}
+
+function _twitter_convert_user($user) {
+  $result = (array)$user;
+  $result['twitter_uid'] = $result['id'];
+  if (!empty($result['status']) && is_object($result['status'])) {
+    $result['status'] = _twitter_convert_status($result['status']);
+  }
+  else {
+    $result['twitter_uid'] = NULL;
+  }
+  return $result;
+}
Index: twitter.schema
===================================================================
RCS file: twitter.schema
diff -N twitter.schema
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ twitter.schema	26 Jul 2007 21:04:59 -0000
@@ -0,0 +1,32 @@
+<?php
+// $Id$
+
+function twitter_schema() {
+  $schema['twitter'] = array(
+    'fields' => array(
+      'twitter_id'    => array('type' => 'int', 'not null' => TRUE),
+      'screen_name'   => array('type' => 'varchar', 'length' => 255),
+      'created_at'    => array('type' => 'varchar', 'length' => 64, 'not null' => TRUE, 'default' => ''),
+      'created_time'  => array('type' => 'int', 'not null' => TRUE),
+      'text'          => array('type' => 'varchar', 'length' => 255, 'not null' => FALSE),
+      'source'        => array('type' => 'varchar', 'length' => 255, 'not null' => FALSE),
+    ),
+    'indexes' => array('screen_name' => array('screen_name')),
+    'primary key' => array('twitter_id'),
+  );
+
+  $schema['twitter_account'] = array(
+    'fields' => array(
+      'twitter_uid'       => array('type' => 'int', 'not null' => TRUE),
+      'name'              => array('type' => 'varchar', 'length' => 64, 'not null' => TRUE, 'default' => ''),
+      'screen_name'       => array('type' => 'varchar', 'length' => 255),
+      'description'       => array('type' => 'varchar', 'length' => 255),
+      'profile_image_url' => array('type' => 'varchar', 'length' => 255),
+      'url'               => array('type' => 'varchar', 'length' => 255),
+      'protected'         => array('type' => 'int', 'not null' => FALSE, 'default' => 0),
+    ),
+    'primary key' => array('screen_name'),
+  );
+
+  return $schema;
+}
\ No newline at end of file
Index: twitter_integration.info
===================================================================
RCS file: twitter_integration.info
diff -N twitter_integration.info
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ twitter_integration.info	26 Jul 2007 21:04:59 -0000
@@ -0,0 +1,6 @@
+; $Id$
+name = Twitter integration
+description = Connects twitter data to Drupal users and content
+dependencies[] = twitter
+package = Twitter
+core = 6.x
Index: twitter_integration.install
===================================================================
RCS file: twitter_integration.install
diff -N twitter_integration.install
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ twitter_integration.install	26 Jul 2007 21:04:59 -0000
@@ -0,0 +1,14 @@
+<?php
+// $Id$
+
+/**
+ * Implementation of hook_install().
+ */
+function twitter_integration_install() {
+  // Create tables.
+  drupal_install_schema('twitter_integration');
+}
+
+function twitter_integration_uninstall() {
+  drupal_uninstall_schema('twitter_integration');
+}
\ No newline at end of file
Index: twitter_integration.module
===================================================================
RCS file: twitter_integration.module
diff -N twitter_integration.module
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ twitter_integration.module	26 Jul 2007 21:04:59 -0000
@@ -0,0 +1,421 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Manages Twitter connections to Drupal users and nodes
+ */
+
+/**
+ * Hooks into the core Drupal system. Menus, permissions, theme functions,
+ * etc...
+ */
+
+function twitter_integration_perm() {
+  return array('use twitter account', 'import twitter statuses', 'view twitter statuses');
+}
+
+function twitter_menu() {
+  $items = array();
+  $items['user/%user/twitter'] = array(
+    'title' => 'Twitter status',
+    'page callback' => 'twitter_integration_user_status_page',
+    'page arguments' => array(1),
+    'access callback' => 'user_access',
+    'access arguments' => array('view twitter statuses'),
+    'weight' => 10,
+    'type' => MENU_LOCAL_TASK,
+  );
+  return $items;
+}
+
+function twitter_integration_user($op, &$edit, &$account, $category = NULL) {
+  switch ($op) {
+    case 'form':
+      if (user_access('use twitter account')) {
+        $twitter_accounts = twitter_integration_account_load($account->uid);
+        if (!empty($twitter_accounts)) {
+          $twitter = array_pop($twitter_accounts);
+        }
+        else {
+          $twitter = array();
+        }
+
+        $form['twitter'] = array(
+          '#type' => 'fieldset',
+          '#tree' => TRUE,
+          '#collapsible' => TRUE,
+          '#title' => t('Twitter settings'),
+        );
+        $form['twitter']['screen_name'] = array(
+          '#type' => 'textfield',
+          '#title' => t('Twitter user name'),
+          '#default_value' => !empty($twitter) ? $twitter['screen_name'] : '',
+        );
+        $form['twitter']['password'] = array(
+          '#type' => 'password',
+          '#title' => t('Password'),
+          '#default_value' => !empty($twitter) ? $twitter['password'] : '',
+        );
+
+        $form['twitter']['status_featured'] = array (
+          '#type' => 'checkbox',
+          '#title' => t('Display my status in a sidebar block'),
+          '#default_value' => !empty($twitter) ? $twitter['status_featured'] : TRUE,
+        );
+
+        $form['twitter']['status_on_profile'] = array (
+          '#type' => 'checkbox',
+          '#title' => t('Display my status on my profile page'),
+          '#default_value' => !empty($twitter) ? $twitter['status_on_profile'] : TRUE,
+        );
+
+        if (user_access('import twitter statuses')) {
+          $options = array(-1 => t('Never'));
+          $intervals = drupal_map_assoc(array(14400, 28800, 86400, 604800), 'format_interval');
+          $options = array_merge($options, $intervals);
+          $form['twitter']['node_interval'] = array (
+            '#type' => 'select',
+            '#title' => t('Create a Twitter node every'),
+            '#options' => $options,
+            '#default_value' => !empty($twitter) ? $twitter['node_interval'] : 86400,
+          );
+
+          $options = node_get_types('names');
+          foreach ($options as $type => $option) {
+            if (node_access('view', $type)) {
+              $types[$type] = $option;
+            }
+          }
+
+          $form['twitter']['node_type'] = array (
+            '#type' => 'select',
+            '#title' => t('Node type for Twitter statuses'),
+            '#options' => $types,
+            '#default_value' => !empty($twitter['node_type']) ? $twitter['node_type'] : 'story',
+          );
+
+          $form['twitter']['node_title'] = array (
+            '#type' => 'textfield',
+            '#title' => t('Title for Twitter nodes'),
+            '#default_value' => !empty($twitter['node_title']) ? $twitter['node_title'] : t("Today's tweets..."),
+          );
+        }
+
+        return $form;
+      }
+    case 'validate':
+      if (!empty($edit['twitter'])) {
+        $verify = FALSE;
+
+        $pass = $edit['twitter']['password'];
+        $name = $edit['twitter']['screen_name'];
+
+        if (!empty($pass)) {
+          $verify = TRUE;
+        }
+
+        if ($verify) {
+          $valid = twitter_authenticate($name, $pass);
+          if (!$valid) {
+            form_set_error("twitter][password", t('Twitter authentication failed. Please check your account name and try again.'));
+          }
+        }
+      }
+      break;
+    case 'insert':
+    case 'update':
+      if (!empty($edit['twitter'])) {
+        if (!empty($edit['twitter']['screen_name'])) {
+        $edit['twitter']['uid'] = $account->uid;
+        if (!user_access('import twitter statuses')) {
+          $edit['twitter'] = -1;
+          $edit['twitter'] = NULL;
+          $edit['twitter'] = NULL;
+        }
+          twitter_integration_user_save($edit['twitter']);
+        }
+        else {
+          twitter_integration_user_delete($account->uid);
+        }
+      }
+      unset($edit['twitter']);
+      break;
+  }
+}
+
+function twitter_integration_block($op = 'list', $delta = 0, $edit = array()) {
+  if ($op == 'list') {
+     $blocks[0]['info'] = t('Latest tweets');
+     $blocks[1]['info'] = t('Author tweets');
+     return $blocks;
+  }
+  else if ($op == 'view' && user_access('view twitter statuses')) {
+    switch ($delta) {
+      case 0:
+        $output = '';
+        $account = NULL;
+        if ((arg(0) == 'node') && is_numeric(arg(1)) && (arg(2) == NULL)) {
+          $node = node_load(arg(1));
+          $account = user_load(array('uid' => $node->uid));
+        }
+        elseif (((arg(0) == 'blog' || arg(0) == 'user') && is_numeric(arg(1)))) {
+          $account = user_load(array('uid' => arg(1)));
+        }
+
+        $items = array();
+        if (!empty($account->uid)) {
+          $output = '';
+          $sql = "SELECT t.* FROM {twitter} t INNER JOIN {twitter_user} tu ";
+          $sql .= "ON t.screen_name = tu.screen_name WHERE tu.status_featured = 1 AND tu.uid = %d ";
+          $sql .= "ORDER BY t.created_time DESC ";
+          $results = db_query_range($sql, array($account->uid), 1, 5);
+
+          while ($status = db_fetch_array($results)) {
+            $items[] = theme('twitter_integration_status', $status['screen_name'], $status['text'], 'short', $status, TRUE);
+          }
+        }
+
+        if (!empty($items)) {
+           $block['subject'] = t('!name\'s tweets', array('!name' => $account->name));
+           $block['content'] = theme('item_list', $items);
+           return $block;
+        }
+        break;
+      case 1:
+        $output = '';
+        $sql = "SELECT t.* FROM {twitter} t INNER JOIN {twitter_user} tu ";
+        $sql .= "ON t.screen_name = tu.screen_name WHERE tu.status_featured = 1 ";
+        $sql .= "ORDER BY t.created_time DESC ";
+        $results = db_query_range($sql, array(), 1, 5);
+
+        $items = array();
+        while ($status = db_fetch_array($results)) {
+          $items[] = theme('twitter_integration_status', $status['screen_name'], $status['text'], 'named', $status, TRUE);
+        }
+
+        if (!empty($items)) {
+           $block['subject'] = t('Latest tweets');
+           $block['content'] = theme('item_list', $items);
+           return $block;
+        }
+        break;
+    }
+  }
+}
+
+function twitter_integration_theme() {
+  return array(
+    'twitter_integration_status' => array(
+      'arguments' => array('screen_name' => '', 'text' => '', 'format' => 'short', 'status' => array(), $inline = FALSE),
+    ),
+    'twitter_integration_user_status_page' => array(
+      'arguments' => array('user' => NULL, 'statues' => array()),
+    ),
+  );
+}
+
+
+
+function twitter_integration_cron() {
+  $cron_interval = variable_get('twitter_integration_cron_interval', 3600);
+  $last_cron = variable_get('twitter_integration_last_cron', 0);
+  $current_cron = time();
+
+  $sql = "SELECT * FROM {twitter_user} tu WHERE tu.last_refresh < %d";
+
+  // We'll only process 20 users at a time to avoid hammering Twitter.
+  $result = db_query_range($sql, array(time() - $cron_interval), 1, 20);
+  while ($account = db_fetch_array($result)) {
+
+    // First refresh the local tweets
+    $statuses = twitter_fetch_user_timeline($account['screen_name']);
+
+    // This code should see if any unlinked tweets fall inside the account's
+    // node-creation window.
+    /*
+    $node = array('type' => $account['node_type']);
+    $form_state = array();
+    $form_state['values']['title'] = $account['note_title'];
+    $form_state['values']['uid'] = $account['uid'];
+    drupal_execute($account['node_type'] .'_node_form', $form_state, $node);
+
+    foreach ($statuses as $status) {
+      twitter_integration_attach_to_node($form_state['nid'], $status['twitter_id']);
+    }
+    */
+
+    db_query("UPDATE {twitter_user} SET last_refresh = %d WHERE uid = %d AND screen_name = '%s'", $current_cron, $account['uid'], $account['screen_name']);
+  }
+
+  variable_set('twitter_integration_last_cron', $current_cron);
+}
+
+/**
+ * Page generation and shared theming functions
+ */
+
+function twitter_integration_user_status_page($user = NULL) {
+  if (empty($user)) {
+    drupal_not_found();
+    return;
+  }
+  drupal_set_title($user->name);
+
+  $accounts = twitter_integration_account_load($user->uid);
+  $names = array_keys($accounts);
+  $grouped = twitter_integration_get_account_statuses($names, FALSE, 20);
+
+  $results = array();
+  foreach ($grouped as $name => $statuses) {
+    foreach($statuses as $status) {
+      $results[] = $status;
+    }
+  }
+
+  return theme('twitter_integration_user_status_page', $user, $results);
+}
+
+function theme_twitter_integration_user_status_page($user, $statuses = array()) {
+  $output = '';
+  if (!empty($statuses)) {
+    $current = array_shift($statuses);
+    $output .= '<h1>'. check_plain($current['text']) .'</h1>';
+  }
+  foreach ($statuses as $status) {
+    $output .= theme('twitter_integration_status', $status['screen_name'], $status['text'], 'timed', $status);
+  }
+  return $output;
+}
+
+function theme_twitter_integration_status($screen_name, $text, $format = 'short', $status = array(), $inline = FALSE) {
+  switch ($format) {
+    case 'short':
+      $output = check_plain($text);
+      break;
+    case 'timed':
+      $output = check_plain($text);
+      $output .= ' <span class="twitter-timestamp">'. l(format_date($status['created_time'], 'small'), 'http://www.twitter.com/statuses/'. $status['twitter_id'], array('absolute' => TRUE, 'html' => TRUE)) .'</span>';
+      break;
+    case 'named':
+      $output = l($screen_name, 'http://www.twitter.com/'. $screen_name, array('absolute' => TRUE)) .': ';
+      $output .= check_plain($text);
+      break;
+    case 'full':
+      $output = l($screen_name, 'http://www.twitter.com/'. $screen_name, array('absolute' => TRUE)) .': ';
+      $output .= check_plain($text);
+      $output .= ' <span class="twitter-timestamp">'. l(format_date($status['created_time'], 'small'), 'http://www.twitter.com/statuses/'. $status['twitter_id'], array('absolute' => TRUE, 'html' => TRUE)) .'</span>';
+      break;
+    default:
+      $output = check_plain($text);
+      break;
+  }
+
+  if (!$inline) {
+    $output = '<div class="twitter-status">'. $output .'</div>';
+  }
+  return $output;
+}
+
+
+
+
+/**
+ * CRUD and connection management functions for the Twitter-to-node and
+ * Twitter-account-to-Drupal-user mapping.
+ */
+
+function twitter_integration_get_node_statuses($nid, $module = NULL) {
+  $sql = "SELECT t.* FROM {twitter} t LEFT JOIN {twitter_integration} tn ON t.twitter_id = tn.twitter_id WHERE tn.nid = %d";
+  $args = array($nid);
+  if (!empty($module)) {
+    $sql .= " AND tn.module = '%s'";
+    $args[] = $module;
+  }
+  $results = db_query($sql, $args);
+
+  $statuses = array();
+  while ($status = db_fetch_array($results)) {
+    $statuses[$status['twitter_id']] = $status;
+  }
+  return $statuses;
+}
+
+function twitter_integration_get_account_statuses($screen_names = array(), $refresh = FALSE, $limit = 10) {
+  if ($refresh) {
+    foreach ($screen_names as $name) {
+      twitter_fetch_user_timeline($name);
+    }
+  }
+
+  if (!is_array($screen_names)) {
+    $screen_names = array($screen_names);
+  }
+  $sql  = "SELECT t.* FROM {twitter} t WHERE t.screen_name IN (";
+  $sql .= implode(",", array_fill(0, count($screen_names), "'%s'"));
+  $sql .= ") ORDER BY t.created_time DESC";
+
+  if (empty($limit)) {
+    $results = db_query($sql, $screen_names);
+  }
+  else {
+    $results = db_query_range($sql, $screen_names, 1, $limit);
+  }
+
+  $statuses = array();
+  while ($status = db_fetch_array($results)) {
+    $statuses[$status['screen_name']][$status['twitter_id']] = $status;
+  }
+  return $statuses;
+}
+
+function twitter_integration_attach_to_node($nid, $twitter_id, $module = 'twitter_integration') {
+  twitter_detach_from_node($nid, $twitter_id);
+  db_query("INSERT INTO {twitter_integration} tn (nid, twitter_id, module) VALUES (%d, %d, '%s')", $nid, $twitter_id, $module);
+}
+
+function twitter_integration_detach_from_node($nid, $twitter_id) {
+  db_query("DELETE FROM {twitter_integration} WHERE nid = %d AND twitter_id = %d", $nid, $twitter_id);
+}
+
+function twitter_integration_user_save($user = array()) {
+  if (db_result(db_query("SELECT 1 FROM {twitter_user} WHERE uid = %d AND screen_name = '%s'", $user['uid'], $user['screen_name']))) {
+    $sql  = "UPDATE {twitter_user} SET password = '%s', last_refresh = %d, status_on_profile = %d, status_featured = %d, node_interval = %d, node_type = '%s', node_title = '%s' ";
+    $sql .= "WHERE uid = %d AND screen_name = '%s'";
+    db_query($sql, $user['password'], time(), $user['status_on_profile'], $user['status_featured'], $user['node_interval'], $user['node_type'], $user['node_title'], $user['uid'], $user['screen_name']);
+  }
+  else {
+    $sql  = "INSERT INTO {twitter_user} (uid, screen_name, password, last_refresh, status_on_profile, status_featured, node_interval, node_type, node_title) ";
+    $sql .= "VALUES (%d, '%s', '%s', %d, %d, %d, %d, '%s', '%s')";
+    db_query($sql, $user['uid'], $user['screen_name'], $user['password'], time(), $user['status_on_profile'], $user['status_featured'], $user['node_interval'], $user['node_type'], $user['node_title']);
+  }
+  twitter_fetch_user_details($user['screen_name'], $user['password']);
+  twitter_fetch_user_timeline($user['screen_name']);
+}
+
+function twitter_integration_user_delete($uid, $screen_name = NULL) {
+  $sql = "DELETE FROM {twitter_user} WHERE uid = %d";
+  $args = array($uid);
+  if (!empty($screen_name)) {
+    $sql .= " AND tu.screen_name = '%s'";
+    $args[] = $screen_name;
+  }
+  db_query($sql, $args);
+}
+
+function twitter_integration_account_load($uid, $screen_name = NULL) {
+  $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);
+  if (!empty($screen_name)) {
+    $sql .= " AND tu.screen_name = '%s'";
+    $args[] = $screen_name;
+  }
+
+  $results = db_query($sql, $args);
+
+  $accounts = array();
+  while ($account = db_fetch_array($results)) {
+    $accounts[$account['screen_name']] = $account;
+  }
+  return $accounts;
+}
Index: twitter_integration.schema
===================================================================
RCS file: twitter_integration.schema
diff -N twitter_integration.schema
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ twitter_integration.schema	26 Jul 2007 21:05:00 -0000
@@ -0,0 +1,30 @@
+<?php
+// $Id$
+
+function twitter_integration_schema() {
+  $schema['twitter_node'] = array(
+    'fields' => array(
+      'nid'               => array('type' => 'int', 'not null' => TRUE),
+      'twitter_id'        => array('type' => 'int', 'not null' => TRUE),
+      'module'            => array('type' => 'varchar', 'length' => 64, 'not null' => TRUE, 'default' => 'twitter_node'),
+    ),
+    'primary key' => array('nid', 'twitter_id'),
+  );
+
+  $schema['twitter_user'] = array(
+    'fields' => array(
+      'uid'               => array('type' => 'int', 'not null' => TRUE),
+      'screen_name'       => array('type' => 'varchar', 'length' => 255),
+      'password'          => array('type' => 'varchar', 'length' => 64),
+      'last_refresh'      => array('type' => 'int', 'not null' => TRUE),
+      'status_on_profile' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
+      'status_featured'   => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
+      'node_interval'     => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
+      'node_type'         => array('type' => 'varchar', 'length' => 64),
+      'node_title'        => array('type' => 'varchar', 'length' => 128),
+    ),
+    'primary key' => array('uid', 'screen_name'),
+  );
+
+  return $schema;
+}
Index: twitter_log.info
===================================================================
RCS file: twitter_log.info
diff -N twitter_log.info
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ twitter_log.info	26 Jul 2007 21:05:00 -0000
@@ -0,0 +1,6 @@
+; $Id$
+name = Twitter log
+description = Logs and records system events to Twitter.
+dependencies[] = twitter
+package = Twitter
+core = 6.x
Index: twitter_log.module
===================================================================
RCS file: twitter_log.module
diff -N twitter_log.module
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ twitter_log.module	26 Jul 2007 21:05:00 -0000
@@ -0,0 +1,110 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Logs and records system events to Twitter.
+ */
+
+function twitter_log_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#twitter_log':
+      return '<p>'. t('Provides the facility to log Drupal messages to a Twitter.com account. Twitter is a free microblogging site with new messages notification via SMS, email, desktop widget, etc. Because the API is a free resource, only severe issues should be directed to Twitter.') .'</p>';
+  }
+}
+
+function twitter_log_menu() {
+  $items['admin/settings/logging/twitter_log'] = array(
+    'title'          => 'Twitter log',
+    'description'    => 'Settings for Twitter logging. Twitter is a free microblogging site with new messages notification via SMS, email, desktop widget, etc. Because the API is a free resource, only severe issues should be directed to Twitter.',
+    'page callback'  => 'drupal_get_form',
+    'page arguments' => array('twitter_log_admin_settings'),
+  );
+  return $items;
+}
+
+function twitter_log_admin_settings() {
+  if ($screen_name = variable_get('twitter_log_screen_name', NULL)) {
+    $form['description'] = array(
+      '#type'   => 'markup',
+      '#value'  => t("Log messages will be sent to !url.", array('!url' => l("http://www.twitter.com/$screen_name", "http://www.twitter.com/$screen_name"))),
+    );
+  }
+
+  $form['twitter_log_screen_name'] = array(
+    '#type'          => 'textfield',
+    '#title'         => t('Twitter account name'),
+    '#default_value' => variable_get('twitter_log_screen_name', NULL),
+    '#required'      => TRUE,
+  );
+
+  $pass =  variable_get('twitter_log_password', NULL);
+  $form['twitter_log_password'] = array(
+    '#title'         => t('Twitter password'),
+    '#type'          => 'password',
+    '#size'          => 25,
+    '#required'      => empty($pass),
+  );
+
+  $options = array(
+    WATCHDOG_EMERG    => t('Emergency: system is unusable'),
+    WATCHDOG_ALERT    => t('Alert: action must be taken immediately'),
+    WATCHDOG_CRITICAL => t('Critical: critical conditions'),
+    WATCHDOG_ERROR    => t('Error: error conditions'),
+    WATCHDOG_WARNING  => t('Warning: warning conditions'),
+    WATCHDOG_NOTICE   => t('Notice: normal but significant condition'),
+    WATCHDOG_INFO     => t('Informational: informational messages'),
+    WATCHDOG_DEBUG    => t('Debug: debug-level messages'),
+  );
+
+  $form['twitter_log_severity'] = array(
+    '#type'          => 'select',
+    '#title'         => t('Severity level'),
+    '#default_value' => variable_get('twitter_log_severity', WATCHDOG_CRITICAL),
+    '#options'       => $options,
+    '#description'   => t('Watchdog messages below this severity level will not be posted. Severity levels, as defined in !rfc, allow administrators to filter alert messages. Most Drupal log messages are Errors or Notices; Twitter\'s free API should be reservered for serious messages.', array(
+      '!rfc'         => l("RFC 3164", 'http://www.faqs.org/rfcs/rfc3164.html'),
+      )),
+  );
+
+  $form = system_settings_form($form);
+  $form['#validate'][] = 'twitter_log_admin_settings_validate';
+  return $form;
+}
+
+function twitter_log_admin_settings_validate($form, &$form_state) {
+  $verify = FALSE;
+
+  $pass = $form_state['values']['twitter_log_password'];
+  $name = $form_state['values']['twitter_log_screen_name'];
+
+  if (empty($pass)) {
+    $pass = variable_get('twitter_log_password', NULL);
+    unset($form_state['values']['twitter_log_password']);
+  }
+  else {
+    $verify = TRUE;
+  }
+  
+  if (variable_get('twitter_log_screen_name', NULL) != $name) {
+    $verify = TRUE;
+  }
+  
+  if ($verify) {
+    $valid = twitter_authenticate($name, $pass);
+    if (!$valid) {
+      form_set_error('twitter_log_password', t('Twitter authentication failed. Please check your account name and try again.'));
+    }
+  }
+}
+
+function twitter_log_watchdog($entry) {
+  $severity = variable_get('twitter_log_severity', WATCHDOG_ALERT);
+  $user_name = variable_get('twitter_log_screen_name', NULL);
+  $password = variable_get('twitter_log_password', NULL);
+  $message = strip_tags(is_null($entry['variables']) ? $entry['message'] : strtr($entry['message'], $entry['variables']));
+
+  if ($entry['severity'] <= $severity && !empty($user_name) && !empty($password)) {
+    twitter_set_status($user_name, $password, $message);
+  }
+}
\ No newline at end of file
Index: twitter_ping.info
===================================================================
RCS file: twitter_ping.info
diff -N twitter_ping.info
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ twitter_ping.info	26 Jul 2007 21:05:00 -0000
@@ -0,0 +1,6 @@
+; $Id$
+name = Twitter ping
+description = Notifies a Twitter account when new nodes are created.
+dependencies[] = twitter
+package = Twitter
+core = 6.x
Index: twitter_ping.module
===================================================================
RCS file: twitter_ping.module
diff -N twitter_ping.module
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ twitter_ping.module	26 Jul 2007 21:05:00 -0000
@@ -0,0 +1,102 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Posts to Twitter when new nodes are created.
+ */
+
+function twitter_ping_menu() {
+  $items['admin/content/twitter_ping'] = array(
+    'title'          => 'Twitter ping',
+    'description'    => 'Post a notice to Twitter when new nodes are published.',
+    'page callback'  => 'drupal_get_form',
+    'page arguments' => array('twitter_ping_admin_settings'),
+  );
+  return $items;
+}
+
+function twitter_ping_admin_settings() {
+  if ($screen_name = variable_get('twitter_ping_screen_name', NULL)) {
+    $form['description'] = array(
+      '#type'   => 'markup',
+      '#value'  => t("Pings about new content will be sent to !url.", array('!url' => l("http://www.twitter.com/$screen_name", "http://www.twitter.com/$screen_name"))),
+    );
+  }
+
+  $form['twitter_ping_screen_name'] = array(
+    '#type'          => 'textfield',
+    '#title'         => t('Twitter account name'),
+    '#default_value' => variable_get('twitter_ping_screen_name', NULL),
+    '#required'      => TRUE,
+  );
+
+  $pass =  variable_get('twitter_ping_password', NULL);
+  $form['twitter_ping_password'] = array(
+    '#title'         => t('Twitter password'),
+    '#type'          => 'password',
+    '#size'          => 25,
+    '#required'      => empty($pass),
+  );
+
+  $form['twitter_ping_node_types'] = array(
+    '#type'          => 'select',
+    '#title'         => t('Node types'),
+    '#default_value' => variable_get('twitter_ping_node_types', array('story' => 'story')),
+    '#options'       => node_get_types('names'),
+    '#multiple'      => TRUE,
+  );
+
+  // TODO: Token support as soon as it's ported to 6
+  $form['twitter_ping_text'] = array(
+    '#type'          => 'textarea',
+    '#title'         => t('Twitter text'),
+    '#default_value' => variable_get('twitter_ping_text', 'New post: !title (!url)'),
+    '#description'   => t('Format of the text to submit. Use !title and !url for the post title and url respectively.'),
+  );
+
+  $form = system_settings_form($form);
+  $form['#validate'][] = 'twitter_ping_admin_settings_validate';
+  return $form;
+}
+
+function twitter_ping_admin_settings_validate($form, &$form_state) {
+  $verify = FALSE;
+
+  $pass = $form_state['values']['twitter_ping_password'];
+  $name = $form_state['values']['twitter_ping_screen_name'];
+
+  if (empty($pass)) {
+    $pass = variable_get('twitter_ping_password', NULL);
+    unset($form_state['values']['twitter_ping_password']);
+  }
+  else {
+    $verify = TRUE;
+  }
+  
+  if (variable_get('twitter_ping_screen_name', NULL) != $name) {
+    $verify = TRUE;
+  }
+  
+  if ($verify) {
+    $valid = twitter_authenticate($name, $pass);
+    if (!$valid) {
+      form_set_error('twitter_ping_password', t('Twitter authentication failed. Please check your account name and try again.'));
+    }
+  }
+}
+
+function twitter_nodeapi(&$node, $op) {
+  switch ($op) {
+    case 'insert':
+      $user_name = variable_get('twitter_ping_screen_name', NULL);
+      $password = variable_get('twitter_ping_password', NULL);
+      $types = variable_get('twitter_ping_node_types', array('story' => 'story'));
+      $message = variable_get('twitter_ping_text', 'New post: !title (!url)');
+
+      if ($node->status == 1 && !empty($types[$node->type])) {
+        $message = t($message, array('!title' => $node->title, '!url' => url('node/' . $node->nid, array('absolute' => TRUE))));
+        twitter_set_status($user_name, $password, $message);
+      }
+  }
+}
