/// /// Copyright © 2003-2008 JetBrains s.r.o. /// You may distribute under the terms of the GNU General Public License, as published by the Free Software Foundation, version 2 (see License.txt in the repository root folder). /// using System; using System.Collections; using System.IO; using System.Net; using System.Security.Cryptography; using System.Text; using System.Web; using System.Windows.Forms; using JetBrains.Omea.OpenAPI; namespace JetBrains.Omea.SamplePlugins.LiveJournalPlugin { /// /// Summary description for FriendsImportAction. /// public class FriendsImportAction : SimpleAction { private const string _protocolURL = "http://www.livejournal.com/interface/flat"; private const string _contentType = "application/x-www-form-urlencoded"; private const string _urlTemplate = "http://www.livejournal.com/users/{0}/data/rss?auth=digest"; private const string _groupNameTemplate = "LiveJournal friends of {1}"; private const string _feedNameTemplate = "{1} ({0})"; private const string _feedDescTemplate = "Posts of LeveJournal user '{0}'"; private static char[] _hexChars; private delegate void ImportJob( string UserName, string Password, int UpdateFreq, string UpdatePeriod ); private delegate void StatusReportJob( string message, MessageBoxIcon icon ); public FriendsImportAction() { _hexChars = new char[] {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; } public override void Execute( IActionContext context ) { FriendsImportForm frm = null; string login = Core.SettingStore.ReadString(LiveJournalPlugin.ConfigSection,LiveJournalPlugin.ConfigKeyUsername); string passwd = Core.SettingStore.ReadString(LiveJournalPlugin.ConfigSection,LiveJournalPlugin.ConfigKeyPassword); int updateFreq = Core.SettingStore.ReadInt( "RSS" , "UpdateFrequency", -1); string updatePeriod = Core.SettingStore.ReadString( "RSS" , "UpdatePeriod", "hourly"); frm = new FriendsImportForm( login, passwd, updateFreq, LiveJournalPlugin.UpdatePeriodToIndex(updatePeriod) ); DialogResult res = frm.ShowDialog(); if ( DialogResult.OK != res ) { return; } login = frm.Login; passwd = frm.Password; updateFreq = frm.UpdateFreq; updatePeriod = LiveJournalPlugin.UpdateIndexToPeriod( frm.UpdatePeriod ); Core.SettingStore.WriteString(LiveJournalPlugin.ConfigSection,LiveJournalPlugin.ConfigKeyUsername,login); Core.SettingStore.WriteString(LiveJournalPlugin.ConfigSection,LiveJournalPlugin.ConfigKeyPassword,passwd); Core.NetworkAP.QueueJob( new ImportJob(ImportFreinds), new object[] { login, passwd, updateFreq, updatePeriod } ); } private void ImportFreinds( string UserName, string Password, int UpdateFreq, string UpdatePeriod ) { Hashtable answer; Hashtable friends = new Hashtable(); string[] chrsp; string fullName = ""; try { chrsp = ChallengeResponse(Password); answer = FlatRequest("login", "user", UserName, "auth_method", "challenge", "auth_challenge", chrsp[0], "auth_response", chrsp[1]); fullName = answer.ContainsKey( "name" ) ? answer["name"] as string : UserName; chrsp = ChallengeResponse(Password); answer = FlatRequest("getfriends", "user", UserName, "auth_method", "challenge", "auth_challenge", chrsp[0], "auth_response", chrsp[1]); if(!answer.ContainsKey("friend_count")) throw new Exception("Answer format error: no number of friends provided"); int friendsCount = 0; try { friendsCount = Int32.Parse( answer["friend_count"] as string ); } catch { throw new Exception("Answer format error: number of friends is not numeric"); } for(int i = 1; i <= friendsCount; ++i) { string key; string friend; key = String.Format( "friend_{0}_user", i ); if(!answer.ContainsKey(key)) throw new Exception( String.Format( "Answer format error: no friend {0} provided", i ) ); friend = answer[key] as string; key = String.Format( "friend_{0}_name", i ); if(!answer.ContainsKey(key)) throw new Exception( String.Format( "Answer format error: no name for friend {0} provided", i ) ); friends.Add( friend, answer[key] as string ); } } catch(Exception ex) { Core.UIManager.QueueUIJob( new StatusReportJob(ReportStatus), new object[] { "Protocol error:\n" + ex.Message, MessageBoxIcon.Error }); return; } // Make group fullName = String.Format( _groupNameTemplate, UserName, fullName ); IResource feedGroup = Core.ResourceStore.FindUniqueResource( "RSSFeedGroup", "Name", fullName ); if( null == feedGroup ) { IResource parentGroup = null; parentGroup = Core.ResourceStore.FindUniqueResource( "ResourceTreeRoot", "RootResourceType", "RSSFeed" ); ResourceProxy proxy = ResourceProxy.BeginNewResource( "RSSFeedGroup" ); try { proxy.SetProp( "Name", fullName ); if( null != parentGroup ) { proxy.SetProp( "Parent", parentGroup ); } } finally { proxy.EndUpdate(); feedGroup = proxy.Resource; } if(null == feedGroup) { Core.UIManager.QueueUIJob( new StatusReportJob(ReportStatus), new object[] { "Can not add group for new feeds", MessageBoxIcon.Error }); return; } } // Ok, friends list is populated bool added = false; foreach( string friend in friends.Keys ) { string URL = String.Format( _urlTemplate, friend ); IResource feed = null; feed = Core.ResourceStore.FindUniqueResource( "RSSFeed", "URL", URL ); if( null != feed ) continue; ResourceProxy proxy = ResourceProxy.BeginNewResource( "RSSFeed" ); try { proxy.SetProp( "Name", String.Format( _feedNameTemplate, friend, friends[friend] as string )); proxy.SetProp( "Description", String.Format( _feedDescTemplate, friend, friends[friend] as string )); proxy.SetProp( "URL", URL ); proxy.SetProp( "Parent", feedGroup ); proxy.SetProp( "HttpUserName", UserName ); proxy.SetProp( "HttpPassword", Password ); proxy.SetProp( "UpdateFrequency", UpdateFreq ); proxy.SetProp( "UpdatePeriod", UpdatePeriod ); } finally { proxy.EndUpdate(); feed = proxy.Resource; } if(null != feed) { added = true; LiveJournalPlugin.RSSService.QueueFeedUpdate( feed ); } } if(added) { Core.WorkspaceManager.AddToActiveWorkspace( feedGroup ); } else { Core.UIManager.QueueUIJob( new StatusReportJob(ReportStatus), new object[] { "No new friends were added", MessageBoxIcon.Information }); } } private string md5_hex(string s) { string res = ""; foreach( byte b in MD5.Create().ComputeHash( Encoding.UTF8.GetBytes( s ) ) ) { res += _hexChars[ ( b >> 4 ) & 0x0f ]; res += _hexChars[ ( b ) & 0x0f ]; } return res; } private Hashtable FlatRequest(string mode, params string[] parameters) { HttpWebRequest req = null; HttpWebResponse rsp = null; string content; int i; content = "mode=" + mode; for(i = 0; i < parameters.Length - 1; i += 2) { content += "&" + HttpUtility.UrlEncode(parameters[i]) + "=" + HttpUtility.UrlEncode(parameters[i + 1]); } if(i < parameters.Length) throw new ArgumentException("Invalid number of parameters","parameters"); content += "&ver=1"; // Make request byte[] reqb = Encoding.UTF8.GetBytes( content ); // Get response try { req = WebRequest.Create(_protocolURL) as HttpWebRequest; req.Method = "POST"; req.ContentType = _contentType; req.ContentLength = content.Length; req.SendChunked = false; Stream reqs = req.GetRequestStream(); reqs.Write(reqb , 0, reqb.Length); reqs.Close(); rsp = req.GetResponse() as HttpWebResponse; } catch(Exception ex) { throw new Exception("HTTP request failed: " + ex.Message); } if(rsp.StatusCode != HttpStatusCode.OK) throw new Exception("HTTP request failed: " + rsp.StatusDescription); // Parse response TextReader rsptr = new StreamReader(rsp.GetResponseStream(), Encoding.UTF8); Hashtable answer = new Hashtable(); while(true) { string var = null; string val = null; try { var = rsptr.ReadLine(); if(var == null) { // End of loop break; } val = rsptr.ReadLine(); } catch(Exception ex) { throw new Exception("Response error: " + ex.Message); } if(var == null || val == null) throw new Exception("Response error: No var-val pair"); answer.Add( var, val ); } if(!answer.ContainsKey("success")) throw new Exception("Answer format error"); if(answer["success"] as string != "OK") throw new Exception( answer.ContainsKey("errmsg") ? answer["errmsg"] as string : "Unknown error" ); return answer; } private string[] ChallengeResponse( string Password ) { Hashtable answer = FlatRequest("getchallenge"); // check answer if(!answer.ContainsKey("challenge")) throw new Exception("Answer format error: no challenge provided"); // Extract challenge string challenge = answer["challenge"] as string; // Prepare response string response = md5_hex( challenge + md5_hex( Password ) ); return new string[] { challenge, response }; } private void ReportStatus(string message, MessageBoxIcon icon) { MessageBox.Show(message,LiveJournalPlugin.Name, MessageBoxButtons.OK, icon); } } }