Pular para o conteúdo principal

Postagem em destaque

BlackTDN :: 🚀 **Depurando Blocos de Código (xBase)** 🚀

🚀 **Depurando Blocos de Código (xBase)** 🚀 Interessante a abordagem de **Blocos de Código** no **Harbour**! Eles funcionam literalmente como "cidadãos de primeira classe", permitindo até mesmo depuração passo a passo. 🔍 **Exemplo Prático:** ```xBase Eval( {|aFunTst as array| LOCAL lValid AS LOGICAL LOCAL i AS NUMERIC FOR i := 1 TO Len(aFunTst) // Verifica resultado esperado lValid := aFunTst[i][3] IF lValid SetColor("g+/n") QOut("(" + aFunTst[i][2] + "): passed") SetColor("") ELSE SetColor("r+/n") QOut("(" + aFunTst[i][2] + "): failed") SetColor("") ENDIF NEXT i RETURN NIL }, aFunTst ) ``` 🤔 **Pergunta aos escovadores de bit de plantão:** É pos...

BlackTDN :: Autenticação OAuth2 em Harbour para Publicação no Blogger

_Créditos das imagens: ChatGPT

# Autenticação OAuth2 em Harbour para Publicação no Blogger

**Publicado em:** 11 de fevereiro de 2025

## Introdução

No universo das aplicações web modernas, o protocolo OAuth2 se tornou o padrão para a autenticação e autorização de acesso a APIs de terceiros, como as do Google. Neste post, vamos explorar como usar Harbour — uma linguagem derivada do Clipper — para implementar um fluxo de autenticação OAuth2. O objetivo é automatizar a publicação de conteúdos em um blog no Blogger, integrando a obtenção de notícias, a conversão para Markdown/HTML e, finalmente, a postagem no blog.

## Visão Geral do Código

O exemplo de código Harbour realiza os seguintes passos:

1. **Obtenção de Notícias:** Utiliza a API de um serviço de notícias (através do TIPClientHTTP) para buscar conteúdos relacionados à tecnologia.
2. **Processamento do Conteúdo:** Converte o JSON retornado em um texto formatado em Markdown e, em seguida, converte esse Markdown para HTML — o formato exigido pelo Blogger.
3. **Fluxo OAuth2 para Autenticação:** Implementa o fluxo de autorização do OAuth2 para obter um token de acesso. Esse token é necessário para autorizar a requisição de publicação no Blogger.
4. **Publicação no Blogger:** Com o token em mãos, o código envia uma requisição POST à API do Blogger, publicando o conteúdo processado.

## Configuração do Ambiente e Bibliotecas

O código inicia com definições e inclusão de bibliotecas essenciais, como:

- **`hbssl` e `hbtip`:** Para lidar com conexões seguras e requisições HTTP.
- **Configuração de CodePage e otimizações:** As diretivas `#pragma` ajudam a manter o código otimizado e compatível com diferentes ambientes.

Além disso, variáveis estáticas são definidas para armazenar as credenciais e URLs necessárias, incluindo as chaves do serviço de notícias, ID do blog no Blogger e os parâmetros do OAuth2 (como `client_id`, `client_secret`, `redirect_uri`, entre outros).

## Fluxo de Autenticação OAuth2

### 1. Iniciando o Fluxo OAuth2

A função `GetOAuth2Token()` é o ponto de partida para a obtenção do token de acesso. Essa função realiza as seguintes ações:

- **Inicialização do Servidor HTTP Local:** Utiliza o UHttpd para iniciar um servidor na porta 8002, que ficará responsável por gerenciar as rotas necessárias para o fluxo OAuth2.
- **Abertura do Navegador:** A função `LaunchCommand()` é chamada para abrir o navegador (neste caso, o Microsoft Edge) e exibir uma página HTML que direciona o usuário para a autenticação.

### 2. Redirecionamento para Autenticação

A função `AuthHandler()` monta os parâmetros necessários para a requisição do OAuth2. Entre os parâmetros, destacam-se:

- **`redirect_uri`:** Onde o usuário será redirecionado após a autenticação.
- **`response_type`:** Geralmente definido como `"code"`, pois estamos utilizando o Authorization Code Flow.
- **`client_id` e `scope`:** Informações que identificam a aplicação e os recursos solicitados (neste exemplo, acesso à API do Blogger).

Após montar os parâmetros, a função redireciona o navegador para a URL de autorização do Google, onde o usuário poderá consentir com o acesso.

### 3. Recebendo o Código de Autorização

Quando o usuário autoriza o acesso, o Google redireciona o navegador para a rota definida (por exemplo, `/oauth2callback`). A função `CallbackHandler()` captura o código de autorização (`code`) enviado como parâmetro na URL.

### 4. Troca do Código pelo Token de Acesso

Com o código de autorização em mãos, a função `ExchangeCodeForToken()` realiza uma requisição POST para a URL de token do Google. Os parâmetros enviados incluem:

- **`code`:** O código de autorização recebido.
- **`client_id` e `client_secret`:** Credenciais da aplicação.
- **`redirect_uri`:** Confirmação da URL de redirecionamento.
- **`grant_type`:** Definido como `"authorization_code"`.

Se a requisição for bem-sucedida, o JSON de resposta conterá o `access_token`, que é então armazenado e utilizado para autorizar a publicação no Blogger.

## Publicando o Post no Blogger

Após obter o token de acesso, a função `PublishToBlogger()` monta uma requisição HTTP para a API do Blogger. Os passos incluem:

1. **Montagem da URL:** A URL é construída utilizando o ID do blog.
2. **Criação do Corpo da Requisição:** Um objeto JSON é formado contendo os dados do post, como título e conteúdo (já convertido para HTML).
3. **Configuração dos Cabeçalhos:** Inclui o cabeçalho de autorização com o token no formato `Bearer `.
4. **Envio da Requisição:** Se a requisição POST for bem-sucedida, o post é publicado no blog.

## Considerações Finais

Este exemplo demonstra como é possível integrar diversas tecnologias — requisições HTTP, manipulação de JSON, conversão de formatos e autenticação OAuth2 — em uma única aplicação desenvolvida em Harbour. A utilização do OAuth2 garante que as credenciais sejam tratadas de maneira segura, permitindo acesso controlado à API do Blogger.

Além de servir como base para automação de postagens, o código pode ser adaptado para outras aplicações que exigem acesso seguro a serviços de terceiros, ampliando o leque de possibilidades para desenvolvedores que trabalham com Harbour.

---

Esperamos que este tutorial tenha esclarecido como implementar o fluxo OAuth2 via Harbour para a publicação de posts no Blogger. Se você tiver dúvidas ou sugestões, deixe seu comentário abaixo!

---

Esta postagem foi feita para inspirar desenvolvedores que desejam explorar integrações modernas utilizando Harbour e protocolos de autenticação seguros. Acompanhe nosso blog para mais conteúdos sobre tecnologias, programação e inovação.

---

### Utilizei o exemplo abaixo para as últimas publicações do "BlackTDN News" no Blogger.

### Código: [hb_blogger_post.prg](https://github.com/naldodj/naldodj-hb_blogger_post/blob/main/src/hb_blogger_post.prg)
```
/* Keeping it tidy */
#pragma -w3
#pragma -es2

/* Optimizations */
#pragma -km+
#pragma -ko+

#require "hbssl"
#require "hbtip"

REQUEST HB_CODEPAGE_UTF8EX

#if ! defined( __HBSCRIPT__HBSHELL )
    REQUEST __HBEXTERN__HBSSL__
#endif

#include "tip.ch"
#include "inkey.ch"
#include "fileio.ch"
#include "hbclass.ch"

// Defina suas credenciais e chaves (substitua pelos seus valores)
static cNewsApiKey as character // API key para o serviço de notícias
static cBloggerID as character // ID do seu blog no Blogger
static cBloggerAccessToken as character // Blogger API Key

// Variáveis globais para OAuth2
static cClientID as character
static cClientSecret as character
static cRedirectURI as character
static cAuthURL as character
static cTokenURL as character
static cScope as character

static cEOL as character

MEMVAR server, get, post, cookie, session

//---------------------------------------------------------------------
// Função: Main()
// Executa o fluxo: busca notícias -> gera Markdown -> converte para HTML -> publica no Blogger
//---------------------------------------------------------------------
procedure Main()

    local cHtml as character
    local cTitle as character
    local cMarkdown as character
    local cNewsJSON as character

    local lSuccess as logical:=.F.

    cEOL:=hb_eol()
    hb_setCodePage("UTF8")
    hb_cdpSelect("UTF8EX")

    SET DATE ANSI
    SET CENTURY ON

    #ifdef __ALT_D__    // Compile with -b
        AltD(1)         // Enables the debugger. Press F5 to go.
        AltD()          // Invokes the debugger
    #endif

    begin sequence

        cNewsApiKey:=GetEnv("NEWS_API_KEY") // API key para o serviço de notícias
        // 1. Buscar notícias de tecnologia
        cNewsJSON:=GetNews()

        // 2. Processar JSON e gerar Markdown
        cMarkdown:=JSONToMarkdown(cNewsJSON)

        // 3. Converter Markdown para HTML (Blogger requer HTML)
        if (!Empty(cMarkdown))
            cHtml:=ConvertMarkdownToHtml(cMarkdown)
            // 4. Publicar o post no Blogger
            cTitle:="BlackTDN NEWS :: "+DToC(Date())+" :: "+Time()
            lSuccess:=PublishToBlogger(cTitle,cHtml)
        endif

        if (lSuccess)
          ? "Post publicado com sucesso!"
        else
          ? "Falha ao publicar o post."
        endif

    end sequence

    return

//---------------------------------------------------------------------
// Função: GetNews()
// Usa TIPClientHTTP para efetuar uma requisição GET à API de notícias.
//---------------------------------------------------------------------
static function GetNews()

    local cURL as character
    local cResponse as character

    local oURL as object
    local oHTTP as object

    cURL:="https://newsapi.org/v2/everything/"

    //FixMe!
    cURL:=strTran(cURL,"https","http")

    // Cria objeto TUrl (construtor definido na TIP – ver tipwget.prg)
    oURL:=TUrl():New(cURL)
    oHTTP:=TIPClientHTTP():New(oURL)
    oHTTP:hFields["Content-Type"]:="application/JSON"
    oHTTP:hFields["X-Api-Key"]:=cNewsApiKey

    /* build the search query and add it to the TUrl object */
    oHTTP:oURL:addGetForm(;
        {;
            "q" => "tecnologia";
           ,"sortBy" => "popularity";
           ,"language" => "pt";
           ,"from" => hb_DToC(Date(),"yyyy-mm-dd");
           ,"to" => hb_DToC(Date(),"yyyy-mm-dd");
           ,"apiKey" => cNewsApiKey;
        };
    )

    /* Connect to the HTTP server */
    oHTTP:nConnTimeout:=2000 /* 20000 */

    ? "Connecting to",oURL:cProto+"://"+oURL:cServer+oURL:cPath+"?"+oURL:cQuery

    if (oHTTP:Open())
        ? "Connection status:",iif(Empty(oHTTP:cReply),"",oHTTP:cReply)
        /* download the response */
        cResponse:=oHTTP:ReadAll()
        if (Empty(cResponse))
            ? oHTTP:LastErrorMessage(oHTTP:SocketCon)
        endif
        oHTTP:Close()
    else
         ? oHTTP:LastErrorMessage(oHTTP:SocketCon)
    endif

    hb_default(@cResponse,"")

    return(cResponse) as character

//---------------------------------------------------------------------
// Função: JSONToMarkdown(cJSON)
// Gera um texto em Markdown formatado com título, descrição e link para cada notícia.
//---------------------------------------------------------------------
static function JSONToMarkdown(cJSON as character)

    local aArticles as array

    local cMarkdown as character := ""

    local hJSON as hash

    local hArticle as hash

    local i as numeric

    hb_MemoWrit("C:\tmp\news.json",cJSON)

    hb_JSONDecode(cJSON,@hJSON)

    begin sequence

        if (!hb_HHasKey(hJSON,"status"))
            break
        endif

        if (hJSON["status"]!="ok")
            break
        endif

        if (hb_HHasKey(hJSON,"totalResults"))
            if (hJSON["totalResults"]==0)
                break
            endif
        endif

        if (!hb_HHasKey(hJSON,"articles"))
            break
        endif

        aArticles:=hJSON["articles"]

        for i:=1 to Len(aArticles)

            hArticle:=aArticles[i]

            // Título da notícia
            cMarkdown+="# "+if(!Empty(hArticle["title"]),hArticle["title"],hArticle["description"])+cEOL

            // Fonte e autor
            cMarkdown+="**Fonte:** "+hArticle["source"]["name"]+cEOL
            if (!Empty(hArticle["author"]))
                cMarkdown+="**Autor:** "+hArticle["author"]+cEOL
            endif

            // Data de publicação
            cMarkdown+="**Publicado em:** "+hArticle["publishedAt"]+cEOL+cEOL

            // Imagem (se disponível)
            if (!Empty(hArticle["urlToImage"]))
                cMarkdown+="![Imagem]("+hArticle["urlToImage"]+")"+cEOL+cEOL
            endif

            // Descrição (citação)
            if (!Empty(hArticle["description"]))
                cMarkdown+="> "+hArticle["description"]+cEOL+cEOL
            endif

            // Link para a notícia completa
            cMarkdown+="[Leia mais]("+hArticle["url"]+")"+cEOL+cEOL

            // Separador entre notícias
            cMarkdown+="---"+cEOL+cEOL

        next i

    end sequence

    return(cMarkdown) as character

//---------------------------------------------------------------------
// Função: ConvertMarkdownToHtml( cMarkdown )
//---------------------------------------------------------------------
static function ConvertMarkdownToHtml(cMarkdown as character)
   local cNewMarkDown as character
#pragma __cstream|cNewMarkDown:=%s
#pragma __endtext cNewMarkDown+=cEOL cNewMarkDown+='
_Créditos das imagens: ChatGPT'+cEOL+cMarkdown+cEOL+'
' return(cNewMarkDown) as character //--------------------------------------------------------------------- // Função: PublishToBlogger( cTitle, cContent ) // Publica o post no Blogger via API REST usando TIPClientHTTP. //--------------------------------------------------------------------- static function PublishToBlogger(cTitle as character,cContent as character) local cURL as character local cAccessToken as character local cJSONData as character local cResponse as character local hJSONData as hash local oURL as object local oHTTP as object cBloggerID:=GetEnv("BLOGGER_ID") // ID do seu blog no Blogger cBloggerAccessToken:=GetEnv("BLOGGER_API_KEY") // Blogger API Key // Variáveis globais para OAuth2 cClientID:=GetEnv("GOOGLE_CLIENT_ID") cClientSecret:=GetEnv("GOOGLE_CLIENT_SECRET") cRedirectURI:="http://localhost:8002/oauth2callback" cAuthURL:="https://accounts.google.com/o/oauth2/v2/auth" cTokenURL:="https://accounts.google.com/o/oauth2/token" cScope:="https://www.googleapis.com/auth/blogger" cAccessToken:=GetOAuth2Token() // Obter token via OAuth2 if (!Empty(cAccessToken)) // Constrói a URL do endpoint de inserção de posts do Blogger cURL:="https://www.googleapis.com/blogger/v3/blogs/"+cBloggerID+"/posts/" // Prepara o corpo JSON da requisição conforme a documentação do Blogger API v3 hJSONData:={; "kind" => "blogger#post"; ,"blog" => {; "id" => cBloggerID; }; ,"title" => cTitle; ,"content" => cContent; } // Cria o objeto TIPClientHTTP para a URL do Blogger oURL:=TUrl():New(cURL) oHTTP:=TIPClientHTTP():New(oURL) // Define os cabeçalhos adicionais (em hFields – conforme a implementação TIP) oHTTP:hFields["Authorization"]:="Bearer "+cAccessToken oHTTP:hFields["Content-Type"]:="application/JSON" // Efetua a requisição POST com o corpo JSON cJSONData:=hb_JSONEncode(hJSONData) if (oHTTP:Open()) if (oHTTP:Post(cJSONData)) ? cResponse:=oHTTP:cReply ? hb_ValToExp(oHTTP:hHeaders) else ? "Error:", "oHTTP:Post()", oHTTP:LastErrorMessage() endif oHTTP:Close() else ? "Error:", "oHTTP:Open()", oHTTP:LastErrorMessage() endif endif return(!Empty(cResponse)) as logical //--------------------------------------------------------------------- // Função: GetOAuth2Token() // Obtém um token de acesso via OAuth2 (Authorization Code Flow) //--------------------------------------------------------------------- static function GetOAuth2Token() local cAccessToken as character local lHTTPServer as logical local oLogError,oLogAccess as object local oHTTPServer as object ? "http://localhost:8002/" hb_idleSleep(.1) oLogError:=UHttpdLog():New("hb_blogger_post_error.log") oLogAccess:=UHttpdLog():New("hb_blogger_post_access.log") if (hb_FileExists(".uhttpd.stop")) fErase(".uhttpd.stop") endif // Configurar rotas oHTTPServer:=UHttpdNew() lHTTPServer:=oHTTPServer:Run(; {; "FirewallFilter" => ""; ,"LogAccess" => {| m | oLogAccess:Add( m+cEOL ) }; ,"LogError" => {| m | oLogError:Add( m+cEOL ) }; ,"Trace" => {| ... | QOut( ... ) }; ,"Port" => 8002; ,"Idle" => {|o|iif(hb_FileExists(".uhttpd.stop"),(fErase(".uhttpd.stop"),o:Stop()),NIL)}; ,"SSL" => .F.; ,"Mount" => {; "/info" => {||UProcInfo()}; ,"/auth" => @AuthHandler();// Inicia o fluxo OAuth2 ,"/oauth2callback" => @CallbackHandler();// Recebe o código de autorização ,"/" => {||URedirect("/auth")}; }; }; ) oLogError:Close() oLogAccess:Close() if (lHTTPServer) if (file("hb_blogger_post.token")) cAccessToken:=hb_MemoRead("hb_blogger_post.token") fErase("hb_blogger_post.token") else hb_default(@cAccessToken,"") endif else hb_default(@cAccessToken,"") endif return(cAccessToken) static function AuthHandler() local cParams as character:="" local cKey,cValue as character local hkey as hash local hkeys as hash := {; "redirect_uri" => cRedirectURI; ,"prompt" => "consent"; ,"response_type" => "code"; ,"client_id" => cClientID; ,"scope" => cScope; } for each hkey in hkeys cKey:=hKey:__enumKey() cValue:=hKey:__enumValue() cParams+=tip_URLEncode(AllTrim(hb_CStr(cKey)))+"="+tip_URLEncode(AllTrim(cValue)) if (!hkey:__enumIsLast()) cParams+="&" endif next each if (Right(cParams,1)=="&") cParams:=SubStr(cParams,1,Len(cParams)-1) endif USessionStart() URedirect(cAuthURL+"?"+cParams) // Redireciona o navegador return(.T.) static function CallbackHandler() local cAuthCode,cAccessToken as character if (hb_HHasKey(get,"code")) cAuthCode:=get["code"] cAccessToken:=ExchangeCodeForToken(cAuthCode) hb_MemoWrit("hb_blogger_post.token",cAccessToken) endif hb_MemoWrit(".uhttpd.stop","") return({"AccessToken"=>cAccessToken}) static function ExchangeCodeForToken(cAuthCode as character) local cTokenResponse, cAccessToken local hkeys,hJSONResponse as hash local oURL as object local oHTTP as object // 2. Trocar código por token de acesso hkeys:={; "code" => cAuthCode,; "redirect_uri" => cRedirectURI,; "client_id" => cClientID,; "client_secret" => cClientSecret,; "grant_type" => "authorization_code"; } // Cria o objeto TIPClientHTTP para a URL do Blogger oURL:=TUrl():New(cTokenURL) oHTTP:=TIPClientHTTP():New(oURL) oHTTP:hFields["Content-Type"]:="application/x-www-form-urlencoded" if (oHTTP:Open()) if (oHTTP:Post(hkeys)) cTokenResponse:=oHTTP:ReadAll() QOut( "cTokenResponse", cTokenResponse ) hb_JSONDecode(cTokenResponse,@hJSONResponse) if (hb_HHasKey(hJSONResponse,"access_token")) cAccessToken:=hJSONResponse["access_token"] QOut( "cAccessToken", cAccessToken ) hb_MemoWrit("hb_blogger_post.token",cAccessToken) hb_MemoWrit(".uhttpd.stop","") endif ? hb_ValToExp(oHTTP:hHeaders) else QOut( "Error:", "oHTTP:Post()", oHTTP:LastErrorMessage() ) ? "Error:", "oHTTP:Post()", oHTTP:LastErrorMessage() endif oHTTP:Close() else ? "Error:", "oHTTP:Open()", oHTTP:LastErrorMessage() QOut( "Error:", "oHTTP:Open()", oHTTP:LastErrorMessage() ) endif return(cAccessToken) //--------------------------------------------------------------------- // Fim do programa //--------------------------------------------------------------------- ``` --- ### Dependências: hbmk.hbm ``` -w3 -es2 xhb.hbc hbct.hbc hbtip.hbc hbssl.hbc hbhttpd.hbc ``` --- ### HashTags: #HarbourLang, #OAuth2, #APIBlogger, #Automação, #Programação, #Desenvolvimento, #AutenticaçãoSegura, #APIs, #JSON #CódigoAberto

Comentários

Postagens mais visitadas