PostsNow
cover image for this post
30 October 2020

Membuat Twitter Bot dengan Node.js

Biasanya bot di Twitter adalah sebuah akun yang akan menulis sebuah tweet dalam jangka waktu tertentu atau jika memenuhi syarat kejadian tertentu. Kali ini saya akan membahas proses pembuatan bot saya, sebuah bot yang menghasilkan tweet berisi judul artikel Wikipedia yang dapat dinyanyikan dengan nada jingle Tepung Beras Rosebrand.

Ide Pembuatan

Awal mula tercetusnya ide untuk membuat bot ini dimulai dari kesadaran bahwa diri sendiri ini kadang terlalu dimanja dengan bekerja murni di sisi front-end saja. Pekerjaan front-end tersebut pun di sebuah codebase yang sudah terbentuk. Dari sinilah terbersit keinginan untuk kembali merasakan menciptakan sesuatu dari awal. Selain itu, saya juga kadang menikmati cuitan dari akun ini, sehingga saya juga terinspirasi untuk membuat hal serupa.

Karena sumber datanya berasal dari judul-judul artikel Wikipedia, maka sebagai awalan, kita harus mendapatkan judul-judul tersebut.

Pengambilan Judul Artikel Wikipedia

Untuk mendapatkan judul-judul artikel di Wikipedia, saya menggunakan bantuan dari sebuah library bernama WikiJs.

const wiki = require("wikijs");

async function findMatchedTitle() {
  return wiki({ apiUrl: "https://id.wikipedia.org/w/api.php" })
    .random(10)
    ...
}

Pertama-tama kita perlu mendefinisikan bahwa sumber data kita adalah Wikipedia dalam Bahasa Indonesia. Di sini saya mengambil 10 artikel secara acak. Dari judul-judul ini nantinya akan dihitung jumlah suku katanya agar sesuai untuk dinyanyikan pada jingle Rosebrand. Apabila ditemukan sebuah judul yang memenuhi syarat tersebut, maka judul ini akan langsung terpilih dan judul lain yang belum dihitung suku katanya akan diabaikan. Jika semua 10 judul yang terambil ini tidak memenuhi syarat, ulangi kembali dari proses pengambilan acak 10 judul artikel Wikipedia.

Penghitungan Suku Kata

Sebelum proses perhitungan suku kata dimulai, perlu diberikan batasan pada judul yang akan dihitung. Judul yang tidak akan kita proses adalah judul yang mengandung angka.

Mengapa judul yang mengandung angka dihindari?

Karena pengucapan angka di dalam judul tersebut akan sangat beragam macamnya tergantung konteks dari judul tersebut. Misalkan ada angka 108, cara pembacaan angka tersebut akan sangat bervariasi seperti misalnya "seratus delapan", "satu nol delapan", "satu kosong lapan", "sepuluh delapan", dll. Dari sinilah maka batasan ini diperlukan demi konsistensi dan mengurangi ketidakcocokan perhitungan.

Membedakan Huruf Vokal dan Huruf Konsonan

Agar dapat memulai logika perhitungan suku kata, maka diperlukan juga logika untuk membedakan huruf vokal dan huruf konsonan. Tentunya hal ini dapat dicapai dengan mengunakan regular expression (regex).

const vocal = ["a", "i", "u", "e", "o"];
function isVocal(char) {
  return vocal.includes(char.toLowerCase());
}

Cukup simpel bukan?

Oh tidak, ternyata kenyataannya tidak semudah itu.

Kadang pada judul artikel terdapat huruf vokal yang mengandung tanda diakritik. Apakah tanda diakritik ini? Saya yakin banyak yang sebenarnya tahu tapi tidak tahu bahwa namanya adalah tanda diakritik (Saya pun baru tahu namanya ketika mengerjakan hal ini).

Tanda diakritik adalah tanda baca tambahan pada sebuah huruf yang mengubah cara baca dari huruf tersebut. Beberapa contohnya adalah: à, é, ö, ê, dan masih banyak lagi.

Karena huruf dengan tanda diakritik ini tidak termasuk dalam aturan regex kita, maka huruf tersebut tidak dianggap sebagai huruf vokal. Dengan banyaknya kombinasi dari banyak huruf dan banyak tanda diakritik, akan sangat merepotkan apabila harus mendaftarkan semua kombinasi tersebut pada aturan regex huruf vokal.

Untungnya saya menemukan cara untuk "membersihkan" judul artikel tersebut dari tanda diakritik dan mengubahnya menjadi huruf vokal biasa:

let word = "Crème brûlée";
word.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
console.log(word); // Creme brulee

Logika Penghitungan Suku Kata

Bagi komputer, judul artikel hanyalah barisan karakter. Kode kita tidak akan dapat menghitung suku kata apabila kita tidak menentukan bagaimana sebuah suku kata dapat terbentuk. Jadi bagaimana logikanya sehingga sekumpulan karakter dapat dinyatakan menjadi sebuah suku kata?

Apabila dalam sekumpulan karakter terdapat huruf vokal yang diikuti dengan huruf konsonan

Dalam aturan ini, kita akan menyusun suku kata dari huruf yang kita ambil satu persatu. Setelah mengambil huruf pertama, kita akan mengecek huruf selanjutnya dan mencocokan dengan huruf terakhir yang kita punya.

Jika huruf terakhir yang kita punya adalah huruf vokal dan huruf selanjutnya juga huruf vokal, maka ini masih belum terhitung sebagai akhir sebuah suku kata dan masih kita lanjutkan ke huruf berikutnya.

Jika huruf terakhir yang kita punya adalah huruf vokal dan huruf selanjutnya adalah huruf konsonan, maka ini dianggap sebagai akhir dari sebuah suku kata. Kita tambahkan 1 pada total penjumlahan suku kata kita dan kita lanjut dengan perhitungan suku kata baru dengan huruf selanjutnya. Proses ini berulang hingga huruf habis, jumlah suku kata sudah pasti melebihi jumlah yang kita tentukan, atau apabila jumlah suku kata sudah memenuhi jumlahnya.

Kita ambil contoh dengan kata jerapah. Dalam aturan bahasa yang benar, suku katanya adalah je-ra-pah. Namun dengan aturan ini, maka pemisahan perhitungan suku katanya menjadi jer-ap-ah, 3 suku kata. Tidak sempurna, namun mendekati konsep asli.

Apabila menemui karakter non-alfabet

Karakter non-alfabet di sini tidak termasuk angka karena kita sudah mengeliminasi judul yang mengandung angka. Karakter-karakter yang mungkin ditemui adalah spasi, titik, koma, tanda strip, dan tanda baca lainnya. Maka jika belum menemui huruf konsonan, tapi menemukan karakter-karakter tersebut, maka dapat diartikan bahwa kita sudah mencapai akhir dari sebuah suku kata.

Apabila ada "aa"

Bahasa Indonesia mengenal akhiran "-an" yang biasanya dibaca sebagai sebuah suku kata tersendiri.

Apabila mengikuti aturan pertama yang harus menemui huruf konsonan atau tanda baca terlebih dahulu baru terhitung sebagai satu suku kata, maka perhitungannya akan menjadi kacau seperti ini:

  • Perasaan: per - as - aan (3 suku kata)
  • Bersamaan: ber - sa - maan (3 suku kata)

Maka dibuatlah sebuah aturan khusus (atau bisa juga disebut hacky), apabila menemui 2 huruf "a" yang berjejeran, maka dihitung menjadi 2 suku kata.

Dari semua aturan suku kata tersebut, dapat kita gabungkan dalam sebuah kode menjadi:

const TARGET_SYLLABLE = 6;
function isMatchTargetSyllable(word) {
  if (numRegex.test(word)) {
    return false;
  }

  // menghilangkan tanda diakritik
  word.normalize("NFD").replace(/[\u0300-\u036f]/g, "");

  const wordLength = word.length;
  let syllableCount = 0;
  let syllable = "";

  for (let i = 0; i < wordLength; i++) {
    // jika sudah lebih dari target suku kata, abaikan
    if (syllableCount > TARGET_SYLLABLE) {
      return false;
    }

    // jika suku kata yang dihitung sedang kosong, isi dan lanjut
    if (syllable.length < 1) {
      syllable = word[i];
      continue;
    }

    const lastCharSyllable = syllable[syllable.length - 1];
    // jika menemukan non-alfabet, reset suku kata dan tambah jumlah 1
    if (!isAlphabet(word[i])) {
      if (isVocal(lastCharSyllable)) {
        syllableCount += 1;
      }
      syllable = "";
      continue;
    }

    if (!isVocal(lastCharSyllable) && !isVocal(word[i])) {
      syllable += word[i];
    } else if (!isVocal(lastCharSyllable) && isVocal(word[i])) {
      syllable += word[i];
    } else if (lastCharSyllable === "a" && word[i] === "a") {
      syllable = "";
      syllableCount += 2;
    } else {
      syllable = "";
      syllableCount += 1;
    }
  }

Setelah judul artikel yang memenuhi kriteria suku kata ditemukan, tidak lupa untuk juga mengambil tautan Wikipedia dari artikel tersebut supaya membuktikan bahwa artikel ini tidak mengada-ada dan benar adanya.

Membuat Gambar

Sebuah tweet tentunya akan lebih menarik perhatian apabila mengandung sebuah gambar. Gambar ini akan dibuat bernuansa menyerupai iklan Rosebrand sesungguhnya dan menyertakan judul artikel yang tadi kita temukan.

Persiapkan dulu gambar dasarnya. Gambar sawah diambil dari Unsplash, digabungkan dengan gambar Tepung Beras Rosebrand.

Sawah dan 2 bungkus tepung rosebrand

Gambar dasar yang nantinya akan ditambahkan judul artikel

Untuk menambahkan tulisan pada gambar dasar di atas, kita akan menggunakan library node-canvas yang API-nya mirip dengan Canvas API.

const fs = require("fs");
const { createCanvas, loadImage } = require("canvas");

module.exports = async function generateImage(text) {
  const canvas = createCanvas(500, 275);
  const context = canvas.getContext("2d");

  // gambar sawah sebagai dasar
  const sawah = await loadImage("./sawah.png");
  context.drawImage(sawah, 0, 0, 500, 275);

  // pasang logo rosebrand
  const logo = await loadImage("./rosebrand.png");
  context.drawImage(logo, 20, 10, 80, 60);

  // tuliskan judul artikel
  context.font = "bold 25px Arial";
  context.fillStyle = "red";
  context.textAlign = "left";
  context.fillText(text.toUpperCase(), 110, 50);

  // simpan gambar
  const buffer = canvas.toBuffer("image/png");
  fs.writeFileSync("./twit.png", buffer);
};

Setelah kelengkapan konten baik dari judul maupun gambar tersedia, saatnya mengirimkan konten tersebut sebagai sebuah tweet menggunakan Twitter API.

Twitter API

Twitter API memungkinkan kita untuk mengakses fitur-fitur Twitter dari kode program kita, baik untuk mengirim tweet, analisa data, maupun hal lain.

Persiapan Sebelum Dapat Menggunakan Twitter API

Untuk dapat mengakses Twitter API, masuk terlebih dahulu ke developer.twitter.com menggunakan akun bot, bukan akun personal.

Developer Twitter Website

Tampilan dari developer.twitter.com

Setelah berhasil masuk, buat app baru dan jangan lupa untuk mendapatkan 4 hal berikut:

  • API Key
  • API Secret
  • Access Token
  • Access Secret

Empat hal tersebut diperlukan sebagai autentikasi untuk mengakses API Twitter dan melakukan hal-hal yang dapat dilakukan di Twitter sebagai pengguna tersebut. Perlu diingat bahwa untuk Access Token dan Access Secret hanya dapat dilihat sekali saja ketika di-generate, maka isi dari dua hal ini perlu dicatat di tempat lain.

Twitter API Token

Tampilan dimana token-token dapat dilihat dan di-generate

Untuk mempermudah dalam pemanggilan Twitter API, kita akan menggunakan bantuan dari sebuah library bernama twitter-lite)

Mengirim Tweet Menggunakan API

Karena tweet yang akan kita kirim mengandung gambar, maka sebelum mengirim sebuah tweet kita perlu mengunggah gambar tersebut terlebih dahulu. Setelah gambar ini diunggah ke Twitter API, maka kita akan mendapatkan sebuah field bernama media_id. Field media_id dan isinya ini kemudian nanti kita sertakan di payload API untuk mengirim sebuah tweet.

Sebagai catatan, perlu diketahui juga bahwa Twitter API memiliki berbagai subdomain.

Sebagai contoh,

  • Endpoint untuk mengunggah gambar adalah https://upload.twitter.com/media/upload
  • Endpoint untuk mengirim tweet adalah https://api.twitter.com/statuses/update

Dapat terlihat bahwa origin dari API untuk mengunggah gambar adalah upload.twitter.com dan untuk mengirim tweet adalah api.twitter.com. Dari dokumentasi penggunaan twitter-lite, perbedaan subdomain ini perlu didefinisikan ketika menginisiasi sebuah client instance yang akan digunakan untuk mengakses API.

const Twitter = require("twitter-lite");

// definisikan subdomain ke upload untuk memanggil upload.twitter.com
const uploadClient = new Twitter({
  subdomain: "upload",
  consumer_key: process.env.CONSUMER_KEY,
  consumer_secret: process.env.CONSUMER_SECRET,
  access_token_key: process.env.ACCESS_TOKEN_KEY,
  access_token_secret: process.env.ACCESS_TOKEN_SECRET,
});

// subdomain default ke api.twitter.com
const client = new Twitter({
  consumer_key: process.env.CONSUMER_KEY,
  consumer_secret: process.env.CONSUMER_SECRET,
  access_token_key: process.env.ACCESS_TOKEN_KEY,
  access_token_secret: process.env.ACCESS_TOKEN_SECRET,
});

const image = fs.readFileSync("./twit.png", "base64");
try {
  const uploadResult = await uploadClient.post("media/upload", {
    media_data: image,
  });

  const tweet = text + "\n" + articleUrl;

  await client.post("statuses/update", {
    status: tweet,
    media_ids: uploadResult.media_id_string,
  });
} catch (error) {
  console.log("err", error);
}

Setelah selesai, kode lalu perlu di-host dan diberi scheduler agar bisa mengirimkan tweet dalam beberapa waktu tertentu.

Saya sendiri mencoba host kode bot ini di Heroku. Tidak ada alasan khusus selain gratis dan masih merasa ngeri menggunakan Amazon Web Service apalagi hanya untuk sebuah proyek iseng seperti ini. Untungnya juga di Heroku terdapat scheduler bawaan yang gratis sehingga saya tidak perlu membuatnya sendiri.

Kekurangan Bot

Karena ini sebuah bot iseng yang dibuat dalam waktu singkat tentu saja masih ada kekurangan dalam kinerjanya.

Yang paling kentara adalah tidak akurat dalam penghitungan jumlah suku katanya, sehingga kadang terasa tidak pas dinyanyikan di jinglenya. Hal ini disebabkan karena masih terambilnya judul artikel dalam bahasa asing walau sudah bersumber dari Wikipedia indonesia. Bahasa asing tersebut biasanya adalah nama ilmiah, nama orang, judul lagu, film, atau buku.

Ketidakakuratan ini bersumber pada perbedaan logika penghitungan suku kata pada bahasa lain:

  • Huruf vokal di Bahasa Indonesia hanya "a", "i", "u", "e", "o". Sementara misalnya pada Bahasa Inggris, huruf "y" dapat berlaku sebagai huruf vokal dan konsonan.
  • Perbedaan cara baca. Semisal ada kata "horse", cara bacanya yang benar dalam Bahasa Inggris adalah "hors" yang terhitung sebagai 1 suku kata sementara dengan logika saya menjadi "hor-se" yang terhitung sebagai 2 suku kata.

Di luar beberapa ketidaksesuaian jumlah suku kata, saya cukup senang ada salah satu tweet yang tampak sangat susah dibaca namun ternyata sesuai suku katanya. Ada salah seorang follower yang merekam pembacaannya sambil dinyanyikan.

Daftar tautan terkait: