Page 1 of 1

Mod Upload API is either poorly documented, or broken

Posted: Wed Jun 19, 2024 1:39 pm
by GreatGrogrodor
So, I've been tinkering with a powershell module for mod making.

It's not complete yet, and I've had some trouble interpreting how to upload the new releases from the official documentation here https://wiki.factorio.com/Mod_upload_API

I'm not a python guy so the example given is difficult to interpret for me, but this is my best answer as to what the python code is trying to do, however this code does not upload correctly:

Code: Select all

function Push-FactorioMod {
    param (
        [Parameter(Mandatory=$true)][string]$APIKey,
        [Parameter(Mandatory=$true)][string]$Name,
        [Parameter(Mandatory=$true)][System.IO.FileInfo]$Path
    )
    
    try {
        $Response = Invoke-RestMethod -Uri "https://mods.factorio.com/api/v2/mods/releases/init_upload" -Method POST -Body @{ mod = $Name } -Headers @{ Authorization = "Bearer $APIKey" }
    } catch {
        return $_
    }

    try {
        $boundary = [System.Guid]::NewGuid().ToString() 
        $LF = "`r`n"    
        $body = New-Object System.Text.StringBuilder
            [void]$body.AppendLine("--$boundary")
            [void]$body.AppendLine("Content-Disposition: form-data; name=`"file`"; filename=`"$($Path.Name)`"")
            [void]$body.AppendLine("Content-Type: application/zip$LF")
            [void]$body.AppendLine([System.Text.Encoding]::Default.GetString([System.IO.File]::ReadAllBytes($Path.FullName)))
            [void]$body.AppendLine("--$boundary--$LF")

        $body.ToString()
        
        return Invoke-RestMethod -Uri $Response.upload_url -Method POST -ContentType "multipart/form-data; boundary=$boundary" -Body $body.ToString()
    } catch {
        return $_
    }
}
I'm getting this error when giving it a zip file, name, and valid API key:

Code: Select all

Invoke-RestMethod:
Line |
  26 |  … $response = Invoke-RestMethod -Uri $response.upload_url -Method Post  …
     |                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     |  {   "error": "Unknown",   "message": "Unknown error, please try again later." }
I even printed the body, and this is the result (has been truncated and anonymized):

Code: Select all

--aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
Content-Disposition: form-data; name="file"; filename="mod-name_1.0.0.zip"
Content-Type: application/octet-stream
<binary-data-here>
--aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee--
Can anyone spot my oopsie?

Re: Mod Upload API is either poorly documented, or broken

Posted: Wed Jun 19, 2024 3:14 pm
by GreatGrogrodor
I fixed it.

In powershell, you can't get around the fact that invoke-restmethod will always encode the binary data you give it to some UTF8 encoded string, which ultimately completely messes up the mod data.

So, you have to use system.net.http.httpclient instead like this:

Code: Select all

function Push-FactorioMod {
    param (
        [Parameter(Mandatory=$true)][string]$APIKey,
        [Parameter(Mandatory=$true)][string]$Name,
        [Parameter(Mandatory=$true)][System.IO.FileInfo]$Path
    )
    
    try {
        $Response = Invoke-RestMethod -Uri "https://mods.factorio.com/api/v2/mods/releases/init_upload" -Method POST -Body @{ mod = $Name } -Headers @{ Authorization = "Bearer $APIKey" }
    } catch {
        return $_
    }

    try {
        $fileContent = [System.IO.File]::ReadAllBytes($Path.FullName)
        $fileContentStream = New-Object System.IO.MemoryStream(,$fileContent)

        $content = New-Object System.Net.Http.MultipartFormDataContent
        $content.Add((New-Object System.Net.Http.StreamContent($fileContentStream)), "file", $Path.Name)

        $httpClient = New-Object System.Net.Http.HttpClient
        return $httpClient.PostAsync($Response.upload_url, $content).Result
    }
    catch {
        return $_
    }
}