Working twitter-client+oauth example code ?

Ough… just wasted many hours trying to make Corona’s official twitter demo to work at:

http://developer.anscamobile.com/content/twitter

only to find out that is uses basic auth which twitter stopped supporting many, many months ago.

Ansca, please remove that page!!!

Maybe I should just stop reading the official docs and go to the forums straight away… :frowning:

Now trying to catch up with other efforts to make oauth work with twitter at:
http://developer.anscamobile.com/code/oauth-library

but that thread kind of stopped just before any simple working twitter client code was posted…

Did anyone get that to work?
Any working example code ?

Any “official” Ansca-news on *real* support for twitter/oauth/ssl ?

Appreciate any pointers/suggestions.

-FrankS.
[import]uid: 8093 topic_id: 8375 reply_id: 308375[/import]

Also waiting for an official answer, my purchase of Corona SDK will depend on this, basically.

Andrea S. [import]uid: 27774 topic_id: 8375 reply_id: 36294[/import]

[lua]require(“oAuth”)

consumer_key = “yourkeyfromtwitter”
consumer_secret = “yoursecretfromtwitter”
local access_token
local access_token_secret
local user_id
local screen_name

local twitter_request = (oAuth.getRequestToken(consumer_key, “Wesbiteurl, matching your callback url”, “http://twitter.com/oauth/request_token”, consumer_secret))
local twitter_request_token = twitter_request.token
local twitter_request_token_secret = twitter_request.token_secret

local function listener(event)
print(“listener”)
local remain_open = true
local url = event.url

if url:find(“oauth_token”) then

url = url:sub(url:find("?") + 1, url:len())

local authorize_response = responseToTable(url, {"=", “&”})
remain_open = false

local access_response = responseToTable(oAuth.getAccessToken(authorize_response.oauth_token, authorize_response.oauth_verifier, twitter_request_token_secret, consumer_key, consumer_secret, “https://api.twitter.com/oauth/access_token”), {"=", “&”})

access_token = access_response.oauth_token
access_token_secret = access_response.oauth_token_secret
user_id = access_response.user_id
screen_name = access_response.screen_name
– API CALL:

local params = {}
params[1] =
{
key = ‘status’,
value = “I just scored " … score … " on a Corona made app!”
}

request_response = oAuth.makeRequest(“http://api.twitter.com/1/statuses/update.json”, params, consumer_key, access_token, consumer_secret, access_token_secret, “POST”)
print("req resp ",request_response)
end

return remain_open
end
–this is your webpopup, change position/size as you wish
function tweetit (event)
native.showWebPopup(10, 10, 460, 360, “http://api.twitter.com/oauth/authorize?oauth_token=” … twitter_request_token, {urlRequest = listener})
end

function responseToTable(str, delimeters)
local obj = {}

while str:find(delimeters[1]) ~= nil do
if #delimeters > 1 then
local key_index = 1
local val_index = str:find(delimeters[1])
local key = str:sub(key_index, val_index - 1)

str = str:sub((val_index + delimeters[1]:len()))

local end_index
local value

if str:find(delimeters[2]) == nil then
end_index = str:len()
value = str
else
end_index = str:find(delimeters[2])
value = str:sub(1, (end_index - 1))
str = str:sub((end_index + delimeters[2]:len()), str:len())
end
obj[key] = value
print(key … “:” … value)
else

local val_index = str:find(delimeters[1])
str = str:sub((val_index + delimeters[1]:len()))

local end_index
local value

if str:find(delimeters[1]) == nil then
end_index = str:len()
value = str
else
end_index = str:find(delimeters[1])
value = str:sub(1, (end_index - 1))
str = str:sub(end_index, str:len())
end
obj[#obj + 1] = value
print(value)
end
end
return obj
end

local twitterButton = display.newImage (“twitter.png”)
twitterButton.x = 310
twitterButton.y = 280
localGroup:insert(twitterButton)

twitterButton:addEventListener(“tap”, tweetit)[/lua]

This is the exact code (minus personal info) I used less than a week ago in Pixel Slice (approved today) and works perfectly.

That help? :wink: [import]uid: 52491 topic_id: 8375 reply_id: 36571[/import]

@Peach, on line 10 it says “Wesbiteurl, matching your callback url” what does that exactly mean?

I have a few questions;

  1. Do I have to have my own website or can I just pick any or what does that do?
  2. I’ve read something on the forum about storing a key or something on my website database, is that key some sort of validation of my app/account for twitter so it’s not fake or spam?
  3. Where should I store that key, can I just add it within some html or as a text doc in some folder?
  4. When I have my twitter key and secret inserted in the code, is this code fully functional then?

Thanks Peach, your posts are always very helpful.
David
[import]uid: 34126 topic_id: 8375 reply_id: 36574[/import]

Hey holmes,

When you create your app (on the Twitter end) you will need to enter a callback URL; I used http://techority.com/ which is exactly what I put in line 10 above in my own app.

You should use your own website; I assume you have one as Apple requires it? (This might have changed since I joined up.)

At the end of the day it doesn’t make a difference because the pop up closes rather than forwarding; no one actually sees the site.

Question 2:
I didn’t have to do anything to my website so this is either outdated or simply not compulsory as I was not, at any point, required to do it - yet my posting works fine :slight_smile:

Question 3:
If referring to the “key” from Q2, NA.

Question 4:
Yes, just make sure you change the URL as well :slight_smile: [import]uid: 52491 topic_id: 8375 reply_id: 36644[/import]

I used your code and downloaded the oAuth.lua from the code exchange but I get this error with just a blank (black) screen in the simulator;

...cuments/Developer/CoronaSDK/Projects/theGame/menu.lua:39: attempt to call field 'getRequestToken' (a nil value)  
stack traceback:  
 [C]: in function 'getRequestToken'  
 ...cuments/Developer/CoronaSDK/Projects/theGame/menu.lua:39: in main chunk  
 [C]: in function 'require'  
  

Then I tried Trollapps oAuth version and got this error after pressing my twitter button, What am I doing wrong and what oAuth version did you use?

Runtime error ...cuments/Developer/CoronaSDK/Projects/theGame/menu.lua:265: attempt to concatenate upvalue 'twitter\_request\_token' (a nil value) stack traceback: [C]: ? ...cuments/Developer/CoronaSDK/Projects/theGame/menu.lua:265: in function <...cuments><br> ?: in function <?:214><br>

edit;
Forgot to mention, I use director for my project. But I think you used trollapps oAuth.lua as it looks after comparing the code. I don’t understand what my problem is, my code is exactly like yours and I added key, tokens etc in the right place too. I switched back to trollapps oAuth code. [import]uid: 34126 topic_id: 8375 reply_id: 36709[/import] </…cuments>

I had some issues too with twitter, when you set up your app on twitter dev make sure you check the Application Type as “Browser” and register your callback URL. That fixed my problems.

So callbackURL = Your website.

I tried Peach’s code and it works fine just make sure you do what I mentioned above.

Hope it helped.
lano78

[import]uid: 13560 topic_id: 8375 reply_id: 36723[/import]

I’m getting the same error: attempt to call field ‘getRequestToken’ (a nil value)
I’m using Director. At first I thought I was getting the error because it would only work on the device, but in the device things freeze when it’s time to execute the code. Xcode shows nothing at all (very weird)
[import]uid: 10426 topic_id: 8375 reply_id: 36731[/import]

I’m not getting any explicit error, but I get redirected to the website that I specified as the callback url…

Not sure what goes wrong there.

I get the twitter webpage asking me whether I grant permission to my app, when I pass the correct username/password for twitter, I get redirected to my own website (?) instead of actually tweeting.

When I add some debugging code, I can see that I never get passed the “if url:find(“oauth_token”) then” test in “local function listener(event)”, while the url equals “https://api.twitter.com/oauth/authorize”.

Furthermore, when I go to my twitter account I can see that I did authorized my app.

It seems that almost all goes well, except the final authorization redirection…

Any suggestions what may be wrong?

As far as I can see I literally copied both oAuth.lua and Pellen’s code, and only filled-in the customer key&secret and website url.

Thanks, FrankS.
[import]uid: 8093 topic_id: 8375 reply_id: 36741[/import]

My test works fine with trollapps oAuth module and I even tried it with director and it’s fine there too. One error though is when the user decline to use twitter, then the web popup doesn’t terminate.

Have you guys tried to set the app as a browser app on the twitter dev site? Also set up a callback URL at the dev site too, that fixed my problem and don’t forget to add that URL to your apps settings page on twitter.

I get redirected back to my app after login, don’t know why you get sent to your URL?

@lano78 [import]uid: 13560 topic_id: 8375 reply_id: 36766[/import]

As lano said, double and then triple check your settings.

Below is the entirety of the oAuth.lua file I’m using; if you are certain you have zero errors in your code then check to make sure your oAuth.lua matches up.

[lua]module(…,package.seeall)

local http = require(“socket.http”)
local ltn12 = require(“ltn12”)
local crypto = require(“crypto”)
local mime = require(“mime”)
–/////////////////////////////////////////////////////////////////////////////////////
–// GET REQUEST TOKEN
–/////////////////////////////////////////////////////////////////////////////////////
function getRequestToken(consumer_key, token_ready_url, request_token_url, consumer_secret)

local post_data =
{
oauth_consumer_key = consumer_key,
oauth_timestamp = get_timestamp(),
oauth_version = ‘1.0’,
oauth_nonce = get_nonce(),
oauth_callback = token_ready_url,
oauth_signature_method = “HMAC-SHA1”
}

local post_data = oAuthSign(request_token_url, “POST”, post_data, consumer_secret)

local result = rawPostRequest(request_token_url, post_data)
local token = result:match(‘oauth_token=([^&]+)’)
local token_secret = result:match(‘oauth_token_secret=([^&]+)’)

return
{
token = token,
token_secret = token_secret
}

end
–/////////////////////////////////////////////////////////////////////////////////////
–// GET ACCESS TOKEN
–/////////////////////////////////////////////////////////////////////////////////////
function getAccessToken(token, verifier, token_secret, consumer_key, consumer_secret, access_token_url)

local post_data =
{
oauth_consumer_key = consumer_key,
oauth_timestamp = get_timestamp(),
oauth_version = ‘1.0’,
oauth_nonce = get_nonce(),
oauth_token = token,
oauth_token_secret = token_secret,
oauth_verifier = verifier,
oauth_signature_method = “HMAC-SHA1”

}
local post_data = oAuthSign(access_token_url, “POST”, post_data, consumer_secret)
local result = rawPostRequest(access_token_url, post_data)
return result
end
–/////////////////////////////////////////////////////////////////////////////////////
–// MAKE REQUEST
–/////////////////////////////////////////////////////////////////////////////////////
function makeRequest(url, body, consumer_key, token, consumer_secret, token_secret, method)

local post_data =
{
oauth_consumer_key = consumer_key,
oauth_nonce = get_nonce(),
oauth_signature_method = “HMAC-SHA1”,
oauth_token = token,
oauth_timestamp = get_timestamp(),
oauth_version = ‘1.0’,
oauth_token_secret = token_secret
}
for i=1, #body do
post_data[body[i].key] = body[i].value
end
local post_data = oAuthSign(url, method, post_data, consumer_secret)

local result

if method == “POST” then
result = rawPostRequest(url, post_data)
else
result = rawGetRequest(post_data)
end

return result
end
–/////////////////////////////////////////////////////////////////////////////////////
–// OAUTH SIGN
–/////////////////////////////////////////////////////////////////////////////////////
function oAuthSign(url, method, args, consumer_secret)

local token_secret = args.oauth_token_secret or “”

args.oauth_token_secret = nil

local keys_and_values = {}

for key, val in pairs(args) do
table.insert(keys_and_values,
{
key = encode_parameter(key),
val = encode_parameter(val)
})
end

table.sort(keys_and_values, function(a,b)
if a.key < b.key then
return true
elseif a.key > b.key then
return false
else
return a.val < b.val
end
end)

local key_value_pairs = {}

for _, rec in pairs(keys_and_values) do
table.insert(key_value_pairs, rec.key … “=” … rec.val)
end

local query_string_except_signature = table.concat(key_value_pairs, “&”)

local sign_base_string = method … ‘&’ … encode_parameter(url) … ‘&’ … encode_parameter(query_string_except_signature)

local key = encode_parameter(consumer_secret) … ‘&’ … encode_parameter(token_secret)
local hmac_binary = sha1(sign_base_string, key, true)

local hmac_b64 = mime.b64(hmac_binary)
local query_string = query_string_except_signature … ‘&oauth_signature=’ … encode_parameter(hmac_b64)

if method == “GET” then
return url … “?” … query_string
else
return query_string
end
end
–/////////////////////////////////////////////////////////////////////////////////////
–// ENCODE PARAMETER
–/////////////////////////////////////////////////////////////////////////////////////
function encode_parameter(str)
return str:gsub(’[^-%._~a-zA-Z0-9]’,function©
return string.format("%%%02x",c:byte()):upper()
end)
end
–/////////////////////////////////////////////////////////////////////////////////////
–// SHA 1
–/////////////////////////////////////////////////////////////////////////////////////
function sha1(str,key,binary)
binary = binary or false
return crypto.hmac(crypto.sha1,str,key,binary)
end
–/////////////////////////////////////////////////////////////////////////////////////
–// GET NONCE
–/////////////////////////////////////////////////////////////////////////////////////
function get_nonce()
return mime.b64(crypto.hmac(crypto.sha1,tostring(math.random()) … “random” … tostring(os.time()),“keyyyy”))
end
–/////////////////////////////////////////////////////////////////////////////////////
–// GET TIMESTAMP
–/////////////////////////////////////////////////////////////////////////////////////
function get_timestamp()
return tostring(os.time() + 1)
end
–/////////////////////////////////////////////////////////////////////////////////////
–// RAW GET REQUEST
–/////////////////////////////////////////////////////////////////////////////////////
function rawGetRequest(url)
local r,c,h
local response = {}

r,c,h = http.request
{
url = url,
sink = ltn12.sink.table(response)
}

return table.concat(response,"")
end
–/////////////////////////////////////////////////////////////////////////////////////
–// RAW POST REQUEST
–/////////////////////////////////////////////////////////////////////////////////////
function rawPostRequest(url, rawdata)

local r,c,h
local response = {}

r,c,h = http.request
{
url = url,
method = “POST”,
headers =
{
[“Content-Type”] = “application/x-www-form-urlencoded”,
[“Content-Length”] = string.len(rawdata)
},
source = ltn12.source.string(rawdata),
sink = ltn12.sink.table(response)
}

return table.concat(response,"")
end[/lua]

Peach :slight_smile: [import]uid: 52491 topic_id: 8375 reply_id: 36783[/import]

Hey Peach,

Did you have any issues with termination of the web Popup when user decline to authorize the app?
When I decline, I get his error at the top of the popup right under the header that says like;

  • oops, something went terribly wrong, visit twitter.com for more info.

Then there’s no way to close the popup after that error, I’m thinking of just adding a “exit button” at the top corner somewhere so I can just exit if I want to.

But anyways, I guess that error is on the twitter side of the login as we can’t do anything to that part unless we make our logins 100% custom with our own php/html and everything…

lano78
[import]uid: 13560 topic_id: 8375 reply_id: 36792[/import]

Hey lano,

I had not previously checked this; you’re right. It wont close on cancel. (For testing it seems you can click “sign up to Twitter” in the top right to close it, however naturally users would not know (nor should they have to) to do that.

Grrrr!

Peach [import]uid: 52491 topic_id: 8375 reply_id: 36799[/import]

Ough… i got it to work…

There are two “errors” in Pellen’s example code that won’t make it work:

The first one was easy to find:
you have to comment/delete
[lua]–localGroup:insert(twitterButton)[/lua]

I got that one easily as the Corona compiler complained.

The second one was more obfuscated as Lua/Corona does not help you find undeclared global vars in the test tweet: “I just scored " … score … " on a Corona made app!”

The var “score” is undefined or nil, which makes the string concatenation fail…

but because it’s inside of this native.showWebPopup, the error doesn’t show up on any console, and the app kind of crashes inside the listener meaning that the listener stops where the error occurred but the app seems to recover and just continues more or less as if nothing happened - that’s why the function never returned remain_open because the exception happened before returning… therefor the http-redirection continued…

Anyway… I can see the tweet now - thanks for the code.

-FrankS.

[import]uid: 8093 topic_id: 8375 reply_id: 36827[/import]

I got this to work too!!

But what is up with the cancel thingy? I can’t close the popup if I press the No,thanks button. Oh, so i’m not the only one with that issue?

How do I fix that, so the popup exits when I push that button?

Thanks,
David [import]uid: 34126 topic_id: 8375 reply_id: 37067[/import]

I have added a cancel-button that I instantiate in the “tweetit” handler like:

[lua] cancelB = ui.newButton{
default=“cancel.png”,x=150,y=450,size=9,
onPress = function(e) native.cancelWebPopup(); cancelB:removeSelf() end
}[/lua]

and I added [lua]cancelB:removeSelf()[/lua] to the “listener” handler right after [lua]remain_open = false[/lua].

That will show you the cancel-button underneath the webpopup, and will discard it when the webpopup is discarded/cancelled.

… the only issue is that the app will crash when I cancel right after I bring-up the webpopup for the first time, but the button works correct the second time the webpopup is shown… ough…

Redefining the onPress handler in the newButton() as:

[lua] cancelB = ui.newButton{default=“cancel.png”,x=150,y=450,size=9,
onPress = function(e)
native.cancelWebPopup()
timer.performWithDelay(1, function(e2) cancelB:removeSelf() end, 1)
end}[/lua]

makes all work well again… but I shouldn’t have to postpone the removeSelf()…

Could someone please confirm this weird behavior for the “self destruct” of the cancel-button?

Thanks, Frank.
[import]uid: 8093 topic_id: 8375 reply_id: 37088[/import]

hey Peach, would you mind posting the source of this oAuth file you are using…it seems there are several out there and I want to be able to check versions/errors at the horses mouth…thanks for working all of this out for us… [import]uid: 6175 topic_id: 8375 reply_id: 39906[/import]

Hey,

I don’t have the original source I am afraid - obviously you can get the file itself off Techority, however it was originally sent to me by a buddy and I believe it was sent to him by a friend of his - as such I’m not entirely sure of its origins.

Apologies!

Peach [import]uid: 52491 topic_id: 8375 reply_id: 39940[/import]

This is great! Thanks!

After some head-scratching, I managed to get twitter working smoothly using this code. ( http://techority.com/2011/05/26/post-to-twitter-from-your-application/ )

For those who cannot get it to work, here are two simple and stupid pitfalls I fell into:

* Make sure your twitter app has permissions to “Read & Write”. It defaults to Read only. I felt pretty stupid not catching that one at first.

* Make sure you a trailing slash to your callback url in the lua code - “http://www.example.com/” worked for me, but not “http://www.example.com

Good luck!
[import]uid: 13935 topic_id: 8375 reply_id: 44950[/import]

Thanks for sharing those pointers mark, very important and VERY easy to overlook :slight_smile:

Peach [import]uid: 52491 topic_id: 8375 reply_id: 44956[/import]