(GAS-18) Bot Telegram dan GMail

12 menit saja |

Ingin membaca email (gmail) di Telegram? Yuk, kita bikin sendiri botnya!

Pendahuluan

Bacalah pendahuluan biar mengerti filosofi setiap tulisan dibuat :-D

Latar Belakang

Sudah ada @GmailBot buat apa membuat lagi?

Script cek email basicnya sudah aku buat jauh hari sebelum ada Gmail dilaunching. Namun, masih terbilang sederhana dan alakadarnya (saat itu fitur keyboard inline belum ada). Kemunculan Gmail bot, membuat pengecek emailku enggak “dipakai” lagi.

Dalam beberapa waktu ada dimana @GmailBot mengalami kemacetan alias engga bisa dipakai, script ini menjadi penting. Bahkan bisa berjalan bersamaan.

Selain itu, terkadang ada kebutuhan khusus yang tidak tersedia. Jadi dibutuhkan penanganan khusus atau fungsi yang custom. Misalnya handling notifikasi yang masuk ke email terkait notif atau warning ketika sistem monitoring masuk. Atau pembuatan push email bersama 1 devisi untuk dilihat rame-rame.

Maka, basic bot cek email (lihat, baca, operasi hapus, penanda, dll) masih sangat diperlukan. Dan tidak ada salahnya untuk mengshare versi “replika” atau mempelajari, bagaimana sebuah bot cek email bekerja, bukan?

Basic source code lama sudah banyak di modif, agar layak menjadi satu bot utuh dan berdiri sendiri. Serta ditambahkan fitur interkatif. Maka, materi kali ini sangat baik untuk dijadikan pengayaan dan menjadi percontohan terhadap banyak kasus dan proyek.

Ruang Lingkup

Tulisan ini akan membahas pembuatan bot untuk melakukan pengecekan email pada akun GMail. Operasi yang dapat dilakukan adalah:

  • pengecekkan berkala email
  • pengecekan manual
  • operasi baca ringkas dan penuh
  • operasi hapus email

Preview

New Email

ada email baru

Cek Email

cek manual

Bot juga per 10 menit melakukan pengecekkan otomatis.

Tingkat Kesulitan

Pada materi-materi sebelumnya, level kesulitan yang ada barangkali masih tergolong minimalis. Kali ini kita padukan sedikit banyak pengetahuan yang sudah-sudah ada. Kesulitan yang dihadapi relatif lebih sulit dibanding sebelumnya.

Penggabungan rangkaian fitur bot :

  • Fungsi Trigger / Pemicu
  • Keyboard Inline
  • Edit Pesan
  • Pengetahuan API Google Apps Scripts dan Telegram
  • Pemahaman tentang istilah thread (1 bundel yang berisi rangkaian beberapa pesan)
  • Peningkatan versi Library Telegram, tentang Utils dan Button
  • Proteksi / limitasi pengakses

Sehingga, artikel yang ini akan menjadi lebih sulit dan kompleks.

Selain itu juga, tidak semua hal akan saya komentari lagi. Agar tidak berulang-ulang, dan hanya komentar untuk hal-hal penting saja ya.

Code

Tulisan ini diberi kode Materi 18.

Ada beberapa step dan beberapa file script yang harus dibuat. Mari ikuti satu per satu.

I. Source Code

Struktur File

Kita akan membuat 4 buat file script.

  1. Code.gs ini file default, atau main file bot
  2. gmail.gs ini file fungsi-fungsi untuk gmail
  3. pemicu.gs ini file untuk pemicu (pengecekan email terjadwal)
  4. sekaliJalan.js untuk script yang dijalankan hanya 1x saja

Kira-kira seperti ini strukturnya:

Struktur Files

Karena ini termasuk bot yang source nya agak panjang.

Semoga ada gambarnyan sebelum memulainya.

I.1. Library

Seperti biasa, masukkan Library Telegram nya. Gunakan versi paling besar angkanya. Saat ditulis, sudah versi 23.

Menu: Sumber Daya -> Pustaka

ID Lib: MHczUHrzvBLV1HsUn5XkOIfvg_do21SJR

Tambah Library

Kalau belum tahu caranya menambah library, bisa lihat di dokumentasi atau materi sebelumnya.

I.2. Code

Pada halaman utama, atau sebagai engine utama bot. Yakni Kode.gs atau Code.gs tulis (copas) bot seperti biasanya. Tidak banyak perubahan, kecuali pada event cek /email atau handle callback data untuk keyboard inline.

Yang paling penting dalam pelajaran tulisan ini adalah, bagaimana mengedit sebuah pesan dan juga button inline.

// masukkan token bot mu
var token = 'TOKEN_BOT_KAMU';

// buat objek baru kita kasih nama tg
var tg = new telegram.daftar(token);

// buat variable untuk Button dan Utils
Utils = telegram.Utils;
Button = telegram.Button;

// target chat id, boleh ID user/group/channel
// karena isi email termasuk kredensial, maka perlu kita definisikan
var chat_id = 213567634;        // ini ID saya, silakan diganti!

// label untuk penanda
var gLabel= 'tg';               // contoh disini pakai label tg untuk Telegram

// panjang Char per pesan default
var panjangChar = 500;          // agar pertama dibaca tidak terlalu panjang

// --- codingan seperti biasa --

// fungsi buat handle hanya menerima pesan berupa POST, kalau GET keluarkan pesan error
function doGet(e) {
  return HtmlService.createHtmlOutput("Hanya data POST yang kita proses yak!");
}

// fungsi buat handle pesan POST
function doPost(e) {

  // Memastikan pesan yang diterima hanya dalam format JSON  
  if(e.postData.type == "application/json") {
    
    // Kita parsing data yang masuk
    var update = JSON.parse(e.postData.contents);
    
    // Jika data pesan update valid, kita proses
    if (update) {
      prosesPesan(update);
    }
  } 
}


// fungsi utama kita buat handle segala pesan
function prosesPesan(update) {
  
  // detek klo ada pesan dari user
  if (update.message) { 

    // penyederhanaan variable
    var msg = update.message;

    // jika ada pesan berupa text
    if (msg.text) {

      // jika user ketik /ping, bot akan jawab Pong!
      if ( /\/ping/i.exec(msg.text) ){
        return tg.kirimPesan(msg.chat.id, '<b>Pong!</b>', 'HTML');
      }
      
      // cek manual email
      if ( /\/email/i.exec(msg.text) ){
        // kita batasi yang boleh mengakses ini hanya user tertentu
        // cuekin aja orangnya, ga usah dikasih pesan error apa-apa.
        if (msg.chat.id !== chat_id) return;
        
        // panggil fungsi pemicuEmail, cek nanti di bagian Pemicu
        var adaEmail = pemiculEmail();
        if (!adaEmail) {
          return tg.kirimPesan(msg.chat.id, '✅ Email sudah terbaca semua.');
        } else {
          return;
        }
      }

      // akhir deteksi pesan text
    }

  }
  
   // proses buat handle callback
    if (update.callback_query) {
    
      // penyederhanaan variable
      var cb = update.callback_query;      
      var msg = cb.message;
      
      // deteksi jika ada cb (callback) data untuk menciutkan
      var pola = /^ciutkan_(\w+)/i;      
      if ( pola.exec(cb.data) ){
        
        var cocok = cb.data.match(pola);
        var gID = cocok[1];
        
        // ambil pesan Gmail berdasarkan ID nya
        var pesan = gmail.getMessage(gID);
        
        // susun format data buat dikirim
        var data = {
          chat_id: msg.chat.id,
          message_id: msg.message_id,
          text: pesan,
          parse_mode: 'HTML',
          reply_markup: msg.reply_markup
        };
        
        // ganti tombol dan isi callback data nya
        data.reply_markup.inline_keyboard[1][0].text = "📖 Baca Lebih";
        data.reply_markup.inline_keyboard[1][0].callback_data = 'readMore_'+gID;
        
        // edit pesan 
        return tg.request('editMessageText',data);        
      }
      
      // deteksi jika ada cb (callback) data untuk membaca lebih panjang
      var pola = /^readMore_(\w+)/i;      
      if ( pola.exec(cb.data) ){
        
        var cocok = cb.data.match(pola);
        var gID = cocok[1];
        
        // ambil pesan Gmail berdasarkan ID nya
        var pesan = gmail.getMessage(gID, true);
        
        // susun format data buat dikirim
        var data = {
          chat_id: msg.chat.id,
          message_id: msg.message_id,
          text: pesan,
          parse_mode: 'HTML',
          reply_markup: msg.reply_markup
        };
        
        // ganti tombol dan isi callback data nya
        data.reply_markup.inline_keyboard[1][0].text = "🗞 Ciutkan";
        data.reply_markup.inline_keyboard[1][0].callback_data = "ciutkan_"+gID;
        
        return tg.request('editMessageText',data);
        // tg.kirimPesan(msg.chat.id, pesan);
        
      }
    
      var pola = /^markRead_(\w+)/i;
      if ( pola.exec(cb.data) ){
        var cocok = cb.data.match(pola);
        var gID = cocok[1];

        // susun format data buat dikirim
        var data = {
          chat_id: msg.chat.id,
          message_id: msg.message_id,
          text: msg.text,
          reply_markup: msg.reply_markup
        };
        
        // ganti tombol dan isi callback data nya
        data.reply_markup.inline_keyboard[0][0].text = "✅ Terbaca";
        data.reply_markup.inline_keyboard[0][0].callback_data = "markUnread_"+gID;
        
        // tandai email Terbaca
        gmail.markRead(gID);
        
        // edit pesan dan tombolnya
        tg.request('editMessageText',data);

        // kasih notif sudah dikerjakan
        return tg.request('answerCallbackQuery', { callback_query_id: cb.id, text: "✅ Pesan telah ditandai terbaca." });
      }
      
      var pola = /^markUnread_(\w+)/i;
      if ( pola.exec(cb.data) ){
        var cocok = cb.data.match(pola);
        var gID = cocok[1];

        // susun format data buat dikirim
        var data = {
          chat_id: msg.chat.id,
          message_id: msg.message_id,
          text: msg.text,
          reply_markup: msg.reply_markup
        };
        
        // ganti tombol dan isi callback data nya
        data.reply_markup.inline_keyboard[0][0].text = "☑️ Baca";
        data.reply_markup.inline_keyboard[0][0].callback_data = "markRead_"+gID;
        
        // tandai email BELUM dibaca
        gmail.markUnread(gID);

        // edit pesan dan tombolnya
        tg.request('editMessageText',data);
        
        // kasih notif sudah dikerjakan
        return tg.request('answerCallbackQuery', { callback_query_id: cb.id, text: "☑️ Tandai BELUM dibaca." });
      }
      
      var pola = /^moveToTrash_(\w+)/i;
      if ( pola.exec(cb.data) ){
        var cocok = cb.data.match(pola);
        var gID = cocok[1];
        
        // tambahkan pesan dibawahnya, buat tanda kalau sudah dihapus di GMAIL
        var pesan = gmail.getMessage(gID) + "\n\n<code>(DI GMAIL: PESAN INI TELAH DIHAPUS)</code>";
        
        // susun format data buat dikirim
        var data = {
          chat_id: msg.chat.id,
          message_id: msg.message_id,
          text: pesan,
          parse_mode: 'HTML'
        }
  
        // hapus emailnya
        gmail.moveToTrash(gID);

        // edit pesan dan tombolnya
        tg.request('editMessageText',data);

        // kasih notif sudah dikerjakan
        return tg.request('answerCallbackQuery', { callback_query_id: cb.id, text:'🗑 Pesan Telah DIHAPUS.' });
      }
    
    }

}

I.3. Fungsi Gmail

Buat sebuah file script baru (File -> Baru -> File Skrip) kemudian kita isikan sebuah variable yang menampung rangkaian fungsi gmail:

New File Script

Beri nama: gmail. Isikan source codenya seperti berikut:

// buat variable global dengan nama gmail
// untuk menampung fungsi-fungsi berkenaan dengan email
var gmail = {  
  
  getMessage: function(id, full) {
    
    // atur panjang Pesan
    // panjangChar = kita letakkan nanti di file script utama (bot/Code.gs)
    var panjangPesan = panjangChar;    
    if (full) panjangPesan = 3500;
    
    // ambil pesan berdasarkan ID nya
    var message = GmailApp.getMessageById(id);
    
    var gSubject = message.getSubject();
    gSubject = Utils.clearHTML(gSubject);
    
    // dapatkan pengirimnya
    var gFrom = message.getFrom();
    // trus kita bersihkan dari tags HTML
    gFrom = Utils.clearHTML(gFrom);
    
    // dapatkan waktunya
    var gDate = message.getDate();
    gDate = Utils.clearHTML( String(gDate));
    
    // ambil isi email dalam mode text plain aja
    var gMessage = message.getPlainBody();

    // potong panjangnya sesuai parameter
    gMessage = gMessage.substring(0, panjangPesan)

    // bersihkan dari tag HTML
    gMessage = Utils.clearHTML(gMessage);
    
    // susun pesannya
    var pesan = '' + gFrom + "\n📝 <b>" + gSubject + "</b>\n";
    pesan += '⏱ <code>' + gDate + '</code>';
    pesan += "\n\n"+gMessage;
    
    // jika pesan kepotong, kasih informasi
    if (gMessage.length > panjangPesan) pesan += "...\n(dipotong)";
    
    // jika pesan ada attach nya, kasih informasi
    var gAttach = message.getAttachments().length;
    if (gAttach>0) pesan += "\n\n🗂 Lampiran: <code>"+gAttach+ "</code> buah.";
    
    // kembalikan fungsi dengan pesan yang disusun
    return pesan;
  },
  
  // fungsi untuk menandai Read
  markRead : function (id) {
    var message = GmailApp.getMessageById(id);
    return message.markRead();
  },
  
  // fungsi untuk menandai unRead
  markUnread : function (id) {
    var message = GmailApp.getMessageById(id);
    return message.markUnread();
  },
  
  // fungsi untuk mengambil isi email berupa text plain
  getPlainBody : function (id) {
    var message = GmailApp.getMessageById(id);
    return message.getPlainBody();
  },
  
  // fungsi untuk membuang email ke tong sampah
  moveToTrash : function (id) {
    var message = GmailApp.getMessageById(id);
    return message.moveToTrash();    
  },  
  
  // fungsi untuk membuat label
  createLabel : function (label) {
    return GmailApp.createLabel(label);
  },
  
  // fungsi untuk menghapus label
  deleteLabel: function (label) {
    var labelID = GmailApp.getUserLabelByName(label);
    return GmailApp.deleteLabel(labelID);
  }
}

I.4. Pemicu / Trigger

Sama seperti diatas, buat file script baru dengan nama pemicu.

Kemudian masukkan script ini:

// fungsi buat pemicu email unread
function pemiculEmail() {
  // ambil dulu label yang tersedia
  var label = GmailApp.getUserLabelByName(gLabel);
  
  // buat threads untuk mengambil pesan yang belum dibaca dan tidak ditandai label
  // ambil satu batch 10 buah aja
  var threads = GmailApp.search('label:unread NOT label:'+gLabel, 0, 10);
  
  // jika tidak ada pesannya, udah pulang aja ga usah dilanjutkan
  if (threads.length<1) return false;
  
  // ambil semua pesannya
  for (var i = 0; i < threads.length; i++) {
    
    // dapatkan pesannya, ambil yang paling atas (terbaru) = index ke-0
    var message = threads[i].getMessages()[0];
    
    // dapatkan pengirimnya
    var gFrom = message.getFrom();
    // trus kita bersihkan dari tags HTML
    gFrom = Utils.clearHTML(gFrom);
    
    // dapatkan waktunya
    var gDate = message.getDate();
    gDate = Utils.clearHTML( String(gDate));
    
    // dapatkan subject email
    // karena bisa jadi ada perubahan saat reply, ambil yang paling atas aja lah ya
    var gSubject = threads[i].getFirstMessageSubject();    
    gSubject = Utils.clearHTML(gSubject);
    
    // dapatkan ID message nya
    var gID = message.getId();
    
    // dapatkan isi pesannya
    // ambil yang text plain aja ya, bukan HTML. Biar ga ribet ngolahnya
    // klo mau diolah silakan di modifikasi sendiri
    var gMessage = message.getPlainBody();
    gMessage = Utils.clearHTML(gMessage);
    
    // untuk ISI nya attachment, ga dibahas dulu
    // silakan modif sendiri hehe
    // tampilkan ada gak nya duank aja ya
    var gAttach = message.getAttachments().length;
    
    // oke itu aja, yuk kita susun pesannya
    var pesan = '' + gFrom + "\n📝 <b>" + gSubject + "</b>\n";
    pesan += '⏱ <code>' + gDate + '</code>';
    
    // isi email kita potong klo lebih dari panjangChar
    pesan += "\n\n" + gMessage.substring(0, panjangChar)   
    
    // sesudah ditampakkin, tandai label nya
    // biar ga kebaca ulang
    threads[i].addLabel(label);
    
    // kirim pesannya ke Telegram
    // tg.kirimPesan(chat_id, pesan, 'HTML', true);
    
    // kirim pesan dengan menu (keyboard inline)
    var keyboard = [];
    
    //buat barisan (row) keyboard
    // 1 baris diisi 2 button
    var kBaris = [
      Button.inline('☑️ Baca','markRead_'+gID),
      Button.inline('🗑 Hapus','moveToTrash_'+gID)
    ];
    
    // masukkan baris ke keyboard
    keyboard.push(kBaris);
    
    // kalau pesan puanjang, tambahkan tombol baca lebih
    if (gMessage.length > panjangChar) {
      pesan += "... (dipotong)";

      // 1 baris 1 button saja
      kBaris = [ 
        Button.inline('📖 Baca Lebih','readMore_'+gID)
      ];
      keyboard.push(kBaris);

      // dari sini ada pelajaran baru? membuat button di keyboard per baris ya
    }
    
    // informasi ada lampiran
    if (gAttach>0) pesan += "\n\n🗂 Lampiran: <code>"+gAttach+ "</code> buah.";
    
    // semua pesan sudah oke? baru dikirim 
    tg.sendMsgKeyboardInline(chat_id, pesan, keyboard);    
    
    // sebelum mengulang ke thread berikutnya, kasih sedikit jeda biar ga flooding
    // 1 detik saja cukup
    Utilities.sleep(1000);
    
  }
  
  // hasil kasih true (sukses)
  return true;
  
}

Silakan di deploy 3 script + library di atas. Dan dapatkan web App URL nya untuk dipasangkan di webhook.

Kalau lupa cara deploy, silakan baca materi sebelumnya.

II. Sekali Jalan

Buat sekali lagi file script baru, misal namanya sekaliJalan. Karena script ini memang hanya sekali saja dijalankan.

II.1 setWebhook

function setWebhook() {
  // ubah web_app_URL yang di dapat saat Deploy  
  var url = 'web_app_URL';
  return tg.setWebhook(url);
}

Simpan dan kemudian jalankan.

set Webhook

II.2 Buat Label

Fungsi ini buat bikin label penanda.

Ingat, jalankan sekali saja.

function buatLabel() {
  return gmail.createLabel(gLabel);
}

create Label Mail

Silakan di cek di akun gmailnya, seharusnya ada label baru bernama tg seperti dibawah ini:

Manage Label

Test Run

Sampai di sini, bot sudah bisa berjalan dengan pemicu manual. Yakni di dalam bot ketik /email maka akan menampilkan email yang belum terbaca.

Cek Email

III. Pemicu

Klik tombol pemicu yang berbentuk seperti icon jam.

Tombol Pemicu

Kemudian klik tombol Tambahkan Pemicu untuk membuat pemicu (trigger) baru.

Pemicu Email

Keterangan gambar: perhatikan baik-baik yang dikotaki merah

Atur pada form pemicu menjadi seperti ini:

  • Pilih fungsi yang dijalankan: pemicuEmail
  • Pilih penerapan mana yang harus dijalankan: Head (biarkan saja)
  • Pilih sumber acara: Dipicu oleh waktu
  • Pilih jenis pemicu berdasarkan waktu: timer menit (bisa diubah perjam, jika ingin mengecek lebih jarang)
  • Pilih interval meni: Setiap 10 menit (bisa diubah lebih cepat atau lebih lama. Saran tidak terlalu cepat)

Kemudian klik tombol Simpan.

Dengan demikian, email akan dicek otomatis per 10 menit oleh bot. Jika ada yang baru, akan dikirimkan ke kita.

FYI. Jangan menyeting timer (pemicu) nya per (1) menit sekali. Dikhawatirkan jatah quota API kita habis dan atau keburu timeout dan bisa juga bertumpuk.

SELESAI!

Youtube

Akan diupdate jika sudah ada video nya.

Penutup

Jika ada pertanyaan, saran atau masukkan silakan didiskusikan. Jika ingin live dan biasanya tanggapan juga lebih cepat, sangat disarankan bergabung pada group Telegram @botIndonesia.