Join us in Phaze Demesnes

LSL Script Library Home   Add a script Show All
Category: Contributor: Description
Twitter Babbage Linden Twiiter Library
The Twitter OAuth Library allows scripted objects in Second Life to update the Twitter status streams of residents in Second Life interacting with the objects.
Using the Twitter OAuth Library

The Twitter OAuth Library allows scripted objects in Second Life to update the Twitter status streams of residents in Second Life interacting with the objects. A dance machine might send an update saying that a resident is dancing at at a particular location in Second Life, a vendor might send an update saying that a resident has bought a particular item, an arena might say that a resident has won or lost a game. These status updates can be seen by the resident's followers on Twitter and can include SLURLs back in to Second Life, allowing followers on Twitter to jump in to Second Life, whether they are a resident or not.

By using OAuth, the Twitter OAuth Library avoids the need for residents to share their Twitter username and password with anyone other than twitter.com (the password anti-pattern) and gives them fine grained control over which Second Life objects are able to send updates to twitter. The Twitter OAuth Library also uses the HTTP-In and HTTP-Out facilities to communicate directly with Twitter, avoiding the need to use a web server to intermediate communication between Second Life and Twitter. If you have an object in Second Life, you can use theTwitter OAuth library to make it send updates to Twitter. A video of the Twitter OAuth Library in use is here: http://www.youtube.com/watch?v=_19cl8qOZKA

The Twitter OAuth Library is designed to be easy to integrate with existing Second Life objects even for those with little scripting experience. To add Twitter integration to your Second Life object, follow these instructions:
Setting Up Your Twitter Application

   1. Set up a Twitter account and log in to Twitter.
   2. Go to http://twitter.com/oauth_clients
   3. Click the Register a new application link.
   4. Set Default access type to "Read & Write".
   5. Set Application Type to "Browser".
   6. Set callback URL to http://example.com/we-use-dynamic-urls/
   7. Leave use Twitter for login unchecked.
   8. Make sure you fill the rest of the form in or you application may not work.
   9. Once you have successfully registered your application, Twitter will take you to the Application Details page for your new application. 

Setting Up The Twitter OAuth Library

   1. Get a copy of the Twitter OAuth Library from Ambleside or Xstreet.
   2. Drag the Twitter OAuth Library in from your inventory on to your land.
   3. Right click on the Twitter OAuth Library, edit it and click on the Content tab.
   4. Drag the scripts from the Twitter OAuth Library box to your inventory.
   5. Rez your second life object in world.
   6. Right click on your object edit it and click on on the Content tab.
   7. Drag the Twitter OAuth scripts from your inventory in to your second life object.
   8. Double click on the TwitterOAuthClient script to edit it.
   9. Replace YOUR CONSUMER KEY HERE with the Consumer key from your Twitter Application Details page.
  10. Replace YOUR CONSUMER SECRET HERE with the Consumer secret from your Twitter Application Details page.
  11. Replace I did something amazing in Second Life here with the message you would like to send from your Second Life object.
  12. Click the save button, close the script editor and close the edit floater. 

Testing The Twitter OAuth Library

   1. Touch your second life object to test your Twitter application settings.
   2. A popup should appear asking you to "Authorise Twitter access", click the "Go to page button".
   3. You should be directed back to a page on Twitter saying "An application would like to connect to your account".
   4. Fill in your Twitter user name and password if your are not already signed in to Twitter (note these details only go to twitter.com) then click the green "Allow" button.
   5. Check your Twitter stream now contains the message you wanted to send followed by a SLURL from your twitter application.
   6. Once you have authorized the application to use Twitter, touching the box again will send a new update to Twitter (but be aware, multiple identical status updates will not appear).
   7. If the test fails at any point, touch the Twitter OAuth Library box again to retry. 

Next Steps

Rather than sending updates to Twitter every time your object is touched, you will probably want to send updates when other interactions take place. To do this, just copy the first part of TwitterOAuthClient script up to "// STOP COPYING HERE" to the top of your script, then call TwitterOAuthInit when your script is initialized in state_entry and on_rez and TwitterOAuthUpdateStatus whenever you want your script to send an update to Twitter. Note that you should always ask a resident whether it's OK to post an update to Twitter before calling TwitterOAuthUpdateStatus. Although the Twitter OAuth library is designed to talk to Twitter out of the box, it is a fully compliant OAuth 1.0a consumer library and so can be easily modified to talk to any web service that supports OAuth integration.
Twitter OAuth Example Client

Download this script - Please use this link to get this script. If you see all the code on one long line, please use Wordpad or another editor, such as LSLEdit.exe. The .LSL file you will download is an ordinary text file.

1 //////////////////////////////////////////////////////////////////////////////////////
2 //
3 // Twitter OAuth Client 1.0: An example client that uses the LSL OAuth 1.0a
4 // Library for Twitter by Babbage Linden.
5 //
6 // Released under the Creative Commons Creative Commons Attribution-Share Alike 3.0
7 // license http://creativecommons.org/licenses/by-sa/3.0/
8 //
9 //////////////////////////////////////////////////////////////////////////////////////
10
11 // Application constants generated by Twitter.
12 // Set up a new Twitter application here: http://twitter.com/oauth_clients
13 string TWITTER_OAUTH_CONSUMER_KEY = "YOUR CONSUMER KEY HERE";
14 string TWITTER_OAUTH_CONSUMER_SECRET = "YOUR CONSUMER SECRET HERE";
15
16 // Message constants defined by Twitter OAuth library.
17 integer TWITTER_OAUTH_SET_CONSUMER_KEY = 999000;
18 integer TWITTER_OAUTH_SET_CONSUMER_SECRET = 999001;
19 integer TWITTER_OAUTH_SET_MAX_RETRIES = 999002;
20 integer TWITTER_OAUTH_UPDATE_STATUS = 999003;
21
22 TwitterOAuthInit()
23 {
24 // Set up Twitter OAuth library, using application consumer key and secret generated by Twitter.
25 llMessageLinked(LINK_THIS, TWITTER_OAUTH_SET_CONSUMER_KEY, TWITTER_OAUTH_CONSUMER_KEY, NULL_KEY);
26 llMessageLinked(LINK_THIS, TWITTER_OAUTH_SET_CONSUMER_SECRET, TWITTER_OAUTH_CONSUMER_SECRET, NULL_KEY);
27 llMessageLinked(LINK_THIS, TWITTER_OAUTH_SET_MAX_RETRIES, "10", NULL_KEY);
28 }
29
30 string TwitterOAuthGetSLURL()
31 {
32 string globe = "http://maps.secondlife.com/secondlife";
34 vector pos = llGetPos();
35 string posx = (string) llRound(pos.x);
36 string posy = (string) llRound(pos.y);
37 string posz = (string) llRound(pos.z);
38 return globe + "/" + llEscapeURL(region) +"/" + posx + "/" + posy + "/" + posz;
39 }
40
41 string TwitterOAuthBuildMessage()
42 {
43 // Example message, change this as appropriate.
44 return "I did something amazing in Second Life here " + TwitterOAuthGetSLURL() + " #inSL";
45 }
46
47 TwitterOAuthUpdateStatus(string message, key avatar)
48 {
49 llMessageLinked(LINK_THIS, TWITTER_OAUTH_UPDATE_STATUS, message, avatar);
50 }
51
52 // STOP COPYING HERE
53
54 default
55 {
57 {
58 TwitterOAuthInit();
59 }
60
61 on_rez(integer param)
62 {
63 TwitterOAuthInit();
64 }
65
66 touch_start(integer total_number)
67 {
68 TwitterOAuthUpdateStatus(TwitterOAuthBuildMessage(), llDetectedKey(0));
69 }
70 }
Twitter OAuth Library

Download this script - Please use this link to get this script. If you see all the code on one long line, please use Wordpad or another editor, such as LSLEdit.exe. The .LSL file you will download is an ordinary text file.

1 //////////////////////////////////////////////////////////////////////////////////////
2 //
3 // Twitter OAuth Lib 1.0: An LSL OAuth 1.0a Library for Twitter by Babbage Linden.
4 // Built with Cale Flanagan's LSL HMAC-SHA1 implementation and Strife Onizuka's
5 // LGPL Combined Library and with help from Latif Khalifa.
6 //
7 // This library is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU Lesser General Public License
9 // as published by the Free Software Foundation;
10 // version 3 of the License.
11 //
12 // This library is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Lesser General Public License for more details.
16 //
17 // You should have received a copy of the GNU Lesser General Public License
18 // along with this library. If not, see <http://www.gnu.org/licenses/>
19 // or write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
20 // Boston, MA 02111-1307 USA
21 //
22 //////////////////////////////////////////////////////////////////////////////////////
23
24 // Library protocol constants.
25 integer TWITTER_OAUTH_SET_CONSUMER_KEY = 999000;
26 integer TWITTER_OAUTH_SET_CONSUMER_SECRET = 999001;
27 integer TWITTER_OAUTH_SET_MAX_RETRIES = 999002;
28 integer TWITTER_OAUTH_UPDATE_STATUS = 999003;
29
30 // Library state.
31 string gConsumerKey = "";
32 string gConsumerSecret = "";
33 integer gMaxRetries = 10;
34
35 // Request state.
36 key gAvatarKey;
37 string gMessage;
38 key gRequestTokenKey;
39 string gRequestToken;
40 string gRequestTokenSecret;
41 key gAccessTokenKey;
42 string gVerifier;
43 string gAuthorizeUrl = "oob";
44 integer gRetries;
45
46 string TrimRight(string src, string chrs)//LSLEditor Unsafe, LSL Safe
47 {
49 do ; while(~llSubStringIndex(chrs, llGetSubString(src, i = ~-i, i)) && i);
50 return llDeleteSubString(src, -~(i), 0x7FFFFFF0);
51 }
52
53 string WriteBase64Integer(string data, integer index, integer value)
54 {
55
56 integer S = 12 - ((index % 3) << 1);
57 return llDeleteSubString(
59 data,
60 index = ((index << 4) / 3),
63 (llBase64ToInteger(llGetSubString((data = llGetSubString(data, index, index+7)) + "AAAAAA", 0, 5)) & (0xFFF00000 << S)) |
64 ((value >> (12 - S)) & ~(0xFFF00000 << S))
65 ), 2,
67 (llBase64ToInteger(llGetSubString((llDeleteSubString(data, 0, 1)) + "AAAAAA", 0, 5)) & ~(0xFFFFFFFF << S)) |
68 (value << S)
69 ) ) ), index+7, index + 22);//insert it then remove the old and the extra.
70 }
71
72 string DwordListToBase64(list a)
73 {
74 integer len = (a != []);
75 integer i = -1;
76 string out;
77 while((i = -~i) < len)
78 out = WriteBase64Integer(out, i, llList2Integer(a, i));
79 return TrimRight(out,"A");
80 }
81
82 // Takes a dwordblock, adds a string and puts it padded for blocksize into a dwordlist
83 // returns sha1blocks
84 list PrepareShortkey(string s)
85 {
86 integer v = 0;
88 integer n = cnt;
89 list dw_skey;
90
91 for (n = 0; n < cnt; n++)
92 {
93 v = v | 0xFF & llBase64ToInteger("AAAA" + llStringToBase64(llGetSubString(s, n, n)));
94 if (n % 4 == 3)
95 {
96 dw_skey += [v];
97 v = 0;
98 }
99 else
100 {
101 v = v << 8;
102 }
103 }
104
105 //pad 0s (could be done dword-wise, after filling up to boundary, later, maybe...)
106 for ( ; n < 64; n++)
107 {
108 if (n % 4 == 3)
109 {
110 dw_skey += [v];
111 v = 0;
112 }
113 else
114 {
115 v = v << 8;
116 }
117 }
118 dw_skey += [v];
119
120 return dw_skey;
121 }
122
123 // Takes a dwordblock, adds a string and puts it padded for blocksize into a dwordlist
124 // returns sha1blocks
125 list PrepareSha1Blocks(list dwords, string s)
126 {
127 integer v = 0;
129 integer n = cnt;
130 integer mcnt = cnt;
131 list shablocks = dwords;
132
133 mcnt = cnt + (dwords != []) * 4; // add up the dword-data (total message length)
134
135 for (n = 0; n < cnt; n++)
136 {
137 v = v | 0xFF & llBase64ToInteger("AAAA" + llStringToBase64(llGetSubString(s, n, n)));
138 if (n % 4 == 3)
139 {
140 shablocks += [v];
141 v = 0;
142 }
143 else
144 {
145 v = v << 8;
146 }
147 }
148
149 // pad a 1 and seven 0's
150 v = v | 0x80;
151 if (n % 4 == 3)
152 {
153 shablocks += [v];
154 v = 0;
155 }
156 else
157 {
158 v = v << 8;
159 }
160 n++;
161
162 //we ignored the dwords silently, but now we have to take them into account
163
164 //how many bytes do we need to fill blocks and have 8 bytes left...
165 cnt = ((mcnt + 8) / 64 + 1) * 64 - 9;
166
167 //pad 0s (could be done dword-wise, after filling up to boundary, later, maybe...)
168 for (n += (dwords != []) * 4 ; n < cnt; n++)
169 {
170 if (n % 4 == 3)
171 {
172 shablocks += [v];
173 v = 0;
174 }
175 else
176 {
177 v = v << 8;
178 }
179 }
180 shablocks += [v];
181
182 // pad message length
183 shablocks += [0]; // we assume not to have more as 16M messagesize (roughly)
184 shablocks += [8 * mcnt];
185
186 return shablocks;
187 }
188
189 // Inner core of sha1 calculation, based on FIPS 180-1
190 // http://www.itl.nist.gov/fipspubs/fip180-1.htm
191 // and some help from http://www.herongyang.com/crypto/message_digest_sha1.html
192 // and a bit from lkalif specialized on dwordlists
193 //
194 // Takes a dwordlist as input and returns hash as dwordlist
195 list ProcessSha1(list dwblocks)
196 {
197 integer block;
198 integer blocks = (dwblocks != []) / 16;
199 integer H0 = 0x67452301;
200 integer H1 = 0xEFCDAB89;
201 integer H2 = 0x98BADCFE;
202 integer H3 = 0x10325476;
203 integer H4 = 0xC3D2E1F0;
204
205 for (block = 0; block < blocks; block++)
206 {
207 list W;
209 integer A = H0;
210 integer B = H1;
211 integer C = H2;
212 integer D = H3;
213 integer E = H4;
214
215 for (t = 0; t < 16; t++)
216 {
217 W += [llList2Integer(dwblocks, t + block * 16)];
218 }
219 for ( ; t < 80; t++)
220 {
221 integer x = llList2Integer(W, t - 3) ^ llList2Integer(W, t - 8) ^ llList2Integer(W, t - 14) ^ llList2Integer(W, t - 16);
222 W += [(x << 1) |!!(x & 0x80000000)]; // borrowed from lkalif
223 }
224
225 for (t = 0; t < 20; t++)
226 {
227 integer TEMP = ((A << 5) | ((A >> 27) & 0x1F)) + ((B & C) | ((~B) & D)) + E + llList2Integer(W, t) + 0x5A827999;
228 E = D; D = C; C = ((B << 30) | ((B >> 2) & 0x3FFFFFFF)); B = A; A = TEMP;
229 }
230 for (; t < 40; t++)
231 {
232 integer TEMP = ((A << 5) | ((A >> 27) & 0x1F)) + (B ^ C ^ D) + E + llList2Integer(W, t) + 0x6ED9EBA1;
233 E = D; D = C; C = ((B << 30) | ((B >> 2) & 0x3FFFFFFF)); B = A; A = TEMP;
234 }
235 for (; t < 60; t++)
236 {
237 integer TEMP = ((A << 5) | ((A >> 27) & 0x1F)) + ((B & C) | (B & D) | (C & D)) + E + llList2Integer(W, t) + 0x8F1BBCDC;
238 E = D; D = C; C = ((B << 30) | ((B >> 2) & 0x3FFFFFFF)); B = A; A = TEMP;
239 }
240 for (; t < 80; t++)
241 {
242 integer TEMP = ((A << 5) | ((A >> 27) & 0x1F)) + (B ^ C ^ D) + E + llList2Integer(W, t) + 0xCA62C1D6;
243 E = D; D = C; C = ((B << 30) | ((B >> 2) & 0x3FFFFFFF)); B = A; A = TEMP;
244 }
245
246 H0 += A;
247 H1 += B;
248 H2 += C;
249 H3 += D;
250 H4 += E;
251 }
252
253 return [H0, H1, H2, H3, H4];
254 }
255
256 //Caveats: Handling of unicode undefined and no message longer 16M allowed
257 list Sha1DWord(list dw, string message)
258 {
259 list sha1blocks = PrepareSha1Blocks(dw, message);
260 list digest = ProcessSha1(sha1blocks);
261 return digest;
262 }
263
264 list dw_key;
265 list dw_ipad;
266 list dw_opad;
267
268 // xor the 64bytes (16 dwords) with value
269 list PreparePad(integer val)
270 {
271 list r;
273
274 for (i = 0; i < 16; )
275 {
276 r += [llList2Integer(dw_key, i++) ^ val];
277 }
278
279 return r;
280 }
281
282 // 2 step design, for simple re-use of key-data
283 HmacInit(string secretkey)
284 {
285 if (llStringLength(secretkey) > 64) // sha1 only if blocksize exceeded
286 dw_key = Sha1DWord([], secretkey) + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
287 else
288 dw_key = PrepareShortkey(secretkey);
289
290 dw_ipad = PreparePad(0x36363636);
291 dw_opad = PreparePad(0x5c5c5c5c);
292 }
293
294 list HmacUpdate(string message)
295 {
296 list dw_ihash = Sha1DWord(dw_ipad, message);
297 list dw_opad2 = dw_opad + dw_ihash;
298
299 return Sha1DWord(dw_opad2, "");
300 }
301
302 integer cacheSize = 25;
303 list tokenCache = [];
304
305 string GetAccessToken(key avatarId)
306 {
307 string result = "";
308 integer index = llListFindList(tokenCache, [avatarId]);
309 if(index != -1)
310 {
311 result = llList2String(tokenCache, index + 1);
312 }
313 return result;
314 }
315
316 string GetAccessTokenSecret(key avatarId)
317 {
318 string result = "";
319 integer index = llListFindList(tokenCache, [avatarId]);
320 if(index != -1)
321 {
322 result = llList2String(tokenCache, index + 2);
323 }
324 return result;
325 }
326
327 SetAccessToken(key avatarId, string accessToken, string accessTokenSecret)
328 {
329 integer stride = 3;
330 integer maxLength = (cacheSize * stride) - stride;
331 if(llGetListLength(tokenCache) > maxLength)
332 {
333 tokenCache = llDeleteSubList(tokenCache, maxLength, -1);
334 }
335 tokenCache = [avatarId, accessToken, accessTokenSecret] + tokenCache;
336 }
337
338 string OAuthUrlEncodeString(string s)
339 {
340 string result = "";
343 for (i = 0; i < count; ++i)
344 {
345 string c = llGetSubString(s, i, i);
346 if(c != "-" && c != "_" && c != "." && c != "," && c != "~")
347 {
348 c = llEscapeURL(c);
349 }
350 result += c;
351 }
352 return result;
353 }
354
355 list OAuthUrlEncodeList(list l)
356 {
357 list result = [];
360 for(i = 0; i < c; ++i)
361 {
362 result += OAuthUrlEncodeString(llList2String(l, i));
363 }
364 return result;
365 }
366
367 string OAuthConcatenate(list l)
368 {
369 string result = "";
371 integer i = 0;
372 while (i < count)
373 {
374 result += llList2String(l, i) + "=" + llList2String(l, i + 1);
375 i += 2;
376 if (i < count)
377 {
378 result += "&";
379 }
380 }
381 return result;
382 }
383
384 string Sign(string method, string url, string consumerSecret, string tokenSecret, list parameters)
385 {
386 string signatureBase = OAuthUrlEncodeString(method) + "&" + OAuthUrlEncodeString(url) + "&" +
387 OAuthUrlEncodeString(OAuthConcatenate(parameters));
388 //llOwnerSay(signatureBase);
389
390 list keyList = [];
391 keyList += consumerSecret;
392 keyList += tokenSecret;
393 string keyString = consumerSecret + "&" + tokenSecret;
394 //llOwnerSay(keyString);
395
396 HmacInit(keyString);
397 list dwSig = HmacUpdate(signatureBase);
398
399 return DwordListToBase64(dwSig) + "=";
400 }
401
402 string OAuthUrl(string method, string url, string consumerKey, string consumerSecret, string token,
403 string tokenSecret, list additionalParams)
404 {
405 integer nonce = (integer)llFrand(1000000);
406 list parameters = ["oauth_version","1.0a",
407 "oauth_nonce", (string)nonce,
408 "oauth_consumer_key", consumerKey,
409 "oauth_signature_method", "HMAC-SHA1",
410 "oauth_timestamp", (string)llGetUnixTime()];
411
412 if(token != "")
413 {
414 parameters += "oauth_token";
415 parameters += token;
416 }
417
418 parameters += additionalParams;
419
420 parameters = OAuthUrlEncodeList(parameters);
421 parameters = llListSort(parameters, 2, 1);
422
423 string sig = Sign(method, url, consumerSecret, tokenSecret, parameters);
424 parameters += "oauth_signature";
425 parameters += sig;
426
427 url = url + "?" + OAuthConcatenate(parameters);
428 //llOwnerSay(url);
429 return url;
430 }
431
432 string FindOAuthResponseValue(string name, list tokens)
433 {
434 //llOwnerSay("Looking for " + name + " in:" + llList2CSV(tokens));
435 integer index = llListFindList(tokens, [name]);
436 if (index != -1)
437 {
438 return llList2String(tokens, index + 1);
439 }
440 return "";
441 }
442
443 list TokenizeOAuthResponse(string response)
444 {
445 return llParseString2List(response, ["=", "&"], []);
446 }
447
448 ResetTimeout()
449 {
450 // Time out HTTP requests and input requests after a minute of inactivity.
452 }
453
454 Reset()
455 {
456 gAvatarKey = NULL_KEY;
457 gMessage = "";
458 gRequestTokenKey = NULL_KEY;
459 gRequestToken = "";
460 gRequestTokenSecret = "";
461 gAccessTokenKey = NULL_KEY;
462 gVerifier = "";
463 gRetries = 0;
465 }
466
467 RequestAccessToken()
468 {
469 list parameters = ["oauth_verifier", gVerifier];
470 string url = OAuthUrl("POST", "http://twitter.com/oauth/access_token",
471 gConsumerKey, gConsumerSecret, gRequestToken, gRequestTokenSecret, parameters);
472 ResetTimeout();
473 gAccessTokenKey = llHTTPRequest(url, [HTTP_METHOD, "POST"], "");
474 }
475
476 RequestRequestToken()
477 {
478 string url = OAuthUrl("GET", "http://twitter.com/oauth/request_token", gConsumerKey, gConsumerSecret, "", "",
479 ["oauth_callback", gAuthorizeUrl]);
480 ResetTimeout();
481 gRequestTokenKey = llHTTPRequest(url, [], "");
482 }
483
484 RequestTokens(key avatar, string message)
485 {
486 string accessToken = GetAccessToken(avatar);
487 string accessTokenSecret = GetAccessTokenSecret(avatar);
488
489 if(accessToken != "" && accessTokenSecret != "")
490 {
491 // Have access token, update immediately.
492 UpdateStatus(accessToken, accessTokenSecret, message);
493 }
494 else
495 {
496 if(gRequestTokenKey != NULL_KEY ||
497 gAccessTokenKey != NULL_KEY)
498 {
499 // Currently requesting access token, drop this request on the floor.
500 // TODO: babbage: queue request for later, handle paralell requests, or signal failure to caller...
501 llOwnerSay("OAuth request in progress, please wait.");
502 return;
503 }
504
505 // Access token unknown and no request in progress, request access token.
506 Reset();
507 gAvatarKey = avatar;
508 gMessage = message;
509 RequestRequestToken();
510 }
511 }
512
513 UpdateStatus(string accessToken, string accessTokenSecret, string message)
514 {
515 list parameters = ["status", message];
516 string url = OAuthUrl("POST", "http://twitter.com/statuses/update.xml",
517 gConsumerKey, gConsumerSecret, accessToken, accessTokenSecret, parameters);
518 llHTTPRequest(url, [HTTP_METHOD, "POST"], "");
519 }
520
521 default
522 {
524 {
526 Reset();
527 }
528
529 on_rez(integer param)
530 {
532 Reset();
533 }
534
535 changed(integer changes)
536 {
537 if((changes & CHANGED_REGION_START) != 0)
538 {
540 }
541 }
542
543 link_message(integer sender_num, integer num, string message, key avatar)
544 {
545 if(num == TWITTER_OAUTH_SET_CONSUMER_KEY)
546 {
547 gConsumerKey = message;
548 }
549 else if(num == TWITTER_OAUTH_SET_CONSUMER_SECRET)
550 {
551 gConsumerSecret = message;
552 }
553 else if(num == TWITTER_OAUTH_SET_MAX_RETRIES)
554 {
555 gMaxRetries = (integer) message;
556 }
557 else if(num == TWITTER_OAUTH_UPDATE_STATUS)
558 {
559 if(gConsumerKey == "")
560 {
561 llOwnerSay("Consumer key must be set before status update.");
562 return;
563 }
564 if(gConsumerSecret == "")
565 {
566 llOwnerSay("Consumer secret must be set before status update.");
567 return;
568 }
569 RequestTokens(avatar, message);
570 }
571 }
572
574 {
575 //llOwnerSay("status:" + (string)status);
576 //llOwnerSay("body:" + body);
577
578 if (id == gRequestTokenKey)
579 {
580 if(status != 200)
581 {
582 if(gRetries++ < gMaxRetries)
583 {
584 llOwnerSay("Failed to obtain oauth request token, retrying...");
585 RequestRequestToken();
586 }
587 else
588 {
589 llOwnerSay("Failed to obtain oauth request token");
590 llOwnerSay("Status:" + (string)status);
591 llOwnerSay("Body:" + body);
592 Reset();
593 }
594 return;
595 }
596
597 gRetries = 0;
598 list responseTokens = TokenizeOAuthResponse(body);
599 gRequestToken = FindOAuthResponseValue("oauth_token", responseTokens);
600 gRequestTokenSecret = FindOAuthResponseValue("oauth_token_secret", responseTokens);
601
602 string url = "http://twitter.com/oauth/authorize?" +
603 OAuthConcatenate(["oauth_token", gRequestToken]);
604 ResetTimeout();
605 llLoadURL(gAvatarKey, "Please authorise Twitter access", url);
606
607 if(gAuthorizeUrl == "oob")
608 {
609 llListen(3, "", NULL_KEY, "");
610 llOwnerSay("Please chat PIN on channel 3 (eg \"/3 12345678\")");
611 }
612 }
613 else if (id == gAccessTokenKey)
614 {
615 if(status != 200)
616 {
617 if(gRetries++ < gMaxRetries)
618 {
619 llOwnerSay("Failed to obtain oauth access token, retrying...");
620 RequestAccessToken();
621 }
622 else
623 {
624 llOwnerSay("Failed to obtain oauth access token");
625 llOwnerSay("Status:" + (string)status);
626 llOwnerSay("Body:" + body);
627 Reset();
628 }
629 return;
630 }
631
632 gRetries = 0;
633 list responseTokens = TokenizeOAuthResponse(body);
634 string accessToken = FindOAuthResponseValue("oauth_token", responseTokens);
635 string accessTokenSecret = FindOAuthResponseValue("oauth_token_secret", responseTokens);
636 string screenName = FindOAuthResponseValue("screen_name", responseTokens);
637 SetAccessToken(gAvatarKey, accessToken, accessTokenSecret);
638 UpdateStatus(accessToken, accessTokenSecret, gMessage);
639 llLoadURL(gAvatarKey, "Show status update?", "http://twitter.com/" + screenName);
640 Reset();
641 }
642 else
643 {
644 // NOTE: babbage: status update always fail as http out cannot accept XML or JSON
645 // TODO: babbage: allow http out to accept XML or JSON, so we can actually check for errors here...
646 return;
647 }
648 }
649
650 listen(integer channel, string name, key id, string message)
651 {
652 if(id == gAvatarKey && channel == 3)
653 {
654 // PIN based authorization flow.
655 gVerifier = message;
656 RequestAccessToken();
657 }
658 }
659
661 {
662 if(method == "URL_REQUEST_GRANTED")
663 {
664 // NOTE: babbage: need trailing / path...
665 gAuthorizeUrl = body + "/";
666 }
667 else if(method == "URL_REQUEST_DENIED")
668 {
669 llOwnerSay("No URLs available, using PIN based flow.");
670 gAuthorizeUrl = "oob";
671 }
672 else if(method == "GET")
673 {
674 list responseTokens = TokenizeOAuthResponse(llGetHTTPHeader(id, "x-query-string"));
675 gVerifier = FindOAuthResponseValue("oauth_verifier", responseTokens);
676
677 RequestAccessToken();
678
679 // TODO: babbage: allow HTML response body, so we can show something more useful than a blank web page here...
680 llHTTPResponse(id, 200, "");
681 }
682 }
683
684 timer()
685 {
686 llOwnerSay("Request timeout, resetting...");
687 Reset();
688 }
689 }