O Google App Script, Mikrotik, Telegram e VPNBook começaram a tocar um quarteto

Hoje no programa: onde mais você pode aplicar o Script do Google Apps se as idéias normais acabarem? Automação do trabalho com o VPNBook por meio de uma cadeia de scripts em diferentes idiomas que eu não conheço. Nedo-cURL por Mikrotik. Telegrama através de um lugar, para não ficar em outro, a auto-supervisão permite.

Parte 1. Sem título


Há um ano, escrevi uma nota " Quase OCR para obter uma senha do VPNBook. PHP + Mikrotik " sobre como configurar a recuperação automática de senha em um roteador Mikrotik para obter acesso gratuito à VPN via VPNBook. O começo da história está aí.

Desde então, muita água correu; na Rússia, eles bloquearam o site VPNBook, mas não os servidores VPN públicos, que são publicados nele. Agora, esse script PHP para decodificar uma imagem PNG de uma senha em uma sequência de texto também funcionará quando iniciado em um servidor cujo tráfego não é passado pelo sistema de bloqueio. Mas, há algum tempo, experimentando o serviço script.google.com do Google Apps Script (GAS), decidi abandonar o script PHP em um servidor web externo, substituindo-o parcial ou totalmente por um script GAS em execução como um aplicativo Web (aplicativo Web). Não entendi a política de execução e as restrições de GAS, mas tudo o que fiz funciona em uma conta gratuita do Google e ainda não pede dinheiro. Não tenho o objetivo de descrever o Script do Google Apps em detalhes. O GAS é baseado na linguagem JavaScript, você pode usar bibliotecas JS de terceiros, publicar o script como um aplicativo da Web, que pode ser disponibilizado para todos sem autorização. Os recursos da implementação atual do GAS não eram suficientes para mim, então tive que sair e procurar soluções alternativas.

No começo, decidi escrever um proxy para imagens PNG. O script da web deveria solicitar uma imagem de senha no site do VPNBook (lembro que a senha foi publicada lá em PNG) e fornecê-la ao cliente que chamou esse script para decodificação. Uma maneira de contornar a fechadura. Aqui a primeira restrição do GAS foi cumprida. Acontece que o script não pode renderizar MIME image / png, mas apenas formatos de texto, JSON, TEXT, XML, etc. Mas havia uma maneira de contornar isso. Você pode codificar PNG para Base64 e retornar uma sequência de texto para o cliente. Existem scripts semelhantes na Internet, por exemplo, techslides.com/image-proxy-with-google-app-scripts . Eu apenas simplifiquei um deles. Eu precisava de apenas uma imagem e emitir apenas seqüências Base64. O resultado é um script que consiste em apenas uma função doGet - um manipulador de solicitações GET que retorna uma string em resposta.

function doGet() { var response = UrlFetchApp.fetch('https://www.vpnbook.com/password.php'); var b64 = Utilities.base64Encode(response.getContent()); //var data = 'data:'+type+';base64,'+b64; return ContentService.createTextOutput(b64); } 

Exemplo de saída do navegador:

 iVBORw0KGgoAAAANSUhEUgAAAGQAAAANAQMAAABl11mFAAAABlBMVEX29vZMTExY89ZbAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAVUlEQVQImWNgIBrwSzCw/2ZgOADhSc5gYJCG8wxQedLdCcYFNXcgPHOZsxuSZxx7BuFZzsjdcJi34TBU5Y3cjc3IvM3McJ7kjNxtzDwwffwSIB7UTACt/h52C5DFqQAAAABJRU5ErkJggg== 

A seguir, vem o script PHP, que pode ser colocado no servidor dentro da zona com bloqueio de recursos. É muito semelhante ao script do artigo anterior, exceto por uma pequena alteração nos parâmetros da chamada cURL. É necessário permitir que o cURL passe por HTTP / 1.1 302 movido temporariamente, porque O GAS, quando chamado, redireciona do endereço de script da web para um endereço temporário dinâmico:

 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); 

E decodificação Base64:

 $imgOCR = imagecreatefromstring(base64_decode($output)); 

Script em si
 <?php //   $wchar = 9; $hchar = 13; $strDict = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 '; $imgDict = imagecreatetruecolor(2 + strlen($strDict)* $wchar, $hchar); $bg = imagecolorallocate($imgDict, 0xF6, 0xF6, 0xF6); $textcolor = imagecolorallocate($imgDict, 0x4C, 0x4C, 0x4C); imagefill($imgDict, 0, 0, $bg); imagestring($imgDict, 5, 2, 0, $strDict, $textcolor); //  cURL $ch = curl_init(); //  url,      //curl_setopt($ch, CURLOPT_URL, 'https://www.vpnbook.com/password.php'); curl_setopt($ch, CURLOPT_URL, 'https://script.google.com/macros/s/AKfycbwYPfaZobtjbFv0mSYI8U4NIXPh1Sft_DkGH8QKgg/exec'); //  ,      string curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_BINARYTRANSFER, 1); // also, this seems wise considering output is image. //   $output = curl_exec($ch); //  cURL curl_close($ch); // echo $output; $imgOCR = imagecreatefromstring(base64_decode($output)); //$imgOCR = imageCreateFromPng('password.png'); //      10  . 2 + 10*9 = 92 < 100 $maxchar = floor((imagesx($imgOCR) - 2) / 9); $imgBox = imagecreatetruecolor($wchar, $hchar); $hashDict = Array(); //   for ($k = 0; $k < strlen($strDict) ; $k++) { imagecopy($imgBox, $imgDict, 0, 0, 2 + $k * $wchar, 0, $wchar, $hchar); $hashStr = ""; for($y = 0; $y < $hchar ; $y++) for($x = 0; $x < $wchar; $x++) $hashStr .= (imagecolorat($imgBox, $x, $y) != 0xF6F6F6)? '1': '0'; $hashDict[$hashStr] = $strDict[$k]; } //     for ($k = 0; $k < $maxchar ; $k++) { imagecopy($imgBox, $imgOCR, 0, 0, 2 + $k * $wchar, 0, $wchar, $hchar); $hashStr = ""; for($y = 0; $y < $hchar ; $y++) for($x = 0; $x < $wchar; $x++) $hashStr .= (imagecolorat($imgBox, $x, $y) != 0xF6F6F6)? '1': '0'; $tempchar = $hashDict[$hashStr]; if ($tempchar=='u' || $tempchar=='y') //    $tempchar = (mt_rand(0, 1))? 'u': 'y'; //$tempchar = (time() / 60 % 60 % 2)? 'u': 'y'; elseif ($tempchar==' ') break; print($tempchar); } /* header('Content-type: image/png'); imagepng($imgDict); */ //var_dump($hashDict); imagedestroy($imgDict); imagedestroy($imgOCR); imagedestroy($imgBox); ?> 


Esse script PHP decodifica a senha PNG e a retorna como uma sequência de texto. Além disso, como no primeiro artigo da parte sobre Mikrotik. O roteador pega a senha usando buscar.
O resultado foi um esquema de funcionamento de 2 serviços intermediários na frente do Mikrotik.

Parte 2. Pressione GAS. Livre-se do script decodificador PHP


Durante os experimentos com o GAS, surgiu a idéia de abandonar o decodificador de senhas no PHP, reescrevendo-o no GAS. E aqui foi descoberto um grande problema: o script do Google não possui funções de processamento PNG, a única coisa que pode ser feita é converter PNG em uma matriz de bytes. Não houve dúvida de qualquer manipulação com partes e pixels da imagem. Subi no Github em busca de uma biblioteca JS para trabalhar com PNG, encontrei muitos deles: PNG.js, UPNG.js, pngjs. Alguns não suportam a profundidade de cor de 1 bit de um pixel PNG (imagem com senha). Eles puxaram várias bibliotecas de compactação zlib. Em geral, tudo me pareceu um pouco complicado, e decidi escrever sozinho um conversor primitivo apenas para minha imagem PNG em um bitmap com a função de acessar pixels pelas coordenadas XY. Então veio uma imersão completa no formato PNG: editor hexadecimal, padrões de leitura, montes de descrições na rede. E, finalmente, encontrei a seção PNG do arquivo IDAT, embalada com zlib, que continha uma matriz de pixels.

Exigia uma função para descompactar o zlib, que obviamente não estava no GAS. Surpreendentemente, eles têm gzip / ungzip e zip / unzip, mas não zlib. Depois de ler sobre o gzip (o segundo nível de imersão após o formato PNG), cheguei à conclusão de que não seria possível montar uma “bicicleta” na forma de um quase-arquivo gzip na seção IDAT, embora a compactação zlib seja usada aqui e ali. Porque Para criar um arquivo gzip válido, você precisa saber o tamanho dos dados descompactados, o que eu não conseguia obter sem descompactá-los :) E com o tamanho errado, o GAS considerou o arquivo corrompido. No final, virei para o Github e encontrei uma ótima solução: a biblioteca de scripts zlib.js para o Google Apps (https://github.com/hinimub/zlib.js/blob/develop/README.en.md). Que foi especialmente preparado para integração em projetos de GAS através de bibliotecas de chaves de projeto. Então o quebra-cabeça começou a convergir. Após escrever a descompressão da matriz de pixels e a função de acessar as coordenadas do pixel XY, foi possível transferir o script do decodificador do PHP para o GAS.

Calculou separadamente uma tabela de hash de um dicionário de possíveis caracteres de senha. Esta é uma ação única que realizei em um programa de terceiros (no LabVIEW, olá, colegas). Cada caractere na imagem pode ser alocado como 8 bits (sem indentação) x 10 linhas. 1 byte é suficiente para codificar 8 pixels de uma linha de um caractere. Você pode armazenar uma sequência de pixels em um número inteiro (byte) e o caractere inteiro como uma sequência de 10 bytes. Acontece 10 números hexadecimais por caractere. Em seguida, o decodificador GAS repete seu progenitor PHP.

O resultado é um script que funciona totalmente no GAS.
 function doGet() { //var file = DriveApp.getFilesByName("password2.png").next(); //var image = file.getBlob(); var image = UrlFetchApp.fetch('https://www.vpnbook.com/password.php').getBlob(); var imageString = image.getDataAsString(); var imageArray = image.getBytes().map(function(e) { return e & 0xff; }); // imageArray = blobToUint8(imageArray); var chunkIDATStart = imageString.indexOf("IDAT") + 4; //   IDAT var chunkIDATLen = bytesToUint32(imageArray, imageString.indexOf("IDAT") - 4); //   IDAT var IDATArray = imageArray.slice(chunkIDATStart, chunkIDATStart + chunkIDATLen) var inflate = new zlibjs.Inflate(IDATArray); //  IDAT   zlib var plain = inflate.decompress(); const Width = 100; //   const Height = 13; //   const wchar = 9; //   const hchar = 13; //   //Logger.log(typeof(plain)); //Logger.log(plain); rowlen = (Width / 8) >> 0; //   ,    if ((Width - rowlen * 8) > 0) { //    rowlen+=2; // +1 filter byte at the beginning of each row.    PNG } else { rowlen++; } function getXY(x, y) { //  : 0/1 var xbyte = (x / 8 >> 0); //  ,   //Logger.log("xbyte: " + xbyte); var xbit = x - xbyte * 8; //  ,    //Logger.log("xbit: " + xbit); return (plain[xbyte + 1 + y * rowlen] << xbit & 0x80) >> 7; // +1 filter byte at the beginning of each row } // Logger.log("getXY: " + getXY(4, 3)); // - .        8  (  ) x 10 ,        (byte),      10  . //  10 hex   . var hashDict = {'183C66C3C3C3FFC3C3C3':'A','FCC6C3C6FCC6C3C3C6FC':'B','3E63C1C0C0C0C0C1633E':'C','FCC6C3C3C3C3C3C3C6FC':'D', 'FEC0C0C0FCC0C0C0C0FE':'E','FFC0C0C0FCC0C0C0C0C0':'F','3E63C0C0C0C7C3C3633E':'G','C3C3C3C3FFC3C3C3C3C3':'H', '7E18181818181818187E':'I','1E666666466C38':'J','C3C6CCD8F0F0D8CCC6C3':'K','C0C0C0C0C0C0C0C0C0FE':'L', 'C3E7FFDBDBDBC3C3C3C3':'M','C3E3F3F3DBDBCFC7C7C3':'N','3C66C3C3C3C3C3C3663C':'O','FEC3C3C3FEC0C0C0C0C0':'P', '3C66C3C3C3C3DBCF663D':'Q','FEC3C3C3FEF8CCC6C3C3':'R','7EC3C0C07E333C37E':'S','FF181818181818181818':'T', 'C3C3C3C3C3C3C3C3663C':'U','C3C3C36666663C3C1818':'V','C3C3C3C3DBDBDBFFE7C3':'W','C3C3663C18183C66C3C3':'X', 'C3C3663C181818181818':'Y','FE66C183060C0C0FE':'Z','0003E6337FC3C77B':'a','C0C0C0DCE6C3C3C3E6DC':'b', '0003E63C0C0C0633E':'c','3333B67C3C3C3673B':'d','0003C66C3FFC0633E':'e','1E33333030FC30303030':'f', '0007DC7C6C67CC07E':'g','C0C0C0DCE6C3C3C3C3C3':'h','181803818181818187E':'i','660E66666C6':'j', '606060666C78786C6663':'k','3818181818181818183C':'l','000B6DBDBDBDBDBDB':'m','000DCE6C3C3C3C3C3':'n', '0003C66C3C3C3663C':'o','000DCE6C3C3C3E6DC':'p','0003B67C3C3C3673B':'q','000DE736060606060':'r', '0007EC3C07E3C37E':'s','03030FC30303030331E':'t','000C3C3C3C3C3673B':'u','000C3C366663C3C18':'v', '000C3C3DBDBDBFF66':'w','000C3663C183C66C3':'x','000C3C3C3C3C3673B':'y','0007E6C1830607E':'z', '183C66C3C3C3C3663C18':'0','1838781818181818187E':'1','3C66C336C183060FF':'2','7CC6361C633C67C':'3', '6E1E3666C6FF666':'4','FEC0C0DCE633C3663C':'5','3C66C2C0DCE6C3C3663C':'6','FF336C183060C0C0':'7', '3C66C3663C66C3C3663C':'8','3C66C3C3673B343663C':'9','0000000000':' '}; //      10  . 2 + 10*9 = 92 < 100 const maxchar = (Width - 2) / wchar >> 0; var password = ''; for (var charX = 2; charX < maxchar * wchar + 2; charX+=wchar) { //    var hash = ''; //   for (var charY = 3; charY < hchar; charY++) { //   Y- var charrow = 0; //   -  8   for (var charXbit = 0; charXbit < 8; charXbit++) { //   X- charrow <<= 1; charrow |= getXY(charX + charXbit, charY); } hash += charrow.toString(16).toUpperCase(); //Logger.log("charrow: " + charrow.toString(2)); //Logger.log("charrow: " + charrow.toString(16).toUpperCase()); } var tempChar = hashDict[hash]; if (tempChar === 'u' || tempChar === 'y') { //     tempChar = (Date.now() % 2) ? 'u': 'y'; } if (tempChar !== ' ') { password += tempChar; // Logger.log("hash: " + hash); // Logger.log("Char: " + tempChar); } } Logger.log("password: " + password); return ContentService.createTextOutput(password); } function blobToUint8(blob) { return blob.map(function(e){ return e & 0xff; }); } function bytesToUint32(byteArray, start) { var value = 0; for (var i = start; i < start + 4; i++) { value = (value * 256) + (byteArray[i] & 0xff); } return value; } function my2() { var file = DriveApp.getFilesByName("password2.png").next(); // var file = DriveApp.getFilesByName("test.bin").next(); var image = file.getBlob(); //var imageArray = image.getBytes(); //var img = UrlFetchApp.fetch('http://example.com/image.png'); var reader = new pngjs.PNGReader(image.getBytes()); var png = reader.parse(function(err, png){ if (err) throw err; return png; }); Logger.log(png); } 'A', 'FCC6C3C6FCC6C3C3C6FC': 'B', '3E63C1C0C0C0C0C1633E': 'C', 'FCC6C3C3C3C3C3C3C6FC': 'D', 'FEC0C0C0FCC0C0C0C0FE': 'E', 'FFC0C0C0FCC0C0C0C0C0': 'F function doGet() { //var file = DriveApp.getFilesByName("password2.png").next(); //var image = file.getBlob(); var image = UrlFetchApp.fetch('https://www.vpnbook.com/password.php').getBlob(); var imageString = image.getDataAsString(); var imageArray = image.getBytes().map(function(e) { return e & 0xff; }); // imageArray = blobToUint8(imageArray); var chunkIDATStart = imageString.indexOf("IDAT") + 4; //   IDAT var chunkIDATLen = bytesToUint32(imageArray, imageString.indexOf("IDAT") - 4); //   IDAT var IDATArray = imageArray.slice(chunkIDATStart, chunkIDATStart + chunkIDATLen) var inflate = new zlibjs.Inflate(IDATArray); //  IDAT   zlib var plain = inflate.decompress(); const Width = 100; //   const Height = 13; //   const wchar = 9; //   const hchar = 13; //   //Logger.log(typeof(plain)); //Logger.log(plain); rowlen = (Width / 8) >> 0; //   ,    if ((Width - rowlen * 8) > 0) { //    rowlen+=2; // +1 filter byte at the beginning of each row.    PNG } else { rowlen++; } function getXY(x, y) { //  : 0/1 var xbyte = (x / 8 >> 0); //  ,   //Logger.log("xbyte: " + xbyte); var xbit = x - xbyte * 8; //  ,    //Logger.log("xbit: " + xbit); return (plain[xbyte + 1 + y * rowlen] << xbit & 0x80) >> 7; // +1 filter byte at the beginning of each row } // Logger.log("getXY: " + getXY(4, 3)); // - .        8  (  ) x 10 ,        (byte),      10  . //  10 hex   . var hashDict = {'183C66C3C3C3FFC3C3C3':'A','FCC6C3C6FCC6C3C3C6FC':'B','3E63C1C0C0C0C0C1633E':'C','FCC6C3C3C3C3C3C3C6FC':'D', 'FEC0C0C0FCC0C0C0C0FE':'E','FFC0C0C0FCC0C0C0C0C0':'F','3E63C0C0C0C7C3C3633E':'G','C3C3C3C3FFC3C3C3C3C3':'H', '7E18181818181818187E':'I','1E666666466C38':'J','C3C6CCD8F0F0D8CCC6C3':'K','C0C0C0C0C0C0C0C0C0FE':'L', 'C3E7FFDBDBDBC3C3C3C3':'M','C3E3F3F3DBDBCFC7C7C3':'N','3C66C3C3C3C3C3C3663C':'O','FEC3C3C3FEC0C0C0C0C0':'P', '3C66C3C3C3C3DBCF663D':'Q','FEC3C3C3FEF8CCC6C3C3':'R','7EC3C0C07E333C37E':'S','FF181818181818181818':'T', 'C3C3C3C3C3C3C3C3663C':'U','C3C3C36666663C3C1818':'V','C3C3C3C3DBDBDBFFE7C3':'W','C3C3663C18183C66C3C3':'X', 'C3C3663C181818181818':'Y','FE66C183060C0C0FE':'Z','0003E6337FC3C77B':'a','C0C0C0DCE6C3C3C3E6DC':'b', '0003E63C0C0C0633E':'c','3333B67C3C3C3673B':'d','0003C66C3FFC0633E':'e','1E33333030FC30303030':'f', '0007DC7C6C67CC07E':'g','C0C0C0DCE6C3C3C3C3C3':'h','181803818181818187E':'i','660E66666C6':'j', '606060666C78786C6663':'k','3818181818181818183C':'l','000B6DBDBDBDBDBDB':'m','000DCE6C3C3C3C3C3':'n', '0003C66C3C3C3663C':'o','000DCE6C3C3C3E6DC':'p','0003B67C3C3C3673B':'q','000DE736060606060':'r', '0007EC3C07E3C37E':'s','03030FC30303030331E':'t','000C3C3C3C3C3673B':'u','000C3C366663C3C18':'v', '000C3C3DBDBDBFF66':'w','000C3663C183C66C3':'x','000C3C3C3C3C3673B':'y','0007E6C1830607E':'z', '183C66C3C3C3C3663C18':'0','1838781818181818187E':'1','3C66C336C183060FF':'2','7CC6361C633C67C':'3', '6E1E3666C6FF666':'4','FEC0C0DCE633C3663C':'5','3C66C2C0DCE6C3C3663C':'6','FF336C183060C0C0':'7', '3C66C3663C66C3C3663C':'8','3C66C3C3673B343663C':'9','0000000000':' '}; //      10  . 2 + 10*9 = 92 < 100 const maxchar = (Width - 2) / wchar >> 0; var password = ''; for (var charX = 2; charX < maxchar * wchar + 2; charX+=wchar) { //    var hash = ''; //   for (var charY = 3; charY < hchar; charY++) { //   Y- var charrow = 0; //   -  8   for (var charXbit = 0; charXbit < 8; charXbit++) { //   X- charrow <<= 1; charrow |= getXY(charX + charXbit, charY); } hash += charrow.toString(16).toUpperCase(); //Logger.log("charrow: " + charrow.toString(2)); //Logger.log("charrow: " + charrow.toString(16).toUpperCase()); } var tempChar = hashDict[hash]; if (tempChar === 'u' || tempChar === 'y') { //     tempChar = (Date.now() % 2) ? 'u': 'y'; } if (tempChar !== ' ') { password += tempChar; // Logger.log("hash: " + hash); // Logger.log("Char: " + tempChar); } } Logger.log("password: " + password); return ContentService.createTextOutput(password); } function blobToUint8(blob) { return blob.map(function(e){ return e & 0xff; }); } function bytesToUint32(byteArray, start) { var value = 0; for (var i = start; i < start + 4; i++) { value = (value * 256) + (byteArray[i] & 0xff); } return value; } function my2() { var file = DriveApp.getFilesByName("password2.png").next(); // var file = DriveApp.getFilesByName("test.bin").next(); var image = file.getBlob(); //var imageArray = image.getBytes(); //var img = UrlFetchApp.fetch('http://example.com/image.png'); var reader = new pngjs.PNGReader(image.getBytes()); var png = reader.parse(function(err, png){ if (err) throw err; return png; }); Logger.log(png); } '' C3C3C3C3FFC3C3C3C3C3 ':' H '' 7E18181818181818187E ':' I '' 1E666666466C38 ':' J '' C3C6CCD8F0F0D8CCC6C3 ':' K '' C0C0C0C0C0C0C0C0C0FE ':' L', function doGet() { //var file = DriveApp.getFilesByName("password2.png").next(); //var image = file.getBlob(); var image = UrlFetchApp.fetch('https://www.vpnbook.com/password.php').getBlob(); var imageString = image.getDataAsString(); var imageArray = image.getBytes().map(function(e) { return e & 0xff; }); // imageArray = blobToUint8(imageArray); var chunkIDATStart = imageString.indexOf("IDAT") + 4; //   IDAT var chunkIDATLen = bytesToUint32(imageArray, imageString.indexOf("IDAT") - 4); //   IDAT var IDATArray = imageArray.slice(chunkIDATStart, chunkIDATStart + chunkIDATLen) var inflate = new zlibjs.Inflate(IDATArray); //  IDAT   zlib var plain = inflate.decompress(); const Width = 100; //   const Height = 13; //   const wchar = 9; //   const hchar = 13; //   //Logger.log(typeof(plain)); //Logger.log(plain); rowlen = (Width / 8) >> 0; //   ,    if ((Width - rowlen * 8) > 0) { //    rowlen+=2; // +1 filter byte at the beginning of each row.    PNG } else { rowlen++; } function getXY(x, y) { //  : 0/1 var xbyte = (x / 8 >> 0); //  ,   //Logger.log("xbyte: " + xbyte); var xbit = x - xbyte * 8; //  ,    //Logger.log("xbit: " + xbit); return (plain[xbyte + 1 + y * rowlen] << xbit & 0x80) >> 7; // +1 filter byte at the beginning of each row } // Logger.log("getXY: " + getXY(4, 3)); // - .        8  (  ) x 10 ,        (byte),      10  . //  10 hex   . var hashDict = {'183C66C3C3C3FFC3C3C3':'A','FCC6C3C6FCC6C3C3C6FC':'B','3E63C1C0C0C0C0C1633E':'C','FCC6C3C3C3C3C3C3C6FC':'D', 'FEC0C0C0FCC0C0C0C0FE':'E','FFC0C0C0FCC0C0C0C0C0':'F','3E63C0C0C0C7C3C3633E':'G','C3C3C3C3FFC3C3C3C3C3':'H', '7E18181818181818187E':'I','1E666666466C38':'J','C3C6CCD8F0F0D8CCC6C3':'K','C0C0C0C0C0C0C0C0C0FE':'L', 'C3E7FFDBDBDBC3C3C3C3':'M','C3E3F3F3DBDBCFC7C7C3':'N','3C66C3C3C3C3C3C3663C':'O','FEC3C3C3FEC0C0C0C0C0':'P', '3C66C3C3C3C3DBCF663D':'Q','FEC3C3C3FEF8CCC6C3C3':'R','7EC3C0C07E333C37E':'S','FF181818181818181818':'T', 'C3C3C3C3C3C3C3C3663C':'U','C3C3C36666663C3C1818':'V','C3C3C3C3DBDBDBFFE7C3':'W','C3C3663C18183C66C3C3':'X', 'C3C3663C181818181818':'Y','FE66C183060C0C0FE':'Z','0003E6337FC3C77B':'a','C0C0C0DCE6C3C3C3E6DC':'b', '0003E63C0C0C0633E':'c','3333B67C3C3C3673B':'d','0003C66C3FFC0633E':'e','1E33333030FC30303030':'f', '0007DC7C6C67CC07E':'g','C0C0C0DCE6C3C3C3C3C3':'h','181803818181818187E':'i','660E66666C6':'j', '606060666C78786C6663':'k','3818181818181818183C':'l','000B6DBDBDBDBDBDB':'m','000DCE6C3C3C3C3C3':'n', '0003C66C3C3C3663C':'o','000DCE6C3C3C3E6DC':'p','0003B67C3C3C3673B':'q','000DE736060606060':'r', '0007EC3C07E3C37E':'s','03030FC30303030331E':'t','000C3C3C3C3C3673B':'u','000C3C366663C3C18':'v', '000C3C3DBDBDBFF66':'w','000C3663C183C66C3':'x','000C3C3C3C3C3673B':'y','0007E6C1830607E':'z', '183C66C3C3C3C3663C18':'0','1838781818181818187E':'1','3C66C336C183060FF':'2','7CC6361C633C67C':'3', '6E1E3666C6FF666':'4','FEC0C0DCE633C3663C':'5','3C66C2C0DCE6C3C3663C':'6','FF336C183060C0C0':'7', '3C66C3663C66C3C3663C':'8','3C66C3C3673B343663C':'9','0000000000':' '}; //      10  . 2 + 10*9 = 92 < 100 const maxchar = (Width - 2) / wchar >> 0; var password = ''; for (var charX = 2; charX < maxchar * wchar + 2; charX+=wchar) { //    var hash = ''; //   for (var charY = 3; charY < hchar; charY++) { //   Y- var charrow = 0; //   -  8   for (var charXbit = 0; charXbit < 8; charXbit++) { //   X- charrow <<= 1; charrow |= getXY(charX + charXbit, charY); } hash += charrow.toString(16).toUpperCase(); //Logger.log("charrow: " + charrow.toString(2)); //Logger.log("charrow: " + charrow.toString(16).toUpperCase()); } var tempChar = hashDict[hash]; if (tempChar === 'u' || tempChar === 'y') { //     tempChar = (Date.now() % 2) ? 'u': 'y'; } if (tempChar !== ' ') { password += tempChar; // Logger.log("hash: " + hash); // Logger.log("Char: " + tempChar); } } Logger.log("password: " + password); return ContentService.createTextOutput(password); } function blobToUint8(blob) { return blob.map(function(e){ return e & 0xff; }); } function bytesToUint32(byteArray, start) { var value = 0; for (var i = start; i < start + 4; i++) { value = (value * 256) + (byteArray[i] & 0xff); } return value; } function my2() { var file = DriveApp.getFilesByName("password2.png").next(); // var file = DriveApp.getFilesByName("test.bin").next(); var image = file.getBlob(); //var imageArray = image.getBytes(); //var img = UrlFetchApp.fetch('http://example.com/image.png'); var reader = new pngjs.PNGReader(image.getBytes()); var png = reader.parse(function(err, png){ if (err) throw err; return png; }); Logger.log(png); } 'C3E3F3F3DBDBCFC7C7C3': 'N', N '3C66C3C3C3C3C3C3663C': 'O', 'FEC3C3C3FEC0C0C0C0C0': 'P', '3C66C3C3C3C3DBCF663D': 'Q', 'FEC3C3C3FEF8CCC6C3C3': 'R', '7EC3C0C07E333C37E function doGet() { //var file = DriveApp.getFilesByName("password2.png").next(); //var image = file.getBlob(); var image = UrlFetchApp.fetch('https://www.vpnbook.com/password.php').getBlob(); var imageString = image.getDataAsString(); var imageArray = image.getBytes().map(function(e) { return e & 0xff; }); // imageArray = blobToUint8(imageArray); var chunkIDATStart = imageString.indexOf("IDAT") + 4; //   IDAT var chunkIDATLen = bytesToUint32(imageArray, imageString.indexOf("IDAT") - 4); //   IDAT var IDATArray = imageArray.slice(chunkIDATStart, chunkIDATStart + chunkIDATLen) var inflate = new zlibjs.Inflate(IDATArray); //  IDAT   zlib var plain = inflate.decompress(); const Width = 100; //   const Height = 13; //   const wchar = 9; //   const hchar = 13; //   //Logger.log(typeof(plain)); //Logger.log(plain); rowlen = (Width / 8) >> 0; //   ,    if ((Width - rowlen * 8) > 0) { //    rowlen+=2; // +1 filter byte at the beginning of each row.    PNG } else { rowlen++; } function getXY(x, y) { //  : 0/1 var xbyte = (x / 8 >> 0); //  ,   //Logger.log("xbyte: " + xbyte); var xbit = x - xbyte * 8; //  ,    //Logger.log("xbit: " + xbit); return (plain[xbyte + 1 + y * rowlen] << xbit & 0x80) >> 7; // +1 filter byte at the beginning of each row } // Logger.log("getXY: " + getXY(4, 3)); // - .        8  (  ) x 10 ,        (byte),      10  . //  10 hex   . var hashDict = {'183C66C3C3C3FFC3C3C3':'A','FCC6C3C6FCC6C3C3C6FC':'B','3E63C1C0C0C0C0C1633E':'C','FCC6C3C3C3C3C3C3C6FC':'D', 'FEC0C0C0FCC0C0C0C0FE':'E','FFC0C0C0FCC0C0C0C0C0':'F','3E63C0C0C0C7C3C3633E':'G','C3C3C3C3FFC3C3C3C3C3':'H', '7E18181818181818187E':'I','1E666666466C38':'J','C3C6CCD8F0F0D8CCC6C3':'K','C0C0C0C0C0C0C0C0C0FE':'L', 'C3E7FFDBDBDBC3C3C3C3':'M','C3E3F3F3DBDBCFC7C7C3':'N','3C66C3C3C3C3C3C3663C':'O','FEC3C3C3FEC0C0C0C0C0':'P', '3C66C3C3C3C3DBCF663D':'Q','FEC3C3C3FEF8CCC6C3C3':'R','7EC3C0C07E333C37E':'S','FF181818181818181818':'T', 'C3C3C3C3C3C3C3C3663C':'U','C3C3C36666663C3C1818':'V','C3C3C3C3DBDBDBFFE7C3':'W','C3C3663C18183C66C3C3':'X', 'C3C3663C181818181818':'Y','FE66C183060C0C0FE':'Z','0003E6337FC3C77B':'a','C0C0C0DCE6C3C3C3E6DC':'b', '0003E63C0C0C0633E':'c','3333B67C3C3C3673B':'d','0003C66C3FFC0633E':'e','1E33333030FC30303030':'f', '0007DC7C6C67CC07E':'g','C0C0C0DCE6C3C3C3C3C3':'h','181803818181818187E':'i','660E66666C6':'j', '606060666C78786C6663':'k','3818181818181818183C':'l','000B6DBDBDBDBDBDB':'m','000DCE6C3C3C3C3C3':'n', '0003C66C3C3C3663C':'o','000DCE6C3C3C3E6DC':'p','0003B67C3C3C3673B':'q','000DE736060606060':'r', '0007EC3C07E3C37E':'s','03030FC30303030331E':'t','000C3C3C3C3C3673B':'u','000C3C366663C3C18':'v', '000C3C3DBDBDBFF66':'w','000C3663C183C66C3':'x','000C3C3C3C3C3673B':'y','0007E6C1830607E':'z', '183C66C3C3C3C3663C18':'0','1838781818181818187E':'1','3C66C336C183060FF':'2','7CC6361C633C67C':'3', '6E1E3666C6FF666':'4','FEC0C0DCE633C3663C':'5','3C66C2C0DCE6C3C3663C':'6','FF336C183060C0C0':'7', '3C66C3663C66C3C3663C':'8','3C66C3C3673B343663C':'9','0000000000':' '}; //      10  . 2 + 10*9 = 92 < 100 const maxchar = (Width - 2) / wchar >> 0; var password = ''; for (var charX = 2; charX < maxchar * wchar + 2; charX+=wchar) { //    var hash = ''; //   for (var charY = 3; charY < hchar; charY++) { //   Y- var charrow = 0; //   -  8   for (var charXbit = 0; charXbit < 8; charXbit++) { //   X- charrow <<= 1; charrow |= getXY(charX + charXbit, charY); } hash += charrow.toString(16).toUpperCase(); //Logger.log("charrow: " + charrow.toString(2)); //Logger.log("charrow: " + charrow.toString(16).toUpperCase()); } var tempChar = hashDict[hash]; if (tempChar === 'u' || tempChar === 'y') { //     tempChar = (Date.now() % 2) ? 'u': 'y'; } if (tempChar !== ' ') { password += tempChar; // Logger.log("hash: " + hash); // Logger.log("Char: " + tempChar); } } Logger.log("password: " + password); return ContentService.createTextOutput(password); } function blobToUint8(blob) { return blob.map(function(e){ return e & 0xff; }); } function bytesToUint32(byteArray, start) { var value = 0; for (var i = start; i < start + 4; i++) { value = (value * 256) + (byteArray[i] & 0xff); } return value; } function my2() { var file = DriveApp.getFilesByName("password2.png").next(); // var file = DriveApp.getFilesByName("test.bin").next(); var image = file.getBlob(); //var imageArray = image.getBytes(); //var img = UrlFetchApp.fetch('http://example.com/image.png'); var reader = new pngjs.PNGReader(image.getBytes()); var png = reader.parse(function(err, png){ if (err) throw err; return png; }); Logger.log(png); } 000B6DBDBDBDBDBDB ':' m '' 000DCE6C3C3C3C3C3 ':' n '' 0003C66C3C3C3663C ':' o '' 000DCE6C3C3C3E6DC ':' p '' 0003B67C3C3C3673B ':' q '' 000DE736060606060 ' function doGet() { //var file = DriveApp.getFilesByName("password2.png").next(); //var image = file.getBlob(); var image = UrlFetchApp.fetch('https://www.vpnbook.com/password.php').getBlob(); var imageString = image.getDataAsString(); var imageArray = image.getBytes().map(function(e) { return e & 0xff; }); // imageArray = blobToUint8(imageArray); var chunkIDATStart = imageString.indexOf("IDAT") + 4; //   IDAT var chunkIDATLen = bytesToUint32(imageArray, imageString.indexOf("IDAT") - 4); //   IDAT var IDATArray = imageArray.slice(chunkIDATStart, chunkIDATStart + chunkIDATLen) var inflate = new zlibjs.Inflate(IDATArray); //  IDAT   zlib var plain = inflate.decompress(); const Width = 100; //   const Height = 13; //   const wchar = 9; //   const hchar = 13; //   //Logger.log(typeof(plain)); //Logger.log(plain); rowlen = (Width / 8) >> 0; //   ,    if ((Width - rowlen * 8) > 0) { //    rowlen+=2; // +1 filter byte at the beginning of each row.    PNG } else { rowlen++; } function getXY(x, y) { //  : 0/1 var xbyte = (x / 8 >> 0); //  ,   //Logger.log("xbyte: " + xbyte); var xbit = x - xbyte * 8; //  ,    //Logger.log("xbit: " + xbit); return (plain[xbyte + 1 + y * rowlen] << xbit & 0x80) >> 7; // +1 filter byte at the beginning of each row } // Logger.log("getXY: " + getXY(4, 3)); // - .        8  (  ) x 10 ,        (byte),      10  . //  10 hex   . var hashDict = {'183C66C3C3C3FFC3C3C3':'A','FCC6C3C6FCC6C3C3C6FC':'B','3E63C1C0C0C0C0C1633E':'C','FCC6C3C3C3C3C3C3C6FC':'D', 'FEC0C0C0FCC0C0C0C0FE':'E','FFC0C0C0FCC0C0C0C0C0':'F','3E63C0C0C0C7C3C3633E':'G','C3C3C3C3FFC3C3C3C3C3':'H', '7E18181818181818187E':'I','1E666666466C38':'J','C3C6CCD8F0F0D8CCC6C3':'K','C0C0C0C0C0C0C0C0C0FE':'L', 'C3E7FFDBDBDBC3C3C3C3':'M','C3E3F3F3DBDBCFC7C7C3':'N','3C66C3C3C3C3C3C3663C':'O','FEC3C3C3FEC0C0C0C0C0':'P', '3C66C3C3C3C3DBCF663D':'Q','FEC3C3C3FEF8CCC6C3C3':'R','7EC3C0C07E333C37E':'S','FF181818181818181818':'T', 'C3C3C3C3C3C3C3C3663C':'U','C3C3C36666663C3C1818':'V','C3C3C3C3DBDBDBFFE7C3':'W','C3C3663C18183C66C3C3':'X', 'C3C3663C181818181818':'Y','FE66C183060C0C0FE':'Z','0003E6337FC3C77B':'a','C0C0C0DCE6C3C3C3E6DC':'b', '0003E63C0C0C0633E':'c','3333B67C3C3C3673B':'d','0003C66C3FFC0633E':'e','1E33333030FC30303030':'f', '0007DC7C6C67CC07E':'g','C0C0C0DCE6C3C3C3C3C3':'h','181803818181818187E':'i','660E66666C6':'j', '606060666C78786C6663':'k','3818181818181818183C':'l','000B6DBDBDBDBDBDB':'m','000DCE6C3C3C3C3C3':'n', '0003C66C3C3C3663C':'o','000DCE6C3C3C3E6DC':'p','0003B67C3C3C3673B':'q','000DE736060606060':'r', '0007EC3C07E3C37E':'s','03030FC30303030331E':'t','000C3C3C3C3C3673B':'u','000C3C366663C3C18':'v', '000C3C3DBDBDBFF66':'w','000C3663C183C66C3':'x','000C3C3C3C3C3673B':'y','0007E6C1830607E':'z', '183C66C3C3C3C3663C18':'0','1838781818181818187E':'1','3C66C336C183060FF':'2','7CC6361C633C67C':'3', '6E1E3666C6FF666':'4','FEC0C0DCE633C3663C':'5','3C66C2C0DCE6C3C3663C':'6','FF336C183060C0C0':'7', '3C66C3663C66C3C3663C':'8','3C66C3C3673B343663C':'9','0000000000':' '}; //      10  . 2 + 10*9 = 92 < 100 const maxchar = (Width - 2) / wchar >> 0; var password = ''; for (var charX = 2; charX < maxchar * wchar + 2; charX+=wchar) { //    var hash = ''; //   for (var charY = 3; charY < hchar; charY++) { //   Y- var charrow = 0; //   -  8   for (var charXbit = 0; charXbit < 8; charXbit++) { //   X- charrow <<= 1; charrow |= getXY(charX + charXbit, charY); } hash += charrow.toString(16).toUpperCase(); //Logger.log("charrow: " + charrow.toString(2)); //Logger.log("charrow: " + charrow.toString(16).toUpperCase()); } var tempChar = hashDict[hash]; if (tempChar === 'u' || tempChar === 'y') { //     tempChar = (Date.now() % 2) ? 'u': 'y'; } if (tempChar !== ' ') { password += tempChar; // Logger.log("hash: " + hash); // Logger.log("Char: " + tempChar); } } Logger.log("password: " + password); return ContentService.createTextOutput(password); } function blobToUint8(blob) { return blob.map(function(e){ return e & 0xff; }); } function bytesToUint32(byteArray, start) { var value = 0; for (var i = start; i < start + 4; i++) { value = (value * 256) + (byteArray[i] & 0xff); } return value; } function my2() { var file = DriveApp.getFilesByName("password2.png").next(); // var file = DriveApp.getFilesByName("test.bin").next(); var image = file.getBlob(); //var imageArray = image.getBytes(); //var img = UrlFetchApp.fetch('http://example.com/image.png'); var reader = new pngjs.PNGReader(image.getBytes()); var png = reader.parse(function(err, png){ if (err) throw err; return png; }); Logger.log(png); } 


O script implementa apenas o método GET. Ao executar uma solicitação GET para esse script, publicada como Web App, a resposta conterá imediatamente a senha decodificada na forma de uma sequência.

Parte 3. Mikrotik e Movido Temporariamente 302


Portanto, temos um script que é executado em servidores externos de aplicativos da Web, independentes dos bloqueios e retornando uma senha em texto sem formatação. E parece que não há nada mais fácil do que solicitá-lo com o comando buscar no RouterOS Mikrotik. Mas então outra surpresa me esperava. Em resposta à solicitação (endereços reais alterados), a busca retorna "302 movido temporariamente".

 [admin@MikroTik] /environment> :put ([/tool fetch url="https://script.google.com/macros/s/A.....A/exec" http-method=get output=user as-value]->"data") failure: closing connection: <302 Moved Temporarily "https://script.googleusercontent.com/macros/echo?user_content_key=....."> 173.194.222.138:443 (4) [admin@MikroTik] /environment> 

No começo do artigo, eu já escrevi sobre isso. Ao acessar o URL conhecido persistente do script do Web App, o Google redireciona para um URL temporário, que por sua vez retorna uma resposta à solicitação. Mas, diferentemente do PHP cURL, o Buster RouterOS não sabe como passar por redirecionamentos, mas retorna uma falha. Mas forum.mikrotik.com não o fez imediatamente, mas houve uma solução alternativa. Você pode redirecionar a saída de busca padrão do console para um arquivo chamando a execução assíncrona em uma tarefa separada, envolvendo: execute. Você pode recuperar o URL de redirecionamento e buscar novamente já com o novo endereço. O que é feito abaixo.

 #  . Moved Temporarily 302.  fetch  gasfetchout.txt :local jobid [:execute script={/tool fetch url="https://script.google.com/macros/s/A.....A/exec" output=user as-value} file=gasfetchout.txt] #    ,     :while ([:len [/system script job find .id=$jobid ]] > 0) do={ delay 1s } #  gasfetchout.txt,  URL  :local fetchOut [/file get gasfetchout.txt contents] :local startURL [:find $fetchOut "http" -1] :local endURL [:find $fetchOut "\"> " startURL] :local moveURL [:pick $fetchOut $startURL $endURL] :global VPNBookPass2 ([/tool fetch url=$moveURL output=user as-value]->"data") 

Aqui está o texto completo do script Mikrotik para trabalhar com o GAS Web App
 # VPNBookScript v4 :local VPNBookpIfName "pptp-out1" :local VPNBookServerAddresses {"PL226.vpnbook.com";"de4.vpnbook.com";"us1.vpnbook.com";"us2.vpnbook.com";"fr1.vpnbook.com ";"fr8.vpnbook.com ";"ca222.vpnbook.com ";"ca198.vpnbook.com"} :local VPNBookErr false :global VPNBookPass :global VPNBookRun :global VPNBookServerIndex :if ([:typeof $VPNBookServerIndex] != "num") do={:set VPNBookServerIndex 0} :if ([/interface pptp-client get $VPNBookpIfName running]) do={ :set VPNBookRun true } else { :if (!$VPNBookRun) do={ :set VPNBookServerIndex ($VPNBookServerIndex + 1) :if ($VPNBookServerIndex>=[:len $VPNBookServerAddresses]) do={:set VPNBookServerIndex 0} } else { :set VPNBookRun false } :if (![/interface pptp-client get $VPNBookpIfName disabled]) do={/interface pptp-client set $VPNBookpIfName disabled=yes} # :do {:set VPNBookPass ([/tool fetch url="http://serv/vpnbookpass_googlescript.php" output=user as-value]->"data")} on-error={:set VPNBookErr true} :do { # First request with Moved Temporarily. Fetch out to gasfetchout.txt :local jobid [:execute script={/tool fetch url="https://script.google.com/macros/s/A.....g/exec" output=user as-value} file=gasfetchout.txt] # Wait end job :while ([:len [/system script job find .id=$jobid ]] > 0) do={ delay 1s } # parse new URL for second fetch :local fetchOut [/file get gasfetchout.txt contents] :local startURL [:find $fetchOut "http" -1] :local endURL [:find $fetchOut "\"> " startURL] :local moveURL [:pick $fetchOut $startURL $endURL] :set VPNBookPass ([/tool fetch url=$moveURL output=user as-value]->"data") } on-error={:set VPNBookErr true} :if (!$VPNBookErr) do={ :if ([/interface pptp-client get $VPNBookpIfName password] != $VPNBookPass) do={/interface pptp-client set $VPNBookpIfName password=$VPNBookPass} :if ([/interface pptp-client get $VPNBookpIfName connect-to] != $VPNBookServerAddresses->$VPNBookServerIndex) do={/interface pptp-client set $VPNBookpIfName connect-to=($VPNBookServerAddresses->$VPNBookServerIndex)} :log info "VPNBook: Attempt to connect to: $($VPNBookServerAddresses->$VPNBookServerIndex). Password: $VPNBookPass" /interface pptp-client set $VPNBookpIfName disabled=no } } 


Parte 4. Proxy de Telegrama GAS


Decidi dedicar esta parte à próxima iteração de integração do serviço Telegram no Mikrotik. Usar o GAS aqui seria de interesse puramente acadêmico se não fosse a realidade de bloquear o serviço Telegram, incluindo o api.telegram.org, por meio do qual os robôs trabalham com o serviço. A ideia repete a ideia no início do artigo sobre solicitação de proxy para imagens PNG.
Nesse caso, o GAS Web App é gravado em solicitações de proxy do Mikrtotik para api.telegram.org. Como base, peguei um script pronto de manzoorwanijk, WPTelegram Google Script gist.github.com/manzoorwanijk/ee9ed032caedf2bb0c83dea73bc9a28e . Esse script pode usar como proxy muitos métodos da API do Telegram (mas não todos). Em args, você pode transmitir um objeto JSON que contém os parâmetros de solicitação, por exemplo {"chat_id":"123","text":"HelloWorld"} . Mas para a minha tarefa de enviar mensagens de texto do RouterOS Mikrtotik, a implementação parecia complicada e eu a simplifiquei. Por fim, você geralmente pode escrever vários scripts do Web App para fazer proxy de vários métodos da API do Telegram. Aqui está minha implementação para o método sendMessage. Isso pode ser simplificado ainda mais, incorporando o nome do método sendMessage que está sendo chamado e até bot_token e chat_id no corpo da função requestHandler.

 function doGet(e) { if(typeof e !== 'undefined'){ return ContentService.createTextOutput(requestHandler(e)); } } function doPost(e) { if(typeof e !== 'undefined'){ return ContentService.createTextOutput(requestHandler(e)); } } function requestHandler(e){ if (typeof e.parameter.bot_token === 'undefined'){ return 'Error! Bot token not provided'; } else if (typeof e.parameter.method === 'undefined') { return 'Error! Method name not provided'; } else if (typeof e.parameter.chat_id === 'undefined') { return 'Error! Chat id not provide'; } else if (typeof e.parameter.text === 'undefined') { return 'Error! Text not provide'; } /* if(typeof e.parameter.args !== 'undefined'){ var args = e.parameter.args; data.payload = JSON.parse(args); } */ if (e.parameter.method === 'sendMessage') { var data = { "method": "post", "muteHttpExceptions": true, payload : 'chat_id=' + e.parameter.chat_id + '&text=' + e.parameter.text } return UrlFetchApp.fetch('https://api.telegram.org/bot' + e.parameter.bot_token + '/' + e.parameter.method, data).getContentText(); } } 

Depois de publicar o script no Web App, você pode executar uma solicitação no navegador GET para verificar:

 https://script.google.com/macros/s/A.....A/exec?bot_token=3.....3&method=sendMessage&chat_id=2.....3&text=testtext123 

Ou na solicitação do RouterOS POST:

 :do { /tool fetch url=("https://script.google.com/macros/s/A.....A/exec") keep-result=no http-method=post http-data=("bot_token=3.....3&method=sendMessage&chat_id=2.....3&text=testtext123") } on-error={ } 

A solicitação é agrupada em do-on-error, porque, como mostrado acima, a primeira chamada a buscar lançará uma exceção “Movido Temporariamente 302” e o script sem o manipulador de erro irá parar neste momento. Uma chamada para buscar sem encaminhamento é suficiente para que a mensagem seja enviada; portanto, uma segunda chamada para buscar não será necessária se você não precisar do objeto JSON retornado pela API do Telegram.

Parte 5. Final


Eu trouxe meus aplicativos reais na junção do Script do Google Apps com outros serviços. Você pode criar muito mais. Por exemplo, escreva um bot de Telegram no GAS, que responderá com uma senha VPNBook com solicitações de cache para reduzir a carga no VPNBook (Serviço de Cache), e tudo isso estará em um script GAS. Você pode escrever no GAS um sistema de registro ou configurações de backup para o Mikrtotik, que serão colocados nos arquivos do Google Docs e do Google Sheets e muito mais.

Source: https://habr.com/ru/post/pt475856/


All Articles