Mengonfigurasi API Gmail untuk mengganti ekstensi IMAP PHP dan bekerja dengan protokol OAuth2

Setelah salah satu yang beruntung, tidak siap untuk fakta bahwa mulai 15 Februari 2021 Otorisasi ke Gmail dan produk lain hanya akan berjalan melalui OAuth, saya membaca artikel " Google mengubur ekstensi PHP IMAP " dan sedih mulai mengambil tindakan pada penggantian ekstensi IMAP PHP proyek Anda di Google API. Ada lebih banyak pertanyaan daripada jawaban, jadi saya menulis manual pada saat yang sama.



Saya memiliki PHP IMAP yang digunakan untuk tugas-tugas berikut:



  1. Menghapus surat-surat lama dari kotak surat . Sayangnya, di panel kontrol akun G Suite perusahaan, Anda hanya dapat mengonfigurasi periode untuk menghapus pesan dari semua kotak surat organisasi setelah N hari setelah penerimaan. Saya, bagaimanapun, perlu menghapus surat hanya di kotak surat tertentu dan setelah beberapa hari yang berbeda setelah penerimaan.
  2. Memfilter, mengurai, dan menandai huruf . Banyak surat dikirim dari situs kami dalam mode otomatis, beberapa di antaranya tidak sampai ke penerima, yang karenanya, laporan datang. Penting untuk menangkap laporan ini, membongkar, menemukan klien melalui email dan membentuk surat yang dapat dibaca manusia untuk manajer, sehingga dia dapat menghubungi klien dan mengklarifikasi relevansi alamat email.


Kami akan menyelesaikan dua tugas ini menggunakan API Gmail dalam artikel ini (dan pada saat yang sama menonaktifkan akses untuk aplikasi yang tidak aman di setelan kotak surat, yang diaktifkan untuk PHP IMAP untuk bekerja, dan, pada kenyataannya, akan berhenti bekerja pada hari yang buruk di bulan Februari 2021). Kami akan menggunakan apa yang disebut sebagai akun layanan dari aplikasi Gmail, yang, jika dikonfigurasi sesuai, memungkinkan untuk terhubung ke semua kotak surat organisasi dan melakukan tindakan apa pun di dalamnya.



1. Buat proyek di konsol pengembang Google API



Dengan bantuan proyek ini, kami akan melakukan interaksi API dengan Gmail, dan di dalamnya kami akan membuat akun layanan yang sama.



Untuk membuat proyek:



  1. Buka konsol pengembang Google API dan masuk sebagai administrator G Suite (baik, atau siapa pengguna Anda di sana dengan semua hak)
  2. Kami mencari tombol "Buat Proyek".



    Saya temukan di sini:
    image



    Dan kemudian di sini:
    image



    Isi nama proyek dan simpan:



    Pembuatan proyek
    image



  3. Buka proyek dan klik tombol "Aktifkan API dan Layanan":



    Aktifkan API dan layanan
    image



    Memilih API Gmail



2. Membuat dan mengkonfigurasi akun layanan



Untuk melakukan ini, Anda dapat menggunakan manual resmi atau melanjutkan membaca:



  1. Buka API Gmail kami yang ditambahkan, klik tombol "Buat kredensial" dan pilih "Akun layanan":



    Pembuatan akun layanan
    image



    Isi sesuatu dan klik "Buat":



    Detail akun layanan
    image



    Yang lainnya dapat dikosongkan:



    Hak akses untuk akun layanan
    image



    image



  2. , . G Suite, « — API».



    API
    image

    image



  3. « »:



    image



    «», « » , « OAuth» — :



    - https://mail.google.com/ -

    - https://www.googleapis.com/auth/gmail.modify -

    - https://www.googleapis.com/auth/gmail.readonly -

    - https://www.googleapis.com/auth/gmail.metadata -




    image

    image



  4. « G Suite»:



    image



    Dan juga isi nama produk Anda pada kolom di bawah ini.

  5. Sekarang Anda perlu membuat kunci akun layanan: ini adalah file yang harus tersedia di aplikasi Anda. Dia, pada kenyataannya, akan digunakan untuk otorisasi.



    Untuk melakukannya, dari halaman "Kredensial" project Anda, ikuti link "Kelola akun layanan":



    Kredensial
    image



    dan pilih "Tindakan - Buat Kunci", ketik: JSON:



    Manajemen akun layanan
    image



    Setelah itu, file kunci akan dibuat dan diunduh ke komputer Anda, yang harus ditempatkan di proyek Anda dan diberi akses ketika Anda memanggil API Gmail.



Ini melengkapi konfigurasi API Gmail, kemudian akan ada sedikit kode kakao saya, pada kenyataannya, menerapkan fungsi yang sejauh ini telah diselesaikan oleh ekstensi PHP IMAP.



3. Menulis kode



Ada dokumentasi resmi yang cukup bagus ( klik dan klik ) untuk API Gmail , yang saya gunakan. Tapi sejak saya mulai menulis manual detail, saya akan melampirkan kode kakao saya sendiri.



Jadi, pertama-tama, kami menginstal Perpustakaan Klien Google (apiclient) menggunakan komposer:



composer require google/apiclient



(Awalnya, sebagai ahli sastra sejati, saya menginstal versi 2.0 dari klien api, seperti yang ditunjukkan dalam Panduan Memulai PHP , tetapi pada awalnya, semua jenis vornings dan alarm jatuh pada PHP 7.4 , jadi saya tidak menyarankan Anda untuk melakukan hal yang sama)



Kemudian, berdasarkan contoh dari dokumentasi resmi, kami menulis kelas kami sendiri untuk bekerja dengan Gmail, tidak lupa untuk menentukan file kunci akun layanan:



Kelas untuk bekerja dengan Gmail
<?php
//     Gmail
class GmailAPI
{
    private $credentials_file = __DIR__ . '/../Gmail/credentials.json'; //   

    // ---------------------------------------------------------------------------------------------
    /**
     *   Google_Service_Gmail Authorized Gmail API instance
     *
     * @param  string $strEmail  
     * @return Google_Service_Gmail Authorized Gmail API instance
     * @throws Exception
     */
    function getService(string $strEmail){
        //    
        try{
            $client = new Google_Client();
            $client->setAuthConfig($this->credentials_file);
            $client->setApplicationName('My Super Project');
            $client->setScopes(Google_Service_Gmail::MAIL_GOOGLE_COM);
            $client->setSubject($strEmail);
            $service = new Google_Service_Gmail($client);
        }catch (Exception $e) {
            throw new \Exception('   getService: '.$e->getMessage());
        }
        return $service;
    }
    // ---------------------------------------------------------------------------------------------

    /**
     *    ID    
     *
     * @param  Google_Service_Gmail $service Authorized Gmail API instance.
     * @param  string $strEmail  
     * @param  array $arrOptionalParams      
     *         Gmail  after: 2020/08/20 in:inbox label:
     *      q  $opt_param
     * @return array  ID     array('arrErrors' => $arrErrors),   
     * @throws Exception
     */
    function listMessageIDs(Google_Service_Gmail $service, string $strEmail, array $arrOptionalParams = array()) {
        $arrIDs = array(); //  ID 

        $pageToken = NULL; //     
        $messages = array(); //    

        //  
        $opt_param = array();
        //    ,       Gmail      q
        if (count($arrOptionalParams)) $opt_param['q'] = str_replace('=', ':', http_build_query($arrOptionalParams, null, ' '));

        //   ,   ,     
        do {
            try {
                if ($pageToken) {
                    $opt_param['pageToken'] = $pageToken;
                }
                $messagesResponse = $service->users_messages->listUsersMessages($strEmail, $opt_param);
                if ($messagesResponse->getMessages()) {
                    $messages = array_merge($messages, $messagesResponse->getMessages());
                    $pageToken = $messagesResponse->getNextPageToken();
                }
            } catch (Exception $e) {
                throw new \Exception('   listMessageIDs: '.$e->getMessage());
            }
        } while ($pageToken);

        //   ID  
        if (count($messages)) {
            foreach ($messages as $message) {
                $arrIDs[] = $message->getId();
            }
        }
        return $arrIDs;
    }
    // ---------------------------------------------------------------------------------------------

    /**
     *      ID  batchDelete
     *
     * @param  Google_Service_Gmail $service Authorized Gmail API instance.
     * @param  string $strEmail  
     * @param  array $arrIDs  ID      listMessageIDs
     * @throws Exception
     */
    function deleteMessages(Google_Service_Gmail $service, string $strEmail, array $arrIDs){
        //      1000 ,      batchDelete
        $arrParts = array_chunk($arrIDs, 999);
        if (count($arrParts)){
            foreach ($arrParts as $arrPartIDs){
                try{
                    //     
                    $objBatchDeleteMessages = new Google_Service_Gmail_BatchDeleteMessagesRequest();
                    //   
                    $objBatchDeleteMessages->setIds($arrPartIDs);
                    //  
                    $service->users_messages->batchDelete($strEmail,$objBatchDeleteMessages);
                }catch (Exception $e) {
                    throw new \Exception('   deleteMessages: '.$e->getMessage());
                }
            }
        }
    }
    // ---------------------------------------------------------------------------------------------

    /**
     *     get
     *
     * @param  Google_Service_Gmail $service Authorized Gmail API instance.
     * @param  string $strEmail  
     * @param  string $strMessageID ID 
     * @param  string $strFormat The format to return the message in.
     * Acceptable values are:
     * "full": Returns the full email message data with body content parsed in the payload field; the raw field is not used. (default)
     * "metadata": Returns only email message ID, labels, and email headers.
     * "minimal": Returns only email message ID and labels; does not return the email headers, body, or payload.
     * "raw": Returns the full email message data with body content in the raw field as a base64url encoded string; the payload field is not used.
     * @param  array $arrMetadataHeaders When given and format is METADATA, only include headers specified.
     * @return  object Message
     * @throws Exception
     */
    function getMessage(Google_Service_Gmail $service, string $strEmail, string $strMessageID, string $strFormat = 'full', array $arrMetadataHeaders = array()){
        $arrOptionalParams = array(
            'format' => $strFormat // ,    
        );
        //   - metadata,     
        if (($strFormat == 'metadata') and count($arrMetadataHeaders))
            $arrOptionalParams['metadataHeaders'] = implode(',',$arrMetadataHeaders);

        try{
            $objMessage = $service->users_messages->get($strEmail, $strMessageID,$arrOptionalParams);
            return $objMessage;
        }catch (Exception $e) {
            throw new \Exception('   getMessage: '.$e->getMessage());
        }
    }
    // ---------------------------------------------------------------------------------------------

    /**
     *   ,    
     *
     * @param  Google_Service_Gmail $service Authorized Gmail API instance.
     * @param  string $strEmail  
     * @return  object $objLabels -  -  
     * @throws Exception
     */
    function listLabels(Google_Service_Gmail $service, string $strEmail){
        try{
            $objLabels = $service->users_labels->listUsersLabels($strEmail);
            return $objLabels;
        }catch (Exception $e) {
            throw new \Exception('   listLabels: '.$e->getMessage());
        }
    }
    // ---------------------------------------------------------------------------------------------

    /**
     *     ()  
     *
     * @param  Google_Service_Gmail $service Authorized Gmail API instance.
     * @param  string $strEmail  
     * @param  string $strMessageID ID 
     * @param  array $arrAddLabelIds  ID ,     
     * @param  array $arrRemoveLabelIds  ID ,     
     * @return  object Message -  
     * @throws Exception
     */
    function modifyLabels(Google_Service_Gmail $service, string $strEmail, string $strMessageID, array $arrAddLabelIds = array(), array $arrRemoveLabelIds = array()){
        try{
            $objPostBody = new Google_Service_Gmail_ModifyMessageRequest();
            $objPostBody->setAddLabelIds($arrAddLabelIds);
            $objPostBody->setRemoveLabelIds($arrRemoveLabelIds);
            $objMessage = $service->users_messages->modify($strEmail,$strMessageID,$objPostBody);
            return $objMessage;
        }catch (Exception $e) {
            throw new \Exception('   modifyLabels: '.$e->getMessage());
        }
    }
    // ---------------------------------------------------------------------------------------------

}




Setiap kali kita berinteraksi dengan Gmail, hal pertama yang kita lakukan adalah memanggil fungsi getService ($ strEmail) dari kelas GmailAPI, yang mengembalikan objek "resmi" untuk bekerja dengan kotak surat $ strEmail. Selanjutnya, objek ini sudah diteruskan ke fungsi lain untuk langsung melakukan tindakan yang kita butuhkan. Semua fungsi lain di kelas GmailAPI sudah melakukan tugas tertentu:



  • listMessageIDs - menemukan pesan sesuai dengan kriteria yang ditentukan dan mengembalikan ID mereka (string pencarian yang diteruskan ke listUsersMessages Fungsi API Gmail harus serupa dengan string pencarian di antarmuka web kotak surat),
  • deleteMessages - menghapus pesan dengan ID yang diteruskan kepadanya (fungsi Gmail API batchDelete menghapus tidak lebih dari 1000 pesan dalam satu kali jalan, jadi saya harus membagi array ID yang diteruskan ke fungsi menjadi beberapa larik yang masing-masing terdiri dari 999 huruf dan melakukan penghapusan beberapa kali),
  • getMessage - mendapatkan semua informasi tentang pesan dengan ID yang diteruskan padanya,
  • listLabels - mengembalikan daftar bendera di kotak surat (Saya menggunakannya untuk mendapatkan ID dari bendera yang awalnya dibuat di antarmuka web kotak surat dan ditetapkan ke pesan yang diinginkan)
  • modifikasikan label - tambahkan atau hapus tanda pada pesan


Selanjutnya, kita memiliki tugas untuk menghapus surat-surat lama di berbagai kotak surat. Pada saat yang sama, kami menganggap surat-surat lama diterima jumlah hari yang lalu untuk setiap kotak surat. Untuk menyelesaikan tugas ini, kami menulis skrip berikut, yang dijalankan setiap hari oleh cron:



Menghapus email lama
<?php
/**
 *      Gmail
 *      
 */
require __DIR__ .'/../general/config/config.php'; //   
require __DIR__ .'/../vendor/autoload.php'; //   

//       
$arrMailBoxesForClean = array(
    'a@domain.com' => 30,
    'b@domain.com' => 30,
    'c@domain.com' => 7,
    'd@domain.com' => 7,
    'e@domain.com' => 7,
    'f@domain.com' => 1
);

$arrErrors = array(); //  
$objGmailAPI = new GmailAPI(); //     GMail

//     ,      
foreach ($arrMailBoxesForClean as $strEmail => $intDays) {
    try{
        //    
        $service = $objGmailAPI->getService($strEmail);
        //       
        $arrParams = array('before' => date('Y/m/d', (time() - 60 * 60 * 24 * $intDays)));
        //   ,   
        $arrIDs = $objGmailAPI->listMessageIDs($service,$strEmail,$arrParams);
        //     ID   $arrIDs
        if (count($arrIDs)) $objGmailAPI->deleteMessages($service,$strEmail,$arrIDs);
        //    
        unset($service,$arrIDs);
    }catch (Exception $e) {
        $arrErrors[] = $e->getMessage();
    }
}

if (count($arrErrors)){
    $strTo = 'my_email@domain.com';
    $strSubj = '       ';
    $strMessage = '         :'.
        '<ul><li>'.implode('</li><li>',$arrErrors).'</li></ul>'.
        '<br/>URL: '.filter_input(INPUT_SERVER, 'REQUEST_URI', FILTER_SANITIZE_URL);
    $objMailSender = new mailSender();
    $objMailSender->sendMail($strTo,$strSubj,$strMessage);
}




Skrip terhubung ke setiap kotak surat yang ditentukan, memilih huruf lama dan menghapusnya.



Tugas membuat laporan untuk manajer tentang email yang tidak terkirim berdasarkan laporan otomatis diselesaikan dengan skrip berikut:



Memfilter dan menandai email
<?php
/*
 *    a@domain.com
 *      ,     : : mailer-daemon@googlemail.com
 *      .        ,   b@domain.com
 *   
 */
require __DIR__ .'/../general/config/config.php'; //   
require __DIR__ .'/../vendor/autoload.php'; //   

$strEmail = 'a@domain.com';
$strLabelID = 'Label_2399611988534712153'; //  reportProcessed -    

//  
$arrParams = array(
    'from' => 'mailer-daemon@googlemail.com', //       
    'in' => 'inbox', //  
    'after' => date('Y/m/d', (time() - 60 * 60 * 24)), //   
    'has' => 'nouserlabels' //  
);

$arrErrors = array(); //  
$objGmailAPI = new GmailAPI(); //     GMail
$arrClientEmails = array(); //    ,      

try{
    //    
    $service = $objGmailAPI->getService($strEmail);
    //         ,    
    $arrIDs = $objGmailAPI->listMessageIDs($service,$strEmail, $arrParams);
    //      'X-Failed-Recipients',    ,      
    if (count($arrIDs)){
        foreach ($arrIDs as $strMessageID){
            //   
            $objMessage = $objGmailAPI->getMessage($service,$strEmail,$strMessageID,'metadata',array('X-Failed-Recipients'));
            //  
            $arrHeaders = $objMessage->getPayload()->getHeaders();
            //  
            foreach ($arrHeaders as $objMessagePartHeader){
                if ($objMessagePartHeader->getName() == 'X-Failed-Recipients'){
                    $strClientEmail = mb_strtolower(trim($objMessagePartHeader->getValue()), 'UTF-8');
                    if (!empty($strClientEmail)) {
                        if (!in_array($strClientEmail, $arrClientEmails)) $arrClientEmails[] = $strClientEmail;
                    }
                    //    reportProcessed,       
                    $objGmailAPI->modifyLabels($service,$strEmail,$strMessageID,array($strLabelID));
                }
            }
        }
    }
    unset($service,$arrIDs,$strMessageID);
}catch (Exception $e) {
    $arrErrors[] = $e->getMessage();
}

//     ,      ,    
if (count($arrClientEmails)) {
    $objClients = new clients();
    //   email  
    $arrAllClientsEmails = $objClients->getAllEmails();

    foreach ($arrClientEmails as $strClientEmail){
        $arrUsages = array();
        foreach ($arrAllClientsEmails as $arrRow){
            if (strpos($arrRow['email'], $strClientEmail) !== false) {
                $arrUsages[] = '  email  "<a href="'.MANAGEURL.'?m=admin&sm=clients&edit='.$arrRow['s_users_id'].'">'.$arrRow['name'].'</a>"';
            }
            if (strpos($arrRow['email2'], $strClientEmail) !== false) {
                $arrUsages[] = '  email  "<a href="'.MANAGEURL.'?m=admin&sm=clients&edit='.$arrRow['s_users_id'].'">'.$arrRow['name'].'</a>"';
            }
            if (strpos($arrRow['site_user_settings_contact_email'], $strClientEmail) !== false) {
                $arrUsages[] = '  email  "<a href="'.MANAGEURL.'?m=admin&sm=clients&edit='.$arrRow['s_users_id'].'">'.$arrRow['name'].'</a>"';
            }
        }
        $intUsagesCnt = count($arrUsages);
        if ($intUsagesCnt > 0){
            $strMessage = '          <span style="color: #000099;">'.$strClientEmail.'</span><br/>
                  ';
            if ($intUsagesCnt == 1){
                $strMessage .= ' '.$arrUsages[0].'<br/>';
            }else{
                $strMessage .= ':<ul>';
                foreach ($arrUsages as $strUsage){
                    $strMessage .= '<li>'.$strUsage.'</li>';
                }
                $strMessage .= '</ul>';
            }
            $strMessage .= '<br/>,        .<br/><br/>
                    ,    ';
            if (empty($objMailSender)) $objMailSender = new mailSender();
            $objMailSender->sendMail('b@domain.com',' email ',$strMessage);
        }
    }
}

if (count($arrErrors)){
    $strTo = 'my_email@domain.com';
    $strSubj = '      ';
    $strMessage = '        :'.
        '<ul><li>'.implode('</li><li>',$arrErrors).'</li></ul>'.
        '<br/>URL: '.filter_input(INPUT_SERVER, 'REQUEST_URI', FILTER_SANITIZE_URL);
    if (empty($objMailSender)) $objMailSender = new mailSender();
    $objMailSender->sendMail($strTo,$strSubj,$strMessage);
}




Skrip ini, seperti yang pertama, menghubungkan ke kotak surat yang ditentukan, memilih huruf yang diperlukan darinya (melaporkan pesan yang tidak terkirim) tanpa bendera, menemukan di surat alamat email yang diusahakan untuk mengirim surat dan menandai surat ini dengan bendera "Diproses" ... Kemudian manipulasi dilakukan dengan alamat email yang ditemukan, akibatnya surat yang dapat dibaca manusia dibentuk kepada karyawan yang bertanggung jawab.



Sumber tersedia di GitHub .



Itu saja yang ingin saya sampaikan di artikel ini. Terima kasih sudah membaca! Jika kode saya menyengat Anda, putar saja spoilernya atau tulis komentar Anda - saya akan senang untuk kritik yang membangun.



All Articles