Wie ich ein Minecraft-Zahlungsakzeptanzsystem mit reiner PowerShell erstellt habe


In diesem Artikel werden wir den gottlosen Donut mit Powershell an den Minecraft-Vanille-Server schrauben. Der Vorteil der Methode ist, dass Minecraft nur ein Sonderfall für die Implementierung automatischer Zahlungen mithilfe von Konsolenbefehlen ist. Wir hören nur zu, was das Zahlungssystem uns sendet, und wickeln es in ein Team ein. Und vor allem - keine Plugins.
Und wir akzeptieren Zahlungen über PayPal. Um Zahlungen zu akzeptieren, müssen Sie den Code nicht ändern. PayPal sendet uns vor allem alles, was Sie benötigen. Wir werden die Schaltflächen auf der Site verwenden, damit die Site mit reinem HTML arbeiten kann. Wir abstrahieren von den Feinheiten des Zahlungssystems selbst und konzentrieren uns nur auf die Hauptpunkte im Code.

Übrigens, der Autor wird sich sehr freuen, wenn Sie alle seine Module durchsehen und darin Kinderfehler finden, auf die Sie verweisen oder die Sie korrigieren. Hier ist ein Link zum Github- Projekt.

Ein paar Worte zu IPN


IPN


Wir akzeptieren Zahlungen über die Schaltflächen. Schaltflächen erfordern kein Backend von Ihnen, sie arbeiten in reinem HTML und sie haben auch ihre eigenen Felder.

Schaltflächen lösen IPN - Instant Payment Notification aus, bei der Daten an unseren WebListener gesendet werden. Wir werden die IPN-Struktur unten betrachten.

Darüber hinaus kann jeder, der ein PayPal-Konto hat, seine eigene Schaltfläche erstellen.
IPN verfügt nicht über die vollständige PayPal-REST-API, die grundlegenden Funktionen können jedoch darauf implementiert werden. Tatsächlich ist das IPN, das wir in Betracht ziehen, keine REST-API im wahrsten Sinne des Wortes, nur weil PayPal selbst nichts außer Code 200 von uns erwartet.

Erhöhen Sie den WebListener


Aus Sicherheitsgründen sendet PayPal keine Anfragen über HTTP. Daher müssen wir ein Zertifikat ausstellen, um loszulegen. 

Der Autor hat WinAcme verwendet . Sie können ein Zertifikat für jede Domäne ausstellen und müssen das Zertifikat in einem lokalen Zertifikatspeicher ablegen. WinAcme befindet sich übrigens im Stammverzeichnis der Festplatte im Image.

#     
Get-ChildItem -Path Cert:\LocalMachine\My 
 
#      443 .
netsh http add sslcert ipport=0.0.0.0:443 certhash=D106F5676534794B6767D1FB75B58D5E33906710 "appid={00112233-4455-6677-8899-AABBCCDDEEFF}"

Powershell kann Klassen von .net verwenden, wodurch es fast gleich .net ist. Erhöhen Sie zunächst mithilfe der HttpListener-Klasse den Webserver.

#   .net
$http = [System.Net.HttpListener]::new() 
 
#   
$http.Prefixes.Add("http://donate.to/")
$http.Prefixes.Add("https://donate.to/")
 
# 
$http.Start()

Führen Sie netstat aus, um zu überprüfen, ob alles ordnungsgemäß ausgeführt wurde.



Wenn unser Skript Port 443 in der Liste abhört, bedeutet dies, dass Sie alles richtig gemacht haben und wir mit der Annahme der Anforderungsverarbeitung fortfahren können. Vergessen Sie einfach nicht die Firewall.

Anfrage annehmen


Mit IPN Simulator können wir uns eine Test-POST-Anfrage senden, um zu sehen, was es ist. Sie können jedoch keine eigenen Felder hinzufügen, daher empfiehlt der Autor, eine Schaltfläche zu erstellen und sofort etwas von sich selbst zu kaufen. Der IPN-Verlauf zeigt eine normale Anforderung über die Schaltfläche an, die Sie verwenden werden. Der Autor hat genau das getan, indem er eine Kohle für einen Rubel gekauft hat.

Wir akzeptieren die Verwendung der While-Schleife. Während der Webserver läuft, können wir den eingehenden Datenstrom lesen.

while ($http.IsListening) {
 
  $context = $http.GetContext()
 
  if ($context.Request.HttpMethod -eq 'POST' -and $context.Request.RawUrl -eq '/') {
 
    #  POST 
    $Reader = [System.IO.StreamReader]::new($context.Request.InputStream).ReadToEnd()
    
    #  .
    $DecodedContent = [System.Web.HttpUtility]::UrlDecode($Reader)
 
    #   .
    $Payment | Format-Table
 
    #  200 OK   .
    $context.Response.Headers.Add("Content-Type", "text/plain")
    $context.Response.StatusCode = 200
    $ResponseBuffer = [System.Text.Encoding]::UTF8.GetBytes("")
    $context.Response.ContentLength64 = $ResponseBuffer.Length
    $context.Response.OutputStream.Write($ResponseBuffer, 0, $ResponseBuffer.Length)
    $context.Response.Close()
  }
}

Wenn Sie eine solche Fadennudel bekommen, dann bewerben Sie sich:

$Payment = $DecodedContent -split "&" | ConvertFrom-StringData



Danach erhalten Sie endlich ein normales Objekt, bei dem der gesamte Wert String ist.



Sie können das Lesen hier beenden, wenn Sie nicht tiefer in den Code einsteigen möchten, sondern nur Anforderungen von der API einer anderen Person annehmen möchten.

Hier ist der Code, der sofort funktioniert, kopiert und verwendet wird:

# 
$http = [System.Net.HttpListener]::new() 
 
# ,   
$http.Prefixes.Add("http://localhost/")
$http.Prefixes.Add("https://localhost/")
 
$http.Start()
 
while ($http.IsListening) {
 
  $context = $http.GetContext()
 
  if ($context.Request.HttpMethod -eq 'POST' -and $context.Request.RawUrl -eq '/') {
 
    #  POST 
    $Reader = [System.IO.StreamReader]::new($context.Request.InputStream).ReadToEnd()
    
    #  .
    $DecodedContent = [System.Web.HttpUtility]::UrlDecode($Reader)
          
    #  IPN   
    $Payment = $DecodedContent -split "&" | ConvertFrom-StringData
 
    #   .
    $Payment | Format-Table
 
    #  200 OK   .
    $context.Response.Headers.Add("Content-Type", "text/plain")
    $context.Response.StatusCode = 200
    $ResponseBuffer = [System.Text.Encoding]::UTF8.GetBytes("")
    $context.Response.ContentLength64 = $ResponseBuffer.Length
    $context.Response.OutputStream.Write($ResponseBuffer, 0, $ResponseBuffer.Length)
    $context.Response.Close()
  }
}

Minecraft Nuances


Also haben wir herausgefunden, wie wir Benachrichtigungen über Zahlungen erhalten können, jetzt können wir sie gutschreiben. Aber auch hier ist es nicht so einfach. Das Problem ist, dass das Spiel keine Gegenstände gibt oder den Status von Spielern ändert, die sich nicht auf dem Server befinden. Das heißt, wir müssen warten, bis eine Person den Server betritt, um ihr zu geben, wofür sie bezahlt hat.

Daher wird Ihre Aufmerksamkeit auf das allgemeine Konzept des Rauchers zur Gutschrift von Zahlungen gelenkt.



Zahlungen werden über den obigen Listener empfangen. Es wurde nur eine Zeile hinzugefügt, um das Objekt in die Datei zu schreiben. Complete-Payment (Prozessor) überprüft den Spitznamen und ordnet ihn dem Dateinamen zu. Wenn eine Datei gefunden wird, wird ein Befehl für rcon kompiliert und ausgeführt.

Starten Sie Minecraftüber die der Autor in einem früheren Artikel geschrieben hat, wurde leicht modifiziert. Jetzt hört er auf die Schlussfolgerung, schaut sich die Spitznamen der Spieler an und gibt sie an den Zahlungsabwickler weiter.

Echte Rückrufe machen


Ohne Plugins werden wir echte Rückrufe machen. Dafür wurde Start-Minecraft modifiziert. Jetzt weiß er nicht nur, wie man StdOut zu einer Datei hinzufügt, sondern geht auch mit einem regulären Zeitplan durch jede Zeile. Glücklicherweise hinterlässt Minecraft eine sehr spezifische Nachricht, wenn ein Spieler den Server betritt.

[04:20:00 INFO]: UUID of player XXPROHUNTERXX is 23e93d2e-r34d-7h15 -5h17-a9192cd70b48

Es ist sehr einfach, einen Spitznamen aus dieser Zeile auszuwählen. Hier ist der gesamte Code, den wir zum Abrufen von Daten aus Stdout-Zeichenfolgen benötigen.

$Regex = [Regex]::new("of player ([^ ]+)")
 
powershell.exe -file ".\Start-MinecraftHandler.ps1" -type $type -MinecraftPath $MinecraftPath | Tee-Object $LogFile -Append | ForEach-Object {
 
     Write-host $_
        
    $Player = $Regex.Matches($_).value -replace "of player "
        
    if ($true -eq $Regex.Matches($_).Success) {
        #   
    }
}


Eine neue Zeile wird in die $ _ -Pipeline eingespeist, wir schreiben sie in das Konsolenfenster und gehen sie regelmäßig durch. Der Stammgast selbst benachrichtigt uns, wenn es funktioniert, was sehr praktisch ist.

Von hier aus können wir jeden Code aufrufen. Mit demselben RCON können wir beispielsweise den Spieler in der PM begrüßen, indem wir den Bot in der Zwietracht verwenden, um zu benachrichtigen, dass sich jemand am Server angemeldet hat, den Schachmatt zu sperren und so weiter.

Zahlungen leisten


Da wir mit der Verarbeitung von Zahlungen begonnen haben, möchten wir zumindest recht vollständige Daten über die Operation und den Verlauf der durchgeführten Operationen haben, da es sich sozusagen um Zahlen mit zwei Nullen handelt.

Der Autor möchte alles extrem einfach lassen und noch keine Basis simulieren. Schauen wir uns den NoSQL-Ansatz an. Erstellen wir eine eigene Klasse, die alle akzeptierten Zahlungen in den Ordner / messages / in Json-Dateien importiert.

    class Payment {
        #  .
        [datetime]$Date = [datetime]::ParseExact($i.payment_date, "HH:mm:ss MMM dd, yyyy PDT", [System.Globalization.CultureInfo]::InvariantCulture)
        # 
        [string]$Item = $i.item_name
        # 
        [UInt16]$Quantity = $i.Quantity
        #    
        [UInt16]$AmountPaid = $AmountPaid -as [UInt16]
        #     
        [string]$Currency = $i.mc_currency
        # ,   
        [string]$Player = $i.option_selection1
    
        [bool]$Completed = $false
        [UInt16]$ItemId = $i.item_number
    }
/source>

    , , ,          .

 ,    <b>option_selection1</b> –   .      input,   ,     .
     <b>option_selection1</b>,<b>option_selection2</b>   .

      ,     ,      .

<source lang="powershell"> #     Payment,        .
    $Payment = [Payment]::new()
    $Payment | Format-Table
    #  ,   ----
    $FileName = $Payment.Player + "-" + $Payment.date.Hour + "-" + $Payment.date.Minute + "-" + $Payment.date.Day + "-" + $Payment.date.Month + "-" + $Payment.date.Year + ".json"
 
# ,      
    $JsonPath = Join-Path $MinecraftPath \payments\Pending $FileName
    
    #   
    $Payment | ConvertTo-Json | Out-File $JsonPath

Das ist alles, was von unserem Zuhörer verlangt wurde. Empfangen Sie Daten von PayPal und schreiben Sie in eine Datei.

Wir machen Zahlungsabwicklung


Der Handler wird der reguläre genannt, über den zuvor geschrieben wurde. Wir übertragen den Spitznamen des Spielers auf das Modul und das wars. Als nächstes wird ein neues Skript gestartet, das nach der Datei sucht. Wenn eine Datei vorhanden ist, erhält der Player das Element, das in die Datei geschrieben wurde.

powershell.exe -file "C:\mc.fern\Start-MinecraftHandler.ps1" -type $type -MinecraftPath $MinecraftPath | Tee-Object $LogFile -Append | ForEach-Object {
       
        #     ,      .
        Write-host $_   
 
        # Regex     
        if ($true -eq $Regex.Matches($_).Success) {
            
            #       
            $Player = $Regex.Matches($_).value -replace "of player "
            
            #  ,       
            Complete-Payment -Player $Player
        }
    }

Wenn die Regelmäßigkeit ausgelöst wird, wird ein Modul gestartet, das die Zahlung abschließt, dh dem Spieler einen Gegenstand gibt. Zu diesem Zweck sucht das Skript im Ordner / Payments / Pending / nach Dateien, die den Spitznamen des Spielers enthalten, der das Spiel betreten hat, und liest dessen Inhalt.

Jetzt müssen Sie den Befehl für den Server sammeln und dorthin senden. Es wird aus einer Datei gesammelt. Wir wissen, dass der Spitzname des Spielers, der Name des Gegenstands und seine ID aufgezeichnet wurden. Wie viele Teile wurden ebenfalls aufgezeichnet. Es bleibt nur ein Befehl an den Spielserver zu senden. Dafür verwenden wir mcrcon .

#    
    $JsonPath = Join-Path $MinecraftPath\payments\Pending -ChildPath $Player*
    $i = $JsonPath | Get-Item | Where-Object { !$_.PSIsContainer } | Get-Content | ConvertFrom-Json -ErrorVariable Errored
 
    #      
    if ($null -ne $i) {
 
        #  
        $Command = '"' + "give " + $i.Player + " " + $i.Item + " " + $i.Quantity + '"'
        Write-host $Command -ForegroundColor Green
    
        #   
        Start-Process -FilePath mcrcon.exe -ArgumentList "-H localhost -p 123 -w 5 $Command"
    
        # ,      
        $JsonPath = Join-Path $MinecraftPath\payments\Pending -ChildPath $FileName
        
        #   
        $i | ConvertTo-Json | Out-File $JsonPath
    
        #     
        Move-Item  -Path $JsonPath -Destination $MinecraftPath\payments\Completed
    }

Wir machen alles in einem praktischen Modul


Der Java-Prozess und der WebListener-Prozess erfordern unterschiedliche Threads, aber der Autor ist nicht zufrieden mit der Notwendigkeit, den WebListener und den Server separat auszuführen. Der Autor möchte alles auf einmal mit einem Team.

Daher werden wir mit Powershell 7 sowohl dies als auch das starten. Und es wird uns helfen:

ForEach-Object -Parallel {}

Das Cmdlet funktioniert mit inputObject, daher geben wir ihm ein unkompliziertes Array und teilen die Streams mithilfe eines Schalters.

"A", "B" | ForEach-Object -Parallel {
 
    Import-Module ".\Start-Minecraft.ps1"
 
    Import-Module ".\Start-WebListener.ps1"
 
    switch ($_) {
        "A" {
            Start-WebListener -Path "C:\mc\"
        }
        "B" {
            Start-Minecraft -Type Vanilla -LogFile ".\stdout.txt" -MinecraftPath "C:\mc\"
        }
        
    }
}

Auf Krückenart haben wir also zwei verschiedene Prozesse von einem Terminal aus gestartet und nicht einmal die Eingabe verloren. Aber es gab noch ein anderes Problem. WebListener sperrt die Konsole nach einem regulären Herunterfahren des Servers und möchte nirgendwo hingehen.

Um das Terminal nicht jedes Mal neu zu starten, wurde Start-MinecraftHandler.ps1 und Start-WebListener.ps1 ein zufälliger Schlüssel hinzugefügt, der den Server über POST auf WebListener stoppt.

Start-MinecraftHandler.ps1 führt den folgenden Befehl aus, wenn ein erfolgreicher Abschluss aufgezeichnet wurde:

Invoke-WebRequest -Method Post -Uri localhost -Body $StopToken | Out-Null

$ StopToken enthält einen zufälligen numerischen Wert, der vom Startskript sowohl an Listener als auch an Handler übergeben wird. Der Listener überprüft, was er in der Anforderung erhalten hat, und schaltet sich aus, wenn der Anforderungshauptteil mit $ StopToken übereinstimmt.

if ($DecodedContent -eq $StopToken) {
        Write-Host "Stopping WebListener"
        $context.Response.Headers.Add("Content-Type", "text/plain")
        $context.Response.StatusCode = 200
        $ResponseBuffer = [System.Text.Encoding]::UTF8.GetBytes("")
        $context.Response.ContentLength64 = $ResponseBuffer.Length
        $context.Response.OutputStream.Write($ResponseBuffer, 0, $ResponseBuffer.Length)
        $context.Response.Close()
        $http.Close()
        break
      }

Es ist sicher genug, nur der RAM kennt das Token und sonst niemand. Alle Module werden unter PowerShell 7 gestartet, und der Pfad zu den Modulen für PowerShell 7 unterscheidet sich vom Pfad in Windows Powershell. Hier wurde alles gestapelt. Denken Sie daran, wenn Sie Ihre eigenen schreiben.

C:\Program Files\PowerShell\7\Modules

Wir machen eine Konfigurationsdatei


Damit all diese Schande ohne starke Kopfschmerzen genutzt werden kann, müssen Sie eine normale Konfigurationsdatei erstellen. Die Datei enthält Variablen und nichts weiter. Die Konfiguration klammert sich an den Standard:

Import-Module $MinecraftPath\config.ps1 -Force

Wir müssen auf das Wichtigste hinweisen. Die Domäne, auf die getippt wird, ist die reguläre, die nach dem Spitznamen des Players sucht. Die Ausgabe kann von Version zu Version variieren, und das Kennwort stammt von rcon.

Es sieht aus wie das:

#,    
$DomainName = "localhost"
 
# ,      
# ,  
$RegExp = "of player ([^ ]+)"
#    ,   ,  .
$RegExpCut = "of player "
 
#  rcon,     server.properties
$rconPassword = "123"

Es ist wünschenswert, die Konfiguration im Serverordner abzulegen, da das Skript sie im root-MinecraftPath sucht

Wie benutzt man das alles?


Zunächst werden diese Skripte installiert und können auf dem Ruvds- Marktplatz verwendet werden. Wenn Sie jedoch noch keinen Client haben oder das Image noch nicht ausprobiert haben, finden Sie hier einen Link zu allen Dateien im Repository. Zögern Sie nicht, einen Commit durchzuführen

  1. Laden Sie PowerShell 7 herunter und installieren Sie es
  2. Laden Sie das Modularchiv herunter und entpacken Sie es


Nun sind alle notwendigen Module und Befehle erschienen. Was machen sie?

Starten Sie Minecraft


Optionen:

-Typ
Forge oder Vanilla. Der Server wird entweder über Server.Jar oder Forge gestartet und die neueste Version im Ordner ausgewählt.

-MinecraftPath Zeigt
auf den Ordner, von dem aus der Server gestartet wird.

-LogFile
Eine alternative Methode zum Sammeln von Protokollen. Gibt eine Datei an, in die alles geschrieben wird, was in der Konsole angezeigt wird.

-StartPaymentListener
Zusammen mit dem Server werden Zahlungen gestartet und akzeptiert. Die Zahlungsannahme selbst ist als separates Modul erhältlich. Ersetzt das Cmdlet Start-Weblistener

Start-Weblistener


Startet das Zahlungsannahmemodul.

-MinecraftPath Zeigt
auf den Ordner mit der Konfigurationsdatei.

-StopToken
Gibt die HTTP-POST-Anforderung von -Body an, um WebListener'a zu stoppen.

Fazit:


Nun, Wunder geschehen.


All Articles