Jump to content


Photo

[Lib] OAuth 1.0a (AHK-Basic + AHK-L + Unicode) (v1.03)


  • Please log in to reply
11 replies to this topic

#1 VxE

VxE
  • Fellows
  • 3504 posts

Posted 18 July 2011 - 03:43 AM

Update (2012-01-14): I revised this library (OAuth 1.0a Authorization) but I have not yet updated the Imgur example. Until I do, both versions will be available.

Version 1.03 (2012-01-14): I updated the premise for this library. Versions 1.03 and higher only deal with formulating the 'Authorization' header and the signature for it. Therefore, these versions don't require HTTPRequest, though you are of course welcome to use it. As a side-effect, you are responsible for managing the OAuth token exchange AND the actual API calls.

The following link points to a zip file containing the library-compatible "oauth.ahk" and a test script. The test script contains examples taken from https://dev.twitter....docs/auth/oauth and shows how to use the library.

                                Download OAuth v1_03.zip


------- version 0.4 -------
OAuth creates a freely-implementable and generic methodology for API authentication.

And now OAuth is available for AHK. However, there are a few things you should know...

This collection of functions is NOT an API wrapper. Before your script can do anything useful, you'll need to formulate the API requests.

This library uses HTTPRequest internally for submitting 'request_token' and 'access_token' requests. HOWEVER, the function is NOT included here (you must download it separately to your AutoHotkey\Lib folder or copy/paste it into your script).

The current version (0.4) is essentially an alpha release. I fully expect to make significant changes to this library as I gain a broader experience of APIs that require OAuth. One change I anticipate is independence from HTTPRequest in order to be more compatible with other methods of submitting HTTP (like winHTTP or curl).

I have only tested these functions using the Imgur Authenticated API. Therefore, I won't guarantee compatibility with other APIs.

Two debugging functions are available, one that holds the last signature base string and one that holds the last response... use them!Lastly, please do NOT put requests for API wrappers in this thread. If you know of a web service with an authenticated API that you would like AHK to access, by all means post in General Chat where more people will see it.

                              Download OAuth.ahk

Explanation of Core Functions:

OAuth_ConsumerKey( your_consumer_key )   -   The first thing your script should do is call this function with your consumer key as its parameter. To get a consumer key, you'll need to register your application with the target API's host.

OAuth_ConsumerSecret( your_consumer_secret )   -   The second thing your script should do is call this function with your consumer secret that was given to you when you received your consumer key.

OAuth_RequestToken( request_token_endpoint )   -   Assuming you're going to start a new session and need the user to authorize your script, call this function with the URL (endpoint) of the target API's "request token" endpoint. Use the value returned as the value of the 'oauth_token' query parameter of the "authorize" endpoint and open the resulting url in a browser.

OAuth_PromptUserForOOBVerifier()   -   A glorified inputbox that prompts the user for the verification code (pin code) they receive after logging in and authorizing your script.

OAuth_AccessToken( access_token_endpoint, pin )   -   The last step of the OAuth "token dance" is to trade the request token for an access token. This function does that by making a request to the "access token" endpoint with the oauth verifier (pin code) obtained in the last step.

OAuth_HeaderAuth( url, params, verb )   -   This is the function that every subsequent API request uses to generate the signed OAuth header. 'Verb' here is synonymous with 'method' and is usually 'GET' or 'POST'. The 'url' must be the full url (including 'HTTP' and any query parameters at the end) for the request. 'Params' may be blank, but depending on the API, may be the POST parameters used in the rquest (NOT binary data). The return value looks like this:

Authorization: OAuth oauth_callback="http%3A%2F%2Flocalhost%3A3005%2Fthe_dance%2Fprocess_callback%3Fservice_provider_id%3D11", oauth_consumer_key="GDdmIQH6jhtmLUypg82g", oauth_nonce="QP70eNmVz8jvdPevU3oJD2AfF7R7odC2XJcn4XlZJqk", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1272323042", oauth_version="1.0", oauth_signature="8wUi7m5HFQy76nowoCThusfgB%2BQ%3D"



#2 VxE

VxE
  • Fellows
  • 3504 posts

Posted 18 July 2011 - 03:43 AM

                           Test Case: Twitter Developers: Using OAuth

This snippet should reproduce the request signature for the 'request_token' example request found here.
url := "https://api.twitter.com/oauth/request_token"
oauth_consumersecret( "MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98" )
params =
( LTRIM JOIN&
oauth_consumer_key=GDdmIQH6jhtmLUypg82g
oauth_nonce=QP70eNmVz8jvdPevU3oJD2AfF7R7odC2XJcn4XlZJqk
oauth_signature_method=HMAC-SHA1
oauth_timestamp=1272323042
oauth_version=1.0
oauth_callback=
)
params .= uriencode( "http://localhost:3005/the_dance/process_callback?service_provider_id=11" )
msgbox % oauth_headerauth( url, params, "POST" )
exitapp


                           Example: Imgur's Authenticated API

You will need an Imgur account to use this demo script.

Please understand, this script is ONLY to demonstrate OAuth authenticated requests and is NOT meant as a professional release. Its functionality is LIMITED.

This script contains a consumer_key and consumer_secret. Please do not hijack these for your own Imgur scripts. It is very easy to register for your own keys (and you get to choose a cool name for your script, unlike 'OAuth Example: Imgur API', which is the name you should see when authorising this script).
/*
####################################################################################################
##                                  Demo: Imgur Authenticated API                                 ##
####################################################################################################

     AutoHotkey Version:    1.0.48.05
     Language:              English
     Encoding:              ANSI
     Developed Using:       WIN_XP
     Created On:            2011/07/11
     Author:                [VxE]
     Description:           Authenticated Imgur API. Upload and view images on your Imgur account.
*/

#NoEnv
#NoTrayIcon
Menu, Tray, Icon, Shell32.dll, 128
Menu, Tray, Icon

; Use a config.ini file to store the OAuth urls and credentials (create with defaults if it doesn't exist)
IfNotExist, % IniFile := A_ScriptDir "\config.ini"
	FileAppend, [Imgur]`n
	( LTRIM
		Request_Token_Endpoint=https://api.imgur.com/oauth/request_token
		Authorize_Endpoint=https://api.imgur.com/oauth/authorize
		Access_Token_Endpoint=https://api.imgur.com/oauth/access_token

		[Imgur]
		Consumer_Key=160dbabd9359f35a9eed2c2757ec0dc404e12324d
		Consumer_Secret=7ad540780e8f7cc68a1d46cc1a25823f
		Access_Token=
		Token_Secret=

		[Script Settings]
	), % IniFile

OAuth_ConsumerKey(        IniRead( IniFile, "Imgur: OAuth Tokens", "Consumer_Key"    ) )
OAuth_ConsumerSecret(     IniRead( IniFile, "Imgur: OAuth Tokens", "Consumer_Secret" ) )
OAuth_TokenSecret(        IniRead( IniFile, "Imgur: OAuth Tokens", "Token_Secret"    ) )
OAuth_Token(              IniRead( IniFile, "Imgur: OAuth Tokens", "Access_Token"    ) )

; If there IS an OAuth token already available, use a simple GET function to test it.
If !OAuth_Token() || !( Account_Name := Imgur_AccountName() )
{
	; Either there is no existing token, or it's invalid. Either way, we have to get a new token.
	; Acquire a request token.
	If !( Request_Token := OAuth_RequestToken( IniRead( IniFile, "Imgur: OAuth Endpoints", "Request_Token_Endpoint" ) ) )
	{
		MsgBox, 16, Imgur API: Critical Error, % ""
		. "For some reason`, a request token could not be obtained. This script will exit."
		. "`nLast Response: " OAuth_LastResponse()
		Exitapp
	}
	
	; Open the authorization page in the user's default browser.
	Run, % IniRead( IniFile, "Imgur: OAuth Endpoints", "Authorize_Endpoint" ) "?oauth_token=" Request_Token
	
	; Display a custom prompt for the user to copy/paste the verifier code after authorizing the script.
	If !( verifier := OAuth_PromptUserForOOBVerifier() )
		Exitapp ; user cancelled... we're done
	
	; Acquire an access token
	Access_Token := OAuth_AccessToken( IniRead( IniFile, "Imgur: OAuth Endpoints", "Access_Token_Endpoint" ), verifier )

	If !( Account_Name := Imgur_AccountName() )
	{
		MsgBox, 16, Imgur API: Critical Error, % ""
		. "There was a problem verifying the access token. This script will exit."
		. "`nLast Response: " OAuth_LastResponse()
		Exitapp
	}

	; Store the token and secret in the config.ini for future use. NOTE: tokens should be encrypted for storage.
	IniWrite, % Access_Token, % IniFile, Imgur: OAuth Tokens, Access_Token
	IniWrite, % OAuth_TokenSecret(), % IniFile, Imgur: OAuth Tokens, Token_Secret
}
StringUpper, Account_Name, Account_Name, T

; Build the main gui.

	Gui, +LastFound +Resize +MinSize212x131 +LabelImgur_API_DemoScript_Gui_
	GroupAdd, MainWindowHotkeys, % "AHK_ID " ( 1gui_hwnd := WinExist() )
	Gui, Margin, 3, 3
	Gui, Font, S11, Arial
	Gui, Font,, Lucida Sans
	Gui, Add, ListView, x3 y3 Grid R7 -Multi vImages, Title|Size Kb|Size Px|Hash
	LV_ModifyCol( 2, "Float" )
	Gui, Add, Button, xm gImgur_API_DemoScript_Gui_Upload DEFAULT, &Upload
	Gui, Add, Button, yp gImgur_API_DemoScript_Gui_Options hwndGui_OptsBtn_hwnd, Image &Options
	Gui, Add, Button, yp gImgur_API_DemoScript_Gui_Close, &Quit
	ControlGetPos,,,, Btn_H,, % "AHK_ID " Gui_OptsBtn_hwnd
	Gui, Add, Progress, x3 yp h%Btn_H% vUploadProgress c22FFDD HIDDEN
	Gui, Show, y-99999, % Account_Name " - Imgur API Demo"
	WinMove, % "AHK_ID " 1gui_hwnd,,,
	, % IniRead( IniFile, "Script Settings", "Gui.W", 434 )
	, % IniRead( IniFile, "Script Settings", "Gui.H", 268 )

	Gui, Show, % "X" IniRead( IniFile, "Script Settings", "Gui.X", "Center" )
		. " Y" IniRead( IniFile, "Script Settings", "Gui.Y", "Center" )
	Gosub, Imgur_API_DemoScript_Gui_Refresh
	LV_ModifyCol( ), LV_ModifyCol( 4, "Autohdr" )
	WinWaitClose, % "AHK_ID " 1gui_hwnd
; Wait for the main gui to close, then exit the script
ExitApp

Imgur_API_DemoScript_Gui_ContextMenu:
; Build and show the cntextmenu for the appropriate listview item
	If ( A_GuiControl != "Images" )
		Return

Imgur_API_DemoScript_Gui_Options:
	If !LV_GetCount()
		Gosub, Imgur_API_DemoScript_Gui_Refresh
	If !( Chosen_Row := 0 | ( A_ThisLabel = "Imgur_API_DemoScript_Gui_Options" ? LV_GetNext( 0 ) : A_EventInfo ) )
		Return

	LV_GetText( Image_Hash, Chosen_Row, 4 )
	Menu, Imgur_API_DemoScript_Menu, UseErrorLevel
	Menu, Imgur_API_DemoScript_Menu, Add, &Copy URL (original), Imgur_API_DemoScript_Menu_CopyUrlOrg
	Menu, Imgur_API_DemoScript_Menu, Add, Copy URL (Lg Thum&b), Imgur_API_DemoScript_Menu_CopyUrlLgt
	Menu, Imgur_API_DemoScript_Menu, Add, Copy URL (Sq Thu&mb), Imgur_API_DemoScript_Menu_CopyUrlSmt
	Menu, Imgur_API_DemoScript_Menu, Add
	Menu, Imgur_API_DemoScript_Menu, Add, &Refresh Image List, Imgur_API_DemoScript_Gui_Refresh
	Menu, Imgur_API_DemoScript_Menu, Add
	Menu, Imgur_API_DemoScript_Menu, Add, About This Demo, Imgur_API_DemoScript_Menu_HelpAbout
	Menu, Imgur_API_DemoScript_Menu, Add
	Menu, Imgur_API_DemoScript_Menu, Add, Open De&lete Page, Imgur_API_DemoScript_Menu_OpenDelUrl
	If ( A_ThisLabel = "Imgur_API_DemoScript_Gui_Options" )
	{
		ControlGetPos, wx, wy,,,, % "AHK_ID " Gui_OptsBtn_hwnd
		Menu, Imgur_API_DemoScript_Menu, Show, % wx, % wy
	}
	Else Menu, Imgur_API_DemoScript_Menu, Show
	Menu, Imgur_API_DemoScript_Menu, Delete
	SetTimer, RemoveTooltip, -2880
Return

Imgur_API_DemoScript_Gui_Close:
; Close the gui after saving the window's position to the ini file
	WinGetPos, wx, wy,,, AHK_ID %1gui_hwnd%
	If wx IS INTEGER
		IniWrite, % wx, % IniFile, Script Settings, Gui.X
	If wy IS INTEGER
		IniWrite, % wy, % IniFile, Script Settings, Gui.Y
	Gui, Cancel
Return

Imgur_API_DemoScript_Gui_Refresh:
; Update the gui's listview with the data in the Account_Images table
	Account_Images := Imgur_TableFromAccountXML( Imgur_AccountImages() )
	Images := "`n"
	Loop, % LV_GetCount()
		If LV_GetText( Image_Hash, A_Index, 4 )
			IfInString, Account_Images, % "`n" Image_Hash "`t"
				Images .= Image_Hash "`n"
			Else LV_Delete( A_Index )

	Loop, Parse, Account_Images, `n
		If ( A_Index = 1 )
			url := A_LoopField "`n"
		Else
		{
			StringLeft, Image_Hash, A_LoopField, InStr( A_LoopField, "`t" ) - 1
			StringGetPos, pos, Images, % "`n" Image_Hash "`n"
			If !( ErrorLevel )
			{
				StringLeft, Chosen_Row, Images, pos + 1
				StringReplace, Chosen_Row, Chosen_Row, `n, `n, UseErrorLevel
				Chosen_Row := ErrorLevel
			}
			Else Chosen_Row := LV_Add( "Col4", Image_Hash )
			LV_Modify( Chosen_Row, "Col1", HTML_Decode( TbGet( url A_LoopField, Image_Hash, "Title" ) ) )
			LV_Modify( Chosen_Row, "Col2", Round( TbGet( url A_LoopField, Image_Hash, "Size" ) / 1024, 1 ) )
			LV_Modify( Chosen_Row, "Col3", TbGet( url A_LoopField, Image_Hash, "Width" ) "x" TbGet( url A_LoopField, Image_Hash, "Height" ) )
		}
Return

Imgur_API_DemoScript_Gui_Size:
	Loop 3
		GuiControl, MoveDraw, Button%A_Index%
		, % "x" Round( 3 + ( ( 1 << A_Index - 1 ) - 1 ) * ( A_GuiWidth / 4 - 1.5 ) )
		. " y" A_GuiHeight - 3 - Btn_H
		. " w" Round( ( A_Index + A_GuiWidth - 14 ) / ( 4 >> ( A_Index = 2 ) ) )
	GuiControl, MoveDraw, Images, % "w" A_GuiWidth - 6 " h" A_GuiHeight - 9 - Btn_H
	GuiControl, MoveDraw, UploadProgress, % "y" A_GuiHeight - 3 - Btn_H " w" A_GuiWidth - 6
	SetTimer, Imgur_API_DemoScript_Gui_SaveSize, -250
Return

Imgur_API_DemoScript_Gui_SaveSize:
	WinGetPos,,, wx, wy, AHK_ID %1gui_hwnd%
	If wx IS INTEGER
		IniWrite, % wx, % IniFile, Script Settings, Gui.W
	If wy IS INTEGER
		IniWrite, % wy, % IniFile, Script Settings, Gui.H
Return

Imgur_API_DemoScript_Gui_Upload:
	Gui, +OwnDialogs
	Gui, 1:+Disabled
	FileSelectFile, image_file, 2, ::{450d8fba-ad25-11d0-98a8-0800361b1103}
	, Upload Image To Imgur, Image Files (*.jpg; *.jpeg; *.gif; *.png; *.bmp)
	If !( ErrorLevel )
	{
		FileGetSize, image_size, % image_file
		GuiControl,, UploadProgress, 0
		GuiControl, +Range0-%image_size%, UploadProgress
		GuiControl, Show, UploadProgress
		Loop 3
			GuiControl, Hide, Button%A_Index%

		Image_Hash := Imgur_Upload( image_file )
		Images := Imgur_SetImageTitle( Image_Hash, RegexReplace( image_file, ".*\\" ) )
		Account_Images := Imgur_TableFromAccountXML( Images, Account_Images )

		GuiControl, Hide, UploadProgress
		Loop 3
			GuiControl, Show, Button%A_Index%

		Gosub, Imgur_API_DemoScript_Gui_Refresh
	}
	Gui, 1:-Disabled
Return

Imgur_API_DemoScript_Menu_HelpAbout:
	MsgBox, 64, Imgur API Demo - by [VxE],
	( LTRIM
			This script is a demonstration of OAuth 1.0 using Imgur's Authenticated API.

			Using this script, you can:
			- View a list of images in your account.
			- Upload images from your computer to your account.
			- Copy the URLs for linking to your images.
	)
Return

Imgur_API_DemoScript_Menu_CopyUrlOrg:
	Clipboard := url := TbGet( Account_Images, Image_Hash, "original" )
	Tooltip, Copied '%url%' to the clipboard.
Return

Imgur_API_DemoScript_Menu_CopyUrlLgt:
	Clipboard := url := TbGet( Account_Images, Image_Hash, "large_thumbnail" )
	Tooltip, Copied '%url%' to the clipboard.
Return

Imgur_API_DemoScript_Menu_CopyUrlSmt:
	Clipboard := url := TbGet( Account_Images, Image_Hash, "small_square" )
	Tooltip, Copied '%url%' to the clipboard.
Return

Imgur_API_DemoScript_Menu_OpenDelUrl:
	Run, % TbGet( Account_Images, Image_Hash, "delete_page" )
Return

RemoveTooltip:
	Tooltip
Return

; Hotkeys

#IfWinActive, AHK_Group MainWindowHotkeys

	F1::SetTimer, Imgur_API_DemoScript_Menu_HelpAbout

	Del::
	BS::
		LV_GetText( Image_Hash, Chosen_Row := LV_GetNext(0), 4 )
		SetTimer, Imgur_API_DemoScript_Menu_OpenDelUrl, -20
	Return

; ------------------- Imgur API Functions: These invoke specific API functions. --------------------

Imgur_AccountImages() { ; --------------------------------------------------------------------------
; Returns the XML feed that describes the images currently in the user's account.
	Static EndPoint := "http://api.imgur.com/2/account/images.xml?"
	headers := OAuth_HeaderAuth( EndPoint, "", "GET" )
	HTTPRequest( EndPoint, data := "", headers )
	OAuth_LastResponse( EndPoint ), OAuth_LastResponse( data ), OAuth_LastResponse( headers )
	IfInString, data, <images>
		Return data
	Return ""
} ; Imgur_AccountImages() --------------------------------------------------------------------------

Imgur_AccountName() { ; ----------------------------------------------------------------------------
; Returns the name (url) of the account that has authorized this script.
	Static EndPoint := "http://api.imgur.com/2/account.xml"
	headers := OAuth_HeaderAuth( EndPoint, "", "GET" )
	HTTPRequest( EndPoint, data := "", headers )
	OAuth_LastResponse( EndPoint ), OAuth_LastResponse( data ), OAuth_LastResponse( headers )
	StringGetPos, pos, data, <url>
	If ( ErrorLevel )
		Return "" ; failure
	StringTrimLeft, data, data, pos + 5
	Return SubStr( data, 1, InStr( data, "<" ) - 1 )
} ; Imgur_AccountName() ----------------------------------------------------------------------------
/*
; BROKEN. I could not get Imgur to recognize the '_method' parameter required for the delete operaition
Imgur_DeleteImage( image_hash ) { ; ----------------------------------------------------------------
; Deletes the indicated image from the user's Imgur account
	Static EndPoint := "http://api.imgur.com/2/account/images/%image_hash%.xml?_method=DELETE"
	StringReplace, url, EndPoint, % "%image_hash%", % image_hash
	data := "_method=DELETE"
	headers := OAuth_HeaderAuth( url, data, "POST" )
	. "`n" . "Content-Type: application/x-www-form-urlencoded; charset=UTF-8"
	HTTPRequest( url, data, headers )
	OAuth_LastResponse( url ), OAuth_LastResponse( data ), OAuth_LastResponse( headers )
	IfInString, data, Success
		Return 1
	Return ""
} ; Imgur_DeleteImage( image_hash ) ----------------------------------------------------------------
*/
Imgur_SetImageTitle( image_hash, title ) { ; -------------------------------------------------------
; Changes the title of an image in the user's Imgur account.
	Static EndPoint := "http://api.imgur.com/2/account/images/%image_hash%.xml"
	StringReplace, url, EndPoint, % "%image_hash%", % image_hash
	data := "title=" uriencode( title )
	headers := OAuth_HeaderAuth( url, data, "POST" )
	. "`n" . "Content-Type: application/x-www-form-urlencoded; charset=UTF-8"
	HTTPRequest( url, data, headers )
	OAuth_LastResponse( url ), OAuth_LastResponse( data ), OAuth_LastResponse( headers )
	Return data
} ; Imgur_SetImageTitle( image_hash, title ) -------------------------------------------------------

Imgur_Upload( image_file ) { ; ---------------------------------------------------------------------
; Uploads one image to the user's Imgur account and returns the URL of the image.
	Static EndPoint := "http://api.imgur.com/2/upload.xml"
	FileGetSize, size, % image_file
	FileRead, data, % "*c " image_file
	headers := OAuth_HeaderAuth( EndPoint, "", "POST" )
	. "`n" . "Content-Length: " size
	. "`n" . "Content-Type: application/octet-stream"
	HTTPRequest( EndPoint, data, headers, "Callback: Imgur_Progress" )
	OAuth_LastResponse( EndPoint ), OAuth_LastResponse( data ), OAuth_LastResponse( headers )
	StringGetPos, pos, data, <hash>
	If !( ErrorLevel )
		Return SubStr( data, pos + 7, Instr( data, "</hash>", 0, pos + 6 ) - pos - 7 )
	Else Return "" ; error: see response
} ; Imgur_Upload( image_file ) ---------------------------------------------------------------------

; --------------------------------- Accessory and GUI functions. -----------------------------------

Imgur_TableFromAccountXML( XML, Prior_Table="" ) { ; -----------------------------------------------
; Parses the XML response that contains Imgur image info into a 9/10 table, optionally appending or
; updating an existing table.

Static nodes := "
( LTRIM JOIN,
		name,title,caption,hash,deletehash,datetime,type,animated,width,height,size
		views,bandwidth,original,imgur_page,delete_page,small_square,large_thumbnail
)"

	; If the prior table is not blank, chop off its header, otherwise initialize both.
	Prior_Table .= "`n"
	If ( Prior_Table != "`n" )
	{
		StringGetPos, pos, Prior_Table, `n
		StringLeft, hd, Prior_Table, pos
		hd := "`t" hd "`t"
		StringTrimLeft, Prior_Table, Prior_Table, pos
	}
	Else hd := "`thash`t"

	; parse the XML by lessthan signs.
	Loop, Parse, XML, <
		If ( A_Index != 1 )
			Loop, Parse, A_LoopField, >, % "`t`n`r "
				If ( A_Index = 1 )
					StringLeft, node, A_LoopField, InStr( A_LoopField " ", " " ) - 1
				Else If node IN %nodes%
						Imgur_TFAXML_%node% := A_LoopField
				Else If ( node = "/links" ) ; Use </links> as end-of-item
				{
					; check for a duplicate
					StringGetPos, pos, Prior_Table, % "`n" Imgur_TFAXML_hash "`t"
					If !( ErrorLevel )
					{
						StringGetPos, node, Prior_Table, % "`n",, pos + 1
						StringTrimLeft, XML, Prior_Table, node + 1
						StringLeft, Prior_Table, Prior_Table, pos + 1
						Prior_Table .= XML
					}
					; Build the table row in order by existing headers
					node := "," nodes ","
					Loop, Parse, hd, % "`t", % "`r`n"
						If ( A_LoopField != "" )
						{
							Prior_Table .= ( A_Index = 2 ? "" : "`t" ) Imgur_TFAXML_%A_LoopField%
							StringReplace, node, node, % "," A_LoopField ",", `,
						}
					; add any extra columns to the entire table
					Loop, Parse, node, `,
						If ( A_LoopField != "" )
						{
							Prior_Table .= "`t" Imgur_TFAXML_%A_LoopField%
							StringReplace, Prior_Table, Prior_Table, `n, % "`t`n", A
							hd .= A_LoopField "`t"
						}
					; wipe the array to prevent bleeding
					Loop, Parse, nodes, `,
						Imgur_TFAXML_%A_LoopField% := ""
					Prior_Table .= "`n"
				}
	StringGetPos, pos, Prior_Table, `n
	Return SubStr( hd, 2, -1 ) SubStr( Prior_Table, pos + 1, -1 )
} ; Imgur_TableFromAccountXML( XML, Prior_Table="" ) -----------------------------------------------

Imgur_Progress( p, t ) { ; -------------------------------------------------------------------------
; Update the progress bar in the gui
	GuiControl, 1:, UploadProgress, % Round( ( 1 + p ) * t )
} ; Imgur_Progress( p, t ) -------------------------------------------------------------------------

IniRead( Filename, Section, Key, Default=" " ) {
	IniRead, OutputVar, % Filename, % Section, % Key, % Default
	Return OutputVar
}

TbGet( data, row, col ) {
; This is a stripped down version of 'Table_GetCell()' from my Table Manipulation Library.
	oel := ErrorLevel, data .= "`n"
	StringGetPos, pos, data, % "`n"
	StringLeft, hd, data, pos
	hd := "`t" hd "`t"
	StringGetPos, pos, hd, % "`t" col "`t"
	If ( ErrorLevel )
		Return "", ErrorLevel := oel
	StringLeft, hd, hd, pos
	StringGetPos, pos, data, % "`n" row "`t"
	If ( ErrorLevel )
		Return "", ErrorLevel := oel
	StringTrimLeft, data, data, pos + 1
	StringGetPos, pos, data, % "`n"
	StringLeft, data, data, pos
	If ( hd != "" )
	{
		StringReplace, hd, hd, % "`t", % "`t", UseErrorLevel
		StringGetPos, pos, data, % "`t", L%ErrorLevel%
		StringTrimLeft, data, data, pos + 1
	}
	StringLeft, data, data, InStr( data "`t", "`t" ) - 1
	Return data, ErrorLevel := oel	
}

HTML_Decode( string ) { ; --------------------------------------------------------------------------
; Function by [VxE]. Transforms HTML entities (like {, or «,) into their actual characters.
; NOTE: unless you are using a unicode version of AHK, characters with a character code above 255
; will appear in UTF-8 encoding and will look like '™'. Named entity codes were ripped from here:
; http://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references
	Static NamedEntities := "
	( LTRIM JOIN
		",22&,26&apos,27<,3C>,3E ,A0¡,A1¢,A2£,A3¤,A4¥,A5
		¦,A6§,A7¨,A8©,A9ª,AA«,AB¬,AC­,AD®,AE¯,AF°,B0
		±,B1²,B2³,B3´,B4µ,B5¶,B6·,B7¸,B8¹,B9º,BA
		»,BB¼,BC½,BD¾,BE¿,BFÀ,C0Á,C1Â,C2Ã,C3
		Ä,C4Å,C5Æ,C6Ç,C7È,C8É,C9Ê,CAË,CBÌ,CC
		Í,CDÎ,CEÏ,CFÐ,D0Ñ,D1Ò,D2Ó,D3Ô,D4Õ,D5Ö,D6
		×,D7Ø,D8Ù,D9Ú,DAÛ,DBÜ,DCÝ,DDÞ,DEß,DF
		à,E0á,E1â,E2ã,E3ä,E4å,E5æ,E6ç,E7è,E8
		é,E9ê,EAë,EBì,ECí,EDî,EEï,EFð,F0ñ,F1ò,F2
		ó,F3ô,F4õ,F5ö,F6÷,F7ø,F8ù,F9ú,FAû,FB
		ü,FCý,FDþ,FEÿ,FF&OElig,152&oelig,153&Scaron,160&scaron,161&Yuml,178
		&fnof,192&circ,2C6&tilde,2DC&Alpha,391&Beta,392&Gamma,393&Delta,394&Epsilon,395&Zeta,396
		&Eta,397&Theta,398&Iota,399&Kappa,39A&Lambda,39B&Mu,39C&Nu,39D&Xi,39E&Omicron,39F&Pi,3A0
		&Rho,3A1&Sigma,3A3&Tau,3A4&Upsilon,3A5&Phi,3A6&Chi,3A7&Psi,3A8&Omega,3A9&alpha,3B1&beta,3B2
		&gamma,3B3&delta,3B4&epsilon,3B5&zeta,3B6&eta,3B7&theta,3B8&iota,3B9&kappa,3BA&lambda,3BB
		&mu,3BC&nu,3BD&xi,3BE&omicron,3BF&pi,3C0&rho,3C1&sigmaf,3C2&sigma,3C3&tau,3C4&upsilon,3C5
		&phi,3C6&chi,3C7&psi,3C8&omega,3C9&thetasym,3D1&upsih,3D2&piv,3D6&ensp,2002&emsp,2003
		&thinsp,2009&zwnj,200C&zwj,200D&lrm,200E&rlm,200F&ndash,2013&mdash,2014&lsquo,2018
		&rsquo,2019&sbquo,201A&ldquo,201C&rdquo,201D&bdquo,201E&dagger,2020&Dagger,2021&bull,2022
		&hellip,2026&permil,2030&prime,2032&Prime,2033&lsaquo,2039&rsaquo,203A&oline,203E
		&frasl,2044&euro,20AC&image,2111&weierp,2118&real,211C&trade,2122&alefsym,2135&larr,2190
		&uarr,2191&rarr,2192&darr,2193&harr,2194&crarr,21B5&lArr,21D0&uArr,21D1&rArr,21D2&dArr,21D3
		&hArr,21D4&forall,2200&part,2202&exist,2203&empty,2205&nabla,2207&isin,2208¬in,2209
		&ni,220B&prod,220F&sum,2211&minus,2212&lowast,2217&radic,221A&prop,221D&infin,221E&ang,2220
		&and,2227&or,2228&cap,2229&cup,222A&int,222B&there4,2234&sim,223C&cong,2245&asymp,2248
		&ne,2260&equiv,2261&le,2264&ge,2265&sub,2282&sup,2283&nsub,2284&sube,2286&supe,2287
		&oplus,2295&otimes,2297&perp,22A5&sdot,22C5&lceil,2308&rceil,2309&lfloor,230A&rfloor,230B
		&lang,27E8&rang,27E9&loz,25CA&spades,2660&clubs,2663&hearts,2665&diams,2666&
	)"
	oel := ErrorLevel, oscs := A_StringCaseSense
	StringCaseSense, On
	Loop, Parse, string, & ; parse the string by ampersands, because they neatly delimit entities
		If ( A_Index = 1 )
			string := A_LoopField ; the part before the first ampersand can't contain an entity
		Else
		{
			StringGetPos, pos, A_LoopField, % ";" ; find the ending ';' of the entity
			If !( ErrorLevel )
			{
				StringLeft, entity, A_LoopField, pos
				StringGetPos, np, NamedEntities, &%entity%, ; see if the entity is a named entity
				If !( ErrorLevel )
				{
					; Use the static lookup list to convert the entity name to a number
					np += 3 + StrLen( entity )
					StringGetPos, cp, NamedEntities, &,, np
					StringMid, entity, NamedEntities, np, 1 + cp - np
					entity := "0x" entity
				}
				Else StringReplace, entity, entity, #, 0
				If entity IS INTEGER ; convert the numeric entity to character(s)
				{
					If ( A_IsUnicode ) || ( entity < 0x100 ) ; normal character conversion
						string .= Chr( entity )
					Else If ( entity < 0x800 ) ; UTF-8 character conversion
						string .= Chr( 0xC0 | ( entity >> 6 ) ) Chr( 0x80 | ( entity & 63 ) )
					Else If ( entity < 0x10000 )
						string .= Chr( 0xE0 | ( entity >> 12 ) ) Chr( 0x80 | ( ( entity >> 6 ) & 63 ) ) Chr( 0x80 | ( entity & 63 ) )
					Else If ( entity < 0x110000 )
						string .= Chr( 0xF0 | ( entity >> 18 ) ) Chr( 0x80 | ( ( entity >> 12 ) & 63 ) ) Chr( 0x80 | ( ( entity >> 6 ) & 63 ) ) Chr( 0x80 | ( entity & 63 ) )
					Else string .= "&#" SubStr( entity, 2 ) ";" ; unknown character code ?
				}
				Else string .= "&" SubStr( A_LoopField, 1, pos + 1 ) ; unrecognized entity
				string .= SubStr( A_LoopField, pos + 2 ) ; append the rest of the substring
			}
			Else string .= "&" A_LoopField ; so a lone '&' was found... just move along.
		}
	StringCaseSense, % oscs
	Return string, ErrorLevel := oel
} ; HTML_Decode( string ) --------------------------------------------------------------------------


#3 VxE

VxE
  • Fellows
  • 3504 posts

Posted 18 July 2011 - 03:47 AM

<reserved>

#4 octal

octal
  • Members
  • 92 posts

Posted 18 July 2011 - 02:40 PM

Let me be the first to thank and congratulate you on this library. Not only does it work well, its clean, well documented, and easy to use.

I haven't gotten a chance to really test it, but I did get the twitter handshake working.

Thanks a ton!

#5 sumon

sumon
  • Moderators
  • 1307 posts

Posted 18 July 2011 - 02:59 PM

Let me be the second to congratulate you, and applaud you for your efforts - well done! I'll be testing it and giving feedback shortly. Many thanks and huzzahs! :lol:

#6 octal

octal
  • Members
  • 92 posts

Posted 18 July 2011 - 07:42 PM

Ok so I just wrote up a little OAuth_Twitter library based on the code found here:
<!-- m -->https://github.com/a... ... roauth.php<!-- m -->


Requires OAuth.ahk
Sean's ComUtils?
HTTPRequest.ahk


OAuth_Twitter.ahk
;OAuth_Twitter 0.1
;Jason Stallings


#include oAuth.ahk

;Just a wrapper for OAuth_RequestToken. Returns the request token.
Twitter_getRequestToken() 
{
    return OAuth_RequestToken("https://api.twitter.com/oauth/request_token")
}

;Wrapper for OAuth_AccessToken. Returns the Acess Token.
Twitter_getAccessToken(pin)
{
	return OAuth_AccessToken("https://api.twitter.com/oauth/access_token",pin) 
}

;Use this to make your api calls. 
;url=					Api url you're trying to access.
;method=			GET or POST.
;parameters=		parameters to send to the api call. 

;example:
;	params := "status=" uriencode( "testing" )
;	Twitter_oAuthRequest("statuses/update.xml", "post", params)
Twitter_OAuthRequest(url, method, parameters = "")
{
	if not (instr(url, "twitter.com"))
		url:="http://api.twitter.com/1/"url
	headers:=oauth_headerauth(url, parameters, method)
	HTTPRequest( url, parameters,headers)
	return parameters

}

;Just run this with the request token, and it will return the pin with a nice clean gui. 
Twitter_PinPrompt(token,title="OAuth: Grant Authorization", width=800, height=650)
{
	global pipa
	Gui 4:  +Lastfound +OwnDialogs
	Gui 4:    show, w%width% h%height%, %title%
	hGui:=WinExist()
	ATLWinHWND := Atl_AxCreateContainer(hGui,0,0,width,height, "Shell.Explorer")
	pwb := Atl_AxGetControl( ATLWinHWND )
	pwb.silent := true
	IOleInPlaceActiveObject_Interface:="{00000117-0000-0000-C000-000000000046}"
	pipa := Query_Interface(pwb, IOleInPlaceActiveObject_Interface)
	OnMessage(WM_KEYDOWN:=0x0100, "WM_KEYDOWN")
	OnMessage(WM_KEYUP:=0x0101, "WM_KEYDOWN")
	url:="http://api.twitter.com/oauth/authorize?oauth_token="token
	pwb.Navigate(url)
	loop
		If !pwb.busy
			break
	while(pwb.document.getElementsByTagName("code").length=0)
		sleep 500
	 pin:=pwb.document.getElementsByTagName("code")[0].innerText
	Gui 4: destroy

	COM_Term()
	
	 return pin
}

WM_KEYDOWN(wParam, lParam, nMsg, hWnd)
{
   Global pipa
   pipaun := ComObjUnwrap(pipa)
   If  (wParam = 0x09 || wParam = 0x0D || wParam = 0x2E || wParam = 0x26 || wParam = 0x28) ; tab enter delete up down
   ;If  (wParam = 9 || wParam = 13 || wParam = 46 || wParam = 38 || wParam = 40) ; tab enter delete up down
   {
      WinGetClass, Class, ahk_id %hWnd%
      ;tooltip % class
      If  (Class = "Internet Explorer_Server")
      {
         VarSetCapacity(Msg, 28)
         NumPut(hWnd,Msg), NumPut(nMsg,Msg,4), NumPut(wParam,Msg,8), NumPut(lParam,Msg,12)
         NumPut(A_EventInfo,Msg,16), NumPut(A_GuiX,Msg,20), NumPut(A_GuiY,Msg,24)
         DllCall(NumGet(NumGet(1*pipaun)+20), "Ptr", pipaun, "Ptr", &Msg)
         Return 0
      }
   }
}

Example Status Post
#include HTTPRequest.ahk
#include oAuth.ahk
#include OAuth_Twitter.ahk
#include Query.ahk
#include atl.ahk


OAuth_ConsumerKey:="<INSERT OAuth ConsumerKey>"
OAuth_ConsumerSecret:="<INSERT OAuth ConsumerSecret>"
OAuth_ConsumerKey(OAuth_ConsumerKey)
OAuth_ConsumerSecret(OAuth_ConsumerSecret )

token:=Twitter_getRequestToken()
pin:=Twitter_PinPrompt(token)
access_token:=Twitter_getAccessToken(pin)
params := "status=" uriencode( "testing1234" )
msgbox % Twitter_oAuthRequest("statuses/update.xml", "post", params)


#7 VxE

VxE
  • Fellows
  • 3504 posts

Posted 19 July 2011 - 01:32 AM

Ok so I just wrote up a little OAuth_Twitter library...

Wow, that was fast! I'm guessing a full-service twitter script isn't too far behind? I'm especially impressed at the pin-code retrieval function.

I think that the next update to the oauth lib is going to drop the pin gui entirely, leaving that step up to the individual API wrappers themselves.

#8 octal

octal
  • Members
  • 92 posts

Posted 19 July 2011 - 01:54 AM

I've been waiting for this for a while haha.

I think dropping the pin retrieval in the main API is probably a good idea. I wanted to make mine generic, but I quickly found it was going to have to be different for each service.

Are you a fan of SVN? I think a project like this could benefit from it, and others could start adding APIs for other services.

#9 fragman

fragman
  • Members
  • 1591 posts

Posted 19 July 2011 - 08:56 AM

Great! Mind if I add twitter support to 7plus?

#10 octal

octal
  • Members
  • 92 posts

Posted 19 July 2011 - 01:31 PM

Of course not, that's why its there! I be posting a couple of changes to the twitter library but its nothing big.

#11 fragman

fragman
  • Members
  • 1591 posts

Posted 19 July 2011 - 03:55 PM

Cool, it's gonna take me some more time so it will probably be updated by then.

#12 VxE

VxE
  • Fellows
  • 3504 posts

Posted 15 January 2012 - 12:39 AM

Version 1.03 is available. See the OP for (not much) more info.

The new version should be vastly more modular since I have narrowed the scope from the previous version. The new version delegates all HTTP responsibilities to the parent script, focusing on the 'Authorization' header that contains the OAuth credentials and signature.