diff -Naur chatroom/CHANGELOG.txt chatroom-6.x-dev/CHANGELOG.txt
--- chatroom/CHANGELOG.txt	2007-10-12 09:06:33.000000000 -0400
+++ chatroom-6.x-dev/CHANGELOG.txt	2008-10-18 21:14:27.000000000 -0400
@@ -1,4 +1,4 @@
-$Id: CHANGELOG.txt,v 1.11.2.4 2007/10/12 13:06:33 darrenoh Exp $
+$Id$
 
 Chat Room 5.x-1.9, 2007-10-12
 -----------------------------
diff -Naur chatroom/chatroom.css chatroom-6.x-dev/chatroom.css
--- chatroom/chatroom.css	2007-10-10 11:05:09.000000000 -0400
+++ chatroom-6.x-dev/chatroom.css	2008-10-18 21:14:27.000000000 -0400
@@ -1,28 +1,18 @@
-/* $Id: chatroom.css,v 1.20.2.10 2007/10/10 15:05:09 darrenoh Exp $ */
+/* $Id$ */
 #chatroom table {
   width: 100%;
 }
-#chatroom-textentry {
-  padding: 8px;
-  margin-top: 15px;
-  color: #666666;
-}
-#chatroom-board {
-  overflow: auto;
-  height: 300px;
-}
 #chatroom-container-archive tbody {
   border-top: hidden;
 }
 #chatroom-container-archive td {
   padding: 0;
 }
-#chatroom-textentry,
 #chatroom-user-options {
   font-size: 80%;
 }
-#chatroom-textentry,
 #chatroom-board,
+#chatroom-chat-form div.item-list,
 #chatroom-container-archive {
   border: solid 1px #e0e0e0;
 }
@@ -93,15 +83,6 @@
 .chatroom-me-msg {
   color: #7777FF;
 }
-.chatroom-time-msg,
-.chatroom-me-msg,
-.chatroom-old-msg,
-.chatroom-old-me-msg,
-.chatroom-system-msg,
-.chatroom-msg {
-  padding: .4em;
-}
-#chatroom-textentry,
 #chatroom-container-archive,
 .chatroom-time-msg,
 .chatroom-me-msg,
@@ -118,9 +99,6 @@
   margin-right: .4em;
   vertical-align: top;
 }
-.chatroom-old-msg, .chatroom-msg{
-  padding-left: 2.2em;
-}
 .chatroomLink {
   font-size: 0.9em;
 }
@@ -136,3 +114,40 @@
   font-weight: normal;
 }
 
+/*****************************************************************************/
+#chatroom-chat-form #edit-message {
+  padding: 0.25em;
+  font-size: 1.25em;
+}
+
+#chatroom-chat-form div.item-list {
+  float: right
+}
+  #chatroom-chat-form div.item-list ul {
+    margin: 0.25em;
+  }
+  #chatroom-chat-form div.item-list li {
+    list-style-type: none;
+  }
+
+div.ahah-response,
+#chatroom-chat-form #edit-control-fieldset {
+  display: none;
+}
+
+#chatroom-board,
+#chatroom-chat-form div.item-list {
+  overflow: auto;
+  height: 300px;
+}
+
+.chatroom-msg {
+  padding: 0.2em 0;
+}
+
+/* TODO: find a way to disable filter #2 from:
+  http://api.drupal.org/api/function/filter_filter/6 */
+.chatroom-msg > p {
+  display: inline;
+}
+
diff -Naur chatroom/chatroom.forms.inc chatroom-6.x-dev/chatroom.forms.inc
--- chatroom/chatroom.forms.inc	1969-12-31 19:00:00.000000000 -0500
+++ chatroom-6.x-dev/chatroom.forms.inc	2008-10-18 21:14:27.000000000 -0400
@@ -0,0 +1,491 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Form functions for chatroom.module.
+ */
+
+/**
+ * Implementation of hook_forms()
+ */
+function chatroom_forms() {
+  $forms['chatroom_chat_form'] = array(
+    'callback' => 'chatroom_chat_form',
+  );
+  $forms['chatroom_create_chat_form'] = array(
+    'callback' => 'chatroom_create_chat_form',
+  );
+  $forms['chatroom_archive_chat_form'] = array(
+    'callback' => 'chatroom_archive_chat_form',
+  );
+  $forms['chatroom_delete_chat_form'] = array(
+    'callback' => 'chatroom_delete_chat_form',
+  );
+  return $forms;
+}
+
+/**
+ * Implementation of hook_form().
+ *
+ * This is the node editing form for chatroom nodes.
+ */
+function chatroom_form(&$node, &$params) {
+  global $user;
+  $form['title'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Name'),
+    '#default_value' => check_plain($node->title),
+    '#required' => TRUE,
+  );
+  $form['body_filter']['body'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Description'),
+    '#default_value' => $node->body,
+    '#rows' => 3,
+    '#description' => t('Describe your chat room so other people will know if they want to join.'),
+  );
+  $form['body_filter']['format'] = filter_form($node->format);
+  $form['kicked_out_message'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Chat room kicked out message'),
+    '#default_value' => isset($node->chatroom->kicked_out_message) ? $node->chatroom->kicked_out_message : null,
+    '#rows' => 3,
+    '#description' => t('This text will appear on the page kicked out users are sent to. Defaults to, "You have been kicked out of %chat for misbehaving."', array('%chat' => t('chat-name'))),
+  );
+  $form['banned_message'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Chat room banned message'),
+    '#default_value' => isset($node->chatroom->banned_message) ? $node->chatroom->banned_message : null,
+    '#rows' => 3,
+    '#description' => t('This text will appear on the page banned users are sent to. Defaults to, "You have been banned from %chatroom."', array('%chatroom' => t('chat-room'))),
+  );
+  if (!empty($node->chatroom->banned_users)) {
+    $form['chatroom_banned_users'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Manage banned users'),
+      '#collapsible' => TRUE,
+    );
+    foreach ($node->chatroom->banned_users as $banned_user) {
+      $banned_users[$banned_user->uid] = check_plain($banned_user->name);
+    }
+    $form['chatroom_banned_users']['unban_list'] = array(
+      '#type' => 'checkboxes',
+      '#options' => $banned_users,
+      '#description' => t('Check the users you would like to unban'),
+    );
+  }
+  $form['chat_settings'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Chat settings'),
+    '#collapsible' => TRUE,
+  );
+  $form['chat_settings']['poll_freq'] = array(
+    '#type' => 'select',
+    '#title' => t('Update frequency'),
+    '#default_value' => empty($node->chatroom->poll_freq) ? 1 : $node->chatroom->poll_freq / 1000,
+    '#options' => drupal_map_assoc(range(1, 10)),
+    '#description' => t('How many seconds between each request for updates from the server.'),
+  );
+  $form['chat_settings']['idle_freq'] = array(
+    '#type' => 'select',
+    '#title' => t('Idle time'),
+    '#default_value' => empty($node->chatroom->idle_freq) ? 60 : $node->chatroom->idle_freq / 1000,
+    '#options' => drupal_map_assoc(array(20, 40, 60, 80, 100, 120, 140, 160, 180)),
+    '#description' => t('How many seconds between each message before a last message time is shown in the chat.'),
+  );
+  $old_msg_range = array();
+  for ($i = 1; $i <= 25; $i++) {
+    $old_msg_range[$i] = $i * 10;
+  }
+  $form['chat_settings']['old_msg_count'] = array(
+    '#type' => 'select',
+    '#title' => t('Old messages'),
+    '#description' => t('How many old messages to show when entering a chat.'),
+    '#default_value' => empty($node->chatroom->old_msg_count) ? 20 : $node->chatroom->old_msg_count,
+    '#options' => drupal_map_assoc($old_msg_range),
+  );
+  if (!empty($node->chatroom->chats)) {
+    foreach ($node->chatroom->chats as $chat) {
+      if ($chat->section != 'archives') {
+        $chats[$chat->ccid] = check_plain($chat->chatname);
+      }
+      else {
+        $closed_chats[$chat->ccid] = check_plain($chat->chatname);
+      }
+    }
+    if (!empty($chats)) {
+      $form['chatroom_chats'] = array(
+        '#type' => 'fieldset',
+        '#title' => t('Manage open chats'),
+        '#collapsible' => TRUE,
+        '#collapsed' => FALSE,
+      );
+      $form['chatroom_chats']['chat_list'] = array(
+        '#type' => 'checkboxes',
+        '#options' => $chats,
+        '#description' => t('Check the chats you would like to close'),
+      );
+    }
+    if (!empty($closed_chats)) {
+      $form['closed_chats'] = array(
+        '#type' => 'fieldset',
+        '#title' => t('Manage archived chats'),
+        '#collapsible' => TRUE,
+        '#collapsed' => FALSE,
+      );
+      $form['closed_chats']['closed_chat_list'] = array(
+        '#type' => 'checkboxes',
+        '#options' => $closed_chats,
+        '#description' => t('Check the chats you would like to delete'),
+      );
+    }
+  }
+  return $form;
+}
+
+/**
+ * Menu callback; display site-wide chat room settings.
+ */
+function chatroom_admin_settings() {
+  $form['chatroom_auto_archive'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Automatically archive old messages.'),
+    '#description' => t('If there are a lot of old messages, archiving will improve chat performance.'),
+    '#default_value' => variable_get('chatroom_auto_archive', FALSE),
+  );
+  $form['chatroom_block_update_interval'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Chat room block update interval'),
+    '#default_value' => variable_get('chatroom_block_update_interval', 5),
+    '#description' => t('Determines how often blocks should update active chat rooms, active chats, and on-line users.'),
+    '#size' => 2,
+  );
+  $form['chatroom_guest_user_prefix'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Guest user prefix'),
+    '#description' => t('Prefixed to guest ID to provide user name for anonymous users.'),
+    '#default_value' => variable_get('chatroom_guest_user_prefix', t('guest-')),
+    '#size' => 20,
+  );
+  $form['chatroom_chat_date_format'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Chat date format'),
+    '#attributes' => array('class' => 'custom-format'),
+    '#default_value' => variable_get('chatroom_chat_date_format', '* \S\e\n\t \a\t G:i'),
+    '#description' => t('Format for system time messages in chats. See the <a href="@url">PHP manual</a> for available options. This format is currently set to display as <span>%date</span>.', array('@url' => 'http://php.net/manual/function.date.php', '%date' => format_date(time(), 'custom', variable_get('chatroom_chat_date_format', '* \S\e\n\t \a\t G:i')))),
+  );
+  if (module_exists('smileys')) {
+    $form['chatroom_smileys_support'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Smileys module support'),
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE,
+    );
+    $form['chatroom_smileys_support']['chatroom_smileys_enabled'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Enable Smileys module support.'),
+      '#default_value' => variable_get('chatroom_smileys_enabled', FALSE),
+    );
+    $form['chatroom_smileys_support']['chatroom_smileys_showtextentry'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Show smileys in text entry box.'),
+      '#default_value' => variable_get('chatroom_smileys_enabled', FALSE) && variable_get('chatroom_smileys_showtextentry', FALSE),
+      '#disabled' => !variable_get('chatroom_smileys_enabled', FALSE),
+    );
+  }
+  $form['chatroom_alerts'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Chat alerts'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+  );
+  $form['chatroom_alerts']['chatroom_alerts'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Enable chat alerts.'),
+    '#description' => t('Checking this box will allow users to turn on alerts for chat events.'),
+    '#default_value' => variable_get('chatroom_alerts', FALSE),
+  );
+  $form['chatroom_alerts']['chatroom_alerts_default'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Turn alerts on by default.'),
+    '#description' => t('Check this box if you want chats to open with alerts on.'),
+    '#default_value' => variable_get('chatroom_alerts', FALSE) && variable_get('chatroom_alerts_default', FALSE),
+    '#disabled' => !variable_get('chatroom_alerts', FALSE),
+  );
+  $form['chatroom_alerts']['chatroom_custom_sounds'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Use custom sounds for chat alerts.'),
+    '#description' => t('Check this box if you want to replace default chat alert sounds with your own MP3s.'),
+    '#default_value' => variable_get('chatroom_alerts', FALSE) && variable_get('chatroom_custom_sounds', FALSE),
+    '#disabled' => !variable_get('chatroom_alerts', FALSE),
+  );
+  $path = file_directory_path() .'/chatroom';
+  $js = array();
+  if (file_exists("$path/message.mp3")) {
+    $js['messageSound'] = file_create_url("$path/message.mp3");
+  }
+  if (file_exists("$path/user.mp3")) {
+    $js['userSound'] = file_create_url("$path/user.mp3");
+  }
+  $form['chatroom_alerts']['chatroom_message_alert_upload'] = array(
+    '#type' => 'file',
+    '#title' => t('Custom new message sound'),
+    '#description' => isset($js['messageSound']) ? t('Replace the custom new message sound with a new MP3. !Listen to current file.', array('!Listen' => l(t('Listen'), $js['messageSound'], array('attributes' => array('id' => 'sound_message'))))) : t('Replace the default new message sound with your own MP3.'),
+    '#size' => 30,
+  );
+  $form['chatroom_alerts']['chatroom_user_alert_upload'] = array(
+    '#type' => 'file',
+    '#title' => t('Custom new user sound'),
+    '#description' => isset($js['userSound']) ? t('Replace the custom new user sound with a new MP3. !Listen to current file.', array('!Listen' => l(t('Listen'), $js['userSound'], array('attributes' => array('id' => 'sound_user'))))) : t('Replace the default new user sound with your own MP3.'),
+    '#size' => 30,
+  );
+  if (!empty($js)) {
+    global $base_path;
+    $js['basePath'] = $base_path;
+    $js['chatroomBase'] = drupal_get_path('module', 'chatroom');
+    drupal_add_js(array('chatroom' => $js), 'setting');
+    drupal_add_js(drupal_get_path('module', 'chatroom') .'/soundmanager2.js');
+    drupal_add_js('$("#sound_message").attr("href", "javascript:Drupal.chatroom.soundManager.play(\'message\')")', 'inline', 'footer');
+    drupal_add_js('$("#sound_user").attr("href", "javascript:Drupal.chatroom.soundManager.play(\'user\')")', 'inline', 'footer');
+  }
+  $form['#attributes'] = array('enctype' => 'multipart/form-data');
+  return system_settings_form($form);
+}
+
+/**
+ * Validate site-wide chat room settings.
+ */
+function chatroom_admin_settings_validate($form, &$form_state) {
+  // Check for a valid update interval.
+  $interval = $form_state['values']['chatroom_block_update_interval'];
+  if (is_numeric($interval)) {
+    if ($interval == 0) {
+      form_set_error('chatroom_block_update_interval', t('The block update interval cannot be zero.'));
+    }
+    elseif ($interval < 0) {
+      form_set_error('chatroom_block_update_interval', t('The block update interval cannot be negative.'));
+    }
+  }
+  else {
+    form_set_error('chatroom_block_update_interval', t('The block update interval must be a number.'));
+  }
+}
+
+/**
+ * Validate that a file is MP3 audio
+ *
+ * Cribbed largely from file_validate_extensions().
+ */
+function file_validate_mimetype($file, $types) {
+  $errors = array();
+  $regex = '/^('. ereg_replace(' +', '|', preg_quote($types)) .')$/i';
+  if (!preg_match($regex, $file->filemime)) {
+    $errors[] = t('Only files with the following MIME types are allowed: %types-allowed.', array('%types-allowed' => $types));
+  }
+  return $errors;
+}
+
+/**
+ * Save files from site-wide chatroom settings form.
+ */
+function chatroom_admin_settings_submit($form, &$form_state) {
+  // Check uploaded files are MP3s.
+  $path = file_directory_path() .'/chatroom';
+  if (file_check_directory(file_directory_path(), FILE_CREATE_DIRECTORY)
+    && file_check_directory($path, FILE_CREATE_DIRECTORY)) {
+    $validators = array(
+      'file_validate_mimetype' => array('audio/mpeg'),
+    );
+    file_save_upload('chatroom_user_alert_upload', $validators, "$path/user.mp3", FILE_EXISTS_REPLACE);
+    file_save_upload('chatroom_message_alert_upload', $validators, "$path/message.mp3", FILE_EXISTS_REPLACE);
+  }
+  system_settings_form_submit($form, &$form_state);
+}
+
+/**
+ * Return the chat interface.
+ */
+function chatroom_chat_form(&$form_state, $chat) {
+  // The message board itself.
+  chatroom_load_users($chat);
+  $form['board']['#value'] = theme('chatroom_user_list', $chat->users);
+  $form['board']['#value'] .= '<div id="chatroom-board">';
+  // Messages for initial display.
+  if (!$form_state['submitted']) {
+    $chat->last_cmid = chatroom_load_messages($chat, 0, TRUE, TRUE);
+    $form['board']['#value'] .= theme('chatroom_messages', $chat->messages);
+  }
+  $form['board']['#value'] .= '</div>';
+  // Control data for the form
+  $form['control'] = array(
+    '#type' => 'fieldset',
+    '#attributes' => array('id' => 'edit-control-fieldset'),
+  );
+  $data = array('nid', 'ccid', 'last_cmid');
+  foreach ($data as $key) {
+    $form['control'][$key] = array(
+      '#type' => 'hidden',
+      '#value' => $chat->{$key},
+    );
+  }
+  $form['control']['control'] = array('#type' => 'hidden');
+  // The actual submit button. Not displayed so the user isn't troubled with a
+  // whole bunch of throbbers.
+  $form['control']['submit'] = array(
+    '#type' => 'submit',
+    '#name' => 'submit',
+    '#ahah' => array(
+      'path' => 'chatroom/js',
+      'wrapper' => 'chatroom-board',
+      'method' => 'append',
+    ),
+  );
+  // Other form elements.
+  $form['message'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Type your message'),
+    '#size' => 50,
+  );
+  $form['away'] = array(
+     '#type' => 'checkbox',
+     '#title' => t('Show me as away.'),
+     '#name' => 'away',
+  );
+  if (variable_get('chatroom_alerts', FALSE)) {
+    $checked = variable_get('chatroom_alerts_default', FALSE) ? ' checked' : '';
+    $form['alerts'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Alert me if new messages are received.'),
+      '#name' => 'alert',
+    );
+  }
+  return $form;
+}
+
+/**
+ * Return a create chat form.
+ */
+function chatroom_create_chat_form(&$form_state, $room) {
+  $form['chatroom_create_chat'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Create a new chat'),
+    '#collapsible' => TRUE,
+    '#collapsed' => FALSE,
+  );
+  $form['chatroom_create_chat']['chat_name'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Chat name'),
+    '#size' => 30,
+    '#required' => TRUE,
+    '#description' => t('Enter the name for the chat'),
+  );
+  $form['chatroom_create_chat']['nid'] = array(
+    '#type' => 'hidden',
+    '#value' => $room->nid,
+  );
+  $form['chatroom_create_chat']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Create chat'),
+  );
+  $form['#validate'][] = 'chatroom_create_chat_form_validate';
+  $form['#submit'][] = 'chatroom_create_chat_form_submit';
+  return $form;
+}
+
+/**
+ * Validate attempt to create chat - check if name is already in use.
+ */
+function chatroom_create_chat_form_validate($form, &$form_state) {
+  $nid = $form_state['values']['nid'];
+  $name = $form_state['values']['chat_name'];
+  $sql = "SELECT COUNT(ccid) FROM {chatroom_chat} WHERE crid = %d AND chatname = '%s' AND when_archived IS NULL";
+  if (db_result(db_query_range($sql, $nid, $name, 0, 1))) {
+    form_set_error('chatroom_chat_name', t('A chat called %name already exists.', array('%name' => $name)));
+  }
+}
+
+/**
+ * Create a chat.
+ */
+function chatroom_create_chat_form_submit($form, &$form_state) {
+  global $user;
+  $created = db_query("
+    INSERT INTO {chatroom_chat} (crid, uid, chatname, modified)
+    VALUES (%d, %d, '%s', %d)
+  ", $form_state['values']['nid'], $user->uid, $form_state['values']['chat_name'], time());
+  if (!$created) {
+    drupal_set_message(t("There was an error creating your chat"), 'error');
+  }
+  else {
+    cache_clear_all(TRUE, 'cache_block');
+  }
+}
+
+/**
+ * Form to delete an archived chat.
+ *
+ * @param integer $chat_id
+ * @return array
+ */
+function chatroom_delete_chat_form($ccid = NULL) {
+  $form = array();
+  $form['ccid'] = array(
+    '#type' => 'value',
+    '#value' => $chat_id,
+  );
+  $form['delete'] = array(
+    '#type' => 'submit',
+    '#value' => t('Delete this chat'),
+  );
+  return $form;
+}
+
+/**
+ * Delete an archived chat
+ *
+ * @param array $form_id
+ * @param array $form_values
+ * @return string
+ */
+function chatroom_delete_chat_form_submit($form, &$form_state) {
+  $chat = chatroom_chat_get_from_id($form_state['values']['chat_id']);
+  chatroom_chat_delete($form_state['values']['chat_id']);
+  $form_state['redirect'] = "node/$chat->nid";
+}
+
+/**
+ * form for archived chat view of open chat to archive that chat
+ *
+ * @param integer $chat_id
+ * @return array
+ */
+function chatroom_archive_chat_form($chat_id) {
+  $form['chat_id'] = array(
+    '#type' => 'value',
+    '#value' => $chat_id,
+  );
+  $form['delete'] = array(
+    '#type' => 'submit',
+    '#value' => t('Archive this chat'),
+  );
+  return $form;
+}
+
+/**
+ * Archive an open chat.
+ *
+ * @param array $form_id
+ * @param array $form_values
+ * @return string
+ */
+function chatroom_form_chat_archive_submit($form, &$form_state) {
+  chatroom_archive_chat($form_state['values']['chat_id']);
+  file_delete(_chatroom_get_cache_file('chat.'. $form_state['values']['chat_id']));
+  db_query("
+    INSERT INTO {chatroom_msg_archive} (cmid, ccid, uid, msg_type, msg, session_id, recipient, modified)
+    SELECT * FROM {chatroom_msg} WHERE ccid = %d
+  ", $form_state['values']['chat_id']);
+  $chat = chatroom_chat_get_from_id($form_state['values']['chat_id']);
+  cache_clear_all(TRUE, 'cache_block');
+}
diff -Naur chatroom/chatroom.info chatroom-6.x-dev/chatroom.info
--- chatroom/chatroom.info	2007-10-12 09:10:02.000000000 -0400
+++ chatroom-6.x-dev/chatroom.info	2008-10-18 21:14:27.000000000 -0400
@@ -1,9 +1,5 @@
-; $Id: chatroom.info,v 1.1.2.4 2007/06/28 12:09:20 darrenoh Exp $
+; $Id$
 name = Chat Room
 description = Enables the creation of rooms that provide access to chats and chat archives.
-
-; Information added by drupal.org packaging script on 2007-10-12
-version = "5.x-1.9"
-project = "chatroom"
-datestamp = "1192194602"
+core = 6.x
 
diff -Naur chatroom/chatroom.install chatroom-6.x-dev/chatroom.install
--- chatroom/chatroom.install	2007-10-11 11:28:29.000000000 -0400
+++ chatroom-6.x-dev/chatroom.install	2008-10-18 21:14:27.000000000 -0400
@@ -1,9 +1,225 @@
 <?php
-// $Id: chatroom.install,v 1.23.2.17 2007/10/11 15:28:29 darrenoh Exp $
+// $Id$
+
+/**
+ * @file
+ * Install chatroom module
+ */
+
+/**
+ * Implementation of hook_schema().
+ */
+function chatroom_schema() {
+  $schema['chatroom'] = array(
+    'fields' => array(
+      'nid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'description' => t('Primary key: node ID of the chatroom.'),
+      ),
+      'poll_freq' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 3000,
+        'description' => t('Polling interval, in milliseconds, of the chat.'),
+      ),
+      'idle_freq' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 60000,
+        'description' => t('Idle interval, in milliseconds, of the chat.'),
+      ),
+      'kicked_out_message' => array(
+        'type' => 'text',
+        'size' => 'big',
+        'description' => t('Message sent to users kicked from the chat.'),
+      ),
+      'banned_message' => array(
+        'type' => 'text',
+        'size' => 'big',
+        'description' => t('Message sent to users banned from the chat.'),
+      ),
+      'module' => array(
+        'type' => 'varchar',
+        'length' => '255',
+        'default' => 'chatroom',
+        'description' => t('Unknown.'),
+      ),
+      'auto_archive' => array(
+        'type' => 'int',
+        'default' => 0,
+        'description' => t('Unknown.'),
+      ),
+      'old_msg_count' => array(
+        'type' => 'int',
+        'default' => 20,
+        'description' => t('Unknown. Number of old messages?'),
+      ),
+    ),
+    'primary key' => array('nid'),
+  );
+  $schema['chatroom_ban_list'] = array(
+    'fields' => array(
+      'nid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'description' => t('Chatroom ID.'),
+      ),
+      'uid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'description' => t('ID of the banned user.'),
+      ),
+      'admin_uid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'description' => t('ID of the chatroom admin who imposed the ban.'),
+      ),
+      'modified' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('UNIX timestamp of when the ban was imposed.'),
+      ),
+    ),
+    'primary key' => array('admin_uid', 'uid'),
+  );
+  $schema['chatroom_chat'] = array(
+    'fields' => array(
+      'ccid' => array(
+        'type' => 'serial',
+        'not null' => TRUE,
+        'description' => t('Primary key: ID of the chat.'),
+      ),
+      'crid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'description' => t('ID of the chatroom the chat belongs to.'),
+      ),
+      'uid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'description' => t('ID of the user who created the chat.'),
+      ),
+      'chatname' => array(
+        'type' => 'varchar',
+        'length' => '255',
+        'description' => t('Name of the chat.'),
+      ),
+      'modified' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('UNIX timestamp of when the chat was last modified.'),
+      ),
+      'when_archived' => array(
+        'type' => 'int',
+        'description' => t('UNIX timestamp of when the chat was last archived.'),
+      ),
+    ),
+    'indexes' => array(
+      'crid' => array('crid'),
+      'modified' => array('modified'),
+    ),
+    'primary key' => array('ccid'),
+  );
+  $schema['chatroom_msg'] = array(
+    'fields' => array(
+      'cmid' => array(
+        'type' => 'serial',
+        'not null' => TRUE,
+      ),
+      'ccid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+      ),
+      'uid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+      ),
+      'msg_type' => array(
+        'type' => 'varchar',
+        'length' => '64',
+        'not null' => TRUE,
+      ),
+      'msg' => array(
+        'type' => 'text',
+        'size' => 'big',
+      ),
+      'sid' => array(
+        'type' => 'varchar',
+        'length' => '64',
+        'not null' => TRUE,
+      ),
+      'recipient' => array(
+        'type' => 'varchar',
+        'length' => '255',
+        'not null' => TRUE,
+      ),
+      'archived' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'modified' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+    ),
+    'indexes' => array(
+      'ccid' => array('ccid'),
+      'modified' => array('modified'),
+      'recipient' => array('recipient'),
+      'sid' => array('sid')
+    ),
+    'primary key' => array('cmid'),
+  );
+  $schema['chatroom_online_list'] = array(
+    'fields' => array(
+      'ccid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'description' => t('Chat ID.'),
+      ),
+      'sid' => array(
+        'type' => 'varchar',
+        'length' => '64',
+        'not null' => TRUE,
+        'description' => t('Session ID.'),
+      ),
+      'guest_id' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 1,
+        'description' => t('Unknown.'),
+      ),
+      'away' => array(
+        'type' => 'int',
+        'default' => 0,
+        'description' => t('Boolean: whether the user is away.'),
+      ),
+      'is_admin' => array(
+        'type' => 'int',
+        'default' => 0,
+        'description' => t('Boolean: whether the user is an admin.'),
+      ),
+      'modified' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('UNIX timestamp of when the user was last seen.'),
+      ),
+    ),
+    'primary key' => array('ccid', 'sid'),
+  );
+  return $schema;
+}
 
 /**
  * Implementation of hook_requirements().
  */
+/*
 function chatroom_requirements($phase) {
   $requirements = array();
   if ($GLOBALS['db_type'] == 'mysql' && version_compare(db_version(), '4.1') < 0) {
@@ -15,195 +231,28 @@
     );
   }
   return $requirements;
-}
+}*/
 
 /**
- * Implementation of hook_install()
+ * Implementation of hook_install().
  */
 function chatroom_install() {
-  $t = get_t();
-  switch ($GLOBALS['db_type']) {
-    case 'mysql':
-    case 'mysqli':
-      // check to see if we have mysql 4.1. if we do, then we want to
-      // set the default charset to utf8. snippet taken from db_connect()
-      // in includes/database.mysql.inc and utf8 declaration taken from
-      // database/database.4.1.mysql
-      $utf_declaration = '';
-      if (version_compare(mysql_get_server_info(), '4.1.0', '>=')) {
-        $utf_declaration = 'DEFAULT CHARACTER SET utf8';
-      }
-      global $active_db;
-      $ok = db_query("CREATE TABLE {chatroom} (
-        crid int(11) NOT NULL AUTO_INCREMENT,
-        nid int(11) NOT NULL,
-        poll_freq int(2) NOT NULL default '3000',
-        idle_freq int(3) NOT NULL default '60000',
-        kicked_out_message longtext,
-        banned_message longtext,
-        module varchar(255) default 'chatroom',
-        auto_archive int(1) default '0',
-        old_msg_count int(3) default '20',
-        modified int(11) NOT NULL default '0',
-        PRIMARY KEY (crid),
-        KEY nid (nid)
-      ) $utf_declaration;");
-      $ok = $ok && db_query("CREATE TABLE {chatroom_chat} (
-        ccid int(11) NOT NULL AUTO_INCREMENT,
-        crid int(11) NOT NULL,
-        uid int(11) NOT NULL,
-        chatname varchar(255) NOT NULL,
-        modified int(11) NOT NULL default '0',
-        when_archived int(11) default NULL,
-        PRIMARY KEY  (ccid),
-        KEY crid (crid),
-        KEY modified (modified)
-      ) $utf_declaration;");
-      $ok = $ok && db_query("CREATE TABLE {chatroom_msg} (
-        cmid int(11) NOT NULL AUTO_INCREMENT,
-        ccid int(11) NOT NULL,
-        uid int(11) NOT NULL,
-        msg_type varchar(64) NOT NULL,
-        msg longtext NOT NULL,
-        session_id varchar(255) NOT NULL,
-        recipient varchar(255) NOT NULL,
-        modified int(11) NOT NULL default '0',
-        PRIMARY KEY  (cmid),
-        KEY ccid (ccid),
-        KEY session_id (session_id),
-        KEY recipient (recipient),
-        KEY modified (modified)
-      ) $utf_declaration;");
-      $ok = $ok && db_query("CREATE TABLE {chatroom_msg_archive} (
-        cmid int(11) NOT NULL,
-        ccid int(11) NOT NULL,
-        uid int(11) NOT NULL,
-        msg_type varchar(64) NOT NULL,
-        msg varchar(512) NOT NULL,
-        session_id varchar(255) NOT NULL,
-        recipient varchar(255) NOT NULL,
-        modified int(11) NOT NULL default '0',
-        PRIMARY KEY  (cmid),
-        KEY ccid (ccid),
-        KEY session_id (session_id),
-        KEY recipient (recipient),
-        KEY modified (modified)
-      ) $utf_declaration;");
-      $ok = $ok && db_query("CREATE TABLE {chatroom_ban_list} (
-        crid int(11) NOT NULL AUTO_INCREMENT,
-        uid int(11) NOT NULL,
-        admin_uid int(11) NOT NULL,
-        modified int(11) NOT NULL default '0',
-        KEY crid_uid (crid,uid)
-      ) $utf_declaration;");
-      $ok = $ok && db_query("CREATE TABLE {chatroom_online_list} (
-        coid int(11) NOT NULL AUTO_INCREMENT,
-        ccid int(11) NOT NULL,
-        uid int(11) NOT NULL,
-        session_id varchar(255) NOT NULL,
-        guest_id int(11) NOT NULL default '1',
-        away int(1) default '0',
-        is_admin int(1) default '0',
-        modified int(11) NOT NULL default '0',
-        PRIMARY KEY  (coid),
-        KEY update_time (ccid,uid,session_id)
-      ) $utf_declaration;");
-      break;
-    case 'pgsql':
-      $ok = db_query("CREATE TABLE {chatroom} (
-        crid SERIAL NOT NULL,
-        nid INTEGER NOT NULL,
-        poll_freq INTEGER NOT NULL default 3000,
-        idle_freq INTEGER NOT NULL default 60000,
-        kicked_out_message TEXT,
-        banned_message TEXT,
-        module VARCHAR default 'chatroom',
-        auto_archive INTEGER default 0,
-        old_msg_count INTEGER default 20,
-        modified INTEGER NOT NULL default '0',
-        PRIMARY KEY (crid)
-      );");
-      $ok = $ok && db_query("CREATE TABLE {chatroom_chat} (
-        ccid SERIAL NOT NULL,
-        crid INTEGER NOT NULL,
-        uid INTEGER NOT NULL,
-        chatname VARCHAR NOT NULL,
-        modified INTEGER NOT NULL default 0,
-        when_archived INTEGER default NULL,
-        PRIMARY KEY  (ccid)
-      );");
-      $ok = $ok && db_query("CREATE TABLE {chatroom_msg} (
-        cmid SERIAL NOT NULL,
-        ccid INTEGER NOT NULL,
-        uid INTEGER NOT NULL,
-        msg_type VARCHAR NOT NULL,
-        msg TEXT NOT NULL,
-        session_id VARCHAR NOT NULL,
-        recipient VARCHAR NOT NULL,
-        modified INTEGER NOT NULL default 0,
-        PRIMARY KEY  (cmid)
-      );");
-      $ok = $ok && db_query("CREATE TABLE {chatroom_msg_archive} (
-        cmid SERIAL NOT NULL,
-        ccid INTEGER NOT NULL,
-        uid INTEGER NOT NULL,
-        msg_type VARCHAR NOT NULL,
-        msg TEXT NOT NULL,
-        session_id VARCHAR NOT NULL,
-        recipient VARCHAR NOT NULL,
-        modified INTEGER NOT NULL default '0',
-        PRIMARY KEY  (cmid)
-      );");
-      $ok = $ok && db_query("CREATE TABLE {chatroom_ban_list} (
-        crid SERIAL NOT NULL,
-        uid INTEGER NOT NULL,
-        admin_uid INTEGER NOT NULL,
-        modified INTEGER NOT NULL default '0',
-        PRIMARY KEY (crid,uid)
-      ) $utf_declaration;");
-      $ok = $ok && db_query("CREATE TABLE {chatroom_online_list} (
-        coid SERIAL NOT NULL,
-        ccid INTEGER NOT NULL,
-        uid INTEGER NOT NULL,
-        session_id VARCHAR NOT NULL,
-        guest_id INTEGER NOT NULL default '1',
-        away INTEGER default '0',
-        is_admin INTEGER default '0',
-        modified INTEGER NOT NULL default '0',
-        PRIMARY KEY  (coid)
-      );");
-      break;
-  }
-  $error = FALSE;
-  if (!$ok) {
-    $error = $t('A database operation failed when setting up Chat Room.');
-    db_query('DROP TABLE {chatroom}');
-    db_query('DROP TABLE {chatroom_chat}');
-    db_query('DROP TABLE {chatroom_msg}');
-    db_query('DROP TABLE {chatroom_msg_archive}');
-    db_query('DROP TABLE {chatroom_ban_list}');
-    db_query('DROP TABLE {chatroom_online_list}');
-  }
-  if ($error) {
-    $error .= $t('Please fix these errors and attempt to enable Chat Room again.');
-    drupal_set_message($error, 'error');
-    db_query("UPDATE {system} SET status = 0 WHERE name = 'chatroom'");
-  }
-  else {
-    global $user;
-    $themes = list_themes();
-    $theme = $user->theme && $themes[$user->theme]->status ? $user->theme : variable_get('theme_default', 'garland');
-    db_query("INSERT INTO {blocks} (status, weight, region, throttle, module, delta, theme) VALUES (1, -10, 'left', 0, 'chatroom', '2', '%s')", $theme);
-    variable_set('comment_chatroom', variable_get('comment_chatroom', COMMENT_NODE_DISABLED));
-    variable_set('chatroom_guest_user_prefix', $t('guest-'));
-    drupal_set_message($t('Chat Room is ready to go.'));
+  drupal_install_schema('chatroom');
+
+  if (function_exists('block_rehash')) {
+    _block_rehash();
   }
+
+  drupal_set_message(t('Chat room module enabled.'));
 }
 
 /**
  * Implementation of hook_disable().
  */
 function chatroom_disable() {
+
+  // Empty cache
+/*
   $path = file_directory_temp();
   if ($dir = @opendir($path .'/drupal.chatroom.'. session_name())) {
     $path .= '/drupal.chatroom.'. session_name();
@@ -222,7 +271,7 @@
       }
     }
     closedir($dir);
-  }
+  }*/
 }
 
 /**
@@ -234,12 +283,10 @@
   while ($room = db_fetch_object($rooms)) {
     node_delete($room->nid);
   }
-  db_query('DROP TABLE {chatroom}');
-  db_query('DROP TABLE {chatroom_chat}');
-  db_query('DROP TABLE {chatroom_msg}');
-  db_query('DROP TABLE {chatroom_msg_archive}');
-  db_query('DROP TABLE {chatroom_ban_list}');
-  db_query('DROP TABLE {chatroom_online_list}');
+
+  // Schema api
+  drupal_uninstall_schema('chatroom');
+
   drupal_set_message($t('Chat room tables have been dropped from the database.'));
   db_query("DELETE FROM {blocks} WHERE module = 'chatroom'");
   $settings = db_query("SELECT name FROM {variable} WHERE name LIKE 'chatroom\_%'");
@@ -303,12 +350,12 @@
   $nids = db_query("SELECT nid FROM {node} WHERE type = '%s'", 'chatroom');
   $row = 0;
   while ($nid = db_result($nids, $row++)) {
-    if (db_num_rows(db_query("SELECT * FROM {chatroom} WHERE nid = %d", $nid)) == 0) {
+    if (db_result(db_query("SELECT COUNT(*) FROM {chatroom} WHERE nid = %d", $nid)) == 0) {
       $ret[] = update_sql("INSERT INTO {chatroom} SET nid = %d", $nid);
     }
   }
   $ret[] = update_sql("DELETE FROM {chatroom_chat} WHERE crid = %d", 0);
-  $ret[] = update_sql('DROP TABLE {chatroom_chat_invites}');
+  db_drop_table($ret, 'chatroom_chat_invites');
   return $ret;
 }
 
@@ -326,12 +373,9 @@
 
 function chatroom_update_4() {
   $ret = array();
-  switch ($GLOBALS['db_type']) {
-    case 'mysql':
-    case 'mysqli':
-      $ret[] = update_sql('ALTER TABLE {chatroom_msg} CHANGE msg msg varchar(512) NOT NULL');
-      break;
-  }
+  db_change_field($ret, 'chatroom_msg', 'msg', 'msg', array(
+        'type' => 'text', 'size' => 'big',
+        'not null' => TRUE));
   return $ret;
 }
 
@@ -349,3 +393,7 @@
   return array();
 }
 
+function chatroom_update_6200() {
+  // TODO: schema updates from #5
+  return array();
+}
diff -Naur chatroom/chatroom.js chatroom-6.x-dev/chatroom.js
--- chatroom/chatroom.js	2007-10-10 11:07:05.000000000 -0400
+++ chatroom-6.x-dev/chatroom.js	2008-10-18 21:14:27.000000000 -0400
@@ -1,9 +1,223 @@
-// $Id: chatroom.js,v 1.48.2.33 2007/10/10 15:07:05 darrenoh Exp $
+// $Id$
+
+/**
+ * Client-side functionality for chatroom.module.
+ *
+ * Information about the chat is stored in one of three ways, depending on its
+ * use:
+ *  * Values that are useful to chatroom_js() in processing AHAH requests (ccid
+ *    of the chat; nid of the parent chatroom, cmid of the latest message) are
+ *    stored as form elements so that they are automatically submitted with any
+ *    AHAH request.
+ *  * Values that are static and only affect the client-side behaviour (e.g.
+ *    update and polling intervals) are stored in Drupal.settings.chatroom. See
+ *    the PHP function chatroom_add_js().
+ *  * Actual display content is generated in functions called by
+ *    theme('chatroom_chat') (on page load) and chatroom_js() (on updates).
+ *    These are enclosed in <div>s with id attributes that trigger functions in
+ *    Drupal.behaviors.
+ *
+ * TODO: smileys interface
+ */
 
 Drupal.chatroom = Drupal.chatroom || {};
 Drupal.chatroom.chat = Drupal.chatroom.chat || {};
 
 /**
+ * Initialize the chat.
+ *
+ * The chat form is given the class 'chatroom-processed' when this behaviour
+ * runs on page load, so that it is not triggered by AHAH requests.
+ */
+Drupal.behaviors.chatroom = function(context) {
+  var retval = $('#chatroom-chat-form:not(.chatroom-processed)', context).addClass(
+    'chatroom-processed').each(function() {
+    // Alerts.
+//    if ($('#edit-alerts')) {
+//      soundManager.url = Drupal.settings.chatroom.sm2URL;
+//      soundManager.waitForWindowLoad = true;
+//      soundManager.onload = function() {
+//        soundManager.createSound({
+//         id: 'message',
+//         url: Drupal.settings.chatroom.messageSound,
+//         autoLoad: true,
+//         autoPlay: false
+//        });
+//        soundManager.createSound({
+//         id: 'user',
+//         url: Drupal.settings.chatroom.userSound,
+//         autoLoad: true,
+//         autoPlay: false
+//        });
+//      }
+//    }
+    // Periodic updates.
+    Drupal.chatroom.poll = setInterval("poll()", Drupal.settings.chatroom.pollInterval);
+    idleReset();
+    // Do not let the form submit unless some JavaScript code has set a control action.
+    $(this).submit(function() {
+      return $('#edit-control').html() != '';
+    });
+    // Actions that send a message.
+    $('#edit-message', context).keypress(function(event) {
+      if (event.keyCode == 13) {
+        sendMessage();
+      }
+    });
+    // Set a user as away.
+    $('#edit-away', context).change(sendStatus);
+    // Scroll to bottom of initial messages.
+    var boardOffset = $('#chatroom-board').offset().top;
+//    var targetOffset = $('div.chatroom-msg:last').offset().top;
+ //   var scrollAmount = targetOffset - boardOffset;
+ //   $('#chatroom-board').animate({scrollTop: '+='+ scrollAmount +'px'}, 500);
+  });
+  return true;
+};
+
+/**
+ * Control.
+ *
+ * Updates <input type="hidden" id="edit-control" value="..."/> from
+ * <div id="response-control">...</a>. The PHP function control_js() *always*
+ * includes this element, so this behaviour is triggered on every request.
+ */
+Drupal.behaviors.chatroomControl = function(context) {
+  var retval = $('#response-control', context).each(function() {
+    $('#edit-control').val($(this).html());
+    // Clean up any empty <div>s in the board (wrappers for previous requests).
+    $('#chatroom-board div:empty').remove();
+  }).remove();
+  return true;
+};
+
+/**
+ * Handle messages in AHAH response.
+ *
+ * Pulls new messages from <div id="response-messages">...</a>. See
+ * http://www.learningjquery.com/2007/09/animated-scrolling-with-jquery-12 ,
+ * specifically http://www.learningjquery.com/js/scroll-element.js
+ */
+Drupal.behaviors.chatroomMessages = function(context) {
+  var retval = $('#response-messages', context).each(function() {
+    // Only process if there's some actual content.
+    if ($(this).html().length > 0) {
+      // Tag the first new message and append to the display.
+      $(this).children('.chatroom-msg:first').addClass('new-message');
+      $('#chatroom-board').append($(this).html());
+      // Scroll to first new message
+      var boardOffset = $('#chatroom-board').offset().top;
+      var targetOffset = $('div.new-message:last').offset().top;
+      var scrollAmount = targetOffset - boardOffset;
+      $('#chatroom-board').animate({scrollTop: '+='+ scrollAmount +'px'}, 500);
+      $('.new-message').removeClass('new-message');
+      // Play an alert
+      if ($('#edit-alerts:checked')) {
+//        soundManager.play('message');
+      }
+    }
+   var afterProc = true;
+  }).remove();
+  return true;
+};
+
+/**
+ * Latest message ID.
+ *
+ * Updates <input type="hidden" id="edit-last-cmid" value="..."/> from
+ * <div id="response-last-cmid">...</a>. This is sent with each AHAH request so
+ * messages are not fetched more than once.
+ */
+Drupal.behaviors.chatroomLastCmid = function(context) {
+  retval = $('#response-last-cmid', context).each(function() {
+    $('#edit-last-cmid').val($(this).html());
+  }).remove();
+  return true;
+};
+
+/**
+ * Userlist
+ */
+Drupal.behaviors.chatroomUserList = function(context) {
+  retval = $('#response-users', context).each(function() {
+    $('#chatroom-user-list:first').replaceWith($(this).html());
+  }).remove();
+  return true;
+};
+
+/**
+ * Poll for new information from the server.
+ */
+function poll() {
+  // Don't poll while away
+  if ($('#edit-away:checked').length == 0) {
+    $('#edit-control').val('poll');
+    // The AHAH-bound event for the submit button is 'mousedown', not 'click'.
+    // See http://api.drupal.org/api/function/form_expand_ahah/6 . Don't poll
+    // if a previous AHAH request is in progress.
+    $('#edit-submit:not(.progress-disabled)').triggerHandler('mousedown');
+  }
+}
+
+/**
+ * Idle the user if no action is performed for a while.
+ */
+function idle() {
+  $('#edit-away').checked = true;
+}
+
+/**
+ * Return the user from away and set the idle timer.
+ */
+function idleReset() {
+  $('#edit-away:checked').each(function() {
+    this.checked = false;
+  });
+  if (Drupal.chatroom.idle) {
+    clearInterval(Drupal.chatroom.idle);
+  }
+  Drupal.chatroom.idle = setInterval("idle()", Drupal.settings.chatroom.idleInterval);
+}
+
+/**
+ * Trigger the actual AHAH request when the 'Send' button is pressed.
+ */
+function sendMessage() {
+  if ($('#edit-submit.progress-disabled').length > 0) {
+    // Wait for an ongoing AHAH request to complete.
+    setTimeout("sendMessage()", 10);
+  }
+  else {
+    idleReset();
+    // Send the message.
+    $('#edit-control').val('message');
+    $('#edit-submit').triggerHandler('mousedown');
+    // Clear the send box.
+    $('#edit-message').val('');
+
+  }
+  // Prevent the event from bubbling.
+  return false;
+}
+
+/**
+ *
+ */
+function sendStatus(signal) {
+  if ($('#edit-submit.progress-disabled').length > 0) {
+    // Wait for an ongoing AHAH request to complete.
+    setTimeout("sendStatus()", 10);
+  }
+  else {
+    // Send the control message.
+    $('#edit-control').val('status');
+    $('#edit-submit').triggerHandler('mousedown');
+  }
+  // Prevent the event from bubbling.
+  return false;
+}
+
+/**
  * function to add chatroom events handlers to the onscreen widgets
  */
 Drupal.chatroom.chat.addEvents = function() {
@@ -14,17 +228,22 @@
       return false;
     }
   );
-  $('#chatroom-msg-input').keyup(
-    function(e) {
-      Drupal.chatroom.chat.inputOnkeyup(this, e);
-      return true;
-    }
-  );
   $('#chatroom-msg-away').click(
     function() {
       Drupal.chatroom.chat.setAway(this);
     }
   );
+  $('#edit-submit').click(
+    function() {
+        alert('edit-submit pressed');
+        Drupal.chatroom.chat.sendMessage();
+        return false;
+    }
+  );
+  $('#edit-submit').bind('mousedown', function(event) {
+      alert('edit-submit mousedown');
+  });
+  
   for (var i = 0; i < Drupal.settings.chatroom.chatUsers.length; i++) {
     if (Drupal.settings.chatroom.chatUsers[i].self) {
       $('#chatroom-msg-away').attr('checked', Drupal.settings.chatroom.chatUsers[i].away);
@@ -47,93 +266,13 @@
   );
   Drupal.chatroom.chat.getUpdates();
   Drupal.chatroom.chat.updates = setInterval("Drupal.chatroom.chat.getUpdates()", Drupal.settings.chatroom.chatUpdateInterval);
-  return;
-}
 
-/**
- * handles response from msg HTTP requests
- */
-Drupal.chatroom.chat.msgCallback = function(responseText) {
-  if (responseText) {
-    var response = eval('('+ responseText +')');
-    if (typeof response == 'object') {
-      if (response.cacheTimestamp != undefined) {
-        Drupal.settings.chatroom.cacheTimestamp = response.cacheTimestamp;
-      }
-      if (response.msgs != undefined && response.msgs.length > 0) {
-        Drupal.chatroom.chat.updateMsgList(response.msgs);
-      }
-      if (response.chatUsers != undefined) {
-        Drupal.chatroom.chat.updateChatUsers(response.chatUsers);
-      }
-    }
-  }
   return;
 }
 
 /**
- * function to handle input to the message board
- */
-Drupal.chatroom.chat.inputOnkeyup = function(input, e) {
-  if (!e) {
-    e = window.event;
-  }
-  switch (e.keyCode) {
-    case 13:  // return/enter
-      Drupal.chatroom.chat.sendMessage();
-      break;
-  }
-}
-
-/**
- * sends text of message to the server
- */
-Drupal.chatroom.chat.sendMessage = function() {
-  var text = $('#chatroom-msg-input').val();
-  if (text == '' || text.match(/^\s+$/)) {
-    $('#chatroom-msg-input').val('');
-    $('#chatroom-msg-input')[0].focus();
-    return;
-  }
-  $('#chatroom-msg-input').val('');
-  $('#chatroom-msg-input')[0].focus();
-  if (text.search(/^\/(me|away|msg|back|kick|close|ban)/) != -1) {
-    var msg = Drupal.chatroom.chat.getCommandMsg(text);
-    if (!msg) {
-      return;
-    }
-  }
-  else {
-    var msg = {chatroomMsg:text};
-  }
-  Drupal.settings.chatroom.skipUpdate = true;
-  Drupal.settings.chatroom.updateCount++;
-  $.post(Drupal.chatroom.chat.getUrl('write'), Drupal.chatroom.chat.prepareMsg(msg), Drupal.chatroom.chat.msgCallback);
-}
-
-/**
- * prepare a msg object
- */
-Drupal.chatroom.chat.prepareMsg = function(msg) {
-  if (Drupal.settings.chatroom.smileysBase) {
-    msg.smileys_base = Drupal.settings.chatroom.smileysBase;
-  }
-  msg.drupal_base = Drupal.settings.chatroom.drupalBase;
-  msg.base_url = Drupal.settings.chatroom.baseUrl;
-  msg.chatroom_base = Drupal.settings.chatroom.chatroomBase;
-  msg.update_count = Drupal.settings.chatroom.updateCount;
-  msg.user_base = Drupal.settings.chatroom.userBase;
-  msg.chat_id = Drupal.settings.chatroom.chatId;
-  msg.last_msg_id = Drupal.settings.chatroom.lastMsgId;
-  msg.timezone = Drupal.settings.chatroom.timezone;
-  msg.timestamp = Drupal.settings.chatroom.cacheTimestamp;
-  msg.chat_cache_file = Drupal.settings.chatroom.chatCacheFile;
-  return msg;
-}
-
-/**
  * get a command msg object
- */
+ *
 Drupal.chatroom.chat.getCommandMsg = function(text) {
   var msg = {type:text.replace(/^\//, '').split(' ')[0]};
   var args = text.replace(/^\//, '').split(' ').slice(1);
@@ -192,47 +331,9 @@
 }
 
 /**
- * Find a user by guest ID.
- */
-Drupal.chatroom.chat.findGuestId = function(guestId) {
-  for (var i = 0; i < Drupal.settings.chatroom.chatUsers.length; i++) {
-    if (Drupal.settings.chatroom.chatUsers[i].guestId == guestId) {
-      return Drupal.settings.chatroom.chatUsers[i];
-    }
-  }
-  return false;
-}
-
-/**
- * Find a user by user name.
- */
-Drupal.chatroom.chat.findName = function(name) {
-  for (var i = 0; i < Drupal.settings.chatroom.chatUsers.length; i++) {
-    if (Drupal.settings.chatroom.chatUsers[i].user == name) {
-      return Drupal.settings.chatroom.chatUsers[i];
-    }
-  }
-  return false;
-}
-
-/**
- * return the appropriate url
- */
-Drupal.chatroom.chat.getUrl = function(type) {
-  switch (type) {
-    case 'read':
-    case 'write':
-    case 'command':
-      return Drupal.settings.chatroom.updateUrl;
-    case 'user':
-      return Drupal.settings.chatroom.userUrl;
-  }
-}
-
-/**
  * updates message list with response from server
  * To do: Move msgs formating to php so things like using t() can be done
- */
+ *
 Drupal.chatroom.chat.updateMsgList = function(msgs) {
   var msgBoard = $('#chatroom-board');
   var scroll = false;
@@ -344,76 +445,8 @@
 }
 
 /**
- * set a user as back if they send a message
- */
-Drupal.chatroom.chat.setAsBack = function(user) {
-  for (var i = 0; i < Drupal.settings.chatroom.chatUsers.length; i++) {
-    if (Drupal.settings.chatroom.chatUsers[i].user == user) {
-      // update the online list
-      Drupal.settings.chatroom.chatUsers[i].away = 0;
-      var guestId = Drupal.settings.chatroom.chatUsers[i].guestId;
-
-      // update on screen display
-      var guestIdObj = $('#'+ guestId);
-      if (guestIdObj.length > 0) {
-        guestIdObj.removeClass('chatroom-user-away');
-      }
-
-      // make sure away box is unchecked
-      if (Drupal.settings.chatroom.chatUsers[i].self) {
-        $('#chatroom-msg-away').attr('checked', 0);
-      }
-      return;
-    }
-  }
-}
-
-/**
- * Process smileys in text and append text to node.
- */
-Drupal.chatroom.chat.addSmileys = function(node, text) {
-  if (Drupal.settings.chatroom.smileysBase == undefined || text.indexOf(Drupal.settings.chatroom.smileysMarker) == -1) {
-    node.append(text);
-  }
-  else {
-    var bits = text.split(Drupal.settings.chatroom.smileysMarker);
-    for (var i = 0; i < bits.length; i++) {
-      var bit = $('#'+ bits[i]);
-      if (bit.length > 0) {
-        var smiley = bit.clone();
-        smiley.removeAttr('title');
-        smiley.removeAttr('id');
-        bits[i] = smiley;
-      }
-      else {
-        bits[i] = $('<html>'+ bits[i] +'</html>');
-      }
-      node.append(bits[i]);
-    }
-  }
-  return node;
-}
-
-/**
- * Replace smileys markers with text smileys.
- */
-Drupal.chatroom.chat.removeSmileys = function(text) {
-  if (Drupal.settings.chatroom.smileysBase != undefined && text.indexOf(Drupal.settings.chatroom.smileysMarker) != -1) {
-    var bits = text.split(Drupal.settings.chatroom.smileysMarker);
-    for (var i = 0; i < bits.length; i++) {
-      var bit = $('#'+ bits[i]);
-      if (bit.length > 0) {
-        bits[i] = bit.attr('alt');
-      }
-    }
-    text = bits.join('');
-  }
-  return text;
-}
-
-/**
  * take a list of users and draw a whois online list
- */
+ *
 Drupal.chatroom.chat.updateChatUsers = function(chatUsers) {
   var chatUserList = $('#chatroom-online');
   if (chatUserList.length > 0) {
@@ -483,7 +516,7 @@
 
 /**
  * Select a user to send a private message.
- */
+ *
 Drupal.chatroom.chat.selectUser = function(userName) {
   var input = $('#chatroom-msg-input');
   input.val('/msg '+ userName + ' '+ input.val());
@@ -493,7 +526,7 @@
 
 /**
  * Write a system message to the chat.
- */
+ *
 Drupal.chatroom.chat.writeSystemMsg = function(msgText, type) {
   switch (type) {
     case 'join':
@@ -539,18 +572,8 @@
 }
 
 /**
- * function that controls sets/clears Drupal.chatroom.chat.writeTime()'s timeout
- */
-Drupal.chatroom.chat.setWriteTime = function() {
-  if (Drupal.settings.chatroom.writeMsgTimeId) {
-    clearTimeout(Drupal.settings.chatroom.writeMsgTimeId);
-  }
-  Drupal.settings.chatroom.writeMsgTimeId = setTimeout('Drupal.chatroom.chat.writeTime()', Drupal.settings.chatroom.idleInterval);
-}
-
-/**
  * function that writes a time to board when idle
- */
+ *
 Drupal.chatroom.chat.writeTime = function() {
   if (Drupal.settings.chatroom.lastMsgTime != undefined) {
     var msgBoard = $('#chatroom-board');
@@ -566,31 +589,8 @@
 }
 
 /**
- * gets updates from the server for this chat
- */
-Drupal.chatroom.chat.getUpdates = function() {
-  if (Drupal.settings.chatroom.skipUpdate) {
-    Drupal.settings.chatroom.skipUpdate = false;
-    return;
-  }
-  Drupal.settings.chatroom.updateCount++;
-  $.post(Drupal.chatroom.chat.getUrl('read'), Drupal.chatroom.chat.prepareMsg({}), Drupal.chatroom.chat.msgCallback);
-}
-
-/**
- * update last seen message id
- */
-Drupal.chatroom.chat.updateLastMsg = function(msgId) {
-  if (msgId > Drupal.settings.chatroom.lastMsgId) {
-    Drupal.settings.chatroom.lastMsgId = msgId;
-    return true;
-  }
-  return false;
-}
-
-/**
  * Get the colour for a user.
- */
+ *
 Drupal.chatroom.chat.getUserColour = function(user) {
   for (var i = 0; i < Drupal.settings.chatroom.chatUsers.length; i++) {
     if (Drupal.settings.chatroom.chatUsers[i].user == user) {
@@ -612,7 +612,7 @@
 
 /**
  * Set colours for users.
- */
+ *
 Drupal.chatroom.chat.setUserColours = function() {
   if ($('#chatroom-online').length > 0) {
     for (var i = 0; i < Drupal.settings.chatroom.chatUsers.length; i++) {
@@ -625,7 +625,7 @@
 
 /**
  * onclick smileys insertion
- */
+ *
 Drupal.chatroom.chat.smileyInsert = function(acronym) {
   var msgInput = $('#chatroom-msg-input');
   var text = msgInput.val();
@@ -636,7 +636,7 @@
 
 /**
  * toggle away status
- */
+ *
 Drupal.chatroom.chat.setAway = function(obj) {
   if (obj.checked) {
     var msg = Drupal.chatroom.chat.getCommandMsg('/away');
@@ -649,7 +649,7 @@
 
 /**
  * Toggle message alert status.
- */
+ *
 Drupal.chatroom.chat.setMsgAlerts = function(obj) {
   if ($(obj).attr('checked')) {
     Drupal.settings.chatroom.msgAlerts = true;
@@ -667,9 +667,6 @@
     document.title = Drupal.settings.chatroom.pageTitle;
   }
 }
+*/
 
-// Global Killswitch
-if (Drupal.jsEnabled) {
-  $(document).ready(Drupal.chatroom.chat.addEvents);
-}
 
diff -Naur chatroom/chatroom.module chatroom-6.x-dev/chatroom.module
--- chatroom/chatroom.module	2007-10-05 15:37:34.000000000 -0400
+++ chatroom-6.x-dev/chatroom.module	2008-10-18 21:14:27.000000000 -0400
@@ -1,5 +1,5 @@
 <?php
-// $Id: chatroom.module,v 1.59.2.67 2007/10/05 19:37:34 darrenoh Exp $
+// $Id$
 
 /**
  * @file
@@ -7,10 +7,18 @@
  */
 
 /**
+ * Implementation of hook_init().
+ */
+function chatroom_init() {
+  module_load_include('inc', 'chatroom', 'chatroom.forms');
+  module_load_include('inc', 'chatroom', 'chatroom.theme');
+}
+  
+/**
  * Implementation of hook_help().
  */
-function chatroom_help($section) {
-  switch ($section) {
+function chatroom_help($path, $arg) {
+  switch ($path) {
     case 'admin/help#chatroom':
       return '<p />';
   }
@@ -19,13 +27,13 @@
 /**
  * Implementation of hook_access().
  */
-function chatroom_access($op, $node) {
-  global $user;
+function chatroom_access($op, $node, $account) {
   if ($op == 'create') {
-    return user_access('create chat rooms');
+    return user_access('create chat rooms', $account);
   }
+
   if ($op == 'update' || $op == 'delete') {
-    if (user_access('edit own chat rooms') && ($user->uid == $node->uid)) {
+    if (user_access('edit own chat rooms', $account) && ($account->uid == $node->uid)) {
       return TRUE;
     }
   }
@@ -41,199 +49,51 @@
 /**
  * Implementation of hook_menu().
  */
-function chatroom_menu($may_cache) {
-  $items = array();
-  $items[] = array(
-    'path' => 'admin/settings/chatroom',
-    'callback' => 'drupal_get_form',
-    'callback arguments' => array('chatroom_admin_settings'),
-    'title' => t('Chat room'),
-    'description' => t('Configure chat rooms.'),
-    'access' => user_access('administer chat rooms')
-  );
-  $items[] = array(
-    'path' => 'chatrooms',
-    'callback' => 'chatroom_page',
-    'access' => user_access('access chat rooms'),
-    'title' => t('Chat rooms'),
-    'type' => MENU_SUGGESTED_ITEM
-  );
-  $items[] = array(
-    'path' => 'chatrooms/kicked',
-    'callback' => 'chatroom_chat_kicked_user',
-    'access' => user_access('access chat rooms'),
-    'type' => MENU_CALLBACK
-  );
-  $items[] = array(
-    'path' => 'chatrooms/chat',
-    'callback' => 'chatroom_chat',
-    'access' => user_access('access chat rooms'),
-    'type' => MENU_CALLBACK
-  );
-  $items[] = array(
-    'path' => 'chatrooms/archives',
-    'callback' => 'chatroom_chat_archive',
-    'access' => user_access('access chat rooms'),
-    'type' => MENU_CALLBACK
+function chatroom_menu() {
+  $items['admin/settings/chatroom'] = array(
+    'title' => 'Chat room',
+    'description' => t('Configure global settings for chat rooms and chats.'),
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('chatroom_admin_settings'),
+    'access arguments' => array('administer chat rooms'),
+  );
+  $items['chatroom'] = array(
+    'type' => MENU_SUGGESTED_ITEM,
+    'title' => 'Chat rooms',
+    'page callback' => 'chatroom_page',
+    'access arguments' => array('access chat rooms'),
+  );
+  $items['chatroom/kicked'] = array(
+    'type' => MENU_CALLBACK,
+    'page callback' => 'chatroom_chat_kicked_user',
+    'page arguments' => array(2),
+    'access arguments' => array('access chat rooms'),
+  );
+  $items['chatroom/archive/%'] = array(
+    'type' => MENU_CALLBACK,
+    'page callback' => 'chatroom_view_chat',
+    'page arguments' => array(2, TRUE),
+    'access arguments' => array('access chat rooms'),
+  );
+  $items['chatroom/chat/%'] = array(
+    'type' => MENU_CALLBACK,
+    'page callback' => 'chatroom_view_chat',
+    'page arguments' => array(2),
+    'access arguments' => array('access chat rooms'),
+  );
+  $items['chatroom/js'] = array(
+    'type' => MENU_CALLBACK,
+    'page callback' => 'chatroom_js',
+    'access arguments' => array('access chat rooms'),
   );
   return $items;
 }
 
 /**
- * Menu callback; display site-wide chat room settings.
+ * Implementation of hook_file_download().
  */
-function chatroom_admin_settings() {
-  $form['chatroom_auto_archive'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Automatically archive old messages.'),
-    '#description' => t('If there are a lot of old messages, archiving will improve chat performance.'),
-    '#default_value' => variable_get('chatroom_auto_archive', FALSE),
-  );
-  $form['chatroom_block_update_interval'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Chat room block update interval'),
-    '#default_value' => variable_get('chatroom_block_update_interval', 5),
-    '#description' => t('Determines how often blocks should update active chat rooms, active chats, and on-line users.'),
-    '#size' => 2,
-    '#validate' => array('_chatroom_element_numeric_unsigned' => array()),
-  );
-  $form['chatroom_guest_user_prefix'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Guest user prefix'),
-    '#description' => t('Prefixed to guest ID to provide user name for anonymous users.'),
-    '#default_value' => variable_get('chatroom_guest_user_prefix', t('guest-')),
-    '#size' => 20,
-  );
-  $form['chatroom_chat_date_format'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Chat date format'),
-    '#attributes' => array('class' => 'custom-format'),
-    '#default_value' => variable_get('chatroom_chat_date_format', '* \S\e\n\t \a\t G:i'),
-    '#description' => t('Format for system time messages in chats. See the <a href="@url">PHP manual</a> for available options. This format is currently set to display as <span>%date</span>.', array('@url' => 'http://php.net/manual/function.date.php', '%date' => format_date(time(), 'custom', variable_get('chatroom_chat_date_format', '* \S\e\n\t \a\t G:i')))),
-  );
-  if (function_exists('_smileys_list')) {
-    $form['chatroom_smileys_support'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Smileys module support'),
-      '#collapsible' => TRUE,
-      '#collapsed' => TRUE,
-    );
-    $form['chatroom_smileys_support']['chatroom_smileys_enabled'] = array(
-      '#type' => 'checkbox',
-      '#title' => t('Enable Smileys module support.'),
-      '#default_value' => variable_get('chatroom_smileys_enabled', FALSE),
-    );
-    $form['chatroom_smileys_support']['chatroom_smileys_showtextentry'] = array(
-      '#type' => 'checkbox',
-      '#title' => t('Show smileys in text entry box.'),
-      '#default_value' => variable_get('chatroom_smileys_enabled', FALSE) && variable_get('chatroom_smileys_showtextentry', FALSE),
-      '#disabled' => !variable_get('chatroom_smileys_enabled', FALSE),
-    );
-  }
-  $form['chatroom_alerts'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Chat alerts'),
-    '#collapsible' => TRUE,
-    '#collapsed' => TRUE,
-  );
-  $form['chatroom_alerts']['chatroom_alerts'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Enable chat alerts.'),
-    '#description' => t('Checking this box will allow users to turn on alerts for chat events.'),
-    '#default_value' => variable_get('chatroom_alerts', FALSE),
-  );
-  $form['chatroom_alerts']['chatroom_alerts_default'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Turn alerts on by default.'),
-    '#description' => t('Check this box if you want chats to open with alerts on.'),
-    '#default_value' => variable_get('chatroom_alerts', FALSE) && variable_get('chatroom_alerts_default', FALSE),
-    '#disabled' => !variable_get('chatroom_alerts', FALSE),
-  );
-  $form['chatroom_alerts']['chatroom_custom_sounds'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Use custom sounds for chat alerts.'),
-    '#description' => t('Check this box if you want to replace default chat alert sounds with your own MP3s.'),
-    '#default_value' => variable_get('chatroom_alerts', FALSE) && variable_get('chatroom_custom_sounds', FALSE),
-    '#disabled' => !variable_get('chatroom_alerts', FALSE),
-  );
-  $path = file_directory_path() .'/chatroom';
-  $js = array();
-  if (file_exists("$path/message.mp3")) {
-    $js['messageSound'] = file_create_url("$path/message.mp3");
-  }
-  if (file_exists("$path/user.mp3")) {
-    $js['userSound'] = file_create_url("$path/user.mp3");
-  }
-  $form['chatroom_alerts']['chatroom_message_alert_upload'] = array(
-    '#type' => 'file',
-    '#title' => t('Custom new message sound'),
-    '#description' => isset($js['messageSound']) ? t('Replace the custom new message sound with a new MP3. !Listen to current file.', array('!Listen' => l(t('Listen'), $js['messageSound'], array('id' => 'sound_message')))) : t('Replace the default new message sound with your own MP3.'),
-    '#size' => 30,
-  );
-  $form['chatroom_alerts']['chatroom_user_alert_upload'] = array(
-    '#type' => 'file',
-    '#title' => t('Custom new user sound'),
-    '#description' => isset($js['userSound']) ? t('Replace the custom new user sound with a new MP3. !Listen to current file.', array('!Listen' => l(t('Listen'), $js['userSound'], array('id' => 'sound_user')))) : t('Replace the default new user sound with your own MP3.'),
-    '#size' => 30,
-  );
-  if (!empty($js)) {
-    global $base_path;
-    $js['basePath'] = $base_path;
-    $js['chatroomBase'] = drupal_get_path('module', 'chatroom');
-    drupal_add_js(array('chatroom' => $js), 'setting');
-    drupal_add_js(drupal_get_path('module', 'chatroom') .'/soundmanager2.js');
-    drupal_add_js('$("#sound_message").attr("href", "javascript:Drupal.chatroom.soundManager.play(\'message\')")', 'inline', 'footer');
-    drupal_add_js('$("#sound_user").attr("href", "javascript:Drupal.chatroom.soundManager.play(\'user\')")', 'inline', 'footer');
-  }
-  $form['#attributes'] = array('enctype' => 'multipart/form-data');
-  return system_settings_form($form);
-}
-
-function chatroom_admin_settings_validate($form_id, $form_values) {
-  $path = file_directory_path();
-  if (file_check_directory($path, FILE_CREATE_DIRECTORY)) {
-    $path .= '/chatroom';
-    if (file_check_directory($path, FILE_CREATE_DIRECTORY)) {
-      if ($user_sound = file_check_upload('chatroom_user_alert_upload')) {
-        if ($user_sound->filemime != 'audio/mpeg') {
-          form_set_error('chatroom_alerts][chatroom_user_alert_upload', t('New user sound must be an MP3 file.'));
-        }
-      }
-      if ($message_sound = file_check_upload('chatroom_message_alert_upload')) {
-        if ($message_sound->filemime != 'audio/mpeg') {
-          form_set_error('chatroom_alerts][chatroom_message_alert_upload', t('New message sound must be an MP3 file.'));
-        }
-      }
-    }
-  }
-}
-
-function chatroom_admin_settings_submit($form_id, $form_values) {
-  $path = file_directory_path() .'/chatroom';
-  if (file_check_upload('chatroom_user_alert_upload')) {
-    file_save_upload('chatroom_user_alert_upload', "$path/user.mp3", FILE_EXISTS_REPLACE);
-  }
-  if (file_check_upload('chatroom_message_alert_upload')) {
-    file_save_upload('chatroom_message_alert_upload', "$path/message.mp3", FILE_EXISTS_REPLACE);
-  }
-  system_settings_form_submit($form_id, $form_values);
-}
-
-function _chatroom_element_numeric_unsigned($element) {
-  if (is_numeric($element['#value'])) {
-    if ($element['#value'] == 0) {
-      form_error($element, t('The block update interval cannot be zero.'));
-    }
-    elseif ($element['#value'] < 0) {
-      form_error($element, t('The block update interval cannot be negative.'));
-    }
-  }
-  else {
-    form_error($element, t('The block update interval must be a number.'));
-  }
-}
-
 function chatroom_file_download($file) {
+  // Ensure downloaded sounds have the correct MIME type.
   if (strpos($file, 'chatroom/') === 0 && pathinfo($file, PATHINFO_EXTENSION) == 'mp3') {
     return array('Content-type: audio/mpeg');
   }
@@ -253,130 +113,14 @@
 }
 
 /**
- * Implementation of hook_form().
- */
-function chatroom_form(&$node) {
-  global $user;
-  $form['title'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Name'),
-    '#default_value' => check_plain($node->title),
-    '#required' => TRUE
-  );
-  $form['body_filter']['body'] = array(
-    '#type' => 'textarea',
-    '#title' => t('Description'),
-    '#default_value' => $node->body,
-    '#rows' => 3,
-    '#description' => t('Describe your chat room so other people will know if they want to join.'),
-  );
-  $form['body_filter']['format'] = filter_form($node->format);
-  $form['kicked_out_message'] = array(
-    '#type' => 'textarea',
-    '#title' => t('Chat room kicked out message'),
-    '#default_value' => $node->chatroom->kicked_out_message,
-    '#rows' => 3,
-    '#description' => t('This text will appear on the page kicked out users are sent to. Defaults to, "You have been kicked out of %chat for misbehaving."', array('%chat' => t('chat-name'))),
-  );
-  $form['banned_message'] = array(
-    '#type' => 'textarea',
-    '#title' => t('Chat room banned message'),
-    '#default_value' => $node->chatroom->banned_message,
-    '#rows' => 3,
-    '#description' => t('This text will appear on the page banned users are sent to. Defaults to, "You have been banned from %chatroom."', array('%chatroom' => t('chat-room'))),
-  );
-  if (!empty($node->chatroom->banned_users)) {
-    $form['chatroom_banned_users'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Manage banned users'),
-      '#collapsible' => TRUE,
-    );
-    foreach ($node->chatroom->banned_users as $banned_user) {
-      $banned_users[$banned_user->uid] = check_plain($banned_user->name);
-    }
-    $form['chatroom_banned_users']['unban_list'] = array(
-      '#type' => 'checkboxes',
-      '#options' => $banned_users,
-      '#description' => t('Check the users you would like to unban')
-    );
-  }
-  $form['chat_settings'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Chat settings'),
-    '#collapsible' => TRUE,
-  );
-  $form['chat_settings']['poll_freq'] = array(
-    '#type' => 'select',
-    '#title' => t('Update frequency'),
-    '#default_value' => empty($node->chatroom->poll_freq) ? 1 : $node->chatroom->poll_freq / 1000,
-    '#options' => drupal_map_assoc(range(1,10)),
-    '#description' => t('How many seconds between each request for updates from the server.'),
-  );
-  $form['chat_settings']['idle_freq'] = array(
-    '#type' => 'select',
-    '#title' => t('Idle time'),
-    '#default_value' => empty($node->chatroom->idle_freq) ? 60 : $node->chatroom->idle_freq / 1000,
-    '#options' => drupal_map_assoc(array(20, 40, 60, 80, 100, 120, 140, 160, 180)),
-    '#description' => t('How many seconds between each message before a last message time is shown in the chat.'),
-  );
-  $old_msg_range = array();
-  for ($i = 1; $i <= 25; $i++) {
-    $old_msg_range[$i] = $i * 10;
-  }
-  $form['chat_settings']['old_msg_count'] = array(
-    '#type' => 'select',
-    '#title' => t('Old messages'),
-    '#description' => t('How many old messages to show when entering a chat.'),
-    '#default_value' => empty($node->chatroom->old_msg_count) ? 20 : $node->chatroom->old_msg_count,
-    '#options' => drupal_map_assoc($old_msg_range),
-  );
-  if (!empty($node->chatroom->chats)) {
-    foreach ($node->chatroom->chats as $chat) {
-      if ($chat->section != 'archives') {
-        $chats[$chat->ccid] = check_plain($chat->chatname);
-      }
-      else {
-        $closed_chats[$chat->ccid] = check_plain($chat->chatname);
-      }
-    }
-    if (!empty($chats)) {
-      $form['chatroom_chats'] = array(
-        '#type' => 'fieldset',
-        '#title' => t('Manage open chats'),
-        '#collapsible' => TRUE,
-        '#collapsed' => FALSE,
-      );
-      $form['chatroom_chats']['chat_list'] = array(
-        '#type' => 'checkboxes',
-        '#options' => $chats,
-        '#description' => t('Check the chats you would like to close')
-      );
-    }
-    if (!empty($closed_chats)) {
-      $form['closed_chats'] = array(
-        '#type' => 'fieldset',
-        '#title' => t('Manage archived chats'),
-        '#collapsible' => TRUE,
-        '#collapsed' => FALSE,
-      );
-      $form['closed_chats']['closed_chat_list'] = array(
-        '#type' => 'checkboxes',
-        '#options' => $closed_chats,
-        '#description' => t('Check the chats you would like to delete')
-      );
-    }
-  }
-  return $form;
-}
-
-/**
- * Implementation of hook_insert()
+ * Implementation of hook_insert().
  */
 function chatroom_insert($node) {
   $result = db_query("
     INSERT INTO {chatroom}
-    (nid, poll_freq, idle_freq, old_msg_count, kicked_out_message, banned_message, modified)
-    VALUES (%d, %d, %d, %d, '%s', '%s', %d)
+      (nid, poll_freq, idle_freq, old_msg_count, kicked_out_message,
+      banned_message)
+    VALUES (%d, %d, %d, %d, '%s', '%s')
   ", array(
     $node->nid,
     1000 * $node->poll_freq,
@@ -384,15 +128,14 @@
     $node->old_msg_count,
     $node->kicked_out_message,
     $node->banned_message,
-    time()
   ));
   if ($result) {
-    chatroom_block_update_cache('chatrooms');
+    cache_clear_all(TRUE, 'cache_block');
   }
 }
 
 /**
- * Implementation of hook_update()
+ * Implementation of hook_update().
  */
 function chatroom_update($node) {
   db_query("
@@ -403,7 +146,6 @@
       old_msg_count = %d,
       kicked_out_message = '%s',
       banned_message = '%s',
-      modified = %d
     WHERE nid = %d
   ", array(
     1000 * $node->poll_freq,
@@ -411,9 +153,9 @@
     $node->old_msg_count,
     $node->kicked_out_message,
     $node->banned_message,
-    time(),
     $node->nid,
   ));
+  // Archive all open chats when the node is updated?
   if (isset($node->chat_list)) {
     foreach ($node->chat_list as $chat_id) {
       if ($chat_id > 0) {
@@ -425,9 +167,9 @@
         ", $chat_id);
       }
     }
-    chatroom_block_update_cache('chatrooms');
-    chatroom_block_update_cache('chats');
+    cache_clear_all(TRUE, 'cache_block');
   }
+  // Delete all archived chats when the node is updated?
   if (isset($node->closed_chat_list)) {
     foreach ($node->closed_chat_list as $chat_id) {
       if (!empty($chat_id)) {
@@ -435,6 +177,7 @@
       }
     }
   }
+  // Unban users?
   if (isset($node->unban_list)) {
     db_query('DELETE FROM {chatroom_ban_list} WHERE crid = %d AND uid IN (%s)', $node->chatroom->crid, implode(',', $node->unban_list));
   }
@@ -445,125 +188,100 @@
  */
 function chatroom_delete(&$node) {
   db_query('DELETE FROM {chatroom} WHERE nid = %d', $node->nid);
-  db_query("DELETE FROM {chatroom_chat} WHERE crid = %d", $node->chatroom->crid);
-  db_query('DELETE FROM {chatroom_ban_list} WHERE crid = %d', $node->chatroom->crid);
-  if (isset($node->chatroom) && !empty($node->chatroom->chats)) {
-    $ccids = implode(',', array_keys($node->chatroom->chats));
+  db_query("DELETE FROM {chatroom_chat} WHERE crid = %d", $node->nid);
+  db_query('DELETE FROM {chatroom_ban_list} WHERE crid = %d', $node->nid);
+  // 
+  if (!empty($node->chats)) {
+    $ccids = implode(',', array_keys($node->chats));
     db_query('DELETE FROM {chatroom_msg} WHERE ccid IN (%s)', $ccids);
     db_query('DELETE FROM {chatroom_online_list} WHERE ccid IN (%s)', $ccids);
     db_query('DELETE FROM {chatroom_msg_archive} WHERE ccid IN (%s)', $ccids);
-    foreach ($node->chatroom->chats as $chat_id => $name) {
-      file_delete(_chatroom_get_cache_file("chat.$chat_id"));
-    }
   }
-  chatroom_block_update_cache('chatrooms');
-  chatroom_block_update_cache('chats');
+  cache_clear_all(TRUE, 'cache_block');
+  // Clear cache related to this chat.
+  // cache_clear_all(TRUE, 'cache_chat');
 }
 
 /**
  * Implementation of hook_load().
  */
 function chatroom_load($node) {
-  $chatroom->chatroom = db_fetch_object(db_query('SELECT * FROM {chatroom} WHERE nid = %d', $node->nid));
-  if (!empty($chatroom->chatroom)) {
-    $chatroom->chatroom->banned_users = chatroom_get_banned_users($chatroom->chatroom->crid);
+  // Load room information.
+  $result = db_query('SELECT * FROM {chatroom} WHERE nid = %d', $node->nid);
+  $chatroom = db_fetch_object($result);
+  if (!empty($chatroom)) {
+    unset($chatroom->nid);
+    $chatroom->banned_users = chatroom_get_banned_users($node);
     // If the user is banned, don't load chats.
-    if (!chatroom_is_banned_user($chatroom->chatroom->crid)) {
-      $chatroom->chatroom->chats = chatroom_get_room_summary($chatroom->chatroom->crid);
-    }
-  }
-  return $chatroom;
-}
-
-/**
- * returns a the list of chats for a given room
- */
-function chatroom_get_room_summary($room_id) {
-  $result = db_query("
-    SELECT (
-      SELECT COUNT(*) FROM {chatroom_msg}
-      WHERE ccid = cc.ccid AND recipient = ''
-    ) + (
-      SELECT COUNT(*) FROM {chatroom_msg_archive}
-      WHERE ccid = cc.ccid AND recipient = ''
-    ) AS msg_count, MAX(cm.cmid) AS last_cmid, cc.ccid, cc.chatname, cc.when_archived
-    FROM {chatroom} AS cr
-    INNER JOIN {chatroom_chat} AS cc ON cc.crid = cr.crid
-    LEFT JOIN {chatroom_msg} AS cm ON cm.ccid = cc.ccid AND cm.recipient = ''
-    WHERE cr.crid = %d
-    GROUP BY cc.ccid, cc.chatname, cc.when_archived
-  ", $room_id, $room_id, $room_id);
-  $chats = array();
-  $msg_ids = array();
-  while ($chat = db_fetch_object($result)) {
-    if (isset($chat->when_archived)) {
-      $chat->section = 'archives';
-      $chat->msg_info = t('Archived on !date.', array('!date' => format_date($chat->when_archived, 'medium')));
+    if (chatroom_is_banned_user($node)) {
+      return $chatroom;
     }
-    else {
-      $chat->section = 'chat';
-      if ($chat->last_cmid) {
-        $msg_ids[] = $chat->last_cmid;
+    // Load chats for current chatroom.
+    $chatroom->chats = array();
+    $cmids = array();
+    $result = db_query("
+      SELECT (SELECT COUNT(*) FROM {chatroom_msg} WHERE ccid = cc.ccid AND
+        recipient = '') AS msg_count, MAX(cm.cmid) AS last_cmid, cc.ccid,
+        cc.chatname, cc.when_archived
+      FROM {chatroom} cr
+      INNER JOIN {chatroom_chat} cc ON cc.crid = cr.nid
+      LEFT JOIN {chatroom_msg} cm ON cm.ccid = cc.ccid AND cm.recipient = ''
+      WHERE cr.nid = %d
+      GROUP BY cc.ccid, cc.chatname, cc.when_archived
+    ", $node->nid);
+    while ($chat = db_fetch_object($result)) {
+      if (isset($chat->when_archived)) {
+        $chat->section = 'archives';
+        $chat->msg_info = t('Archived on !date.', array('!date' => format_date($chat->when_archived, 'medium')));
       }
       else {
-        $chat->msg_info = t('No messages');
+        $chat->section = 'chat';
+        if ($chat->last_cmid) {
+          $cmids[] = $chat->last_cmid;
+        }
+        else {
+          $chat->msg_info = t('No messages');
+        }
       }
+      $chatroom->chats[$chat->ccid] = $chat;
     }
-    $chats[$chat->ccid] = $chat;
-  }
-  if (!empty($msg_ids)) {
-    $result = db_query('
-      SELECT cm.*, col.guest_id, u.name FROM {chatroom_msg} cm
-      LEFT JOIN {chatroom_online_list} col ON col.session_id = cm.session_id
-      LEFT JOIN {users} u ON u.uid = cm.uid
-      WHERE cm.cmid IN (%s)
-    ', implode(',', $msg_ids));
-    while ($msg = db_fetch_object($result)) {
-      foreach ($chats as $i => $chat) {
-        if ($chat->last_cmid == $msg->cmid) {
-          $chats[$i]->msg_info = chatroom_get_msg_info($msg);
-        }
+    // Load information about the last message in each chatroom.
+    if (!empty($cmids)) {
+      $result = db_query("
+        SELECT cm.*, col.guest_id, u.name FROM {chatroom_msg} cm
+        LEFT JOIN {chatroom_online_list} col ON col.sid = cm.sid
+        LEFT JOIN {sessions} s ON s.sid = col.sid
+        LEFT JOIN {users} u ON u.uid = s.uid
+        WHERE cm.cmid IN (%s)
+      ", implode(',', $cmids));
+      while ($message = db_fetch_object($result)) {
+        $chatroom->chats[$message->ccid]->msg_info = theme('chatroom_last_message', $message);
       }
     }
   }
-  return $chats;
-}
-
-/**
- * Format a message for display in a summary table.
- */
-function chatroom_get_msg_info($msg) {
-  if (function_exists('_smileys_list') && variable_get('chatroom_smileys_enabled', FALSE)) {
-    $msg->msg = smileys_filter_process($msg->msg);
-  }
-  $output = $msg->msg .'<br />';
-  $output .= t('Posted by <strong>!user</strong> on !date', array(
-    '!user' => $msg->uid ? l($msg->name, "user/$msg->uid") : variable_get('chatroom_guest_user_prefix', t('guest-')) . $msg->guest_id,
-    '!date' => format_date($msg->modified, 'medium'),
-  ));
-  return $output;
+  return $chatroom;
 }
 
 /**
  * Gets a list of banned users for a given chat room.
  */
-function chatroom_get_banned_users($crid) {
-  $sql = '
-    SELECT cbl.uid, u.name FROM {chatroom_ban_list} cbl
-    INNER JOIN {users} u ON u.uid = cbl.uid
-    WHERE cbl.crid = %d
-  ';
+function chatroom_get_banned_users($room) {
   $banned_users = array();
-  if ($result = db_query($sql, $crid)) {
-    if ($banned_user = db_fetch_object($result)) {
-      $banned_users[] = $banned_user;
-    }
+  $result = db_query("
+    SELECT bl.uid, u.name FROM {chatroom_ban_list} bl
+    INNER JOIN {users} u ON u.uid = bl.uid
+    WHERE bl.nid = %d
+  ", $room->nid);
+  while ($user = db_fetch_object($result)) {
+    $banned_users[$user->uid] = $user;
   }
   return $banned_users;
 }
 
 /**
  * Implementation of hook_view().
+ *
+ * List active and archived chats in a chatroom.
  */
 function chatroom_view($node, $teaser = FALSE, $page = FALSE) {
   $node = node_prepare($node);
@@ -577,23 +295,23 @@
   }
   if (!$teaser) {
     // if the user is banned, just tell them why
-    if (chatroom_is_banned_user($node->chatroom->crid)) {
-      $node->content['body']['#value'] = !empty($node->chatroom->banned_message) ? $node->chatroom->banned_message : t('You have been banned from %chatroom.', array('%chatroom' => $node->title));
+    if (chatroom_is_banned_user($node)) {
+      $node->content['body']['#value'] = !empty($node->banned_message) ? $node->banned_message : t('You have been banned from %chatroom.', array('%chatroom' => $node->title));
     }
     else {
       // if the user can create chats, show the form
       if (user_access('create chats')) {
         $node->content['add_chat'] = array(
-          '#value' => drupal_get_form('chatroom_create_chat_form', $node->chatroom->crid),
+          '#value' => drupal_get_form('chatroom_create_chat_form', $node),
           '#weight' => 1,
         );
       }
       // if there are some chats, build some tables to display them
-      if (!empty($node->chatroom->chats) > 0) {
-        foreach ($node->chatroom->chats as $chat) {
+      if (!empty($node->chats) && is_array($node->chats)) {
+        foreach ($node->chats as $chat) {
           $type = $chat->section == 'chat' ? 'open' : 'archived';
           $rows[$type][] = array(
-            array('data' => l($chat->chatname, "chatrooms/$chat->section/$chat->ccid")),
+            array('data' => l($chat->chatname, "chatroom/$chat->section/$chat->ccid")),
             array('data' => $chat->msg_count),
             array('data' => $chat->msg_info)
           );
@@ -627,64 +345,6 @@
 }
 
 /**
- * returns a create chat form
- */
-function chatroom_create_chat_form($room_id) {
-  $form['chatroom_create_chat'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Create a new chat'),
-    '#collapsible' => TRUE,
-    '#collapsed' => FALSE
-  );
-  $form['chatroom_create_chat']['chat_name'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Chat name'),
-    '#size' => 30,
-    '#required' => TRUE,
-    '#description' => t('Enter the name for the chat'),
-  );
-  $form['chatroom_create_chat']['room_id'] = array(
-    '#type' => 'hidden',
-    '#value' => $room_id
-  );
-  $form['chatroom_create_chat']['submit'] = array(
-    '#type' => 'submit',
-    '#value' => t('Create chat')
-  );
-  return $form;
-}
-
-/**
- * validates attempt to create chat - checks this name is not already in use
- */
-function chatroom_create_chat_form_validate($f_id, $f_values) {
-  $crid = $f_values['room_id'];
-  $name = $f_values['chat_name'];
-  $sql = "SELECT ccid FROM {chatroom_chat} WHERE crid = %d AND chatname = '%s' AND when_archived IS NULL";
-  if (db_num_rows(db_query_range($sql, $crid, $name, 0, 1))) {
-    form_set_error('chatroom_chat_name', t('A chat called %name already exists.', array('%name' => $name)));
-  }
-}
-
-/**
- * creates a chat
- */
-function chatroom_create_chat_form_submit($f_id, $f_values) {
-  global $user;
-  $created = db_query("
-    INSERT INTO {chatroom_chat} (crid, uid, chatname, modified)
-    VALUES (%d, %d, '%s', %d)
-  ", $f_values['room_id'], $user->uid, $f_values['chat_name'], time());
-  if (!$created) {
-    drupal_set_message(t("There was an error creating your chat"), 'error');
-  }
-  else {
-    chatroom_block_update_cache('chats');
-    chatroom_block_update_cache('chatrooms');
-  }
-}
-
-/**
  * Archive old chat.
  */
 function chatroom_archive_chat($chat_id) {
@@ -700,8 +360,7 @@
   else {
     db_query('UPDATE {chatroom_chat} SET when_archived = NULL WHERE ccid = %d', $chat_id);
   }
-  chatroom_block_update_cache('chats');
-  chatroom_block_update_cache('chatrooms');
+  cache_clear_all(TRUE, 'cache_block');
   return $result;
 }
 
@@ -709,20 +368,37 @@
  * Implementation of hook_block().
  */
 function chatroom_block($op = 'list', $delta = 0, $edit = array()) {
-  $types = array(
-    array('chats', t('Chat Room: active chats')),
-    array('chat_rooms', t('Chat Room: active chat rooms')),
-    array('online_chat_users', t('Chat Room: chat on-line list')),
-    array('online_site_users', t('Chat Room: site-wide on-line list')),
-    array('command_list', t('Chat Room: chat commands')),
-  );
+  $types = array('chats', 'rooms', 'chat_users', 'site_users', 'commands');
   switch ($op) {
     case 'list':
-      $blocks[0]['info'] = $types[0][1];
-      $blocks[1]['info'] = $types[1][1];
-      $blocks[2]['info'] = $types[2][1];
-      $blocks[3]['info'] = $types[3][1];
-      $blocks[4]['info'] = $types[4][1];
+      $blocks[0] = array(
+        'info' => t('Chat room: active chats'),
+        'cache' => BLOCK_CACHE_GLOBAL,
+        'visibility' => 0,
+        'pages' => 'chatroom',
+      );
+      $blocks[1] = array(
+        'info' => t('Chat room: active chat rooms'),
+        'cache' => BLOCK_CACHE_GLOBAL,
+        'visibility' => 0,
+        'pages' => 'chatroom',
+      );
+      $blocks[2] = array(
+        'info' => t('Chat room: chat user list'),
+        'cache' => BLOCK_CACHE_PER_PAGE,
+        'visibility' => 1,
+        'pages' => 'chatroom/chat/*',
+      );
+      $blocks[3] = array(
+        'info' => t('Chat room: site-wide user list'),
+        'cache' => BLOCK_CACHE_GLOBAL,
+      );
+      $blocks[4] = array(
+        'info' => t('Chat room: chat commands'),
+        'cache' => BLOCK_CACHE_GLOBAL,
+        'visibility' => 1,
+        'pages' => 'chatroom/chat/*',
+      );
       return $blocks;
     case 'configure':
       switch ($types[$delta][0]) {
@@ -746,87 +422,33 @@
       break;
     case 'view':
       if (user_access('access chat rooms')) {
-        if ($types[$delta][0] == 'command_list') {
-          $block = theme('chatroom_block_commands');
+            return theme("chatroom_block_{$types[$delta][0]}");
+        switch ($types[$delta][0]) {
+          case 'command_list':
+            return theme('chatroom_block_commands');
+          case 'chats':
+            return theme('chatroom_block_chats');
+          case 'chat_rooms':
+            $block = theme('chatroom_block_rooms');
+            break;
+          case 'online_chat_users':
+            $block = theme('chatroom_block_chat_online_list');
+            break;
+          case 'online_site_users':
+            global $user;
+            $block = theme('chatroom_block_site_online_list', $user->uid);
+            break;
         }
-        else {
-          // Hack to set up cache files - can't find a way to do this on
-          // enable/disable of chat room blocks.
-          if ($types[$delta][0] != 'online_chat_users' && !@file_exists(_chatroom_get_cache_file($types[$delta][0]))) {
-            chatroom_block_update_cache($types[$delta][0]);
-          }
-          switch ($types[$delta][0]) {
-            case 'chats':
-              $block = theme('chatroom_block_chats');
-              break;
-            case 'chat_rooms':
-              $block = theme('chatroom_block_rooms');
-              break;
-            case 'online_chat_users':
-              $block = theme('chatroom_block_chat_online_list');
-              break;
-            case 'online_site_users':
-              global $user;
-              $block = theme('chatroom_block_site_online_list', $user->uid);
-              break;
-          }
-          if (!empty($block['content'])) {
-            chatroom_block_settings();
-          }
+        if (!empty($block['content'])) {
+          chatroom_block_settings();
         }
-        return $block;
-      }
-  }
-}
-
-/**
- * List chat commands.
- */
-function theme_chatroom_block_commands() {
-  if (preg_match('/^chatrooms\/chat\/(\d+)/', $_GET['q'], $matches)) {
-    $content = '/msg <em>'. t('username') .'</em> '. t('message');
-    if (user_access('administer chats')) {
-      $content .= '<br />/kick <em>'. t('username') .'</em>';
-      $content .= '<br />/ban <em>'. t('username') .'</em>';
-    }
-    $content .= '<br />/me '. t('message');
-    $content .= '<br />/away';
-    $content .= '<br />/back';
-    return array(
-      'content' => $content,
-      'subject' => t('Chat commands'),
-    );
-  }
-}
-
-/**
- * List site-wide chats.
- */
-function theme_chatroom_block_chats() {
-  if ($_GET['q'] != 'chatrooms') {
-    $content = '<ul class="menu" id="chatroom-sitewide-chats">';
-    $chats = chatroom_get_active_chats(0, variable_get('chatroom_block_chats', 5));
-    if ($chats) {
-      foreach ($chats as $chat) {
-        $chat_link = l($chat->chatname, "chatrooms/chat/$chat->ccid");
-        $room_link = l($chat->room_name, "node/$chat->nid");
-        $content .= '<li id="chat_'. $chat->ccid .'">'. $chat_link .'<br />';
-        $content .= '<span class="chatroomLink">'. t('in') .' '. $room_link .'</span></li>';
       }
-    }
-    else {
-      $content .= '<li id="chat_empty"><em>'. t('There are no active chats.') .'</em></li>';
-    }
-    $content .= '</ul>';
-    return array(
-      'content' => $content,
-      'subject' => t('Active chats'),
-    );
+      return $block;
   }
 }
 
 /**
- * gets a list of active chats
+ * Get a list of active chats.
  */
 function chatroom_get_active_chats($start = NULL, $end = NULL, $save_query = TRUE) {
   global $user;
@@ -843,7 +465,7 @@
     variable_set("chatroom_get_active_chats_query_{$user->uid}", $sql);
   }
   else {
-    $sql = _chatroom_variable_get("chatroom_get_active_chats_query_{$user->uid}", $sql);
+    $sql = variable_get("chatroom_get_active_chats_query_{$user->uid}", $sql);
   }
   if (isset($start) && isset($end)) {
     $result = db_query_range($sql, $start, $end);
@@ -851,245 +473,100 @@
   else {
     $result = db_query($sql);
   }
-  if (db_num_rows($result) > 0) {
-    $chats = array();
-    while ($chat = db_fetch_object($result)) {
-      $chats[] = $chat;
-    }
+  $chats = array();
+  while ($chat = db_fetch_object($result)) {
+    $chats[] = $chat;
+  }
+  if (!empty($rows)) {
     return $chats;
   }
   return FALSE;
 }
 
-/**
- * List chat rooms.
- */
-function theme_chatroom_block_rooms() {
-  if ($_GET['q'] != 'chatrooms') {
-    $content = '<ul class="menu" id="chatroom-sitewide-chatrooms">';
-    $rooms = chatroom_get_chatroom_list(FALSE, 0, variable_get('chatroom_block_chat_rooms', 5));
-    if (empty($rooms)) {
-      $content .= '<li id="chatroom_empty"><em>'. t('There are no active chat rooms.') .'</em></li>';
-    }
-    else {
-      foreach ($rooms as $room) {
-        $content .= '<li id="chatroom_'. $room->nid .'">'. l($room->title, "node/$room->nid") .'</li>';
-      }
-    }
-    $content .= '</ul>';
-    return array(
-      'content' => $content,
-      'subject' => t('Active chat rooms'),
-    );
-  }
-}
-
-/**
- * Get a list of on-line users in a given chat.
- */
-function theme_chatroom_block_chat_online_list() {
-  if (!preg_match('/^chatrooms\/chat\/(\d+)/', $_GET['q'], $matches)) {
-    return array(false, false);
-  }
-  $chat_id = $matches[1];
-  $cache_file = _chatroom_get_cache_file("chat.$chat_id");
-  $cache_timestamp = @filemtime($cache_file);
-  $users = chatroom_chat_get_online_list($chat_id, $cache_timestamp, 0);
-  $title = '';
-  $content = '';
-  if (!empty($users)) {
-    $chatname = db_result(db_query("SELECT chatname FROM {chatroom_chat} WHERE ccid = %d", $chat_id));
-    $title = t('Who is on line in %chat', array('%chat' => $chatname));
-    $content = '<ul class="menu" id="chatroom-online">';
-    foreach ($users as $user) {
-      $content .= '<li id="'. $user['guestId'] .'"';
-      $content .= $user['away'] ? ' class="chatroom-user-away">' : '>';
-      $content .= '<a href="javascript:Drupal.chatroom.chat.selectUser(\''. $user['user'] .'\')">'. $user['user'] .'</a></li>';
-    }
-    $content .= '</ul>';
-    if (variable_get('chatroom_alerts', FALSE)) {
-      $checked = variable_get('chatroom_alerts_default', FALSE) ? ' checked' : '';
-      $content .= '<div id="chatroom-user-options">';
-      $content .= '<label><input type="checkbox"'. $checked .' id="chatroom-user-alert" /> ';
-      $content .= t('Alert me if new users enter.') .'</label></div>';
-    }
-  }
-  return array(
-    'content' => $content,
-    'subject' => $title,
-  );
-}
-
-/**
- * outputs html for list of online users for the whole site
- */
-function theme_chatroom_block_site_online_list($uid) {
-  chatroom_site_update_online_time($uid);
-  $users = chatroom_get_site_online_list($uid);
-  $content = '<ul class="menu" id="chatroom-sitewide-online">';
-  if (!empty($users)) {
-    foreach ($users as $user) {
-      $content .= '<li id="user-li-'. $user->uid .'"><strong>'. check_plain($user->name) .'</strong></li>';
-    }
-  }
-  else {
-    $content .= '<li id="no_users"><em>'. t('There are no other users on line.') .'</em></li>';
-  }
-  $content .= '</ul>';
-  return array(
-    'content' => $content,
-    'subject' => t('On-line users'),
-  );
-}
 
 /**
- * Add settings to block pages.
+ * Handle AHAH requests.
  */
-function chatroom_block_settings() {
-  // only do this once per request
-  static $state_set = FALSE;
-  if (!$state_set) {
-    $state_set = TRUE;
-    global $user, $base_url;
-    // need some css for the user list display when not in chat room chat page
-    if (substr($_GET['q'], 0, strlen('chatrooms/chat/')) != 'chatrooms/chat/') {
-      drupal_add_css(drupal_get_path('module', 'chatroom') .'/chatroom.css');
-    }
-    $module_base = drupal_get_path('module', 'chatroom');
-    $chats_cache_file = _chatroom_get_cache_file('chats');
-    $rooms_cache_file = _chatroom_get_cache_file('chatrooms');
-    $js = array(
-      'drupalBase' => realpath('.'),
-      'baseUrl' => $base_url,
-      'updateUrl' => $base_url .'/'. $module_base .'/chatroomread.php',
-      'chatroomBase' => $module_base,
-      'roomBase' => url('node/'),
-      'chatBase' => url('chatrooms/chat/'),
-      'chatsCacheFile' => $chats_cache_file,
-      'roomsCacheFile' => $rooms_cache_file,
-      'userBase' => drupal_get_path('module', 'user'),
-      'blockUpdateInterval' => variable_get('chatroom_block_update_interval', 5) * 1000,
-      'chatTimestamp' => @file_exists($chats_cache_file) ? @filemtime($chats_cache_file) : time() - 5,
-      'roomTimestamp' => @file_exists($rooms_cache_file) ? @filemtime($rooms_cache_file) : time() - 5,
-      'uid' => (int) $user->uid,
-      'usersMessage' => t('There are no other users on line.'),
-      'chatsMessage' => t('There are no active chats.'),
-      'roomsMessage' => t('There are no active chat rooms.'),
-    );
-    $drupal_js = drupal_add_js();
-    foreach ($drupal_js['setting'] as $setting) {
-      if (is_array($setting['chatroom'])) {
-        foreach ($js as $key => $value) {
-          if (array_key_exists($key, $setting['chatroom'])) {
-            unset($js[$key]);
-          }
-        }
-      }
-    }
-    drupal_add_js(array('chatroom' => $js), 'setting');
-    drupal_add_js(drupal_get_path('module', 'chatroom') .'/chatroom.block.js');
+function chatroom_js() {
+  // Load the form from the Form API cache.
+  $cache = cache_get('form_'. $_POST['form_build_id'], 'cache_form');
+
+  $form = $cache->data;
+  $form_state = $_POST;
+  $chat = chatroom_load_chat($form_state['ccid']);
+
+  $debug = TRUE;
+  if ($debug) {
+    watchdog('chatroom', 'Handled a chatroom "@type" AHAH request at @time',
+      array(
+        '@type' => $form_state['control'],
+        '@time' => time(),
+      ));
+  }
+
+  // Act on commands
+  switch($form_state['control']) {
+    case 'message':
+      // Save message.
+      chatroom_save_message($chat, $form_state['message']);
+      break;
+    case 'status':
+    case 'poll':
+    default:
+      // Do nothing.
+      break;
   }
-}
+//  watchdog('chatroom', 'chatroom_update_online_time');
+  chatroom_update_online_time($chat, $form_state['away']);
 
-/**
- * Menu callback; prints a chat room listing.
- */
-function chatroom_page() {
-  foreach (chatroom_get_chatroom_list(TRUE) as $room) {
-    $tree[$room->nid] = node_load($room->nid);
+  // Retrieve any new messages.
+  watchdog('chatroom', 'chatroom_load_messages cmid is @cmid',
+    array('@cmid' => $form_state['last_cmid']));
+  if ($last_cmid = chatroom_load_messages($chat, $form_state['last_cmid'])) {
+    $response['last-cmid'] = $last_cmid;
+      watchdog('chatroom', 'chatroom_load_messages new cmid is @cmid',
+      array('@cmid' => $response['last-cmid']));
   }
-  return theme('chatroom_display', $tree);
-}
+  $response['messages'] = theme('chatroom_messages', $chat->messages);
+  // TODO: logic to determine when to avoid this.
+//  watchdog('chatroom', 'chatroom_load_users');
 
-/**
- * Format the chat room listing.
- *
- * @ingroup themeable
- */
-function theme_chatroom_display($tree) {
-  global $user;
-  // chat room list, chats list, and 'add new chat' link
+  chatroom_load_users($chat);
+  $response['users'] = theme('chatroom_user_list', $chat->users);
 
-  if (!empty($tree)) {
-    $output  = '<div id="chatroom">';
-    $output .= '<ul>';
+  // Clear the 'control' element in the form.
+  $response['control'] = '';
 
-    if (user_access('create chat rooms')) {
-      $output .= '<li>'. l(t('Post a new chat room.'), "node/add/chatroom") .'</li>';
-    }
-    else if ($user->uid) {
-      //
-    }
-    else {
-      $output .= '<li>'. t('<a href="!login">Login</a> to post a new chat room.', array('!login' => url('user/login'))) .'</li>';
-    }
-    $output .= '</ul>';
-    $output .= theme('chatroom_list', $tree);
-    $output .= '</div>';
-  }
-  else {
-    drupal_set_title(t('No chat rooms defined'));
-    $output = '';
+//    watchdog('chatroom', 'foreach response');
+  foreach($response as $key => $data) {
+    $output .= '<div class="ahah-response" id="response-'. $key .'">'. $data .'</div>';
   }
 
-  return $output;
+//     watchdog('chatroom', 'drupal_to_js');
+  print drupal_to_js(array('status' => TRUE, 'data' => $output));
+  exit;
 }
 
 /**
- * Format the chat room table.
- *
- * @ingroup themeable
+ * Menu callback; a page listing all chat rooms.
  */
-function theme_chatroom_list($tree) {
-  global $user;
-
-  if ($tree) {
-    $header = array(t('Chat room'), t('Chats'), t('Messages'), t('Last message'));
-    foreach ($tree as $room) {
-      $description  = "<div>\n";
-      $description .= ' <div class="name">'. l($room->title, "node/$room->nid").'</div>';
-
-      if ($room->body) {
-        $description .= ' <div class="description">'. $room->body ."</div>\n";
-      }
-      $description .= "</div>\n";
-
-      $rows[] = array(array('data' => $description, 'class' => 'container', 'colspan' => '4'));
-
-      if (isset($room->chatroom->chats)) {
-        foreach ($room->chatroom->chats as $id => $chat) {
-          $description  = "<div>\n";
-          if (isset($chat->when_archived)) {
-            $description .= ' <div class="name">'. l($chat->chatname, "chatrooms/archives/$chat->ccid") ."</div>\n";
-          }
-          else {
-            $description .= ' <div class="name">'. l($chat->chatname, "chatrooms/chat/$chat->ccid") ."</div>\n";
-          }
-          $description .= "</div>\n";
-
-          $row[] = array('data' => '&nbsp;');
-          $row[] = array('data' => $description, 'class' => 'chatroom-chat');
-          $row[] = array('data' => $chat->msg_count, 'class' => 'chatroom-msg-count');
-          $row[] = array('data' => $chat->msg_info, 'class' => 'chatrom-msg-info');
-          $rows[] = $row;
-          unset($row);
-        }
-      }
-    }
-    return theme('table', $header, $rows);
+function chatroom_page() {
+  foreach (chatroom_get_room_list(TRUE) as $room) {
+    $tree[$room->nid] = node_load($room->nid);
   }
+  return theme('chatroom_display', $tree);
 }
 
 /**
  * tells a kicked out user not to be a knob
  */
 function chatroom_chat_kicked_user($chat_id = FALSE) {
-  if ($chat = chatroom_chat_get_from_id($chat_id)) {
+  if ($chat = chatroom_load_chat($chat_id)) {
     if (isset($chat->when_archived)) {
       drupal_goto("chatrooms/archives/$chat_id", NULL, NULL, 301);
     }
     else {
-      if (chatroom_is_banned_user($chat->crid)) {
+      if (chatroom_is_banned_user($chat)) {
         $content = theme('chatroom_chat_banned_user', $chat);
       }
       else {
@@ -1104,54 +581,46 @@
 }
 
 /**
- * Get HTML for kick message.
- *
- * @ingroup themeable
- */
-function theme_chatroom_chat_kicked_user($chat) {
-  $msg = $chat->kicked_out_message ? $chat->kicked_out_message : t('You have been kicked out of %chat for misbehaving.', array('%chat' => $chat->chatname));
-  return '<div id="chatroom-kicked-msg">'. $msg .'</div>';
-}
-
-/**
- * Get HTML for ban message.
- *
- * @ingroup themeable
- */
-function theme_chatroom_chat_banned_user($chat) {
-  $msg = $chat->banned_message ? $chat->banned_message : t('You have been banned from %chatroom.', array('%chatroom' => $chat->chatroom_name));
-  return '<div id="chatroom-banned-msg">'. $msg .'</div>';
-}
-
-/**
- * loads the UI for a chat, and registers this user as online
+ * Menu callback; display an active or archived chat.
  */
-function chatroom_chat($chat_id = FALSE) {
-  if ($chat = chatroom_chat_get_from_id($chat_id)) {
-    if (isset($chat->when_archived)) {
-      drupal_goto("chatrooms/archives/$chat_id", NULL, NULL, 301);
+function chatroom_view_chat($ccid = NULL, $archive = FALSE) {
+  if ($chat = chatroom_load_chat($ccid)) {
+    // Common features for any chat: CSS, breadcrumb, page title
+    drupal_add_css(drupal_get_path('module', 'chatroom') .'/chatroom.css');
+    $bc = drupal_get_breadcrumb();
+    $bc[] = l($chat->chatroom_name, "node/$chat->nid");
+    drupal_set_breadcrumb($bc);
+    drupal_set_title(check_plain($chat->chatname));
+    // Display a message if the user is banned.
+    if (chatroom_is_banned_user($chat)) {
+      $content = theme('chatroom_chat_banned_user', $chat);
     }
-    else {
-      $content = '';
-      if (chatroom_is_banned_user($chat->crid)) {
-        $content .= theme('chatroom_chat_banned_user', $chat);
+    elseif (!isset($chat->when_archived) && !$archive) {
+      // Automatically archive old messages
+      if (variable_get('chatroom_auto_archive', FALSE)) {
+        chatroom_archive_old_msgs($chat);
+      }
+      // Register the user.
+      chatroom_chat_register_user($chat);
+      // Add AJAX code.
+      chatroom_add_js($chat);
+      // chatroom_chat_settings($chat);
+      $content .= theme('chatroom_chat', $chat);
+      // Forms to archive or delete the chat.
+      if (user_access('administer chat rooms')) {
+        $content .= drupal_get_form('chatroom_archive_chat_form', $ccid);
+        $content .= drupal_get_form('chatroom_delete_chat_form', $ccid);
       }
-      else {
-        if (variable_get('chatroom_auto_archive', FALSE)) {
-          chatroom_archive_old_msgs($chat_id);
-        }
-        chatroom_chat_register_user($chat->ccid);
-        chatroom_chat_set_cache($chat->ccid);
-        drupal_add_css(drupal_get_path('module', 'chatroom') .'/chatroom.css');
-        chatroom_chat_settings($chat);
-        $bc = drupal_get_breadcrumb();
-        $bc[] = l($chat->chatroom_name, "node/$chat->nid");
-        drupal_set_breadcrumb($bc);
-        drupal_set_title(check_plain($chat->chatname));
-        $content .= theme('chatroom_chat', $chat);
+    }
+    else {
+      // Display an archived chat.
+      $content = theme('chatroom_chat_archive', $chat);
+      // Form to delete the archive.
+      if (user_access('administer chat rooms')) {
+        $content .= drupal_get_form('chatroom_delete_chat_form', $ccid);
       }
-      return $content;
     }
+    return $content;
   }
   else {
     drupal_not_found();
@@ -1159,20 +628,6 @@
 }
 
 /**
- * Get HTML for chat.
- *
- * @ingroup themeable
- */
-function theme_chatroom_chat($chat) {
-  $html = '<p>'. l(t('View old messages'), "chatrooms/archives/{$chat->ccid}") .'</p>';
-  $html .= '<div id="chatroom-container">';
-  $html .= theme('chatroom_chat_board');
-  $html .= theme('chatroom_chat_textentry');
-  $html .= '</div>';
-  return $html;
-}
-
-/**
  * Move old messages to archive.
  */
 function chatroom_archive_old_msgs($chat_id) {
@@ -1199,36 +654,34 @@
  */
 function chatroom_chat_register_user($chat_id) {
   global $user;
-  $cache_file = _chatroom_get_cache_file("chat.$chat_id");
-  chatroom_chat_update_cache($cache_file);
-  $result = db_query_range("
-    SELECT coid FROM {chatroom_online_list}
-    WHERE ccid = %d AND uid = %d AND session_id = '%s'
-  ", $chat_id, $user->uid, session_id(), 0, 1);
-  if (db_num_rows($result)) {
-    chatroom_chat_update_online_time($chat_id);
+  $count = db_result(db_query_range("
+    SELECT COUNT(*) FROM {chatroom_online_list}
+    WHERE ccid = %d AND sid = '%s'
+  ", $chat_id, session_id(), 0, 1));
+  if ($count) {
+    chatroom_update_online_time($chat_id);
   }
   else {
     $is_admin = user_access('administer chats');
-    $result = db_query_range("
-      SELECT coid FROM {chatroom_online_list}
-      WHERE ccid = %d AND session_id = '%s'
-    ", $chat_id, session_id(), 0, 1);
-    if (db_num_rows($result)) {
+    $count = db_result(db_query_range("
+      SELECT COUNT(*) FROM {chatroom_online_list}
+      WHERE ccid = %d AND sid = '%s'
+    ", $chat_id, session_id(), 0, 1));
+    if ($count) {
       db_query("
-        UPDATE {chatroom_online_list} SET uid = %d, is_admin = %d
-        WHERE ccid = %d AND session_id = '%s'
-      ", $user->uid, $is_admin, $chat_id, session_id());
+        UPDATE {chatroom_online_list} SET is_admin = %d
+        WHERE ccid = %d AND sid = '%s'
+      ", $is_admin, $chat_id, session_id());
     }
     else {
       db_lock_table('chatroom_online_list');
       $result = db_query("SELECT COALESCE(MAX(guest_id) + 1, 1) FROM {chatroom_online_list} WHERE ccid = %d", $chat_id);
-      if ($result && db_num_rows($result)) {
+      if ($result) {
         db_query("
           INSERT INTO {chatroom_online_list}
-          (ccid, uid, is_admin, session_id, guest_id, modified)
-          VALUES (%d, %d, %d, '%s', %d, %d)
-        ", $chat_id, $user->uid, $is_admin, session_id(), db_result($result), time());
+          (ccid, is_admin, sid, guest_id, modified)
+          VALUES (%d, %d, '%s', %d, %d)
+        ", $chat_id, $is_admin, session_id(), db_result($result), time());
       }
       db_unlock_tables();
     }
@@ -1238,68 +691,54 @@
 /**
  * Add settings to chat page.
  */
-function chatroom_chat_settings($chat) {
-  global $user, $base_url, $base_path;
-  $chatroom_base = drupal_get_path('module', 'chatroom');
-  $cache_file = _chatroom_get_cache_file("chat.{$chat->ccid}");
-  $cache_timestamp = @filemtime($cache_file);
-  $users = chatroom_chat_get_online_list($chat->ccid, $cache_timestamp, 0);
-  if (variable_get('configurable_timezones', 1) && $user->uid && strlen($user->timezone)) {
-    $timezone = $user->timezone;
-  }
-  else {
-    $timezone = variable_get('date_default_timezone', 0);
-  }
+function chatroom_add_js($chat) {
+  $module_path = drupal_get_path('module', 'chatroom');
+  // Settings for chatroom.js. Variables which change during a chat session are
+  // included in AHAH replies from chatroom_js(), and handled by code in
+  // chatroom.js, so they are not duplicated here.
   $js = array(
-    'chatId' => $chat->ccid,
-    'lastMsgId' => 0,
-    'cacheTimestamp' => $cache_timestamp,
-    'updateCount' => 0,
-    'updateUrl' => $base_url .'/'. $chatroom_base .'/chatroomread.php',
-    'kickUrl' => url('chatrooms/kicked/'),
-    'drupalBase' => realpath('.'),
-    'baseUrl' => $base_url,
-    'userBase' => drupal_get_path('module', 'user'),
-    'chatroomBase' => $chatroom_base,
-    'userUrl' => url('user/'),
-    'chatCacheFile' => $cache_file,
-    'chatUpdateInterval' => (int) $chat->poll_freq,
+    'pollInterval' => (int) $chat->poll_freq,
     'idleInterval' => (int) $chat->idle_freq,
-    'chatUsers' => $users,
-    'userColours' => _chatroom_load_hex_colours(),
-    'timezone' => $timezone,
-    'joinMessage' => t(' has joined the chat'),
-    'leaveMessage' => t(' has left the chat'),
-    'awayMessage' => t(' is away'),
-    'backMessage' => t(' is back'),
-    'basePath' => $base_path,
   );
+  // Timezones setting.
+  if (variable_get('configurable_timezones', 1) && $user->uid && drupal_strlen($user->timezone)) {
+    $js['timezone'] = $user->timezone;
+  }
+  else {
+    $js['timezone'] = variable_get('date_default_timezone', 0);
+  }
+  // Sound files for audio alerts.
+  $alerts = variable_get('chatroom_alerts', FALSE);
+  if ($alerts) {
+    $js['sm2URL'] = base_path() . $module_path;
+    $file_path = file_directory_path() .'/chatroom';
+    if (variable_get('chatroom_custom_sounds', FALSE)) {
+      $js['messageSound'] = file_exists("$file_path/message.mp3") ? file_create_url("$file_path/message.mp3") : "$module_path/sounds/message.mp3";
+      $js['userSound'] = file_exists("$file_path/user.mp3") ? file_create_url("$file_path/user.mp3") : "$module_path/sounds/user.mp3";
+    }
+    else {
+      $js['messageSound'] = base_path() ."$module_path/sounds/message.mp3";
+      $js['userSound'] = base_path() ."$module_path/sounds/user.mp3";
+    }
+  }
+  // Smileys settings. TODO: make this work.
   if (function_exists('_smileys_list') && variable_get('chatroom_smileys_enabled', FALSE)) {
     $js['smileysBase'] = drupal_get_path('module', 'smileys');
     $js['smileysMarker'] = '------';
   }
+  // Avoid overwriting existing settings.
   $drupal_js = drupal_add_js();
-  foreach ($drupal_js['setting'] as $setting) {
-    if (is_array($setting['chatroom'])) {
-      foreach ($js as $key => $value) {
-        if (array_key_exists($key, $setting['chatroom'])) {
-          unset($js[$key]);
-        }
+  if (isset($drupal_js['setting']['chatroom']) && is_array($drupal_js['setting']['chatroom'])) {
+    foreach ($js as $key => $value) {
+      if (array_key_exists($key, $drupal_js['setting']['chatroom'])) {
+        unset($js[$key]);
       }
     }
   }
-  drupal_add_js(drupal_get_path('module', 'chatroom') .'/chatroom.js');
-  if (variable_get('chatroom_alerts', FALSE)) {
-    $path = file_directory_path() .'/chatroom';
-    if (variable_get('chatroom_custom_sounds', FALSE)) {
-      $js['messageSound'] = file_exists("$path/message.mp3") ? file_create_url("$path/message.mp3") : "$chatroom_base/sounds/message.mp3";
-      $js['userSound'] = file_exists("$path/user.mp3") ? file_create_url("$path/user.mp3") : "$chatroom_base/sounds/user.mp3";
-    }
-    else {
-      $js['messageSound'] = "$chatroom_base/sounds/message.mp3";
-      $js['userSound'] = "$chatroom_base/sounds/user.mp3";
-    }
-    drupal_add_js(drupal_get_path('module', 'chatroom') .'/soundmanager2.js');
+  // Add scripts and settings.
+  drupal_add_js("$module_path/chatroom.js");
+  if ($alerts) {
+    drupal_add_js("$module_path/soundmanager2.js");
   }
   drupal_add_js(array('chatroom' => $js), 'setting');
 }
@@ -1325,37 +764,6 @@
 }
 
 /**
- * returns html for the message board
- */
-function theme_chatroom_chat_board() {
-  return '<div id="chatroom-board"></div>';
-}
-
-/**
- * returns html for the text entry widget
- * themeable
- */
-function theme_chatroom_chat_textentry() {
-  $output  = '<div class="clear-both"></div>'."\n";
-  $output .= '<div id="chatroom-textentry">'."\n";
-  $output .= '<span class="chatroom-textentry-title">'. t('Type your message') ."</span><br/><br/>\n";
-  $output .= '<input type="text" id="chatroom-msg-input" size="50" maxlength="160" />'."\n";
-  $output .= '<input type="submit" id="chatroom-msg-submit" value="'. t('Send') .'" />'."\n";
-  $output .= chatroom_smileys_get_textentry();
-  $output .= '<div class="chatroom-textentry-options">';
-  $output .= '<table><tr>';
-  $output .= '<td><label><input type="checkbox" id="chatroom-msg-away" /> '. t('Show me as away.') .'</label></td>';
-  if (variable_get('chatroom_alerts', FALSE)) {
-    $checked = variable_get('chatroom_alerts_default', FALSE) ? ' checked' : '';
-    $output .= '<td><label><input type="checkbox"'. $checked .' id="chatroom-msg-alert" /> '. t('Alert me if new messages are received.') .'</label></td>';
-  }
-  $output .= '</table>';
-  $output .= "</div>\n";
-  $output .= "</div>\n";
-  return $output;
-}
-
-/**
  * smileys module support
  * return text entry smileys list
  */
@@ -1379,105 +787,6 @@
 }
 
 /**
- * loads an archived chat
- */
-function chatroom_chat_archive($chat_id = FALSE) {
-  if ($chat = chatroom_chat_get_from_id($chat_id)) {
-    $content = '';
-    if (chatroom_is_banned_user($chat->crid)) {
-      $content = theme('chatroom_chat_banned_user', $chat);
-    }
-    else {
-      drupal_add_css(drupal_get_path('module', 'chatroom') .'/chatroom.css');
-      $bc = drupal_get_breadcrumb();
-      $bc[] = l($chat->chatroom_name, "node/$chat->nid");
-      drupal_set_breadcrumb($bc);
-      drupal_set_title(check_plain($chat->chatname));
-      $content = theme('chatroom_chat_archive', $chat);
-      if (user_access('administer chat rooms') && isset($chat->when_archived)) {
-        $content .= drupal_get_form('chatroom_form_chat_delete', $chat_id);
-      }
-      elseif (user_access('administer chat rooms')) {
-        $content .= drupal_get_form('chatroom_form_chat_archive', $chat_id);
-      }
-    }
-    return $content;
-  }
-  else {
-    drupal_not_found();
-  }
-}
-
-/**
- * form for archived chat view to delete that chat
- *
- * @param integer $chat_id
- * @return array
- */
-function chatroom_form_chat_delete($chat_id = NULL) {
-  $form = array();
-  $form['chat_id'] = array (
-    '#type' => 'value',
-    '#value' => $chat_id,
-  );
-  $form['delete'] = array(
-    '#type' => 'submit',
-    '#value' => t('Delete this chat'),
-  );
-  return $form;
-}
-
-/**
- * Delete an archived chat
- *
- * @param array $form_id
- * @param array $form_values
- * @return string
- */
-function chatroom_form_chat_delete_submit($form_id, $form_values) {
-  $chat = chatroom_chat_get_from_id($form_values['chat_id']);
-  chatroom_chat_delete($form_values['chat_id']);
-  return "node/$chat->nid";
-}
-
-/**
- * form for archived chat view of open chat to archive that chat
- *
- * @param integer $chat_id
- * @return array
- */
-function chatroom_form_chat_archive($chat_id) {
-  $form['chat_id'] = array (
-    '#type' => 'value',
-    '#value' => $chat_id,
-  );
-  $form['delete'] = array(
-    '#type' => 'submit',
-    '#value' => t('Archive this chat'),
-  );
-  return $form;
-}
-
-/**
- * Archive an open chat.
- *
- * @param array $form_id
- * @param array $form_values
- * @return string
- */
-function chatroom_form_chat_archive_submit($form_id, $form_values) {
-  chatroom_archive_chat($form_values['chat_id']);
-  file_delete(_chatroom_get_cache_file('chat.'. $form_values['chat_id']));
-  db_query("
-    INSERT INTO {chatroom_msg_archive} (cmid, ccid, uid, msg_type, msg, session_id, recipient, modified)
-    SELECT * FROM {chatroom_msg} WHERE ccid = %d
-  ", $form_values['chat_id']);
-  $chat = chatroom_chat_get_from_id($form_values['chat_id']);
-  chatroom_block_update_cache('chatrooms');
-  chatroom_block_update_cache('chats');
-}
-
-/**
  * delete a single chat from the database
  *
  * @param int $chat_id
@@ -1490,234 +799,194 @@
 }
 
 /**
- * Get HTML for chat archive page.
+ * Get messages for a given chat.
  *
- * @ingroup themeable
- */
-function theme_chatroom_chat_archive($chat) {
-  $html = '';
-  if (isset($chat->when_archived)) {
-    $html = '<p>'. t('Archived on !date.', array('!date' => format_date($chat->when_archived, 'medium'))) .'</p>';
-  }
-  else {
-    $html = '<p>'. l(t('Join !chat', array('!chat' => $chat->chatname)), "chatrooms/chat/$chat->ccid") .'</p>';
-  }
-  if ($msgs = chatroom_get_all_msgs($chat->ccid)) {
-    $html .= '<div id="chatroom-container-archive"><table><tbody valign="top">';
-    foreach ($msgs as $msg) {
-      $name = $msg->name ? $msg->name : variable_get('chatroom_guest_user_prefix', t('guest-')) . $msg->guest_id;
-      $html .= '<tr><td><span class="chatroom-archive-date">'. format_date($msg->modified, 'small') .'</span></td>';
-      if ($msg->msg_type == 'me') {
-        $html .= '<td><p class="chatroom-old-me-msg">';
-        $html .= "* $name {$msg->msg}</p></td>";
-      }
-      else {
-        $html .= '<td><p class="chatroom-old-msg">';
-        if (empty($msg->recipient)) {
-          if ($previous != $name) {
-            $html .= '<span class="header">'."$name:</span>";
-            $previous = $name;
-          }
-        }
-        else {
-          $html .= '<span class="header">'."$name ";
-          $recipient = db_result(db_query("
-            SELECT u.name FROM {chatroom_online_list} col
-            LEFT JOIN {users} u ON u.uid = col.uid
-            WHERE col.ccid = %d AND col.guest_id = %d
-          ", $msg->ccid, $msg->guest_id));
-          $recipient = empty($recipient) ? variable_get('chatroom_guest_user_prefix', t('guest-')) . $msg->recipient : $recipient;
-          if ($msg->session_id == session_id() && $recipient != $name) {
-            $html .= '<span class="chatroom-private">(privately to '. $recipient .')</span>:</span> ';
-          }
-          else {
-            $html .= '<span class="chatroom-private">(privately)</span>:</span> ';
-          }
-        }
-        $html .= "{$msg->msg}</p></td>";
-      }
-    }
-    $html .= '</tbody></table></div>';
-    $limit = chatroom_chat_old_msg_limit($chat->ccid);
-    $html .= theme('pager', array(), $limit);
+ * @param &$chat
+ *   The chat object to load messages into.
+ * @param $last_cmid
+ *   Only load messages with cmids greater than this value. If nonzero, then 
+ *   $archived = FALSE and $limit = FALSE are assumed. Default: 0 (load all
+ *   messages).
+ * @param $archived
+ *   Include archived messages. Default: FALSE.
+ * @param $limit
+ *   Limit the total number of messages to the old_msg_limit of the chatroom.
+ *   Default: FALSE.
+ * @return
+ *   The highest cmid of if any messages were loaded; FALSE if no messages were
+ *   loaded.
+*/
+function chatroom_load_messages(&$chat, $last_cmid = 0, $archived = FALSE, $limit = FALSE) {
+  // Load the chatroom for relevant settings.
+  $chat->chatroom = node_load($chat->nid);
+  // Avoid multiple function calls.
+  $sid = session_id();
+  // Basic query.
+  $query = '
+    SELECT
+      cm.*,
+      col.guest_id,
+      u.name
+    FROM {chatroom_msg} cm
+    LEFT JOIN {chatroom_online_list} col
+      ON col.sid LIKE cm.sid AND col.ccid = cm.ccid
+    LEFT JOIN {users} u ON u.uid = cm.uid
+    WHERE cm.ccid = %d
+  ';
+  $query_args = array($chat->ccid);
+  // TODO: document what this does. What is the logic behind the below?
+  // Select:
+  //  * Messages to everyone (no set recipient)
+  //  * Messages from the current session
+  //  * Messages to the current user?
+  //  * Messages from the current user
+//  $query .= "
+//    AND (
+//      cm.recipient LIKE ''
+//      OR cm.sid LIKE '%s'
+//      OR cm.recipient LIKE ANY (
+//        SELECT guest_id FROM {chatroom_online_list} col
+//        LEFT JOIN {sessions} s ON s.sid = col.sid
+//        WHERE col.sid LIKE '%s' OR (s.uid = %d AND s.uid > 0)
+//      )
+//      OR (cm.uid = %d AND cm.uid > 0)
+//    )
+//  ";
+//  array_push($query_args, $sid, $sid, $user->uid, $user->uid);
+  // Exclude archived messages.
+  if ($last_cmid != 0) {
+    $query .= ' AND cm.archived = 0 AND cm.cmid > %d';
+    $query_args[] = $last_cmid;
+  }
+  $query .= ' ORDER BY cmid DESC';
+  // Limit number of messages, e.g. on initial display of a chatroom.
+  if ($last_cmid == 0 && $limit) {
+    $query .= ' LIMIT %d';
+    $query_args[] = $chat->old_msg_count;
+  }
+  // Query and process results
+  $result = db_query($query, $query_args);
+  $messages = array();
+  $max_cmid = 0;
+  while ($message = db_fetch_object($result)) {
+    _chatroom_message_prepare($message);
+    $max_cmid = ($message->cmid > $max_cmid) ? $message->cmid : $max_cmid;
+    array_unshift($messages, $message);
+  }
+  // Assign any messages to the chat object and return.
+  if ($result !== FALSE && !empty($messages)) {
+    $chat->messages = $messages;
+    return $max_cmid;
   }
   else {
-    $html .= '<p>'. t('This chat contains no messages.') .'</p>';
+    return FALSE;
   }
-  return $html;
 }
 
 /**
- * get all msgs for a given chat
+ * Load online users for the given chat.
  */
-function chatroom_get_all_msgs($chat_id) {
+function chatroom_load_users(&$chat) {
   global $user;
-  $msgs = FALSE;
-  $limit = chatroom_chat_old_msg_limit($chat_id);
-  if (!empty($limit)) {
-    $query = "
-      (
-        SELECT cma.*, u.name, col.guest_id FROM {chatroom_msg_archive} cma
-        LEFT JOIN {chatroom_online_list} col ON col.session_id = cma.session_id AND col.ccid = cma.ccid
-        LEFT JOIN {users} u ON u.uid = cma.uid
-        WHERE cma.ccid = %d AND (
-          cma.recipient = '' OR cma.session_id = '%s' OR cma.recipient IN (
-            SELECT guest_id FROM {chatroom_online_list}
-            WHERE session_id = '%s' OR (uid = %d AND uid > 0)
-          ) OR (cma.uid = %d AND cma.uid > 0)
-        )
-      )
-      UNION
-      (
-        SELECT cm.*, u.name, col.guest_id FROM {chatroom_msg} cm
-        LEFT JOIN {chatroom_online_list} col ON col.session_id = cm.session_id AND col.ccid = cm.ccid
-        LEFT JOIN {users} u ON u.uid = cm.uid
-        WHERE cm.ccid = %d AND (
-          cm.recipient = '' OR cm.session_id = '%s' OR cm.recipient IN (
-            SELECT guest_id FROM {chatroom_online_list}
-            WHERE session_id = '%s' OR (uid = %d AND uid > 0)
-          ) OR (cm.uid = %d AND cm.uid > 0)
-        )
-      )
-      ORDER BY cmid ASC
-    ";
-    $count_query = "
-      SELECT
-      (
-        SELECT COUNT(*) FROM {chatroom_msg_archive}
-        WHERE ccid = %d AND (
-          recipient = '' OR session_id = '%s' OR recipient IN (
-            SELECT guest_id FROM {chatroom_online_list}
-            WHERE session_id = '%s' OR (uid = %d AND uid > 0)
-          ) OR (uid = %d AND uid > 0)
-        )
-      )
-      +
-      (
-        SELECT COUNT(*) FROM {chatroom_msg}
-        WHERE ccid = %d AND (
-          recipient = '' OR session_id = '%s' OR recipient IN (
-            SELECT guest_id FROM {chatroom_online_list}
-            WHERE session_id = '%s' OR (uid = %d AND uid > 0)
-          ) OR (uid = %d AND uid > 0)
-        )
-      )
-    ";
-    $arguments = array(
-      $chat_id,
-      session_id(),
-      session_id(),
-      $user->uid,
-      $user->uid,
-      $chat_id,
-      session_id(),
-      session_id(),
-      $user->uid,
-      $user->uid,
-    );
-    $result = pager_query($query, $limit, 0, $count_query, $arguments);
-    $msgs = array();
-    while ($message = db_fetch_object($result)) {
-      if (function_exists('_smileys_list') && variable_get('chatroom_smileys_enabled', FALSE)) {
-        $message->msg = smileys_filter_process($message->msg);
-      }
-      $msgs[] = $message;
+  // Time out users
+  $result = db_query("
+    DELETE FROM {chatroom_online_list}
+    WHERE
+      modified < %d - ((
+        SELECT cr.poll_freq
+        FROM {chatroom_chat} cc
+        JOIN {chatroom} cr ON cr.nid = cc.crid
+        WHERE cc.ccid = %d
+      )/100)
+      AND away = 0
+      AND ccid = %d
+    ", time(), $chat->ccid, $chat->ccid);
+  // Load users
+  $result = db_query("
+    SELECT
+      s.uid,
+      u.name,
+      col.guest_id
+    FROM {chatroom_online_list} col
+    JOIN {sessions} s ON s.sid = col.sid
+    LEFT JOIN {users} u ON u.uid = s.uid
+    WHERE col.ccid = %d
+    ORDER BY u.name ASC
+  ", $chat->ccid);
+  $users = array();
+  while ($u = db_fetch_object($result)) {
+    if (empty($u->uid)) {
+      $u->name = variable_get('chatroom_guest_user_prefix', 'guest-') . $u->guest_id;
+    }
+    if ($u->uid != $user->uid) {
+      array_unshift($users, $u);
+    }
+    else {
+      $users[] = $u;
     }
   }
-  return $msgs;
-}
-
-/**
- * updates chat's cache file modified time
- */
-function chatroom_chat_update_cache($cache_file) {
-  if (@touch($cache_file)) {
-    return @filemtime($cache_file);
+  if ($result !== FALSE) {
+    $chat->users = $users;
+  }
+  else {
+    return FALSE;
   }
-  return FALSE;
 }
 
 /**
- * update a users online timestamp value
+ * Save a message to the database.
  */
-function chatroom_chat_update_online_time($chat_id) {
+function chatroom_save_message($chat, $message, $type = 'message', $recipient = '') {
   global $user;
   db_query("
-    UPDATE {chatroom_online_list} SET modified = %d
-    WHERE ccid = %d AND uid = %d AND session_id = '%s'
-  ", time(), $chat_id, $user->uid, session_id());
+    INSERT INTO {chatroom_msg}
+      (ccid, uid, msg_type, msg, sid, recipient, modified)
+    VALUES (%d, %d, '%s', '%s', '%s', '%s', %d)
+  ", $chat->ccid, $user->uid, $type, $message, session_id(), $recipient, time());
 }
 
 /**
- * Get all messages for chat room after last message.
+ * Prepare a message to be themed.
+ *
+ * This function is analogous to but NOT an implementation of hook_prepare().
  */
-function chatroom_chat_get_latest_msgs($chat_id, $uid, $last_msg_id = 0, $timezone = 0, $smileys = FALSE) {
-  global $user;
-  $msgs = array();
-  $limit = chatroom_chat_old_msg_limit($chat_id, $last_msg_id);
-  if (!empty($limit)) {
-    $result = db_query_range("
-      SELECT cm.*, u.name, col.guest_id FROM {chatroom_msg} cm
-      INNER JOIN {chatroom_online_list} col ON col.session_id = cm.session_id AND col.ccid = cm.ccid
-      LEFT JOIN {users} u ON u.uid = cm.uid
-      WHERE cm.ccid = %d AND (
-        cm.recipient = '' OR cm.session_id = '%s' OR cm.recipient IN (
-          SELECT guest_id FROM {chatroom_online_list}
-          WHERE session_id = '%s' OR (uid = %d AND uid > 0)
-        ) OR (cm.uid = %d AND cm.uid > 0)
-      )
-      ORDER BY cm.cmid DESC
-    ", array(
-      $chat_id,
-      session_id(),
-      session_id(),
-      $user->uid,
-      $user->uid,
-    ), 0, $limit);
-    while ($message = db_fetch_object($result)) {
-      if ($smileys) {
-        $message->msg = chatroom_smileys_filter_process($message->msg);
-      }
-      $name = empty($message->name) ? _chatroom_variable_get('chatroom_guest_user_prefix', 'guest-') . $message->guest_id : $message->name;
-      $time = gmdate(_chatroom_variable_get('chatroom_chat_date_format', '* \S\e\n\t \a\t G:i'), $message->modified + $timezone);
-      $msg = array(
-        'id' => (int) $message->cmid,
-        'text' => stripslashes(str_replace(array("\\", '"'), array("\\\\", '\"'), $message->msg)),
-        'user' => $name,
-        'time' => $time,
-        'recipient' => $message->recipient,
-        'type' => $message->msg_type,
-      );
-      $msgs[] = $msg;
-    }
-    $msgs = array_reverse($msgs);
+function _chatroom_message_prepare(&$message) {
+  // Array for display content.
+  $message->content = array();
+  if (empty($message->name)) {
+    // Guest name.
+    $message->name = variable_get('chatroom_guest_user_prefix', 'guest-') . $message->guest_id;
+    $message->content['user'] = $message->name;
+  }
+  else {
+    // Link to user profile.
+    $message->content['user'] = l($message->name, 'user/'. $message->uid);
   }
-  return $msgs;
+  // Date. TODO: use configurable date.
+  $message->content['timestamp'] = format_date($message->modified);
+  // Apply filters to message. TODO: determine if this includes smileys.
+  $message->content['message'] = check_markup($message->msg, FILTER_FORMAT_DEFAULT, FALSE);
 }
 
 /**
- * Get the number of old messages to show when entering a chat
+ * updates chat's cache file modified time
  */
-function chatroom_chat_old_msg_limit($chat_id, $last_msg_id = 0) {
-  $limit = db_result(db_query('
-    SELECT cr.old_msg_count FROM {chatroom} cr
-    INNER JOIN {chatroom_chat} cc ON cc.crid = cr.crid
-    WHERE cc.ccid = %d
-  ', $chat_id));
-  $count = db_result(db_query("SELECT COUNT(*) FROM {chatroom_msg} WHERE ccid = %d AND cmid > %d", $chat_id, $last_msg_id));
-  $count += db_result(db_query("SELECT COUNT(*) FROM {chatroom_msg_archive} WHERE ccid = %d AND cmid > %d", $chat_id, $last_msg_id));
-  return $count > $limit ? $limit : $count;
+function chatroom_chat_update_cache($cache_file) {
+  if (@touch($cache_file)) {
+    return @filemtime($cache_file);
+  }
+  return FALSE;
 }
 
 /**
- * Replacement for variable_get because configuration variables are not loaded in session bootstrap
+ * Update the online timestamp value for the current user
  */
-function _chatroom_variable_get($name, $default) {
-  $result = db_query_range("SELECT value FROM {variable} WHERE name = '%s'", $name, 0, 1);
-  if (db_num_rows($result)) {
-    $default = unserialize(db_result($result));
-  }
-  return $default;
+function chatroom_update_online_time($chat, $away = FALSE) {
+  db_query("
+    UPDATE {chatroom_online_list}
+    SET modified = %d, away = %d
+    WHERE ccid = %d AND sid = '%s'
+  ", time(), $away, $chat->ccid, session_id());
 }
 
 /**
@@ -1727,7 +996,7 @@
  * - because we don't want to send html, just data about image
  */
 function chatroom_smileys_filter_process($text) {
-  if (function_exists('_smileys_list') && _chatroom_variable_get('chatroom_smileys_enabled', FALSE)) {
+  if (function_exists('_smileys_list') && variable_get('chatroom_smileys_enabled', FALSE)) {
     $text = ' '. $text .' ';
     $list = _smileys_list();
     $marker = '------';
@@ -1745,89 +1014,46 @@
         }
       }
     }
-    $text = substr($text, 1, -1);
+    $text = drupal_substr($text, 1, -1);
   }
   return $text;
 }
 
 /**
- * sets the chat's cache file by touching it
- */
-function chatroom_chat_set_cache($chat_id) {
-  $chat_file = _chatroom_get_cache_file("chat.$chat_id");
-  if (!file_exists($chat_file)) {
-    @touch($chat_file);
-  }
-}
-
-/**
- * Returns a chat room object for $chat_id.
+ * Returns a chat room object for $ccid.
  */
-function chatroom_chat_get_from_id($chat_id, $save_query = TRUE) {
-  if ($chat_id !== FALSE) {
-    global $user;
-    $sql = "
+function chatroom_load_chat($ccid) {
+  if (is_numeric($ccid)) {
+    $result = db_query_range("
       SELECT
         cc.*,
         n.title AS chatroom_name,
         n.nid,
         cr.poll_freq,
         cr.idle_freq,
+        cr.old_msg_count,
         cr.kicked_out_message,
         cr.banned_message
       FROM {chatroom_chat} cc
-      INNER JOIN {chatroom} cr ON cr.crid = cc.crid
+      INNER JOIN {chatroom} cr ON cr.nid = cc.crid
       INNER JOIN {node} n ON n.nid = cr.nid
       WHERE cc.ccid = %d
-    ";
-    if ($save_query) {
-      $sql = db_rewrite_sql($sql);
-      variable_set("chatroom_chat_get_from_id_query_{$user->uid}", $sql);
-    }
-    else {
-      $sql = _chatroom_variable_get("chatroom_chat_get_from_id_query_{$user->uid}", $sql);
-    }
-    $result = db_query_range($sql, $chat_id, 0, 1);
+    ", $ccid, 0, 1);
     return db_fetch_object($result);
   }
-  return FALSE;
-}
-
-/**
- * updates a users online time
- */
-function chatroom_site_update_online_time($uid) {
-  db_query('UPDATE {users} SET access = %d WHERE uid = %d', time(), $uid);
-}
-
-/**
- * updates a block cache file
- */
-function chatroom_block_update_cache($type) {
-  @touch(_chatroom_get_cache_file($type));
-}
-
-/**
- * get block cache file
- */
-function _chatroom_get_cache_file($type) {
-  $path = file_directory_temp() .'/drupal.chatroom.'. session_name();
-  if (file_check_directory($path, TRUE)) {
-    $path .= "/$type";
-  }
   else {
-    $path .= ".$type";
+    return FALSE;
   }
-  return $path;
 }
 
+
 /**
  * gets a list of online users, not including the current user
  */
 function chatroom_get_site_online_list($uid) {
   $users = array();
-  $time_period = 2 * _chatroom_variable_get('chatroom_block_update_interval', 5);
-  $result = db_query("SELECT uid, name FROM {users} WHERE access >= %d AND uid != 0", time() - $time_period);
+  $time_period = 2 * variable_get('chatroom_block_update_interval', 5);
+  $result = db_query("SELECT uid, name FROM {users} WHERE access >= %d AND uid <> 0", time() - $time_period);
   while ($user = db_fetch_object($result)) {
     if ($uid != $user->uid) {
       $users[] = $user;
@@ -1837,45 +1063,40 @@
 }
 
 /**
- * Checks if the user is banned from the chat room - returns false if not.
+ * Check if the current user is banned from the chat room.
  */
-function chatroom_is_banned_user($crid) {
+function chatroom_is_banned_user($room) {
   global $user;
-  static $result = NULL;
-  if (!is_null($result)) {
-    return $result;
-  }
-  if ($user->uid == 0) {
-    $result = FALSE;
+  // If the node is already loaded, check the ban list. Otherwise, query the
+  // database.
+  if (isset($room->banned_list) && is_array($room->banned_list)) {
+    return in_array($user->uid, array_keys($room->banned_list));
   }
   else {
-    $result = db_num_rows(db_query_range("SELECT crid FROM {chatroom_ban_list} WHERE crid = %d AND uid = %d", $crid, $user->uid, 0, 1));
+    $result = db_query_range("
+      SELECT COUNT(nid) FROM {chatroom_ban_list}
+      WHERE nid = %d AND uid = %d
+    ", $room->nid, $user->uid, 0, 1);
+    return db_result($result);
   }
-  return $result;
 }
 
 /**
  * returns the list of available chat rooms
  */
-function chatroom_get_chatroom_list($archived = FALSE, $start = NULL, $end = NULL, $save_query = TRUE) {
-  global $user;
+function chatroom_get_room_list($archived = FALSE, $start = NULL, $end = NULL) {
   $sql = '
-    SELECT nr.*, COUNT(cc.ccid) AS chat_count FROM {chatroom} cr
-    INNER JOIN {node_revisions} nr ON nr.nid = cr.nid
-    INNER JOIN {node} n ON n.nid = nr.nid
-    INNER JOIN {chatroom_chat} cc ON cc.crid = cr.crid
+    SELECT
+      cr.nid,
+      n.title,
+      COUNT(cc.ccid) AS chat_count
+    FROM {chatroom} cr
+    INNER JOIN {node} n ON n.nid = cr.nid
+    INNER JOIN {node_revisions} nr ON nr.nid = n.nid
+    LEFT JOIN {chatroom_chat} cc ON cc.crid = cr.nid
   ';
   $sql .= $archived ? '' : ' WHERE cc.when_archived IS NULL';
-  $sql .= ' GROUP BY cr.crid ORDER BY n.sticky DESC, nr.timestamp DESC';
-  $variable = 'chatroom_get_chatroom_list_';
-  $variable .= $archived ? "archived_query_{$user->uid}" : "query_{$user->uid}";
-  if ($save_query) {
-    $sql = db_rewrite_sql($sql);
-    variable_set($variable, $sql);
-  }
-  else {
-    $sql = _chatroom_variable_get($variable, $sql);
-  }
+  $sql .= ' GROUP BY cr.nid, n.title, n.sticky, nr.timestamp ORDER BY n.sticky DESC, nr.timestamp DESC';
   if (isset($start) && !empty($end)) {
     $result = db_query_range($sql, $start, $end);
   }
@@ -1883,61 +1104,61 @@
     $result = db_query($sql);
   }
   $rooms = array();
-  if (db_num_rows($result) > 0) {
-    while ($room = db_fetch_object($result)) {
-      $rooms[] = $room;
-    }
+  while ($room = db_fetch_object($result)) {
+    $rooms[] = $room;
   }
   return $rooms;
 }
 
 /**
  * Get a list of online users in a given chat.
- * Return the current user at the top of the list.
- * Users who do not update within five seconds after the last message is
- * sent are dropped from the online list.
+ *
+ * The current user is at the top of the returned list. Users who do not update
+ * within five seconds after the last message is sent are dropped from the
+ * online list.
  */
-function chatroom_chat_get_online_list($chat_id, $cache_timestamp, $update_count = 7) {
+function _chatroom_get_online_list($chat) {
   $users = array();
-  if ($chat = chatroom_chat_get_from_id($chat_id, FALSE) && !isset($chat->when_archived)) {
+  // No users in an archived chat.
+  if ($chat = chatroom_load_chat($ccid) && !isset($chat->when_archived)) {
+    // Update the 'modified' time of the chat to match the cache (???)
+/*
+    $cache_timestamp = empty($cache_timestamp) ? time() : $cache_timestamp;
     if ($cache_timestamp < time() - 5) {
-      db_query("UPDATE {chatroom_chat} SET modified = %d WHERE ccid = %d", $cache_timestamp, $chat_id);
+      db_query("UPDATE {chatroom_chat} SET modified = %d WHERE ccid = %d", $cache_timestamp, $ccid);
     }
+*/
     $result = db_query("
       SELECT
-        col.uid,
-        col.session_id,
-        col.guest_id,
-        col.away,
+        col.sid, col.guest_id, col.away,
         cc.chatname,
-        du.name
+        u.uid, u.name
       FROM {chatroom_online_list} col
       INNER JOIN {chatroom_chat} cc ON cc.ccid = col.ccid
-      LEFT JOIN {users} du ON du.uid = col.uid
+      INNER JOIN {sessions} s ON s.sid = col.sid
+      LEFT JOIN {users} u ON u.uid = s.uid
       WHERE col.ccid = %d AND col.modified > cc.modified
-    ", $chat_id);
-    while ($row = db_fetch_object($result)) {
-      $name = $row->name ? check_plain($row->name) : _chatroom_variable_get('chatroom_guest_user_prefix', 'guest-') . $row->guest_id;
-      $user = array(
-        'user' => $name,
-        'uid' => $row->uid,
-        'guestId' => $row->guest_id,
-        'away' => (int) $row->away,
-      );
-      if ($row->session_id == session_id()) {
-        $user['self'] = TRUE;
+    ", $chat->ccid);
+    while ($user = db_fetch_object($result)) {
+      // Format the user or guest name.
+      if (empty($user->name)) {
+        $user->name = variable_get('chatroom_guest_user_prefix', 'guest-') . $row->guest_id;
+      }
+      else {
+        $user->name = check_plain($user->name);
+      }
+      // Store the current user separately.
+      if ($user->sid == session_id()) {
         $current_user = $user;
       }
       else {
         $users[] = $user;
       }
     }
+    // Add the current user at the head of the list.
     if (isset($current_user)) {
       array_unshift($users, $current_user);
     }
-    if ($update_count < 7) {
-      $users = array($current_user);
-    }
   }
   return $users;
 }
diff -Naur chatroom/chatroom.theme.inc chatroom-6.x-dev/chatroom.theme.inc
--- chatroom/chatroom.theme.inc	1969-12-31 19:00:00.000000000 -0500
+++ chatroom-6.x-dev/chatroom.theme.inc	2008-10-18 21:14:27.000000000 -0400
@@ -0,0 +1,399 @@
+<?php
+// $Id$
+
+/**
+ * $file
+ * Theme functions for chatroom.module.
+ */
+
+/**
+ * Implementation of hook_theme().
+ */
+function chatroom_theme() {
+  // Blocks provided by the module.
+  $theme['chatroom_block_commands']   = array('arguments' => array());
+  $theme['chatroom_block_chats']      = array('arguments' => array());
+  $theme['chatroom_block_rooms']      = array('arguments' => array());
+  $theme['chatroom_block_chat_list']  = array('arguments' => array());
+  $theme['chatroom_block_site_list']  = array('arguments' => array());
+  // Message theming for display in an active chat.
+  $theme['chatroom_messages']         = array('arguments' => array('messages'));
+  $theme['chatroom_message']          = array('arguments' => array('message'));
+  // User list theming for display in an active chat.
+  $theme['chatroom_user_list']        = array('arguments' => array('users'));
+  // Information about the latest message, for use in a chat list.
+  $theme['chatroom_last_message']     = array('arguments' => array('message'));
+  $theme['chatroom_chat_kicked_user'] = array('arguments' => array('chat'));
+  $theme['chatroom_chat_banned_user'] = array('arguments' => array('chat'));
+  // List of all chatrooms.
+  $theme['chatroom_display']          = array('arguments' => array('tree'));
+  // List of chats in a chatroom.
+  $theme['chatroom_list']             = array('arguments' => array('tree'));
+  // Actual chats and archives.
+  $theme['chatroom_chat']             = array('arguments' => array('chat'));
+  $theme['chatroom_chat_archive']     = array('arguments' => array('chat'));
+  return $theme;
+}
+
+/**
+ * Theme the chat commands block.
+ *
+ * @ingroup themeable
+ */
+function theme_chatroom_block_commands() {
+  $content = '/msg <em>'. t('username') .'</em> '. t('message');
+  if (user_access('administer chats')) {
+    $content .= '<br />/kick <em>'. t('username') .'</em>';
+    $content .= '<br />/ban <em>'. t('username') .'</em>';
+  }
+  $content .= '<br />/me '. t('message');
+  $content .= '<br />/away';
+  $content .= '<br />/back';
+  $block = array('content' => $content, 'subject' => t('Chat commands'));
+  return $block;
+}
+
+/**
+ * Theme the site-wide chat list block.
+ *
+ * @ingroup themeable
+ */
+function theme_chatroom_block_chats() {
+  $content = '<ul class="menu" id="chatroom-sitewide-chats">';
+  $chats = chatroom_get_active_chats(0, variable_get('chatroom_block_chats', 5));
+  if ($chats) {
+    foreach ($chats as $chat) {
+      $chat_link = l($chat->chatname, "chatrooms/chat/$chat->ccid");
+      $room_link = l($chat->room_name, "node/$chat->nid");
+      $content .= '<li id="chat_'. $chat->ccid .'">'. $chat_link .'<br />';
+      $content .= '<span class="chatroomLink">'. t('in') .' '. $room_link .'</span></li>';
+    }
+  }
+  else {
+    $content .= '<li id="chat_empty"><em>'. t('There are no active chats.') .'</em></li>';
+  }
+  $content .= '</ul>';
+  return array(
+    'content' => $content,
+    'subject' => t('Active chats'),
+  );
+}
+
+/**
+ * Theme the chat room list block.
+ *
+ * @ingroup themeable
+ */
+function theme_chatroom_block_rooms() {
+  $content = '<ul class="menu" id="chatroom-sitewide-chatrooms">';
+  $rooms = chatroom_get_chatroom_list(FALSE, 0, variable_get('chatroom_block_chat_rooms', 5));
+  if (empty($rooms)) {
+    $content .= '<li id="chatroom_empty"><em>'. t('There are no active chat rooms.') .'</em></li>';
+  }
+  else {
+    foreach ($rooms as $room) {
+      $content .= '<li id="chatroom_'. $room->nid .'">'. l($room->title, "node/$room->nid") .'</li>';
+    }
+  }
+  $content .= '</ul>';
+  return array(
+    'content' => $content,
+    'subject' => t('Active chat rooms'),
+  );
+}
+
+/**
+ * Theme the block listing on-line users in the current chat.
+ *
+ * @ingroup themeable
+ */
+function theme_chatroom_block_chat_users() {
+  $chat_id = $matches[1];
+  $cache_file = _chatroom_get_cache_file("chat.$chat_id");
+  $cache_timestamp = @filemtime($cache_file);
+  $users = chatroom_chat_get_online_list($chat_id, $cache_timestamp, 0);
+  $title = '';
+  $content = '';
+  if (!empty($users)) {
+    $chatname = db_result(db_query("SELECT chatname FROM {chatroom_chat} WHERE ccid = %d", $chat_id));
+    $title = t('Who is on line in %chat', array('%chat' => $chatname));
+    $content = '<ul class="menu" id="chatroom-online">';
+    foreach ($users as $user) {
+      $content .= '<li id="'. $user['guestId'] .'"';
+      $content .= $user['away'] ? ' class="chatroom-user-away">' : '>';
+      $content .= '<a href="javascript:Drupal.chatroom.chat.selectUser(\''. $user['user'] .'\')">'. $user['user'] .'</a></li>';
+    }
+    $content .= '</ul>';
+    if (variable_get('chatroom_alerts', FALSE)) {
+      $checked = variable_get('chatroom_alerts_default', FALSE) ? ' checked' : '';
+      $content .= '<div id="chatroom-user-options">';
+      $content .= '<label><input type="checkbox"'. $checked .' id="chatroom-user-alert" /> ';
+      $content .= t('Alert me if new users enter.') .'</label></div>';
+    }
+  }
+  return array(
+    'content' => $content,
+    'subject' => $title,
+  );
+}
+
+/**
+ * Theme the block listing online users for the whole site.
+ *
+ * @ingroup themeable
+ */
+function theme_chatroom_block_site_users() {
+  global $user;
+  $uid = $user->uid;
+  chatroom_site_update_online_time($uid);
+  $users = chatroom_get_site_online_list($uid);
+  $content = '<ul class="menu" id="chatroom-sitewide-online">';
+  if (!empty($users)) {
+    foreach ($users as $u) {
+      $content .= '<li id="user-li-'. $u->uid .'"><strong>'. check_plain($u->name) .'</strong></li>';
+    }
+  }
+  else {
+    $content .= '<li id="no_users"><em>'. t('There are no other users on line.') .'</em></li>';
+  }
+  $content .= '</ul>';
+  return array(
+    'content' => $content,
+    'subject' => t('On-line users'),
+  );
+}
+
+/**
+ * Format an array of messages.
+ *
+ * @ingroup themeable
+ */
+function theme_chatroom_messages($messages) {
+  $output = '';
+  if (is_array($messages)) {
+    foreach($messages as $message) {
+      $output .= theme('chatroom_message', $message);
+    }
+  }
+  return $output;
+}
+
+/**
+ * @ingroup themeable.
+ */
+function theme_chatroom_last_message() {}
+
+/**
+ * Format a single message for display.
+ *
+ * TODO: use a configurable format.
+ *
+ * @ingroup themeable
+ */
+function theme_chatroom_message($message) {
+  $output = '<div class="chatroom-msg">';
+  $output .= '<strong>'. $message->content['user'] .':</strong> ';
+  $output .= $message->content['message'];
+  $output .= '</div>';
+  return $output;
+}
+
+/**
+ * Format the list of users in a chatroom.
+ *
+ * @ingroup themeable
+ */
+function theme_chatroom_user_list($users) {
+  if (!empty($users)) {
+    foreach ($users as $user) {
+      $items[] = theme('username', $user);
+    }
+  }
+  $output = theme('item_list', $items);
+  return str_replace('class="item-list"', 'id="chatroom-user-list" class="item-list"', $output);
+}
+
+/**
+ * Format a message for display in a summary table.
+ */
+function theme_chatroom_latest($message) {
+  if (function_exists('_smileys_list') && variable_get('chatroom_smileys_enabled', FALSE)) {
+    $msg->msg = smileys_filter_process($message->msg);
+  }
+  $output = $message->msg .'<br />';
+  $output .= t('Posted by <strong>!user</strong> on !date', array(
+    '!user' => $message->uid ? l($message->name, "user/$message->uid") : variable_get('chatroom_guest_user_prefix', t('guest-')) . $message->guest_id,
+    '!date' => format_date($message->modified, 'medium'),
+  ));
+  return $output;
+}
+
+/**
+ * Format the chat room listing.
+ *
+ * @ingroup themeable
+ */
+function theme_chatroom_display($tree) {
+  global $user;
+  // chat room list, chats list, and 'add new chat' link
+
+  if (!empty($tree)) {
+    $output  = '<div id="chatroom">';
+    $output .= '<ul>';
+
+    if (user_access('create chat rooms')) {
+      $output .= '<li>'. l(t('Post a new chat room.'), "node/add/chatroom") .'</li>';
+    }
+    else if ($user->uid) {
+      //
+    }
+    else {
+      $output .= '<li>'. t('<a href="!login">Login</a> to post a new chat room.', array('!login' => url('user/login'))) .'</li>';
+    }
+    $output .= '</ul>';
+    $output .= theme('chatroom_list', $tree);
+    $output .= '</div>';
+  }
+  else {
+    drupal_set_title(t('No chat rooms defined'));
+    $output = '';
+  }
+
+  return $output;
+}
+
+/**
+ * Format the chat room table.
+ *
+ * @ingroup themeable
+ */
+function theme_chatroom_list($rooms) {
+  global $user;
+
+  if ($rooms) {
+    $header = array(t('Chat room'), t('Chats'), t('Messages'), t('Last message'));
+    foreach ($rooms as $room) {
+      $description  = "<div>\n";
+      $description .= ' <div class="name">'. l($room->title, "node/$room->nid") .'</div>';
+
+      if ($room->body) {
+        $description .= ' <div class="description">'. $room->body ."</div>\n";
+      }
+      $description .= "</div>\n";
+
+      $rows[] = array(array('data' => $description, 'class' => 'container', 'colspan' => '4'));
+
+      if (isset($room->chatroom->chats)) {
+        foreach ($room->chatroom->chats as $id => $chat) {
+          $description  = "<div>\n";
+          if (isset($chat->when_archived)) {
+            $description .= ' <div class="name">'. l($chat->chatname, "chatrooms/archives/$chat->ccid") ."</div>\n";
+          }
+          else {
+            $description .= ' <div class="name">'. l($chat->chatname, "chatrooms/chat/$chat->ccid") ."</div>\n";
+          }
+          $description .= "</div>\n";
+
+          $row[] = array('data' => '&nbsp;');
+          $row[] = array('data' => $description, 'class' => 'chatroom-chat');
+          $row[] = array('data' => $chat->msg_count, 'class' => 'chatroom-msg-count');
+          $row[] = array('data' => $chat->msg_info, 'class' => 'chatrom-msg-info');
+          $rows[] = $row;
+          unset($row);
+        }
+      }
+    }
+    return theme('table', $header, $rows);
+  }
+}
+
+/**
+ * Get HTML for kick message.
+ *
+ * @ingroup themeable
+ */
+function theme_chatroom_chat_kicked_user($chat) {
+  $msg = $chat->kicked_out_message ? $chat->kicked_out_message : t('You have been kicked out of %chat for misbehaving.', array('%chat' => $chat->chatname));
+  return '<div id="chatroom-kicked-msg">'. $msg .'</div>';
+}
+
+/**
+ * Get HTML for ban message.
+ *
+ * @ingroup themeable
+ */
+function theme_chatroom_chat_banned_user($chat) {
+  $msg = $chat->banned_message ? $chat->banned_message : t('You have been banned from %chatroom.', array('%chatroom' => $chat->chatroom_name));
+  return '<div id="chatroom-banned-msg">'. $msg .'</div>';
+}
+
+/**
+ * Theme a chat.
+ *
+ * @ingroup themeable
+ */
+function theme_chatroom_chat($chat) {
+  $output = '<p>'. l(t('View old messages'), "chatroom/archive/{$chat->ccid}") .'</p>';
+  $output .= drupal_get_form('chatroom_chat_form', $chat);
+  return $output;
+}
+
+/**
+ * Theme the chat archive page.
+ *
+ * @ingroup themeable
+ */
+function theme_chatroom_chat_archive($chat) {
+  $html = '';
+  if (isset($chat->when_archived)) {
+    $html = '<p>'. t('Archived on !date.', array('!date' => format_date($chat->when_archived, 'medium'))) .'</p>';
+  }
+  else {
+    $html = '<p>'. l(t('Join !chat', array('!chat' => $chat->chatname)), "chatroom/chat/$chat->ccid") .'</p>';
+  }
+  $previous = '';
+  if ($last_cmid = chatroom_load_messages($chat, 0, TRUE)) {
+    $html .= '<div id="chatroom-container-archive"><table><tbody valign="top">';
+    foreach ($chat->messages as $msg) {
+      $name = $msg->name ? $msg->name : variable_get('chatroom_guest_user_prefix', t('guest-')) . $msg->guest_id;
+      $html .= '<tr><td><span class="chatroom-archive-date">'. format_date($msg->modified, 'small') .'</span></td>';
+      if ($msg->msg_type == 'me') {
+        $html .= '<td><p class="chatroom-old-me-msg">';
+        $html .= "* $name {$msg->msg}</p></td>";
+      }
+      else {
+        $html .= '<td><p class="chatroom-old-msg">';
+        if (empty($msg->recipient)) {
+          if ($previous != $name) {
+            $html .= '<span class="header">'."$name:</span>";
+            $previous = $name;
+          }
+        }
+        else {
+          $html .= '<span class="header">'."$name ";
+          $recipient = db_result(db_query("
+            SELECT u.name FROM {chatroom_online_list} col
+            LEFT JOIN {users} u ON u.uid = col.uid
+            WHERE col.ccid = %d AND col.guest_id = %d
+          ", $msg->ccid, $msg->guest_id));
+          $recipient = empty($recipient) ? variable_get('chatroom_guest_user_prefix', t('guest-')) . $msg->recipient : $recipient;
+          if ($msg->session_id == session_id() && $recipient != $name) {
+            $html .= '<span class="chatroom-private">(privately to '. $recipient .')</span>:</span> ';
+          }
+          else {
+            $html .= '<span class="chatroom-private">(privately)</span>:</span> ';
+          }
+        }
+        $html .= "{$msg->msg}</p></td>";
+      }
+    }
+    $html .= '</tbody></table></div>';
+//  $limit = chatroom_chat_old_msg_limit($chat->ccid);
+    $limit = 100;
+    $html .= theme('pager', array(), $limit);
+  }
+  else {
+    $html .= '<p>'. t('This chat contains no messages.') .'</p>';
+  }
+  return $html;
+}
diff -Naur chatroom/nbproject/private/private.xml chatroom-6.x-dev/nbproject/private/private.xml
--- chatroom/nbproject/private/private.xml	1969-12-31 19:00:00.000000000 -0500
+++ chatroom-6.x-dev/nbproject/private/private.xml	2008-10-18 21:14:27.000000000 -0400
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project-private xmlns="http://www.netbeans.org/ns/project-private/1">
+    <editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/1"/>
+    <open-files xmlns="http://www.netbeans.org/ns/projectui-open-files/1">
+        <file>file:/var/www/drupal-6.4/sites/all/modules/chatroom/CHANGELOG.txt</file>
+        <file>file:/var/www/drupal-6.4/sites/all/modules/chatroom/INSTALL.txt</file>
+        <file>file:/var/www/drupal-6.4/sites/all/modules/chatroom/chatroom.block.js</file>
+        <file>file:/var/www/drupal-6.4/sites/all/modules/chatroom/chatroom.forms.inc</file>
+        <file>file:/var/www/drupal-6.4/sites/all/modules/chatroom/chatroom.info</file>
+        <file>file:/var/www/drupal-6.4/sites/all/modules/chatroom/chatroom.install</file>
+        <file>file:/var/www/drupal-6.4/sites/all/modules/chatroom/chatroom.js</file>
+        <file>file:/var/www/drupal-6.4/sites/all/modules/chatroom/chatroom.module</file>
+        <file>file:/var/www/drupal-6.4/sites/all/modules/chatroom/chatroom.theme.inc</file>
+        <file>file:/var/www/drupal-6.4/sites/all/modules/chatroom/chatroomread.php</file>
+        <file>file:/var/www/drupal-6.4/sites/all/modules/chatroom/soundmanager2.js</file>
+    </open-files>
+</project-private>
diff -Naur chatroom/nbproject/project.properties chatroom-6.x-dev/nbproject/project.properties
--- chatroom/nbproject/project.properties	1969-12-31 19:00:00.000000000 -0500
+++ chatroom-6.x-dev/nbproject/project.properties	2008-10-18 21:14:27.000000000 -0400
@@ -0,0 +1,8 @@
+copy.src.files=false
+copy.src.target=/var/www/chatroom
+include.path=${php.global.include.path}
+index.file=chatroomread.php
+run.as=LOCAL
+source.encoding=UTF-8
+src.dir=.
+url=http://localhost/drupal-6.4/?q=chatroom/
diff -Naur chatroom/nbproject/project.xml chatroom-6.x-dev/nbproject/project.xml
--- chatroom/nbproject/project.xml	1969-12-31 19:00:00.000000000 -0500
+++ chatroom-6.x-dev/nbproject/project.xml	2008-10-18 21:14:27.000000000 -0400
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://www.netbeans.org/ns/project/1">
+    <type>org.netbeans.modules.php.project</type>
+    <configuration>
+        <data xmlns="http://www.netbeans.org/ns/php-project/1">
+            <name>chatroom</name>
+        </data>
+    </configuration>
+</project>
diff -Naur chatroom/soundmanager2.js chatroom-6.x-dev/soundmanager2.js
--- chatroom/soundmanager2.js	2007-10-04 18:59:07.000000000 -0400
+++ chatroom-6.x-dev/soundmanager2.js	2008-10-18 21:14:27.000000000 -0400
@@ -1,16 +1,23 @@
-// $Id: soundmanager2.js,v 1.1.2.6 2007/10/04 22:59:07 darrenoh Exp $
-
-Drupal.chatroom = Drupal.chatroom || {};
-
-Drupal.chatroom.SoundManager = function(smURL,smID) {
-  var self = this;
-  this.version = 'V2.0b.20070415';
-  this.url = Drupal.settings.chatroom.basePath + Drupal.settings.chatroom.chatroomBase +'/soundmanager2.swf';
-
-  this.debugMode = false;           // enable debugging output (div#soundmanager-debug, OR console if available + configured)
-  this.useConsole = false;          // use firebug/safari console.log()-type debug console if available
+/*
+   SoundManager 2: Javascript Sound for the Web
+   --------------------------------------------
+   http://schillmania.com/projects/soundmanager2/
+
+   Copyright (c) 2008, Scott Schiller. All rights reserved.
+   Code licensed under the BSD License:
+   http://schillmania.com/projects/soundmanager2/license.txt
+
+   V2.76a.20080808
+*/
+
+function SoundManager(smURL,smID) {
+ 
+  this.flashVersion = 8;           // Version of flash to require, either 8 or 9. Some API features require Flash 9.
+  this.debugMode = true;           // enable debugging output (div#soundmanager-debug, OR console if available + configured)
+  this.useConsole = true;          // use firebug/safari console.log()-type debug console if available
   this.consoleOnly = false;        // if console is being used, do not create/write to #soundmanager-debug
-  this.nullURL = 'data/null.mp3';  // path to "null" (empty) MP3 file, used to unload sounds
+  this.waitForWindowLoad = false;  // force SM2 to wait for window.onload() before trying to call soundManager.onload()
+  this.nullURL = 'data/null.mp3';  // path to "null" (empty) MP3 file, used to unload sounds (Flash 8 only)
 
   this.defaultOptions = {
     'autoLoad': false,             // enable automatic loading (otherwise .load() will be called on demand with .play(), the latter being nicer on bandwidth - if you want to .load yourself, you also can)
@@ -20,6 +27,8 @@
     'onload': null,                // callback function for "load finished"
     'whileloading': null,          // callback function for "download progress update" (X of Y bytes received)
     'onplay': null,                // callback for "play" start
+    'onpause': null,               // callback for "pause"
+    'onresume': null,              // callback for "resume" (pause toggle)
     'whileplaying': null,          // callback during play (position update)
     'onstop': null,                // callback for "user stop"
     'onfinish': null,              // callback function for "sound finished playing"
@@ -29,18 +38,32 @@
     'onjustbeforefinish':null,     // callback for [n] msec before end of current sound
     'onjustbeforefinishtime':200,  // [n] - if not using, set to 0 (or null handler) and event will not fire.
     'multiShot': true,             // let sounds "restart" or layer on top of each other when played multiple times, rather than one-shot/one at a time
+    'position': null,              // offset (milliseconds) to seek to within loaded sound data.
     'pan': 0,                      // "pan" settings, left-to-right, -100 to 100
     'volume': 100                  // self-explanatory. 0-100, the latter being the max.
-  }
+  };
 
-  this.allowPolling = true;        // allow flash to poll for status update (required for "while playing", "progress" etc. to work.)
+  this.flash9Options = {           // FLASH 9-ONLY options, merged into defaultOptions if applicable
+    usePeakData: false,            // enable left/right channel peak (level) data
+    useWaveformData: false,        // enable sound spectrum (raw waveform data) - WARNING: CPU-INTENSIVE: may set CPUs on fire.
+    useEQData: false               // enable sound EQ (frequency spectrum data) - WARNING: Also CPU-intensive.
+  };
+
+  this.allowPolling = true;        // allow flash to poll for status update (required for "while playing", peak, sound spectrum functions to work.)
+
+  var self = this; 
+  this.version = null;
+  this.versionNumber = 'V2.76a.20080808';
+  this.movieURL = null;
+  this.url = null;
+  this.swfLoaded = false;
   this.enabled = false;
   this.o = null;
   this.id = (smID||'sm2movie');
   this.oMC = null;
   this.sounds = [];
   this.soundIDs = [];
-  this.isIE = (navigator.userAgent.match(/MSIE/));
+  this.isIE = (navigator.userAgent.match(/MSIE/i));
   this.isSafari = (navigator.userAgent.match(/safari/i));
   this.debugID = 'soundmanager-debug';
   this._debugOpen = true;
@@ -48,15 +71,53 @@
   this._appendSuccess = false;
   this._didInit = false;
   this._disabled = false;
+  this._windowLoaded = false;
   this._hasConsole = (typeof console != 'undefined' && typeof console.log != 'undefined');
-  this._debugLevels = !self.isSafari?['debug','info','warn','error']:['log','log','log','log'];
+  this._debugLevels = ['log','info','warn','error'];
+  this._defaultFlashVersion = 8;
+  this.features = {
+    peakData: false,
+    waveformData: false,
+    eqData: false
+  };
+  this.sandbox = {
+    'type': null,
+    'types': {
+      'remote': 'remote (domain-based) rules',
+      'localWithFile': 'local with file access (no internet access)',
+      'localWithNetwork': 'local with network (internet access only, no local access)',
+      'localTrusted': 'local, trusted (local + internet access)'
+    },
+    'description': null,
+    'noRemote': null,
+    'noLocal': null
+  };
+  this._setVersionInfo = function() {
+    if (self.flashVersion != 8 && self.flashVersion != 9) {
+      alert('soundManager.flashVersion must be 8 or 9. "'+self.flashVersion+'" is invalid. Reverting to '+self._defaultFlashVersion+'.');
+      self.flashVersion = self._defaultFlashVersion;
+    }
+    self.version = self.versionNumber+(self.flashVersion==9?' (AS3/Flash 9)':' (AS2/Flash 8)');
+    self.movieURL = (self.flashVersion==8?'soundmanager2.swf':'soundmanager2_flash9.swf');
+    self.features.peakData = self.features.waveformData = self.features.eqData = (self.flashVersion==9);
+  }
+  this._overHTTP = (document.location?document.location.protocol.match(/http/i):null);
+  this._waitingforEI = false;
+  this._initPending = false;
+  this._tryInitOnFocus = (this.isSafari && typeof document.hasFocus == 'undefined');
+  this._isFocused = (typeof document.hasFocus != 'undefined'?document.hasFocus():null);
+  this._okToDisable = !this._tryInitOnFocus;
+  var flashCPLink = 'http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html';
 
   // --- public methods ---
+  
+  this.supported = function() {
+    return (self._didInit && !self._disabled);
+  };
 
   this.getMovie = function(smID) {
-    // return self.isIE?window[smID]:document[smID];
-    return self.isIE?window[smID]:(self.isSafari?document[smID+'-embed']:document.getElementById(smID+'-embed'));
-  }
+    return self.isIE?window[smID]:(self.isSafari?document.getElementById(smID)||document[smID]:document.getElementById(smID));
+  };
 
   this.loadFromXML = function(sXmlUrl) {
     try {
@@ -64,55 +125,68 @@
     } catch(e) {
       self._failSafely();
       return true;
-    }
-  }
+    };
+  };
 
   this.createSound = function(oOptions) {
-    if (!self._didInit) throw new Error('Drupal.chatroom.soundManager.createSound(): Not loaded yet - wait for Drupal.chatroom.soundManager.onload() before calling sound-related methods');
+    if (!self._didInit) throw new Error('soundManager.createSound(): Not loaded yet - wait for soundManager.onload() before calling sound-related methods');
     if (arguments.length==2) {
       // function overloading in JS! :) ..assume simple createSound(id,url) use case
-      oOptions = {'id':arguments[0],'url':arguments[1]}
-    }
-    var thisOptions = self._mergeObjects(oOptions);
-    self._writeDebug('Drupal.chatroom.soundManager.createSound(): "<a href="#" onclick="Drupal.chatroom.soundManager.play(\''+thisOptions.id+'\');return false" title="play this sound">'+thisOptions.id+'</a>" ('+thisOptions.url+')',1);
+      oOptions = {'id':arguments[0],'url':arguments[1]};
+    };
+    var thisOptions = self._mergeObjects(oOptions); // inherit SM2 defaults
+    self._writeDebug('soundManager.createSound(): '+thisOptions.id+' ('+thisOptions.url+')',1);
     if (self._idCheck(thisOptions.id,true)) {
-      self._writeDebug('sound '+thisOptions.id+' already defined - exiting',2);
-      return false;
-    }
-    self.sounds[thisOptions.id] = new Drupal.chatroom.SMSound(self,thisOptions);
+      self._writeDebug('soundManager.createSound(): '+thisOptions.id+' exists',1);
+      return self.sounds[thisOptions.id];
+    };
+    self.sounds[thisOptions.id] = new SMSound(self,thisOptions);
     self.soundIDs[self.soundIDs.length] = thisOptions.id;
     try {
-      self.o._createSound(thisOptions.id,thisOptions.onjustbeforefinishtime);
+      // AS2:
+      if (self.flashVersion==8) {
+        self.o._createSound(thisOptions.id,thisOptions.onjustbeforefinishtime);
+      } else {
+        self.o._createSound(thisOptions.id,thisOptions.url,thisOptions.onjustbeforefinishtime,thisOptions.usePeakData,thisOptions.useWaveformData,thisOptions.useEQData);
+      }
     } catch(e) {
       self._failSafely();
       return true;
-    }
-    if (thisOptions.autoLoad || thisOptions.autoPlay) self.sounds[thisOptions.id].load(thisOptions);
-    if (thisOptions.autoPlay) self.sounds[thisOptions.id].playState = 1; // we can only assume this sound will be playing soon.
-  }
+    };
+    if (thisOptions.autoLoad || thisOptions.autoPlay) window.setTimeout(function(){self.sounds[thisOptions.id].load(thisOptions);},20);
+    if (thisOptions.autoPlay) {
+	  if (self.flashVersion == 8) {
+	    self.sounds[thisOptions.id].playState = 1; // we can only assume this sound will be playing soon.
+	  } else {
+	    self.sounds[thisOptions.id].play();	
+	  }
+	}
+    return self.sounds[thisOptions.id];
+  };
 
   this.destroySound = function(sID) {
     // explicitly destroy a sound before normal page unload, etc.
     if (!self._idCheck(sID)) return false;
-    for (var i=self.soundIDs.length; i--;) {
+    for (var i=0; i<self.soundIDs.length; i++) {
       if (self.soundIDs[i] == sID) {
-        delete self.soundIDs[i];
+	    self.soundIDs.splice(i,1);
         continue;
-      }
-    }
+      };
+    };
     self.sounds[sID].unload();
+    self.sounds[sID].destruct();
     delete self.sounds[sID];
-  }
+  };
 
   this.load = function(sID,oOptions) {
     if (!self._idCheck(sID)) return false;
     self.sounds[sID].load(oOptions);
-  }
+  };
 
   this.unload = function(sID) {
     if (!self._idCheck(sID)) return false;
     self.sounds[sID].unload();
-  }
+  };
 
   this.play = function(sID,oOptions) {
     if (!self._idCheck(sID)) {
@@ -124,93 +198,121 @@
         self.createSound(oOptions);
       } else {
         return false;
-      }
-    }
+      };
+    };
     self.sounds[sID].play(oOptions);
-  }
+  };
 
   this.start = this.play; // just for convenience
 
   this.setPosition = function(sID,nMsecOffset) {
     if (!self._idCheck(sID)) return false;
     self.sounds[sID].setPosition(nMsecOffset);
-  }
+  };
 
   this.stop = function(sID) {
     if (!self._idCheck(sID)) return false;
-    self._writeDebug('Drupal.chatroom.soundManager.stop('+sID+')',1);
-    self.sounds[sID].stop();
-  }
+    self._writeDebug('soundManager.stop('+sID+')',1);
+    self.sounds[sID].stop(); 
+  };
 
   this.stopAll = function() {
-    self._writeDebug('Drupal.chatroom.soundManager.stopAll()',1);
+    self._writeDebug('soundManager.stopAll()',1);
     for (var oSound in self.sounds) {
       if (self.sounds[oSound] instanceof SMSound) self.sounds[oSound].stop(); // apply only to sound objects
-    }
-  }
+    };
+  };
 
   this.pause = function(sID) {
     if (!self._idCheck(sID)) return false;
     self.sounds[sID].pause();
-  }
+  };
 
   this.resume = function(sID) {
     if (!self._idCheck(sID)) return false;
     self.sounds[sID].resume();
-  }
+  };
 
   this.togglePause = function(sID) {
     if (!self._idCheck(sID)) return false;
     self.sounds[sID].togglePause();
-  }
+  };
 
   this.setPan = function(sID,nPan) {
     if (!self._idCheck(sID)) return false;
     self.sounds[sID].setPan(nPan);
-  }
+  };
 
   this.setVolume = function(sID,nVol) {
     if (!self._idCheck(sID)) return false;
     self.sounds[sID].setVolume(nVol);
-  }
+  };
+
+  this.mute = function(sID) {
+    if (!sID) {
+      var o = null;
+      self._writeDebug('soundManager.mute(): Muting all sounds');
+      for (o in self.sounds) {
+        self.sounds[o].mute();
+      }
+    } else {
+      if (!self._idCheck(sID)) return false;
+      self._writeDebug('soundManager.mute(): Muting "'+sID+'"');
+      self.sounds[sID].mute();
+    }
+  };
+
+  this.unmute = function(sID) {
+    if (!sID) {
+      var o = null;
+      self._writeDebug('soundManager.unmute(): Unmuting all sounds');
+      for (o in self.sounds) {
+        self.sounds[o].unmute();
+      }
+    } else {
+      if (!self._idCheck(sID)) return false;
+      self._writeDebug('soundManager.unmute(): Unmuting "'+sID+'"');
+      self.sounds[sID].unmute();
+    }
+  };
 
   this.setPolling = function(bPolling) {
     if (!self.o || !self.allowPolling) return false;
-    self._writeDebug('Drupal.chatroom.soundManager.setPolling('+bPolling+')');
+    // self._writeDebug('soundManager.setPolling('+bPolling+')');
     self.o._setPolling(bPolling);
-  }
+  };
 
   this.disable = function() {
     // destroy all functions
     if (self._disabled) return false;
     self._disabled = true;
-    self._writeDebug('Drupal.chatroom.soundManager.disable(): Disabling all functions - future calls will return false.',1);
+    self._writeDebug('soundManager.disable(): Disabling all functions - future calls will return false.',1);
     for (var i=self.soundIDs.length; i--;) {
       self._disableObject(self.sounds[self.soundIDs[i]]);
-    }
+    };
     self.initComplete(); // fire "complete", despite fail
     self._disableObject(self);
-  }
+  };
 
   this.getSoundById = function(sID,suppressDebug) {
-    if (!sID) throw new Error('Drupal.chatroom.SoundManager.getSoundById(): sID is null/undefined');
+    if (!sID) throw new Error('SoundManager.getSoundById(): sID is null/undefined');
     var result = self.sounds[sID];
     if (!result && !suppressDebug) {
       self._writeDebug('"'+sID+'" is an invalid sound ID.',2);
-      // Drupal.chatroom.soundManager._writeDebug('trace: '+arguments.callee.caller);
-    }
+      // soundManager._writeDebug('trace: '+arguments.callee.caller);
+    };
     return result;
-  }
+  };
 
   this.onload = function() {
     // window.onload() equivalent for SM2, ready to create sounds etc.
-    // this is a stub - you can override this in your own external script, eg. Drupal.chatroom.soundManager.onload = function() {}
-    Drupal.chatroom.soundManager._writeDebug('<em>Warning</em>: Drupal.chatroom.soundManager.onload() is undefined.',2);
-  }
+    // this is a stub - you can override this in your own external script, eg. soundManager.onload = function() {}
+    soundManager._writeDebug('<em>Warning</em>: soundManager.onload() is undefined.',2);
+  };
 
   this.onerror = function() {
     // stub for user handler, called when SM2 fails to load/init
-  }
+  };
 
   // --- "private" methods ---
 
@@ -218,43 +320,54 @@
 
   this._disableObject = function(o) {
     for (var oProp in o) {
-      if (typeof o[oProp] == 'function' && typeof o[oProp]._protected == 'undefined') o[oProp] = function(){return false;}
-    }
+      if (typeof o[oProp] == 'function' && typeof o[oProp]._protected == 'undefined') o[oProp] = function(){return false;};
+    };
     oProp = null;
-  }
+  };
 
   this._failSafely = function() {
-    // exception handler for "object doesn't support this property or method"
-    var flashCPLink = 'http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html';
-    var fpgssTitle = 'You may need to whitelist this location/domain eg. file:///C:/ or C:/ or mysite.com, or set ALWAYS ALLOW under the Flash Player Global Security Settings page. Note that this seems to apply only to file system viewing.';
+    // exception handler for "object doesn't support this property or method" or general failure
+    var fpgssTitle = 'You may need to whitelist this location/domain eg. file:///C:/ or C:/ or mysite.com, or set ALWAYS ALLOW under the Flash Player Global Security Settings page. The latter is probably less-secure.';
     var flashCPL = '<a href="'+flashCPLink+'" title="'+fpgssTitle+'">view/edit</a>';
     var FPGSS = '<a href="'+flashCPLink+'" title="Flash Player Global Security Settings">FPGSS</a>';
     if (!self._disabled) {
-      self._writeDebug('soundManager: JS-&gt;Flash communication failed. Possible causes: flash/browser security restrictions ('+flashCPL+'), insufficient browser/plugin support, or .swf not found',2);
-      self._writeDebug('Verify that the movie path of <em>'+self.url+'</em> is correct (<a href="'+self.url+'" title="If you get a 404/not found, fix it!">test link</a>)',1);
-      if (self._didAppend) {
-        if (!document.domain) {
-          self._writeDebug('Loading from local file system? (document.domain appears to be null, this URL path may need to be added to \'trusted locations\' in '+FPGSS+')',1);
-          self._writeDebug('Possible security/domain restrictions ('+flashCPL+'), should work when served by http on same domain',1);
-        }
-        // self._writeDebug('Note: Movie added via JS method, static object/embed in-page markup method may work instead.');
-      }
+      self._writeDebug('soundManager: Failed to initialise.',2);
       self.disable();
+    };
+  };
+  
+  this._normalizeMovieURL = function(smURL) {
+    if (smURL) {
+      if (smURL.match(/\.swf/)) {
+        smURL = smURL.substr(0,smURL.lastIndexOf('.swf'));
+      }
+      if (smURL.lastIndexOf('/') != smURL.length-1) {
+        smURL = smURL+'/';
+      }
     }
+    return(smURL && smURL.lastIndexOf('/')!=-1?smURL.substr(0,smURL.lastIndexOf('/')+1):'./')+self.movieURL;
   }
 
   this._createMovie = function(smID,smURL) {
     if (self._didAppend && self._appendSuccess) return false; // ignore if already succeeded
     if (window.location.href.indexOf('debug=1')+1) self.debugMode = true; // allow force of debug mode via URL
     self._didAppend = true;
-    var html = ['<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0" width="16" height="16" id="'+smID+'"><param name="movie" value="'+smURL+'"><param name="quality" value="high"><param name="allowScriptAccess" value="always" /></object>','<embed name="'+smID+'-embed" id="'+smID+'-embed" src="'+smURL+'" width="1" height="1" quality="high" allowScriptAccess="always" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash"></embed>'];
-    var toggleElement = '<div id="'+self.debugID+'-toggle" style="position:fixed;_position:absolute;right:0px;bottom:0px;_top:0px;width:1.2em;height:1.2em;line-height:1.2em;margin:2px;padding:0px;text-align:center;border:1px solid #999;cursor:pointer;background:#fff;color:#333;z-index:706" title="Toggle SM2 debug console" onclick="Drupal.chatroom.soundManager._toggleDebug()">-</div>';
-    var debugHTML = '<div id="'+self.debugID+'" style="display:'+(self.debugMode && ((!self._hasConsole||!self.useConsole)||(self.useConsole && self._hasConsole && !self.consoleOnly))?'block':'none')+';opacity:0.85"></div>';
-    var appXHTML = 'Drupal.chatroom.soundManager._createMovie(): appendChild/innerHTML set failed. Serving application/xhtml+xml MIME type? Browser may be enforcing strict rules, not allowing write to innerHTML. (PS: If so, this means your commitment to XML validation is going to break stuff now, because this part isn\'t finished yet. ;))';
+	
+    // safety check for legacy (change to Flash 9 URL)
+    self._setVersionInfo();
+    self.url = self._normalizeMovieURL(smURL?smURL:self.url);
+    smURL = self.url;
+
+    var htmlEmbed = '<embed name="'+smID+'" id="'+smID+'" src="'+smURL+'" width="1" height="1" quality="high" allowScriptAccess="always" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash"></embed>';
+    var htmlObject = '<object id="'+smID+'" data="'+smURL+'" type="application/x-shockwave-flash" width="1" height="1"><param name="movie" value="'+smURL+'" /><param name="AllowScriptAccess" value="always" /><!-- --></object>';
+    html = (!self.isIE?htmlEmbed:htmlObject);
 
-    var sHTML = '<div style="position:absolute;left:-256px;top:-256px;width:1px;height:1px" class="movieContainer">'+html[self.isIE?0:1]+'</div>'+(self.debugMode && ((!self._hasConsole||!self.useConsole)||(self.useConsole && self._hasConsole && !self.consoleOnly)) && !document.getElementById(self.debugID)?'x'+debugHTML+toggleElement:'');
+    var toggleElement = '<div id="'+self.debugID+'-toggle" style="position:fixed;_position:absolute;right:0px;bottom:0px;_top:0px;width:1.2em;height:1.2em;line-height:1.2em;margin:2px;padding:0px;text-align:center;border:1px solid #999;cursor:pointer;background:#fff;color:#333;z-index:706" title="Toggle SM2 debug console" onclick="soundManager._toggleDebug()">-</div>';
+    var debugHTML = '<div id="'+self.debugID+'" style="display:'+(self.debugMode && ((!self._hasConsole||!self.useConsole)||(self.useConsole && self._hasConsole && !self.consoleOnly))?'block':'none')+';opacity:0.85"></div>';
+    var appXHTML = 'soundManager._createMovie(): appendChild/innerHTML set failed. May be app/xhtml+xml DOM-related.';
+    var sHTML = '<div style="position:absolute;left:-256px;top:-256px;width:1px;height:1px" class="movieContainer">'+html+'</div>'+(self.debugMode && ((!self._hasConsole||!self.useConsole)||(self.useConsole && self._hasConsole && !self.consoleOnly)) && !document.getElementById(self.debugID)?'x'+debugHTML+toggleElement:'');
 
-    var oTarget = (document.body?document.body:document.getElementsByTagName('div')[0]);
+    var oTarget = (document.body?document.body:(document.documentElement?document.documentElement:document.getElementsByTagName('div')[0]));
     if (oTarget) {
       self.oMC = document.createElement('div');
       self.oMC.className = 'movieContainer';
@@ -265,12 +378,11 @@
       self.oMC.style.height = '1px';
       try {
         oTarget.appendChild(self.oMC);
-        self.oMC.innerHTML = html[self.isIE?0:1];
+        self.oMC.innerHTML = html;
         self._appendSuccess = true;
       } catch(e) {
-        // may fail under app/xhtml+xml - has yet to be tested
         throw new Error(appXHTML);
-      }
+      };
       if (!document.getElementById(self.debugID) && ((!self._hasConsole||!self.useConsole)||(self.useConsole && self._hasConsole && !self.consoleOnly))) {
         var oDebug = document.createElement('div');
         oDebug.id = self.debugID;
@@ -282,43 +394,60 @@
             oD.innerHTML = toggleElement;
           } catch(e) {
             throw new Error(appXHTML);
-          }
-        }
+          };
+        };
         oTarget.appendChild(oDebug);
-      }
+      };
       oTarget = null;
-    }
-    self._writeDebug('-- SoundManager 2 Version '+self.version.substr(1)+' --',1);
-    self._writeDebug('Drupal.chatroom.soundManager._createMovie(): trying to load <a href="'+smURL+'" title="Test this link (404=bad)">'+smURL+'</a>',1);
-  }
+    };
+    self._writeDebug('-- SoundManager 2 '+self.version+' --',1);
+    self._writeDebug('soundManager._createMovie(): Trying to load '+smURL,1);
+  };
 
-  this._writeDebug = function(sText,sType) {
+  this._writeDebug = function(sText,sType,bTimestamp) {
     if (!self.debugMode) return false;
+    if (typeof bTimestamp != 'undefined' && bTimestamp) {
+      sText = sText + ' | '+new Date().getTime();
+    };
     if (self._hasConsole && self.useConsole) {
-      console[self._debugLevels[sType]||'log'](sText); // firebug et al
+      var sMethod = self._debugLevels[sType];
+      if (typeof console[sMethod] != 'undefined') {
+        console[sMethod](sText);
+      } else {
+        console.log(sText);
+      };
       if (self.useConsoleOnly) return true;
-    }
+    };
     var sDID = 'soundmanager-debug';
     try {
       var o = document.getElementById(sDID);
       if (!o) return false;
-      var p = document.createElement('div');
-      p.innerHTML = sText;
-      // o.appendChild(p); // top-to-bottom
-      o.insertBefore(p,o.firstChild); // bottom-to-top
+      var oItem = document.createElement('div');
+      sText = sText.replace(/\n/g,'<br />');
+      if (typeof sType == 'undefined') {
+        var sType = 0;
+      } else {
+        sType = parseInt(sType);
+      };
+      oItem.innerHTML = sText;
+      if (sType) {
+        if (sType >= 2) oItem.style.fontWeight = 'bold';
+        if (sType == 3) oItem.style.color = '#ff3333';
+      };
+      // o.appendChild(oItem); // top-to-bottom
+      o.insertBefore(oItem,o.firstChild); // bottom-to-top
     } catch(e) {
       // oh well
-    }
+    };
     o = null;
-  }
+  };
   this._writeDebug._protected = true;
 
-  this._writeDebugAlert = function(sText) { alert(sText); }
+  this._writeDebugAlert = function(sText) { alert(sText); };
 
-  if (window.location.href.indexOf('debug=alert')+1) {
-    self.debugMode = true;
+  if (window.location.href.indexOf('debug=alert')+1 && self.debugMode) {
     self._writeDebug = self._writeDebugAlert;
-  }
+  };
 
   this._toggleDebug = function() {
     var o = document.getElementById(self.debugID);
@@ -331,33 +460,38 @@
     } else {
       oT.innerHTML = '-';
       o.style.display = 'block';
-    }
+    };
     self._debugOpen = !self._debugOpen;
-  }
+  };
 
   this._toggleDebug._protected = true;
 
   this._debug = function() {
-    self._writeDebug('Drupal.chatroom.soundManager._debug(): sounds by id/url:',0);
+    self._writeDebug('soundManager._debug(): sounds by id/url:',0);
     for (var i=0,j=self.soundIDs.length; i<j; i++) {
       self._writeDebug(self.sounds[self.soundIDs[i]].sID+' | '+self.sounds[self.soundIDs[i]].url,0);
-    }
-  }
+    };
+  };
 
   this._mergeObjects = function(oMain,oAdd) {
     // non-destructive merge
-    var o1 = oMain;
+    var o1 = {}; // clone o1
+    for (var i in oMain) {
+      o1[i] = oMain[i];
+    }
     var o2 = (typeof oAdd == 'undefined'?self.defaultOptions:oAdd);
     for (var o in o2) {
       if (typeof o1[o] == 'undefined') o1[o] = o2[o];
-    }
+    };
     return o1;
-  }
+  };
 
   this.createMovie = function(sURL) {
     if (sURL) self.url = sURL;
     self._initMovie();
-  }
+  };
+
+  this.go = this.createMovie; // nice alias
 
   this._initMovie = function() {
     // attempt to get, or create, movie
@@ -367,97 +501,225 @@
       // try to create
       self._createMovie(self.id,self.url);
       self.o = self.getMovie(self.id);
-    }
+    };
     if (self.o) {
-      self._writeDebug('Drupal.chatroom.soundManager._initMovie(): Got '+self.o.nodeName+' element ('+(self._didAppend?'created via JS':'static HTML')+')',1);
-    }
-  }
+      self._writeDebug('soundManager._initMovie(): Got '+self.o.nodeName+' element ('+(self._didAppend?'created via JS':'static HTML')+')',1);
+      self._writeDebug('soundManager._initMovie(): Waiting for ExternalInterface call from Flash..');
+    };
+  };
+
+  this.waitForExternalInterface = function() {
+    if (self._waitingForEI) return false;
+    self._waitingForEI = true;
+    if (self._tryInitOnFocus && !self._isFocused) {
+      self._writeDebug('soundManager: Special case: Flash may not have started due to non-focused tab (Safari is lame), and/or focus cannot be detected. Waiting for focus-related event..');
+      return false;
+    };
+    if (!self._didInit) {
+      self._writeDebug('soundManager: Getting impatient, still waiting for Flash.. ;)');
+    };
+    setTimeout(function() {
+      if (!self._didInit) {
+        self._writeDebug('soundManager: No Flash response within reasonable time after document load.\nPossible causes: Flash version under 8, no support, or Flash security denying JS-Flash communication.',2);
+        if (!self._overHTTP) {
+          self._writeDebug('soundManager: Loading this page from local/network file system (not over HTTP?) Flash security likely restricting JS-Flash access. Consider adding current URL to "trusted locations" in the Flash player security settings manager at '+flashCPLink+', or simply serve this content over HTTP.',2);
+        };
+      };
+      // if still not initialized and no other options, give up
+      if (!self._didInit && self._okToDisable) self._failSafely();
+    },750);
+  };
+
+  this.handleFocus = function() {
+    if (self._isFocused || !self._tryInitOnFocus) return true;
+    self._okToDisable = true;
+    self._isFocused = true;
+    self._writeDebug('soundManager.handleFocus()');
+    if (self._tryInitOnFocus) {
+      // giant Safari 3.1 hack - assume window in focus if mouse is moving, since document.hasFocus() not currently implemented.
+      window.removeEventListener('mousemove',self.handleFocus,false);
+    };
+    // allow init to restart
+    self._waitingForEI = false;
+    setTimeout(self.waitForExternalInterface,500);
+    // detach event
+    if (window.removeEventListener) {
+      window.removeEventListener('focus',self.handleFocus,false);
+    } else if (window.detachEvent) {
+      window.detachEvent('onfocus',self.handleFocus);
+    };
+  };
 
   this.initComplete = function() {
     if (self._didInit) return false;
     self._didInit = true;
     self._writeDebug('-- SoundManager 2 '+(self._disabled?'failed to load':'loaded')+' ('+(self._disabled?'security/load error':'OK')+') --',1);
     if (self._disabled) {
-      self._writeDebug('Drupal.chatroom.soundManager.initComplete(): calling Drupal.chatroom.soundManager.onerror()',1);
+      self._writeDebug('soundManager.initComplete(): calling soundManager.onerror()',1);
       self.onerror.apply(window);
       return false;
-    }
-    self._writeDebug('Drupal.chatroom.soundManager.initComplete(): calling Drupal.chatroom.soundManager.onload()',1);
+    };
+    if (self.waitForWindowLoad && !self._windowLoaded) {
+      self._writeDebug('soundManager: Waiting for window.onload()');
+      if (window.addEventListener) {
+        window.addEventListener('load',self.initUserOnload,false);
+      } else if (window.attachEvent) {
+        window.attachEvent('onload',self.initUserOnload);
+      };
+      return false;
+    } else {
+      if (self.waitForWindowLoad && self._windowLoaded) {
+        self._writeDebug('soundManager: Document already loaded');
+      };
+      self.initUserOnload();
+    };
+  };
+
+  this.initUserOnload = function() {
+    self._writeDebug('soundManager.initComplete(): calling soundManager.onload()',1);
+    // call user-defined "onload", scoped to window
     try {
-      // call user-defined "onload", scoped to window
       self.onload.apply(window);
     } catch(e) {
       // something broke (likely JS error in user function)
-      self._writeDebug('Drupal.chatroom.soundManager.onload() threw an exception: '+e.message,2);
-      throw e; // (so browser/console gets message)
-    }
-    self._writeDebug('Drupal.chatroom.soundManager.onload() complete',1);
-  }
+      self._writeDebug('soundManager.onload() threw an exception: '+e.message,2);
+      setTimeout(function(){throw new Error(e)},20);
+      return false;
+    };
+    self._writeDebug('soundManager.onload() complete',1);
+  };
 
   this.init = function() {
+    self._writeDebug('-- soundManager.init() --');
     // called after onload()
-    // self._initMovie();
+    self._initMovie();
+    if (self._didInit) {
+      self._writeDebug('soundManager.init(): Already called?');
+      return false;
+    };
     // event cleanup
     if (window.removeEventListener) {
-      window.removeEventListener('load',self.beginInit,false);
+      window.removeEventListener('load',self.beginDelayedInit,false);
     } else if (window.detachEvent) {
-      window.detachEvent('onload',self.beginInit);
-    }
+      window.detachEvent('onload',self.beginDelayedInit);
+    };
     try {
-      self.o._externalInterfaceTest(); // attempt to talk to Flash
-      self._writeDebug('Flash ExternalInterface call (JS -&gt; Flash) succeeded.',1);
+      self._writeDebug('Attempting to call JS-Flash..');
+      self.o._externalInterfaceTest(false); // attempt to talk to Flash
+      // self._writeDebug('Flash ExternalInterface call (JS-Flash) OK',1);
       if (!self.allowPolling) self._writeDebug('Polling (whileloading/whileplaying support) is disabled.',1);
       self.setPolling(true);
+	  if (!self.debugMode) self.o._disableDebug();
       self.enabled = true;
-    }  catch(e) {
+    } catch(e) {
       self._failSafely();
       self.initComplete();
       return false;
-    }
+    };
     self.initComplete();
-  }
+  };
 
   this.beginDelayedInit = function() {
-    setTimeout(self.beginInit,200);
-  }
+    self._writeDebug('soundManager.beginDelayedInit(): Document loaded');
+    self._windowLoaded = true;
+    setTimeout(self.waitForExternalInterface,500);
+    setTimeout(self.beginInit,20);
+  };
 
   this.beginInit = function() {
+    if (self._initPending) return false;
     self.createMovie(); // ensure creation if not already done
     self._initMovie();
-    setTimeout(self.init,1000); // some delay required, otherwise JS<->Flash/ExternalInterface communication fails under non-IE (?!)
-  }
+    self._initPending = true;
+    return true;
+  };
+
+  this.domContentLoaded = function() {
+    self._writeDebug('soundManager.domContentLoaded()');
+    if (document.removeEventListener) document.removeEventListener('DOMContentLoaded',self.domContentLoaded,false);
+    self.go();
+  };
+
+  this._externalInterfaceOK = function() {
+    // callback from flash for confirming that movie loaded, EI is working etc.
+    if (self.swfLoaded) return false;
+    self._writeDebug('soundManager._externalInterfaceOK()');
+    self.swfLoaded = true;
+    self._tryInitOnFocus = false;
+    if (self.isIE) {
+      // IE needs a timeout OR delay until window.onload - may need TODO: investigating
+      setTimeout(self.init,100);
+    } else {
+      self.init();
+    };
+  };
+
+  this._setSandboxType = function(sandboxType) {
+    var sb = self.sandbox;
+    sb.type = sandboxType;
+    sb.description = sb.types[(typeof sb.types[sandboxType] != 'undefined'?sandboxType:'unknown')];
+    self._writeDebug('Flash security sandbox type: '+sb.type);
+    if (sb.type == 'localWithFile') {
+      sb.noRemote = true;
+      sb.noLocal = false;
+      self._writeDebug('Flash security note: Network/internet URLs will not load due to security restrictions. Access can be configured via Flash Player Global Security Settings Page: http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html',2);
+    } else if (sb.type == 'localWithNetwork') {
+      sb.noRemote = false;
+      sb.noLocal = true;
+    } else if (sb.type == 'localTrusted') {
+      sb.noRemote = false;
+      sb.noLocal = false;
+    };
+  };
 
   this.destruct = function() {
-    if (self.isSafari) {
-      /* --
-        Safari 1.3.2 (v312.6)/OSX 10.3.9 and perhaps newer will crash if a sound is actively loading when user exits/refreshes/leaves page
-       (Apparently related to ExternalInterface making calls to an unloading/unloaded page?)
-       Unloading sounds (detaching handlers and so on) may help to prevent this
-      -- */
-      for (var i=self.soundIDs.length; i--;) {
-        if (self.sounds[self.soundIDs[i]].readyState == 1) self.sounds[self.soundIDs[i]].unload();
-      }
-    }
+    self._writeDebug('soundManager.destruct()');
     self.disable();
-    // self.o = null;
-    // self.oMC = null;
-  }
-
-}
-
-Drupal.chatroom.SMSound = function(oSM,oOptions) {
+  };
+  
+  // SMSound (sound object)
+  
+  function SMSound(oSM,oOptions) {
   var self = this;
   var sm = oSM;
   this.sID = oOptions.id;
   this.url = oOptions.url;
   this.options = sm._mergeObjects(oOptions);
+  this.instanceOptions = this.options; // per-play-instance-specific options
+
+  this._debug = function() {
+    if (sm.debugMode) {
+    var stuff = null;
+    var msg = [];
+    var sF = null;
+    var sfBracket = null;
+    var maxLength = 64; // # of characters of function code to show before truncating
+    for (stuff in self.options) {
+      if (self.options[stuff] != null) {
+        if (self.options[stuff] instanceof Function) {
+	  // handle functions specially
+	  sF = self.options[stuff].toString();
+	  sF = sF.replace(/\s\s+/g,' '); // normalize spaces
+	  sfBracket = sF.indexOf('{');
+	  msg[msg.length] = ' '+stuff+': {'+sF.substr(sfBracket+1,(Math.min(Math.max(sF.indexOf('\n')-1,maxLength),maxLength))).replace(/\n/g,'')+'... }';
+	} else {
+	  msg[msg.length] = ' '+stuff+': '+self.options[stuff];
+	};
+      };
+    };
+    sm._writeDebug('SMSound() merged options: {\n'+msg.join(', \n')+'\n}');
+    };
+  };
+
+  this._debug();
+
   this.id3 = {
-   /*
+   /* 
     Name/value pairs set via Flash when available - see reference for names:
     http://livedocs.macromedia.com/flash/8/main/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Parts&file=00001567.html
     (eg., this.id3.songname or this.id3['songname'])
    */
-  }
+  };
 
   self.resetProperties = function(bLoaded) {
     self.bytesLoaded = null;
@@ -472,88 +734,102 @@
     self.readyState = 0; // 0 = uninitialised, 1 = loading, 2 = failed/error, 3 = loaded/success
     self.didBeforeFinish = false;
     self.didJustBeforeFinish = false;
-  }
+    self.instanceOptions = {};
+    self.instanceCount = 0;
+    self.peakData = {
+      left: 0,
+      right: 0
+    };
+    self.waveformData = [];
+    self.eqData = [];
+  };
 
   self.resetProperties();
 
   // --- public methods ---
 
   this.load = function(oOptions) {
+    self.instanceOptions = sm._mergeObjects(oOptions);
+    if (typeof self.instanceOptions.url == 'undefined') self.instanceOptions.url = self.url;
+    sm._writeDebug('soundManager.load(): '+self.instanceOptions.url,1);
+    if (self.instanceOptions.url == self.url && self.readyState != 0 && self.readyState != 2) {
+      sm._writeDebug('soundManager.load(): current URL already assigned.',1);
+      return false;
+    }
     self.loaded = false;
     self.loadSuccess = null;
     self.readyState = 1;
-    self.playState = (oOptions.autoPlay||false); // if autoPlay, assume "playing" is true (no way to detect when it actually starts in Flash unless onPlay is watched?)
-    var thisOptions = sm._mergeObjects(oOptions);
-    if (typeof thisOptions.url == 'undefined') thisOptions.url = self.url;
+    self.playState = (oOptions.autoPlay?1:0); // if autoPlay, assume "playing" is true (no way to detect when it actually starts in Flash unless onPlay is watched?)
     try {
-      sm._writeDebug('loading '+thisOptions.url,1);
-      sm.o._load(self.sID,thisOptions.url,thisOptions.stream,thisOptions.autoPlay,thisOptions.whileloading?1:0);
+	  if (sm.flashVersion==8) {
+	    sm.o._load(self.sID,self.instanceOptions.url,self.instanceOptions.stream,self.instanceOptions.autoPlay,(self.instanceOptions.whileloading?1:0));
+	  } else {
+        sm.o._load(self.sID,self.instanceOptions.url,self.instanceOptions.stream?true:false,self.instanceOptions.autoPlay?true:false); // ,(thisOptions.whileloading?true:false)
+	  };
     } catch(e) {
-      sm._writeDebug('SMSound().load(): JS-&gt;Flash communication failed.',2);
-    }
-  }
+      sm._writeDebug('SMSound.load(): JS-Flash communication failed.',2);
+    };
+  };
 
   this.unload = function() {
     // Flash 8/AS2 can't "close" a stream - fake it by loading an empty MP3
-    sm._writeDebug('SMSound().unload(): "'+self.sID+'"');
+    // Flash 9/AS3: Close stream, preventing further load
+    sm._writeDebug('SMSound.unload(): "'+self.sID+'"');
     self.setPosition(0); // reset current sound positioning
     sm.o._unload(self.sID,sm.nullURL);
     // reset load/status flags
     self.resetProperties();
+  };
+
+  this.destruct = function() {
+    // kill sound within Flash
+    sm._writeDebug('SMSound.destruct(): '+self.sID);
+    sm.o._destroySound(self.sID);
   }
 
   this.play = function(oOptions) {
     if (!oOptions) oOptions = {};
-
-    // --- TODO: make event handlers specified via oOptions only apply to this instance of play() (eg. onfinish applies but will reset to default on finish)
-    if (oOptions.onfinish) self.options.onfinish = oOptions.onfinish;
-    if (oOptions.onbeforefinish) self.options.onbeforefinish = oOptions.onbeforefinish;
-    if (oOptions.onjustbeforefinish) self.options.onjustbeforefinish = oOptions.onjustbeforefinish;
-    // ---
-
-    var thisOptions = sm._mergeObjects(oOptions);
+    self.instanceOptions = sm._mergeObjects(oOptions,self.instanceOptions);
+    self.instanceOptions = sm._mergeObjects(self.instanceOptions,self.options);
     if (self.playState == 1) {
-      // var allowMulti = typeof oOptions.multiShot!='undefined'?oOptions.multiShot:sm.defaultOptions.multiShot;
-      var allowMulti = thisOptions.multiShot;
+      var allowMulti = self.instanceOptions.multiShot;
       if (!allowMulti) {
-        sm._writeDebug('Drupal.chatroom.SMSound.play(): "'+self.sID+'" already playing? (one-shot)',1);
+        sm._writeDebug('SMSound.play(): "'+self.sID+'" already playing (one-shot)',1);
         return false;
       } else {
-        sm._writeDebug('Drupal.chatroom.SMSound.play(): "'+self.sID+'" already playing (multi-shot)',1);
-      }
-    }
+        sm._writeDebug('SMSound.play(): "'+self.sID+'" already playing (multi-shot)',1);
+      };
+    };
     if (!self.loaded) {
       if (self.readyState == 0) {
-        sm._writeDebug('Drupal.chatroom.SMSound.play(): .play() before load request. Attempting to load "'+self.sID+'"',1);
+        sm._writeDebug('SMSound.play(): Attempting to load "'+self.sID+'"',1);
         // try to get this sound playing ASAP
-        thisOptions.stream = true;
-        thisOptions.autoPlay = true;
+        self.instanceOptions.stream = true;
+        self.instanceOptions.autoPlay = true;
         // TODO: need to investigate when false, double-playing
         // if (typeof oOptions.autoPlay=='undefined') thisOptions.autoPlay = true; // only set autoPlay if unspecified here
-        self.load(thisOptions); // try to get this sound playing ASAP
+        self.load(self.instanceOptions); // try to get this sound playing ASAP
       } else if (self.readyState == 2) {
-        sm._writeDebug('Drupal.chatroom.SMSound.play(): Could not load "'+self.sID+'" - exiting',2);
+        sm._writeDebug('SMSound.play(): Could not load "'+self.sID+'" - exiting',2);
         return false;
       } else {
-        sm._writeDebug('Drupal.chatroom.SMSound.play(): "'+self.sID+'" is loading - attempting to play..',1);
-      }
+        sm._writeDebug('SMSound.play(): "'+self.sID+'" is loading - attempting to play..',1);
+      };
     } else {
-      sm._writeDebug('Drupal.chatroom.SMSound.play(): "'+self.sID+'"');
-    }
+      sm._writeDebug('SMSound.play(): "'+self.sID+'"');
+    };
     if (self.paused) {
       self.resume();
     } else {
       self.playState = 1;
-      self.position = (thisOptions.offset||0);
-      if (thisOptions.onplay) thisOptions.onplay.apply(self);
-      self.setVolume(thisOptions.volume);
-      self.setPan(thisOptions.pan);
-      if (!thisOptions.autoPlay) {
-        // sm._writeDebug('starting sound '+self.sID);
-        sm.o._start(self.sID,thisOptions.loop||1,self.position); // TODO: verify !autoPlay doesn't cause issue
-      }
-    }
-  }
+      if (!self.instanceCount || sm.flashVersion == 9) self.instanceCount++;
+      self.position = (typeof self.instanceOptions.position != 'undefined' && !isNaN(self.instanceOptions.position)?self.instanceOptions.position:0);
+      if (self.instanceOptions.onplay) self.instanceOptions.onplay.apply(self);
+      self.setVolume(self.instanceOptions.volume);
+      self.setPan(self.instanceOptions.pan);
+      sm.o._start(self.sID,self.instanceOptions.loop||1,(sm.flashVersion==9?self.position:self.position/1000));
+    };
+  };
 
   this.start = this.play; // just for convenience
 
@@ -561,159 +837,210 @@
     if (self.playState == 1) {
       self.playState = 0;
       self.paused = false;
-      if (sm.defaultOptions.onstop) sm.defaultOptions.onstop.apply(self);
-      sm.o._stop(self.sID);
-    }
-  }
+      // if (sm.defaultOptions.onstop) sm.defaultOptions.onstop.apply(self);
+      if (self.instanceOptions.onstop) self.instanceOptions.onstop.apply(self);
+      sm.o._stop(self.sID,bAll);
+      self.instanceCount = 0;
+      self.instanceOptions = {};
+    };
+  };
 
   this.setPosition = function(nMsecOffset) {
-    // sm._writeDebug('setPosition('+nMsecOffset+')');
-    sm.o._setPosition(self.sID,nMsecOffset/1000,self.paused||!self.playState); // if paused or not playing, will not resume (by playing)
-  }
+    self.instanceOptions.position = nMsecOffset;
+    sm.o._setPosition(self.sID,(sm.flashVersion==9?self.instanceOptions.position:self.instanceOptions.position/1000),(self.paused||!self.playState)); // if paused or not playing, will not resume (by playing)
+  };
 
   this.pause = function() {
     if (self.paused) return false;
-    sm._writeDebug('Drupal.chatroom.SMSound.pause()');
+    sm._writeDebug('SMSound.pause()');
     self.paused = true;
     sm.o._pause(self.sID);
-  }
+    if (self.instanceOptions.onpause) self.instanceOptions.onpause.apply(self);
+  };
 
   this.resume = function() {
     if (!self.paused) return false;
-    sm._writeDebug('Drupal.chatroom.SMSound.resume()');
+    sm._writeDebug('SMSound.resume()');
     self.paused = false;
     sm.o._pause(self.sID); // flash method is toggle-based (pause/resume)
-  }
+    if (self.instanceOptions.onresume) self.instanceOptions.onresume.apply(self);
+  };
 
   this.togglePause = function() {
-    // if playing, pauses - if paused, resumes playing.
-    sm._writeDebug('Drupal.chatroom.SMSound.togglePause()');
+    sm._writeDebug('SMSound.togglePause()');
     if (!self.playState) {
-      // self.setPosition();
-      self.play({offset:self.position/1000});
+      self.play({position:(sm.flashVersion==9?self.position:self.position/1000)});
       return false;
-    }
+    };
     if (self.paused) {
-      sm._writeDebug('Drupal.chatroom.SMSound.togglePause(): resuming..');
       self.resume();
     } else {
-      sm._writeDebug('Drupal.chatroom.SMSound.togglePause(): pausing..');
       self.pause();
-    }
-  }
+    };
+  };
 
   this.setPan = function(nPan) {
     if (typeof nPan == 'undefined') nPan = 0;
     sm.o._setPan(self.sID,nPan);
-    self.options.pan = nPan;
-  }
+    self.instanceOptions.pan = nPan;
+  };
 
   this.setVolume = function(nVol) {
     if (typeof nVol == 'undefined') nVol = 100;
     sm.o._setVolume(self.sID,nVol);
-    self.options.volume = nVol;
-  }
+    self.instanceOptions.volume = nVol;
+  };
+
+  this.mute = function() {
+    sm.o._setVolume(self.sID,0);
+  };
+
+  this.unmute = function() {
+    sm.o._setVolume(self.sID,self.instanceOptions.volume);
+  };
 
   // --- "private" methods called by Flash ---
 
   this._whileloading = function(nBytesLoaded,nBytesTotal,nDuration) {
     self.bytesLoaded = nBytesLoaded;
     self.bytesTotal = nBytesTotal;
-    self.duration = nDuration;
+    self.duration = Math.floor(nDuration);
     self.durationEstimate = parseInt((self.bytesTotal/self.bytesLoaded)*self.duration); // estimate total time (will only be accurate with CBR MP3s.)
-    if (self.readyState != 3 && self.options.whileloading) self.options.whileloading.apply(self);
-    // Drupal.chatroom.soundManager._writeDebug('duration/durationEst: '+self.duration+' / '+self.durationEstimate);
-  }
+    if (self.readyState != 3 && self.instanceOptions.whileloading) self.instanceOptions.whileloading.apply(self);
+    // soundManager._writeDebug('duration/durationEst: '+self.duration+' / '+self.durationEstimate);
+  };
 
   this._onid3 = function(oID3PropNames,oID3Data) {
     // oID3PropNames: string array (names)
     // ID3Data: string array (data)
-    sm._writeDebug('SMSound()._onid3(): "'+this.sID+'" ID3 data received.');
+    sm._writeDebug('SMSound._onid3(): "'+this.sID+'" ID3 data received.');
     var oData = [];
     for (var i=0,j=oID3PropNames.length; i<j; i++) {
       oData[oID3PropNames[i]] = oID3Data[i];
       // sm._writeDebug(oID3PropNames[i]+': '+oID3Data[i]);
-    }
+    };
     self.id3 = sm._mergeObjects(self.id3,oData);
-    if (self.options.onid3) self.options.onid3.apply(self);
-  }
+    if (self.instanceOptions.onid3) self.instanceOptions.onid3.apply(self);
+  };
 
-  this._whileplaying = function(nPosition) {
+  this._whileplaying = function(nPosition,oPeakData,oWaveformData,oEQData) {
     if (isNaN(nPosition) || nPosition == null) return false; // Flash may return NaN at times
     self.position = nPosition;
+	if (self.instanceOptions.usePeakData && typeof oPeakData != 'undefined' && oPeakData) {
+	  self.peakData = {
+	   left: oPeakData.leftPeak,
+	   right: oPeakData.rightPeak
+	  };
+	};
+	if (self.instanceOptions.useWaveformData && typeof oWaveformData != 'undefined' && oWaveformData) {
+	  self.waveformData = oWaveformData;
+	  /*
+	  self.spectrumData = {
+	   left: oSpectrumData.left.split(','),
+	   right: oSpectrumData.right.split(',')
+	  }
+	  */
+	};
+	if (self.instanceOptions.useEQData && typeof oEQData != 'undefined' && oEQData) {
+	  self.eqData = oEQData;
+	};
     if (self.playState == 1) {
-      if (self.options.whileplaying) self.options.whileplaying.apply(self); // flash may call after actual finish
-      if (self.loaded && self.options.onbeforefinish && self.options.onbeforefinishtime && !self.didBeforeFinish && self.duration-self.position <= self.options.onbeforefinishtime) {
-        sm._writeDebug('duration-position &lt;= onbeforefinishtime: '+self.duration+' - '+self.position+' &lt= '+self.options.onbeforefinishtime+' ('+(self.duration-self.position)+')');
+      if (self.instanceOptions.whileplaying) self.instanceOptions.whileplaying.apply(self); // flash may call after actual finish
+      if (self.loaded && self.instanceOptions.onbeforefinish && self.instanceOptions.onbeforefinishtime && !self.didBeforeFinish && self.duration-self.position <= self.instanceOptions.onbeforefinishtime) {
+        sm._writeDebug('duration-position &lt;= onbeforefinishtime: '+self.duration+' - '+self.position+' &lt= '+self.instanceOptions.onbeforefinishtime+' ('+(self.duration-self.position)+')');
         self._onbeforefinish();
-      }
-    }
-  }
+      };
+    };
+  };
 
   this._onload = function(bSuccess) {
     bSuccess = (bSuccess==1?true:false);
-    sm._writeDebug('Drupal.chatroom.SMSound._onload(): "'+self.sID+'"'+(bSuccess?' loaded.':' failed to load (or loaded from cache - weird bug) - [<a href="'+self.url+'">test URL</a>]'));
+    sm._writeDebug('SMSound._onload(): "'+self.sID+'"'+(bSuccess?' loaded.':' failed to load? - '+self.url));
+    if (!bSuccess) {
+      if (sm.sandbox.noRemote == true) {
+        sm._writeDebug('SMSound._onload(): Reminder: Flash security is denying network/internet access',1);
+      };
+      if (sm.sandbox.noLocal == true) {
+        sm._writeDebug('SMSound._onload(): Reminder: Flash security is denying local access',1);
+      };
+    };
     self.loaded = bSuccess;
     self.loadSuccess = bSuccess;
     self.readyState = bSuccess?3:2;
-    if (self.options.onload) self.options.onload.apply(self);
-  }
+    if (self.instanceOptions.onload) {
+      self.instanceOptions.onload.apply(self);
+    };
+  };
 
   this._onbeforefinish = function() {
     if (!self.didBeforeFinish) {
       self.didBeforeFinish = true;
-      if (self.options.onbeforefinish) self.options.onbeforefinish.apply(self);
-    }
-  }
+      if (self.instanceOptions.onbeforefinish) self.instanceOptions.onbeforefinish.apply(self);
+    };
+  };
 
   this._onjustbeforefinish = function(msOffset) {
     // msOffset: "end of sound" delay actual value (eg. 200 msec, value at event fire time was 187)
     if (!self.didJustBeforeFinish) {
       self.didJustBeforeFinish = true;
-      // Drupal.chatroom.soundManager._writeDebug('Drupal.chatroom.SMSound._onjustbeforefinish()');
-      if (self.options.onjustbeforefinish) self.options.onjustbeforefinish.apply(self);;
-    }
-  }
+      // soundManager._writeDebug('SMSound._onjustbeforefinish()');
+      if (self.instanceOptions.onjustbeforefinish) self.instanceOptions.onjustbeforefinish.apply(self);
+    };
+  };
 
   this._onfinish = function() {
     // sound has finished playing
-    sm._writeDebug('Drupal.chatroom.SMSound._onfinish(): "'+self.sID+'"');
+    sm._writeDebug('SMSound._onfinish(): "'+self.sID+'"');
     self.playState = 0;
     self.paused = false;
-    if (self.options.onfinish) self.options.onfinish.apply(self);
-    if (self.options.onbeforefinishcomplete) self.options.onbeforefinishcomplete.apply(self);
+    if (self.instanceOptions.onfinish) self.instanceOptions.onfinish.apply(self);
+    if (self.instanceOptions.onbeforefinishcomplete) self.instanceOptions.onbeforefinishcomplete.apply(self);
     // reset some state items
-    self.setPosition(0);
+    // self.setPosition(0);
     self.didBeforeFinish = false;
     self.didJustBeforeFinish = false;
-  }
+    if (self.instanceCount) {
+      self.instanceCount--;
+      if (!self.instanceCount) {
+        // reset instance options
+        self.instanceCount = 0;
+        self.instanceOptions = {};
+      }
+    }
+  };
 
-}
+  }; // SMSound()
 
-// Global Killswitch
-if (Drupal.jsEnabled) {
-  $(document).ready(
-    function() {
-      Drupal.chatroom.soundManager = new Drupal.chatroom.SoundManager();
+  // set up default options
+  if (this.flashVersion == 9) {
+    // this.defaultOptions = this._mergeObjects(this.defaultOptions,this.flash9Options);
+  }
+
+  // register a few event handlers
+  if (window.addEventListener) {
+    window.addEventListener('focus',self.handleFocus,false);
+    window.addEventListener('load',self.beginDelayedInit,false);
+    window.addEventListener('beforeunload',self.destruct,false);
+    if (self._tryInitOnFocus) window.addEventListener('mousemove',self.handleFocus,false); // massive Safari focus hack
+  } else if (window.attachEvent) {
+    window.attachEvent('onfocus',self.handleFocus);
+    window.attachEvent('onload',self.beginDelayedInit);
+    window.attachEvent('beforeunload',self.destruct);
+  } else {
+    // no add/attachevent support - safe to assume no JS -> Flash either.
+    soundManager.onerror();
+    soundManager.disable();
+  };
+
+  if (document.addEventListener) document.addEventListener('DOMContentLoaded',self.domContentLoaded,false);
+
+  var SM2_COPYRIGHT = [
+    'SoundManager 2: Javascript Sound for the Web',
+    'http://schillmania.com/projects/soundmanager2/',
+    'Copyright (c) 2008, Scott Schiller. All rights reserved.',
+    'Code provided under the BSD License: http://schillmania.com/projects/soundmanager2/license.txt',
+  ];
 
-      // attach onload handler
-      if (window.addEventListener) {
-        window.addEventListener('load',Drupal.chatroom.soundManager.beginDelayedInit,false);
-        window.addEventListener('beforeunload',Drupal.chatroom.soundManager.destruct,false);
-      } else if (window.attachEvent) {
-        window.attachEvent('onload',Drupal.chatroom.soundManager.beginInit);
-        window.attachEvent('beforeunload',Drupal.chatroom.soundManager.destruct);
-      } else {
-        // no add/attachevent support - safe to assume no JS->Flash either.
-        Drupal.chatroom.soundManager.onerror();
-        Drupal.chatroom.soundManager.disable();
-      }
-
-      Drupal.chatroom.soundManager.onload = function() {
-        Drupal.chatroom.soundManager.createSound('message', Drupal.settings.chatroom.messageSound);
-        Drupal.chatroom.soundManager.createSound('user', Drupal.settings.chatroom.userSound);
-      }
-    }
-  );
-}
+}; // SoundManager()
 
+var soundManager = new SoundManager();
diff -Naur chatroom/soundmanager2.swf chatroom-6.x-dev/soundmanager2.swf
--- chatroom/soundmanager2.swf	2007-07-10 01:36:16.000000000 -0400
+++ chatroom-6.x-dev/soundmanager2.swf	2008-10-18 21:14:27.000000000 -0400
@@ -1,5 +1,9 @@
-CWSf  xڔXKsY>꾒Z~g'%I̐ Nn!ř-%w"u-2SJKUe1U=U`Ŏ,Rs%ǎ
-W=uokU (o/AS?Okxi-o ѿo@߶	j[7;P7=QuuX}jԨ>ȃK<.tNc;V^mڵ6>In\"X_	M<le6,hԬ'篖qb5^noyt,@_wMO\Whol XI1w+jNA;/[-A1_i7<j}ظ\)jy&\u$!ԁ, `6=PK׷ڶ>1X8-ha,}ht%x"߽I;+BJr0+N0O}bmLՠN+,+~nH,l:zv]8H/0EYk{eY>;:>,b`(q߷ʀ1tC	,}م|Kz !y2mvþE*`n'̾+5Qဳ6::j2`2xҷHTžr阳hԒ{]uςi;`zUAD,#4("Rf5[s%Hh:<>ʂɒ6XMSBOJ'N%P<$=Up6ǌ:=*uL)0ʁ6ުMoQ!(4b(40:)	5I$ΏP$7˖3CZE-KCGgRI"fNDs_y-R-)Vi"02D%1{~6@NG.PBU(`?ؓ#1ƄLq`>v:Z{1X?ck/Atu?Rs䃕!`_̽rPAOO
-m">.v&O'0t$9a׫F?!LIaأHaB$*8)Yj*s^T&Moێ:H"ǝ`U&Z$jJ4EH2ճsi0EvM1,_)!.B߈ѝq
-ns7ևNja{ey4JP\:XJ.%,2#bZupѡGA>Q*ypIȆ!zU)x_8هnƧxVB&kڵ!r83$Ag[qU+>r@nG{~!I]sWi?M.>4eD>
-F5i/ձϹy~&o6wqE Bdw"hts]"EƢ|'vXyډwYCGJ]sf;n8\W6ȋJx5Q\R	zm[)&*Dz Gs2dQG@Aa\8+|]Ff35뀌i^d;37M WMR|bVV	lp{\:{<iIx>}4O|L	e9|gMٌ͡w4-ǙP?y=ު,KAZdcZdlm{,Z!򪴲9͗%>kWM>}vPRړ'6A .XA>v9Vzor"7o 1AyZccC^GAbK(0phpv1"Y]<	C쾄BP?a2vyX{_CYs߈ۜ)sx$5v?)gl^Z).*F_Mɢί'hf:'LWtQ6Agu~;AGA}:3 f>L4ޣCQm5)SyIXquʔF-Yi2!\c~{=٪ԎtOlԯzYv	D> sLVZ?    
\ No newline at end of file
+CWS  xڔXKsnClZ55I8V!ٖ([%(?6̀$H  Z?`+{	scRKNIUN?\sM)=Zf8M7==3z0 8v#Y@9{cVZ-7f
++^ml.vp8pU808Tm9^tf֣JsCsˇ Sk<C>ᆖnBm=p͚c5a:qkUCnOj_>4 [碾f݂8Pw <F<vU.9ᝫZ)6>UڄN@PAZXr=t5ABVjhr9Y7ekVcpZVrZg6QQwoo/|j:+Љl{c9wpx4԰:Ab5=zh6zݑ_M\"\`SN\bFIJ6Vn,@ J̷Z6z/EoX8N+rr1Cګl>IZ-3P~QS[6;.5vlT{^Hm2Tedb~}}#G>pccJX`HL-h~q`o:uFBǗ{{X>'v)K1F)Q94mPvC'
+,얋;lC̉vH	=!(bְHWhR"Bʕ}vlaRAb`iCa|74OS'U`QWRÏ{tMO/ү5Ϭ-rgsZv.jA5䮣'LjRyEZ ƨNhH[C9Uc;gOI@v7oIF@$Ge^?5N<<cǨU呼H+MMeg۵98v
+};;dؤ7Z/x4t#LXld/%"tZ(-W \TNēsx9"ud՗L_[i0iC2M"S4w&g7^068xJ vAhBtd/I%#k7G|hsDf:Iۙ"S~v`pp(+ėW"#c+&*hb{]F/2?Q9RVtHq'9E
+=DQBg4\)u(\5[:x=$@FwuzK>,Sj4Kˡ?JR5Š|M+1S]XkJS~B_*hE
+7Jd0tHFovY*h4V;(>:+"JVQ/o+bnc[Fw;}E';[ jygj˝+u@=@}'i$ʒkBI6yswk՗yry25Hh7Bv'p 3gGɧ	11)HeR)"ۂlK$QdLsSDIsA]bckL/'%]e<!R/H)'Lhup%@t4xIlq*A'O;?O89FD+9mCᏇaG\ƃn6IDZTIm!+qv/֭CRZwz0BA\^MX_"[mOcrd`"rG#WhrDwwW>R@T\H+w-xB%{|TiФx\z}A܉GjYd:~ֲ8:!IB>JK~@n\Qu}cgAaLwAjdezzWY[ib3Ej)cBރwǈ=z4/6Ȋ1p.X'fVvjC"I$&,O`d<~13C&s<qKl~!3h/ !{Ed}|yߝ){D
+ _ V{:N|9!)OJt5Ks(U5(Pu8[D50:d"=uk[,8/w˱vr8x͒#jLpnmn]ևn*!T!-P!
+wvUw4=\QeFR^^ٵa[e8sQt-YtRey*/;v?O
+%kpT}$^cop/o/Ja@gGaD"=呬Hh   MC>
\ No newline at end of file
diff -Naur chatroom/soundmanager2_flash9.swf chatroom-6.x-dev/soundmanager2_flash9.swf
--- chatroom/soundmanager2_flash9.swf	1969-12-31 19:00:00.000000000 -0500
+++ chatroom-6.x-dev/soundmanager2_flash9.swf	2008-10-18 21:14:27.000000000 -0400
@@ -0,0 +1,22 @@
+CWS	!  x}YwSɑﺯ֕,[ma<x4cdfdJkt%?$$w2LB28!;fnde9{9g~ȿ9lu-Üﭮ[U]]]nX"UBB; X9m_-s{{O3Jixppqqq`qhX<pȑ~{P1=iR%[,XH'aj:nT-tj̙yP@C԰U,qTeS37SƂv өd+9t1iFsRh(tjCK&px4=*KbB,4ŕUR5|Z/\&*FYcsFaj̙No}QTB#ac| 8U>FH{kϸe\`L~lC*VF+=55D~NBU6#z?ŜiJ9[St_s-zj>ilhOgRX֦JlT!:kKF!P6v,
+gRά4v̞*މ%JaZlH6bb\Hnhge1G'Yb+m,M2fjR8W6mۗ4ls|LP)s9,uh!Vy^eO:[-<b\5p&#7fd)iW]82)\+yS+y;ŒR@JF6q>-<Ln%m&sgF2gqͨt/sR-Ul1pIf̲ټN0vkIǱuJO]}*|~ĨbXerlΦ|f!]-l%ǫv40G9ύr|ԅ➾2`!ղQ6SMc?`9fYHu|g'ϙp$2K\C,K1ϴn嚑C75Q*Lng2F`Z:bpf逨V<}0<F:}FjFZQo8Pd3w/~
+*GUR(vsa,DQ/v{k>2SUacttudvla#-җBa6̡}7*XVFil,r٤:o6}.<.'UaIr	Kk7Cj6W}+FN˙JFz6f3ٜRWRiUX2\SqR(W{6
+2ې/fMd^vtHLo8âP1VuI^uճr؜ms9٥,Q}R&%)wɃ_5&%' ץUj_4+b-e!!T3aW]l&_	-aV	cTlzrdL⥉W6	kŒMbM8P -ePb\kC<!gBLNMZF^OV0R L^C`\6|gwb9I/Llvt2Uo8T0toB!>*$lsξ; zu+M"*fŕtvFqL@dש
+BAǨپ9SUvHOӦ';Wz*|t[ٲ]9ٗv$N1SlZƍ6Jc2д:wxX(E캊1~:'Un{ 
+>ִ%@lG1)vat+|nOjݥۃC]T<|K0-ЭwV\w;W|^%݇z[`[SoK]RMmo 9P(*i(
+-Z)QQSPB' -(lMavPI]vSC#()쥰B?G)D(0@aB
+)S8BaG)$uI
+~F)pq
+(LP"I
+1
+(\pծRpSg)POjξ%,鶤OJ%I_x>),QX֗$}Y&%/Q&P{އ>>>>El/)wf>-ػ휤$}TpϢQؾח}?U] _JFw}Tү#c[?gywH߃~߽zl¿[lTWGVö
+AɥaR@ pBDD$YTSdIBqI74|j\ɠZ$(iYR0ڂ?71q$Y6'T|6ԥ+vh݂>x'm/p~Tr@<@t wĂ&ZZ聭@nBlD	-c^viK#+u/xQq~	DO8)ٶ B#qr0"<9d09J1$ǁ'=G9͙gsQrs8= "dƘ% 	&V
+G # .]E߃Zt霉$"Od"3k$i}$6?>0>@2kc$q9N]ؕ.=Jt"xTD@ބ[$B؝ĞA$	bK]DXHwv *hb,|>q]n%_]	cSJy(spw3!MĦ͙c^!1jEQ*R3{Ù8RݝEWk7wSR[~?^bD$RY[ϬD+)-q9%-%$t$ֳs
+ArxYԨnCkѧ5!t#&<ğ2*Yͫ⥭n\̻B^7!w=Zׄ׊V~H-ךf5#aZ6	9x'~߯pf\B26Z.+_P_w<~j ÇI	6A
+Gx<XplQ`+;|NƘѤKinKuMU<AjNsMa6\_;N*v1ϐGYVSEV0X8㒸uՑUP}CۛD1\ΐ#y@h0;"|zG<1&>w5ǜ)8Fd1b5-aenb8o0[֙C36%fy7Z%E%EN`{쩐Gh`*}0	<s"_QVϷ"ܳ
+DH )M%%ehFP![[FW#U#KeK2;`%8g!	f0|Vz]G$q).=π1
+3ȍ{RzIJ[omIvǡے5$'uq>	+Ie-6%{'Rq^;
+	oeܲn.jU$4n8n@@"Eݓ2tO)X&k"/{⫎Ԙh .H܉g5恵 ]F'Z(uݔ)l*C:õ5aI%D>wv,i"P@@u|5,WX!_42 (W:g<N46:C|Ўxg3QK%C,mGqV 9BbWgkP=HG"%Uf)I_ Y lS++JC( oCxDa<|;A-H>: /.JDJxn_`w/ڼhM3/3@>qx>|O\TTALAXIA5YIA;+):JJ*+TJ*b%`yC"GY0V}WL<Zg%ƣ1^CI'q.IF,SOҵ8^Yvz!r/VՄu?n	*bTejԢB
+Xr#zc/~gez»fM;,^|ox yMק@>YnD٦oB5z vҮ̰yxd3Fq|{8gAZ4`3<xʏ|Su4aMXa}[Cls`j6.)c$0ff8Q5^*&dٙLt^'`~Q;-KƷIDwXݱķCֽнhe(a񲌗	EVmMc%܆@nV~9IYF~(JGK1?B5*G	+sy0(:"tU>t!vH%{c1g|6=*g|q뚵>q/c@&[4\M}aj9}1@WWk4o0-MF#"Z oz8/J/3|0o!8B5m+[:
+ע&I@4]l}l.{_bzvrnk4z]9oIfb㏓c3yxfRt'g;43:t D"GBzfl$^Xksz&V[˜%̉ZR͜ŏ12Gk$scB	tq"k<@z,Kb? v{ɟc{]w컾\˚u8!ox޸ ?߿zMm&7b&^O-ZG_ɘRZ8C2ho^bg'dnxvhchfev˒FLxl7}8<Ǎ
+	#1)3O?F%˔`;g$r;
\ No newline at end of file
diff -Naur chatroom/sounds/message.mp3 chatroom-6.x-dev/sounds/message.mp3
--- chatroom/sounds/message.mp3	2007-07-10 01:36:16.000000000 -0400
+++ chatroom-6.x-dev/sounds/message.mp3	2008-10-18 21:14:27.000000000 -0400
@@ -1,4 +1,42 @@
-  ęU-$9  n9eP8P9N
-QDphca@fhXiAcAo\~wh{5\m hj<fvjzxJof$v
-wRq{RiIrb`pLaPD` ````X`(Z4XMCF#0<󧷞v))0   H`   `E00030!. $L62yޕVzCv)4:r,õ&pdIB`$eL1C	J|8(}p։ZPot-P,p B(&l 	0 0TFXڕy0 ,%	`~Wu} `  uuLR9D0It '% 4y%! X 8lp>@R7  a`)i`	BAX;@A( 0qdax,&!-SȂN hOh鹢nh& gA :PP	xBDMDBCR
-P@5.[ 66$콫HŦ_?|MG/$R*eLrY˒.K??c1f3ʣQj5JiiiiiiiiiYSSSSSSSc4AAA@AAAQAAE~ACULAME3.97 (beta)UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU       4  UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU
\ No newline at end of file
+ID3                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     `d                 Xing        'j """"+++4444>>>>KKKWWWW````kkkkttt}}}}                   d #*sCZ$)*DRwFr;i&41cWIOGdlSYӦ,	0n&D$p3 0D<ρ\06ύ!Poo0 K6Y4גHrrP߼끋	qq:}w?/ﻻDOswwwtDDDD>Tr`>| @|  P<> 2p:795s7O5Q+*YΕ8@tFGS  [N3PUQ,8Q)*r}0!,,1[ &BLD2恂#xx{'2? p <xc!u  \0(ILJό0C =L3H'B$GΔd4$z({Nyn\搔tACMd7c<NsL52##M(3sTvX9ʀ&p6P0x/@ sW"\`P0e1T/^)5j3,]hI`T:$HLʨIPlJDH4ԯEGBL$T(T^0䴚%BPBT,T
+1BBImږoYf(bMy43](PV7ԥR^7RtT]H(å,Ϡ087MJ$Y+QJA:L60
+J'%pe10`P"Ѐ.(CIg30bfꋊ`ۨE[d>nY.w[c?J+xL8N;diK"{L$qHL}(\4t4 0aa#p `s.Cᆢ@ZY8ppf.cZ:[H@#Bەf: &<o_1Fo}ÀB>4uh*tЏ%D֟(꺾FsXa8Op6CF#ZUT8&F*<@%Wp̕!~![p7! a!L<'tgT)Mxq'03(dw58@՞KbӈrI L\ v:@FX
+Ϩ @~`.d{hlLGbFfgѦj#&b^'F t`Ƈ@b`k0&#00H\0@@y"R $%l05"@Af=K rl.0{VΤ}sc^d;@` Nu(_M}8M$t0*bѭI@BQ:F%)0j	c %-_)Ɖm.X$n(8ռ嶷\$43zgC"(Ȩ=57bJN:v.a	7pL@XRLX d-"{J#
+6DFÄ.d @HJ
+@.`hx`B2F(_F" j<gB)d.2H)ٳ׶ml=l*3hIq|*ib-pt.mYqƻ;57B濸뿳V;wOo^o=]u])0X=[1 $04%0]5 @pŘ
+C L٧<Rat*oPSPOֻ3Wë~grJ?-RƎzSa,҃N 0WA$0q )+A͚Ctd!{L'j2<PIP Ԁ O$##2MF7:ʁ"?P*Vim^ꝯk2Y$t;.ܿ,@6}2"^涜SmHOi(r@hwvIߢ۾K0 #6I"! 2/a` !W H4 0.M.:B&uQY'㯠\8	k#tWYuʺZrb"c$Y31.WZaYo$j;r-mňF;0XM-HL̵a,`pd$(E({oNɺX\DLudLG<0Ky6cevclP6 `X$qʃԌ`N0bLZ5(pz-#  Z.	]j@DA06ZXw( /p#A|h-DJ;8o$Y&ONNGpeQd֣S!U齞<șt^RK1L VEg*EdȪɞM"ǁ<P㿏JC$Hq!JyyɭBpA:av`HaL8u1<@!ɥ:2$(#2EW;\B럆#Iu)\e@F-4h29s>+mҌBHaUD˿ܛLp!	 8p9&׿Z1j=`emoh\|Dhd/&${n)TP !T& H"$ 4Ls$L224dEE#D$ сA$R@I (bP8岑BVP;,6_2H1ĤQSìL'V+"}ero6~]ktd"]T3>rWeN߹2Z߯}[|1A=skٺ1y닭xVsUKqt{ֵzנ.:	Y
+@ NXM0b#  y@酀  i(MPg+-U nDT5=	~u20,
+ՔŰ$ћPoQq
+%jazqռQ7V%_XY*Wk%,Ԣ26d ] Ƹɀ,t7dyNMdK"{l$"L&<`ıL@9`N- P8@\p LAy& "h^FAe!H`Z'$SNIn0s	\ƗIMEV= CSv`t
+$:Xv{Uj+E{|یT[Ó%h{Y.ãzwE-Y,=g]"@Aŀ 4i AC"l
+30[nu`K1vPZVfttE'#(ȶlC<٦nՠ_4өe,f3VB&^J4zŒꐤ 	@xd5"{+z8\H;L+0Xi"46LHL(.&@0f
+ U.Ⴐ, IU|8@aoú,$Ğ٩w
+ɺn?o{rh>2?h%Rh&	"$!͛zvXC,s8ٹqo (`debfj,f!`f`;88,e	2" 	Qu0<EQ@fe#@@~zqy䑶T~j7%M@9Vx})ogTn5i*}  IL@T,Ġǀl}<0|r#H1d/Q="{<%yD^GI0v+ 2LFA)̆to  0Q`,-fT7ۦ; TBVDQVjDys0gZj2I'RLft*Υ&$d}F"U<xeZPm ##A=L1kb7B v
+H2'%ևPιvu	WYxǹ:+:SBôy+@ZS .7̼Fxoru
+}`,H` `d"b{Fxa(&Vo;FBd.i{kl%@}v& |@Faa j8e91D@arU=j|,VU %~~-OEwMwq(s߿?8Ma"Ґд'Bbgs
+s.9m9Q{WM$
+ar',(
+OV+S@(̟B]8
+e`ZUU)0>)F&J~nh{؂5Q<Òp=kjI"&bT@<5q6.G+||r 8P,L҆PQlDɋ0LD	d, {><ИPF	%$\*1g#Vff"mf}Ĳ>.*t o/hޘ[g_-WT˖y\~y͕iYUU;bˇ:kSC}Zr?:=mkV}  	R?	61;[AZ},Ff\
+381rm:'0曳9RϿM?#Ehp728lz,94:^,ja]"Uh 
+ ن{*a4Yg ɂ B `"0XS  0%d@{(li@^Lc*1[r1R1*1c	vω~ƹRv1/+yK:Ԧdmq%-OT=fFȿG*`yvU\U"v$kڀpTT9ls@Ĳ4Ƞx@Ƨp L(,l$јdF
+ tqO䫎3ZI 3ЩOsr% ΘhҦj>:J%j}':jk*;o,F|ٗet?^mw ,iȕXD=q#		хu
+i|4.q1]HN OY4&&` m/av\`{dR /{>!@~TVX|o_~O;Ec R[qHE$OkAW ֲy'Mtr)=)L\]w'WH(\ 0HK6000Q0<fi ahDjs?1مh(:ҹ]cEl/e1HF!؆Q|PM̫=m'0lA62F7,41dX l *@M\%(H5Dp%! X.tF2Hu`vQݜۃmxu۪$KI<h-,%%V_6E1Iٖ׼d /{h>~@~$t<TƸ14腨P㒧9:
+ewjNXT1g UG ᴠ2&X@!auV
+"QUa,`tĩvyr۸ֵk'{lܨ\P21j!v)^GA/>*B1^uy%^>  ˷ʀ 8aqZ, ` f`lbMaarx@d)^E&ރ<Tn?vɢCG2埦4ͭj]G?]Gy-ڊߵώ
+o_$AIq1#hS:MȾMA]da| <
+@Aًd ?{F>q@~͏qيPBER2F6~{VE$s) 5f麤̵B(c14Nɨ??qdļp
+pevwu$˶X 8˨` |a3,`"A`fl? zRÖFa-GeT-w$d]NxVJ-w:[Cm%n!CR
+p9^,K]e7cpQځ.ߜKw(D	s(``LZgFkB,lW [#[{r ,m7Ō1мתEȞoͬ
+J}ߥL	3ݱB;dA?yhD~qr65WӕLAME3.97UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUo` @ad<qAec&rr,b@j` V5+WF	ϝ̮uu?	O\i\'r|ۉ4xXz*ˏjwa1X5q8刦9Zi[&ǥ`$Dу  4LchlNZ~17jkfv3oiՉU!˰ԳgA`lZړOZb
+j)qɺd ?{)@~KwZ pL@Y͉ `tZh"`ld2+4ܬdAnҸ_=L{U$nr#aW{,ʴ*^\hZ[Jk,&l{*,mUK>HD'e,A	`0,HbBbC 7yj{.?adA&#݄S&WBKaeSV7`C\'Sb?ơ2a/W: UUUUUUUd8 ?{6Y@~1UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUu@ 0`T$cp&n[v/JI>=ko
+I͜0l(Y3uPIF9ȶtwYrg-ޤ:7UzUBEU/Q G< 8S.R.`0F~ÊHO20(!R3EY<ˤ$޸He:H>ϧb
+j)qɺde/{B:@F`& ^\C00j`2`>aDoQ	Ms\'vZ
+ 1E
+s\8ePfy;CNFFƗ6,02Q\j*u9SR$ihҴ"yG\(ogo?'U5hK;]d2%as)sXSkV{tȚ02	%JURSSQLˎMꪪd /{fADh7q@  	k@QBq)>=~,n+2drN~PrzSmE-F#WBl%;x
+;b9g<~MxyCa[P)cJa{
+eV/zxnKh~N_}0a?ZR
+Ȉ)9hoEJwPE[EX:gWes7	)e&UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUde"?{cQAƑUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU /`'0;$	@	#!X=gjq_c2Qϔb"KR2ܜxɺzq}qڊ;V ocNɃVSB A5_>iQ{0	N0Tl%?rqD(J/mr*SpsSHui)e&ꪪdQuCJAІ} mwր  P0r%3$HE*.xfO
+鄈0(CX9$E Im!9ME$
+QU]`Y`0sZ5K۷d *Ň0N8Fؼp3,GrEmd($T6l²H`l*2+>2vt .h(I@"0m@1DHS'r2t@I(Ѯ(mbGD/m>">^LXH$OxBӣ"SGE1M򕥧Q'f"_8>;v<eíuh3<Bb
+j)qɽUUUUUUUUUUU )qn P pKpIƔÂJH8rB!A0ЈЈӧ<"XH:E%V0*	m  C@ieVEYY&1Ji4Z"iT3B"jв"j`P$T4TGDTPNMY(+B¡bLAME3.97UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUD7 =AR=!UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAMEULAME3.97UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUd        H    UUUUU
\ No newline at end of file
diff -Naur chatroom/sounds/user.mp3 chatroom-6.x-dev/sounds/user.mp3
--- chatroom/sounds/user.mp3	2007-07-10 01:36:16.000000000 -0400
+++ chatroom-6.x-dev/sounds/user.mp3	2008-10-18 21:14:27.000000000 -0400
@@ -1,5 +1,117 @@
-  y2u7?  X͍TLd`Ԝ*P䡍IĽtD<$@Fa @jP4ptg9igZgV+bX߿~G<     T<<=  1xxx   |p     >8xxx  y `8ӮG\0Oa Xd  oj \E   p'Y@c<3aӰ(
-ו{͜'XȃcϤ94[**86̂ga#F 0@0hacj^K*QW8DIp/*9oLįF
-Hi!E   nmk`
- g ;@ R!'{=K
-w@fȺ+k,PH^p3D A eE,PXP0 O /P <0_ A 00n0` ,:OXY`@A NhNs8`Rs  1@S7Z.gS1G{D;VLMp9O@ VR\Y*x2 Ǆ@S !S߀@bE A;=m7ᅖM b*(b*$	 T(p]!f/&P!c":[5p6LAME3.97 (beta)      4  
\ No newline at end of file
+ID3                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     `d                 Xing      V  | 
+"%(,,/248<?BBEHKNQTTWZ]`cfiilorux{~~                   d t&sypPX(lk
+בǝDhqDÝ)B(˴T(X`m	8+zacB~Zڛ57`6˧r$oN_^9O,#>9#|G, 2~>FO 2;F fxFO3h<@*@!kA'yq]`*cɂ [f:4=5F/}._r8~	|pD,Nr!
+ ;D8L,ڜE"Bt 0	0|8.JLXӑ@24V&dJ &{fNBb\pz   "r9C`vUz	ht8@"18	A2Q(C̱ʝK]VK(~VE88*UU(KHj"0QsRE6`Ln%.YI-"<tq(m婴ܙw:<V|jGاq2 /*z1a0j
+-ES7̥ނX ġs,kSԎ+V%OE6%4cdmԧ߱!BWR_{'u,i+A)AAYFݝBqVSUݵ ?@0VdY {E\ZI|s 3L0PQ3&P0G 
+ =nj[Vfճ=DXLba}- \ϝu
+]3/^4(b)R-:ѽ~R$Rqs(L"+F[;1Tc;Ϣ&[BlԩN_Q;  ># @0w3s`00@T5])T\4jjPَhG*($<WL'u=Ns{5kTJ뫲XV	va	V
+,zRdt,	@  \` `d {(>bAF#c*b `DF@$lU{6wޘNl=`[R_T(Ӑp6ffl	_N'G\ {1~߮n\svL\HJfGMEߣW  )0,v3]CP000d`%X\=UZξhltb $́ºT!S1=&5-W7DnQi{-CˑVΏ:)^Ιfì>O3>ڵ|S DڼdMiVc푚E % =(р17
+$3D{(>*BZB^}A8) I9AP"
+0hvTs?3v~}gVV=˥S?8Feg-2yG(1S|O	r:ƎAvQ6|Qnsl|+QF yL7.o7nMF.l:9p(  " ?Z `RFe~wf a:	 fbVEa +z3iX)ܛ}^k!Tx۝IahBZ@"eu
+
+	!7>bܩ'eIĞnqq1QQH`pT3D9љ#LxZo"7?%ۡ6
+ {`F`zdu{N)ZAPgS'av	F dX:#``6 UO(YLEpf<\'&^DZFJA	
+Vz̙%۬˹xUE`0%Gaβ3?=ſIgdj;gd#~R%\d@ZP Z`*d`f a 	 WBn:WsLMSΒv'v|FʬcIkʆ4/tU*J9ǌQs-wPt	s- p*/(#PRmԩ{pxMqAn '(8-	id' z#\b=֝J	X. x@~@5L:=dK,]*XJ!`YHҪ5+)e)Șm7wQ&NAڝ`ReQ;e]s[Fgh ,"4˭N
+ ä a @t[iS'#i$woHV/\/֍!ۭQݲ6q4^bykrFvܛ=7RDSMk5?J[5ye:hN=)i - o3P0*3DrE00C1Yd!uxħڮ=V}s 6% *S_naRNW~`1V=vю:5&&+3]7??S>c
+0g=Y-)@٫tܾGB_T&emsO;nF>{㯩z u F`>	
+cxf```5f>YL$VSgJ?9ƒϓvPW	,@Sܔ.7	~UAFƢUʠ)K(֪".>ײmVڻ^׺g!7^9sϾ] "oֳ0@
+3_r00PWhd( x(;=VȄZrnaά/,Rjk>M
+5}m}H7{>eޱ<d۹Z}V$	cȈGEs.1:ę%dR|'ʼ58I◮ΆB-o o
+ 0Dl,M FTXDJL>[XT6tr'O
+=b\6{rÚ&}ϟ9Vh(&*NQ"C'ʭv9-(72J\(8lܢwDdPTmeZux )  ܂э@dU~CDDd.(-${&~^]R8,$LB(a&A`&`F6`F a<`	fle8pip98鸖Q1՜@5	bHqx
+]@  1 bAP-!%AZ̹e<ڂ!-֋Zk,9~wR.3D_gS݇McRA4DRҼO,OuzcLIL(R! ՖdY$Y}x|r#O  1L%@113smnL8XIpeJK$^=5ITK,6!4*('}EqXg6CISbMKnn]dY8\d$!&[,>q><Tk\AѤ=Ł h0	S  0>:1J 0@	3
+qg0Y3e0s  C9, d7= /{F^2L~Ӂx+³Ɔ80^3(kN&yff>ZpB+U%)'xL9G,b_KcHkb ,6IaZ\uUyMDm|:*xx֟5U嵯Jz&>Wwi/LN^H+j`t	xbpF1Ǽb^@N`$E+ ( mU41#pcmvaގeSҙtr5v03!P=Z[͎+U-BRlQάRXGl쎬mκț6y68Ҩj	˪ԭe/R}j`*`^B^e#[ZbdQI {(\,{AP1$
+#!0 H2]<ۍ٭R̎z'1]*yﱶfbq+pCn:fTb87vѮ\/m'<7aClTXFڪyD	9zd4˯(E<u{'X m0A `G,&b``00`cn_oTAZ)ciT-牾=
+ lxD8$R5qBf;Ju!6hY~EE'8 J|&a.mGݹȥ]n?}B_UVAhPLd"?{NAפVLx#*-pt
+L<u^@D, ƖSu&U%QahEX3 8`Y>dSmGU[ ;`_/Zel.,}Ȍ/CQ]5h0+k,!R2
+o0Cjl[(}Z?;` u 	T*͘AL? p/CMk|s[ܶXUZ,q4| VfZD~7f[Ezw;!Յ =g%6S(TOдG^yZe@eP1	xˉ&Xr{@) D	K"?xƭD^QI&)x	Y@2y%4d3XNIE!sf^;g-s6P cA\kZ)wYvp؝3p\C޻y.>TFt" -\;u޷QQH&j\.	\ƘL@h07LôaBDi ˗0ұĜ4Ĳ7+;'CF%iݙ@|Zf@4TeHm'EC>y]6mPtM2l0<uHeJ[s}DtD>6-fӫOmoUmP[ @? y(7QYT
+p.4kLTTUD3w?yN@Ï_e4m1(0BZ4?d]A`zs!:q#r;[V{iC(ږJ#OSWW<-Kc&PL_ۺ
+k dJZEL`	y@(Inbk+LƇֵ4n=v~5-`qq~SM{m`QGŊI5 R{.1ZVitY֨+=;@(5C=s]??'鵫UeX  p3Xf D
+ >a``h,ﳊD/%Q?y(*ʚ<~5k9w㍮RZW{ty}ÓwyotF 7ϔӳ \Db(JTEgk\]6}IoW3m+ݐb2L3
+|8\< j. l`Rag4jw8	 &0<C4 \UV3IS\4U,Ԧj}ا(mRrlt06F<\1i=ɦ[o "4҇1bN8k4s%93(B3lnl":?	-|ZUnB	1/>A1@ATd*iS ?y^jj@~mK^U78޷W83n:e+j#[]?a;zïtVu3ع$p᚞FvST3׾F]h^Y\\J_^S c  !0%*3qf
+C\f`*.F8 0C!ݬr57{-7e*ͱ{_ZH+Q00HsMYw=ѷdJ+5뺔[]^<7u'@RdtxQ۵a2t@!>a@)0Wn\2C`0{12	 50F	
+@x0@C@&@	a!QᰡdI }&{,N*zLq2ddb 
+Dr&"bؔC!x0ƹ8ܧݷb2dܹu֌?xH&ʄ愅*7> @aX8Xx~}qׯ}lf]_}|}_Vm~/{{y׶}~^ߥﾞok;{{ܑ9ߨ aI1!Q(M0
+q r-ۇB.әlp*	e8@qܱkpp>	pjP !4.<ixaIxIӗJ^>}q(.[@\¼ѩ#jC>]7 z0	0C 23 3G33H 0F#d&"(/{n:b\> 0l0!0:3@?0.3 `0#0 @ m@A2 RԠaٙd)S2Ȓ5Nt. zzR"ej="4i~gl|f\FWo8*8ĉQ%U3{5jGx8۳K$7$rKG$H]%TENUU"EK#qr^uFf{UJ2E}o	 t2xT`t-$&a0`x2<51Xg,S@`ž˲v4zTuv%Zܥ*$ʩJrW1c p,ХI+\մ"eYDqPAacHH·Wڷ3
+  O 8QNtHD%1u&?{zH~Wd'7Zs"$Q]\x@mNܴwjCŤlXPIŔPE`SGĬ12Hh!,L֧֪Eqe OwI&)PΓ֫&R.dcwܰب3On%[ $	Wb͘Y ! (QǰgB 1]A5-=lj:Jn?kk:N}[\󻙨A;JTum|C@|6OiJ9涝[W&o{*ɯD^vi@ecy,m<2"*MЛ)lZeT*  `!4axD7$z()Z^D~ּ2Zh$Q!`,b& @bu<g[4J m^'o{nn9Piqgc-VѠl][Rl tPH b eyR4ev:T@`0_ ``B 4P׌XL	`  dn4ӡ=Zh	_XG_w6f{}10@r}|O\?p[[U}z*Mn"%wO|bg	`.]/Q쬡跢  2 bn1DYQ"z(*D^ֽ`y'B` 0  p]0CKQ1A3syb$	o<_['K3{Inq'ɍ;PEMڐ㹼#MҼԞ>.~a) q`w8}.1F2LV*S[ӓ OF
+	 C41L#0C0 0	#Ґ h`k^8ʫnxY!ĖϳXG~]{״ ݋H~oM#E3I(.g)#J=F[>a[n!28naw~lשn/ _	pqߚApyPI9`%d5MC&?yC®H~E'RxKǋijiL%6Ku{a76$utI܍;-|eYD^H>@+2<MK-j֐#7@cf*viv@o0Xl"3GX cCO3h  mH$+\Sf<.wjM2Z)4/9eL:m`IU'$:F%3Bq!!h2]4.bsw3_Ե-N&hюnmq"m@PдM@ AсH9$$9Иd"U"?z#v@~U9@((9J=F;4ԑxM<YS-gܹ݊0чA&E\ᶆPz$#ֵqĎ]Z~V.6S&w"2鹶յ}@bheVh^ܘ9,E @p	>=wjc浣|,[_gVpJmCp+"I_Oy;[~%b]bU*UT#Ny0+lnqBܫ:[a|Ҭ߬ѫ	 A@!J/4&8NCx\d'"?yn<^ %F^W1ue#vkq"@I-p*_¯'.1uvX:gjщ(4*T翤}k27ujdhXT%"$6]G,J[Ez[`K5Cc10?C#`q`Hf`vC`4X;fݎO=ڷTnWQA
+<v\zV$_v8xz[ui먒owMg_LETѸFz*Aɪ8m3խE's4 ^ ,YR<Y.ᄌ\d$]?}N*@^( #C
+&tBS5$AMlipfz"LXT1Nʲ3q7״~
+B܍΋5G//((EFYV4q#h
+IhcUTzN#!qvF}Si7?&XraHlɏZddA؀rl+vlGſIpە:Eѭ
+x(Tҭ)gj7F;k;R:4kWXkK-Q-ZDF,}JwVooVYbXΥHԌ
+Z͜A{ ;X0`0#e8d]/{N-rD~36p.0.h!Y VE4
+A_SP6x͹7R9bùWN􆞞]Icыn]POgBƮ]I'jfNHnj*{t]ש{VV}nHNJG2(X	 pXL] Ϡa Jdla0^e2:r1]"7(M\E1|\ܹE%ns{vo_]%vmeyaY^TkvRۜTH֑dvc~8"=,a"MgM~Tk`ѫP|m qHd!)- ?yc2D~ԝ YU$ /wUW\&`Zyњc'}`SR(U^M_Q:n`]Hѥ;#\ȨO͘vc%@. .4S=]?M+9!BJxjn=~8oBPWss9
+1rcv093 `Q0 P01 {)p4Th))9gbh\ES"Ìn(fv-ݴ[{zڲ7,JӺ&MuS^ckL*э!x,OszlZvt]_Tesg|o3?xM) `@ ZKJR$RW
+:D#aC /uhN(^@^V(S	HC:?U٠Xa[	SBqV`) ~p} HH.mT"#I%f5j5#I[ 
+CpiBݏܜRu?`r@ bx*xmE[	hfGA842T@S6K9g(7kmdIɥTbX( Ag磅W+L@pE&aͺZ禃E102:aHq	C$iPats)̡Imk ?(
+`׌4D$S SB {DHD<%?yLj<~Hյouj}0\P[ȭ@F/V^6n"y3FlǊc_Xy箭D:k{Ix=1TNB͎ۧ-}->/ RSclh$Ā& )ki `x	F-&8T*x#FZ}T"K6$*CUIwo]˓-\VǠG{PY,E+TEo}TCH|Dya;׽y Tis*nDk04L~6W][¡i nk 7WaLTuT?RD<] ?uhL1bv@>=krH'PanfX፯ň4m;4PJ&͡wxOta|j<L,i-?6KM!sl<Z`D0b3KH=&dcM#0pٕ3<I q5  Op5(,%+E@rrVfmy(N.A](p;Qa1-Z^l,Ŕq?_*Ӱl)xtBˊ}Č.YD`󯣶.w$_ZM 5D`<=`حfA`%l4kR{Oo7T1D7uy?{v<^نzȽx?*LԀ'JDWrAD ̚~(t:|=UYo˓8xYeQԻ}_?M>rœPY+(FM֤&B`B`BF? 9$aWB8ĔCZL:ujs,a4U+QʭI#AѪ%,IΗ|ƶ,W{VԉӯjD.؅U`"3S֔&olW=|?-j~M-9k ` L'~L ö .f2fD-?uLD~(VԂ^UtIPvcPIY̎5KiOA8hob
+ssS̷U_lڭ꽇k14A&өn1h:BO{Y}2&ck\-![F:6;尃ڀ3I!	jgS)`:eC 7XçuR'I;Xv&S~%AOƾG?BR<qT'9_eΆ22)[|-7gװDvˎtQڲ	udN/`&Ѭh02=96t0;04.G#  ~`L`$kUVsD9Q/uN<~֙G!TQܗ?2:oXVC.VJh&GBN֪8JLE4]Lg._z@"N[aUM"E#+nUoYXފ_݈ dث+Țم遰0"0F@ZfXm+ؾq7uwѯoclR
+"I,-	)$4{̎O?CӎAءjI4scCՖt%à:v8I<[wh0A*01ԝ>W a)Xyx%Ym]S o:LtHDJ /{iv@~Qm~7:` ܈	6FlInn h(`1vu]@	AmrRG9M-Τ~;Ҍwwg-=ʿWxeZj!Q^>FRX  `<FqeaGfN~`dX` 1޻{bqˇ԰K'Ӄt/s/b3Ơ3p91uW_#kf]_}_7U8ʍF0NSԍb) &|?"M;}u%CB#>0 AA0[130 0(s	pYgLd0D6Ʌ"/{nʦ@~֙Rظ0  imR%ӪKhOECNU*乲}=KWE `N,iQ,4!9XfC5$v+JhVV
+ؒJ1q%FI.v3q|ᝣvvlϙy>qPoa2~ ,	0-2&3>d4	C	@30'3 
+00 	b9e$\=$,XedOV`Ä"GkU=ol8CL^wj	Ii͝>S]uSjq1r󫦋mدVw4`D۴ʘsv0{ (bCS@&	ۣ1D C	 S""8 (XD09A ?un<~)]%X]O^XU|)0V-Y
+Ak+5zw}S&cWXcU:^;_kU5_y;[Kƒ@\=ECHO+l`[fd[|tzb!,bI0kr,>6
+W BmP:@3뉻턧|UY6qa'Ic={PstezW75fGâs#[S.c
+:i=4pϮnT @8 dLF(Ű4@\, c&( 2PyKaw\D?Y ?yn@~P=1bKGqN5&	وf}DQmBK)h& ^s*9Zί{BfFc.Xa?m]DͭI[|{U]_5moPT"ʢif,EI	;BZbӎ~~~PE(9 0CdFm0NyN!}0 
+"j6Yzp8>Ea:IF,.ޤ}lF?|]JCLZ!P@=Z@&/B@@F@+]9.k:DBy'@
+wGArEfIհO	GGkpDVu"?un3@~(%c#j51rإ'omAY
+0.ٹ#umc;o:MoݛOǩaA`"(c
+`2$4(=D́f!9ǘoUpus%\EߍJ[CpMWT7HtJcHUG,JgZ!{R[zBV:"ImQK}.z7I%h#R*qA@1?	*^a@kx4Dyme@'谊Ex':#\2ܱk%gNk^U:է&+5'DQ ?{Aϒaooc8_"z	<@{U
+b&ކDe^zfF@`NM-ч+!(i[v'^\a-!hg!07Wc B{/+w)V16ZW%R^.]ܻ|w
+!-1:($0b.Hv5"3~5FiJA|B: A&T7.Õ1FhR=[\SyNנ>y0n3?ؚvH30P1({
+f]Xl"Vs£r( smm_?lhDeM ?qnjv@~ϐw+7!`H榆"|cWfmd``<
+(@+ Z<s!q\D!'4(+* <L$A.e2TICjef+PnQ*2%txƖMu?6>&$vNH0y-9՚0k6ׂ |ԷL5{p}>$>rcOmmoLc~{<6gR_7.ny5ϧ1,qM!#m[k}u$JA040
+SL2DA ?qnf@~ϐx!0;	P/IjfΕ*Ҹ6	(`^A%`KnCKXEHxѣ41|c-ԌYO\E<k^<hj)62uSRv*3enRjQ[ y@FbYFdg(JcY@(84V{qVXX_cG>vG[_Y~+oLb͑FR+EXBQh#{\dQ߲my){z8p*-ܻԛqM̴Ҡp
+)GA T>Iƿou3m}FT8sD'?q^<~P(}b5ɡ+Hh< hY(י*IQ"<VV,QH%!ƳquW5!86 Ta)"SkYU2rcTUnؔ ,$u!jfPqT.sV0nfh۸	عT3fI/iupb;{4z }
+&;7Wtan$L7ZN}v%}:13]ryR{wnV0
+g!UvVY\[6GRc0 10 0-C0cC*	' "VIcP@`8`D?qB<~R(	TWz4wj[^?qM5.JŅU-enXt(4$ t\$s]A'Bm)GZnm.7'Ͻ;Rn_lUpG#:Ry5=jvHmB%Fh ([hUS#V+3;2?yw>dur&7e;RWZ7n&luڗ[6]dxXْ|FHwD}@[uڅFCͰ@Y``N3*mRr/wDy ?qL@~Н&7Wib>Zɸ"?#LRFttkjVc,pg9|9H_֛ș4i'N:,yت. %$A[?nJB߂Tj3@RM-3$VfTJ2dʝ@H-5յo!y9RT|%IN}8sͳxhR&Uv2>䖝Ϛ[7tvmLgkw`Bx`RZRL%BT8z0*AoiZkZ>	֚g#:GM̤줂!M 1Y_FR@5V%yD1 ?qhN<~L<d֝E3dMzx2qw4CwcӡZ6
+i)e&ꪪcO)b!Lv&"j*T)DA@bAzxW7t>u	§AKFq 鬝HIDȳ1LcI'fGY4zwxC4V@N0bqnƚvU7Z9X]F@ۂB4ޣ	5MK[Z;-MInd%)G2Dݜ8e"n8;vN1$/8kDe/uN@~L=.P+ΘM;}|xy9aEMI)e&UUUUUUUUUUUUUUUUUUUUUUUUUUUUq7cW,ni/X(4yVQaH6ix:BZr@`PniEv*`ɚEљ(hގejOG/[ BbEX@7QHJZ5.@R;kK6& )
+܍iGJJ F*v>#ʳtHR.>ár찄 K\ݏ)I)F('1rD5A ?p(H*r=5HΥS:=d܍E^,rjwl2<Q٪0zsWI)e&ꪪ .M0("M0- 8W0c<
++:mCGٝb2oCr"WN!w|4=u+S8bFEYr7}(9!Xf	`q)ODsnklx;e
+bL5K!W)Ki ے+@!G6S4O+V$VZTu-w7H?JEG$/]#rYC\Ue"!# bYbXPCda qc^)Cj=D8G'tܝ-/ȗi969~V?߹¤3q1=)1giLAME3.97	95Vc34(Fd`C}4U=6)cՖ.Hy@r,b{<rJ-{(ER5RL6kә1*}^)g_oi"h9[KrTkwIH)P9H KdgC#	46`kd
+T űk">kե
+{L*7=3`ѳeyDy p&F҆AP,~BEXV@I9jg}r UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU9.~tB8 [ <ER⟜QX$؝N$KFzfq}?J|("=hņ=;3US\P&5`@;t&Rit/!:ba&>ah]NG?Zʫ:UWx4Ah78	a*qL)y#^d\8a{'$~if4XZURU#7)ʶD phJ#=L
+zۅ:Nh-3;u'(6Sd15̸UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU)'ZCu iBLUFd+׻=ǦqF,Dr9,
+T#=\(ۘr}3MΔ86 xm?MĘo]qDᓭQ	fjU-Y'"O]L4*gKɩ=;"r(T$Ūv*  2#t&(0a!C9L,g"H)#!$o	thӶY(it(1CDE?u#"=P1[Iza<tLM:y/,|Sv:a㍪#81oJb
+j)qɺ)8k\ YD`8$fmd8 `Q@p `y)9Fo&ѤnHr3׸mCY5^,Ytfh։i\vhuq])b8ƭ,<qK%U$zT8*lf|^|v;{F-.mȵa)S7b8V^Y8omuL0cgPO*PeZ?樨q':jAXxx.OazwVh~hD]?q^{jB^P(G2p╆dQ:dIk(ŕᢆcy$eGWUWirE-5=u}P2 UU)k[^tSHq``]$İ)G[OKJGk 뼰wp~|%K1Q6Yb
+0ӇLZ{qKfVaԼ36Iy뤾߻iH)zM{LWu9RrMw@F_c'= |D9K2ktNJV,&3fZjyу
+893.L̩2B5H弖s.`js=M-yDq?wH$29БRSkSiuzT7IgZb
+j)qɺ $Oe`cB:=
+6P*J͗c&]NEMMwq/lpm}A>hw$
+g1IdP$TDы #G#N?Fumޚ!e&F8"sX _Uܒ<)dǁ1	+ "tܳb
+qtI\8aRX'"Dh9~{	v*:_[vi޶u;wYDe mc^#ʢ=̔9=dE6ɂ59@OfSQLˎMꪪRdi0,:0S12%Xd"l $oޣՠ&QS%֑MLg7,>@5
+oFK:HEIC̻\K;+ߔ{/rjjgn:Uy*3p2sM2uL]k(`6ԀRF@Sk<N0G<c[Motshwaa`T"J"B
+n<Ct~l-+C!D% ?uN$*Aᆝ@H=|>bY߈ao絽jq15̸ުIfsNT<N$({\|(-s{LЖXg{x>l@bH\a8[f2)+=5sK|Hc<l98O}>^o[ewC Jf8( EI%\\V>Sy\_Ǹ&.JjI0+D+mz.\HfĀ2dIoaD!?mhNZ=-^f^RtD "T0U]b5pVBw2eb qY
+`ڻK+_i{֥tLAME3.97 RkYF2^km^:ׯ]i+3WcV#X(j9؂KR#D<Ea\jdtzaVTn&Hb**ngopxhs-xszvt>uV~ VY+B6-؋+K+i'Dɝ۶wXz@$PB21ht7AHԹ%X$GGSDEۘVKr#fduW!/sH[.>^АZu-~{c=Z98LeP$kȲ0b%LAME3.97 [oaO
+ݱ'yIb]NmsTMa/f!IrpɃ3.5`ub,)h|Gd[bDBP6.%Q}IUƬQo1?1;F1QMUnmKNMZHᘉ~YC˅1puo<avKr9s
+G* RjhdvIAй!Q:TL$C	@8Fdl(J=܃vHF]ԤfQXE^d\Ob
+j)qɽUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU [ݾf61dD}6( -fb{jReȼD4G!d{TFDJ067' Y-ɧ37\&f$fΓ56jS{Bd% 3w,,*hi SrKj+U2H (LxmZgy]:퍰ٟSع 6nfDiqCh7"gE%=&P|hJN/d/qN Aن<nR1 c<WS2uUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU M@SVzT`	h|7^Jst4N)-_td F]b'Ħ.Ȓ
+5Vŏ;0f:XD_J'MIR47sQ:S<35qE9I,VfELT )KǱs;\ےY_2CK\!=Pқ¥=MCC𡄩RM9(@	KZMNɽj,''-~SQ.eKo=cd9yi&BA̔od2Y'7g;-lvY<yg԰ЭM2K15̸UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU iKP3c@ldt nJ1ÿs])2B%έ`7F$A=ǯ(1CAJwHY#\UhTN~GĒnk #v#q&AQ;2CEInKoke]F0:duࣙ|QRDv\=lp!K!@>	KFrs6n6Mx΅Δqdsbɲ\/דׇw[ξAdQ!/in&=̔9FDzj76G6Ϗ=̠R!zdhզ  ,s))Qȑ(:(xC_tnRs]iƄvǪ])
+h{
+zΨ!;}xG eYn2'\Liy:Z&·|>"hn4`^A)d !Iɭg٣`
+ݟFhlHѳE8cm	DmVg+PTd(6r3N9{Ot&UG酼Q]j}3jkv?6:Fޛdy hN"z>^L
+ek9uh|E-՝9Jb
+j)qɽUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU ĒkJGG9`/jV<-oi^&E:DF8A89gTG3Sы&.駘/Fy,o-SvmY푴6yEnҪ/iAմ/OSUb:u:{}HJ[zoo"@-P 	f+{%g!\ԗSE\=S-Cj,LwЄYNr<Wr17ilޛ}-d!/hhJb=̕-`pKͽjsFQ.ѿvf\rn ܶe2& '"^PJ]WVRu6SlfnDm""$b@(4R72%=bl}2b6Z\£U?bdDpQj1s|e"8RjJ̿t}q), 8tĞb%Xu?>k{vQyH)-(5fe}v2<`&@G.r0dy:z96&bKdşh&HAц㠚%r`pfŠQ5ҩB8UK<k4R633Ͽř15̸ު %9@ᖐ`wޕt2sb*^ertl*f繖yHUgȲb9oyc2fN^D4ƧD){=c.kRx)Jq<Б/x{TEs Lu9Y8_4Q\ܬ#M-I9BL@@
+H1vŒ#QC@0!!Aca(hgW)@(F8eJZY?daa hhJ;A"M7Qq'M0;֥}^䘂f\roUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU n]َVS\:Ǘ3׾#doL9~~lRem\mQ)Q2$"yD_}N
+bryl/tA-mKfjvM}oƟ%l|l2{;ℑnl~k .)$U rqYX5oƭ~3oL0]u)b;PGل!0i4cAxe6Ejziv%	_o{L(di/d&X=
+LIR-̐iIdI2>Sb=/Wz15̸UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU [$@RuaP;ZM6^oEj<ˏ,v{g?O;r7[0cF2fFv
+%{1҅3yk"ޱ?H{m)18׋ǯmJpLݞhuv$&iaXlёm]Li8$̬4Eia*~?iPHH8yMpQ	3x#sc$"ݪI# ҧ8융颏Ӹ"<ʺCkLd!/hf>S=Н?F]WHQȣs+pÿnʻ&  ]YOFpjQD'j%zR׵ޱmU]cڝ$mߖ!&Iv2nLkyI
+ 3Ci`ɏrdK+n,fz-FδA쑭36fkێ˵ZAIqH,ؽƄKI0p5a׎9Y9
+rB TgԢˑ.ǸxE(VkEq{,0Ld/dÎcN9ݢy&T6lO!N쥟' R Nf\roUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU {,ajԊQiRQ74ZZ5HITj(]ڔ"<f 	Uv4e酑Mg܋[<Ah}âULLվƪܘm>dوgR˳dDsf/%yoobnKcg6ᒌqS 9$o[d76i;YU*~S]ح8a16m
+ȧ\k*e7LI=MOH:hc|Pda&j(;>^ц$3aA'%Zsrq,l+@Ȓ_<Kh-hSwEM~7OlDS2uUUUUUUUUU %o@р!>$F
+1иbFIi}]J4r6ILףi|MLt-ee~Y-p"'<J.ay|bLanfmN(J'
+M71̈PP-\IPEޑο%fO3n2S3 ePLdX0 0[p+29_;ngK"iWyLEH$2UNsb[X
+ sX5LJRwQ̌cQZTdL~=1qa>!\5wՃ-P>Un̝ok_~  $T җIOE\㻋BV얊QZ}m2eK"ķ#XNxrlȡFw3HdVKArΛX@#(ո&WvuWRo99ΏNTI41<k^1@7\r
+.X0D	`r7ك-dHԎ"pb6<
+J伌-ݵ%:e?>|d1JK؜ثލFd%B^̘{͵ֽؕ}#=|ykEװQ15̸ު$o(1,o.ؔyI5`?/B)^($a':9#hNI8)vs@iHfTl7b:F۩~;/h!$jOT6mnhPcK(Y|	lx8A$~: gC+]".z8@L&ݒuX#FBig\B9`̖aҨ&80;Q掁2*fd9d>NA%>^).B&Ϋx'☂f\roUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU o69 
+iکZ/ol< pR1#gq7C@ADAgA1#*+EP=)&%" F`^Y9:#$BNwsB-4ِ"KX
+	- RihbL[)*ՏڣOW̉^xg펊<d`﨡1n/[FjeDi0t:,¹h\XALyD!a>&[>^t{}ܹ~tә[ns(i:u9Ft8nhB)e&ꪪ m[f@3	йR[gv#6U*ZY/锬LJfBϧ1u4:
+ GS3!akqo(!$z`28P0 ߙƒ3v=u!rpKbKfxJVd5Ɋم :2D[弘.(#R4qA-1çb2RßyTMHNKMDV9n2d	/`n~B^LuSm!ĥ6۷ǿMoώ{'Xg!cZ*b
+j)qɽUUUUUUUUUUUUUUUUUUUUUUUUUUU%oډ	%;(=~%Vd9G7b/ؾgQyv|gtƐ0/Y]DI^%8]%e*QwH**MqO~S~/ަUkԲ\]dcLۢcEB䨾`mM.kƭZysеA_wwcӫD[܍1]Ol aoih~cK|wj{΢?&kTjm{}9d-3B^)6ΪE۝moٲnwk돐S4ywS"&QE& ][OL_vt*fe:I
+;~! cnMx:ۓ{2I'X	!ș/Ը=;=u%S-0xA[HUqZjŗXǄ	1ݘZ5Y3ٖݬ\EbYa*-P P&eQsbHϳk;]fgXM'FE&qbR%ݱrޱEh!id4-I=CrLNVg~D/<n=Ljq3c(nEN]"]VxRYp;=zj-pmLAME3.97!mLU$&Tma`\	40ck~
+fsu-`GXDH`gA)GTa.}ErDEr3PZ1qv'(^2*c/pRKhɦC*YVql'$S?6*e!Gv,4)"ZLhQ*GudRAsRtdlu1,cȉEr(3@TY*$j
+"uD!/=#B^}mɈil?pajDSSQLˎMꪪ_71rXٷF8jJܑd&G_վ㶨gs#3׳2$k>A2&5NkRC,5= 1j9
+vBP˛</DP*μyt:m]A&̎Rk68LIGU%0oSKӉwzMWTe
+WXӡ%BOᑝC`S
+A^k#,mi䠘&K-wzc?PdJSdw!/<#RjB^QLˎMꪪmRjZ4BEٲRr^9y;1!*fá/|9A:4M7oy0ڧX-rIsstgIIS󴇺 ][xT1vi!TPR9q+R+Q m\ۮ:[":*Ad'j@AEF6Blό_*aMXZimoS'y`|
+	u D!/>^-UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUܒ{0jVgj9j<d59nBRɹ̙5Pv23%%|?W(m߻zII8 U"	OBlDjO#:hِDz(Ҹ%ɩ=(R%SF	g޲XvLJ9mIZ]jYTG%2W:Fߣ}[֩>O#>:⣶915̸UUUUUUUUUUUUD}!/#~B^yFyUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU%cOr*vfI5V^$.$X 4XHB'K*Ii$(Z!b j8T Q I-lMܟﾡBj9ogR]:JG5Sg:{ 1}",֤qשPwgFwz,܌Z3LAME3.97UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUDL#/r)&F^UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU$ lpg	hm`1E>>zOWHIݿmGT[[YL3DOe(R5E[fćX[4\_2-zvȒmo#?$lgUC?%I#UBtl)Tf\rnD`#/  ;B^XF@ C|RʅG}i}VdB[@? fbV%\ԍ^ooԹ8hfj-ɫyv\FvUwvޕ0'&SQLˎMꪪD$[F^(᪪Su15̸ުD         LAMELAMELAME3.97d            d        H    
\ No newline at end of file
diff -Naur chatroom/updates.inc chatroom-6.x-dev/updates.inc
--- chatroom/updates.inc	2007-10-03 03:32:08.000000000 -0400
+++ chatroom-6.x-dev/updates.inc	2008-10-18 21:14:27.000000000 -0400
@@ -188,8 +188,8 @@
   if (
     is_dir($smileys_base) && // Is a directory
     (
-      substr($smileys_base, 0, strlen('modules')) == 'modules' || // in the modules directory
-      substr($smileys_base, 0, strlen('sites')) == 'sites' // or in the sites directory.
+      drupal_substr($smileys_base, 0, drupal_strlen('modules')) == 'modules' || // in the modules directory
+      drupal_substr($smileys_base, 0, drupal_strlen('sites')) == 'sites' // or in the sites directory.
     ) &&
     strpos($smileys_base, '..') === FALSE // and path does not contain "..".
   ) {
