Litsl · Ban appeals
How the log grab works
When you appeal a ban on a Java account, your ticket offers a command that finds a Minecraft log on your computer and sends it to the staff looking at your appeal. This page explains, step by step, exactly what that command does. The full script is at the bottom, so you can check every line.
You don't have to use it. Your appeal is looked at the same way whether you use it, attach your log yourself, or send no log at all.
Only Minecraft logs
It only opens Minecraft log files in launcher folders. Nothing else on your computer.
Asks you first
It shows you the file it found. Nothing is sent unless you type y.
Private bits removed
Login tokens, your computer's username, emails and IP addresses are taken out of the copy.
One file, deleted later
One log goes to your ticket. Our copy is deleted after 90 days.
Step by step
- You run the command from your ticket. On Windows you paste it into the Run box (Win + R). On Mac or Linux you paste it into Terminal. It downloads a script, which is just a text file of instructions, from this site and runs it.
- It looks for your Minecraft logs. Minecraft writes a log file (a record of what the game did) every time you play. The script looks in the folders where common launchers keep their game files, and only at files that end in
.logor.log.gzinside a folder calledlogs. - It picks one log. First it looks for the newest log that shows your ban: one with your Ban ID in it (like
#1234) or the Litsl ban screen. If none of them shows the ban, it picks the newest log where you connected to Litsl instead, so staff can still see your most recent Litsl session. - It shows you what it found. You see the file's full location, its size, and which kind of log it is: shows your ban or your most recent Litsl session. Staff see the same label next to the file.
- It asks
[y/N]. Typeyand press Enter to send it. Anything else, pressing Enter on its own, or closing the window cancels, and nothing is sent. - It cleans a copy and sends it. The script takes out private details (see below), squeezes the copy smaller (gzip, like a zip file), and uploads that one file. Your own log on your computer is not changed.
- It shows up in your ticket. This site checks that the file really is a Minecraft log, stores it, and the Litsl ticket bot posts it into your ticket, usually within a couple of minutes.
The link in your command only belongs to your ticket. It works for one upload and stops working after 7 days. Bedrock players aren't offered it, because Bedrock doesn't keep the same kind of log.
What it reads, and what it never touches
What it reads
- The names and dates of files in launcher folders, to find the logs.
- The text inside Minecraft log files, to search them for your ban or a Litsl session.
- Your computer's username, only so it can remove it from the copy.
What it never touches
- Your worlds, screenshots, mods, resource packs or game settings.
- Your passwords, browser, Discord, documents or anything outside the launcher folders.
- Your files: it doesn't change, move or delete any of them.
- Admin rights: it never asks for them, installs nothing and doesn't stay on your computer.
It knows where these launchers keep their logs:
- Minecraft Launcher (the official one)
- Modrinth App
- Prism Launcher, PolyMC, MultiMC and their forks
- CurseForge
- Lunar Client
- Badlion Client
- Feather Client
- LabyMod
- ATLauncher
- GDLauncher
- FTB App
- Technic Launcher
- TLauncher
If your launcher keeps its game folder somewhere else, the script can't see it, and you can attach your log by hand instead. On Windows the script writes no files at all. On Mac and Linux it makes a temporary folder for the cleaned copy and deletes it when it finishes.
What is removed before anything is sent
These are taken out of the copy that is sent. Your own file is left as it was.
- Login tokens
- Minecraft access tokens and session tokens, which could be used to log in as you, including the launcher lines that carry them.
- Your computer's username
- It shows up in folder paths. For example
C:\Users\youbecomesC:\Users\[user]. - Email addresses
- Replaced with
[email]. - IP addresses
- An IP address is the number that identifies an internet connection. Every one is replaced with
[ip], except the Litsl server's own address.
The script finds these by looking for patterns they always have. A log can still contain other things from that game session, such as chat messages you saw or the names of other servers you joined. If you'd rather read the file yourself first, attach it to your ticket by hand.
Exactly what is sent and stored
| What | Why |
|---|---|
| The cleaned copy of that one log file | So staff can see what happened around your ban. |
| The file's name, the launcher it came from (like "Prism Launcher") and whether you're on Windows, macOS or Linux | Shown to staff next to the file. |
| Your ticket number, Discord user ID, Minecraft name and Ban ID | These come from your ticket, not from your computer. They make sure the log lands in the right ticket. |
Your IP address is used for a moment to stop one connection from calling this site too often. It isn't stored with your log and isn't written to our logs. A file bigger than 25 MB of text (5 MB squeezed) is refused.
Who sees it, and how long it's kept
- Who sees it: the log is posted in your ticket, so you and the Litsl staff who handle your ticket can see it.
- Our copy: the copy stored by this site (on Cloudflare R2, Litsl's file storage) is deleted automatically after 90 days.
- The ticket copy: the file posted in your ticket is kept like any other file attached to a ticket.
- Unused links: a link that was never used holds no log at all, and expires after 7 days.
Asking for it to be deleted
Ask in your ticket, or open a new ticket in the Litsl Discord, and say you want your uploaded log deleted. Staff will remove the copy on this site and the file in your ticket.
More about what Litsl keeps and why: Litsl privacy policy.
Check it yourself before you run it
You never have to trust this page. You can read the exact script your command will run:
- Copy the web address out of your command. It's the part that starts with
https://logs.litsl.net/w/(Windows) orhttps://logs.litsl.net/u/(Mac and Linux). - Paste it into your browser's address bar. You'll see the script as plain text. Opening it like this doesn't run anything and doesn't use up your link.
- Compare it with the scripts below. The only differences are four values filled in for you (see below).
Here's what each part of the commands means. Your ticket shows them with your own link in place of <your-link>.
Windows (press Win + R, paste, press Enter):
powershell -NoProfile -Command "[Net.ServicePointManager]::SecurityProtocol=3072;irm https://logs.litsl.net/w/<your-link>|iex"
powershell -NoProfilestarts PowerShell (a program built into Windows) without your personal start-up settings.SecurityProtocol=3072turns on TLS 1.2, a secure connection that older versions of Windows 10 leave off.irmdownloads the script as text, andiexruns it.
Mac and Linux (paste into Terminal):
curl -fsSL https://logs.litsl.net/u/<your-link> | sh
curl -fsSLdownloads the script as text (and stops if the download fails).| shhands it to the shell, which runs it.
The scripts, in full
These are the exact files this site sends. In your copy, four placeholders are filled in: __LG_BASE__ becomes logs.litsl.net, __LG_ID__ your one-time link, __LG_BAN__ your Ban ID and __LG_TICKET__ your ticket number. Nothing else is different.
Windows: loggrab.ps1 (open as plain text)
# Litsl log grab (Windows)
#
# What this script does, in order:
# 1. Looks in the usual Minecraft launcher folders in your user folder for log files.
# 2. Picks the newest log that shows your Litsl ban. If no log shows it (normal when you were
# banned while offline), picks the log of your most recent Litsl session instead.
# 3. Shows you which file it picked and asks "y/N". Nothing is sent unless you answer y.
# 4. Makes a copy in memory with access tokens, your Windows username, emails and
# IP addresses (except Litsl's) removed.
# 5. Sends that one copy to Litsl for your ban appeal ticket.
#
# It needs no admin rights, installs nothing, writes no files and changes none of yours.
# The same source is shown at __LG_BASE__/ with the values below left as placeholders.
# Written for Windows PowerShell 5.1 and newer.
$LgBase = '__LG_BASE__' # where the log is sent
$LgId = '__LG_ID__' # your one-time upload link
$LgBan = '__LG_BAN__' # your Ban ID (digits), or empty when the ticket has none
$LgTicket = '__LG_TICKET__' # your ticket number, only shown to you below
# Litsl's own server addresses (current, and the one used until September 2026). These stay in the log.
$LgLitslIps = @('170.205.24.78', '135.148.136.134')
$LgMaxText = 26214400 # 25 MB of text
$LgMaxGzip = 5242880 # 5 MB compressed
$LgMaxFiles = 400 # newest log files looked at
# Folders that never hold logs. Skipping them keeps the search fast.
$LgSkipDirs = @('assets', 'libraries', 'versions', 'natives', 'cache', 'caches', 'saves', 'mods', 'config',
'resourcepacks', 'shaderpacks', 'screenshots', 'backups', 'crash-reports', 'jre', 'runtime', 'java', 'meta',
'webcache', 'node_modules', '.git')
function Get-LgRoots {
$roots = New-Object System.Collections.Generic.List[string]
$appData = $env:APPDATA
$local = $env:LOCALAPPDATA
$userHome = $env:USERPROFILE
if ($appData) {
foreach ($n in @('.minecraft', 'ModrinthApp', 'com.modrinth.theseus', 'PrismLauncher', 'PolyMC', 'PollyMC',
'FjordLauncher', 'ElyPrismLauncher', 'MultiMC', 'ATLauncher', 'gdlauncher_next', 'gdlauncher_carbon',
'gdlauncher', '.feather', 'LabyMod', '.tlauncher', '.technic', '.ftba', 'Badlion Client')) {
$roots.Add((Join-Path $appData $n))
}
}
if ($local) {
foreach ($n in @('.ftba', 'FTBA', 'ATLauncher', 'Programs/PrismLauncher', 'Programs/MultiMC')) {
$roots.Add((Join-Path $local $n))
}
}
if ($userHome) {
foreach ($n in @('.lunarclient', '.feather', '.tlauncher', 'curseforge/minecraft',
'Documents/curseforge/minecraft', 'ATLauncher', 'MultiMC', 'Desktop/MultiMC', 'Downloads/MultiMC',
'scoop/persist/prismlauncher', 'scoop/persist/multimc', 'scoop/persist/atlauncher')) {
$roots.Add((Join-Path $userHome $n))
}
}
$found = New-Object System.Collections.Generic.List[string]
foreach ($r in $roots) {
if ((Test-Path -LiteralPath $r -PathType Container) -and -not $found.Contains($r)) { $found.Add($r) }
}
return , $found
}
# Every *.log and *.log.gz inside a "logs" folder under the launcher folders, newest first.
function Get-LgCandidates {
param($Roots)
$files = New-Object System.Collections.Generic.List[object]
$seen = New-Object System.Collections.Generic.HashSet[string]
foreach ($root in $Roots) {
$queue = New-Object System.Collections.Generic.Queue[object]
$queue.Enqueue(@($root, 0, $false))
while ($queue.Count -gt 0) {
$cur = $queue.Dequeue()
$path = $cur[0]; $depth = $cur[1]; $inLogs = $cur[2]
$entries = @(Get-ChildItem -LiteralPath $path -Force -ErrorAction SilentlyContinue)
foreach ($e in $entries) {
if ($e.PSIsContainer) {
if ($depth -ge 9) { continue }
$name = $e.Name.ToLowerInvariant()
if ($LgSkipDirs -contains $name) { continue }
$queue.Enqueue(@($e.FullName, ($depth + 1), ($inLogs -or $name -eq 'logs')))
continue
}
if (-not $inLogs) { continue }
$fn = $e.Name.ToLowerInvariant()
if ($fn.StartsWith('debug')) { continue }
if (-not ($fn.EndsWith('.log') -or $fn.EndsWith('.log.gz'))) { continue }
if ($e.Length -le 0) { continue }
if ($seen.Add($e.FullName)) { $files.Add($e) }
}
}
}
return , @($files | Sort-Object -Property LastWriteTimeUtc -Descending | Select-Object -First $LgMaxFiles)
}
# The log as text. Handles .gz, and logs that an older Java wrote in the Windows code page.
function Read-LgText {
param([string]$Path)
# Opened so that it also works while Minecraft is running and still writing the log.
$fs = New-Object System.IO.FileStream($Path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read,
[System.IO.FileShare]::ReadWrite)
try {
$mem = New-Object System.IO.MemoryStream
$fs.CopyTo($mem)
$bytes = $mem.ToArray()
$mem.Dispose()
} finally {
$fs.Dispose()
}
if ($Path.ToLowerInvariant().EndsWith('.gz')) {
$in = New-Object System.IO.MemoryStream(, $bytes)
$gz = New-Object System.IO.Compression.GZipStream($in, [System.IO.Compression.CompressionMode]::Decompress)
$out = New-Object System.IO.MemoryStream
try {
$buf = New-Object byte[] 65536
while ($true) {
$n = $gz.Read($buf, 0, $buf.Length)
if ($n -le 0) { break }
$out.Write($buf, 0, $n)
if ($out.Length -gt (2 * $LgMaxText)) { throw 'log too large' }
}
$bytes = $out.ToArray()
} finally {
$gz.Dispose(); $in.Dispose(); $out.Dispose()
}
}
try {
$strict = New-Object System.Text.UTF8Encoding($false, $true)
return $strict.GetString($bytes)
} catch {
return [System.Text.Encoding]::Default.GetString($bytes)
}
}
# Does this log show the ban with this Ban ID? "#1234" not followed by another digit.
function Test-LgBanId {
param([string]$Text)
if (-not $LgBan) { return $false }
return [regex]::IsMatch($Text, '#' + [regex]::Escape($LgBan) + '(?!\d)')
}
# Does this log show a ban screen from Litsl? It has to mention Litsl, a disconnect, and a ban.
function Test-LgLitslBan {
param([string]$Text)
$i = [System.Text.RegularExpressions.RegexOptions]::IgnoreCase
if (-not [regex]::IsMatch($Text, 'litsl', $i)) { return $false }
if (-not [regex]::IsMatch($Text, 'disconnect|connection lost|lost connection|kicked', $i)) { return $false }
return [regex]::IsMatch($Text,
'you are (ip[- ]?)?banned|you have been (ip[- ]?)?banned|banned from|ban id|ban appeal|has been banned', $i)
}
# Did the player connect to Litsl in this log? The client writes "Connecting to <host>, <port>".
function Test-LgLitslSession {
param([string]$Text)
$ips = ($LgLitslIps | ForEach-Object { [regex]::Escape($_) }) -join '|'
return [regex]::IsMatch($Text, 'Connecting to (?:(?:[a-z0-9-]+\.)*litsl\.net\.?|' + $ips + ')(?=[, \r\n]|$)', 'IgnoreCase')
}
function Get-LgLauncher {
param([string]$Path)
$p = $Path.ToLowerInvariant()
if ($p -match 'modrinth|theseus') { return 'Modrinth App' }
if ($p -match 'elyprism|fjordlauncher|prismlauncher') { return 'Prism Launcher' }
if ($p -match 'pollymc|polymc') { return 'PolyMC' }
if ($p -match 'multimc') { return 'MultiMC' }
if ($p -match 'atlauncher') { return 'ATLauncher' }
if ($p -match 'gdlauncher') { return 'GDLauncher' }
if ($p -match 'curseforge') { return 'CurseForge' }
if ($p -match 'lunarclient') { return 'Lunar Client' }
if ($p -match 'blclient|badlion') { return 'Badlion Client' }
if ($p -match 'feather') { return 'Feather Client' }
if ($p -match 'labymod') { return 'LabyMod' }
if ($p -match 'tlauncher') { return 'TLauncher' }
if ($p -match 'technic') { return 'Technic Launcher' }
if ($p -match 'ftba') { return 'FTB App' }
if ($p -match 'minecraft') { return 'Minecraft Launcher' }
return 'Unknown launcher'
}
# Pick the log, newest first. Three kinds of find, best first:
# 1. a log with your Ban ID in it
# 2. a log that shows a Litsl ban screen
# 3. the log of your most recent Litsl session (no ban screen in it)
function Find-LgLog {
param($Candidates)
$banScreen = $null
$session = $null
foreach ($f in $Candidates) {
if ($f.Length -gt (2 * $LgMaxText)) { continue }
$text = $null
try { $text = Read-LgText -Path $f.FullName } catch { continue }
if (Test-LgBanId -Text $text) {
return @{ File = $f; Text = $text; Kind = 'ban'; HasId = $true }
}
if (($null -eq $banScreen) -and (Test-LgLitslBan -Text $text)) {
$banScreen = @{ File = $f; Text = $text; Kind = 'ban'; HasId = $false }
# Without a Ban ID to look for, the newest log with a Litsl ban screen is the answer.
if (-not $LgBan) { return $banScreen }
}
if (($null -eq $session) -and ($null -eq $banScreen) -and (Test-LgLitslSession -Text $text)) {
$session = @{ File = $f; Text = $text; Kind = 'session'; HasId = $false }
}
}
if ($null -ne $banScreen) { return $banScreen }
return $session
}
# The text with private details removed.
function Protect-LgText {
param([string]$Text)
$t = $Text
$t = [regex]::Replace($t, '(--accessToken[=, ]+"?)[^\] ,"]+', '$1[removed]', 'IgnoreCase')
$t = [regex]::Replace($t, '(--session[=, ]+"?)[^\] ,"]+', '$1[removed]', 'IgnoreCase')
$t = [regex]::Replace($t, '(--(xuid|clientId|userProperties|profileProperties)[=, ]+"?)[^\] ,"]+', '$1[removed]', 'IgnoreCase')
$t = [regex]::Replace($t, '(((refresh|access|id|client)_?token|session_?id)\\?"?[ ]*[:=][ ]*\\?"?)[^ ,"}\\\r\n]+', '$1[removed]', 'IgnoreCase')
$t = [regex]::Replace($t, '(token:)[A-Za-z0-9._-]{6,}(:[0-9A-Fa-f-]{32,36})', '$1[removed]$2')
$t = [regex]::Replace($t, '(Session ID is token:)[^:) ]+', '$1[removed]')
$t = [regex]::Replace($t, '(Bearer[ ]+)[A-Za-z0-9._~+/=-]{8,}', '$1[removed]', 'IgnoreCase')
$t = [regex]::Replace($t, 'eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]*', '[removed-token]')
# Your user folder, however the log wrote it (C:\Users\You, C:\\Users\\You, C:/Users/You).
$userHome = $env:USERPROFILE
if ($userHome) {
$parent = Split-Path -Parent $userHome
if (-not $parent) { $parent = $userHome }
$shown = (Join-Path $parent '[user]')
foreach ($variant in @($userHome, $userHome.Replace('\', '\\'), $userHome.Replace('\', '/'))) {
$t = [regex]::Replace($t, [regex]::Escape($variant), $shown.Replace('$', '$$'), 'IgnoreCase')
}
}
$t = [regex]::Replace($t, '([A-Za-z]:[\\/]+Users[\\/]+)[^\\/:"''\r\n]+', '$1[user]', 'IgnoreCase')
$t = [regex]::Replace($t, '(/Users/|/home/|/run/media/|/media/|/Volumes/)[^/\s:"'']+', '$1[user]')
$t = [regex]::Replace($t, '[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}', '[email]')
$t = [regex]::Replace($t, '([0-9A-Fa-f]{1,4}:){5,7}[0-9A-Fa-f]{1,4}', '[ip]')
$t = [regex]::Replace($t, '(?<![A-Za-z0-9_:.])([0-9A-Fa-f]{1,4}:){1,7}:([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0,6})?', '[ip]')
# IPv4 addresses become [ip]. Left alone: the Litsl addresses, and numbers that are versions and not
# addresses (mod list lines, "version 1.20.4.2", "foo-1.2.3.4.jar", "1.2.3.4+build", a part over 255).
$keep = ($LgLitslIps | ForEach-Object { [regex]::Escape($_) }) -join '|'
$first = '(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d?)'
$part = '(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)'
$ipv4 = '(?<![0-9A-Za-z.+_-])' +
'(?<!(?:version|ver|java|jdk|jre|openjdk|build|release|loader|lwjgl|opengl|driver)[ :=]{0,3})' +
'(?<!^[ \t]+[-|\\][^\n]*)' +
'(?!(?:' + $keep + ')(?!\d))' +
$first + '(?:\.' + $part + '){3}' +
'(?![0-9A-Za-z+_-]|\.[0-9A-Za-z])'
$t = [regex]::Replace($t, $ipv4, '[ip]', 'IgnoreCase, Multiline')
return $t
}
# UTF-8 text, gzip-compressed, as bytes.
function Compress-LgText {
param([string]$Text)
$utf8 = New-Object System.Text.UTF8Encoding($false)
$raw = $utf8.GetBytes($Text)
$out = New-Object System.IO.MemoryStream
$gz = New-Object System.IO.Compression.GZipStream($out, [System.IO.Compression.CompressionMode]::Compress)
$gz.Write($raw, 0, $raw.Length)
$gz.Dispose()
$bytes = $out.ToArray()
$out.Dispose()
return @{ Gzip = $bytes; TextLength = $raw.Length }
}
function Format-LgSize {
param([long]$Bytes)
if ($Bytes -lt 1024) { return "$Bytes bytes" }
if ($Bytes -lt 1048576) { return ('{0:N0} KB' -f [math]::Floor($Bytes / 1024)) }
return ('{0:N1} MB' -f ($Bytes / 1048576))
}
function Get-LgBanLines {
param([string]$Text)
$pattern = 'banned|ban id|ban appeal'
if ($LgBan -and (Test-LgBanId -Text $Text)) { $pattern = '#' + [regex]::Escape($LgBan) + '(?!\d)' }
$lines = New-Object System.Collections.Generic.List[string]
foreach ($line in ($Text -split "`r?`n")) {
if ([regex]::IsMatch($line, $pattern, 'IgnoreCase')) {
if ($line.Length -gt 200) { $line = $line.Substring(0, 200) }
$lines.Add($line)
if ($lines.Count -ge 3) { break }
}
}
return , $lines
}
function Get-LgSessionLines {
param([string]$Text)
$lines = New-Object System.Collections.Generic.List[string]
foreach ($line in ($Text -split "`r?`n")) {
if (Test-LgLitslSession -Text $line) {
if ($line.Length -gt 200) { $line = $line.Substring(0, 200) }
$lines.Add($line)
if ($lines.Count -ge 2) { break }
}
}
return , $lines
}
# Sends the compressed copy. Returns @{ Ok; Message }.
function Send-LgLog {
param([byte[]]$Gzip, [string]$FileName, [string]$Launcher, [string]$Kind)
$client = New-Object System.Net.WebClient
try {
$client.Headers['Content-Type'] = 'application/gzip'
$client.Headers['X-Log-File'] = ($FileName -replace '[^A-Za-z0-9 ._()+-]', '_')
$client.Headers['X-Log-Launcher'] = $Launcher
$client.Headers['X-Log-OS'] = 'Windows'
$client.Headers['X-Log-Kind'] = $Kind
[void]$client.UploadData("$LgBase/up/$LgId", 'POST', $Gzip)
return @{ Ok = $true; Message = '' }
} catch {
$ex = $_.Exception
while (($null -ne $ex) -and -not ($ex -is [System.Net.WebException])) { $ex = $ex.InnerException }
$message = "could not reach $LgBase"
if (($null -ne $ex) -and ($null -ne $ex.Response)) {
try {
$reader = New-Object System.IO.StreamReader($ex.Response.GetResponseStream())
$body = $reader.ReadToEnd()
$reader.Dispose()
if ($body) {
if ($body.Length -gt 300) { $body = $body.Substring(0, 300) }
$message = $body
}
} catch { }
}
return @{ Ok = $false; Message = $message }
} finally {
$client.Dispose()
}
}
function Write-LgManualHelp {
Write-Host ''
Write-Host 'You can still send a log yourself: press Win + R, type %appdata%\.minecraft\logs and press Enter,'
Write-Host 'then drag "latest.log" (or the dated .log.gz from the day of the ban) into your ticket.'
Write-Host 'The log grab is optional. Your appeal is reviewed either way.'
}
function Invoke-LitslLogGrab {
# Older Windows 10 does not use TLS 1.2 by default, and the upload needs it.
try {
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072
} catch { }
Write-Host ''
Write-Host ' Litsl log grab'
if ($LgTicket) { Write-Host " for ban appeal ticket #$LgTicket" }
Write-Host ''
Write-Host " How it works and the full source of this script: $LgBase/"
Write-Host ''
Write-Host 'Looking for Minecraft logs in the usual launcher folders...'
$candidates = Get-LgCandidates -Roots (Get-LgRoots)
if ($candidates.Count -eq 0) {
Write-Host ''
Write-Host 'No Minecraft log files were found in the usual launcher folders.'
Write-Host 'If your launcher keeps its game folder somewhere else, this script cannot see it.'
Write-Host 'Nothing was sent.'
Write-LgManualHelp
return
}
$pick = Find-LgLog -Candidates $candidates
if ($null -eq $pick) {
Write-Host ''
Write-Host "Looked at $($candidates.Count) log file(s). None of them shows your ban or a visit to Litsl."
Write-Host 'If you play Litsl with a launcher that keeps its game folder somewhere else,'
Write-Host 'this script cannot see it.'
Write-Host 'Nothing was sent.'
Write-LgManualHelp
return
}
$file = $pick.File
$launcher = Get-LgLauncher -Path $file.FullName
$clean = Protect-LgText -Text $pick.Text
$packed = Compress-LgText -Text $clean
Write-Host ''
if ($pick.Kind -eq 'ban') {
if ($pick.HasId) { Write-Host "This log shows your ban (#$LgBan):" }
elseif ($LgBan) { Write-Host "This log shows a Litsl ban screen (no log contains Ban ID #$LgBan):" }
else { Write-Host 'This log shows your ban:' }
} else {
Write-Host "No log shows the ban screen (that's normal if you were banned while offline)."
Write-Host 'This is your most recent Litsl session:'
}
Write-Host ''
Write-Host " File: $($file.FullName)"
Write-Host " Date: $($file.LastWriteTime.ToString('yyyy-MM-dd HH:mm'))"
Write-Host " Launcher: $launcher"
Write-Host " Size: $(Format-LgSize $file.Length) on disk, $(Format-LgSize $packed.Gzip.Length) to send (compressed)"
Write-Host ''
if ($pick.Kind -eq 'ban') {
Write-Host ' Lines that show the ban (as they would be sent):'
foreach ($line in (Get-LgBanLines -Text $clean)) { Write-Host " $line" }
} else {
Write-Host ' Where it joins Litsl (as it would be sent):'
foreach ($line in (Get-LgSessionLines -Text $clean)) { Write-Host " $line" }
}
Write-Host ''
Write-Host ' Removed from the copy: access tokens, your Windows username, email addresses,'
Write-Host ' and IP addresses other than the Litsl server. Your file on disk is not changed.'
Write-Host ''
if (($packed.TextLength -gt $LgMaxText) -or ($packed.Gzip.Length -gt $LgMaxGzip)) {
Write-Host 'This log is too big to send this way (limit: 25 MB of text, 5 MB compressed).'
Write-Host 'Nothing was sent.'
Write-LgManualHelp
return
}
$answer = Read-Host 'Send this one file to Litsl staff for your ban appeal? [y/N]'
if ($answer -notmatch '^\s*(y|yes)\s*$') {
Write-Host ''
Write-Host 'Cancelled. Nothing was sent.'
return
}
Write-Host 'Sending...'
$result = Send-LgLog -Gzip $packed.Gzip -FileName $file.Name -Launcher $launcher -Kind $pick.Kind
Write-Host ''
if ($result.Ok) {
Write-Host 'Done. Your log was sent and will show up in your ticket within a couple of minutes.'
return
}
Write-Host "The log was not sent: $($result.Message)"
Write-LgManualHelp
}
# Set $LitslLogGrabNoRun = $true before loading this file to load the functions without running them (tests).
if (-not $LitslLogGrabNoRun) {
try {
Invoke-LitslLogGrab
} catch {
Write-Host ''
Write-Host "Something went wrong: $($_.Exception.Message)"
Write-Host 'Nothing more was sent.'
Write-LgManualHelp
}
Write-Host ''
[void](Read-Host 'Press Enter to close this window')
}
Mac and Linux: loggrab.sh (open as plain text)
#!/bin/sh
# Litsl log grab (Mac and Linux)
#
# What this script does, in order:
# 1. Looks in the usual Minecraft launcher folders in your home folder for log files.
# 2. Picks the newest log that shows your Litsl ban. If no log shows it (normal when you were
# banned while offline), picks the log of your most recent Litsl session instead.
# 3. Shows you which file it picked and asks "y/N". Nothing is sent unless you answer y.
# 4. Makes a temporary copy with access tokens, your computer username, emails and
# IP addresses (except Litsl's) removed.
# 5. Sends that one copy to Litsl for your ban appeal ticket, then deletes its temporary files.
#
# It needs no admin rights, installs nothing and changes none of your files.
# The same source is shown at __LG_BASE__/ with the three values below left as placeholders.
# shellcheck shell=sh
set -u
LG_BASE='__LG_BASE__' # where the log is sent
LG_ID='__LG_ID__' # your one-time upload link
LG_BAN='__LG_BAN__' # your Ban ID (digits), or empty when the ticket has none
LG_TICKET='__LG_TICKET__' # your ticket number, only shown to you below
# Litsl's own server addresses (current, and the one used until September 2026). These stay in the log.
LG_LITSL_IPS='170.205.24.78 135.148.136.134'
LG_MAX_TEXT=26214400 # 25 MB of text
LG_MAX_FILE_KB=51200 # log files over 50 MB on disk are not looked at
LG_MAX_GZIP=5242880 # 5 MB compressed
LG_MAX_FILES=400 # newest log files looked at
# Where the y/N answer is read from. Piped from curl, the script itself is on stdin,
# so the answer has to come from the terminal.
LG_TTY="${LITSL_LOGGRAB_TTY:-/dev/tty}"
say() { printf '%s\n' "$*" >&2; }
manual_help() {
say ''
say 'You can still send a log yourself: open your Minecraft folder, then "logs",'
say 'and drag "latest.log" (or the dated .log.gz from the day of the ban) into your ticket.'
say 'The log grab is optional. Your appeal is reviewed either way.'
}
LG_TMP=''
cleanup() {
if [ -n "$LG_TMP" ] && [ -d "$LG_TMP" ]; then rm -rf "$LG_TMP"; fi
}
trap cleanup EXIT
trap 'cleanup; exit 130' INT TERM HUP
# ---------------------------------------------------------------- where logs live
lg_roots() {
_h="${HOME:-}"
[ -n "$_h" ] || return 0
case "$(uname -s 2>/dev/null || echo unknown)" in
Darwin)
_a="$_h/Library/Application Support"
printf '%s\n' \
"$_a/minecraft" \
"$_a/ModrinthApp" \
"$_a/com.modrinth.theseus" \
"$_a/PrismLauncher" \
"$_a/PolyMC" \
"$_a/PollyMC" \
"$_a/FjordLauncher" \
"$_a/ElyPrismLauncher" \
"$_a/MultiMC" \
"/Applications/MultiMC.app/Data" \
"$_a/ATLauncher" \
"$_a/gdlauncher_next" \
"$_a/gdlauncher_carbon" \
"$_a/gdlauncher" \
"$_a/Badlion Client" \
"$_a/.feather" \
"$_a/feather" \
"$_a/LabyMod" \
"$_a/tlauncher" \
"$_a/.tlauncher" \
"$_a/technic" \
"$_a/.ftba" \
"$_a/ftblauncher" \
"$_h/.lunarclient" \
"$_h/.feather" \
"$_h/.tlauncher" \
"$_h/Documents/curseforge/minecraft" \
"$_h/curseforge/minecraft"
;;
*)
_d="${XDG_DATA_HOME:-$_h/.local/share}"
_c="${XDG_CONFIG_HOME:-$_h/.config}"
_f="$_h/.var/app"
printf '%s\n' \
"$_h/.minecraft" \
"$_d/ModrinthApp" \
"$_d/com.modrinth.theseus" \
"$_d/PrismLauncher" \
"$_d/PolyMC" \
"$_d/PollyMC" \
"$_d/FjordLauncher" \
"$_d/ElyPrismLauncher" \
"$_d/multimc" \
"$_d/MultiMC" \
"$_d/ATLauncher" \
"$_d/atlauncher" \
"$_d/gdlauncher_next" \
"$_d/gdlauncher_carbon" \
"$_d/labymod" \
"$_c/gdlauncher_next" \
"$_c/gdlauncher_carbon" \
"$_c/Badlion Client" \
"$_h/.lunarclient" \
"$_h/.feather" \
"$_h/.tlauncher" \
"$_h/.technic" \
"$_h/.ftba" \
"$_h/.atlauncher" \
"$_h/Documents/curseforge/minecraft" \
"$_h/curseforge/minecraft" \
"$_f/com.mojang.Minecraft/.minecraft" \
"$_f/com.mojang.Minecraft/data/minecraft" \
"$_f/org.prismlauncher.PrismLauncher/data/PrismLauncher" \
"$_f/org.polymc.PolyMC/data/PolyMC" \
"$_f/org.multimc.MultiMC/data/multimc" \
"$_f/com.modrinth.ModrinthApp/data/ModrinthApp" \
"$_f/com.atlauncher.ATLauncher/data" \
"$_f/io.gdevs.GDLauncher/config/gdlauncher_next" \
"$_f/com.lunarclient.LunarClient/.lunarclient" \
"$_f/net.badlion.Client/.minecraft" \
"$_h/snap/mc-installer/current/.minecraft" \
"$_h/snap/minecraft-launcher/common/.minecraft"
;;
esac
}
# Modification time in seconds, then the path. BSD (Mac) and GNU stat differ.
lg_mtime_line() {
if stat -c '%Y' / >/dev/null 2>&1; then
stat -c '%Y %n' "$1" 2>/dev/null
else
stat -f '%m %N' "$1" 2>/dev/null
fi
}
# Every *.log and *.log.gz inside a "logs" folder under the launcher folders. Big folders that
# never hold logs (assets, libraries, saves, mods ...) are skipped. debug logs and files over
# 50 MB are skipped.
lg_candidates() {
lg_roots | while IFS= read -r _root; do
[ -d "$_root" ] || continue
# -L: a launcher folder that is a link to another disk is followed. -maxdepth stops link loops;
# a file reached twice through a loop sorts shortest path first below.
find -L "$_root" -maxdepth 9 \
\( -type d \( -name assets -o -name libraries -o -name versions -o -name natives \
-o -name cache -o -name caches -o -name saves -o -name mods -o -name config \
-o -name resourcepacks -o -name shaderpacks -o -name screenshots -o -name backups \
-o -name crash-reports -o -name jre -o -name runtime -o -name java -o -name meta \
-o -name webcache -o -name node_modules -o -name .git \) -prune \) -o \
\( -type f \( -name '*.log' -o -name '*.log.gz' \) -path '*/logs/*' ! -name 'debug*' \
-size "-${LG_MAX_FILE_KB}k" -print \) \
2>/dev/null
done | while IFS= read -r _f; do
[ -r "$_f" ] && [ -s "$_f" ] && lg_mtime_line "$_f"
done | sort -t' ' -k1,1nr -k2 | awk '!seen[$0]++' | head -n "$LG_MAX_FILES" | cut -d' ' -f2-
}
lg_cat() {
case "$1" in
*.gz) gzip -dc -- "$1" 2>/dev/null ;;
*) cat -- "$1" 2>/dev/null ;;
esac
}
# Does this log show the ban with this Ban ID? "#1234" not followed by another digit.
lg_has_ban_id() {
lg_cat "$1" | LC_ALL=C grep -Eq "#${LG_BAN}([^0-9]|\$)"
}
# Does this log show a ban screen from Litsl? It has to mention Litsl, a disconnect, and a ban.
lg_has_litsl_ban() {
lg_cat "$1" | LC_ALL=C awk '
{ l = tolower($0) }
l ~ /litsl/ { a = 1 }
l ~ /disconnect|connection lost|lost connection|kicked/ { b = 1 }
l ~ /you are (ip[- ]?)?banned|you have been (ip[- ]?)?banned|banned from|ban id|ban appeal|has been banned/ { c = 1 }
END { exit !(a && b && c) }'
}
# Did the player connect to Litsl in this log? The client writes "Connecting to <host>, <port>".
lg_has_litsl_session() {
lg_cat "$1" | LC_ALL=C awk -v ips="$LG_LITSL_IPS" '
BEGIN { n = split(ips, ip, " ") }
{
l = tolower($0)
at = index(l, "connecting to ")
if (!at) next
host = substr(l, at + 14)
sub(/[, ].*$/, "", host)
if (host ~ /(^|\.)litsl\.net\.?$/) { found = 1; exit }
for (i = 1; i <= n; i++) if (host == ip[i]) { found = 1; exit }
}
END { exit !found }'
}
lg_launcher() {
case "$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')" in
*modrinth*|*theseus*) echo 'Modrinth App' ;;
*elyprism*|*fjordlauncher*|*prismlauncher*) echo 'Prism Launcher' ;;
*pollymc*|*polymc*) echo 'PolyMC' ;;
*multimc*) echo 'MultiMC' ;;
*atlauncher*) echo 'ATLauncher' ;;
*gdlauncher*) echo 'GDLauncher' ;;
*curseforge*) echo 'CurseForge' ;;
*lunarclient*) echo 'Lunar Client' ;;
*blclient*|*badlion*) echo 'Badlion Client' ;;
*feather*) echo 'Feather Client' ;;
*labymod*) echo 'LabyMod' ;;
*tlauncher*) echo 'TLauncher' ;;
*technic*) echo 'Technic Launcher' ;;
*ftba*|*ftblauncher*) echo 'FTB App' ;;
*minecraft*) echo 'Minecraft Launcher' ;;
*) echo 'Unknown launcher' ;;
esac
}
lg_size() {
_b="$1"
if [ "$_b" -lt 1024 ]; then printf '%s bytes' "$_b"
elif [ "$_b" -lt 1048576 ]; then printf '%s KB' "$((_b / 1024))"
else printf '%s.%s MB' "$((_b / 1048576))" "$(((_b % 1048576) * 10 / 1048576))"
fi
}
# Escape a literal string for use inside a sed -E pattern (delimiter #).
lg_sed_escape() { printf '%s' "$1" | sed -e 's/[][\\.^$*+?(){}|#/]/\\&/g'; }
# Copy the log to stdout with private details removed.
lg_redact() {
_home_re="$(lg_sed_escape "${HOME:-/nonexistent-home}")"
lg_cat "$1" | LC_ALL=C sed -E \
-e 's#(--[Aa]ccess[Tt]oken[=, ]+"?)[^] ,"]+#\1[removed]#g' \
-e 's#(--[Ss]ession[=, ]+"?)[^] ,"]+#\1[removed]#g' \
-e 's#(--(xuid|clientId|userProperties|profileProperties)[=, ]+"?)[^] ,"]+#\1[removed]#g' \
-e 's#((([Rr]efresh|[Aa]ccess|[Ii]d|[Cc]lient)_?[Tt]oken|[Ss]ession_?[Ii]d)\\?"?[ ]*[:=][ ]*\\?"?)[^ ,"}\\]+#\1[removed]#g' \
-e 's#(token:)[A-Za-z0-9._-]{6,}(:[0-9A-Fa-f-]{32,36})#\1[removed]\2#g' \
-e 's#(Session ID is token:)[^:) ]+#\1[removed]#g' \
-e 's#([Bb]earer[ ]+)[A-Za-z0-9._~+/=-]{8,}#\1[removed]#g' \
-e 's#eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]*#[removed-token]#g' \
-e "s#${_home_re}#~#g" \
-e 's#(/Users/|/home/|/run/media/|/media/|/Volumes/)[^/ :"'"'"']+#\1[user]#g' \
-e 's#([A-Za-z]:[\\/]+Users[\\/]+)[^\\/:"'"'"']+#\1[user]#g' \
-e 's#[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}#[email]#g' \
-e 's#([0-9A-Fa-f]{1,4}:){5,7}[0-9A-Fa-f]{1,4}#[ip]#g' \
-e 's#(^|[^A-Za-z0-9_:.])([0-9A-Fa-f]{1,4}:){1,7}:([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0,6})?#\1[ip]#g' \
| LC_ALL=C awk -v keep="$LG_LITSL_IPS" '
# IPv4 addresses become [ip]. Left alone: the Litsl addresses, and numbers that are versions and not
# addresses (mod list lines, "version 1.20.4.2", "foo-1.2.3.4.jar", "1.2.3.4+build", a part over 255).
BEGIN { n = split(keep, k, " "); for (i = 1; i <= n; i++) litsl[k[i]] = 1 }
{
if ($0 ~ /^[ \t]+[-|\\]/ || $0 !~ /[0-9]\.[0-9]/) { print; next }
out = ""; rest = $0
while (match(rest, /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/)) {
pre = out substr(rest, 1, RSTART - 1)
m = substr(rest, RSTART, RLENGTH)
rest = substr(rest, RSTART + RLENGTH)
ok = !(m in litsl)
split(m, a, ".")
for (i = 1; i <= 4; i++) if (length(a[i]) > 3 || a[i] + 0 > 255) ok = 0
if (a[1] + 0 == 0) ok = 0
if (pre ~ /[0-9A-Za-z.+_-]$/) ok = 0
if (rest ~ /^[0-9A-Za-z+_-]/ || rest ~ /^\.[0-9A-Za-z]/) ok = 0
if (tolower(pre) ~ /(version|ver|java|jdk|jre|openjdk|build|release|loader|lwjgl|opengl|driver)[ :=]*$/) ok = 0
out = pre (ok ? "[ip]" : m)
}
print out rest
}'
}
# ---------------------------------------------------------------- start
say ''
say ' Litsl log grab'
if [ -n "$LG_TICKET" ]; then say " for ban appeal ticket #$LG_TICKET"; fi
say ''
say " How it works and the full source of this script: $LG_BASE/"
say ''
for _need in curl gzip find sed awk; do
if ! command -v "$_need" >/dev/null 2>&1; then
say "This needs \"$_need\", which is not installed here. Nothing was sent."
manual_help
exit 1
fi
done
LG_TMP="$(mktemp -d "${TMPDIR:-/tmp}/litsl-loggrab.XXXXXX")" || {
say 'Could not create a temporary folder. Nothing was sent.'
manual_help
exit 1
}
say 'Looking for Minecraft logs in the usual launcher folders...'
lg_candidates >"$LG_TMP/candidates"
LG_COUNT="$(wc -l <"$LG_TMP/candidates" | tr -d ' ')"
if [ "$LG_COUNT" -eq 0 ]; then
say ''
say 'No Minecraft log files were found in the usual launcher folders.'
say 'If your launcher keeps its game folder somewhere else, this script cannot see it.'
say 'Nothing was sent.'
manual_help
exit 1
fi
# Three kinds of find, best first. Files are looked at newest first.
# 1. a log with your Ban ID in it
# 2. a log that shows a Litsl ban screen
# 3. the log of your most recent Litsl session (no ban screen in it)
LG_PICK=''
LG_KIND=''
LG_BANSCREEN=''
LG_SESSION=''
while IFS= read -r _f; do
[ -n "$_f" ] || continue
if [ -n "$LG_BAN" ] && lg_has_ban_id "$_f"; then
LG_PICK="$_f"
LG_KIND='ban'
break
fi
if [ -z "$LG_BANSCREEN" ] && lg_has_litsl_ban "$_f"; then
LG_BANSCREEN="$_f"
# Without a Ban ID to look for, the newest log with a Litsl ban screen is the answer.
[ -n "$LG_BAN" ] || break
fi
if [ -z "$LG_SESSION" ] && [ -z "$LG_BANSCREEN" ] && lg_has_litsl_session "$_f"; then
LG_SESSION="$_f"
fi
done <"$LG_TMP/candidates"
if [ -z "$LG_PICK" ] && [ -n "$LG_BANSCREEN" ]; then
LG_PICK="$LG_BANSCREEN"
LG_KIND='ban'
elif [ -z "$LG_PICK" ] && [ -n "$LG_SESSION" ]; then
LG_PICK="$LG_SESSION"
LG_KIND='session'
fi
if [ -z "$LG_PICK" ]; then
say ''
say "Looked at $LG_COUNT log file(s). None of them shows your ban or a visit to Litsl."
say 'If you play Litsl with a launcher that keeps its game folder somewhere else,'
say 'this script cannot see it.'
say 'Nothing was sent.'
manual_help
exit 1
fi
LG_NAME="$(basename "$LG_PICK")"
LG_LAUNCHER="$(lg_launcher "$LG_PICK")"
LG_ORIG_BYTES="$(wc -c <"$LG_PICK" | tr -d ' ')"
# How big is it as text? Checked before any copy is made (a .log.gz can unpack to far more).
LG_RAW_BYTES="$(lg_cat "$LG_PICK" | head -c "$((LG_MAX_TEXT + 1))" | wc -c | tr -d ' ')"
if [ "$LG_RAW_BYTES" -gt "$LG_MAX_TEXT" ]; then
say ''
say "The log that was found is too big to send this way (over 25 MB of text):"
say " $LG_PICK"
say 'Nothing was sent.'
manual_help
exit 1
fi
if stat -c '%Y' / >/dev/null 2>&1; then
LG_DATE="$(date -d "@$(stat -c '%Y' "$LG_PICK")" '+%Y-%m-%d %H:%M' 2>/dev/null || true)"
else
LG_DATE="$(stat -f '%Sm' -t '%Y-%m-%d %H:%M' "$LG_PICK" 2>/dev/null || true)"
fi
# The copy that would be sent: private details removed, then made valid UTF-8 text.
lg_redact "$LG_PICK" >"$LG_TMP/redacted.log"
if command -v iconv >/dev/null 2>&1 && ! iconv -f UTF-8 -t UTF-8 "$LG_TMP/redacted.log" >/dev/null 2>&1; then
iconv -c -f UTF-8 -t UTF-8 "$LG_TMP/redacted.log" >"$LG_TMP/clean.log" 2>/dev/null || true
if [ -s "$LG_TMP/clean.log" ]; then mv "$LG_TMP/clean.log" "$LG_TMP/redacted.log"; fi
fi
LG_TEXT_BYTES="$(wc -c <"$LG_TMP/redacted.log" | tr -d ' ')"
gzip -9 -c "$LG_TMP/redacted.log" >"$LG_TMP/upload.log.gz"
LG_GZ_BYTES="$(wc -c <"$LG_TMP/upload.log.gz" | tr -d ' ')"
say ''
if [ "$LG_KIND" = 'ban' ]; then
if [ -n "$LG_BAN" ] && LC_ALL=C grep -Eq "#${LG_BAN}([^0-9]|\$)" "$LG_TMP/redacted.log"; then
say "This log shows your ban (#$LG_BAN):"
elif [ -n "$LG_BAN" ]; then
say "This log shows a Litsl ban screen (no log contains Ban ID #$LG_BAN):"
else
say 'This log shows your ban:'
fi
else
say "No log shows the ban screen (that's normal if you were banned while offline)."
say 'This is your most recent Litsl session:'
fi
say ''
say " File: $LG_PICK"
say " Date: ${LG_DATE:-unknown}"
say " Launcher: $LG_LAUNCHER"
say " Size: $(lg_size "$LG_ORIG_BYTES") on disk, $(lg_size "$LG_GZ_BYTES") to send (compressed)"
say ''
if [ "$LG_KIND" = 'ban' ]; then
say ' Lines that show the ban (as they would be sent):'
if [ -n "$LG_BAN" ] && LC_ALL=C grep -Eq "#${LG_BAN}([^0-9]|\$)" "$LG_TMP/redacted.log"; then
LC_ALL=C grep -E "#${LG_BAN}([^0-9]|\$)" "$LG_TMP/redacted.log" | head -n 3 | cut -c1-200 | sed 's/^/ /' >&2
else
LC_ALL=C grep -Ei 'banned|ban id|ban appeal' "$LG_TMP/redacted.log" | head -n 3 | cut -c1-200 | sed 's/^/ /' >&2
fi
else
say ' Where it joins Litsl (as it would be sent):'
LC_ALL=C grep -Ei 'connecting to .*(litsl|170\.205\.24\.78|135\.148\.136\.134)' "$LG_TMP/redacted.log" | head -n 2 | cut -c1-200 | sed 's/^/ /' >&2
fi
say ''
say ' Removed from the copy: access tokens, your computer username, email addresses,'
say ' and IP addresses other than the Litsl server. Your file on disk is not changed.'
say ''
if [ "$LG_TEXT_BYTES" -gt "$LG_MAX_TEXT" ] || [ "$LG_GZ_BYTES" -gt "$LG_MAX_GZIP" ]; then
say 'This log is too big to send this way (limit: 25 MB of text, 5 MB compressed).'
say 'Nothing was sent.'
manual_help
exit 1
fi
if [ ! -r "$LG_TTY" ]; then
say 'Cannot ask you to confirm here (no terminal), so nothing was sent.'
manual_help
exit 1
fi
printf 'Send this one file to Litsl staff for your ban appeal? [y/N] ' >&2
LG_ANSWER=''
IFS= read -r LG_ANSWER <"$LG_TTY" || LG_ANSWER=''
case "$LG_ANSWER" in
y|Y|yes|Yes|YES) ;;
*)
say ''
say 'Cancelled. Nothing was sent.'
exit 0
;;
esac
case "$(uname -s 2>/dev/null || echo unknown)" in
Darwin) LG_OS='macOS' ;;
Linux) LG_OS='Linux' ;;
*) LG_OS='Unix' ;;
esac
say 'Sending...'
LG_CODE="$(curl -sS --max-time 180 -o "$LG_TMP/response" -w '%{http_code}' -X POST \
-H 'Content-Type: application/gzip' \
-H "X-Log-File: $LG_NAME" \
-H "X-Log-Launcher: $LG_LAUNCHER" \
-H "X-Log-OS: $LG_OS" \
-H "X-Log-Kind: $LG_KIND" \
--data-binary @"$LG_TMP/upload.log.gz" \
"$LG_BASE/up/$LG_ID" 2>"$LG_TMP/curl-error")" || LG_CODE='000'
say ''
if [ "$LG_CODE" = '200' ] || [ "$LG_CODE" = '201' ]; then
say 'Done. Your log was sent and will show up in your ticket within a couple of minutes.'
say 'You can close this window.'
exit 0
fi
if [ -s "$LG_TMP/response" ]; then
say "The log was not sent: $(head -c 300 "$LG_TMP/response")"
else
say "The log was not sent (could not reach $LG_BASE)."
fi
manual_help
exit 1