<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -38,8 +38,9 @@ from google.appengine.ext import db
 from google.appengine.api import memcache
 from google.appengine.api import mail
 
-import urlib
+import urllib
 import urllib2
+import base64
 import secrets
 
 
@@ -63,6 +64,8 @@ class CONST(object):
         [(15, 3), (3, 15), (15, 15), (3, 3), (9, 9), (3, 9), (15, 9), (9, 3), (9, 15)],
         [(9, 3), (3, 9), (9, 9), (3, 3), (6, 6), (3, 6), (9, 6), (6, 3), (6, 9)],
         [(6, 2), (2, 6), (6, 6), (2, 2), (4, 4)]]
+    Email_Contact = &quot;email&quot;
+    Twitter_Contact = &quot;twitter&quot;
 
 def opposite_color(color):
     return 3 - color
@@ -570,7 +573,12 @@ It's your turn to make a move against %s. Just follow this link:
 class TwitterHelper(object):
     @staticmethod
     def _open_basic_auth_url(username, password, url, params):
-        # The &quot;right&quot; way to do this with urllib2 sucks. Why bother.
+        if params is None:
+            logging.info(&quot;Making call with %s:%s to %s and no params&quot; % (username, password, url))
+        else:
+            logging.info(&quot;Making call with %s:%s to %s and params %s&quot; % (username, password, url, params.__repr__()))
+        
+        # The &quot;right&quot; way to do this with urllib2 sucks. Why bother?
         data = None
         if params is not None:
             data = urllib.urlencode(params)
@@ -586,21 +594,56 @@ class TwitterHelper(object):
         return handle
 
     @staticmethod
-    def _make_twitter_call(url, params):
-        handle = TwitterHelper._open_basic_auth_url(secrets.twitter_user, secrets.twitter_pass, url, params)
+    def _make_twitter_call_as(url, params, user, user_password):
+        handle = TwitterHelper._open_basic_auth_url(user, user_password, url, params)
         if handle is None:
             return None        
         try:
             result = simplejson.loads(handle.read())
         except:
             logging.warn(&quot;Couldn't process json result from twitter: %s&quot; % ExceptionHelper.exception_string())
-            return None        
+            return None
+        return result        
+    
+    @staticmethod
+    def _make_twitter_call(url, params):
+        return TwitterHelper._make_twitter_call_as(url, params, secrets.twitter_user, secrets.twitter_pass)
+        
+    @staticmethod
+    def _make_boolean_twitter_call(url, params):
+        result = TwitterHelper._make_twitter_call(url, params)
+        if result is None:
+            return None
+        if type(result) != type(True):
+            return None
         return result
+
+    @staticmethod
+    def _make_success_twitter_call(url, params):
+        result = TwitterHelper._make_twitter_call(url, params)
+        if result is None:
+            return None
+        return not ('error' in result)
+
+    @staticmethod
+    def _make_success_twitter_call_as(url, params, user, user_password):
+        result = TwitterHelper._make_twitter_call_as(url, params, user, user_password)
+        if result is None:
+            return None
+        return not ('error' in result)
+    
+    @staticmethod
+    def _game_url(cookie):
+        return &quot;%splay/%s/&quot; % (AppEngineHelper.base_url(), cookie)
+    
+    @staticmethod
+    def _trim_name(name):
+        return name.strip()[:16]
     
     @staticmethod
     def does_follow(a, b):
         # Does &quot;a&quot; follow &quot;b&quot;?
-        return TwitterHelper._make_twitter_call(&quot;http://twitter.com/friendships/exists.json&quot;, {&quot;user_a&quot;: a, &quot;user_b&quot;: b})
+        return TwitterHelper._make_boolean_twitter_call(&quot;http://twitter.com/friendships/exists.json&quot;, {&quot;user_a&quot;: a, &quot;user_b&quot;: b})
 
     @staticmethod
     def are_mutual_followers(a, b):
@@ -616,16 +659,30 @@ class TwitterHelper(object):
 
     @staticmethod
     def create_follow(a, b, a_password):
-        success = TwitterHelper._open_basic_auth_url(a, a_password, &quot;http://twitter.com/friendships/create/%s.json?follow=true&quot; % b)
-        return (success is not None)
+        # {&quot;ignore&quot;: &quot;this&quot;} forces a POST
+        return TwitterHelper._make_success_twitter_call_as(&quot;http://twitter.com/friendships/create/%s.json?follow=true&quot; % b, {&quot;ignore&quot;: &quot;this&quot;}, a, a_password)
 
     @staticmethod
+    def does_go_account_follow_user(user):
+        return TwitterHelper.does_follow(secrets.twitter_user, user)
+
+    @staticmethod
+    def does_user_follow_go_account(user):
+        return TwitterHelper.does_follow(user, secrets.twitter_user)
+    
+    @staticmethod
     def make_go_account_follow_user(user):
-        return TwitterHelper.create_follow(secrets.twitter_user, user, secrets.twitter_pass)
+        did = TwitterHelper.create_follow(secrets.twitter_user, user, secrets.twitter_pass)
+        if did is None:
+            return False
+        return did
 
     @staticmethod
     def make_user_follow_go_account(user, user_password):
-        return TwitterHelper.create_follow(user, secrets.twitter_user, user_password)
+        did = TwitterHelper.create_follow(user, secrets.twitter_user, user_password)
+        if did is None:
+            return False
+        return did
 
     @staticmethod
     def send_direct_message(a, b, a_password, message):
@@ -637,17 +694,30 @@ class TwitterHelper(object):
         return TwitterHelper.send_direct_message(secrets.twitter_user, user, secrets.twitter_pass, message)
     
     @staticmethod
-    def notify_you_new_game(your_name, your_email, your_cookie, opponent_name, your_turn):
-        pass
+    def notify_you_new_game(your_name, your_twitter, your_cookie, opponent_name, your_turn):
+        if your_turn:
+            message = &quot;You've started a game of Go with %s. It is your turn. You can play by visiting %s&quot; % (TwitterHelper._trim_name(opponent_name), TwitterHelper._game_url(your_cookie))
+        else:            
+            message = &quot;You've started a game of Go with %s. You can see what's happening by visiting %s&quot; % (TwitterHelper._trim_name(opponent_name), TwitterHelper._game_url(your_cookie))
+        return TwitterHelper.send_notification_to_user(your_twitter, message)
 
     @staticmethod
-    def notify_opponent_new_game(your_name, opponent_name, opponent_email, opponent_cookie, your_turn):
-        pass
+    def notify_opponent_new_game(your_name, opponent_name, opponent_twitter, opponent_cookie, your_turn):
+        if your_turn:
+            message = &quot;%s has started a game of Go with you. You can see what's happening by visiting %s&quot; % (TwitterHelper._trim_name(your_name), TwitterHelper._game_url(opponent_cookie))
+        else:            
+            message = &quot;%s has started a game of Go with you. It's your turn. You can play by visiting %s&quot; % (TwitterHelper._trim_name(your_name), TwitterHelper._game_url(opponent_cookie))
+        return TwitterHelper.send_notification_to_user(opponent_twitter, message)
     
     @staticmethod
-    def notify_your_turn(your_name, your_email, your_cookie, opponent_name, opponent_email, move_message):
-        pass
-    
+    def notify_your_turn(your_name, your_twitter, your_cookie, opponent_name, move_message):
+        if move_message == &quot;It's your turn to move.&quot;:
+            # TODO UNHACK -- ugly, but I'm too lazy to do this well at the moment.
+            message = &quot;%s moved; it's now your turn.&quot; % TwitterHelper._trim_name(opponent_name)
+        else:
+            message = move_message
+        message += &quot; &quot; + TwitterHelper._game_url(your_cookie)
+        return TwitterHelper.send_notification_to_user(your_twitter, message)    
         
         
 #------------------------------------------------------------------------------
@@ -828,7 +898,32 @@ class GoHandler(webapp.RequestHandler):
             return False
         
         return True
+
+    def is_valid_twitter(self, twitter):
+        if twitter is None:
+            return False
+
+        if (len(twitter) &lt; 1) or (len(twitter) &gt; 16):
+            return False
+
+        # a python hax0r told me this would be faster than REs for very short strings
+        # (TODO validate that claim)
+        twitter = twitter.lower()
+        for t in twitter:
+            if not t in 'abcdefghijklmnopqrstuvwxyz0123456789_':
+                return False
+
+        return True
+
+    def is_valid_contact_type(self, contact_type):
+        return (contact_type == CONST.Email_Contact) or (contact_type == CONST.Twitter_Contact)
     
+    def is_valid_contact(self, contact, contact_type):
+        if contact_type == CONST.Email_Contact:
+            return self.is_valid_email(contact)
+        else:
+            return self.is_valid_twitter(contact)
+        
 
 #------------------------------------------------------------------------------
 # &quot;Get Going&quot; Handler
@@ -851,10 +946,13 @@ class CreateGameHandler(GoHandler):
         super(CreateGameHandler, self).__init__()
     
     def fail(self, flash=&quot;Invalid input.&quot;):
-        self.render_json({'success': False, 'flash': flash})
+        self.render_json({'success': False, 'need_your_twitter_password': False, 'flash': flash})
+
+    def require_twitter_password(self, flash):
+        self.render_json({'success': True, 'need_your_twitter_password': True, 'flash': flash})    
 
-    def create_game(self, your_name, your_email, opponent_name, opponent_email, your_color, board_size_index, handicap_index):
-        # Create cookies for accessing the game
+    def create_game(self, your_name, your_contact, your_contact_type, opponent_name, opponent_contact, opponent_contact_type, your_color, board_size_index, handicap_index):
+        # Create cookies for accessing the game        
         your_cookie, opponent_cookie = GameCookie.unique_pair()                
 
         # Create the game state and board blobs
@@ -887,15 +985,37 @@ class CreateGameHandler(GoHandler):
         your_player.cookie = your_cookie
         your_player.color = your_color
         your_player.name = your_name
-        your_player.email = your_email
-        
+        if your_contact_type == CONST.Email_Contact:
+            your_player.email = your_contact
+            your_player.wants_email = True
+            your_player.twitter = &quot;&quot;
+            your_player.wants_twitter = False
+            your_email = your_contact
+        else:
+            your_player.email = &quot;nobody@example.com&quot;
+            your_player.wants_email = False
+            your_player.twitter = your_contact
+            your_player.wants_twitter = True
+            your_twitter = your_contact
+                
         # Create opponent player
         opponent_player = Player()
         opponent_player.game = game_key
         opponent_player.cookie = opponent_cookie
         opponent_player.color = opposite_color(your_color)
         opponent_player.name = opponent_name
-        opponent_player.email = opponent_email
+        if opponent_contact_type == CONST.Email_Contact:            
+            opponent_player.email = opponent_contact
+            opponent_player.wants_email = True
+            opponent_player.twitter = &quot;&quot;
+            opponent_player.wants_twitter = False
+            opponent_email = opponent_contact
+        else:
+            opponent_player.email = &quot;nobody@example.com&quot;
+            opponent_player.wants_email = False
+            opponent_player.twitter = opponent_contact
+            opponent_player.wants_twitter = True
+            opponent_twitter = opponent_contact
 
         # Put the players
         your_player.put()
@@ -905,31 +1025,38 @@ class CreateGameHandler(GoHandler):
         if your_player.wants_email:
             EmailHelper.notify_you_new_game(your_name, your_email, your_cookie, opponent_name, your_turn)
         elif your_player.does_want_twitter():
-            TwitterHelper.notify_you_new_game(your_name, your_email, your_cookie, opponent_name, your_turn)
+            TwitterHelper.notify_you_new_game(your_name, your_twitter, your_cookie, opponent_name, your_turn)
 
         if opponent_player.wants_email:
             EmailHelper.notify_opponent_new_game(your_name, opponent_name, opponent_email, opponent_cookie, your_turn)
         elif opponent_player.does_want_twitter():
-            TwitterHelper.notify_opponent_new_game(your_name, opponent_name, opponent_email, opponent_cookie, your_turn)        
+            TwitterHelper.notify_opponent_new_game(your_name, opponent_name, opponent_twitter, opponent_cookie, your_turn)        
         
         # Great; the game is created!
         return (your_cookie, your_turn)
 
     def success(self, your_cookie, your_turn):        
-        self.render_json({'success': True, 'your_cookie': your_cookie, 'your_turn': your_turn})
+        self.render_json({'success': True, 'need_your_twitter_password': False, 'your_cookie': your_cookie, 'your_turn': your_turn})
     
     def post(self, *args):
         try:
             your_name = self.request.POST.get(&quot;your_name&quot;)
-            your_email = self.request.POST.get(&quot;your_email&quot;)
+            your_contact = self.request.POST.get(&quot;your_contact&quot;)
             opponent_name = self.request.POST.get(&quot;opponent_name&quot;)
-            opponent_email = self.request.POST.get(&quot;opponent_email&quot;)
+            opponent_contact = self.request.POST.get(&quot;opponent_contact&quot;)
             your_color = int(self.request.POST.get(&quot;your_color&quot;))
             board_size_index = int(self.request.POST.get(&quot;board_size_index&quot;))
             handicap_index = int(self.request.POST.get(&quot;handicap_index&quot;))
+            your_contact_type = self.request.POST.get(&quot;your_contact_type&quot;)
+            opponent_contact_type = self.request.POST.get(&quot;opponent_contact_type&quot;)
         except:
             self.fail()
             return
+
+        try:            
+            your_twitter_password = self.request.POST.get(&quot;your_twitter_password&quot;)
+        except:
+            your_twitter_password = None
             
         if (your_color &lt; CONST.Black_Color) or (your_color &gt; CONST.White_Color):
             self.fail(&quot;Invalid color.&quot;)
@@ -947,20 +1074,60 @@ class CreateGameHandler(GoHandler):
             self.fail(&quot;Your name is invalid.&quot;)
             return
 
-        if not self.is_valid_email(your_email):
-            self.fail(&quot;Your email is invalid.&quot;)
+        if not self.is_valid_contact_type(your_contact_type):
+            self.fail(&quot;Your contact type is invalid.&quot;)
+            return
+
+        if not self.is_valid_contact_type(opponent_contact_type):
+            self.fail(&quot;Your opponent's contact type is invalid.&quot;)
+            return
+        
+        if not self.is_valid_contact(your_contact, your_contact_type):
+            self.fail(&quot;Your contact information is invalid.&quot;)
             return
             
         if not self.is_valid_name(opponent_name):
             self.fail(&quot;Your opponent's name is invalid.&quot;)
             return
         
-        if not self.is_valid_email(opponent_email):
-            self.fail(&quot;Your opponent's email is invalid.&quot;)
+        if not self.is_valid_contact(opponent_contact, opponent_contact_type):
+            self.fail(&quot;Your opponent's contact information is invalid.&quot;)
             return
 
+        #
+        # Twitter test cases: if necessary, connect up all contacts so we can direct-message
+        #
+        
+        if your_contact_type == CONST.Twitter_Contact:
+            if not TwitterHelper.make_go_account_follow_user(your_contact):
+                self.fail(&quot;Sorry, but we couldn't contact twitter or couldn't follow your account. Try again soon, or use email for now.&quot;)
+                return
+
+        if opponent_contact_type == CONST.Twitter_Contact:
+            if not TwitterHelper.make_go_account_follow_user(opponent_contact):
+                self.fail(&quot;Sorry, but we couldn't contact twitter or couldn't follow your opponent's account. Try again soon, or use email for now.&quot;)
+                return
+
+        if your_contact_type == CONST.Twitter_Contact:
+            if your_twitter_password is None:
+                if not TwitterHelper.does_user_follow_go_account(your_contact):
+                    self.require_twitter_password(&quot;In order to play go using twitter, you most follow the @%s account. Enter your password to set this up automatically:&quot; % secrets.twitter_user)
+                    return
+                # success -- you're already following @davepeckgo
+            else:
+                if not TwitterHelper.make_user_follow_go_account(your_contact, your_twitter_password):
+                    self.require_twitter_password(&quot;Sorry, either your password is incorrect or we couldn't contact twitter. Try entering your password again, or use email for now.&quot;)
+                    return
+                # success -- you're now following @davepeckgo
+
+        if opponent_contact_type == CONST.Twitter_Contact:
+            if not TwitterHelper.does_user_follow_go_account(opponent_contact):
+                self.fail(&quot;Sorry, but your opponent is not following @%s on twitter. Because of this, you should use email to start the game with your opponent.&quot; % secrets.twitter_user)
+                return
+            # success -- opponent is following @davepeckgo
+            
         try:
-            your_cookie, your_turn = self.create_game(your_name, your_email, opponent_name, opponent_email, your_color, board_size_index, handicap_index)            
+            your_cookie, your_turn = self.create_game(your_name, your_contact, your_contact_type, opponent_name, opponent_contact, opponent_contact_type, your_color, board_size_index, handicap_index) 
             self.success(your_cookie, your_turn)
         except:
             logging.error(&quot;An unexpected error occured in CreateGameHandler: %s&quot; % ExceptionHelper.exception_string())
@@ -1175,7 +1342,7 @@ class MakeThisMoveHandler(GoHandler):
         if opponent.wants_email:
             EmailHelper.notify_your_turn(opponent.get_friendly_name(), opponent.email, opponent.cookie, player.get_friendly_name(), player.email)
         elif opponent.does_want_twitter():
-            TwitterHelper.notify_your_turn(opponent.get_friendly_name(), opponent.email, opponent.cookie, player.get_friendly_name(), player.email, move_message)
+            TwitterHelper.notify_your_turn(opponent.get_friendly_name(), opponent.twitter, opponent.cookie, player.get_friendly_name(), move_message)
                     
         items = {
             'success': True,</diff>
      <filename>go.py</filename>
    </modified>
    <modified>
      <diff>@@ -23,7 +23,7 @@
 #------
 
 twitter_user = &quot;davepeckgo&quot;
-twitter_pass = &quot;SEKR3T&quot;
+twitter_pass = &quot;SECR3T&quot;
 
 dashboard_user = &quot;dashboard&quot;
 dashboard_pass = &quot;dashboard&quot;</diff>
      <filename>secrets.py</filename>
    </modified>
    <modified>
      <diff>@@ -437,7 +437,7 @@ p.flash
 {
     color: #FFB45C;
     font-size: 0.7em;
-    line-height: 0.7em;
+    line-height: 1.0em;
     padding-left: 2px;
     margin-top: 0.4em;
     margin-bottom: 0.8em;
@@ -755,3 +755,16 @@ img.no_email:hover
 {
     color: #800;
 }
+
+.limited_width
+{
+    width: 650px;
+}
+
+#twitter_password_container
+{
+    font-size: 0.66em;
+    padding-bottom: 1em;
+    padding-left: 1.5em;
+    paddint-top: 0.5em;
+}
\ No newline at end of file</diff>
      <filename>static/css/go.css</filename>
    </modified>
    <modified>
      <diff>@@ -34,8 +34,8 @@ CONST.Star_Ordinals = [[3, 9, 15], [3, 6, 9], [2, 4, 6]];
 CONST.Board_Size_Names = ['19 x 19', '13 x 13', '9 x 9'];
 CONST.Handicaps = [0, 9, 8, 7, 6, 5, 4, 3, 2];
 CONST.Handicap_Names = ['plays first', 'has a nine stone handicap', 'has an eight stone handicap', 'has a seven stone handicap', 'has a six stone handicap', 'has a five stone handicap', 'has a four stone handicap', 'has a three stone handicap', 'has a two stone handicap'];
-CONST.Email_Contact = 1;
-CONST.Twitter_Contact = 2;
+CONST.Email_Contact = &quot;email&quot;;
+CONST.Twitter_Contact = &quot;twitter&quot;;
 
 
 //-----------------------------------------------------------------------------
@@ -74,6 +74,50 @@ function opposite_color(color)
 
 
 //-----------------------------------------------------------------------------
+// Validators
+//-----------------------------------------------------------------------------
+
+var ContactValidator = function() {}
+
+ContactValidator.is_probably_good_email = function(s)
+{
+    if (!s || (s.length &lt;= 4))
+    {
+        return false;
+    }
+
+    if (s.indexOf('@') == -1 || s.indexOf('.') == -1 || s.indexOf('@') == 0 || s.lastIndexOf('.') &gt;= (s.length - 1) || (s.indexOf('@') &gt;= s.lastIndexOf('.') - 1))
+    {
+        return false;
+    }        
+
+    return true;
+}
+
+ContactValidator.is_probably_good_twitter = function(s)
+{
+    if (!s || (s.length &lt; 1) || (s.length &gt; 16))
+    {
+        return false;
+    }
+
+    return s.match(/^[0-9a-zA-Z_]+$/);
+}
+
+ContactValidator.is_probably_good_contact = function(s, contact_type)
+{
+    if (contact_type == CONST.Email_Contact)
+    {
+        return ContactValidator.is_probably_good_email(s);
+    }
+    else
+    {
+        return ContactValidator.is_probably_good_twitter(s);
+    }
+}
+
+
+//-----------------------------------------------------------------------------
 // Game Start Handler
 //-----------------------------------------------------------------------------
 
@@ -93,17 +137,43 @@ var GetGoing = Class.create({
         this.valid_opponent_name = false;
         this.valid_opponent_contact = false;
         this.valid = false;
+
+        this.showing_twitter_password = false;
         
         this._initialize_events();
     },
 
-    swap_your_contact_method : function()
+    swap_your_contact_type : function()
     {
-        
+        if (this.your_contact_type == CONST.Email_Contact)
+        {
+            this.your_contact_type = CONST.Twitter_Contact;
+            $(&quot;your_contact_type&quot;).update(&quot;twitter&quot;);            
+        }
+        else
+        {
+            this.your_contact_type = CONST.Email_Contact;
+            $(&quot;your_contact_type&quot;).update(&quot;email&quot;);
+            this._hide_twitter_password();
+        }
+        this.valid_your_contact = ContactValidator.is_probably_good_contact($(&quot;your_contact&quot;).value, this.your_contact_type);        
+        this._evaluate_validity();
     },
 
-    swap_opponent_contact_method : function()
+    swap_opponent_contact_type : function()
     {
+        if (this.opponent_contact_type == CONST.Email_Contact)
+        {
+            this.opponent_contact_type = CONST.Twitter_Contact;
+            $(&quot;opponent_contact_type&quot;).update(&quot;twitter&quot;);            
+        }
+        else
+        {
+            this.opponent_contact_type = CONST.Email_Contact;
+            $(&quot;opponent_contact_type&quot;).update(&quot;email&quot;);
+        }
+        this.valid_opponent_contact = ContactValidator.is_probably_good_contact($(&quot;opponent_contact&quot;).value, this.opponent_contact_type);
+        this._evaluate_validity();
     },
     
     swap_colors : function()
@@ -162,33 +232,53 @@ var GetGoing = Class.create({
     },
     
     create_game : function()
-    {
+    {        
         if (this.valid)
         {
             var self = this;
+            var params = {
+                &quot;your_name&quot;: $(&quot;your_name&quot;).value,
+                &quot;your_contact&quot;: $(&quot;your_contact&quot;).value,
+                &quot;opponent_name&quot;: $(&quot;opponent_name&quot;).value,
+                &quot;opponent_contact&quot;: $(&quot;opponent_contact&quot;).value,
+                &quot;your_color&quot;: this.your_color,
+                &quot;board_size_index&quot;: this.board_size_index,
+                &quot;handicap_index&quot;: this.handicap_index,
+                &quot;your_contact_type&quot;: this.your_contact_type,
+                &quot;opponent_contact_type&quot;: this.opponent_contact_type
+            };
+
+            if (this.showing_twitter_password)
+            {
+                alert(&quot;wow&quot;);
+                var tp = $(&quot;twitter_password&quot;).value;
+                if (tp &amp;&amp; tp.length &gt; 1)
+                {
+                    alert(&quot;wow wow&quot;);
+                    params[&quot;your_twitter_password&quot;] = $(&quot;twitter_password&quot;).value;
+                }                
+            }
+                                
             new Ajax.Request(
                 &quot;/service/create-game/&quot;,
                 {
                     method: 'POST',                
-                
-                    parameters: 
-                    {
-                        &quot;your_name&quot;: $(&quot;your_name&quot;).value,
-                        &quot;your_email&quot;: $(&quot;your_email&quot;).value,
-                        &quot;opponent_name&quot;: $(&quot;opponent_name&quot;).value,
-                        &quot;opponent_email&quot;: $(&quot;opponent_email&quot;).value,
-                        &quot;your_color&quot;: this.your_color,
-                        &quot;board_size_index&quot;: this.board_size_index,
-                        &quot;handicap_index&quot;: this.handicap_index
-                    },
+
+                    parameters : params,
                 
                     onSuccess: function(transport) 
                     {
                         var response = eval_json(transport.responseText);
                         if (response['success'])
                         {
-                        
-                            self._succeed_create_game(response['your_cookie'], response['your_turn']);
+                            if (response['need_your_twitter_password'])
+                            {
+                                self._require_twitter_password(response['flash'])
+                            }                            
+                            else
+                            {
+                                self._succeed_create_game(response['your_cookie'], response['your_turn']);
+                            }
                         }
                         else
                         {                    
@@ -220,13 +310,38 @@ var GetGoing = Class.create({
             $(&quot;flash&quot;).update(&quot;Your game is ready; it&amp;#146;s your opponent&amp;#146;s turn.&quot;);
         }
         
-        Effect.Appear(&quot;flash&quot;);        
+        Effect.Appear(&quot;flash&quot;);
+        this._hide_twitter_password();
+    },
+
+    _show_twitter_password : function()
+    {
+        if (this.showing_twitter_password) { return; }
+        $(&quot;twitter_password_container&quot;).removeClassName(&quot;hide&quot;);
+        this.showing_twitter_password = true;
+    },
+
+    _hide_twitter_password : function()
+    {
+        if (!this.showing_twitter_password) { return; }
+        $(&quot;twitter_password_container&quot;).addClassName(&quot;hide&quot;);
+        $(&quot;flash&quot;).update(&quot;&quot;);
+        new Effect.Opacity(&quot;flash&quot;, {to: 0.0});
+        this.showing_twitter_password = false;
+    },
+    
+    _require_twitter_password : function(flash)
+    {
+        this._show_twitter_password();
+        $(&quot;flash&quot;).update(flash);
+        Effect.Appear(&quot;flash&quot;);
     },
     
     _fail_create_game : function(flash)
     {
         $(&quot;flash&quot;).update(flash);        
         Effect.Appear(&quot;flash&quot;);
+        this._hide_twitter_password();
     },
 
     _activate_play_link : function()
@@ -241,7 +356,7 @@ var GetGoing = Class.create({
     
     _evaluate_validity : function()
     {
-        var newValid = this.valid_your_name &amp;&amp; this.valid_your_email &amp;&amp; this.valid_opponent_name &amp;&amp; this.valid_opponent_email;
+        var newValid = this.valid_your_name &amp;&amp; this.valid_your_contact &amp;&amp; this.valid_opponent_name &amp;&amp; this.valid_opponent_contact;
         
         if (newValid != this.valid)
         {
@@ -260,24 +375,9 @@ var GetGoing = Class.create({
     _initialize_events : function()
     {
         $(&quot;your_name&quot;).observe('keyup', this._input_your_name.bindAsEventListener(this));
-        $(&quot;your_email&quot;).observe('keyup', this._input_your_email.bindAsEventListener(this));
+        $(&quot;your_contact&quot;).observe('keyup', this._input_your_contact.bindAsEventListener(this));
         $(&quot;opponent_name&quot;).observe('keyup', this._input_opponent_name.bindAsEventListener(this));
-        $(&quot;opponent_email&quot;).observe('keyup', this._input_opponent_email.bindAsEventListener(this));        
-    },
-
-    _is_probably_good_email : function(s)
-    {
-        if (!s || (s.length &lt;= 4))
-        {
-            return false;
-        }
-        
-        if (s.indexOf('@') == -1 || s.indexOf('.') == -1 || s.indexOf('@') == 0 || s.lastIndexOf('.') &gt;= (s.length - 1) || (s.indexOf('@') &gt;= s.lastIndexOf('.') - 1))
-        {
-            return false;
-        }        
-        
-        return true;
+        $(&quot;opponent_contact&quot;).observe('keyup', this._input_opponent_contact.bindAsEventListener(this));        
     },
     
     _input_your_name : function()
@@ -286,9 +386,9 @@ var GetGoing = Class.create({
         this._evaluate_validity();
     },
     
-    _input_your_email : function()
+    _input_your_contact : function()
     {
-        this.valid_your_email = this._is_probably_good_email($(&quot;your_email&quot;).value);
+        this.valid_your_contact = ContactValidator.is_probably_good_contact($(&quot;your_contact&quot;).value, this.your_contact_type);
         this._evaluate_validity();
     },
     
@@ -298,9 +398,9 @@ var GetGoing = Class.create({
         this._evaluate_validity();
     },
     
-    _input_opponent_email : function()
+    _input_opponent_contact : function()
     {
-        this.valid_opponent_email = this._is_probably_good_email($(&quot;opponent_email&quot;).value);
+        this.valid_opponent_contact = ContactValidator.is_probably_good_contact($(&quot;opponent_contact&quot;).value, this.opponent_contact_type);
         this._evaluate_validity();
     }
 });</diff>
      <filename>static/js/go.js</filename>
    </modified>
    <modified>
      <diff>@@ -15,8 +15,8 @@
                         &lt;td class=&quot;divide&quot;&gt;You are &lt;a href=&quot;javascript:get_going.swap_colors()&quot; id=&quot;your_color&quot; class=&quot;subtle-link&quot; tabindex=&quot;5&quot;&gt;black&lt;/a&gt;.&lt;/td&gt;
                     &lt;/tr&gt;
                     &lt;tr&gt;                  
-                        &lt;th&gt;Your &lt;a id=&quot;your_contact_method&quot; href=&quot;javascript:get_going.swap_your_contact_method()&quot; class=&quot;subtle-link&quot; tabindex=&quot;8&quot;&gt;email&lt;/a&gt;:&lt;/th&gt;
-                        &lt;td&gt;&lt;input type=&quot;text&quot; id=&quot;your_email&quot; tabindex=&quot;2&quot; class=&quot;adjust&quot;&gt;&lt;/input&gt;&lt;/td&gt;
+                        &lt;th&gt;Your &lt;a id=&quot;your_contact_type&quot; href=&quot;javascript:get_going.swap_your_contact_type()&quot; class=&quot;subtle-link&quot; tabindex=&quot;8&quot;&gt;email&lt;/a&gt;:&lt;/th&gt;
+                        &lt;td&gt;&lt;input type=&quot;text&quot; id=&quot;your_contact&quot; tabindex=&quot;2&quot; class=&quot;adjust&quot;&gt;&lt;/input&gt;&lt;/td&gt;
                         &lt;td class=&quot;divide&quot;&gt;Your opponent is &lt;a href=&quot;javascript:get_going.swap_colors()&quot; class=&quot;subtle-link&quot; id=&quot;opponent_color&quot;&gt;white&lt;/a&gt;.&lt;/td&gt;
                     &lt;/tr&gt;
                     &lt;tr&gt;
@@ -25,13 +25,16 @@
                         &lt;td class=&quot;divide&quot;&gt;Black &lt;a id=&quot;handicap&quot; href=&quot;javascript:get_going.rotate_handicap();&quot; class=&quot;subtle-link&quot; tabindex=&quot;6&quot;&gt;plays first&lt;/a&gt;.&lt;/td&gt;
                     &lt;/tr&gt;
                     &lt;tr&gt;
-                        &lt;th&gt;Opponent&amp;#146;s &lt;a id=&quot;opponent_contact_method&quot; href=&quot;javascript:get_going.swap_opponent_contact_method()&quot; class=&quot;subtle-link&quot; tabindex=&quot;8&quot;&gt;email&lt;/a&gt;:&lt;/th&gt;
-                        &lt;td&gt;&lt;input type=&quot;text&quot; id=&quot;opponent_email&quot; tabindex=&quot;4&quot; class=&quot;adjust&quot;&gt;&lt;/input&gt;&lt;/td&gt;
+                        &lt;th&gt;Opponent&amp;#146;s &lt;a id=&quot;opponent_contact_type&quot; href=&quot;javascript:get_going.swap_opponent_contact_type()&quot; class=&quot;subtle-link&quot; tabindex=&quot;8&quot;&gt;email&lt;/a&gt;:&lt;/th&gt;
+                        &lt;td&gt;&lt;input type=&quot;text&quot; id=&quot;opponent_contact&quot; tabindex=&quot;4&quot; class=&quot;adjust&quot;&gt;&lt;/input&gt;&lt;/td&gt;
                         &lt;td class=&quot;divide&quot;&gt;The board is &lt;a href=&quot;javascript:get_going.rotate_board_sizes();&quot; class=&quot;subtle-link&quot; id=&quot;board_size&quot; tabindex=&quot;7&quot;&gt;19 x 19&lt;/a&gt;.&lt;/td&gt;
                     &lt;/tr&gt;
                 &lt;/table&gt;
             &lt;/div&gt;
-            &lt;p id=&quot;flash&quot; class=&quot;flash&quot; style=&quot;opacity: 0.0&quot;&gt;&amp;nbsp;&lt;/p&gt;
+            &lt;p id=&quot;flash&quot; class=&quot;flash limited_width&quot; style=&quot;opacity: 0.0&quot;&gt;&amp;nbsp;&lt;/p&gt;
+            &lt;div id=&quot;twitter_password_container&quot; class=&quot;hide&quot;&gt;
+                &lt;span&gt;Your twitter password: &lt;span&gt;&lt;input type=&quot;password&quot; id=&quot;twitter_password&quot;&gt;&lt;/input&gt;
+            &lt;/div&gt;
             &lt;p class=&quot;lets_play&quot;&gt;&lt;a href=&quot;javascript:get_going.create_game()&quot; class=&quot;disabled&quot; id=&quot;play_link&quot;&gt;Create the game &amp;raquo;&lt;/a&gt;&lt;/p&gt;
         &lt;/div&gt;
     &lt;/div&gt;    </diff>
      <filename>templates/get-going.html</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>1bfeb4f6e9a221335b96975d69da753643f72c29</id>
    </parent>
  </parents>
  <author>
    <name>Dave Peck</name>
    <email>c-github@delver.org</email>
  </author>
  <url>http://github.com/davepeck/appengine-go/commit/d0525d8bce21e5784981d66db463adf3d59c3d71</url>
  <id>d0525d8bce21e5784981d66db463adf3d59c3d71</id>
  <committed-date>2009-02-27T17:31:55-08:00</committed-date>
  <authored-date>2009-02-27T17:31:55-08:00</authored-date>
  <message>Fully working signup with twitter, and with connecting both @davepeckgo to your twitter account and vice versa (if necessary and if you give your twitter password.)</message>
  <tree>082379586f941485494f19ad14eb625fe5cd5458</tree>
  <committer>
    <name>Dave Peck</name>
    <email>c-github@delver.org</email>
  </committer>
</commit>
