Mala direta por meio dos serviços do Google

Andrei Novichkov | 14 agosto, 2019

Introdução

Um trader mantendo relações comerciais com outros traders, assinantes, clientes ou amigos pode certamente ter a tarefa de enviar mala direta por e-mail. Enviar capturas de tela, revistas, registros ou relatórios são tarefas relevantes que não são necessárias todos os dias, mas raramente, em qualquer caso, cada um gostaria de ter esse recurso. É definitivamente difícil ou mesmo impossível usar ferramentas MQL gerais aqui. No final do artigo, voltaremos à questão de usar exclusivamente ferramentas MQL para resolver essa tarefa. Por enquanto, usaremos MQL e C#. Isso nos permitirá escrever o código necessário com relativa facilidade e conectá-lo ao terminal. Adicionalmente, essa conexão também será um desafio muito interessante para nós.

O artigo é destinado a desenvolvedores iniciantes e intermediários que desejam aprofundar seus conhecimentos sobre como criar bibliotecas, combiná-las com o terminal e conhecer melhor os serviços do Google.

Formulação do problema

Agora vamos definir mais precisamente o que vamos fazer. Há uma lista de contatos atualizável de contatos que permite enviar e-mails com anexos uma vez ou repetidamente para qualquer contato da lista. Coisas a considerar:

Em seguida, surge imediatamente a questão de gerenciar esta lista. Quais opções nós temos neste caso?

  1. Um banco de dados no disco rígido ou arquivo CSV é inconveniente e não é confiável o suficiente. Eles estão longe de estar sempre disponíveis. Para de alguma forma gerenciar tal repositório, é bem possível que seja necessário usar algum software adicional.
  2. Um banco de dados de um site especial com CMS como Joomla pode ser uma boa solução de trabalho, pois os dados estão protegidos e acessíveis de qualquer lugar. Além disso, você pode enviar e-mails facilmente do site. No entanto, existe uma desvantagem significativa. Para interagir com tal site, será preciso desenvolver um complemento, possivelmente volumoso e, possivelmente, contendo erros e falhas de segurança. Em outras palavras, será necessário o desenvolvimento de uma infra-estrutura confiável.
  3. Usar uma opção pronta disponível: os serviços do Google podem armazenar contatos com segurança, enquanto você tem acesso a eles de qualquer lugar e de diferentes dispositivos. Os contatos armazenados em sua conta do Google podem ser facilmente gerenciados. Em particular, você pode formar várias listas (grupos) e enviar e-mails. Isso é tudo o que é necessário para um trabalho confortável. Portanto, escolhemos o Google para trabalhar.

A interação com o Google é bastante documentada, por exemplo, aqui. Para começar a trabalhar com o Google, registre uma conta e crie uma lista de contatos. A lista deve conter os contatos para os quais você enviará e-mails. Nos contatos, crie um grupo com um determinado nome, por exemplo, "Forex", e adicione contatos selecionados a ele. Cada contato pode armazenar vários dados para serem disponibilizados posteriormente. Infelizmente, se um usuário ainda precisar de um campo de dados adicional, ele não poderá ser criado. Isso não deve causar inconvenientes, pois há muitos campos de dados disponíveis. Vou mostrar como usá-los mais tarde.

Agora é hora de seguir em frente para as tarefas principais.

Preparações no lado do Google

Suponha que já tenhamos uma conta do Google. Sendo assim, retomamos o desenvolvimento do projeto usando o "console do desenvolvedor" do Google. Aqui você pode descobrir em detalhes como usar o console e desenvolver um projeto. Naturalmente, o artigo no link acima descreve outro projeto. Nosso projeto precisa de um nome, escolhamos "WorkWithPeople". Além disso, nesse caso, fala-se de um disco que não é necessário aqui. Vamos precisar de outros serviços. Nesta fase, habilite os seguintes:

O primeiro fornece acesso à lista de contatos (na verdade, também fornece acesso a outras coisas, mas só precisamos da lista). Existe outro serviço para acessar a lista de contatos (Contacts API), mas no momento não é recomendado, por isso não preste atenção a isso.

Como o nome sugere, o segundo serviço fornece acesso ao e-mail.

Ative os serviços e obtenha as chaves que concedem ao aplicativo o acesso a eles. Não há necessidade de anotar ou tentar lembrar-se deles. Faça o download do arquivo anexado, no formato json, contendo todos os dados necessários para acessar os recursos do Google, incluindo essas chaves. Salve o arquivo no seu disco, talvez dando um nome mais adequado. No meu caso, é chamado "WorkWithPeople_gmail.json". Isso conclui o trabalho direto com o Google. Criamos a conta, a lista de contatos e o projeto, além de obter o arquivo de acesso.

Agora vamos começar a trabalhar com o VS 2017.

Projeto e pacotes

Abra o VS 2017 e crie um projeto padrão Class Library (.NET Framework). Nomeie-o de qualquer maneira memorizável (no meu caso, ele coincide com o nome do projeto do Google "WorkWithPeople", embora isso não seja obrigatório). Instale pacotes adicionais usando o NuGet imediatamente:

Durante a instalação, o NuGet oferece a instalação de pacotes relacionados. Concorde em fazer isso. Em nosso caso, o projeto recebe os pacotes do Google para trabalhar com contatos e gerenciar e-mails. Agora estamos prontos para desenvolver o código.

Acessando contatos

Vamos começar com a classe auxiliar. Se considerarmos a quantidade de dados que um determinado contato do Google contém, torna-se óbvio que sua parte principal não é necessária para nossa tarefa. Precisamos de um nome de contato e de um endereço para enviar o e-mail. Na verdade, precisamos de dados de outro campo, mas nos debruçaremos mais sobre isso depois.

A classe correspondente pode ter esta aparência:

namespace WorkWithPeople
{    
    internal sealed class OneContact
    {
        public OneContact(string n, string e)
        {
            this.Name  = n;
            this.Email = e;
        }
        public string Name  { get; set; }
        public string Email { get; set; }
    }
}

Existem duas propriedades do tipo string que armazenam o nome e o endereço do contato, bem como um construtor simples com dois parâmetros para inicializar estas propriedades. Não há verificações adicionais na classe, elas serão realizadas em outro lugar.

Uma lista de elementos simples é criada quando lida a lista de contatos. Isso permite realizar uma campanha de mala com base nos dados dessa lista recém-criada. Se você quiser atualizar a lista, remova todos os elementos da lista e repita a operação de leitura e seleção de dados da conta do Google.

Há ainda outra classe auxiliar. A lista de contatos pode conter endereços de e-mail inválido ou pode estar vazia. Antes de enviar e-mails, é recomendável que você tenha certeza de que o endereço está disponível e certo. Para fazer isso, crie outra classe auxiliar:

namespace WorkWithPeople
{    
    internal static class ValidEmail
    {
        public stati cbool IsValidEmail(this string source) => !string.IsNullOrEmpty(source) && new System.ComponentModel.DataAnnotations.EmailAddressAttribute().IsValid(source);
    }
}

Para realizar verificações, usamos ferramentas disponíveis, embora também possamos usar expressões regulares. Para conveniência de uso posterior, desenvolvemos o código como um método de extensão. Como não é difícil adivinhar, o método retornará true se a linha que contém o endereço de correspondência passar a verificação e false, caso contrário. Agora é hora de passar para a parte principal do código.

Acesso e trabalho com serviços

Já criamos o projeto, obtivemos as chaves e baixamos o arquivo JSON para autorização do aplicativo. Por isso, vamos criar uma nova classe ContactsPeople e adicionar as compilações correspondentes ao arquivo:

using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Net.Mail;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
using Google.Apis.People.v1;
using Google.Apis.Services;
using Google.Apis.Util.Store;
using Google.Apis.Http;
using Google.Apis.PeopleService.v1;
using Google.Apis.PeopleService.v1.Data;
using Google.Apis.Gmail.v1;
using Google.Apis.Gmail.v1.Data;

namespace WorkWithPeople
{
    internal sealed class ContactsPeople
    {
       public static string Applicationname { get; } = "WorkWithPeople";

.....

Adicionamos a propriedade estática que contém o nome do projeto do Google. Esta propriedade estática é feita somente leitura.

Adicionamos campos privados e enumeração à classe:

        private enum             PersonStatus
        {
            Active,
            Passive
        };
        private string           _groupsresourcename;
        private List<OneContact> _list = new List<OneContact>();
        private UserCredential   _credential;
        private PeopleService    _pservice;
        private GmailService     _gservice;

A enumeração é usada para marcar um contato como "ativo" (recebe e-mails) e "passivo" (não recebe e-mails). Outros campos fechados:

Vamos escrever o código da principal função de trabalho:

        publicint WorkWithGoogle(string credentialfile, 
                                   string user, 
                                   string filedatastore, 
                                   string groupname,
                                   string subject,
                                   string body,
                                   bool   isHtml,
                                   List<string> attach = null)
        {
          ...

Seus argumentos são:

O valor de retorno é o número de e-mails enviados. Vamos começar a escrever o código da função:

            if (!File.Exists(credentialfile))
                throw (new FileNotFoundException("Not found: " + credentialfile));
            using (var stream = new FileStream(credentialfile, FileMode.Open, FileAccess.Read))
            {
                if (_credential == null) {
                    _credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
                        GoogleClientSecrets.Load(stream).Secrets,
                        new[]
                        {
                            GmailService.Scope.GmailSend,
                            PeopleService.Scope.ContactsReadonly
                        },
                        user,
                        CancellationToken.None,
                        new FileDataStore(filedatastore)).Result;
                        CreateServicies();
                }
                else if (_credential.Token.IsExpired(Google.Apis.Util.SystemClock.Default)) {
                    bool refreshResult = _credential.RefreshTokenAsync(CancellationToken.None).Result;
                    _list.Clear();
                    if (!refreshResult) return 0;   
                    CreateServicies();
                }
                
            }// using (var stream = new FileStream(credentialfile, FileMode.Open, FileAccess.Read))

Preste atenção ao array de strings que define o acesso ao serviço:

Além disso, observe a chamada do GoogleWebAuthorizationBroker.AuthorizeAsync, pois seu nome sugere que a chamada deve ser executada de forma assíncrona.

Observe que, se um token recebido anteriormente estiver atrasado, o código o atualizará e removerá todos os objetos da lista _list formada anteriormente.

A função auxiliar CreateServicies() cria e inicializa os objetos necessários:

        private void         CreateServicies()
        {
            _pservice = new PeopleService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = _credential,
                ApplicationName = Applicationname
            });
            _gservice = new GmailService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = _credential,
                ApplicationName = Applicationname
            });
        }

Como podemos ver, depois de executar os segmentos de código exibidos acima, obtemos acesso aos serviços necessários:

  Usando o arquivo de dados JSON, primeiro solicitamos as "permissões" e os salvamos no campo _credencial. Em seguida, chamamos os construtores de serviço passando - como lista de inicialização - os campos "permissão" e nome do projeto para eles.

É hora de obter a lista de contatos de um grupo para uma campanha de mala:

            try {
                  if (_list.Count == 0)
                    GetPeople(_pservice, null, groupname);                     
            }
            catch (Exception ex) {
                ex.Data.Add("call GetPeople: ", ex.Message);
                throw;
            }
#if DEBUG
            int i = 1;
            foreach (var nm in _list) {
                Console.WriteLine("{0} {1} {2}", i++, nm.Name, nm.Email);
            }
#endif
            if (_list.Count == 0) {
                Console.WriteLine("Sorry, List is empty...");
                return 0;
            }

A lista _list que armazena os contatos será preenchida pela função GetPeople(...), o que discutiremos mais adiante. Essa função serve como uma fonte de exceção, portanto, sua chamada é empacotada no bloco try. Nenhum tipo de exceção é detectado nas compilações ativadas, portanto, o bloco catch é gravado na forma mais geral. Em outras palavras, não precisamos incluir todas as ocorrências possíveis aqui para não perder dados valiosos para depuração. Por isso, adicionamos os dados que consideramos necessários à exceção e reativamo-los.

Tenha em mente que _list é atualizada apenas quando está vazia, ou seja, quando recebe um novo token ou quando atualizado um antigo.

O próximo bloco será executado apenas para a versão de depuração do aplicativo. Nele, a lista inteira é simplesmente exibida no console.

O bloco final é bastante óbvio. Se a lista permanecer vazia, o trabalho adicional não tem sentido e é interrompido acompanhado de uma mensagem.

A função termina com um bloco de código para a criação do e-mail de saída e com a execução da campanha de mala direta:

            using (MailMessage mail = new MailMessage
            {
                Subject = subject,
                Body = body,
                IsBodyHtml = isHtml
            })  // MailMessage mail = new MailMessage
            {
                if (attach != null)
                {
                    foreach (var path in attach)
                        mail.Attachments.Add(new Attachment(path));
                } //  if (attach != null)

                foreach (var nm in _list)
                    mail.To.Add(new MailAddress(nm.Email, nm.Name));
                try
                {
                    SendOneEmail(_gservice, mail);
                }
                catch (Exception ex)
                {
                    ex.Data.Add("call SendOneEmail: ", ex.Message);
                    throw;
                }
            }// using (MailMessage mail = new MailMessage

Aqui é criada a instância da classe da biblioteca Mailmessage seguido da inicialização e do preenchimento dos campos. No caso de haver uma lista de anexos, ela será adicionada. Finalmente, é formada a lista de endereçamento obtida no estágio anterior.

O envio de e-mails é realizado pela função SendOneEmail(...), que será considerada mais tarde. Assim como a funçãoGetPeople(...), ela também pode se tornar uma fonte de exceção. Por isso, sua chamada também é envolvida no bloco try e o processamento em catch é feito de forma semelhante.

Neste ponto, o trabalho da função WorkWithGoogle(...) é considerado concluído e retorna um valor _list.Count, considerando que as mensagens de e-mail foram enviadas para cada contato da lista.

Preenchendo a lista de contatos

Depois de obter acesso, _list está pronto para ser preenchido. Isso é feito pela função:

        private void         GetPeople(PeopleService service, string pageToken, string groupName)
        {
           ...

Seus argumentos são:

Primeira vez que a função é chamada com pageToken = NULL. Se a solicitação ao Google retornar o token com o valor diferente de NULL, a função será chamada recursivamente.

            if (string.IsNullOrEmpty(_groupsresourcename))
            {
                ContactGroupsResource groupsResource = new ContactGroupsResource(service);
                ContactGroupsResource.ListRequest listRequest = groupsResource.List();
                ListContactGroupsResponse response = listRequest.Execute();
                _groupsresourcename = (from gr in response.ContactGroups
                                       where string.Equals(groupName.ToUpperInvariant(), gr.FormattedName.ToUpperInvariant())
                                       select gr.ResourceName).Single();
                if (string.IsNullOrEmpty(_groupsresourcename))
                    throw (new MissingFieldException($"Can't find GroupName: {groupName}"));
            }// if (string.IsNullOrEmpty(_groupsresourcename))

Descobrimos o nome do recurso pelo nome do grupo. Para conseguir isso, solicite a lista de todos os recursos e descubra o necessário numa expressão lambda simples. Observe que deve haver apenas um recurso com o nome necessário. Se nenhum recurso for encontrado durante o trabalho, será ativada a exceção.

Google.Apis.PeopleService.v1.PeopleResource.ConnectionsResource.ListRequest peopleRequest =
                new Google.Apis.PeopleService.v1.PeopleResource.ConnectionsResource.ListRequest(service, "people/me")
                {
                    PersonFields = "names,emailAddresses,memberships,biographies"
                };
            if (pageToken != null) {
                peopleRequest.PageToken = pageToken;
            }

Vamos construir o pedido ao Google para obter a lista necessária. Para isso, especifique os campos dos dados de contato do Google nos quais estamos interessados:

Finalmente, nós executamos a solicitação:

            var request = peopleRequest.Execute();
            var list1 = from person in request.Connections
                     where person.Biographies != null
                     from mem in person.Memberships
                     where string.Equals(_groupsresourcename, mem.ContactGroupMembership.ContactGroupResourceName) &&
                           PersonActive(person.Biographies.FirstOrDefault()?.Value) == PersonStatus.Active
                     let name = person.Names.First().DisplayName
                     orderby name
                     let email = person.EmailAddresses?.FirstOrDefault(p => p.Value.IsValidEmail())?.Value
                     where !string.IsNullOrEmpty(email)
                     select new OneContact(name, email);
            _list.AddRange(list1);
            if (request.NextPageToken != null) {
                GetPeople(service, request.NextPageToken, groupName);
            }
        }//void GetPeople(PeopleService service, string pageToken, string groupName)

Fazemos a solicitação e filtramos os dados necessários numa expressão lambda que parece assustadora, mas, na verdade, é descomplicada. O contato deve ter uma biografia diferente de zero, deve estar no grupo certo, deve ser um contato ativo e deve ter o endereço correto. Aqui mostramos uma função que determina o status ativo/passivo de um contato individual graças ao conteúdo do campo "biographies":

        private PersonStatus PersonActive(string value)
        {
            try {
                switch (Int32.Parse(value))
                {
                    case 1:
                        return PersonStatus.Active;
                    default:
                        return PersonStatus.Passive;
                }
            }
            catch (FormatException)   { return PersonStatus.Passive; }
            catch (OverflowException) { return PersonStatus.Passive; }
        }//PersonStatus PersonActive(string value)

Esta é a única função no projeto que não procura ressaltar exceções, mas tenta manipular algumas delas localmente.

Isso é tudo! Adicionamos a lista resultante à lista _list. Se nem todos os contatos foram lidos, chamamos a função recursivamente, com o novo valor do token.

Envio de e-mail

Uma pequena função auxiliar faz isso:

        private void SendOneEmail(GmailService service, MailMessage mail)
        {
            MimeKit.MimeMessage mimeMessage = MimeKit.MimeMessage.CreateFromMailMessage(mail);
            var encodedText = Base64UrlEncode(mimeMessage.ToString());
            var message = new Message { Raw = encodedText };

            var request = service.Users.Messages.Send(message, "me").Execute();
        }//  bool SendOneEmail(GmailService service, MailMessage mail)

Sua chamada é descrita acima, por isso não vamos repetir. A tarefa desta função é preparar o envio de e-mail e executá-lo. Além disso, todas as operações "pesadas" para preparar o correio estão concentradas na função. Infelizmente, o Google não aceita dados da classe Mailmessage. Por isso, preparamos os dados num formato aceitável e os codificamos. A composição da codificação Mimekit inclui ferramentas que executam a codificação, mas parece mais fácil usar a função mais simples disponível. Ela é complexa e não será mostrada separadamente no artigo. Preste atenção no userId especializado do tipo string na chamada service.Users.Messages.Send. Ele é igual ao valor especial "me", que permite que o Google acesse sua conta para obter informações do remetente.

Isso conclui a análise de classe ContatosPessoas. Outras funções remanescentes são de natureza auxiliar secundária e não nos deteremos nelas.

Conector do terminal

Resta-nos considerar a questão da conexão entre nossa compilação, que ainda não foi criada, e o terminal. À primeira vista, a tarefa não parece complicada. Definimos vários métodos estáticos, compilamos o projeto, copiamo-lo para a pasta “Libraries” do terminal. Chamamos os métodos estáticos de compilação do código MQL. Mas o que exatamente precisa ser copiado? Existe a nossa compilação na forma de uma biblioteca de dll. Há também cerca de uma dúzia de compilações que são carregadas pelo NuGet e que são usadas no trabalho. Existe um arquivo JSON que contém dados para acessar o Google. Vamos tentar copiar todo esse conjunto para a pasta "Libraries". Vamos criar o script mais primitivo em MQL (mesmo o código deste script não deve ser usado) e tentar chamar algum método estático de nossa compilação. Uma exceção é o erro "O arquivo Google.Apis.dll não foi encontrado", que é uma surpresa muito desagradável e significa que o CLR não encontra a compilação desejada, embora esteja na mesma pasta que a nossa compilação principal. Por que acontece isso? Não vale a pena analisar a situação aqui em detalhes, em vez disso, redirecionarei os interessados nos detalhes para o famoso livro de Richter, para a seção sobre a pesquisa de compilações privadas.

Já existem muitos exemplos de aplicativos .Net totalmente funcionais que trabalham com o MetaTrader, neles também houve problemas, como foram resolvidos? Por exemplo, aqui o problema foi resolvido criando um canal entre o aplicativo .Net e o programa MQL. Já aqui foi usado um modelo baseado em eventos. Pode-se sugerir uma abordagem semelhante quanto à transferência dos dados necessários do programa no MQL para o aplicativo .Net usando a linha de comando.

Mas vale a pena considerar um método que parece mais elegante e, de fato, mais universal. Trata-se do carregamento da compilação usando um evento AppDomain.AssemblyResolve. Este evento ocorre quando o tempo de execução não pode associar uma compilação pelo nome. Nesse caso, o manipulador de eventos pode carregar e retornar a compilação de outra pasta cujo endereço ele conhece. Portanto, sugere-se uma solução muito bonita:

  1. Criamos uma pasta com um nome diferente na pasta "Libraries", no meu caso "WorkWithPeople".
  2. A própria compilaçãoem, cujos métodos devem ser importados para um arquivo com MQL, é copiada para a pasta "Libraries", como deveria ser.
  3. Todas as outras versões do projeto, incluindo um arquivo JSON com informações sobre o acesso aos serviços do Google, são copiadas para a pasta "WorkWithPeople".
  4. Informamos à nossa compilação principal na pasta "Libraries" o endereço onde ela terá que procurar outras compilações - o caminho completo para a pasta "WorkWithPeople".

Como resultado, obtemos uma solução viável e não sujamos a pasta "Libraries". Resta-nos implementar as decisões tomadas no código:

Classe de gerenciamento

Criamos uma classe estática:

    public static class Run
    {

        static Run() {
            AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;
        }// Run()

Vamos criar um construtor estático e incluir um manipulador do evento acima para que ele apareça na cadeia de manipuladores o mais cedo possível. Definimos manipulador em si:

        static Assembly ResolveAssembly(object sender, ResolveEventArgs args) {
            String dllName = new AssemblyName(args.Name).Name + ".dll";
            return Assembly.LoadFile(Path.Combine(_path, dllName) );
        }// static Assembly ResolveAssembly(object sender, ResolveEventArgs args)

Agora, sempre que uma compilação não for detectada, será chamado esse desenvolvedor. Sua tarefa é carregar e retornar a compilação, combinando o caminho da variável _path (definido durante a inicialização) e o nome computado. Agora, ocorrerá uma exceção somente se o manipulador não puder encontrar a compilação.

Agora a função de inicialização:

public static void Initialize(string Path, string GoogleGroup, string AdminEmail, string Storage)
        {
            if (string.IsNullOrEmpty(Path) ||
                string.IsNullOrEmpty(GoogleGroup) ||
                string.IsNullOrEmpty(AdminEmail) ||
                string.IsNullOrEmpty(Storage)) throw (new MissingFieldException("Initialize: bad parameters"));
            _group = GoogleGroup;
            _user = AdminEmail;
            _storage = Storage;
            _path = Path;
        }//  Initialize(string Path, string GoogleGroup, string AdminEmail, string Storage)

Esta função deve a primeira a ser chamada, definitivamente ANTES de tentar executar o envio de e-mails. Seus argumentos são:

Todos os argumentos descritos não devem ser linhas vazias, caso contrário, surge uma exceção.

Para os arquivos anexados, criamos uma lista e uma função de adição simples:

public static void AddAttachment (string attach) { _attachList.Add(attach);}

Lembrando que screenshots serão adicionadas, ou quaisquer outros arquivos criados anteriormente no ambiente MetaTrader, a função não fornece nenhum meio de verificar a presença ou ausência de erros. Assume-se que a ferramenta de gerenciamento que trabalha no terminal tratará desse trabalho.

Imediatamente crie um objeto para envio de mala direta por e-mail:

static ContactsPeople _cContactsPeople = new ContactsPeople();

Executamos chamando a função:

public static int DoWork(string subject, string body, bool isHtml = false) {
            if (string.IsNullOrEmpty(body))
                throw (new MissingFieldException("Email body null or empty"));
            int res = 0;
            if (_attachList.Count > 0) {
                res = _cContactsPeople.WorkWithGoogle(Path.Combine(_path, "WorkWithPeople_gmail.json"),
                    _user,
                    _storage,
                    _group,
                    subject,
                    body,
                    isHtml,
                    _attachList);
                _attachList.Clear();
            } else {
                res = _cContactsPeople.WorkWithGoogle(Path.Combine(_path, "WorkWithPeople_gmail.json"),
                    _user,
                    _storage,
                    _group,
                    subject,
                    body,
                    isHtml);
            }// if (_attachList.Count > 0) ... else ...
            return res;
        }// static int DoWork(string subject, string body, bool isHtml = false)

Os parâmetros de entrada são os seguintes:

Dependendo se há anexos no e-mail ou não, há duas opções para chamar _cContactosPessoas.TrabalharComGoogle. O primeiro argumento para a chamada é interessante:

Path.Combine(_path, "WorkWithPeople_gmail.json")

Este é o caminho completo para o arquivo que contém informações para acessar os serviços do Google.

A função DoWork(...) retorna o número de e-mails enviados.

Todo o projeto para VS++ 2017, com exceção do arquivo com dados sobre o acesso ao Google, está no arquivo anexo google.zip.

Do lado do MetaTrader

Após terminar de revisar o código de compilação, vamos para o lado do terminal e criar um script simples. Ele pde ser escrito algo assim (ignorando parte do código no começo):

#import "WorkWithPeople.dll"


void OnStart()
  {
   string scr = "scr.gif";
   string fl = TerminalInfoString(TERMINAL_DATA_PATH) + "\\MQL5\\Files\\";
   ChartScreenShot(0, scr, 800, 600);  
   Run::Initialize("e:\\Forex\\RoboForex MT5 Demo\\MQL5\\Libraries\\WorkWithPeople\\" ,"Forex" ,"ХХХХХXХ@gmail.com" ,"WorkWithPeople" );
   Run::AddAttachment(fl + scr);
   int res = Run::DoWork("some subj" ,
                         "Very big body" ,
                          false );
   Print("result: ", res);   
  }

O código é bem óbvio. Importamos a compilação, a primeira coisa que fazemos é inicializá-la, como mencionado acima, anexamos a captura de tela feita anteriormente e execute a distribuição de e-mail. O código completo está contido no arquivo anexado google_test1.mq5.

Outro exemplo é um indicador trabalhando no timeframe M5 e enviando um email com uma captura de tela sempre que encontrar um novo candle:

#import "WorkWithPeople.dll"

input string scr="scr.gif";

string fp;

int OnInit()
  {
   fp=TerminalInfoString(TERMINAL_DATA_PATH)+"\\MQL5\\Files\\";
   Run::Initialize("e:\\Forex\\RoboForex MT5 Demo\\MQL5\\Libraries\\WorkWithPeople\\","Forex","0ndrei1960@gmail.com","WorkWithPeople");

   return(INIT_SUCCEEDED);
  }

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   if(IsNewCandle()) 
     {
      ChartScreenShot(0,scr,800,600);
      Run::AddAttachment(fp+scr);
      string body="Time: "+TimeToString(TimeLocal());
      int res=Run::DoWork("some subj",body,false);
      Print(body);
     }
   return(rates_total);  
  }

O código completo deste indicador está no arquivo anexado google_test2.mq5. É tão simples que não precisa de comentários.

Fim do artigo

Vamos resumir um pouco. Vimos como os contatos do Google podem ser usados para interagir com parceiros. Além disso, consideramos uma maneira de integrar compilações com o terminal, o que permite não entupir pastas com arquivos estranhos. Vale a pena dizer um pouco sobre a eficácia do código de compilação. Neste caso, a esta questão não foi dada atenção suficiente. Mas você pode propor algumas atividades nessa direção:

Não vou argumentar que você precisa usar todos os métodos acima, mas é bem possível que sua aplicação melhore o desempenho e permita que você use a compilação resultante não apenas com o MetaTrader, mas também de forma independente, como parte de um processo separado.

Em conclusão, vale a pena voltar à questão de usar ferramentas MQL para essa tarefa. É possível? A julgar pela documentação do Google, a resposta é sim. É possível obter os mesmos resultados usando consultas GET/POST, adicionalmente, seus exemplos estão disponíveis, e, portanto, é possível usar o padrão Webrequest. Vale a pena fazer isso? Deixamos a questão no ar, porque devido ao grande número de solicitações, escrever, depurar e manter esse código será bastante difícil.

Programas utilizados no artigo:

 # Nome
Tipo
 Descrição
1 google_test1.mq5
Script
Script que cria uma captura de tela e a envia para vários endereços.
2
google_test1.mq5 Indicador
Exemplo de indicador enviando e-mails com cada novo candle.
3 google.zip Arquivo Projeto da compilação e do aplicativo de console de teste.