Index: src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/LastfmActions.cs
===================================================================
--- src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/LastfmActions.cs	(revision 4712)
+++ src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/LastfmActions.cs	(working copy)
@@ -137,16 +137,6 @@
 
             // Track actions
             Add (new ActionEntry [] {
-                new ActionEntry (
-                    "LastfmLoveAction", null,
-                    Catalog.GetString ("Love Track"), null,
-                    Catalog.GetString ("Mark current track as loved"), OnLoved),
-
-                new ActionEntry (
-                    "LastfmHateAction", null,
-                    Catalog.GetString ("Ban Track"), null,
-                    Catalog.GetString ("Mark current track as banned"), OnHated),
-
                 new ActionEntry ("LastfmTrackVisitLastfmAction", "audioscrobbler",
                     Catalog.GetString ("View on Last.fm"), null,
                     Catalog.GetString ("View this track's Last.fm page"), OnTrackVisitLastfm),
@@ -156,9 +146,6 @@
                     Catalog.GetString ("Recommend this track to someone"), OnTrackRecommend)
             });
 
-            this["LastfmLoveAction"].IconName = "face-smile";
-            this["LastfmHateAction"].IconName = "face-sad";
-
             Add (
                 new RadioActionEntry [] {
                     new RadioActionEntry (
@@ -184,9 +171,6 @@
                 }
             );
 
-            this["LastfmLoveAction"].IsImportant = true;
-            this["LastfmHateAction"].IsImportant = true;
-
             actions_id = Actions.UIManager.AddUiFromResource ("GlobalUI.xml");
             Actions.AddActionGroup (this);
 
@@ -233,25 +217,6 @@
             }
         }
 
-        private void OnLoved (object sender, EventArgs args)
-        {
-            LastfmTrackInfo track = ServiceManager.PlayerEngine.CurrentTrack as LastfmTrackInfo;
-            if (track == null) 
-                return;
-
-            track.Love ();
-        }
-
-        private void OnHated (object sender, EventArgs args)
-        {
-            LastfmTrackInfo track = ServiceManager.PlayerEngine.CurrentTrack as LastfmTrackInfo;
-            if (track == null)
-                return;
-
-            track.Ban ();
-            ServiceManager.PlaybackController.Next ();
-        }
-
         private void OnArtistVisitLastfm (object sender, EventArgs args)
         {
             Browser.Open (String.Format (
@@ -395,7 +360,7 @@
         }
 
         private void OnPlayerEvent (PlayerEventArgs args)
-        { 
+        {
             UpdateActions ();
         }
 
@@ -419,14 +384,9 @@
             this["LastfmSortAction"].Sensitive = have_user;
             this["LastfmConnectAction"].Visible = lastfm.Connection.State == ConnectionState.Disconnected;
 
-            TrackInfo current_track = ServiceManager.PlayerEngine.CurrentTrack;
-            this["LastfmLoveAction"].Visible = current_track is LastfmTrackInfo;
-            this["LastfmHateAction"].Visible = current_track is LastfmTrackInfo;
-
             updating = false;
         }
 
-        private uint track_actions_id;
         private RadioAction old_shuffle;
         private RadioAction old_repeat;
         private bool was_lastfm = false;
@@ -459,11 +419,6 @@
             shuffle_actions.Sensitive = !is_lastfm;
             repeat_actions.Sensitive = !is_lastfm;
             
-            if (is_lastfm && !was_lastfm)
-                track_actions_id = Actions.UIManager.AddUiFromResource ("LastfmTrackActions.xml");
-            else if (!is_lastfm && was_lastfm)
-                Actions.UIManager.RemoveUi (track_actions_id);
-
             was_lastfm = is_lastfm;
         }
 
Index: src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Audioscrobbler/AudioscrobblerService.cs
===================================================================
--- src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Audioscrobbler/AudioscrobblerService.cs	(revision 4712)
+++ src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Audioscrobbler/AudioscrobblerService.cs	(working copy)
@@ -40,7 +40,6 @@
 using Hyena;
 
 using Lastfm;
-using Lastfm.Gui;
 
 using Banshee.MediaEngine;
 using Banshee.Base;
@@ -58,11 +57,10 @@
     public class AudioscrobblerService : IExtensionService, IDisposable
     {
         private AudioscrobblerConnection connection;
-        private ActionGroup actions;
+        private AudioscrobblerActions actions;
         private uint ui_manager_id;
         private InterfaceActionService action_service;
         private Queue queue;        
-        private Account account;
         
         private bool queued; /* if current_track has been queued */
         private bool now_playing_sent = false; /* self explanitory :) */
@@ -77,11 +75,9 @@
         
         void IExtensionService.Initialize ()
         {
-            account = LastfmCore.Account;
-            
-            if (account.UserName == null) {
-                account.UserName = LastUserSchema.Get ();
-                account.CryptedPassword = LastPassSchema.Get ();
+            if (LastfmCore.Account.UserName == null) {
+                LastfmCore.Account.UserName = LastUserSchema.Get ();
+                LastfmCore.Account.CryptedPassword = LastPassSchema.Get ();
             }
             
             if (LastfmCore.UserAgent == null) {
@@ -113,40 +109,9 @@
                 PlayerEvent.Iterate);
             
             action_service = ServiceManager.Get<InterfaceActionService> ("InterfaceActionService");
-            InterfaceInitialize ();
+            actions = new AudioscrobblerActions (this);
         }
-        
-        public void InterfaceInitialize ()
-        {
-            actions = new ActionGroup ("Audioscrobbler");
-            
-            actions.Add (new ActionEntry [] {
-                new ActionEntry ("AudioscrobblerAction", null,
-                    Catalog.GetString ("_Last.fm"), null,
-                    Catalog.GetString ("Configure the Audioscrobbler plugin"), null),
-                    
-                new ActionEntry ("AudioscrobblerVisitAction", null,
-                    Catalog.GetString ("Visit _User Profile Page"), null,
-                    Catalog.GetString ("Visit Your Last.fm Profile Page"), OnVisitOwnProfile),
-                
-                new ActionEntry ("AudioscrobblerConfigureAction", Stock.Properties,
-                    Catalog.GetString ("_Configure..."), null,
-                    Catalog.GetString ("Configure the Last.fm Extension"), OnConfigurePlugin)
-            });
-            
-            actions.Add (new ToggleActionEntry [] { 
-                new ToggleActionEntry ("AudioscrobblerEnableAction", null,
-                    Catalog.GetString ("_Enable Song Reporting"), "<control>U",
-                    Catalog.GetString ("Enable song reporting"), OnToggleEnabled, Enabled)
-            });
-            
-            action_service.UIManager.InsertActionGroup (actions, 0);
-            ui_manager_id = action_service.UIManager.AddUiFromResource ("AudioscrobblerMenu.xml");
-            
-            actions["AudioscrobblerVisitAction"].Sensitive = account.UserName != null && account.UserName != String.Empty;
-        }
-        
-  
+
         public void Dispose ()
         {
             // Try and queue the currently playing track just in case it's queueable
@@ -160,7 +125,7 @@
             // When we stop the connection, queue ends up getting saved too, so the
             // track we queued earlier should stay until next session.
             connection.Stop ();
-        
+
             action_service.UIManager.RemoveUi (ui_manager_id);
             action_service.UIManager.RemoveActionGroup (actions);
             actions = null;
@@ -279,27 +244,7 @@
             }
         }
         
-        private void OnConfigurePlugin (object o, EventArgs args)
-        {
-            AccountLoginDialog dialog = new AccountLoginDialog (account, true);
-            dialog.SaveOnEdit = true;
-            if (account.UserName == null) {
-                dialog.AddSignUpButton ();
-            }
-            dialog.Run ();
-            dialog.Destroy ();
-        }
         
-        private void OnVisitOwnProfile (object o, EventArgs args)
-        {
-            account.VisitUserProfile (account.UserName);
-        }
-        
-        private void OnToggleEnabled (object o, EventArgs args)
-        {
-            Enabled = (o as ToggleAction).Active;
-        }
-        
         internal bool Enabled {
             get { return EngineEnabledSchema.Get (); }
             set { 
Index: src/Extensions/Banshee.Lastfm/Banshee.Lastfm.csproj
===================================================================
--- src/Extensions/Banshee.Lastfm/Banshee.Lastfm.csproj	(revision 4712)
+++ src/Extensions/Banshee.Lastfm/Banshee.Lastfm.csproj	(working copy)
@@ -72,6 +72,7 @@
     <Compile Include="Banshee.Lastfm.Radio\StationEditor.cs" />
     <Compile Include="Banshee.Lastfm.Radio\StationSource.cs" />
     <Compile Include="Banshee.Lastfm.Radio\StationType.cs" />
+    <Compile Include="Banshee.Lastfm.Audioscrobbler\AudioscrobblerActions.cs" />
     <Compile Include="Banshee.Lastfm.Audioscrobbler\AudioscrobblerService.cs" />
     <Compile Include="Banshee.Lastfm.Audioscrobbler\Queue.cs" />
     <Compile Include="Banshee.Lastfm.Radio\LastfmColumnController.cs" />
@@ -121,4 +122,4 @@
       </Properties>
     </MonoDevelop>
   </ProjectExtensions>
-</Project>
\ No newline at end of file
+</Project>
Index: src/Extensions/Banshee.Lastfm/Makefile.am
===================================================================
--- src/Extensions/Banshee.Lastfm/Makefile.am	(revision 4712)
+++ src/Extensions/Banshee.Lastfm/Makefile.am	(working copy)
@@ -4,6 +4,7 @@
 INSTALL_DIR = $(EXTENSIONS_INSTALL_DIR)
 
 SOURCES =  \
+	Banshee.Lastfm.Audioscrobbler/AudioscrobblerActions.cs \
 	Banshee.Lastfm.Audioscrobbler/AudioscrobblerService.cs \
 	Banshee.Lastfm.Audioscrobbler/Queue.cs \
 	Banshee.Lastfm.Radio/LastfmActions.cs \
Index: src/Extensions/Banshee.Lastfm/Resources/LastfmTrackActions.xml
===================================================================
--- src/Extensions/Banshee.Lastfm/Resources/LastfmTrackActions.xml	(revision 4712)
+++ src/Extensions/Banshee.Lastfm/Resources/LastfmTrackActions.xml	(working copy)
@@ -1,8 +1,8 @@
 <ui>
   <popup name="NotificationAreaIconMenu" action="NotificationAreaIconMenuAction">
     <placeholder name="NotificationPlaceholder">
-      <menuitem name="LastfmLove" action="LastfmLoveAction"/>
-      <menuitem name="LastfmHate" action="LastfmHateAction"/>
+      <menuitem name="AudioscrobblerLoveTrack" action="AudioscrobblerLoveTrackAction"/>
+      <menuitem name="AudioscrobblerBanTrack" action="AudioscrobblerBanTrackAction"/>
     </placeholder>
   </popup>
 </ui>
Index: src/Extensions/Banshee.Lastfm/Resources/GlobalUI.xml
===================================================================
--- src/Extensions/Banshee.Lastfm/Resources/GlobalUI.xml	(revision 4712)
+++ src/Extensions/Banshee.Lastfm/Resources/GlobalUI.xml	(working copy)
@@ -27,8 +27,8 @@
     
     <toolbar name="HeaderToolbar">
         <placeholder name="SourceActions">
-            <toolitem name="LastfmLove" action="LastfmLoveAction"/>
-            <toolitem name="LastfmHate" action="LastfmHateAction"/>
+            <toolitem name="AudioscrobblerLoveTrack" action="AudioscrobblerLoveTrackAction"/>
+            <toolitem name="AudioscrobblerBanTrack" action="AudioscrobblerBanTrackAction"/>
         </placeholder>
     </toolbar>
 
Index: src/Libraries/Lastfm/Lastfm/AudioscrobblerConnection.cs
===================================================================
--- src/Libraries/Lastfm/Lastfm/AudioscrobblerConnection.cs	(revision 4712)
+++ src/Libraries/Lastfm/Lastfm/AudioscrobblerConnection.cs	(working copy)
@@ -41,17 +41,27 @@
 
 namespace Lastfm
 {
+    public class AudioscrobblerStateChangedArgs : EventArgs
+    {
+        public AudioscrobblerState State;
+
+        public AudioscrobblerStateChangedArgs (AudioscrobblerState state)
+        {
+            State = state;
+        }
+    }
+
+    public enum AudioscrobblerState {
+        Idle,
+        NeedHandshake,
+        NeedTransmit,
+        WaitingForRequestStream,
+        WaitingForHandshakeResp,
+        WaitingForResponse
+    };
+
     public class AudioscrobblerConnection
     {
-        private enum State {
-            Idle,
-            NeedHandshake,
-            NeedTransmit,
-            WaitingForRequestStream,
-            WaitingForHandshakeResp,
-            WaitingForResponse
-        };
-
         private const int TICK_INTERVAL = 2000; /* 2 seconds */
         private const int FAILURE_LOG_MINUTES = 5; /* 5 minute delay on logging failure to upload information */
         private const int RETRY_SECONDS = 60; /* 60 second delay for transmission retries */
@@ -65,12 +75,21 @@
         private string post_url;
         private string session_id = null;
         private string now_playing_url;
-        
+
         private bool connected = false; /* if we're connected to network or not */
         public bool Connected {
             get { return connected; }
+            private set {
+                connected = value;
+                StateChangedHandler handler = StateChanged;
+                if (handler != null)
+                    handler (this);
+            }
         }
-        
+
+        public delegate void StateChangedHandler (AudioscrobblerConnection connection);
+        public event StateChangedHandler StateChanged;
+
         private bool started = false; /* engine has started and was/is connected to AS */
         public bool Started {
             get { return started; }
@@ -90,13 +109,14 @@
         private string current_now_playing_uri;
         private HttpWebRequest current_web_req;
         private IAsyncResult current_async_result;
-        private State state;
-        
+
+        private AudioscrobblerState state;
+
         internal AudioscrobblerConnection (IQueue queue)
         {
             LastfmCore.Account.Updated += AccountUpdated;
             
-            state = State.Idle;
+            state = AudioscrobblerState.Idle;
             this.queue = queue;
         }
         
@@ -110,11 +130,11 @@
         public void UpdateNetworkState (bool connected)
         {
             Log.DebugFormat ("Audioscrobbler state: {0}", connected ? "connected" : "disconnected");
-            this.connected = connected;
+            Connected = connected;
         }
 
         public void Start ()
-        {            
+        {
             if (started) {
                 return;
             }
@@ -175,21 +195,21 @@
                 return;
             }
                         
-            if ((state == State.Idle || state == State.NeedTransmit) && hard_failures > 2) {
-                state = State.NeedHandshake;
+            if ((state == AudioscrobblerState.Idle || state == AudioscrobblerState.NeedTransmit) && hard_failures > 2) {
+                state = AudioscrobblerState.NeedHandshake;
                 hard_failures = 0;
             }
             
             // and address changes in our engine state
             switch (state) {
-            case State.Idle:
+            case AudioscrobblerState.Idle:
                 if (LastfmCore.Account.UserName != null &&
                     LastfmCore.Account.CryptedPassword != null && session_id == null) {
                     
-                    state = State.NeedHandshake;
+                    state = AudioscrobblerState.NeedHandshake;
                 } else {
                     if (queue.Count > 0 && session_id != null) {
-                        state = State.NeedTransmit;
+                        state = AudioscrobblerState.NeedTransmit;
                     } else if (current_now_playing_uri != null && session_id != null) {
                         // Now playing info needs to be sent
                         NowPlaying (current_now_playing_uri);
@@ -199,20 +219,20 @@
                 }
                 
                 break;
-            case State.NeedHandshake:
+            case AudioscrobblerState.NeedHandshake:
                 if (DateTime.Now > next_interval) {
                     Handshake ();
                 }
                 
                 break;
-            case State.NeedTransmit:
+            case AudioscrobblerState.NeedTransmit:
                 if (DateTime.Now > next_interval) {
                     TransmitQueue ();
                 }
                 break;
-            case State.WaitingForResponse:
-            case State.WaitingForRequestStream:
-            case State.WaitingForHandshakeResp:
+            case AudioscrobblerState.WaitingForResponse:
+            case AudioscrobblerState.WaitingForRequestStream:
+            case AudioscrobblerState.WaitingForHandshakeResp:
                 // nothing here
                 break;
             }
@@ -260,13 +280,13 @@
             ts.Count = num_tracks_transmitted;
             ts.StringBuilder = sb;
 
-            state = State.WaitingForRequestStream;
+            state = AudioscrobblerState.WaitingForRequestStream;
             current_async_result = current_web_req.BeginGetRequestStream (TransmitGetRequestStream, ts);
             if (!(current_async_result.AsyncWaitHandle.WaitOne (TIME_OUT, false))) {
                 Log.Warning ("Audioscrobbler upload failed", "The request timed out and was aborted", false);
                 next_interval = DateTime.Now + new TimeSpan (0, 0, RETRY_SECONDS);
                 hard_failures++;
-                state = State.Idle;
+                state = AudioscrobblerState.Idle;
                 
                 current_web_req.Abort();
             }
@@ -280,7 +300,7 @@
                 stream = current_web_req.EndGetRequestStream (ar);
             } catch (Exception e) {
                 Log.Exception ("Failed to get the request stream", e);
-                state = State.Idle;
+                state = AudioscrobblerState.Idle;
                 next_interval = DateTime.Now + new TimeSpan (0, 0, RETRY_SECONDS);
                 return;
             }
@@ -292,12 +312,12 @@
             writer.Write (sb.ToString ());
             writer.Close ();
 
-            state = State.WaitingForResponse;
+            state = AudioscrobblerState.WaitingForResponse;
             current_async_result = current_web_req.BeginGetResponse (TransmitGetResponse, ts);
             if (current_async_result == null) {
                 next_interval = DateTime.Now + new TimeSpan (0, 0, RETRY_SECONDS);
                 hard_failures++;
-                state = State.Idle;
+                state = AudioscrobblerState.Idle;
             }
         }
 
@@ -311,7 +331,7 @@
             catch (Exception e) {
                 Log.Warning (String.Format("Failed to get the response: {0}", e), false);
 
-                state = State.Idle;
+                state = AudioscrobblerState.Idle;
                 next_interval = DateTime.Now + new TimeSpan (0, 0, RETRY_SECONDS);
                 return;
             }
@@ -334,7 +354,7 @@
                 
                 // retransmit the queue on the next interval
                 hard_failures++;
-                state = State.NeedTransmit;
+                state = AudioscrobblerState.NeedTransmit;
             }
             else if (line.StartsWith ("BADSESSION")) {
                 if (now - last_upload_failed_logged > TimeSpan.FromMinutes(FAILURE_LOG_MINUTES)) {
@@ -345,7 +365,7 @@
                 // attempt to re-handshake (and retransmit) on the next interval
                 session_id = null;
                 next_interval = DateTime.Now + new TimeSpan (0, 0, RETRY_SECONDS);
-                state = State.NeedHandshake;
+                state = AudioscrobblerState.NeedHandshake;
                 return;
             } else if (line.StartsWith ("OK")) {
                 /* if we've previously logged failures, be nice and log the successful upload. */
@@ -360,14 +380,14 @@
                 queue.RemoveRange (0, ts.Count);
                 queue.Save ();
                 
-                state = State.Idle;
+                state = AudioscrobblerState.Idle;
             } else {
                 if (now - last_upload_failed_logged > TimeSpan.FromMinutes(FAILURE_LOG_MINUTES)) {
                     Log.Warning ("Audioscrobbler upload failed", String.Format ("Unrecognized response: {0}", line), false);
                     last_upload_failed_logged = now;
                 }
                 
-                state = State.Idle;
+                state = AudioscrobblerState.Idle;
             }
         }
 
@@ -396,14 +416,14 @@
 
             current_web_req = (HttpWebRequest) WebRequest.Create (uri);
 
-            state = State.WaitingForHandshakeResp;
+            state = AudioscrobblerState.WaitingForHandshakeResp;
             current_async_result = current_web_req.BeginGetResponse (HandshakeGetResponse, null);
             if (current_async_result == null) {
                 next_interval = DateTime.Now + new TimeSpan (0, 0, hard_failure_retry_sec);
                 hard_failures++;
                 if (hard_failure_retry_sec < MAX_RETRY_SECONDS)
                     hard_failure_retry_sec *= 2;
-                state = State.NeedHandshake;
+                state = AudioscrobblerState.NeedHandshake;
             }
         }
 
@@ -420,7 +440,7 @@
                 Log.Warning ("Failed to handshake: {0}", e.ToString (), false);
 
                 // back off for a time before trying again
-                state = State.Idle;
+                state = AudioscrobblerState.Idle;
                 next_interval = DateTime.Now + new TimeSpan (0, 0, RETRY_SECONDS);
                 return;
             }
@@ -469,7 +489,7 @@
                 }
             }
 
-            state = State.Idle;
+            state = AudioscrobblerState.Idle;
         }
         
         //
@@ -511,7 +531,7 @@
                                         str_track_number,
                                         mbrainzid);
 
-            Console.WriteLine ("Submitting via non-uri handler.");            
+            Log.Debug ("Submitting via non-uri handler.");
             NowPlaying (uri);
         }
         
@@ -545,7 +565,7 @@
                 now_playing_post.Method = "POST";
                 now_playing_post.ContentType = "application/x-www-form-urlencoded";
                 now_playing_post.ContentLength = uri.Length;
-                if (state == State.Idle) {
+                if (state == AudioscrobblerState.Idle) {
                     // Don't actually POST it until we're idle (that is, we
                     // probably have stuff queued which will reset the Now
                     // Playing if we send them first).
@@ -579,7 +599,7 @@
                     // attempt to re-handshake on the next interval
                     session_id = null;
                     next_interval = DateTime.Now + new TimeSpan (0, 0, RETRY_SECONDS);
-                    state = State.NeedHandshake;
+                    state = AudioscrobblerState.NeedHandshake;
                     StartTransitionHandler ();
                     return;
                 } else if (line.StartsWith ("OK")) {
@@ -609,5 +629,112 @@
                 now_playing_post = null;
             }
         }
+
+        public bool Love    (string artist, string title)
+        { return PostTrackRequest ("loveTrack", artist, title); }
+        public bool UnLove  (string artist, string title)
+        { return PostTrackRequest ("unLoveTrack", artist, title); }
+        public bool Ban     (string artist, string title)
+        { return PostTrackRequest ("banTrack", artist, title); }
+        public bool UnBan   (string artist, string title)
+        { return PostTrackRequest ("unBanTrack", artist, title); }
+
+        // XML-RPC foo
+
+        private bool PostTrackRequest (string method, string artist, string title)
+        {
+            return PostXmlRpc (LastFMXmlRpcRequest (method).AddStringParams (artist, title));
+        }
+
+        private bool PostXmlRpc (LameXmlRpcRequest request)
+        {
+	  if (session_id == null || !connected)
+	    return false;
+
+	  return Post (XmlRpcUrl, request.ToString ()) == HttpStatusCode.OK;
+        }
+
+        private string XmlRpcUrl {
+          get { return "http://ws.audioscrobbler.com/1.0/rw/xmlrpc.php"; }
+	  /* get { return post_url; } */
+            /* get { return String.Format ("http://{0}/1.0/rw/xmlrpc.php", post_url); } */
+        }
+
+        private LameXmlRpcRequest LastFMXmlRpcRequest (string method)
+        {
+            string time = UnixTime ();
+            string auth_hash = Hyena.CryptoUtil.Md5Encode (LastfmCore.Account.CryptedPassword + time);
+            return new LameXmlRpcRequest (method).AddStringParams (LastfmCore.Account.UserName, time, auth_hash);
+        }
+
+        protected class LameXmlRpcRequest
+        {
+            private StringBuilder sb = new StringBuilder ();
+            public LameXmlRpcRequest (string method_name)
+            {
+                sb.Append ("<?xml version=\"1.0\" encoding=\"us-ascii\"?>\n");
+                sb.Append ("<methodCall><methodName>");
+                sb.Append (Encode (method_name));
+                sb.Append ("</methodName>\n");
+                sb.Append ("<params>\n");
+            }
+
+            public LameXmlRpcRequest AddStringParams (params string [] values)
+            {
+                foreach (string value in values)
+                    AddStringParam (value);
+                return this;
+            }
+
+            public LameXmlRpcRequest AddStringParam (string value)
+            {
+                sb.Append ("<param><value><string>");
+                sb.Append (Encode (value));
+                sb.Append ("</string></value></param>\n");
+                return this;
+            }
+
+            public static string Encode (string val)
+            {
+                return HttpUtility.HtmlEncode (val);
+            }
+
+            private bool closed = false;
+            public override string ToString ()
+            {
+                if (!closed) {
+                    sb.Append ("</params>\n</methodCall>");
+                    closed = true;
+                }
+
+                return sb.ToString ();
+            }
+        }
+
+        // Basic HTTP helpers
+
+        private HttpStatusCode Post (string uri, string body)
+        {
+            HttpWebRequest request = (HttpWebRequest) WebRequest.Create (uri);
+            request.UserAgent = LastfmCore.UserAgent;
+            request.Timeout = 10000;
+            request.Method = "POST";
+            request.KeepAlive = false;
+            request.ContentLength = body.Length;
+
+            using (StreamWriter writer = new StreamWriter (request.GetRequestStream ())) {
+                writer.Write (body);
+            }
+
+            HttpWebResponse response = (HttpWebResponse) request.GetResponse ();
+            // using (Stream stream = response.GetResponseStream ()) {
+            //   using (StreamReader reader = new StreamReader (stream)) {
+            //     Log.Debug ("Posted ", body);
+            //     Log.Debug ("To ", uri);
+            //     Log.Debug ("Received ", reader.ReadToEnd ());
+            //   }
+            // }
+            return response.StatusCode;
+        }
     }
 }
--- /dev/null	2008-07-02 06:16:45.000000000 -0400
+++ src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Audioscrobbler/AudioscrobblerActions.cs	2008-10-21 01:16:10.000000000 -0400
@@ -0,0 +1,196 @@
+//
+// AudioscrobblerActions.cs
+//
+// Authors:
+//   Warren Seine <warren.seine@gmail.com>
+//
+// Copyright (C) 2007-2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+using System.Collections.Generic;
+using Gtk;
+
+using Mono.Unix;
+
+using Hyena;
+using Lastfm;
+using Lastfm.Gui;
+using SortType = Hyena.Data.SortType;
+
+using Banshee.Base;
+using Banshee.Sources;
+using Banshee.Widgets;
+using Banshee.MediaEngine;
+using Banshee.Database;
+using Banshee.Configuration;
+using Banshee.ServiceStack;
+using Banshee.Gui;
+using Banshee.Collection;
+using Banshee.PlaybackController;
+
+namespace Banshee.Lastfm.Audioscrobbler
+{
+    public class AudioscrobblerActions : BansheeActionGroup
+    {
+        private uint actions_id;
+	private AudioscrobblerService service;
+
+        public AudioscrobblerActions (AudioscrobblerService service) : base (ServiceManager.Get<InterfaceActionService> (), "Audioscrobbler")
+        {
+            this.service = service;
+
+            Add (new ActionEntry [] {
+                new ActionEntry ("AudioscrobblerAction", null,
+                    Catalog.GetString ("_Last.fm"), null,
+                    Catalog.GetString ("Configure the Audioscrobbler plugin"), null),
+
+                new ActionEntry ("AudioscrobblerVisitAction", null,
+                    Catalog.GetString ("Visit _User Profile Page"), null,
+                    Catalog.GetString ("Visit Your Last.fm Profile Page"), OnVisitOwnProfile),
+
+                new ActionEntry ("AudioscrobblerConfigureAction", Stock.Properties,
+                    Catalog.GetString ("_Configure..."), null,
+                    Catalog.GetString ("Configure the Last.fm Extension"), OnConfigurePlugin),
+
+                new ActionEntry ("AudioscrobblerLoveTrackAction", null,
+                    Catalog.GetString ("_Love Current Track"), null,
+                    Catalog.GetString ("Mark current track as loved"), OnLoved),
+
+                new ActionEntry ("AudioscrobblerBanTrackAction", null,
+                    Catalog.GetString ("_Ban Current Track"), null,
+                    Catalog.GetString ("Mark current track as banned"), OnBanned)
+            });
+
+            Add (new ToggleActionEntry [] {
+                new ToggleActionEntry ("AudioscrobblerEnableAction", null,
+                    Catalog.GetString ("_Enable Song Reporting"), "<control>U",
+                    Catalog.GetString ("Enable song reporting"), OnToggleEnabled, service.Enabled)
+            });
+
+            this["AudioscrobblerLoveTrackAction"].IconName = "face-smile";
+            this["AudioscrobblerBanTrackAction"].IconName = "face-sad";
+
+            this["AudioscrobblerLoveTrackAction"].IsImportant = true;
+            this["AudioscrobblerBanTrackAction"].IsImportant = true;
+
+            actions_id = Actions.UIManager.AddUiFromResource ("AudioscrobblerMenu.xml");
+            actions_id = Actions.UIManager.AddUiFromResource ("LastfmTrackActions.xml");
+            Actions.AddActionGroup (this);
+
+            LastfmCore.Audioscrobbler.StateChanged += HandleAudioscrobblerStateChanged;
+            ServiceManager.PlayerEngine.ConnectEvent (OnPlayerEvent,
+                PlayerEvent.StartOfStream |
+                PlayerEvent.StateChange |
+                PlayerEvent.EndOfStream);
+	    // UpdateActions ();
+        }
+
+        public override void Dispose ()
+        {
+            ServiceManager.PlayerEngine.DisconnectEvent (OnPlayerEvent);
+            LastfmCore.Audioscrobbler.StateChanged -= HandleAudioscrobblerStateChanged;
+            Actions.UIManager.RemoveUi (actions_id);
+            Actions.RemoveActionGroup (this);
+            base.Dispose ();
+        }
+
+        private bool updating = false;
+
+        private void HandleAudioscrobblerStateChanged (object sender)
+        {
+            UpdateActions ();
+        }
+
+        private void OnPlayerEvent (PlayerEventArgs args)
+        {
+            UpdateActions ();
+        }
+
+        private void UpdateActions ()
+        {
+            lock (this) {
+                if (updating)
+                    return;
+                updating = true;
+            }
+
+            bool _ = LastfmCore.Audioscrobbler.Connected &&
+                (ServiceManager.PlayerEngine.CurrentState == PlayerState.Playing ||
+	         ServiceManager.PlayerEngine.CurrentState == PlayerState.Paused);
+
+            this["AudioscrobblerLoveTrackAction"].Visible = _;
+            this["AudioscrobblerBanTrackAction"].Visible = _;
+            this["AudioscrobblerVisitAction"].Sensitive = String.IsNullOrEmpty (LastfmCore.Account.UserName);
+
+            updating = false;
+        }
+
+        private void OnConfigurePlugin (object o, EventArgs args)
+        {
+            AccountLoginDialog dialog = new AccountLoginDialog (LastfmCore.Account, true);
+            dialog.SaveOnEdit = true;
+            if (LastfmCore.Account.UserName == null) {
+                dialog.AddSignUpButton ();
+            }
+            dialog.Run ();
+            dialog.Destroy ();
+        }
+
+        private void OnVisitOwnProfile (object o, EventArgs args)
+        {
+            LastfmCore.Account.VisitUserProfile (LastfmCore.Account.UserName);
+        }
+
+        private void OnLoved (object o, EventArgs args)
+        {
+	  TrackInfo track = ServiceManager.PlayerEngine.CurrentTrack;
+	  if (track == null)
+	    return;
+
+	  if (LastfmCore.Audioscrobbler.Love (track.ArtistName, track.TrackTitle))
+	    Log.Debug ("Track successfully loved");
+	  else
+	    Log.Debug ("Failed marking track as loved");
+        }
+
+        private void OnBanned (object o, EventArgs args)
+        {
+	  TrackInfo track = ServiceManager.PlayerEngine.CurrentTrack;
+	  if (track == null)
+	    return;
+
+	  if (LastfmCore.Audioscrobbler.Love (track.ArtistName, track.TrackTitle))
+	    Log.Debug ("Track successfully banned");
+	  else
+	    Log.Debug ("Failed marking track as banned");
+
+	  ServiceManager.PlaybackController.Next ();
+        }
+
+        private void OnToggleEnabled (object o, EventArgs args)
+        {
+            service.Enabled = (o as ToggleAction).Active;
+        }
+    }
+}
