Podio Web API Tutorial

Helpful script writing tricks and HowTo's
User avatar
RazorHalo
Posts: 45
Joined: 21 Dec 2015, 21:23

Podio Web API Tutorial

06 May 2018, 23:27

I have been working with Podio to organize the shop i work at and have needed it to do things automatically and have ways to create, modify and delete items in it which can't be accomplished with its own web interface. Some things could be done with it's Globiflow integration, but that is another account al together with more costs associated.

This forced me to figure out how to communicate with its RESTful API. It took a lot of trial and error on my part and I just wanted to share what I have figured out with this community which has been so helpful on so many occasions.

If you're ever going to utilize AHK and Podio I'm sure this will come in handy and may also give insight and solutions to working with other API's.

This is my first time writing any kind of tutorial so bear with me!

The main libraries that it is dependent on are:
AHKSock.ahk
https://autohotkey.com/board/topic/5382 ... ock-tcpip/
https://github.com/jleb/AHKsock

HTTPRequest.ahk
https://gist.github.com/tmplinshi/0ba3d ... c1ca244056
https://autohotkey.com/board/topic/6798 ... x64/page-1

JSON.ahk - slightliy modified version I had to tweak this one just a little to get it to play nice with Podio so here is the modded version
Forum Topic - http://goo.gl/r0zI8t

Code: Select all

/*
	Modified version by RazorHalo
	Changes:
		* Returnes empty [] for an empty array instead of {}
		* Returns a value of null instead of "" for an empty string/integer
*/
/**
 * Lib: JSON.ahk
 *     JSON lib for AutoHotkey.
 * Version:
 *     v2.1.3 [updated 04/18/2016 (MM/DD/YYYY)]
 * License:
 *     WTFPL [http://wtfpl.net/]
 * Requirements:
 *     Latest version of AutoHotkey (v1.1+ or v2.0-a+)
 * Installation:
 *     Use #Include JSON.ahk or copy into a function library folder and then
 *     use #Include <JSON>
 * Links:
 *     GitHub:     - https://github.com/cocobelgica/AutoHotkey-JSON
 *     Forum Topic - http://goo.gl/r0zI8t
 *     Email:      - cocobelgica <at> gmail <dot> com
 */


/**
 * Class: JSON
 *     The JSON object contains methods for parsing JSON and converting values
 *     to JSON. Callable - NO; Instantiable - YES; Subclassable - YES;
 *     Nestable(via #Include) - NO.
 * Methods:
 *     Load() - see relevant documentation before method definition header
 *     Dump() - see relevant documentation before method definition header
 */
class JSON
{
	/**
	 * Method: Load
	 *     Parses a JSON string into an AHK value
	 * Syntax:
	 *     value := JSON.Load( text [, reviver ] )
	 * Parameter(s):
	 *     value      [retval] - parsed value
	 *     text    [in, ByRef] - JSON formatted string
	 *     reviver   [in, opt] - function object, similar to JavaScript's
	 *                           JSON.parse() 'reviver' parameter
	 */
	class Load extends JSON.Functor
	{
		Call(self, ByRef text, reviver:="")
		{
			this.rev := IsObject(reviver) ? reviver : false
		; Object keys(and array indices) are temporarily stored in arrays so that
		; we can enumerate them in the order they appear in the document/text instead
		; of alphabetically. Skip if no reviver function is specified.
			this.keys := this.rev ? {} : false

			static quot := Chr(34), bashq := "\" . quot
			     , json_value := quot . "{[01234567890-tfn"
			     , json_value_or_array_closing := quot . "{[]01234567890-tfn"
			     , object_key_or_object_closing := quot . "}"

			key := ""
			is_key := false
			root := {}
			stack := [root]
			next := json_value
			pos := 0

			while ((ch := SubStr(text, ++pos, 1)) != "") {
				if InStr(" `t`r`n", ch)
					continue
				if !InStr(next, ch, 1)
					this.ParseError(next, text, pos)

				holder := stack[1]
				is_array := holder.IsArray

				if InStr(",:", ch) {
					next := (is_key := !is_array && ch == ",") ? quot : json_value

				} else if InStr("}]", ch) {
					ObjRemoveAt(stack, 1)
					next := stack[1]==root ? "" : stack[1].IsArray ? ",]" : ",}"

				} else {
					if InStr("{[", ch) {
					; Check if Array() is overridden and if its return value has
					; the 'IsArray' property. If so, Array() will be called normally,
					; otherwise, use a custom base object for arrays
						static json_array := Func("Array").IsBuiltIn || ![].IsArray ? {IsArray: true} : 0
					
					; sacrifice readability for minor(actually negligible) performance gain
						If (ch == "{")
							? ( is_key := true
							  , value := {}
							  , next := object_key_or_object_closing )
						; ch == "["
							: ( value := json_array ? new json_array : []
							  , next := json_value_or_array_closing )
						
						ObjInsertAt(stack, 1, value)

						if (this.keys)
							this.keys[value] := []
					
					} else {
						if (ch == quot) {
							i := pos
							while (i := InStr(text, quot,, i+1)) {
								value := StrReplace(SubStr(text, pos+1, i-pos-1), "\\", "\u005c")

								static tail := A_AhkVersion<"2" ? 0 : -1
								if (SubStr(value, tail) != "\")
									break
							}

							if (!i)
								this.ParseError("'", text, pos)

							  value := StrReplace(value,  "\/",  "/")
							, value := StrReplace(value, bashq, quot)
							, value := StrReplace(value,  "\b", "`b")
							, value := StrReplace(value,  "\f", "`f")
							, value := StrReplace(value,  "\n", "`n")
							, value := StrReplace(value,  "\r", "`r")
							, value := StrReplace(value,  "\t", "`t")

							pos := i ; update pos
							
							i := 0
							while (i := InStr(value, "\",, i+1)) {
								if !(SubStr(value, i+1, 1) == "u")
									this.ParseError("\", text, pos - StrLen(SubStr(value, i+1)))

								uffff := Abs("0x" . SubStr(value, i+2, 4))
								if (A_IsUnicode || uffff < 0x100)
									value := SubStr(value, 1, i-1) . Chr(uffff) . SubStr(value, i+6)
							}

							if (is_key) {
								key := value, next := ":"
								continue
							}
						
						} else {
							value := SubStr(text, pos, i := RegExMatch(text, "[\]\},\s]|$",, pos)-pos)

							static number := "number", integer :="integer"
							if value is %number%
							{
								if value is %integer%
									value += 0
							}
							else if (value == "true" || value == "false")
								value := %value% + 0
							else if (value == "null")
								value := ""
							else
							; we can do more here to pinpoint the actual culprit
							; but that's just too much extra work.
								this.ParseError(next, text, pos, i)

							pos += i-1
						}

						next := holder==root ? "" : is_array ? ",]" : ",}"
					} ; If InStr("{[", ch) { ... } else

					is_array? key := ObjPush(holder, value) : holder[key] := value

					if (this.keys && this.keys.HasKey(holder))
						this.keys[holder].Push(key)
				}
			
			} ; while ( ... )

			return this.rev ? this.Walk(root, "") : root[""]
		}

		ParseError(expect, ByRef text, pos, len:=1)
		{
			static quot := Chr(34), qurly := quot . "}"
			
			line := StrSplit(SubStr(text, 1, pos), "`n", "`r").Length()
			col := pos - InStr(text, "`n",, -(StrLen(text)-pos+1))
			msg := Format("{1}`n`nLine:`t{2}`nCol:`t{3}`nChar:`t{4}"
			,     (expect == "")     ? "Extra data"
			    : (expect == "'")    ? "Unterminated string starting at"
			    : (expect == "\")    ? "Invalid \escape"
			    : (expect == ":")    ? "Expecting ':' delimiter"
			    : (expect == quot)   ? "Expecting object key enclosed in double quotes"
			    : (expect == qurly)  ? "Expecting object key enclosed in double quotes or object closing '}'"
			    : (expect == ",}")   ? "Expecting ',' delimiter or object closing '}'"
			    : (expect == ",]")   ? "Expecting ',' delimiter or array closing ']'"
			    : InStr(expect, "]") ? "Expecting JSON value or array closing ']'"
			    :                      "Expecting JSON value(string, number, true, false, null, object or array)"
			, line, col, pos)

			static offset := A_AhkVersion<"2" ? -3 : -4
			throw Exception(msg, offset, SubStr(text, pos, len))
		}

		Walk(holder, key)
		{
			value := holder[key]
			if IsObject(value) {
				for i, k in this.keys[value] {
					; check if ObjHasKey(value, k) ??
					v := this.Walk(value, k)
					if (v != JSON.Undefined)
						value[k] := v
					else
						ObjDelete(value, k)
				}
			}
			
			return this.rev.Call(holder, key, value)
		}
	}

	/**
	 * Method: Dump
	 *     Converts an AHK value into a JSON string
	 * Syntax:
	 *     str := JSON.Dump( value [, replacer, space ] )
	 * Parameter(s):
	 *     str        [retval] - JSON representation of an AHK value
	 *     value          [in] - any value(object, string, number)
	 *     replacer  [in, opt] - function object, similar to JavaScript's
	 *                           JSON.stringify() 'replacer' parameter
	 *     space     [in, opt] - similar to JavaScript's JSON.stringify()
	 *                           'space' parameter
	 */
	class Dump extends JSON.Functor
	{
		Call(self, value, replacer:="", space:="")
		{
			this.rep := IsObject(replacer) ? replacer : ""

			this.gap := ""
			if (space) {
				static integer := "integer"
				if space is %integer%
					Loop, % ((n := Abs(space))>10 ? 10 : n)
						this.gap .= " "
				else
					this.gap := SubStr(space, 1, 10)

				this.indent := "`n"
			}

			return this.Str({"": value}, "")
		}

		Str(holder, key)
		{
			value := holder[key]

			if (this.rep)
				value := this.rep.Call(holder, key, ObjHasKey(holder, key) ? value : JSON.Undefined)

			if IsObject(value) {
			; Check object type, skip serialization for other object types such as
			; ComObject, Func, BoundFunc, FileObject, RegExMatchObject, Property, etc.
				static type := A_AhkVersion<"2" ? "" : Func("Type")
				if (type ? type.Call(value) == "Object" : ObjGetCapacity(value) != "") {
					if (this.gap) {
						stepback := this.indent
						this.indent .= this.gap
					}

					is_array := value.IsArray
				; Array() is not overridden, rollback to old method of
				; identifying array-like objects. Due to the use of a for-loop
				; sparse arrays such as '[1,,3]' are detected as objects({}). 
					if (!is_array) {
						for i in value
							is_array := i == A_Index
						until !is_array
					}

					str := ""
					if (is_array) {
						Loop, % value.Length() {
							if (this.gap)
								str .= this.indent
	
							v := this.Str(value, A_Index)
							str .= (v != "") ? v . "," : "null,"
						}
					} else {
						colon := this.gap ? ": " : ":"
						for k in value {
							v := this.Str(value, k)
							if (v != "") {
								if (this.gap)
									str .= this.indent

								str .= this.Quote(k) . colon . v . ","
							}
						}
					}

					if (str != "") {
						str := RTrim(str, ",")
						if (this.gap)
							str .= stepback
					}

					if (this.gap)
						this.indent := stepback
					
					;added to return empty brackets	as empty array	-RH				
					If(!str)
						return "[`n`n]"


					return is_array ? "[" . str . "]" : "{" . str . "}"
				}
			
			;Added to return a value of null instead of "" doube quotes
			} else if (!value)
				return "null"
			
			
			else ; is_number ? value : "value"
				return ObjGetCapacity([value], 1)=="" ? value : this.Quote(value)
		}

		Quote(string)
		{
			static quot := Chr(34), bashq := "\" . quot

			if (string != "") {
				  string := StrReplace(string,  "\",  "\\")
				; , string := StrReplace(string,  "/",  "\/") ; optional in ECMAScript
				, string := StrReplace(string, quot, bashq)
				, string := StrReplace(string, "`b",  "\b")
				, string := StrReplace(string, "`f",  "\f")
				, string := StrReplace(string, "`n",  "\n")
				, string := StrReplace(string, "`r",  "\r")
				, string := StrReplace(string, "`t",  "\t")

				static rx_escapable := A_AhkVersion<"2" ? "O)[^\x20-\x7e]" : "[^\x20-\x7e]"
				while RegExMatch(string, rx_escapable, m)
					string := StrReplace(string, m.Value, Format("\u{1:04x}", Ord(m.Value)))
			}

			return quot . string . quot
		}
	}

	/**
	 * Property: Undefined
	 *     Proxy for 'undefined' type
	 * Syntax:
	 *     undefined := JSON.Undefined
	 * Remarks:
	 *     For use with reviver and replacer functions since AutoHotkey does not
	 *     have an 'undefined' type. Returning blank("") or 0 won't work since these
	 *     can't be distnguished from actual JSON values. This leaves us with objects.
	 *     Replacer() - the caller may return a non-serializable AHK objects such as
	 *     ComObject, Func, BoundFunc, FileObject, RegExMatchObject, and Property to
	 *     mimic the behavior of returning 'undefined' in JavaScript but for the sake
	 *     of code readability and convenience, it's better to do 'return JSON.Undefined'.
	 *     Internally, the property returns a ComObject with the variant type of VT_EMPTY.
	 */
	Undefined[]
	{
		get {
			static empty := {}, vt_empty := ComObject(0, &empty, 1)
			return vt_empty
		}
	}

	class Functor
	{
		__Call(method, ByRef arg, args*)
		{
		; When casting to Call(), use a new instance of the "function object"
		; so as to avoid directly storing the properties(used across sub-methods)
		; into the "function object" itself.
			if IsObject(method)
				return (new this).Call(method, arg, args*)
			else if (method == "")
				return (new this).Call(arg, args*)
		}
	}
}
If you don't already use Podio you will have to go and register yourself an account there and do some reading of their account set up tutorials. They are quite thorough and well done docs and videos. Podio is free to use for up to 5 users and has quite a lot of capability for a free version of their platform.

I've commented as much as I can in the code to explain what is going on.

If there are any parts of getting the account set up or using parts of this tutorial please post here and I will do my best to answer.

Code: Select all

;###############################################################################
;##########       PODIO WEB API TUTORIAL/EXAMPLE        ########################
;###############################################################################
;Created by RazorHalo  May 2018
;This example uses libraries not created by myself, refer to their included terms of use/copyrights



;SOME RESOURCES FOR FURTHER READING
;Examples of data returned by filtre views - Full, Short, Mini, Micro
;https://developers.podio.com/doc/applications/get-app-22349

;For receiving webhooks from Podio you must make sure you can receive the pushed data on port 80 (HTTP) or 443 (HTTPS)
;http://www.tomshardware.com/faq/id-3114787/open-firewall-ports-windows.html
;Then forward that port to your PC runniNg the AHK script

;Register your Webhook
;https://developers.podio.com/examples/webhooks

;HTTPRequest.ahk
;https://gist.github.com/tmplinshi/0ba3d9c6e8e367a82d1254c1ca244056
;https://autohotkey.com/board/topic/67989-func-httprequest-for-web-apis-ahk-b-ahk-lunicodex64/page-1

;JSON.ahk - slightliy modified version
;Forum Topic - http://goo.gl/r0zI8t

;AHKSock.ahk
;https://autohotkey.com/board/topic/53827-ahksock-a-simple-ahk-implementation-of-winsock-tcpip/
;https://github.com/jleb/AHKsock



#NoEnv                       ; Avoids checking empty variables to see if they are environment variables
#KeyHistory 0                ; Disable key history
#SingleInstance force        ; Skips the dialog box and replaces the old instance automatically
SendMode Input               ; Use faster and more reliable send method
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory

#Include %A_ScriptDir%\AHKsock.ahk
#Include %A_ScriptDir%\HTTPRequest.ahk
#Include %A_ScriptDir%\JSON.ahk

Global CRLF := "`r`n"
,hAuth

;Put your own user details here
;go to account settings and register the application and generate an API key for it
;for the rURI use your IP address
UserName := ""
Password := ""
ClientID := ""
rURI := ""
ClientSecret := ""

;Authenticate to be able to communicate with Podio
API_OAuth2_UserPass(UserName, Password, ClientID, rURI, ClientSecret)

;Some commonly used ID's
OrgID := 
UserID := 

;App ID's
;eg. aProjects := 65213345

;App ViewID's
;eg. vProjects_MainOverview := 36854192

;Set up an error handler (this is optional)
AHKsock_ErrorHandler("AHKsockErrors")
    
;Set up an OnExit routine
OnExit, GuiClose
    
;Set default value to invalid handle
iPeerSocket := -1
	
;Start listening on port 80 for webhooks (use port 443 for HTTPS)
;https://developers.podio.com/examples/webhooks
;to have Podio send webhook to you for some kind of action like create item / update item etc
;set up the webhook though Podio, and have it use your IP Address and the URL eg. Http://###.###.###.###
;there will be a button to verify the hook before it will become active - this is already set up to do it in the ProcessMsg Function.
;Method to verity that the webhook actualy came from Podio
;https://help.podio.com/hc/en-us/community/posts/200512138-Verify-that-Podio-is-calling-my-API-hooks
AHKsock_Listen(80, "APIDataProcessor")



;DO WHATEVER YOU NEED TO HERE!!!

Return








;#######################################################################################################################################

;Authenticate app with UserName & Password flow
;https://developers.podio.com/authentication/username_password
API_OAuth2_UserPass(UserName, Password, ClientID, rURI, ClientSecret) {
	OAuth2Login := "grant_type=password&username=" UserName
					. "&password=" Password
					. "&client_id=" ClientID
					. "&redirect_uri=" rURI
					. "&client_secret=" ClientSecret
			
	;Returned Access Token is good for 28,800 seconds (8hrs)
	;Need to use the refresh token after that to get a new Access Token
	;you could put a timer on it or just wait for an error that the token is epired and refresh then - didnt put that together in this eample.
	HTTPRequest( "https://podio.com/oauth/token", OAuth2Login, Headers, "Charset: utf-8")

	;Check for Error
	If API_ErrChk(A_ThisFunc, Headers, Data)
		Return
		
	;Put Returned auth data into object
	OAuth2 := JSON.Load(OAuth2Login)
	;Set auth header to use in calls
	hAuth := "HTTP/1.1" . CRLF
			 . "Authorization: OAuth2 " OAuth2.ACCESS_TOKEN
}

;TASK OPERATIONS  #########################################################################################
;Creates a new task.
;https://developers.podio.com/doc/tasks/create-task-22419
API_CreateTask(Task, hook:=true, silent:=false) {
	Headers := hAuth
	Headers .= "`nContent-type : application/json"
	
	;Create an object to convert to JSON for request
	Data := JSON.Dump(Task,,4)
	
	URL := "https://api.podio.com/task/"
	
	;Add options to URL if different from default values
	Hook ? : URL .= "?hook=false"
	Silent ? URL .= "?silent=true"
	
	;Send HTTP request
	HTTPRequest(URL, Data, Headers, "Charset: utf-8")
	
	;Check for Error
	API_ErrChk(A_ThisFunc, Headers, Data)
}

;Create a new Task Object
class cTask {
	text := ""					;The text of the task
	description := ""			;The description of the task (optional)
	private := ""				;True if the task should be private, false otherwise
	due_date := ""				;The due date of the task, if any (in local time) eg."2018-03-19 01:48:56"
	due_time := ""				;The due time of the task, if any (in local time) eg. "01:48:56"
	due_on := ""				;The due date and time of the task, if any (in UTC)
	ref_type := ""				;Optional ref type to which the task is attached to
	ref_id := ""				;Optional ref id to which the task is attached to
	responsible := "" 			;The contact(s) responsible for the task, identified by either:
									;An integer (user_id)
									;A {"type": contact identifier type, "id": contact identifier value} object
									;A list of integers and/or contact identifier objects
									;(with 5 supported contact identifier types: "user", "profile", "mail", "space", "external")
								
	file_ids := []				;The list of files to attach to this task	
	labels := [] 				;The list of labels in text form eg. "High Priority"	
	label_ids := []				;The list labels in id form		
			
	reminder := {}						;Optional reminder on this task
		reminder.remind_delta := "" 		;Minutes (integer) to remind before the due date

	external_id := ""				;Any external id for the task, if from another system
			
	recurrence := {} 			;The recurrence for the task, if any,
		recurrence.name := "" 		;The name of the recurrence, "weekly", "monthly" or "yearly"
		recurrence.step := 1		;The step size, 1 or more
		recurrence.until := ""		;The latest date the recurrence should take place
		
		recurrence.config := {}					;The configuration for the recurrence, depends on the type			
			recurrence.config.days := []			;List of weekdays ("monday", "tuesday", etc) (for "weekly")
			recurrence.config.repeat_on := ""		;When to repeat, "day_of_week" or "day_of_month" (for "monthly")	
}

;Deletes a Task
;https://developers.podio.com/doc/tasks/delete-task-77179
API_DeleteTask(TaskID, hook:=true, silent:=false) {
	Headers := hAuth
	Headers .= "`nContent-type : application/json"
	
	URL := "https://api.podio.com/task/" TaskID
	
	;Add options to URL
	Hook ? : URL .= "?hook=false"
	Silent ? URL .= "?silent=true"
	
	;Send HTTP request using DELETE method
	HTTPRequest(URL, Data, Headers, "Charset: utf-8`nMethod: DELETE")
	
	;Check for Error
	API_ErrChk(A_ThisFunc, Headers, Data)
}

;Assigns the task to another user. This makes the user responsible for the task and its completion.
;https://developers.podio.com/doc/tasks/assign-task-22412
API_AssignTask(TaskID, UserID, Silent:=False) {
	Headers := hAuth
	
	;Using a simple continuation section here to create the JSON data
	Data =
		(Ltrim
			{
				"responsible": %UserID%
			}
		)
	
	URL := "https://api.podio.com/task/" TaskID "/assign"
	Silent ? URL .= "?silent=true"
	
	HTTPRequest(URL, Data, Headers, "Charset: utf-8")
	API_ErrChk(A_ThisFunc, Headers, Data)
}

;Mark the given task as completed
;https://developers.podio.com/doc/tasks/complete-task-22432
API_CompleteTask(TaskID, hook:=true, silent:=false) {
	Headers := hAuth
	
	URL := "https://api.podio.com/task/" TaskID "/complete"
	;Add options to URL
	Hook ? : URL .= "?hook=false"
	Silent ? URL .= "?silent=true"
	
	HTTPRequest(URL, Data, Headers, "Charset: utf-8")
	If API_ErrChk(A_ThisFunc, Headers, Data)
		Return
	Return Data  ;Returns "recurring_task_id": The id of the recurring task that was created, if any
}


;USER OPERATIONS  ###########################################################################################
;Returns the current status for the user. This includes the user data, profile data and notification data.
;https://developers.podio.com/doc/users/get-user-status-22480
API_GetUserStatus() {
	Headers := hAuth
	HTTPRequest("https://api.podio.com/user/status", Data, Headers, "Charset: utf-8")
	
	OutputDebug % "RETURNED DATA: " Data "`nRETURNED HEADERS: " Headers
	If API_ErrChk(A_ThisFunc, Headers, Data)
		Return
		
	Return JSON.Load(Data)
	
}

;ITEM OPERATIONS  ###########################################################################################
;Retrieves the items in the app based on the given view
;https://developers.podio.com/doc/items/filter-items-by-view-4540284
API_GetAppItems(AppID, ViewID, limit:=30, offset:=0, remember:=false, sort_by:="", sort_desc:=true) {
	Headers := hAuth
	
	;Create an object to convert to JSON for request
	Data := {}	
		sData.limit := limit			;The maximum number of items to return, defaults to 30
		sData.offset := offset			;The offset into the returned items, defaults to 0
		sData.remember := remember		;True if the view should be remembered, false otherwise
		sData.sort_by := sort_by		;The sort order to use. If left out, will use the sort order from the view
		sData.sort_desc := sort_desc	;True to sort descending, false otherwise. If left out, will use the sort order from the view
	Data := JSON.Dump(sData,,4)
	
	;Using the micro filter view to send back only the smallest amoun of relevent data.
	URL := "https://api.podio.com/item/app/" AppID "/filter/" ViewID "/?fields=items.view(micro)"	
	HTTPRequest(URL, Data, Headers, "Charset: utf-8")
	
	If API_ErrChk(A_ThisFunc, Headers, Data)
		Return
	
	Return JSON.Load(Data)
}
;Deletes an item and removes it from all views. The item can no longer be retrieved.
;https://developers.podio.com/doc/items/delete-item-22364
API_DeleteItem(ItemID, hook:=true, silent:=false) {
	Headers := hAuth
	URL := "https://api.podio.com/item/" ItemID
	
	Hook ? : URL .= "?hook=false"
	Silent ? URL .= "?silent=true"
	
	;Send HTTP request using DELETE method
	HTTPRequest(URL, Data, Headers, "Charset: utf-8`nMethod: DELETE")
	API_ErrChk(A_ThisFunc, Headers, Data)
}


;Returns the item with the specified id. 
;https://developers.podio.com/doc/items/get-item-22360
API_GetItem(ItemID, mark_as_viewed:=true) {
	Headers := hAuth
	URL := "https://api.podio.com/item/" ItemID
	
	mark_as_viewed ? : URL .= "?mark_as_viewed=false"	
	
	HTTPRequest(URL, Data, Headers, "Charset: utf-8")
	
	If API_ErrChk(A_ThisFunc, Headers, Data)
		Return
	
	Return JSON.Load(Data)
}

;Returns the full item by its app_item_id, which is a unique ID for items per app. 
;https://developers.podio.com/doc/items/get-item-by-app-item-id-66506688
API_GetAppItem(AppID, AppItemID) {
	Headers := hAuth
	URL := "https://api.podio.com/app/" AppID "/item/" AppItemID

	HTTPRequest(URL, Data, Headers, "Charset: utf-8")
	
	If API_ErrChk(A_ThisFunc, Headers, Data)
		Return
	Clipboard := Data
	Return JSON.Load(Data)
}

;Returns   "item_id": The id of the newly created item,  "title": The title of the newly created item
;Adds a new item to the given app.
;https://developers.podio.com/doc/items/add-new-item-22362
;
;First create a new class item then pass that to the function
;Example:
;Item := new cItem
;rItem := API_AddItem(Item,aProjects)

API_AddItem(Item, AppID, hook:=true, silent:=false) {
	Headers := hAuth
	Headers .= "`nContent-type : application/json"
	
	;Convert object to JSON for request
	Data := JSON.Dump(Item,,4)
	
	URL := "https://api.podio.com/item/app/" AppID "/"
	
	;Add options to URL if different from default values
	Hook ? : URL .= "?hook=false"
	Silent ? URL .= "?silent=true"
	
	;Send HTTP request
	HTTPRequest(URL, Data, Headers, "Charset: utf-8")
	
	;Check for Error
	If API_ErrChk(A_ThisFunc, Headers, Data)
		Return
	
	Return JSON.Load(Data)
}

;Create a new Item Object
;For an example of the values, see the get item operation.
;https://developers.podio.com/doc/items/get-item-22360

class cItem {

	external_id := "" 			;The external id of the item. This can be used to hold a reference to the item in an external system.
	fields := {}				;The values for each field ** MUST use the FieldID value if the External ID contains a hyphen
		/**  Put your own fields in here  these are jsut examples - see the developer option for the app for these values. (found by clicking the wrench icon next to the app name)
		fields.164717579 := "NEW API Project"										;"project-title": "sample_value"  <---  can't use this because of the hypen must use the listed Field ID integer
		fields.stage := 4																;"stage": integer_value_of_option  <----  this is OK because it contains no Hyphen
		fields.address := "650 Townsend St., San Francisco, CA 94103"					;"address": "650 Townsend St., San Francisco, CA 94103"
		fields.164717585 := {"start":"2011-12-31 11:27:10","end":"2012-01-31 11:28:20"}	;"start-and-finish-dates": {"start":"2011-12-31 11:27:10","end":"2012-01-31 11:28:20"}
		*/
	file_ids := []				;Temporary files that have been uploaded and should be attached to this item -{file_id}
	tags := []					;The tags to put on the item -  {tag}: The text of the tag to add,

	;reminder/recurrence section is optional
	reminder := {}						;Optional reminder on this task
		reminder.remind_delta := "" 		;Minutes (integer) to remind before the due date			
	recurrence := {} 			;The recurrence for the task, if any,
		recurrence.name := "weekly" 		;The name of the recurrence, "weekly", "monthly" or "yearly"  ; cant be null if this option if reminder is in request
		recurrence.step := 1		;The step size, 1 or more
		recurrence.until := ""		;The latest date the recurrence should take place
		
		recurrence.config := {}					;The configuration for the recurrence, depends on the type			
			recurrence.config.days := ["monday"]			;List of weekdays ("monday", "tuesday", etc) (for "weekly")  ; cant be null if this option if reminder is in request
			recurrence.config.repeat_on := ""		;When to repeat, "day_of_week" or "day_of_month" (for "monthly")	
			
	linked_account_id := "" 			;The linked account to use for the meeting	
	ref := {} 				;The reference for the new item, if any
		ref.type := "item"  		;The type of the reference, currently only "item"
		ref.id := ""   			;The id of the reference

}


;ORGANIZATION OPERATIONS  ###########################################################################################
;Returns a list of all the organizations and spaces the user is member of.
;https://developers.podio.com/doc/organizations/get-organizations-22344
API_GetOrgDetails() {
	Headers := hAuth
	URL := "https://api.podio.com/org/"
	HTTPRequest(URL, Data, Headers, "Charset: utf-8")
	
	If API_ErrChk(A_ThisFunc, Headers, Data)
		Return
		
	Return JSON.Load(Data)
}

;WORKSPACE OPERATIONS  ###########################################################################################
;Returns all the spaces for the organization.
;https://developers.podio.com/doc/organizations/get-spaces-on-organization-22387
API_GetAllOrgSpaces(OrgID) {
	Headers := hAuth
	URL := "https://api.podio.com/org/" OrgID "/space/"
	HTTPRequest(URL, Data, Headers, "Charset: utf-8")
	
	If API_ErrChk(A_ThisFunc, Headers, Data)
		Return
			
	Return JSON.Load(Data)
}

;Returns WorkSpaces in an Organization for active user
;https://developers.podio.com/doc/spaces/get-list-of-organization-workspaces-238875316
API_GetOrgSpaces_ActiveUser(OrgID) {
	Headers := hAuth
	URL := "https://api.podio.com/space/org/" OrgID
	HTTPRequest(URL, Data, Headers, "Charset: utf-8")
	
	If API_ErrChk(A_ThisFunc, Headers, Data)
		Return
	
	Return JSON.Load(Data)
}

;APP OPERATIONS  ###########################################################################################


;Returns the number of items on app matching a given saved view or set of filter(s).
;https://developers.podio.com/doc/items/get-item-count-34819997
API_GetItemCount(AppID, Key:="", ViewID:="") {
	Headers := hAuth
	URL := "https://api.podio.com/item/app/" AppID "/count"
	
	ViewID ? URL .= "?view_id=" ViewID
	
	HTTPRequest(URL, Data, Headers, "Charset: utf-8")
	API_ErrChk(A_ThisFunc, Headers, Data)
}

;############################################################################################################

;Check for HTTP response code other than 2xx indicating an error sent back by Podio
;Returns response of error, code and details sent back by Podio, or false if there is no error
API_ErrChk(Func, rHeaders, rData) {
	RegExMatch(rHeaders, "O)HTTP/1.1\s?(?P<StatusCode>([\d]+))\s(?P<Description>([^\r\n]+))(?P<Details>.*)", Rex)
	If (SubStr(Rex.StatusCode, 1,1) != 2) {
		MsgBox 4144,API ERROR, % Func " Failed`n`n"
				. "STATUS CODE: `n" Rex.StatusCode " " Rex.Description "`n`n"
				. "DETAILS: " Rex.Details
				. "`nRETURNED DATA: `n" rData
		Return True
	}
	Else
		Return False
}



;############################################################################################################
;Function to process what to do with incoming webhook
ProcessMsg(rMsg){
	
	Msg := {}
	;Parse Msg into Objecct
	Loop Parse, rMsg, `r`n
	{
		If (A_Index = 1)
			Continue
		If !(Msg.Data) {
			If (RegExMatch(A_LoopField, "iO)(?P<Key>([^:].*)):\s(?P<Value>([^\r\n]).*)", Rex)) {
				Key := StrReplace(Rex.Key, "-", "_")
				Msg[Key] := Rex.Value
			} Else {
				Msg.Data := {}
			}
		} Else {
			Loop Parse, A_LoopField, &
			{
				RegExMatch(A_LoopField, "iO)(?P<Key>(.*))=(?P<Value>(.*))", Rex)
				Msg.Data[Rex.Key] := Rex.Value		
			}
		}
	}
	
	;Verify WebHook
	If (Msg.Data.type = "hook.verify") {
		
		Headers =
		(LTRIM
			HTTP/1.1 200 OK
			Connection: keep-alive
			Accept-Encoding: gzip, deflate
			Content-Type: application/json; charset=utf-8
		)
		
		URL := "https://api.podio.com/hook/" Msg.Data.hook_id "/verify/validate/"

		;Respond with HTTP 200 stsus code and the verification code for the hook
		Data := "{`n""code"": """ Msg.Data.code """`n}"
		HTTPRequest( URL, Data, Headers, "Charset: utf-8")
		
		;Check to see if response was received
		If API_ErrChk(A_ThisFunc, Headers, Data)
			Return
		
		MsgBox % "WebHook " Rex.HookID " Verified!"
	}

	
}	




APIDataProcessor(sEvent, iSocket = 0, sName = 0, sAddr = 0, sPort = 0, ByRef bData = 0, bDataLength = 0) {
    Global iPeerSocket, bExiting, bSameMachine, bCantListen
    Static iIgnoreDisconnect
	
	 If (sEvent) {
	  
		OutputDebug, % "Event Received: " sEvent
	  }
    
    If (sEvent = "ACCEPTED") {
        OutputDebug, % "A client with IP " sAddr " connected!"
        
        If (iPeerSocket <> -1) {
            OutputDebug, % "We already have a peer! Disconnecting..."
            AHKsock_Close(iSocket) ;Close the socket
            iIgnoreDisconnect += 1 ;So that we don't react when this peer disconnects
            Return
        }
        
        ;Remember the socket
        iPeerSocket := iSocket
        
        ;Stop listening (see comment block in CONNECTED event)
        AHKsock_Listen(80)
        
    } If (sEvent = "CONNECTED") {
        
        ;Check if the connection attempt was successful
        If (iSocket = -1) {
            OutputDebug, % "AHKsock_Connect() failed."
            
            ;Check if we are not currently listening, and if we already tried to listen and failed.
            If bCantListen
                ExitApp
            
            OutputDebug, % "Listening for a peer..."us
            
            ;If the connection attempt was on this computer, we can start listening now since the connect attempt is
            ;over and we thus run no risk of ending up connected to ourselves. 
            If bSameMachine {
                If (i := AHKsock_Listen(80, "APIDataProcessor")) {
                    OutputDebug, % "AHKsock_Listen() failed with return value = " i " and ErrorLevel = " ErrorLevel
                    ExitApp
                }
            }
            
            ;The connect attempt failed, but we are now listening for clients. We can leave now.
            Return
            
        } Else OutputDebug, % "AHKsock_Connect() successfully connected on IP " sAddr "."
        
        ;We now have an established connection with a peer
        
        ;This is the same fail-safe as in the ACCEPTED event (see comment block there)
        If (iPeerSocket <> -1) {
            OutputDebug, % "We already have a peer! Disconnecting..."
            AHKsock_Close(iSocket) ;Close the socket
            iIgnoreDisconnect += 1 ;So that we don't react when this peer disconnects
            Return
        }
        
        ;Remember the socket
        iPeerSocket := iSocket
        
        AHKsock_Listen(80)
        
        ;Update status
       ; GuiControl,, lblStatus, Connected to %sName%!
        
    } Else If (sEvent = "DISCONNECTED") {
        
        ;Check if we're supposed to ignore this event
        If iIgnoreDisconnect {
            iIgnoreDisconnect -= 1
            Return
        }
        
        ;Reset variable
        iPeerSocket := -1
           
        ;We should go back to listening (unless we're in the process of leaving)
        If Not bExiting {
            
            OutputDebug, % "The peer disconnected! Going back to listening..."
            
            ;GuiControl,, lblStatus, Waiting for a peer... ;Update status
            If (i := AHKsock_Listen(80, "APIDataProcessor")) {
                OutputDebug, % "AHKsock_Listen() failed with return value = " i " and ErrorLevel = " ErrorLevel
                ExitApp
            }
            
        } Else OutputDebug, % "The peer disconnected! Exiting..."
        
    } Else If (sEvent = "RECEIVED") {
	
		;Converts incoming Podio data to UTF-8
		VarSetCapacity( rbuffer, bDataLength + 1 << 1 )
		DllCall( "MultiByteToWideChar", "UInt", 65001, "UInt", 0, Ptr, &bData, "UInt", bDataLength, Ptr, &rbuffer, "UInt", bDataLength )
		VarSetCapacity( rMsg, bDataLength + 1 << 1, 0 )
		DllCall( "RtlMoveMemory", Ptr, &rMsg, Ptr, &rbuffer, "UInt", bDataLength << 1 )
		VarSetCapacity( rMsg, -1 )

		;Call function to Process the data received from Podio
		ProcessMsg(rMsg)

    }
}

AHKsockErrors(iError, iSocket) {
    OutputDebug, % "Error " iError " with error code = " ErrorLevel ((iSocket <> -1) ? " on socket " iSocket "." : ".") 
}

CopyBinData(ptrSource, ptrDestination, iLength) {
    If iLength ;Only do it if there's anything to copy
        DllCall("RtlMoveMemory", "Ptr", ptrDestination, "Ptr", ptrSource, "UInt", iLength)
}
	

GuiClose:
Esc::
	bExiting := True
	AHKsock_Close()

Exitapp

Return to “Tutorials (v1)”

Who is online

Users browsing this forum: No registered users and 27 guests