Mod Upload API is either poorly documented, or broken

Anything that prevents you from playing the game properly. Do you have issues playing for the game, downloading it or successfully running it on your computer? Let us know here.
GreatGrogrodor
Manual Inserter
Manual Inserter
Posts: 2
Joined: Tue Feb 06, 2018 12:48 pm
Contact:

Mod Upload API is either poorly documented, or broken

Post 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?
GreatGrogrodor
Manual Inserter
Manual Inserter
Posts: 2
Joined: Tue Feb 06, 2018 12:48 pm
Contact:

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

Post 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 $_
    }
}
Post Reply

Return to “Technical Help”