Como fiz um sistema de aceitação de pagamento Minecraft usando o PowerShell puro


Neste artigo, parafusaremos a rosquinha sem Deus no servidor baunilha do Minecraft usando o Powershell. A vantagem do método é que o minecraft é apenas um caso especial da implementação de pagamentos automáticos usando comandos do console. Apenas ouvimos o que o sistema de pagamento nos envia e agrupamos em uma equipe. E o mais importante - sem plugins.
E vamos aceitar pagamentos através do PayPal. Mais importante, para começar a aceitar pagamentos, você não precisa alterar o código, o PayPal nos enviará tudo o que você precisa. Usaremos os botões no site, para que o site possa funcionar com HTML puro. Nós abstraímos das complexidades do próprio sistema de pagamento e nos concentramos apenas nos pontos principais do código.

A propósito, o autor ficará muito feliz se você examinar todos os seus módulos e encontrar erros de crianças que você aponta ou corrige. Aqui está um link para o projeto github .

Algumas palavras sobre IPN


IPN


Aceitaremos pagamentos através dos botões. Os botões não requerem nenhum back-end, eles funcionam em HTML puro e também possuem seus próprios campos.

Os botões acionam o IPN - Notificação de pagamento instantâneo, na qual os dados são enviados ao nosso WebListener. Vamos considerar a estrutura do IPN abaixo.

Além disso, qualquer pessoa que tenha uma conta no PayPal pode criar seu próprio botão.
O IPN não possui a API REST completa do PayPal, mas a funcionalidade básica pode ser implementada nele. De fato, o IPN que estamos considerando não é uma API REST no sentido pleno da palavra, apenas porque o próprio PayPal não espera nada de nós, exceto o código 200.

Levar o WebListener


O PayPal, por motivos de segurança, não envia solicitações via HTTP, portanto, precisamos emitir um certificado para começar. 

O autor usou o WinAcme . Você pode emitir um certificado para qualquer domínio e precisa colocá-lo em um armazenamento de certificados local. A propósito, o WinAcme está localizado na raiz do disco na imagem.

#     
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}"

O Powershell pode usar classes do .net, o que o torna quase igual ao .net. Primeiro, usando a classe HttpListener, levante o servidor da Web.

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

Para verificar se tudo está bem, execute o netstat.



Se nosso script começou a escutar a porta 443 na lista, significa que você fez tudo corretamente e podemos continuar aceitando o processamento da solicitação. Só não se esqueça do firewall.

Aceitar pedido


Usando o IPN Simulator, podemos enviar uma solicitação POST de teste para ver o que é. Como você não pode incluir seus próprios campos, o autor recomenda que você faça um botão e compre imediatamente algo de si mesmo. O Histórico de IPN exibirá uma solicitação normal no botão que você usará. O autor fez exatamente isso comprando um carvão por um rublo.

Aceitaremos o uso do loop While. Enquanto o servidor da web estiver em execução, podemos ler o fluxo de dados recebidos.

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()
  }
}

Se você receber uma aletria como esta, aplique:

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



Depois disso, você finalmente receberá um objeto normal, onde todo o valor é String.



Você pode parar de ler aqui mesmo se não quiser aprofundar o código, mas apenas deseja aceitar solicitações da API de alguém.

Aqui está o código que funciona imediatamente, copie e use:

# 
$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


Então, descobrimos como podemos receber alertas sobre pagamentos, agora podemos creditá-los. Mas aqui também não é tão simples. O problema é que o jogo não fornece itens ou altera o status dos jogadores que não estão no servidor. Ou seja, precisamos esperar até que uma pessoa entre no servidor para lhe dar o que pagou.

Portanto, sua atenção é apresentada ao conceito geral de fumante, para crédito de pagamentos.



Os pagamentos são recebidos pelo Ouvinte acima; apenas uma linha foi adicionada a ele para gravar o objeto no arquivo. O Pagamento Completo (Processador) examina o apelido e o combina com o nome do arquivo. Se ele encontrar um arquivo, compila um comando para o rcon e o executa.

Iniciar minecraftsobre o qual o autor escreveu em um artigo anterior foi ligeiramente modificado. Agora ele ouve a conclusão, olha os apelidos dos jogadores e os passa para o processador de pagamentos.

Fazendo retornos de chamada reais


Sem usar plug-ins, faremos retornos de chamada verdadeiros. Para isso, o Start-Minecraft foi modificado. Agora ele não apenas sabe como adicionar o StdOut a um arquivo, mas também percorre cada linha com uma programação regular. Felizmente, o minecraft deixa uma mensagem muito específica quando um jogador entra no servidor.

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

É muito fácil escolher um apelido nesta linha. Aqui está todo o código que precisamos para obter dados das seqüências de caracteres Stdout.

$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) {
        #   
    }
}


Uma nova linha é inserida no pipeline $ _, nós a escrevemos na janela do console e a analisamos regularmente. O regular em si nos notifica quando funciona, o que é muito conveniente.

A partir daqui, podemos chamar qualquer código. Por exemplo, usando o mesmo RCON, podemos cumprimentar o jogador no PM, usando o bot na discórdia para notificar que alguém fez logon no servidor, banir o xeque-mate e assim por diante.

Fazendo pagamentos


Desde que começamos a processar os pagamentos, gostaríamos de ter dados pelo menos bastante completos sobre a operação e o histórico das operações realizadas, porque estamos falando de números com dois zeros, por assim dizer.

O autor quer deixar tudo extremamente simples e ainda não simular uma base. Vejamos a abordagem NoSQL. Vamos criar nossa própria classe, que importará todos os pagamentos aceitos para a pasta / payment / nos arquivos Json.

    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

Isso é tudo o que foi exigido do nosso ouvinte. Receba dados do PayPal e grave em um arquivo.

Fazemos o processamento do pagamento


O manipulador será chamado de regular que foi escrito anteriormente. Transferimos o apelido do jogador para o módulo e é isso. Em seguida, um novo script é iniciado, que procura o arquivo e, se houver, ele fornece ao player o item que está escrito no arquivo.

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
        }
    }

Quando a regularidade é acionada, é lançado um módulo que completa o pagamento, ou seja, fornece um item ao jogador. Para fazer isso, na pasta / Pagamentos / Pendente /, o script procura arquivos que contenham o apelido do jogador que entrou no jogo e lê seu conteúdo.

Agora você precisa coletar o comando para o servidor e enviá-lo para lá. Ele será coletado de um arquivo. Sabemos o apelido do jogador, o nome do item e seu ID foram registrados, quantas peças também foram registradas, resta apenas enviar um comando ao servidor do jogo. Para isso, usaremos o 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
    }

Fazemos tudo isso em um módulo conveniente


O processo Java e o processo WebListener requerem encadeamentos diferentes, mas o autor não está satisfeito com a necessidade de executar o WebListener separadamente e o servidor separadamente. O autor quer tudo de uma vez com uma equipe.

Portanto, usando o Powershell 7, lançaremos isso e aquilo. E isso nos ajudará:

ForEach-Object -Parallel {}

O cmdlet trabalha com inputObject, portanto, alimentamos uma matriz descomplicada e compartilhamos os fluxos usando uma opção.

"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\"
        }
        
    }
}

Então, de uma maneira muleta, iniciamos dois processos diferentes em um terminal e nem perdemos informações. Mas havia outro problema. O WebListener bloqueia o console após um desligamento regular do servidor e não deseja ir a lugar algum.

Para não reiniciar o terminal toda vez, uma chave aleatória foi adicionada ao Start-MinecraftHandler.ps1 e ao Start-WebListener.ps1, que interromperá o servidor via POST no WebListener.

Start-MinecraftHandler.ps1, quando registra uma conclusão bem-sucedida, executa o comando:

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

$ StopToken contém um valor numérico aleatório que é pré-passado pelo script de inicialização para o Listener e o Handler. O Ouvinte examina o que recebeu na solicitação e desativa se o corpo da solicitação corresponder a $ StopToken.

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
      }

É seguro o suficiente, apenas a RAM conhece o token e mais ninguém. Todos os módulos são iniciados no PowerShell 7 e o caminho para os módulos do PowerShell 7 é diferente do caminho no Windows Powershell. Tudo estava empilhado aqui. Lembre-se de escrever o seu próprio.

C:\Program Files\PowerShell\7\Modules

Nós fazemos um arquivo de configuração


Para que toda essa desgraça possa ser usada sem uma forte dor de cabeça, você precisa criar um arquivo de configuração normal. O arquivo conterá variáveis ​​e nada mais. A configuração se apega usando o padrão:

Import-Module $MinecraftPath\config.ps1 -Force

Precisamos apontar a coisa mais importante. O domínio que está sendo tocado é o comum que procura o apelido do jogador, pois a saída pode variar de versão para versão e a senha é da rcon.

Se parece com isso:

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

É desejável colocar a configuração na pasta do servidor, porque o script a está procurando no caminho-MinecraftPath raiz

Como usar tudo isso?


Antes de tudo, esses scripts estão instalados e prontos para uso no mercado Ruvds , mas se você ainda não possui um cliente ou ainda não experimentou a imagem, aqui está um link para todos os arquivos no repositório , não hesite em confirmar. 

  1. Baixe e instale o PowerShell 7
  2. Baixe e descompacte o arquivo de módulos


Agora todos os módulos e comandos necessários apareceram. O que eles estão fazendo?

Iniciar minecraft


Opções:

-Tipo
Forge ou Baunilha. Ele inicia o servidor no Server.Jar ou Forge, escolhendo a versão mais recente que está na pasta.

-MinecraftPath
Aponta para a pasta na qual o servidor será iniciado.

-LogFile
Uma maneira alternativa de coletar logs. Indica um arquivo no qual tudo que aparece no console será gravado.

-StartPaymentListener
Juntamente com o servidor, ele inicia e aceita pagamentos. A própria aceitação de pagamento está disponível como um módulo separado. Substitui o cmdlet Start-Weblistener

Start-weblistener


Inicia o módulo de aceitação de pagamento.

-MinecraftPath
Aponta para a pasta com o arquivo de configuração.

-StopToken
Especifica a solicitação -Body HTTP POST para parar o WebListener'a.

Conclusão:


Bem, milagres acontecem.


All Articles