Created
June 20, 2020 05:43
-
-
Save G33kDude/a1483a62c4a085fc1b441c92875d3197 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#NoEnv | |
SetBatchLines, -1 | |
#Include <Socket> | |
MATRIC_CONFIG_PATH := A_Desktop "\..\Documents\.matric\config.json" | |
APP_NAME := "AutoHotkey" | |
API_PORT := 50300 | |
RESP_PORT := 50301 | |
; Assert that MATRIC is running | |
if !WinExist("ahk_exe MatricServer.exe") | |
throw Exception("Please start MATRIC") | |
; Assert the integration API is enabled | |
if !GetMatricConfig().EnableIntegrationAPI | |
throw Exception("Please enable the MATRIC integration API") | |
; Open the UDP socket listening on 50301 | |
udpClient := new BetterUDP() | |
udpClient.OnRecv := Func("OnRecv") | |
udpClient.Bind(["127.0.0.1", RESP_PORT]) | |
udpClient.Listen() | |
; Get the PIN from MATRIC | |
if !(pin := GetPIN(MATRIC_CONFIG_PATH, APP_NAME)) | |
throw Exception("Could not retrieve connection PIN") | |
; Issue a command to MATRIC | |
UDPSend( | |
( JOIN ; ahk | |
{ | |
"command": "GETCONNECTEDCLIENTS", | |
"appName": APP_NAME, | |
"appPIN": pin | |
} | |
)) | |
return | |
; Read the MATRIC config to find the PIN for appName | |
; If one is not present, trigger Matric to generate one | |
GetPIN(configPath, appName) | |
{ | |
; Look for the PIN | |
for i, entry in GetMatricConfig().AuthorizedApps | |
if (entry.appName == appName) | |
return entry.appPin | |
; Get MATRIC to generate a PIN | |
UDPSend({"command": "CONNECT", "appName": appName}) | |
WinWait, Authorization required ahk_exe MatricServer.exe | |
ControlSend, ahk_parent, {Enter} | |
WinWaitClose | |
; Look for the PIN again | |
for i, entry in GetMatricConfig().AuthorizedApps | |
if (entry.appName == appName) | |
return entry.appPin | |
} | |
; Read and parse the MATRIC config file | |
GetMatricConfig() | |
{ | |
global MATRIC_CONFIG_PATH | |
return Jxon_Load(FileOpen(MATRIC_CONFIG_PATH, "r").Read()) | |
} | |
; Handle response data received from MATRIC | |
OnRecv(sock) | |
{ | |
MsgBox, % "Received message from MATRIC:`n`n" | |
. Jxon_Dump(Jxon_Load(sock.RecvText()), 4) | |
} | |
; Wrapper function to make it easier to send commands to MATRIC | |
UDPSend(object) | |
{ | |
global API_PORT, udpClient | |
udpClient.SendTextTo(Jxon_Dump(object), ["127.0.0.1", API_PORT]) | |
} | |
; Add some functions to the UDP class that really ought to be there | |
class BetterUDP extends SocketUDP | |
{ | |
SendTextTo(text, address, encoding:="UTF-8") | |
{ | |
VarSetCapacity(buffer, StrPut(text, encoding) | |
* (encoding="UTF-16" || encoding="cp1200" ? 2 : 1)) | |
length := StrPut(text, &buffer, encoding) | |
return this.SendTo(&buffer, length, address) | |
} | |
SendTo(pBuffer, length, address) | |
{ | |
addr := DllCall("WS2_32\inet_addr", "AStr", address[1], "UInt") | |
port := DllCall("WS2_32\htons", "UShort", address[2], "UShort") | |
VarSetCapacity(sa_in, 16, 0) | |
NumPut(2, sa_in, 0, "UShort") ; AF_INET | |
NumPut(port, sa_in, 2, "UShort") | |
NumPut(addr, sa_in, 4, "UInt") | |
DllCall("Ws2_32\sendto", "UInt", this.Socket, "Ptr", pBuffer | |
, "Int", length, "Int", 0, "Ptr", &sa_in, "Int", 16) | |
} | |
} | |
; --- Coco's JSON Library --- | |
; https://github.com/cocobelgica/AutoHotkey-JSON | |
Jxon_Load(ByRef src, args*) | |
{ | |
static q := Chr(34) | |
key := "", is_key := false | |
stack := [ tree := [] ] | |
is_arr := { (tree): 1 } | |
next := q . "{[01234567890-tfn" | |
pos := 0 | |
while ( (ch := SubStr(src, ++pos, 1)) != "" ) | |
{ | |
if InStr(" `t`n`r", ch) | |
continue | |
if !InStr(next, ch, true) | |
{ | |
ln := ObjLength(StrSplit(SubStr(src, 1, pos), "`n")) | |
col := pos - InStr(src, "`n",, -(StrLen(src)-pos+1)) | |
msg := Format("{}: line {} col {} (char {})" | |
, (next == "") ? ["Extra data", ch := SubStr(src, pos)][1] | |
: (next == "'") ? "Unterminated string starting at" | |
: (next == "\") ? "Invalid \escape" | |
: (next == ":") ? "Expecting ':' delimiter" | |
: (next == q) ? "Expecting object key enclosed in double quotes" | |
: (next == q . "}") ? "Expecting object key enclosed in double quotes or object closing '}'" | |
: (next == ",}") ? "Expecting ',' delimiter or object closing '}'" | |
: (next == ",]") ? "Expecting ',' delimiter or array closing ']'" | |
: [ "Expecting JSON value(string, number, [true, false, null], object or array)" | |
, ch := SubStr(src, pos, (SubStr(src, pos)~="[\]\},\s]|$")-1) ][1] | |
, ln, col, pos) | |
throw Exception(msg, -1, ch) | |
} | |
is_array := is_arr[obj := stack[1]] | |
if i := InStr("{[", ch) | |
{ | |
val := (proto := args[i]) ? new proto : {} | |
is_array? ObjPush(obj, val) : obj[key] := val | |
ObjInsertAt(stack, 1, val) | |
is_arr[val] := !(is_key := ch == "{") | |
next := q . (is_key ? "}" : "{[]0123456789-tfn") | |
} | |
else if InStr("}]", ch) | |
{ | |
ObjRemoveAt(stack, 1) | |
next := stack[1]==tree ? "" : is_arr[stack[1]] ? ",]" : ",}" | |
} | |
else if InStr(",:", ch) | |
{ | |
is_key := (!is_array && ch == ",") | |
next := is_key ? q : q . "{[0123456789-tfn" | |
} | |
else ; string | number | true | false | null | |
{ | |
if (ch == q) ; string | |
{ | |
i := pos | |
while i := InStr(src, q,, i+1) | |
{ | |
val := StrReplace(SubStr(src, pos+1, i-pos-1), "\\", "\u005C") | |
static end := A_AhkVersion<"2" ? 0 : -1 | |
if (SubStr(val, end) != "\") | |
break | |
} | |
if !i ? (pos--, next := "'") : 0 | |
continue | |
pos := i ; update pos | |
val := StrReplace(val, "\/", "/") | |
, val := StrReplace(val, "\" . q, q) | |
, val := StrReplace(val, "\b", "`b") | |
, val := StrReplace(val, "\f", "`f") | |
, val := StrReplace(val, "\n", "`n") | |
, val := StrReplace(val, "\r", "`r") | |
, val := StrReplace(val, "\t", "`t") | |
i := 0 | |
while i := InStr(val, "\",, i+1) | |
{ | |
if (SubStr(val, i+1, 1) != "u") ? (pos -= StrLen(SubStr(val, i)), next := "\") : 0 | |
continue 2 | |
; \uXXXX - JSON unicode escape sequence | |
xxxx := Abs("0x" . SubStr(val, i+2, 4)) | |
if (A_IsUnicode || xxxx < 0x100) | |
val := SubStr(val, 1, i-1) . Chr(xxxx) . SubStr(val, i+6) | |
} | |
if is_key | |
{ | |
key := val, next := ":" | |
continue | |
} | |
} | |
else ; number | true | false | null | |
{ | |
val := SubStr(src, pos, i := RegExMatch(src, "[\]\},\s]|$",, pos)-pos) | |
; For numerical values, numerify integers and keep floats as is. | |
; I'm not yet sure if I should numerify floats in v2.0-a ... | |
static number := "number", integer := "integer" | |
if val is %number% | |
{ | |
if val is %integer% | |
val += 0 | |
} | |
; in v1.1, true,false,A_PtrSize,A_IsUnicode,A_Index,A_EventInfo, | |
; SOMETIMES return strings due to certain optimizations. Since it | |
; is just 'SOMETIMES', numerify to be consistent w/ v2.0-a | |
else if (val == "true" || val == "false") | |
val := %val% + 0 | |
; AHK_H has built-in null, can't do 'val := %value%' where value == "null" | |
; as it would raise an exception in AHK_H(overriding built-in var) | |
else if (val == "null") | |
val := "" | |
; any other values are invalid, continue to trigger error | |
else if (pos--, next := "#") | |
continue | |
pos += i-1 | |
} | |
is_array? ObjPush(obj, val) : obj[key] := val | |
next := obj==tree ? "" : is_array ? ",]" : ",}" | |
} | |
} | |
return tree[1] | |
} | |
Jxon_Dump(obj, indent:="", lvl:=1) | |
{ | |
static q := Chr(34) | |
if IsObject(obj) | |
{ | |
static Type := Func("Type") | |
if Type ? (Type.Call(obj) != "Object") : (ObjGetCapacity(obj) == "") | |
throw Exception("Object type not supported.", -1, Format("<Object at 0x{:p}>", &obj)) | |
is_array := 0 | |
for k in obj | |
is_array := k == A_Index | |
until !is_array | |
static integer := "integer" | |
if indent is %integer% | |
{ | |
if (indent < 0) | |
throw Exception("Indent parameter must be a postive integer.", -1, indent) | |
spaces := indent, indent := "" | |
Loop % spaces | |
indent .= " " | |
} | |
indt := "" | |
Loop, % indent ? lvl : 0 | |
indt .= indent | |
lvl += 1, out := "" ; Make #Warn happy | |
for k, v in obj | |
{ | |
if IsObject(k) || (k == "") | |
throw Exception("Invalid object key.", -1, k ? Format("<Object at 0x{:p}>", &obj) : "<blank>") | |
if !is_array | |
out .= ( ObjGetCapacity([k], 1) ? Jxon_Dump(k) : q . k . q ) ;// key | |
. ( indent ? ": " : ":" ) ; token + padding | |
out .= Jxon_Dump(v, indent, lvl) ; value | |
. ( indent ? ",`n" . indt : "," ) ; token + indent | |
} | |
if (out != "") | |
{ | |
out := Trim(out, ",`n" . indent) | |
if (indent != "") | |
out := "`n" . indt . out . "`n" . SubStr(indt, StrLen(indent)+1) | |
} | |
return is_array ? "[" . out . "]" : "{" . out . "}" | |
} | |
; Number | |
else if (ObjGetCapacity([obj], 1) == "") | |
return obj | |
; String (null -> not supported by AHK) | |
if (obj != "") | |
{ | |
obj := StrReplace(obj, "\", "\\") | |
, obj := StrReplace(obj, "/", "\/") | |
, obj := StrReplace(obj, q, "\" . q) | |
, obj := StrReplace(obj, "`b", "\b") | |
, obj := StrReplace(obj, "`f", "\f") | |
, obj := StrReplace(obj, "`n", "\n") | |
, obj := StrReplace(obj, "`r", "\r") | |
, obj := StrReplace(obj, "`t", "\t") | |
static needle := (A_AhkVersion<"2" ? "O)" : "") . "[^\x20-\x7e]" | |
while RegExMatch(obj, needle, m) | |
obj := StrReplace(obj, m[0], Format("\u{:04X}", Ord(m[0]))) | |
} | |
return q . obj . q | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment