strangelights.com

Main F# Site

Edit Edit
Print Print
Recent Changes Recent Changes
Subscriptions Subscriptions
Lost and Found Lost and Found
Find References Find References
Rename Rename
Search
List all versions List all versions
fsweet
.

Lightweight WPF Twitter client sample F# script

Change the username and password in the code, then simply run with F# interactive.

The client lets you both see your friend's timeline and post tweets, all in under 200 lines of F#!

Advertising degree | hr degree | Online International business degree | Online management degree | Marketing degree

    // fsweet - WPF Twitter client F# script 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 = "<your username>", "<your 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
        )


    [<STAThread>]
    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
Welcome to F Sharp Wiki, view the HomePage

This site supports the new NoFollow anti-spam initiative.

Recent Topics

Copyright 2005, Robert Pickering (Others where credited) | Terms of Use