// fsweet - F# Twitter client script using WPF by Phillip Trelford 2009 #light #r "PresentationCore.dll" #r "PresentationFramework.dll" #r "WindowsBase.dll" open System open System.Diagnostics open System.IO open System.Net open System.Text open System.Windows open System.Windows.Controls open System.Windows.Documents open System.Xml // Enter your username and password here` let username, password = "", "" /// Gets web response as an XmlDocument let GetXmlWebResponse (request:WebRequest) = try use response = request.GetResponse() use stream = response.GetResponseStream() let doc = XmlDocument() in doc.Load(stream) Some(doc) with e -> e.ToString() |> printf "Error: %s"; None /// Gets Xml response from Web Url let Get (url:string) = let request = WebRequest.Create(url) request.Credentials <- NetworkCredential(username, password) GetXmlWebResponse request /// Posts data to Web Url let Post (url:string) user (data:string) = let request = WebRequest.Create(url) :?> HttpWebRequest request.Method <- "POST" request.ServicePoint.Expect100Continue <- false request.Headers.Add("Authorization", "Basic " + user) let bytes = System.Text.Encoding.ASCII.GetBytes(data); request.ContentType <- "application/x-www-form-urlencoded" request.ContentLength <- int64 bytes.Length request.GetRequestStream().Write(bytes, 0, bytes.Length) GetXmlWebResponse request /// Posts user tweet let Tweet tweet = let user = Convert.ToBase64String(Encoding.UTF8.GetBytes(username+":"+password)); "status=" + tweet |> Post "http://twitter.com/statuses/update.xml" user /// Gets user time line let GetUserTimeLine () = sprintf @"http://twitter.com/statuses/user_timeline/%s.xml" username |> Get /// Gets friends time line let GetFriendsTimeLine () = "http://twitter.com/statuses/friends_timeline.xml" |> Get let NodeText (node:XmlNode) child = node.[child].InnerText let Date s = XmlConvert.ToDateTime(s, "ddd MMM dd HH:mm:ss zzzzz yyyy") /// Creates user tuple let CreateUser (user:XmlNode) = let Text = NodeText user // Curry NodeText function (Text "name", Text "screen_name", Text "profile_image_url") /// Creates status tuple let CreateStatus (status:XmlNode) = let Text = NodeText status // Curry NodeText function (Text "created_at" |> Date, Text "text", CreateUser status.["user"]) /// Gets statuses let GetStatuses () = match GetFriendsTimeLine () (*GetUserTimeLine()*) with | Some doc -> seq { for status in doc.DocumentElement.SelectNodes("status") do yield CreateStatus status } | None -> Seq.empty // Table layout panel type extends Grid for easier use in code type TableLayout (columnDefinitions:string seq,rowDefinitions:string seq) = inherit Grid () /// Converts grid length string to GridLength instance let ParseGridLength (s:string) = match s.Length, s.EndsWith("*") with | 0, _ -> GridLength() | n, false -> GridLength(Double.Parse(s)) | 1, true -> GridLength(1.0, GridUnitType.Star) | n, true -> GridLength(Double.Parse(s.Substring(0, n-1)), GridUnitType.Star) do columnDefinitions |> Seq.map (fun text -> ColumnDefinition(Width=ParseGridLength text)) |> Seq.iter base.ColumnDefinitions.Add do rowDefinitions |> Seq.map (fun text -> RowDefinition(Height=ParseGridLength text)) |> Seq.iter base.RowDefinitions.Add /// Adds item to layout, automatically setting grid column and row values member this.AddItem (item:#UIElement) = let span = match this.ColumnDefinitions.Count with | 0 -> 1 | n -> n let index = this.Children.Count item |> this.Children.Add |> ignore Grid.SetColumn(item, index % span) Grid.SetRow(item, index / span) /// Creates bitmap image let CreateBitmap uri = let img = Media.Imaging.BitmapImage() img.BeginInit(); img.UriSource <- uri; img.EndInit() img /// Annotates hyperlinks converting text to an Inline array let AnnotateLinks (text:string) = let rec GetUrlPositions (n:int) (acc) = match text.IndexOf("http", n) with | -1 -> acc | n -> n::(GetUrlPositions (n+1) acc) GetUrlPositions 0 [] |> Seq.map (fun first -> first, match text.IndexOfAny([|' ';'\t';'\r';'\n'|], first) with | -1 -> text.Length | n -> n ) |> Seq.fold (fun (start,lines) (first, last) -> let leadin = text.Substring(start, first - start) let url = text.Substring(first, last - first) let uri = Uri(url, UriKind.Absolute) let link = Hyperlink(Run(url), NavigateUri=uri) link.Click.Add (fun _ -> Process.Start(url) |> ignore ) (last, (link :> Inline) :: (Run(leadin) :> Inline) :: lines) ) (0, []) |> (fun (start, lines) -> (Run(text.Substring(start)) :> Inline) :: lines) |> Seq.to_array |> Array.rev /// Renders statuses in specified control let RenderStatuses (control:#ItemsControl) statuses = control.Items.Clear() statuses |> Seq.iter (fun (createdAt, text, user) -> let name, screen, url = user let across = new TableLayout(["40";"*"], []) let bitmap = Uri(url, UriKind.Absolute) |> CreateBitmap Image(Source=bitmap) |> across.AddItem let down = new TableLayout([], ["1*";"2*";"1*"]) Label(Content=screen, FontWeight=FontWeights.Bold, ToolTip=name) |> down.AddItem let block = TextBlock(TextWrapping=TextWrapping.Wrap) AnnotateLinks text |> block.Inlines.AddRange down.AddItem block Label(Content=createdAt, FontStyle=FontStyles.Italic) |> down.AddItem across.AddItem down control.Items.Add(across) |> ignore ) [] do let statusBox = new TextBox(TextWrapping=TextWrapping.Wrap) let statusItems = new ListBox(Margin=Thickness(4.0)) ScrollViewer.SetHorizontalScrollBarVisibility(statusItems,ScrollBarVisibility.Disabled) let characterCounter = new Label(FontSize=20.0,FontFamily=Media.FontFamily("Georgia")) let updateButton = new Button(Content="Update", Width=80.0, Height=24.0, IsEnabled=false) let SetCharacterCount () = let count = 140 - statusBox.Text.Length characterCounter.Content <- count characterCounter.Foreground <- match count with | n when n >= 0 -> Media.SolidColorBrush(Media.Colors.Gray) | _ -> Media.SolidColorBrush(Media.Colors.Red) SetCharacterCount () statusBox.TextChanged.Add (fun _ -> SetCharacterCount () updateButton.IsEnabled <- statusBox.Text.Length > 0 ) updateButton.Click.Add (fun _ -> Tweet statusBox.Text |> ignore GetStatuses () |> RenderStatuses statusItems statusBox.Clear() ) let CreateStatusLayout () = let header = TableLayout(["*";"80"],[]) Label(Content="What are you doing?", FontSize=16.0) |> header.AddItem characterCounter |> header.AddItem let footer = TableLayout(["*";"80"],[]) Label() |> footer.AddItem updateButton |> footer.AddItem let whatLayout = TableLayout([], ["1*";"2*";"1*"]) whatLayout.Margin <- Thickness(4.0) whatLayout.AddItem header whatLayout.AddItem statusBox whatLayout.AddItem footer whatLayout let windowLayout = TableLayout([], ["160";"*"]) CreateStatusLayout () |> windowLayout.AddItem statusItems |> windowLayout.AddItem let window = new Window(Title="fsweet", Width=320.0, Height=400.0) //window.Icon <- Media.Imaging.BitmapFrame.Create(Uri(@"twitter_icon_16x16.bmp", UriKind.Relative)) window.Content <- windowLayout window.Loaded.Add (fun _ -> GetStatuses () |> RenderStatuses statusItems) (new Application()).Run(window) |> ignore