Jump to content

Recommended Posts

Posted (edited)

SciTE AI assistant

The first approach, to integrate an AI assistant into SciTE
works directly with the ollama. I use a small model  qwen2.5-coder:3b  as default, so that I don't have to wait too long for a response.
However, I have set as a parameter which model it calls, so that it changes the model per job if necessary

The first job is to make function headers

Simply run the script, select a function body  within SciTE and press the {F10} key
It's not perfect, but it's a template.

 

; https://www.autoitscript.com/forum/topic/212888-scite-ai-assistant
;----------------------------------------------------------------------------------------
; Title...........: SciTE_AI_Assistant.au3
; Description.....: SciTE AI assistant - first feature: Function headers generator
;                   Simply select a function within SciTE and press the {F10} key.
; AutoIt Version..: 3.3.16.1   Author: ioa747  Script Version: 0.7
; Note............: Testet in Win10 22H2
;----------------------------------------------------------------------------------------
#AutoIt3Wrapper_Au3Check_Parameters=-d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6 -w 7

#include <File.au3>
#include <SendMessage.au3>
#include <WindowsConstants.au3>

Global $DebugInfo = True ; True = debug info  * <--

HotKeySet("{F10}", "GenerateFunctionHeader") ; * <--

While 1
    Sleep(100)
WEnd

;---------------------------------------------------------------------------------------
Func GenerateFunctionHeader()
    HotKeySet("{F10}")

    Local $hWnd = WinActivate("[CLASS:SciTEWindow]")
    If Not $hWnd Then Return SetError(HotKeySet("{F10}", "GenerateFunctionHeader"))

    Local $sSelected = _SciteIO() ;Get Selection
    If @error Then
        ToolTip(" Copy failed.")
    Else
        If StringLeft($sSelected, 4) = "func" Then
            Send('{UP}')
            Local $sOut = AI_GenerateFunctionHeader($sSelected)
            If @error Then
                ToolTip(@error)
            Else
                _SciteIO(@CRLF & @CRLF & $sOut & @CRLF & @CRLF)
            EndIf
        Else
            ToolTip("No Func selected")
        EndIf
    EndIf
    Sleep(2000)
    ToolTip("")
    HotKeySet("{F10}", "GenerateFunctionHeader")
EndFunc   ;==>GenerateFunctionHeader
;---------------------------------------------------------------------------------------
Func AI_GenerateFunctionHeader($sFullFunction, $sAssistant = -1)
    If $sAssistant = -1 Then $sAssistant = "qwen2.5-coder:3b"
    Local $sInstruction, $sSystem, $sOut

    $sInstruction = "Generate a function header for the given AutoIt function." & @CRLF
    $sInstruction &= "Use the exact standard UDF comment format, as shown in the example." & @CRLF
    $sInstruction &= "DO NOT invent or assume any parameters, return values, or behavior not explicitly visible in the function." & @CRLF
    $sInstruction &= "If the function has no parameters, leave 'Parameters' empty." & @CRLF
    $sInstruction &= "If it does not return a value, then in 'Return values' write 'None'." & @CRLF
    $sInstruction &= "Align all parameter names to the same column width using consistent spaces. The hyphen (-) must appear in the same column for all parameters." & @CRLF
    $sInstruction &= "If the function interacts with GUI elements, include that in the 'Remarks' section." & @CRLF
    $sInstruction &= "If the 'Remarks' line exceeds 130 characters, break it into multiple lines starting each with ';                  ' like in the example." & @CRLF
    $sInstruction &= "Optional parameters must be shown using nested brackets as per the example." & @CRLF
    $sInstruction &= "Match spacing exactly as in the example (e.g., align parameter names and descriptions)." & @CRLF
    $sInstruction &= "Use concise, factual language—do not embellish or rephrase beyond what's explicit in the function." & @CRLF
    $sInstruction &= "In 'Return values', include detailed descriptions of any @error values that may be set." & @CRLF
    $sInstruction &= "Use the following format for @error documentation: '@error = X: Explanation' as shown in the example." & @CRLF
    $sInstruction &= "If applicable, describe both the success and failure outcomes clearly, using 'Success:' and 'Failure:'." & @CRLF
    $sInstruction &= "The 'Author' is ioa747. Dependencies must match actual function calls only. Do not invent links or libraries." & @CRLF
    $sInstruction &= "Avoid unnecessary technical jargon. Keep lines compact and accurate." & @CRLF & @CRLF

    $sInstruction &= "Example format:" & @CRLF
    $sInstruction &= "; #FUNCTION# ====================================================================================================================" & @CRLF
    $sInstruction &= "; Name...........: StreamToEdit" & @CRLF
    $sInstruction &= "; Description....: Generate response from the AI assistant with streaming support." & @CRLF
    $sInstruction &= "; Syntax.........: StreamToEdit( $sPrompt [, $sAssistant = ""qwen2.5-coder:1.5b"" [, $sSystem = ""You are an AI assistant."" [, $fTemperature = 0.3 [, $iSeed = 0]]]] )" & @CRLF
    $sInstruction &= "; Parameters.....: $sPrompt      - The prompt to be sent to the assistant." & @CRLF
    $sInstruction &= ";                  $sAssistant   - [optional] The name of the AI assistant. (Default is ""qwen2.5-coder:3b"")" & @CRLF
    $sInstruction &= ";                  $sSystem      - [optional] The system instruction provided by the user. (Default is ""You are an AI assistant."")" & @CRLF
    $sInstruction &= ";                  $fTemperature - [optional] The temperature value for the assistant's output. (Default is 0.3)" & @CRLF
    $sInstruction &= ";                  $iSeed        - [optional] The seed value for the assistant's response. (Default is 0)" & @CRLF
    $sInstruction &= "; Return values .: Success: Returns the available models if the specified model is found." & @CRLF
    $sInstruction &= ";                  Failure: Sets @error and returns an empty string with a custom message." & @CRLF
    $sInstruction &= ";                  @error = 1: Ollama is not running at the default URL." & @CRLF
    $sInstruction &= ";                  @error = 2: The specified model was not found." & @CRLF
    $sInstruction &= "; Author ........: ioa747" & @CRLF
    $sInstruction &= "; Modified ......: " & @CRLF
    $sInstruction &= "; Remarks .......: This function uses a WinHttp request to communicate with the Ollama API." & @CRLF
    $sInstruction &= "; Related .......: " & @CRLF
    $sInstruction &= "; Link ..........: " & @CRLF
    $sInstruction &= "; Example .......: " & @CRLF
    $sInstruction &= "; ===============================================================================================================================" & @CRLF & @CRLF

    $sInstruction &= "Now generate a header comment for the following function:" & @CRLF
    $sInstruction &= "```AutoIt" & @CRLF
    $sInstruction &= $sFullFunction & @CRLF
    $sInstruction &= "```" & @CRLF

    Local $hTimer = TimerInit()

    $sOut = AskToAI($sInstruction, $sAssistant, $sSystem, 0, 1)

    If @error Then Return SetError(@error, 0, $sOut)

    Return "> " & $sAssistant & " processed in: " & Round(TimerDiff($hTimer) / 1000, 3) & " seconds " & @CRLF & $sOut

EndFunc   ;==>AI_GenerateFunctionHeader
;---------------------------------------------------------------------------------------
Func AskToAI($sPrompt, $sAssistant = -1, $sSystem = -1, $iTemperature = -1, $iSeed = -1)

    If $sSystem = "" Or $sSystem = -1 Or $sSystem = Default Then $sSystem = "You are an AI assistant designed to analyze AutoIt code."
    ; best mini models result with: qwen2.5-coder:1.5b ; qwen2.5-coder:3b 1.8 GB ; llama3.2:3b-instruct-q8_0 3.2 GB
    If $sAssistant = "" Or $sAssistant = -1 Or $sAssistant = Default Then $sAssistant = "qwen2.5-coder:3b"
    If $iTemperature = "" Or $iTemperature = -1 Or $iTemperature = Default Then $iTemperature = 0.3
    If $iSeed = "" Or $iSeed = -1 Or $iSeed = Default Then $iSeed = 0

    Json_EscapeString($sAssistant)
    Json_EscapeString($sSystem)
    Json_EscapeString($sPrompt)

    Local $sRequest = '{'
    $sRequest &= '"model": "' & $sAssistant & '", '
    $sRequest &= '"system": "' & $sSystem & '", '
    $sRequest &= '"prompt": "' & $sPrompt & '", '
    $sRequest &= '"stream": false, '
    $sRequest &= '"temperature": ' & $iTemperature & ', '
    $sRequest &= '"seed": ' & $iSeed
    $sRequest &= '}'

    DW("$sRequest=>" & $sRequest & @CRLF)
    ;_FileWriteLog(StringTrimRight(@ScriptFullPath, 4) & ".log", $sRequest)

    Local $oHttp = ObjCreate("WinHttp.WinHttpRequest.5.1")
    ; ResolveTimeout: 5 sec ; ConnectTimeout: 10 sec ; SendTimeout: 30 sec ; ReceiveTimeout: 120 sec
    $oHttp.SetTimeouts(5000, 10000, 30000, 120000)
    $oHttp.Open("POST", "http://localhost:11434/api/generate", False)
    $oHttp.SetRequestHeader("Content-Type", "application/json")
    $oHttp.Send($sRequest)

    ; Check if request was successful
    If $oHttp.Status <> 200 Then
        Local $aErr = HTTP_STATUS_CODES($oHttp.Status, $oHttp.ResponseText)
        MsgBox(16, "  ( " & $aErr[0] & " )  " & $aErr[1], $aErr[2])
        Return SetError(1, 0, $oHttp.ResponseText)
    EndIf

    Local $sResponse = $oHttp.ResponseText
    DW("$sResponse=>" & $sResponse & @CRLF)

    ; Parse response => suporting value: .model ; .created_at ; .response ; .done_reason
    Local $mJson = Json_Map($sResponse)
    ; DW("$mJson.response=" & $mJson.response & @CRLF)

    MapExists($mJson, "response")
    If @error Then
        MsgBox(16, "AskToAI() l: " & @ScriptLineNumber, "_JsonMap() fail to response")
        Return SetError(2, 0, "_JsonMap() fail to response")
    EndIf

    $sResponse = $mJson.response

    Return $sResponse
EndFunc   ;==>AskToAI
;---------------------------------------------------------------------------------------
Func _SciteIO($sTxt = "")
    Local $hWnd = WinActivate("[CLASS:SciTEWindow]")
    If Not $hWnd Then Return SetError(1)

    Local $ClipBack = ClipGet() ; backup clip data
    Local $hCtrlHnd, $ClipData, $iTimeout = 2000
    ClipPut("<empty>")

    Local $iStartTime = TimerInit()

    If $sTxt = "" Then
        ; ### get selection to clip ###
        $hCtrlHnd = ControlGetHandle($hWnd, '', 'Scintilla1')
        _SendMessage($hCtrlHnd, $WM_COPY)
        While TimerDiff($iStartTime) < $iTimeout ; Wait until the timeout
            $ClipData = ClipGet()
            If $ClipData <> "<empty>" Then ExitLoop
            Sleep(10)
        WEnd

    Else
        ; ### set Txt to Console ###
        $hCtrlHnd = ControlGetHandle($hWnd, '', 'Scintilla2')
        ClipPut($sTxt)
        While TimerDiff($iStartTime) < $iTimeout ; Wait until the timeout
            $ClipData = ClipGet()
            If $ClipData <> "<empty>" Then ExitLoop
            Sleep(10)
        WEnd

        If $ClipData <> "<empty>" Then
            Local $iWndHeight = ControlGetPos ($hWnd, "", $hCtrlHnd)
            If $iWndHeight[3] = 0 Then WinMenuSelectItem($hWnd, "", "&View", "&Output")
            Sleep(10)
            ControlSend($hWnd, "", $hCtrlHnd, "^{DOWN}")
            If Not $DebugInfo Then WinMenuSelectItem($hWnd, "", "&Tools", "Clear &Output") ;* Clear console Output
            Sleep(10)
            _SendMessage($hCtrlHnd, $WM_PASTE)
        EndIf
    EndIf

    ClipPut($ClipBack) ; Restore clip data

    Return ($ClipData = "<empty>" ? SetError(2) : $ClipData)

EndFunc   ;==>_SciteIO
;---------------------------------------------------------------------------------------
Func HTTP_STATUS_CODES($iStatus, $sResponseText = "")
    Local $aResult[3] = [$iStatus, "Unknown Status", "An unknown HTTP status code was returned."]

    Local $HTTP_STATUS_CODES[41][3] = [[40, "HTTP status", "description"] _
            , [100, "HTTP_STATUS_CONTINUE", "The request can be continued."] _
            , [101, "HTTP_STATUS_SWITCH_PROTOCOLS", "The server has switched protocols in an upgrade header."] _
            , [200, "HTTP_STATUS_OK", "The request completed successfully."] _
            , [201, "HTTP_STATUS_CREATED", "The request has been fulfilled and resulted in the creation of a new resource."] _
            , [202, "HTTP_STATUS_ACCEPTED", "The request has been accepted for processing, but the processing has not been completed."] _
            , [203, "HTTP_STATUS_PARTIAL", "The returned meta information in the entity-header is not the definitive set available from the originating server."] _
            , [204, "HTTP_STATUS_NO_CONTENT", "The server has fulfilled the request, but there is no new information to send back."] _
            , [205, "HTTP_STATUS_RESET_CONTENT", "The request has been completed, and the client program should reset the document view that caused the request to be sent to allow the user to easily initiate another input action."] _
            , [206, "HTTP_STATUS_PARTIAL_CONTENT", "The server has fulfilled the partial GET request for the resource."] _
            , [207, "HTTP_STATUS_WEBDAV_MULTI_STATUS", "During a World Wide Web Distributed Authoring and Versioning (WebDAV) operation, this indicates multiple status codes for a single response. The response body contains Extensible Markup Language (XML) that describes the status codes."] _
            , [300, "HTTP_STATUS_AMBIGUOUS", "The requested resource is available at one or more locations."] _
            , [301, "HTTP_STATUS_MOVED", "The requested resource has been assigned to a new permanent Uniform Resource Identifier (URI), and any future references to this resource should be done using one of the returned URIs."] _
            , [302, "HTTP_STATUS_REDIRECT", "The requested resource resides temporarily under a different URI."] _
            , [303, "HTTP_STATUS_REDIRECT_METHOD", "The response to the request can be found under a different URI and should be retrieved using a GET HTTP verb on that resource."] _
            , [304, "HTTP_STATUS_NOT_MODIFIED", "The requested resource has not been modified."] _
            , [305, "HTTP_STATUS_USE_PROXY", "The requested resource must be accessed through the proxy given by the location field."] _
            , [307, "HTTP_STATUS_REDIRECT_KEEP_VERB", "The redirected request keeps the same HTTP verb. HTTP/1.1 behavior."] _
            , [400, "HTTP_STATUS_BAD_REQUEST", "The request could not be processed by the server due to invalid syntax."] _
            , [401, "HTTP_STATUS_DENIED", "The requested resource requires user authentication."] _
            , [402, "HTTP_STATUS_PAYMENT_REQ", "Not implemented in the HTTP protocol."] _
            , [403, "HTTP_STATUS_FORBIDDEN", "The server understood the request, but cannot fulfill it."] _
            , [404, "HTTP_STATUS_NOT_FOUND", "The server has not found anything that matches the requested URI."] _
            , [405, "HTTP_STATUS_BAD_METHOD", "The HTTP verb used is not allowed."] _
            , [406, "HTTP_STATUS_NONE_ACCEPTABLE", "No responses acceptable to the client were found."] _
            , [407, "HTTP_STATUS_PROXY_AUTH_REQ", "Proxy authentication required."] _
            , [408, "HTTP_STATUS_REQUEST_TIMEOUT", "The server timed out waiting for the request."] _
            , [409, "HTTP_STATUS_CONFLICT", "The request could not be completed due to a conflict with the current state of the resource. The user should resubmit with more information."] _
            , [410, "HTTP_STATUS_GONE", "The requested resource is no longer available at the server, and no forwarding address is known."] _
            , [411, "HTTP_STATUS_LENGTH_REQUIRED", "The server cannot accept the request without a defined content length."] _
            , [412, "HTTP_STATUS_PRECOND_FAILED", "The precondition given in one or more of the request header fields evaluated to false when it was tested on the server."] _
            , [413, "HTTP_STATUS_REQUEST_TOO_LARGE", "The server cannot process the request because the request entity is larger than the server is able to process."] _
            , [414, "HTTP_STATUS_URI_TOO_LONG", "The server cannot service the request because the request URI is longer than the server can interpret."] _
            , [415, "HTTP_STATUS_UNSUPPORTED_MEDIA", "The server cannot service the request because the entity of the request is in a format not supported by the requested resource for the requested method."] _
            , [449, "HTTP_STATUS_RETRY_WITH", "The request should be retried after doing the appropriate action."] _
            , [500, "HTTP_STATUS_SERVER_ERROR", "The server encountered an unexpected condition that prevented it from fulfilling the request."] _
            , [501, "HTTP_STATUS_NOT_SUPPORTED", "The server does not support the functionality required to fulfill the request."] _
            , [502, "HTTP_STATUS_BAD_GATEWAY", "The server, while acting as a gateway or proxy, received an invalid response from the upstream server it accessed in attempting to fulfill the request."] _
            , [503, "HTTP_STATUS_SERVICE_UNAVAIL", "The service is temporarily overloaded."] _
            , [504, "HTTP_STATUS_GATEWAY_TIMEOUT", "The request was timed out waiting for a gateway."] _
            , [505, "HTTP_STATUS_VERSION_NOT_SUP", "The server does not support the HTTP protocol version that was used in the request message."]]

    For $i = 1 To $HTTP_STATUS_CODES[0][0]
        If $HTTP_STATUS_CODES[$i][0] = $iStatus Then
            ;$aResult[0] = $HTTP_STATUS_CODES[$i][0]
            $aResult[1] = $HTTP_STATUS_CODES[$i][1]
            $aResult[2] = $HTTP_STATUS_CODES[$i][2]
            ExitLoop
        EndIf
    Next
    ConsoleWrite("! $aResult[0]=" & $aResult[0] & ", [1]=" & $aResult[1] & ", [2]=" & $aResult[2] & @TAB & "( " & $sResponseText & " )" & @CRLF)
    $aResult[2] &= @CRLF & @CRLF & "Details: " & $sResponseText
    Return $aResult
EndFunc   ;==>HTTP_STATUS_CODES
;---------------------------------------------------------------------------------------
Func DW($sString, $iLine = @ScriptLineNumber)
    If Not $DebugInfo Then Return
    ConsoleWrite("(" & $iLine & ") " & $sString)
EndFunc   ;==>DW
;---------------------------------------------------------------------------------------
Func Json_EscapeString(ByRef $sString)
    ;=== Converts normal string to a JSON-safe string ===

    ; Escape backslashes first to avoid double-processing
    $sString = StringReplace($sString, '\', '\\', 0, 1)

    ; Escape known JSON control characters
    $sString = StringReplace($sString, Chr(8), '\b', 0, 1)   ; Backspace
    $sString = StringReplace($sString, Chr(12), '\f', 0, 1)  ; Formfeed
    $sString = StringReplace($sString, @CRLF, '\n', 0, 1)    ; CRLF → \n
    $sString = StringReplace($sString, @LF, '\n', 0, 1)      ; LF → \n
    $sString = StringReplace($sString, @CR, '\r', 0, 1)      ; CR → \r
    $sString = StringReplace($sString, @TAB, '\t', 0, 1)     ; Tab → \t

    ; Escape double quotes
    $sString = StringReplace($sString, '"', '\"', 0, 1)

    ; Escape  \u00XX characters
    ; nothing yet

    Return $sString
EndFunc   ;==>Json_EscapeString
;---------------------------------------------------------------------------------------
Func Json_UnEscapeString(ByRef $sString)
    ;=== Converts a JSON-escaped string back into normal string ===

    If StringInStr($sString, '\') = 0 Then Return $sString

    ; Unescape quotes and backslashes first
    $sString = StringReplace($sString, '\"', '"', 0, 1)
    $sString = StringReplace($sString, '\\', '\', 0, 1)

    ; Unescape standard control characters
    $sString = StringReplace($sString, '\b', Chr(8), 0, 1)
    $sString = StringReplace($sString, '\f', Chr(12), 0, 1)
    $sString = StringReplace($sString, '\n', @LF, 0, 1)
    $sString = StringReplace($sString, '\r', @CR, 0, 1)
    $sString = StringReplace($sString, '\t', @TAB, 0, 1)

    ; Unescape \u00XX characters
    $sString = StringReplace($sString, '\u003c', '<', 0, 1)
    $sString = StringReplace($sString, '\u003e', '>', 0, 1)

    Return $sString
EndFunc   ;==>Json_UnEscapeString
;---------------------------------------------------------------------------------------
Func Json_Map($sJson)
    ;=== It not support numbers, booleans, arrays or nested objects. ===

    Local $mJson[]
    ; Remove curly braces and trim
    $sJson = StringStripWS(StringTrimLeft(StringTrimRight($sJson, 1), 1), 3)
    ; Match all "key": "value" pairs
    Local $aPairs = StringRegExp($sJson, '"([^"]+)"\s*:\s*"((?:\\.|[^"\\])*)"', 3)
    If @error Then Return SetError(2, 0, 0)
    For $i = 0 To UBound($aPairs) - 1 Step 2
        Local $sKey = $aPairs[$i]
        Local $sVal = $aPairs[$i + 1]
        Json_UnEscapeString($sVal)
        $mJson[$sKey] = $sVal
        ;ConsoleWrite("$sKey=" & $sKey & @CRLF)
    Next
    Return $mJson
EndFunc   ;==>Json_Map
;---------------------------------------------------------------------------------------

 

I will update the thread as soon as anything new comes up.

 

Please, every comment is appreciated!
leave your comments and experiences here!
Thank you very much  :)

 

 

Edited by ioa747
update to Version: 0.7

I know that I know nothing

Posted (edited)
... ...
Global $__g_OllamaAssistant_DefaultURL = "http://localhost:11434"

Func OllamaAssistant_DefaultURL($sStr = Default)
    If $sStr <> Default Then $__g_OllamaAssistant_DefaultURL = $sStr
    Return $__g_OllamaAssistant_DefaultURL
EndFunc

ConsoleWrite(Ollama_IsModelAvailable() & @CRLF)
If @error Then Exit 5

Func Ollama_IsModelAvailable($sStr = "qwen2.5-coder")
    Local $sRet = OllamaAPI() ; default is get models at default URL
    If @error Then
        ConsoleWrite("! Ollama is not running there  =(" & @CRLF)
        Return SetError(1, 0, "")
    EndIf
    If StringInStr($sRet, $sStr) Then Return $sRet
    ConsoleWrite(@CRLF & $sRet & @CRLF & "! the model was not found. Pull it !  =)" & @CRLF )
    Return SetError(2, 0, $sRet)
EndFunc

; "/api/tags" ; List models that are available locally.
; "/api/ps" ; List models that are currently loaded into memory.
; "/api/pull"  '{"model": "qwen2.5-coder:3b"}' ; well, pull the model
Func OllamaAPI($sAPI = "/api/tags", $sURL = $__g_OllamaAssistant_DefaultURL)
    Local $sStr = BinaryToString(InetRead($sURL & $sAPI))
    ; this is not the function I should use, but it works to present the idea

    Return SetError(@error, @extended, $sStr)
EndFunc ; ..you may need to retry at times ?, ..I don't have much experience with it to know much

ConsoleWrite('HotKeySet = ' & HotKeySet("{END}", "GenerateFunctionHeader") & @CRLF) ; * <--
... ...

Nice !.
I was thinking yesterday that "I don't wanna leave SciTE" in a child tantrum kind of childish voice :lol:

So, I highlighted a function, pressed the end key and got me a "1" in the console.
But I don't know if is all that is so, add a start and end line, like:

---- 8< ---- start
1
---- >8 ---- end

so I get some feedback.

Again, thanks for working on my wish :) 

Edited by argumentum
fixed OllamaAPI() default

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting.
autoit_scripter_blue_userbar.png

Posted (edited)
... ...
    If $oHttp.Status <> 200 Then
;~      MsgBox(0, "Error", "WinHttp request failed with status code: " & $oHttp.Status)
        FileWriteLine(StringTrimRight(@ScriptFullPath, 4) & ".log", 'Error: $oHttp.Status:' & $oHttp.Status & @CRLF)
... ...

Error: $oHttp.Status:400  :(
What does it mean ?


I pasted the question in open-webui ( ..not quite like that but I forgot what I did )

Spoiler
You are an AutoIt documentation assistant. Your task is to generate precise header comments for AutoIt functions, using the standard UDF format.
Now generate a header comment for the following function:
Func Ollama_IsModelAvailable($sStr = "qwen2.5-coder")
    Local $sRet = OllamaAPI() ; default is get models at default URL
    If @error Then
        ConsoleWrite("! Ollama is not running there  =(" & @CRLF)
        Return SetError(1, 0, "")
    EndIf
    If StringInStr($sRet, $sStr) Then Return $sRet
    ConsoleWrite(@CRLF & $sRet & @CRLF & "! the model was not found. Pull it !  =)" & @CRLF )
    Return SetError(2, 0, $sRet)
EndFunc

and returned it's answer:

Spoiler
qwen2.5-coder:latest
Today at 1:45 PM

Certainly! Below is a header comment for the Ollama_IsModelAvailable function in AutoIt using the standard UDF format:

; #FUNCTION# ===================================================================================================================
;
; Name...........: Ollama_IsModelAvailable
; Description....: Checks if an AI model specified by name is available.
; Syntax.........: $sStr = "qwen2.5-coder" (optional) - The name of the model to check.
; Parameters.....:
;   $sStr         - [optional] The name of the model to check. Default is "qwen2.5-coder".
;
; Return values  :
;               Success: Returns the available models if the specified model is found.
;               Failure: Sets @error and returns an empty string with a custom message.
;               @error = 1: Ollama is not running at the default URL.
;               @error = 2: The specified model was not found.
;
; Author.........: [Your Name]
; Remarks........: This function assumes that there is an API endpoint for checking available models.
;                  It uses the default URL if no other is provided.
;
; Related........:
; Link...........:
; Example........:
;
; #FUNCTION# ===================================================================================================================
Func Ollama_IsModelAvailable($sStr = "qwen2.5-coder")
    Local $sRet = OllamaAPI() ; default is get models at default URL
    If @error Then
        ConsoleWrite("! Ollama is not running there  =(" & @CRLF)
        Return SetError(1, 0, "")
    EndIf
    If StringInStr($sRet, $sStr) Then Return $sRet
    ConsoleWrite(@CRLF & $sRet & @CRLF & "! the model was not found. Pull it !  =)" & @CRLF )
    Return SetError(2, 0, $sRet)
EndFunc

This header comment follows the standard UDF format and includes:

  • The function name and its description.
  • Parameters and their descriptions.
  • Return values with error codes and custom messages.
  • Author and remarks (indicating that it assumes an API endpoint for checking available models).
  • Related functions, links, and examples if applicable.

 so, maybe sending the $sSystem and $sPrompt as a single string is better ?
( am just talking out of my ... imagination here )

Edited by argumentum
more

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting.
autoit_scripter_blue_userbar.png

Posted
... ...
    $sRequest &= '"system": "' & $sSystem & '", '
    $sPrompt = StringReplace($sPrompt, @CRLF, "\r\n")
    $sPrompt = StringReplace($sPrompt, @TAB, "\t")
    $sPrompt = StringReplace($sPrompt, '"', "\""")
    $sRequest &= '"prompt": "' & $sPrompt & '", '
... ...

..it needed to escape stuff. All good :) 

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting.
autoit_scripter_blue_userbar.png

Posted
7 minutes ago, argumentum said:

I understand you answer. But it only worked for me when I "man handle" the string 🤷‍♂️

Quote
#include "JSON.au3"   ;" https://www.autoitscript.com/forum/topic/209502-json-udf-in-pure-autoit/#comment-1511873 "

I went to the git and pulled the file from there. Maybe the version there is bad ?

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting.
autoit_scripter_blue_userbar.png

Posted (edited)

i have this

Spoiler

https://github.com/Sylvan86/autoit-json-udf

#include-once

; #INDEX# =======================================================================================================================
; Title .........: JSON-UDF
; Version .......: 0.10
; AutoIt Version : 3.3.16.1
; Language ......: english (german maybe by accident)
; Description ...: Function for interacting with JSON data in AutoIt.
;                  This includes import, export as well as helper functions for handling nested AutoIt data structures.
; Author(s) .....: AspirinJunkie, Sven Seyfert (SOLVE-SMART)
; Last changed ..: 2023-02-20
; Link ..........: https://autoit.de/thread/85435-json-udf/
; License .......: This work is free.
;                  You can redistribute it and/or modify it under the terms of the Do What The Fuck You Want To Public License, Version 2,
;                  as published by Sam Hocevar.
;                  See http://www.wtfpl.net/ for more details.
; ===============================================================================================================================

; #Function list# =======================================================================================================================
; ---- import and export from or to json ------
;  _JSON_Parse               - converts a JSON-structured string into a nested AutoIt data structure
;  _JSON_Generate            - converts a nested AutoIt data structure into a JSON structured string
;  _JSON_GenerateCompact     - shorthand for _JSON_Generate() to create JSON structured strings as compact as possible
;  _JSON_Unminify            - reads minified (compact) JSON file or string and converts to well readable JSON string
;  _JSON_Minify              - reads unminified (readable) JSON file or string and converts to minified (compact) JSON string
;
; ---- extraction and manipulation of nested AutoIt data structures ----
;  _JSON_Get                 - extract query nested AutoIt-datastructure with a simple selector string
;  _JSON_addChangeDelete     - create a nested AutoIt data structure, change values within existing structures or delete elements from a nested AutoIt data structure
;
; ---- helper functions ----
;      __JSON_FormatString   - converts a string into a json string by escaping the special symbols
;      __JSON_ParseString    - converts a json formatted string into an AutoIt-string by unescaping the json-escapes
;      __JSON_A2DToAinA      - converts a 2D array into a Arrays in Array
;      __JSON_AinAToA2d      - converts a Arrays in Array into a 2D array
;      __JSON_Base64Decode   - decode data which is coded as a base64-string into binary variable
;      __JSON_Base64Encode   - converts a binary- or string-Input into BASE64 (or optional base64url) format
; ===============================================================================================================================

#include <String.au3>

; #FUNCTION# ======================================================================================
; Name ..........: _JSON_Parse
; Description ...: convert a JSON-formatted string into a nested structure of AutoIt-datatypes
; Syntax ........: _JSON_Parse(ByRef $s_String, $i_Os = 1)
; Parameters ....: $s_String      - a string formatted as JSON
;                  [$i_Os]        - search position where to start (normally don't touch!)
; Return values .: Success - Return a nested structure of AutoIt-datatypes
;                       @extended = next string offset
;                  Failure - Return "" and set @error to:
;                       @error = 1 - part is not json-syntax
;                              = 2 - key name in object part is not json-syntax
;                              = 3 - value in object is not correct json
;                              = 4 - delimiter or object end expected but not gained
; Author ........: AspirinJunkie
; =================================================================================================
Func _JSON_Parse(ByRef $s_String, $i_Os = 1)
    Local $i_OsC = $i_Os, $o_Current, $o_Value
    ; Inside a character class, \R is treated as an unrecognized escape sequence, and so matches the letter "R" by default, but causes an error if
    Local Static _ ; '\s' = [\x20\x09\x0A\x0D]
            $s_RE_G_String = '\G\s*"([^"\\]*+(?>\\.[^"\\]*+)*+)"', _ ; old variant: '\G\s*"((?>[^\\"]+|\\.)*+)"' - new one is more efficient coz it searches firstly for non quotes and bs - these are more unlikely
            $s_RE_G_Number = '\G\s*(-?(?>0|[1-9]\d*)(?>\.\d+)?(?>[eE][-+]?\d+)?)', _
            $s_RE_G_KeyWord = '\G\s*\b(null|true|false)\b', _
            $s_RE_G_Object_Begin = '\G\s*\{', _
            $s_RE_G_Object_Key = '\G\s*"((?>[^\\"]+|\\.)*+)"\s*:', _
            $s_RE_G_Object_Further = '\G\s*,', _
            $s_RE_G_Object_End = '\G\s*\}', _
            $s_RE_G_Array_Begin = '\G\s*\[', _
            $s_RE_G_Array_End = '\G\s*\]'

    $o_Current = StringRegExp($s_String, $s_RE_G_String, 1, $i_Os) ; String
    If Not @error Then Return SetExtended(@extended, __JSON_ParseString($o_Current[0]))


    StringRegExp($s_String, $s_RE_G_Object_Begin, 1, $i_Os) ; Object
    If Not @error Then
        $i_OsC = @extended
        Local $s_Key, $a_T

        Local $o_Current[]

        StringRegExp($s_String, $s_RE_G_Object_End, 1, $i_OsC)     ; check for empty object
        If Not @error Then     ; empty object
            $i_OsC = @extended
        Else
            Do
                $a_T = StringRegExp($s_String, $s_RE_G_Object_Key, 1, $i_OsC)     ; key of element
                If @error Then Return SetError(2, $i_OsC, "")
                $i_OsC = @extended

                $s_Key = __JSON_ParseString($a_T[0])

                $o_Value = _JSON_Parse($s_String, $i_OsC)
                If @error Then Return SetError(3, $i_OsC, "")
                $i_OsC = @extended

                $o_Current[$s_Key] = $o_Value     ; add key:value to map

                StringRegExp($s_String, $s_RE_G_Object_Further, 1, $i_OsC)     ; more elements
                If Not @error Then
                    $i_OsC = @extended
                    ContinueLoop
                Else
                    StringRegExp($s_String, $s_RE_G_Object_End, 1, $i_OsC)     ; end of array
                    If Not @error Then
                        $i_OsC = @extended
                        ExitLoop
                    Else
                        Return SetError(4, $i_OsC, "")
                    EndIf
                EndIf
            Until False
        EndIf

        Return SetExtended($i_OsC, $o_Current)
    EndIf


    StringRegExp($s_String, $s_RE_G_Array_Begin, 1, $i_Os) ; Array
    If Not @error Then
        $i_OsC = @extended
        Local $o_Current[1], $d_N = 1, $i_C = 0

        StringRegExp($s_String, $s_RE_G_Array_End, 1, $i_OsC) ; check for empty array
        If Not @error Then ; empty array
            ReDim $o_Current[0]
            $i_OsC = @extended
            Return SetExtended($i_OsC, $o_Current)
        EndIf

        Do
            $o_Value = _JSON_Parse($s_String, $i_OsC)
            If @error Then Return SetError(3, $i_OsC, "")
            $i_OsC = @extended

            If $i_C = $d_N - 1 Then
                $d_N += $d_N  ; or *= 2
                ReDim $o_Current[$d_N]
            EndIf
            $o_Current[$i_C] = $o_Value
            $i_C += 1

            StringRegExp($s_String, $s_RE_G_Object_Further, 1, $i_OsC) ; more elements
            If Not @error Then
                $i_OsC = @extended
                ContinueLoop
            Else
                StringRegExp($s_String, $s_RE_G_Array_End, 1, $i_OsC) ; end of array
                If Not @error Then
                    $i_OsC = @extended
                    ExitLoop
                Else
                    Return SetError(5, $i_OsC, "")
                EndIf
            EndIf

        Until False

        If UBound($o_Current) <> $i_C Then ReDim $o_Current[$i_C]
        Return SetExtended($i_OsC, $o_Current)
    EndIf

    $o_Current = StringRegExp($s_String, $s_RE_G_Number, 1, $i_Os) ; Number
    If Not @error Then Return SetExtended(@extended, Number($o_Current[0]))

    $o_Current = StringRegExp($s_String, $s_RE_G_KeyWord, 1, $i_Os) ; KeyWord
    If Not @error Then Return SetExtended(@extended, Execute($o_Current[0])) ; $o_Current[0] = "null" ? Null : $o_Current[0] = "true" ? True : False)

    Return SetError(1, $i_OsC, "")
EndFunc   ;==>_JSON_Parse


; #FUNCTION# ======================================================================================
; Name ..........: _JSON_Generate
; Description ...: converts a nested AutoIt data structure into a JSON structured string
; Syntax ........: _JSON_Generate($o_Object, $s_ObjIndent = @TAB, $s_ObjDelEl = @CRLF, $s_ObjDelKey = " ", $s_ObjDelVal = "", $s_ArrIndent = @TAB, $s_ArrDelEl = @CRLF, $i_Level = 0)
; Parameters ....: $o_Object      - [nested] AutoIt data structure
;                  [$s_ObjIndent] - indent for object elements (only reasonable if $s_ObjDelEl contains a line skip
;                  [$s_ObjDelEl]  - delimiter between object elements
;                  [$s_ObjDelKey] - delimiter between keyname and ":" in object
;                  [$s_ObjDelVal] - delimiter between ":" and value in object
;                  [$s_ArrIndent] - indent for array elements (only reasonable if $s_ArrDelEl contains a line skip)
;                  [$s_ArrDelEl]  - delimiter between array elements
;                  [$i_Level]     - search position where to start (normally don't touch!)
; Return values .: Success - Return a JSON formatted string
;                  Failure - Return ""
; Author ........: AspirinJunkie
; =================================================================================================
Func _JSON_Generate($o_Object, $s_ObjIndent = @TAB, $s_ObjDelEl = @CRLF, $s_ObjDelKey = "", $s_ObjDelVal = " ", $s_ArrIndent = @TAB, $s_ArrDelEl = @CRLF, $i_Level = 0)
    Local Static $s_JSON_String
    If $i_Level = 0 Then $s_JSON_String = ""

    Switch VarGetType($o_Object)
        Case "String"
            __JSON_FormatString($o_Object)
            $s_JSON_String &= '"' & $o_Object & '"'
        Case "Int32", "Int64", "Float", "Double"
            $s_JSON_String &= String($o_Object)
        Case "Bool"
            $s_JSON_String &= StringLower($o_Object)
        Case "Keyword"
            If IsKeyword($o_Object) = 2 Then $s_JSON_String &= "null"
        Case "Binary"
            $s_JSON_String &= '"' & __JSON_Base64Encode($o_Object) & '"'
        Case "Array"
            If UBound($o_Object, 0) = 2 Then $o_Object = __JSON_A2DToAinA($o_Object)
            If UBound($o_Object) = 0 Then
                $s_JSON_String &= "[]"
            Else
                $s_JSON_String &= "[" & $s_ArrDelEl
                For $o_Value In $o_Object
                    $s_JSON_String &= _StringRepeat($s_ArrIndent, $i_Level + 1)
                    _JSON_Generate($o_Value, $s_ObjIndent, $s_ObjDelEl, $s_ObjDelKey, $s_ObjDelVal, $s_ArrIndent, $s_ArrDelEl, $i_Level + 1)

                    $s_JSON_String &= "," & $s_ArrDelEl
                Next
                $s_JSON_String = StringTrimRight($s_JSON_String, StringLen("," & $s_ArrDelEl)) & $s_ArrDelEl & _StringRepeat($s_ArrIndent, $i_Level) & "]"
            EndIf
        Case "Object"
            If ObjName($o_Object) = "Dictionary" Then
                Local $s_KeyTemp, $o_Value
                If $o_Object.Count() = 0 Then
                    $s_JSON_String &= "{}"
                Else
                    $s_JSON_String &= "{" & $s_ObjDelEl
                    For $s_Key In $o_Object.Keys
                        $s_KeyTemp = $s_Key
                        $o_Value = $o_Object($s_Key)
                        __JSON_FormatString($s_KeyTemp)

                        $s_JSON_String &= _StringRepeat($s_ObjIndent, $i_Level + 1) & '"' & $s_KeyTemp & '"' & $s_ObjDelKey & ':' & $s_ObjDelVal

                        _JSON_Generate($o_Value, $s_ObjIndent, $s_ObjDelEl, $s_ObjDelKey, $s_ObjDelVal, $s_ArrIndent, $s_ArrDelEl, $i_Level + 1)

                        $s_JSON_String &= "," & $s_ObjDelEl
                    Next
                    $s_JSON_String = StringTrimRight($s_JSON_String, StringLen("," & $s_ObjDelEl)) & $s_ObjDelEl & _StringRepeat($s_ObjIndent, $i_Level) & "}"
                EndIf
            EndIf
        Case "Map"
            Local $s_KeyTemp, $o_Value
            If UBound($o_Object) = 0 Then
                $s_JSON_String &= "{}"
            Else
                $s_JSON_String &= "{" & $s_ObjDelEl
                For $s_Key In MapKeys($o_Object)
                    $s_KeyTemp = $s_Key
                    $o_Value = $o_Object[$s_Key]
                    __JSON_FormatString($s_KeyTemp)

                    $s_JSON_String &= _StringRepeat($s_ObjIndent, $i_Level + 1) & '"' & $s_KeyTemp & '"' & $s_ObjDelKey & ':' & $s_ObjDelVal

                    _JSON_Generate($o_Value, $s_ObjIndent, $s_ObjDelEl, $s_ObjDelKey, $s_ObjDelVal, $s_ArrIndent, $s_ArrDelEl, $i_Level + 1)

                    $s_JSON_String &= "," & $s_ObjDelEl
                Next
                $s_JSON_String = StringTrimRight($s_JSON_String, StringLen("," & $s_ObjDelEl)) & $s_ObjDelEl & _StringRepeat($s_ObjIndent, $i_Level) & "}"
            EndIf
    EndSwitch

    If $i_Level = 0 Then
        Local $s_Temp = $s_JSON_String
        $s_JSON_String = ""
        Return $s_Temp
    EndIf
EndFunc   ;==>_JSON_Generate

; #FUNCTION# ====================================================================================================================
; Name ..........: _JSON_GenerateCompact
; Description ...: shorthand for _JSON_Generate()-parameters to produce a compact as possible JSON string
; Syntax ........: _JSON_GenerateCompact($o_Object)
; Parameters ....: $o_Object            - [nested] AutoIt data structure
; Return values .: Success - Return a JSON formatted string
;                  Failure - Return ""
; Author ........: AspirinJunkie
; Modified ......: 2023-05-23
; Related .......: _JSON_Generate
; ===============================================================================================================================
Func _JSON_GenerateCompact($o_Object)
    Return _JSON_Generate($o_Object, "", "", "", "", "", "")
EndFunc   ;==>_JSON_GenerateCompact

; #FUNCTION# ====================================================================================================================
; Name ..........: _JSON_Unminify
; Description ...: reads minified (compact) JSON file or string and converts to well readable JSON string
; Syntax ........: _JSON_Unminify($s_Input)
; Parameters ....: $s_Input - json file path/handle or json string
; Return values .: Success - Return a JSON formatted string
;                  Failure - Return "" and set @error to:
;                       @error = 1 - error during FileRead() - @extended = @error from FileRead()
;                              = 2 - no valid format for $s_Input
; Author ........: Sven Seyfert (SOLVE-SMART), AspirinJunkie
; Related .......: _JSON_Generate
; ===============================================================================================================================
Func _JSON_Unminify($s_Input)
    ; read file if $sInput = file name or file handle
    If FileExists($s_Input) Or IsInt($s_Input) Then $s_Input = FileRead($s_Input)
    If @error Then Return SetError(1, @error, False)
    If Not IsString($s_Input) Then Return SetError(2, 0, False)

    Local Const $o_Object = _JSON_Parse($s_Input)
    Return _JSON_Generate($o_Object)
EndFunc   ;==>_JSON_Unminify

; #FUNCTION# ====================================================================================================================
; Name ..........: _JSON_Minify
; Description ...: reads unminified (readable) JSON file or string and converts to minified (compact) JSON string
; Syntax ........: _JSON_Minify($s_Input)
; Parameters ....: $s_Input - json file path/handle or json string
; Return values .: Success - Return a JSON formatted string
;                  Failure - Return "" and set @error to:
;                       @error = 1 - error during FileRead() - @extended = @error from FileRead()
;                              = 2 - no valid format for $s_Input
; Author ........: Sven Seyfert (SOLVE-SMART), AspirinJunkie
; Related .......: _JSON_GenerateCompact
; ===============================================================================================================================
Func _JSON_Minify($s_Input)
    ; read file if $sInput = file name or file handle
    If FileExists($s_Input) Or IsInt($s_Input) Then $s_Input = FileRead($s_Input)
    If @error Then Return SetError(1, @error, False)
    If Not IsString($s_Input) Then Return SetError(2, 0, False)

    Local Const $o_Object = _JSON_Parse($s_Input)
    Return _JSON_GenerateCompact($o_Object)
EndFunc   ;==>_JSON_Minify

; #FUNCTION# ======================================================================================
; Name ..........: _JSON_Get
; Description ...: query nested AutoIt-datastructure with a simple query string with syntax:
;                  MapKey#1.MapKey#2.[ArrayIndex#1].MapKey#3... (points keynames can be achieved by "\.")
;                  multidimensional (2D or 3D only) array indices are separated through comma - e.g.: [2,3]
; Syntax ........: _JSON_Get(ByRef $o_Object, Const $s_Pattern)
; Parameters ....: $o_Object      - a nested AutoIt datastructure (Arrays, Dictionaries, basic scalar types)
;                  $s_Pattern     - query pattern like described above
; Return values .: Success - Return the queried object out of the nested datastructure
;                  Failure - Return "" and set @error to:
;                       @error = 1 - pattern is not correct
;                              = 2 - keyname query to none dictionary object
;                              = 3 - keyname queried not exists in dictionary
;                              = 4 - index query on none array object
;                              = 5 - index out of array range
;                              = 6 - number of subindices in index query not match array dimensions
;                              = 7 - more than 3 array dimensions are not supported
; Author ........: AspirinJunkie
; =================================================================================================
Func _JSON_Get(ByRef $o_Object, Const $s_Pattern)
    Local $o_Current = $o_Object, $d_Val
    Local $a_Tokens = StringRegExp($s_Pattern, '\[(\d+|[\d\h,]+)\]|((?>\\.|[^\.\[\]\\]+)+)', 4)
    If @error Then Return SetError(1, @error, "")

    For $a_CurToken In $a_Tokens

        If UBound($a_CurToken) = 3 Then ; KeyName
            $a_CurToken[2] = StringRegExpReplace($a_CurToken[2], '\\(.)', '$1')
            Switch VarGetType($o_Current)
                Case "Object"
                    If Not IsObj($o_Current) Or ObjName($o_Current) <> "Dictionary" Then Return SetError(2, 0, "")
                    If Not $o_Current.Exists($a_CurToken[2]) Then Return SetError(3, 0, "")

                    $o_Current = $o_Current($a_CurToken[2])
                Case "Map"
                    If Not MapExists($o_Current, $a_CurToken[2]) Then Return SetError(3, 0, "")

                    $o_Current = $o_Current[$a_CurToken[2]]
            EndSwitch
        ElseIf UBound($a_CurToken) = 2 Then ; ArrayIndex
            If (Not IsArray($o_Current)) Then Return SetError(4, UBound($o_Current, 0), "")

            ; multi dimensional array
            If StringInStr($a_CurToken[1], ',', 1) Then
                Local $aIndices = StringSplit($a_CurToken[1], ',', 3)
                If UBound($aIndices) <> UBound($o_Current, 0) Then Return SetError(6, UBound($o_Current, 0), "")

                ; get the indices and check their range
                Local $x = Int($aIndices[0]), $y = Int($aIndices[1])
                If $x < 0 Or $x >= UBound($o_Current, 1) Then Return SetError(5, $x, "")
                If $y < 0 Or $y >= UBound($o_Current, 2) Then Return SetError(5, $y, "")
                Switch UBound($aIndices)
                    Case 2 ; 2D array
                        $o_Current = $o_Current[$x][$y]
                    Case 3 ; 3D array
                        Local $z = Int($aIndices[2])
                        If $z < 0 Or $z >= UBound($o_Current, 3) Then Return SetError(5, $z, "")
                        $o_Current = $o_Current[$x][$y][$z]
                    Case Else
                        Return SetError(7, @error, "")
                EndSwitch

                ; 1D array
            Else
                If UBound($o_Current, 0) <> 1 Then Return SetError(6, UBound($o_Current, 0), "")
                $d_Val = Int($a_CurToken[1])
                If $d_Val < 0 Or $d_Val >= UBound($o_Current) Then Return SetError(5, $d_Val, "")
                $o_Current = $o_Current[$d_Val]
            EndIf
        EndIf
    Next
    Return $o_Current
EndFunc   ;==>_JSON_Get


; #FUNCTION# ======================================================================================
; Name ..........: _JSON_addChangeDelete
; Description ...: creates, modifies or deletes within nested AutoIt structures with a simple query string with syntax:
;                  MapKey#1.MapKey#2.[ArrayIndex#1].MapKey#3...  (points keynames can be achieved by "\.")
;                  If the specified structure already exists, then the function overwrite the existing data.
;                  If the specified structure not exists, then the functions creates this structure.
;                  If $vVal = Default, then the function deletes this specific data point inside the structure.
; Syntax ........: _JSON_addChangeDelete(ByRef $oObject, Const $sPattern, Const $vVal = Default [, Const $iRecLevel = 0])
; Parameters ....: $oObject    - a nested AutoIt datastructure (Arrays, Maps, basic scalar types etc.)
;                                in which the structure is to be created or data is to be changed or deleted
;                  $sPattern   - query pattern like described above
;                  $vVal       - the value which should be written at the position in $sPattern
;                              - if $vVal = Default then the position in $sPattern is to be deleted
;                  $iRecLevel  - don't touch! - only for internal purposes
; Return values .: Success - Return True
;                  Failure - Return False and set @error to:
;                       @error = 1 - pattern is not correct
;                       @error = 2 - wrong index for array element
; Author ........: AspirinJunkie
; =================================================================================================
Func _JSON_addChangeDelete(ByRef $oObject, Const $sPattern, Const $vVal = Default, Const $iRecLevel = 0)
    Local Static $aLevels[0]

    ; only on highest recursion level: process the selector string
    If $iRecLevel = 0 Then
        Local $aToken = StringRegExp($sPattern, '\[(\d+)\]|((?>\\.|[^\.\[\]\\]+)+)', 4)
        If @error Then Return SetError(1, @error, "")

        Local $aCurToken

        ReDim $aLevels[UBound($aToken) + 1][2]
        For $i = 0 To UBound($aToken) - 1
            $aCurToken = $aToken[$i]
            If UBound($aCurToken) = 3 Then ; KeyName
                $aLevels[$i][0] = "Map"
                $aLevels[$i][1] = StringRegExpReplace($aCurToken[2], '\\(.)', '$1')
            Else ; Array Index
                $aLevels[$i][0] = "Array"
                $aLevels[$i][1] = Int($aCurToken[1])
            EndIf
        Next
        $aLevels[UBound($aLevels) - 1][0] = "end"
    EndIf

    ; If data structure not exists already - build it as stated in the selector string:
    Local $sCurrenttype = $aLevels[$iRecLevel][0]
    If $sCurrenttype <> VarGetType($oObject) Then
        Switch $sCurrenttype
            Case "Map"
                Local $mTmp[]
                $oObject = $mTmp
            Case "Array"
                Local $aTmp[$aLevels[$iRecLevel][1] + 1]
                $oObject = $aTmp
            Case "end"
                Return $vVal
        EndSwitch
    EndIf

    ; special case treatment for arrays
    If $sCurrenttype = "Array" Then
        If UBound($oObject, 0) <> 1 Then
            Local $aTmp[$aLevels[$iRecLevel][1] + 1]
            $oObject = $aTmp
        ElseIf UBound($oObject) < ($aLevels[$iRecLevel][1] + 1) Then
            ReDim $oObject[$aLevels[$iRecLevel][1] + 1]
        EndIf
    EndIf

    ; create or change the objects in the next hierarchical level and use these as value for the current entry
    Local $vTmp = $oObject[$aLevels[$iRecLevel][1]], _
            $oNext = _JSON_addChangeDelete($vTmp, $sPattern, $vVal, $iRecLevel + 1)

    If $oNext = Default Then ; delete the current level
        Switch $sCurrenttype
            Case "Map"
                MapRemove($oObject, $aLevels[$iRecLevel][1])
            Case "Array"
                Local $iInd = $aLevels[$iRecLevel][1], $nElems = UBound($oObject)

                If $iInd < 0 Or $iInd >= $nElems Then Return SetError(2, @error, "")

                For $i = $iInd To $nElems - 2
                    $oObject[$i] = $oObject[$i + 1]
                Next
                ReDim $oObject[$nElems - 1]
            Case Else
                $oObject[$aLevels[$iRecLevel][1]] = ""
                For $j = UBound($oObject) - 1 To 0 Step -1
                    If $oObject[$j] <> "" Then
                        ReDim $oObject[$j + 1]
                        ExitLoop
                    EndIf
                Next
        EndSwitch
    Else
        $oObject[$aLevels[$iRecLevel][1]] = $oNext
    EndIf

    If $iRecLevel > 0 Then
        Return $oObject
    Else
        ReDim $aLevels[0] ; clean
        Return True
    EndIf
EndFunc   ;==>_JSON_addChangeDelete

; helper function for converting a json formatted string into an AutoIt-string
; slower variant of __JSON_ParseString but also can handle large strings
Func __JSON_ParseString(ByRef $s_String)
    Local $aB[5]

    Local $a_RE = StringRegExp($s_String, '\\\\(*SKIP)(*FAIL)|(\\["bf/]|\\u[[:xdigit:]]{4})', 3)
    If Not @error Then
        For $s_Esc In $a_RE
            Switch StringMid($s_Esc, 2, 1)
                Case "b"
                    If $aB[0] Then ContinueLoop
                    $s_String = StringRegExpReplace($s_String, '\\\\(*SKIP)(*FAIL)|\\b', Chr(8))
                    $aB[0] = True
                Case "f"
                    If $aB[1] Then ContinueLoop
                    $s_String = StringRegExpReplace($s_String, '\\\\(*SKIP)(*FAIL)|\\f', Chr(12))
                    $aB[1] = True
                Case "/"
                    If $aB[2] Then ContinueLoop
                    $s_String = StringRegExpReplace($s_String, '\\\\(*SKIP)(*FAIL)|\\/', "/")
                    $aB[2] = True
                Case '"'
                    If $aB[3] Then ContinueLoop
                    $s_String = StringRegExpReplace($s_String, '\\\\(*SKIP)(*FAIL)|\\"', '"')
                    $aB[3] = True
                Case "u"
                    If $aB[4] Then ContinueLoop
                    $a_RE = StringRegExp($s_String, '\\\\(*SKIP)(*FAIL)|\\u\K[[:xdigit:]]{4}', 3)
                    If Not @error Then
                        If UBound($a_RE) > 10 Then
                            Local $mCodes[]
                            For $s_Code In $a_RE
                                If MapExists($mCodes, $s_Code) Then ContinueLoop
                                $s_String = StringReplace($s_String, "\u" & $s_Code, ChrW(Dec($s_Code)), 0, 1)
                                $mCodes[$s_Code] = ""
                            Next
                        Else
                            For $s_Code In $a_RE
                                $s_String = StringReplace($s_String, "\u" & $s_Code, ChrW(Dec($s_Code)), 0, 1)
                            Next
                        EndIf
                        $aB[4] = True
                    EndIf
            EndSwitch
        Next
    EndIf

    ; converts \n \r \t \\ implicit:
    Return StringFormat(StringReplace($s_String, "%", "%%", 0, 1))
EndFunc   ;==>__JSON_ParseString

; helper function for converting a AutoIt-string into a json formatted string
Func __JSON_FormatString(ByRef $s_String)
    $s_String = _
        StringReplace( _
            StringReplace( _
                StringReplace( _
                    StringReplace( _
                        StringReplace( _
                            StringReplace( _
                                StringReplace( _
                                    StringReplace($s_String, '\', '\\', 0, 1) _
                                , Chr(8), "\b", 0, 1) _
                            , Chr(12), "\f", 0, 1) _
                        , @CRLF, "\n", 0, 1) _
                    , @LF, "\n", 0, 1) _
                , @CR, "\r", 0, 1) _
            , @TAB, "\t", 0, 1) _
        , '"', '\"', 0, 1)
EndFunc   ;==>__JSON_FormatString


; #FUNCTION# ======================================================================================
; Name ..........: __JSON_Base64Encode
; Description ...: convert a binary- or string-Input into BASE64 (or optional base64url) format
;                  mainly a wrapper for the CryptBinaryToString API-function
; Syntax ........: __JSON_Base64Encode(Const ByRef $s_Input, [Const $b_base64url = False])
; Parameters ....: $s_Input       - binary data or string which should be converted
;                  [$b_base64url] - If true the output is in base64url-format instead of base64
; Return values .: Success - Return base64 (or base64url) formatted string
;                  Failure - Return "" and set @error to:
;                       @error = 1 - failure at the first run to calculate the output size
;                              = 2 - failure at the second run to calculate the output
; Author ........: AspirinJunkie
; Example .......: Yes
;                  $s_Base64String = __JSON_Base64Encode("This is my test")
; =================================================================================================
Func __JSON_Base64Encode(Const ByRef $s_Input, Const $b_base64url = False)
    Local $b_Input = IsBinary($s_Input) ? $s_Input : Binary($s_Input)

    Local $t_BinArray = DllStructCreate("BYTE[" & BinaryLen($s_Input) & "]")
    DllStructSetData($t_BinArray, 1, $b_Input)

    Local $h_DLL_Crypt32 = DllOpen("Crypt32.dll")

    ; first run to calculate needed size of output buffer
    Local $a_Ret = DllCall($h_DLL_Crypt32, "BOOLEAN", "CryptBinaryToString", _
            "STRUCT*", $t_BinArray, _     ; *pbBinary
            "DWORD", DllStructGetSize($t_BinArray), _     ; cbBinary
            "DWORD", 1, _     ; dwFlags
            "PTR", Null, _ ; pszString
            "DWORD*", 0)
    If @error Or Not IsArray($a_Ret) Or $a_Ret[0] = 0 Then Return SetError(1, @error, DllClose($h_DLL_Crypt32))

    ; second run to calculate base64-string:
    Local $t_Output = DllStructCreate("CHAR Out[" & $a_Ret[5] & "]")
    Local $a_Ret2 = DllCall($h_DLL_Crypt32, "BOOLEAN", "CryptBinaryToString", _
            "STRUCT*", $t_BinArray, _     ; *pbBinary
            "DWORD", DllStructGetSize($t_BinArray), _     ; cbBinary
            "DWORD", 1, _     ; dwFlags
            "STRUCT*", $t_Output, _ ; pszString
            "DWORD*", $a_Ret[5])
    If @error Or Not IsArray($a_Ret2) Or $a_Ret2[0] = 0 Then Return SetError(2, @error, DllClose($h_DLL_Crypt32))

    Local $s_Output = $t_Output.Out
    If StringInStr($s_Output, "=", 1, 1) Then $s_Output = StringLeft($s_Output, StringInStr($s_Output, "=", 1, 1) - 1)

    If $b_base64url Then $s_Output = StringReplace(StringReplace($s_Output, "/", "_", 0, 1), "+", "-", 0, 1)

    DllClose($h_DLL_Crypt32)
    Return $s_Output
EndFunc   ;==>__JSON_Base64Encode


; #FUNCTION# ======================================================================================
; Name ..........: __JSON_Base64Decode
; Description ...: decode data which is coded as a base64-string into binary form
;                  mainly a wrapper for the CryptStringToBinary API-function
; Syntax ........: __JSON_Base64Decode(Const ByRef $s_Input, [Const $b_base64url = False])
; Parameters ....: $s_Input       - string in base64-format
;                  [$b_base64url] - If true the output is in base64url-format instead of base64
; Return values .: Success - Return base64 (or base64url) formatted string
;                  Failure - Return "" and set @error to:
;                       @error = 1 - failure at the first run to calculate the output size
;                              = 2 - failure at the second run to calculate the output
; Author ........: AspirinJunkie
; Example .......: Yes
;                  MsgBox(0, '', BinaryToString(__JSON_Base64Decode("VGVzdA")))
; =================================================================================================
Func __JSON_Base64Decode(Const ByRef $s_Input, Const $b_base64url = False)
    Local $h_DLL_Crypt32 = DllOpen("Crypt32.dll")

    ; hier noch einen Reg-Ex zum testen ob String base64-codiert ist

    ; first run to calculate needed size of output buffer
    Local $a_Ret = DllCall($h_DLL_Crypt32, "BOOLEAN", "CryptStringToBinary", _
            "STR", $s_Input, _ ; pszString
            "DWORD", 0, _ ; cchString
            "DWORD", 1, _ ; dwFlags
            "PTR", Null, _ ; pbBinary
            "DWORD*", 0, _ ; pcbBinary
            "PTR", Null, _ ; pdwSkip
            "PTR", Null) ; pdwFlags
    Local $t_Ret = DllStructCreate("BYTE Out[" & $a_Ret[5] & "]")
    If @error Or Not IsArray($a_Ret) Or $a_Ret[0] = 0 Then Return SetError(1, @error, DllClose($h_DLL_Crypt32))


    ; second run to calculate the output data:
    Local $a_Ret2 = DllCall($h_DLL_Crypt32, "BOOLEAN", "CryptStringToBinary", _
            "STR", $s_Input, _ ; pszString
            "DWORD", 0, _ ; cchString
            "DWORD", 1, _ ; dwFlags
            "STRUCT*", $t_Ret, _ ; pbBinary
            "DWORD*", $a_Ret[5], _ ; pcbBinary
            "PTR", Null, _ ; pdwSkip
            "PTR", Null) ; pdwFlags
    If @error Or Not IsArray($a_Ret2) Or $a_Ret2[0] = 0 Then Return SetError(2, @error, DllClose($h_DLL_Crypt32))
    DllClose($h_DLL_Crypt32)

    Local $s_Output = $t_Ret.Out
    If $b_base64url Then $s_Output = StringReplace(StringReplace($s_Output, "_", "/", 0, 1), "-", "+", 0, 1)

    Return $s_Output
EndFunc   ;==>__JSON_Base64Decode

; #FUNCTION# ======================================================================================
; Name ..........: __JSON_A2DToAinA()
; Description ...: Convert a 2D array into a Arrays in Array
; Syntax ........: __JSON_A2DToAinA(ByRef $A)
; Parameters ....: $A             - the 2D-Array  which should be converted
; Return values .: Success: a Arrays in Array build from the input array
;                  Failure: False
;                     @error = 1: $A is'nt an 2D array
; Author ........: AspirinJunkie
; =================================================================================================
Func __JSON_A2DToAinA(ByRef $A, $bTruncEmpty = True)
    If UBound($A, 0) <> 2 Then Return SetError(1, UBound($A, 0), False)
    Local $N = UBound($A), $u = UBound($A, 2)
    Local $a_Ret[$N]

    If $bTruncEmpty Then
        For $i = 0 To $N - 1
            Local $x = $u - 1
            While IsString($A[$i][$x]) And $A[$i][$x] = ""
                $x -= 1
            WEnd
            Local $t[$x + 1]
            For $j = 0 To $x
                $t[$j] = $A[$i][$j]
            Next
            $a_Ret[$i] = $t
        Next
    Else
        For $i = 0 To $N - 1
            Local $t[$u]
            For $j = 0 To $u - 1
                $t[$j] = $A[$i][$j]
            Next
            $a_Ret[$i] = $t
        Next
    EndIf
    Return $a_Ret
EndFunc   ;==>__JSON_A2DToAinA

; #FUNCTION# ======================================================================================
; Name ..........: __JSON_AinAToA2d()
; Description ...: Convert a Arrays in Array into a 2D array
;                  here useful if you want to recover 2D-arrays from a json-string
;                  (there exists only a array-in-array and no 2D-Arrays)
; Syntax ........: __JSON_AinAToA2d(ByRef $A)
; Parameters ....: $A             - the arrays in array which should be converted
; Return values .: Success: a 2D Array build from the input array
;                  Failure: False
;                     @error = 1: $A is'nt an 1D array
;                            = 2: $A is empty
;                            = 3: first element isn't a array
; Author ........: AspirinJunkie
; =================================================================================================
Func __JSON_AinAToA2d(ByRef $A)
    If UBound($A, 0) <> 1 Then Return SetError(1, UBound($A, 0), False)
    Local $N = UBound($A)
    If $N < 1 Then Return SetError(2, $N, False)
    Local $u = UBound($A[0])
    If $u < 1 Then Return SetError(3, $u, False)

    Local $a_Ret[$N][$u]

    For $i = 0 To $N - 1
        Local $t = $A[$i]
        If UBound($t) > $u Then ReDim $a_Ret[$N][UBound($t)]
        For $j = 0 To UBound($t) - 1
            $a_Ret[$i][$j] = $t[$j]
        Next
    Next
    Return $a_Ret
EndFunc   ;==>__JSON_AinAToA2d

 

 

Edited by ioa747

I know that I know nothing

Posted
5 minutes ago, ioa747 said:

; helper function for converting a AutoIt-string into a json formatted string
Func __JSON_FormatString(ByRef $s_String)
    $s_String = _
        StringReplace( _
            StringReplace( _
                StringReplace( _
                    StringReplace( _
                        StringReplace( _
                            StringReplace( _
                                StringReplace( _
                                    StringReplace($s_String, '\', '\\', 0, 1) _
                                , Chr(8), "\b", 0, 1) _
                            , Chr(12), "\f", 0, 1) _
                        , @CRLF, "\n", 0, 1) _
                    , @LF, "\n", 0, 1) _
                , @CR, "\r", 0, 1) _
            , @TAB, "\t", 0, 1) _
        , '"', '\"', 0, 1)
EndFunc   ;==>__JSON_FormatString

and I 

; helper function for converting a AutoIt-string into a json formatted string
Func __JSON_FormatString(ByRef $sString)
    Return StringLen($sString) < 50 ? _
    StringTrimRight(StringRegExpReplace($sString & '\\\b\f\n\r\t\"', '(?s)(?|\\(?=.*(\\\\))|[\b](?=.*(\\b))|\f(?=.*(\\f))|\r\n(?=.*(\\n))|\n(?=.*(\\n))|\r(?=.*(\\r))|\t(?=.*(\\t))|"(?=.*(\\")))', '\1'), 15) : _
    StringReplace( _
        StringReplace( _
            StringReplace( _
                StringReplace( _
                    StringReplace( _
                        StringReplace( _
                            StringReplace( _
                                StringReplace($sString, '\', '\\', 0, 1) _
                            , Chr(8), "\b", 0, 1) _
                        , Chr(12), "\f", 0, 1) _
                    , @CRLF, "\n", 0, 1) _
                , @LF, "\n", 0, 1) _
            , @CR, "\r", 0, 1) _
        , @TAB, "\t", 0, 1) _
    , '"', '\"', 0, 1)
EndFunc

hence the oops =/

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting.
autoit_scripter_blue_userbar.png

Posted
3 hours ago, ioa747 said:

and which is the right one?
 ; Version .......: 0.10  ?

..good question.
 https://github.com/Sylvan86/autoit-json-udf/blob/master/JSON.au3 claims to be "0.10" and "Last changed ..: 2023-04-29" but,
was changed 2 month ago ( changed: _JSON_Parse(): removed Byref - direct input is now possible ).
I guess the one that works is the answer ? 😅

<rant>
That's why I rather have the files in the forum. Static.
These git places are here today and changed or gone some other time :(
</rant>

<note>I'll have to use a git of sorts/providers, to work with modern/current technologies 😭 </note>

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting.
autoit_scripter_blue_userbar.png

Posted
6 hours ago, argumentum said:

https://github.com/Sylvan86/autoit-json-udf/blob/master/JSON.au3 claims to be "0.10" and "Last changed ..: 2023-04-29" but,
was changed 2 month ago

Yes, version 0.10 is correct, but it should be 0.11.0 instead I would say.
As you already mentioned @argumentum, @AspirinJunkie added few commits (changes) to the UDF which should lead to 0.11.0 from 2025-04-01.

6 hours ago, argumentum said:

<rant>
That's why I rather have the files in the forum. Static.
These git places are here today and changed or gone some other time :(
</rant>

I understand your point of view, but exactly for this situation the idea of forking a repository exists. Then you have your static version or you decide on your own when you want to pull new changes in your repository/fork. To be honest, to search within the forum is way harder then on GitHub (at least for me).

Best regards
Sven

==> AutoIt related: 🔗 GitHub, 🔗 Discord Server, 🔗 Cheat Sheet

Spoiler

🌍 Au3Forums

🎲 AutoIt (en) Cheat Sheet

📊 AutoIt limits/defaults

💎 Code Katas: [...] (comming soon)

🎭 Collection of GitHub users with AutoIt projects

🐞 False-Positives

🔮 Me on GitHub

💬 Opinion about new forum sub category

📑 UDF wiki list

✂ VSCode-AutoItSnippets

📑 WebDriver FAQs

👨‍🏫 WebDriver Tutorial (coming soon)

Posted
5 hours ago, SOLVE-SMART said:

To be honest, to search within the forum is way harder then on GitHub (at least for me).

but what about the future user going to use it and have for breakfast that it ( the code ) don't work because one of the dependencies changed fundamentally and, ...suck it.
I understand selfishness and still advocate for ethical behaviors that don't lead to forking. A letter R apart from the same trend of thought.
And I do understand that you are just adjusted to the current way of developing and you have to do what you must ( at least for you ).

I strongly disagree with this way of participation mainly because anyone trying to replicate what is claimed, is out of phase with time. The time at witch it was all put together because the forks are not what they were.

Pardon my less than nice posture but it is just non-functional. How can one be expected to have forked 2 years ago something so today ( because it was posed 1 day ago ) can not use what is supposed to be there as it was ?

Even tho this chat is with you @SOLVE-SMART, is not necessarily personal. Is the way it is, and leads to these mishaps, hence the concept is wrong.

And no, I can not change it ( the I fork what I need ) because it takes people to feel different, and people adjust for survival of the self over the survival of the group.
That's all there is to it so, foRk it, I guess.

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting.
autoit_scripter_blue_userbar.png

Posted

@argumentum I am not sure i get if it's the platform, the repositories, the individual users or a combination you dislike? :)
When it comes to git, keeping the version in the file seems wrong, in my opinion. Git tags and/or releases on some git platforms seems the best way to keep good track of the available versions of scripts/projects.

In regards to the same code being available at a later date, unless they delete or change the visibility of the repository, you could keep a commit hash reference for a perma-link to the code you were able to access last time ;)

I personally prefer git (GitHub more specifically). Old files on this forum sometimes go poof, and sometimes no one can provide a replica (it's rare, i know).

Both Git and this forum can be a time waster, if dependencies are not kept with the project, and you are forced to go on a treasure hunt to find the necessary files (happened once or twice to me 🫠).

Personally i am working towards a package manager, using primarily git to automatically resolve and fetch dependencies of a project. If developers use the semver standard, patches should be pain free and allow upgrading dependencies using a single command :)

I agree forking is not exactly a perfect solution, but is sometimes necessary, for example when dealing with abandoned projects or not agreeing on where a project is heading ;)

Posted
16 minutes ago, genius257 said:

you could keep a commit hash reference for a perma-link to the code you were able to access last time ;)

Good to know.
Since am not familiar with git, going to get "the file" and is not it, ..is frustrating.
I code so I can debug it but those that expect the stuff to run ( because is stuff we/they discover, run it, and if it didn't, meh, it does not work, next... ) will miss in using it and participating with bugs or code or anything.

21 minutes ago, genius257 said:

Both Git and this forum can be a time waster, if dependencies are not kept with the project, and you are forced to go on a treasure hunt to find the necessary files (happened once or twice to me 🫠).

I've found myself uploading files for those occasions when I have them ( and I have them because I hoard them ). 

Maybe I went overboard in my rant, maybe. I should have advised to include all the external includes and that is a better solution :) 

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting.
autoit_scripter_blue_userbar.png

Posted

Off topic

Spoiler
3 minutes ago, argumentum said:

Maybe I went overboard in my rant, maybe.

I get your point(s) @argumentumI am fine with your statements and I don't take it personally.

I guess I am simply more used to work with Git and GitHub/GitLab. Although I'm all for making things as easy as possible for users and programmers, the world around AutoIt relies on > 90% of Git and GitHub usage (or other platforms). I'm adapting to this because it's common sense, and thousands of developers have chosen this path. Certainly not without reason.

Anyhow, we can discuss such things in a separate thread or in the chat section, because I try not to hijack or disturb the original topic (again) 😇 .

Best regards
Sven

==> AutoIt related: 🔗 GitHub, 🔗 Discord Server, 🔗 Cheat Sheet

Spoiler

🌍 Au3Forums

🎲 AutoIt (en) Cheat Sheet

📊 AutoIt limits/defaults

💎 Code Katas: [...] (comming soon)

🎭 Collection of GitHub users with AutoIt projects

🐞 False-Positives

🔮 Me on GitHub

💬 Opinion about new forum sub category

📑 UDF wiki list

✂ VSCode-AutoItSnippets

📑 WebDriver FAQs

👨‍🏫 WebDriver Tutorial (coming soon)

Posted
14 minutes ago, argumentum said:

Good to know.
Since am not familiar with git, going to get "the file" and is not it, ..is frustrating.

I think then the reference link should be https://github.com/Sylvan86/autoit-json-udf/blob/8f326597f402c1576ef793b68552171472db62c9/JSON.au3 ( untested, just looking at it )
But it would be nice to fix just that function given that the file had many patches after that.

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting.
autoit_scripter_blue_userbar.png

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...