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... :-(

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.

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

Andrea S.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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)

@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

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 :)

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

Question 4:
Yes, just make sure you change the URL as well :)

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;

1
2
3
4
5
...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'

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

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)

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.

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

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
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(c)
                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

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

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

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

1
--localGroup:insert(twitterButton)

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

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

1
2
3
4
        cancelB = ui.newButton{
                default="cancel.png",x=150,y=450,size=9, 
                onPress = function(e) native.cancelWebPopup(); cancelB:removeSelf() end
        }

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

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

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!

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

Peach

Hi,
I'm not sure if I'm doing something wrong..
Anyway, I'm testing Twitter posting from my app and I've used the code on technority.
It works, and I can post message to twitter. However, every time I relaunch the app and post to twitter it's showing me the "Authorize ... to use your account?".

Shouldn't the authorization phase just be the first time?

Do we need to save some data to enable the app to be authorized and don't require the same authorization dialog every time ?

Or.. Am i doing something wrong?

Thanks ;)
Ruben

I believe I have had to do that as well, I haven't actually noticed - someone else may be able to shed some light.

Peach :)

ARGH! It turns out, the oauth.lua offered here:
http://developer.anscamobile.com/code/oauth-library

is NOT AT ALL THE SAME as the oAuth.lua I found here:
http://techority.com/2011/05/26/post-to-twitter-from-your-application/

The second one is the right one. The first one fails! The posting at:
http://developer.anscamobile.com/code/oauth-library
should be removed! Or the library fixed! And, the library should be easily found on the Corona website!

Argh, I say, again!

So basically, after the user authorizes my application using the webpopup, I need to save the access_token and the access_token_secret in my app, and later, when I want to post to twitter I'm simply using oAuth.makeRequest ?

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

I cannot seem to find the setting that insures that my app is a browser app vs a native app. Any ideas or advice?

Twitter ended up just changing the UI with the relaunch so that this is no longer an explicit setting. In fact, as long as you specify a callback URL (which you do in the "Settings" tab of "My applications"), then you've implicitly set your application type as browser. Even if you're callback URL is just a placeholder.

This has not, however, resolved the lingering popup or lack of tweet.

Could you further clarify how you are saving your keys and on which conditions? Thanks :)

This makes three posts in a row. I'm sorry.

views:2697 update:2011/9/27 18:14:54
corona forums © 2003-2011