REBOL [ Title: "Twitter Client for Rebol" Date: 2-May-2016 Author: "Christopher Ross-Gill" Version: 0.3.5 Rights: http://creativecommons.org/licenses/by-sa/3.0/ File: %twitter.r Purpose: {Rebol script to access and use the Twitter OAuth API.} Settings: [ ; use if not working with 'do/args Consumer-Key: <consumer-key> Consumer-Secret: <consumer-secret> User-Store: <path-to-saved-users> ] ] do http://reb4.me/r/altwebform do http://reb4.me/r/altjson twitter: context bind [ as: func [ [catch] "Set current user" user [string!] "Twitter user name" ][ either user: select users user [ persona: make persona user persona/name ][ either not error? user: try [register][ repend users [ user/name new-line/skip/all third user true 2 ] persona/name ][throw :user] ] ] save-users: func [ "Saves authorized users" /to location [file! url!] "Alternate Storage Location" ][ location: any [location settings/user-store] unless any [file? location url? location][ make error! "No Storage Location Provided" ] save/header location new-line/skip/all users true 2 context [ Title: "Twitter Authorized Users" Date: now/date ] ] authorized-users: func ["Lists authorized users"][extract users 2] 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: send/with 'get %1.1/search/tweets.json params ] load-result error/connection ] timeline: func [ "Retrieve a User Timeline" [catch] /for user [string!] /size count [integer!] /page offset [integer!] ][ unless persona/name error/credentials set options reduce [ any [user persona/name] all [count min 200 abs count] offset ] either attempt [ result: send/with 'get %1.1/statuses/user_timeline.json options ] load-result error/connection ] home: friends: func [ "Retrieve status messages from friends" [catch] /size count [integer!] /page offset [integer!] ][ unless persona/name error/credentials set options reduce [ none all [count min 200 abs count] offset ] either attempt [ result: send/with 'get %1.1/statuses/home_timeline.json options ] load-result error/connection ] update: func [ "Send Twitter status update" [catch] status [string!] "Status message" /reply "As reply to" id [issue!] "Reply reference" /override ][ override: either override [200][140] unless persona/name error/credentials unless all [0 < length? status override > length? status] error/invalid set message reduce [status id] either attempt [ result: send/with 'post %1.1/statuses/update.json message ] load-result error/connection ] ] context [ ; internals twitter: https://api.twitter.com/ options: context [screen_name: 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] error: [ credentials [throw make error! "User must be authorized to use this application"] connection [throw make error! "Unable to connect to Twitter"] invalid [throw make error! "Status length should be between between 1 and 140"] ] settings: make context [ consumer-key: consumer-secret: user-store: none ] any [ system/script/args system/script/header/settings ] users: any [attempt [load settings/user-store] []] persona: context [ id: name: none token: secret: none ] oauth!: context [ oauth_callback: none oauth_consumer_key: settings/consumer-key oauth_token: oauth_nonce: none oauth_signature_method: "HMAC-SHA1" oauth_timestamp: none oauth_version: 1.0 oauth_verifier: oauth_signature: none ] send: use [make-nonce timestamp sign][ make-nonce: does [ enbase/base checksum/secure join now/precise settings/consumer-key 64 ] timestamp: func [/for date [date!]][ date: any [date now] date: form any [ attempt [to integer! difference date 1-Jan-1970/0:0:0] date - 1-Jan-1970/0:0:0 * 86400.0 ] clear find/last date "." date ] sign: func [ method [word!] lookup [url!] oauth [object! block! none!] params [object! block! none!] /local out ][ out: copy "" oauth: any [oauth make oauth! []] oauth/oauth_nonce: make-nonce oauth/oauth_timestamp: timestamp oauth/oauth_token: persona/token params: make oauth any [params []] params: sort/skip third params 2 oauth/oauth_signature: enbase/base checksum/secure/key rejoin [ uppercase form method "&" url-encode form lookup "&" url-encode replace/all to-webform params "+" "%20" ] rejoin [ settings/consumer-secret "&" any [persona/secret ""] ] 64 foreach [header value] third oauth [ if value [ repend out [", " form header {="} url-encode form value {"}] ] ] join "OAuth" next out ] send: func [ [catch] method [word!] lookup [file!] /auth oauth [object!] /with params [object!] ][ lookup: twitter/:lookup oauth: make oauth! any [oauth []] if object? params [params: third params] switch method [ put delete [ params: compose [method: (uppercase form method) (any [params []])] method: 'post ] ] switch method [ get [ method: compose/deep [ header [Authorization: (sign 'get lookup oauth params)] ] if params [ params: context sort/skip params 2 append lookup to-webform/prefix params ] ] post put delete [ method: compose/deep [ (method) (either params [to-webform params][""]) [ Authorization: (sign method lookup oauth params) Content-Type: "application/x-www-form-urlencoded" ] ] ] ] lookup: read/custom lookup method ] ] register: use [request-broker access-broker verification-page][ request-broker: %oauth/request_token verification-page: %oauth/authorize?oauth_token= access-broker: %oauth/access_token func [ /requester request [function!] /local response verifier ][ request: any [:request :ask] set persona none response: load-webform send/auth 'post request-broker make oauth! [ oauth_callback: "oob" ] persona/token: response/oauth_token persona/secret: response/oauth_token_secret browse join twitter/:verification-page response/oauth_token unless verifier: request "Enter your PIN from Twitter: " [ make error! "Not a valid PIN" ] response: load-webform send/auth 'post access-broker make oauth! [ oauth_verifier: trim/all verifier ] persona/id: to-issue response/user_id persona/name: response/screen_name persona/token: response/oauth_token persona/secret: response/oauth_token_secret persona ] ] ]