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
]
]
]