REBOL [
    Title: "Core Twitter API Client"
    Date: 31-May-2010
    Author: "Christopher Ross-Gill"
    Version: 0.2.0
    Needs: [
        http://www.ross-gill.com/r/altjson.r
        http://www.ross-gill.com/r/altwebform.r
    ]
    History: [
        26-Sep-2009 0.1.0 "Original Version"
    ]
    ; Type: 'module
    Exports: [twitter]
]

; do http://www.ross-gill.com/r/altxml.r
do http://www.ross-gill.com/r/altjson.r
do http://www.ross-gill.com/r/altwebform.r

twitter: context bind [
    as: func [
        "Set user name and password"
        user [string!] "User name" pass [string!] "Password"
    ][set persona reduce [user pass] true]

    find: func [
        "Tweets by Search" [catch]
        query [string! issue! email!] "Search String"
        /size count [integer!] /page offset [integer!]
    ][
        case [
            issue? query [query: mold query]
            email? query [query: join "@" query/host]
        ]

        set params reduce [query offset count]

        either attempt [
            result: read join http://search.twitter.com/search.json? to-webform params
        ] load-result error/connection
    ]

    timeline: func [
        "Retrieve a User Timeline" [catch]
        /for user [string!] /size count [integer!] /page offset [integer!]
    ][
        ; unless persona/user error/credentials

        set options reduce [all [count min 200 abs count] offset]
        method: rejoin [
            "statuses/user_timeline/"
            any [user persona/user]
            ".json?" to-webform options
        ]

        either attempt [result: read target] load-result error/connection
    ]

    friends: func [
        "Retrieve status messages from friends" [catch]
        /size count [integer!] /page offset [integer!]
    ][
        unless persona/user error/credentials

        set options reduce [all [count min 200 abs count] offset]
        method: rejoin ["statuses/friends_timeline.json?" to-webform options]

        either attempt [result: read target] load-result error/connection
    ]

    update: func [
        "Send Twitter status update" [catch]
        status [string!] "Status message"
        /reply "As reply to" id [issue!] "Reply reference"
    ][
        unless persona/user error/credentials
        unless all [0 < length? status 141 > length? status] error/invalid

        set message reduce [status id]
        method: "statuses/update.json"

        either attempt [
            result: read/custom target compose [post (to-webform message)]
        ] load-result error/connection
    ]

] context [ ; internals
    method: none
    persona: context [user: pass: none]
    options: context [count: page: none]
    params: context [q: page: rpp: none]
    message: context [status: in_reply_to_status_id: none]

    result: none
    load-result: [load-json result]

    target: [
        scheme: 'http
        host: "api.twitter.com" path: "/1/" target: method
        user: persona/user pass: persona/pass
    ]

    error: [
        credentials [throw make error! "Must set credentials (twitter/as user pass)"]
        connection [throw make error! "Unable to connect to Twitter"]
        invalid [throw make error! "Status length should be between between 1 and 140"]
    ]
]