Skip to content

Gemini ile Haberleri Özetleyin ve Gmail Adresinize Otomatik Mail Olarak Gönderin

Günlük haber özetlerini okumak istiyordum ve Google Gemini, Google Apps Script ve Gmail ile bir otomasyon oluşturdum. Ve bu tamamen ücretsiz!

İhtiyaçlarımız bizi çözümlere götürür. Bir yazılım mühendisi olarak, sorunlarımıza çözüm bulmak için dünyayı her zaman gözlemlerim. İşte benim sorunlarımdan biri: Günlük haberleri okumak ve çok fazla zaman kaybetmeden İngilizcemi geliştirmek istiyordum.

Sorunu Netleştirme

Haber sitelerinde RSS özelliği bulunur. Bu RSS sayfalarını kullanarak günlük haberleri alabiliriz. Doğrudan çözümler oluşturmaya çalışmadan önce büyük dil modelleriyle (LLM) beyin fırtınası yapmayı severim. Gemini'ye gönderdiğim ilk ve kısa komut şuydu:

Haberleri özetleyen ve bana e-posta ile gönderen bir otomasyon oluşturmak istiyorum. Bunun faydası olarak hem İngilizcemi geliştireceğim hem de haberleri okuyacağım. Google Gemini, Gmail ve gerekirse Python kullanmak istiyorum. Haydi beyin fırtınası yapalım.

Gemini, ana bileşenler, yaklaşımlar ve tavsiyeler üretti. Ben genellikle Python programlama diliyle çalışıyorum. Çalışması kolay olsa da, bir dağıtım (deployment) gerektirecekti. Python olmadan denemeye karar verdim ve daha önce kullandığım Google Apps Script'i kullanmayı düşündüm. Tekrar komut verdim:

Bunu sadece Gemini ve Gmail ile yapmak mümkün mü? Belki de Apps Script de kullanılabilir?

Beklediğim cevabı aldım! Evet, elbette mümkün.

Google Apps Script'te uzman değilim ve harici URL'lere istek gönderebildiğimizi bilmiyordum. Gemini'den ana fonksiyonları istedim.

summarizeWithGemini(text), getNewsSummaries(), extractTextFromHtml(html), sendDailyDigest() kullanacağımız fonksiyonlar olacak.

Google AI Studio'dan API Anahtarı Alma

Google Gemini hizmetini kullanmak için bir API anahtarına ihtiyacımız var. https://aistudio.google.com/ adresini ziyaret ettim, "Get API Key" (API Anahtarı Al) butonuna ve ardından "Create API Key" (API Anahtarı Oluştur) butonuna tıkladım. "Mevcut Google Cloud projelerinizden bir proje seçin, Google Cloud projelerinde arama yapın" şeklinde bir açılır pencere belirecek. Google Cloud'daki projelerinizden birini seçebilirsiniz. Eğer bir projeniz yoksa, https://console.cloud.google.com/projectcreate bağlantısını takip ederek bir tane oluşturabilirsiniz.

API anahtarınızı oluşturduktan sonra kopyalayın ve güvenli bir şekilde sakladığınızdan emin olun.

Google Apps Script Oluşturma

Google, bazı görevleri otomatikleştirmek için JavaScript kullanabileceğimiz bir Apps Script sunar. Örneğin, Google Docs, Sheets'e erişebilir, verileri işleyebilir ve Gmail ile e-posta gönderebiliriz.

https://script.google.com/ adresine gidin ve yeni bir script oluşturmak için "New project" (Yeni proje) butonuna tıklayın.

Burada fonksyionları tanımlayıp çalıştırabiliriz. Google Apps Script'in, GmailApp, UrlFetchApp, XmlService, Utilities gibi kullanılabilecek bazı yerleşik sınıfları vardır.

Fonksiyonları Tanımlama

summarizeWithGemini

  • Aşağıda gemini-2.0-flash kullanıyorum, ihtiyacınıza göre modeli değiştirebilirsiniz.
/**
 * Sağlanan metni Gemini API'yi kullanarak özetler.
 * @param {string} text Makalenin özetlenecek metni.
 * @return {string} Özetlenmiş metin.
 */
function summarizeWithGemini(text) {
  const GEMINI_API_KEY = PropertiesService.getScriptProperties().getProperty("GEMINI_API_KEY");
  const endpoint = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${GEMINI_API_KEY}`;

  const prompt = `Aşağıdaki haber makalesini 200 kelimeyi aşmayan tek bir paragrafta özetle. Ana olaylara ve temel gerçeklere odaklan. Makale: \n\n${text}`;

  const requestBody = {
    contents: [
      {
        parts: [{ text: prompt }],
      },
    ],
  };

  const options = {
    method: "post",
    contentType: "application/json",
    payload: JSON.stringify(requestBody),
  };

  try {
    const response = UrlFetchApp.fetch(endpoint, options);
    const data = JSON.parse(response.getContentText());

    // Yanıtın, parçaları olan bir aday içerip içermediğini kontrol et
    if (
      data.candidates &&
      data.candidates.length > 0 &&
      data.candidates[0].content.parts.length > 0
    ) {
      return data.candidates[0].content.parts[0].text;
    } else {
      console.error(
        "Gemini API yanıtı geçerli bir özet içermiyor.",
        data
      );
      return "Bir özet oluşturulamadı.";
    }
  } catch (e) {
    console.error("Gemini API çağrısında hata: " + e.message);
    return "Bir hata nedeniyle özet oluşturulamadı.";
  }
}
Gemini API Anahtarını Özelliklere Kaydetme
  • API anahtarını özelliklere kaydetmemiz gerekiyor.

scipts.google.com sayfasında sol kenar çubuğunda "Project settings" (Proje ayarları) bulunur. Ayarları açmak için tıklayın ve sayfanın sonunda "Script Properties" (Script Özellikleri) bulunur. Add Script Property (Script Özelliği Ekle) butonuna tıklayın.

2 adet giriş kutusu vardır. Özellik adı GEMINI_API_KEY ve değeri sizin API anahtarınızdır (buraya kopyalayıp yapıştırın).

Script özelliklerini kaydedin ve (sol kenar çubuğunda görebilirsiniz) tıklayarak editöre geri dönün.

getNewsSummaries

  • MAX_NEWS_AMOUNT AI'nın özetlemesini istediğim haber sayısıdır. Bu önemlidir, çünkü AI API limitleri sorun olabilir.
  • SLEEP_SECONDS gecikme içindir çünkü AI API kotası 15'tir (dakika başına istek).
  • Yukarıdaki değerleri ihtiyacınıza göre değiştirebilirsiniz.
  • rssFeeds haber sitelerinin nesnelerini (isim ve url özelliği ile) içerir.
/**
 * Bir RSS beslemesinden haberleri çeker, her makaleyi özetler ve sonuçları döndürür.
 * @return {Array<Object>} Her biri bir makalenin başlığını, bağlantısını ve özetini içeren nesnelerden oluşan bir dizi.
 */
function getNewsSummaries() {
  const rssFeeds = [
    { name: "Adnan Kaya", url: "https://www.example.com.tr/en/rss/"},
    // daha fazla haber sitesi rss'i ekle
  ];

  let summaries = [];
  const MAX_NEWS_AMOUNT = 12; // burada değiştirebilirsiniz
  const SLEEP_SECONDS = 4000; // burada değiştirebilirsiniz

  for (const feed of rssFeeds) {
    const rssUrl = feed.url;
    const sourceName = feed.name;
    try {
      Logger.log(`------- ${rssUrl} için işleniyor -------`);

      const response = UrlFetchApp.fetch(rssUrl);
      if (response.getResponseCode() !== 200) {
        throw new Error(
          `URL çekilirken hata oluştu, durum kodu: ${response.getResponseCode()}`
        );
      }

      const xml = response.getContentText();
      const document = XmlService.parse(xml);
      const root = document.getRootElement();

      if (!root) {
        throw new Error("Kök öğe boş. Geçersiz XML yapısı.");
      }

      let items = [];
      let namespace = null;
      let titleElement = "title";
      let linkElement = "link";

      const rootName = root.getName();

      // Besleme formatını belirle ve ayrıştırma mantığını ayarla
      if (rootName === "rss") {
        // RSS 2.0 formatı (en yaygın)
        namespace = root.getNamespace();
        const channel = root.getChild("channel", namespace);
        if (channel) {
          items = channel.getChildren("item", namespace);
        }
      } else if (rootName === "feed") {
        // Atom formatı
        namespace = root.getNamespace();
        items = root.getChildren("entry", namespace);
        titleElement = "title";
        linkElement = "link";
      } else if (rootName === "RDF") {
        // RSS 1.0 (RDF) formatı
        const rss1Namespace = XmlService.getNamespace(
          "http://purl.org/rss/1.0/"
        );
        items = root.getChildren("item", rss1Namespace);
        // Başlık/bağlantı çıkarma için namespace'i RSS 1.0 namespace'ine ayarla
        namespace = rss1Namespace;
      } else {
        throw new Error(`Desteklenmeyen RSS feed formatı: ${rssUrl}`);
      }

      if (items.length === 0) {
        Logger.log(`Feed'de item bulunamadı: ${rssUrl}`);
        continue;
      }

      // Makale sayısını sınırla
      for (let i = 0; i < Math.min(items.length, MAX_NEWS_AMOUNT); i++) {
        try {
          const item = items[i];
          const title = item.getChildText(titleElement, namespace);

          let link = item.getChildText(linkElement, namespace);
          const pubDate =
            item.getChildText("pubDate", namespace) ||
            item.getChildText("updated", namespace);

          // Bağlantının bir öznitelik olarak bulunduğu Atom link formatını ele al
          if (!link && rootName === "feed") {
            const linkElementNode = item.getChild(linkElement, namespace);
            if (linkElementNode) {
              link = linkElementNode.getAttribute("href")?.getValue();
            }
          }

          if (!link) {
            throw new Error("Makale bağlantısı boş veya eksik.");
          }

          const articleHtml = UrlFetchApp.fetch(link).getContentText();
          const articleText = extractTextFromHtml(articleHtml);

          if (!articleText || articleText.trim() === "") {
            throw new Error("Çıkarılan makale metni boş.");
          }

          // summarizeWithGemini'nin başka bir yerde tanımladığınız bir işlev olduğunu varsayalım
          const summary = summarizeWithGemini(articleText);

          summaries.push({
            title: title,
            link: link,
            summary: summary,
            source: sourceName,
            pubDate: pubDate,
          });

          // API kotası dahilinde kalmak için bir bekleme işlevi ekle
          Utilities.sleep(SLEEP_SECONDS);

        } catch (e) {
          console.error(
            `${rssUrl} adresinden makale işlenirken hata oluştu: ${e.message}`
          );
          continue;
        }
      }
    } catch (e) {
      console.error(`${rssUrl} RSS beslemesi işlenirken hata oluştu: ${e.message}`);
    }
  }

  return summaries;
}

extractTextFromHtml

/**
 * HTML'den ana makale metnini çıkarmayı deneyen temel bir işlev.
 * Bu, belirli web siteleri için özelleştirme gerektirebilir.
 * @param {string} html Web sayfasının HTML içeriği.
 * @return {string} Çıkarılan metin.
 */
function extractTextFromHtml(html) {
  // Yaygın etiketler içindeki ana makale içeriğini bulmak için bir düzenli ifade kullanır
  // Bu çok kaba bir yaklaşımdır ancak bazı siteler için işe yarayabilir.
  const regex = /<p[^>]*>(.*?)<\/p>/g;
  let matches = [];
  let match;
  while ((match = regex.exec(html)) !== null) {
    // HTML etiketlerini temel olarak temizle
    matches.push(match[1].replace(/<[^>]*>/g, ""));
  }
  return matches.join("\n\n");
}

sendDailyDigest

  • recipient ve mailFrom alanlarını kendi e-posta adreslerinizle değiştirin.
/**
 * Haber özetlerini çeker ve bunları e-posta özeti olarak gönderir.
 */
function sendDailyDigest() {
  const summaries = getNewsSummaries();
  const botName = "günlük haber botu";
  const recipient = "to-adnankaya@example.com"; // burayı değiştir!!!!
  const mailFrom = "from-adnankaya@example.com"; // burayı değiştir!!!!!
  const subject = `Günlük Haber Özetiniz - ${new Date().toLocaleDateString()}`;

  // HTML e-posta gövdesini bir kapsayıcı tablo ile oluşturmaya başla
  let htmlEmailBody = `
    <html>
      <body style="font-family: Arial, sans-serif; background-color: #f4f4f4; margin: 0; padding: 20px;">
        <table width="100%" border="0" cellspacing="0" cellpadding="0">
          <tr>
            <td align="center">
              <table width="600" border="0" cellspacing="0" cellpadding="20" style="background-color: #ffffff; border-radius: 8px;">
                <tr>
                  <td>
                    <h1 style="color: #333333; font-size: 24px;">Günlük Haber Özeti</h1>
                    <p style="color: #666666; font-size: 14px;">İşte günlük haber özetleriniz:</p>
                    <hr style="border: 0; height: 1px; background: #ddd; margin: 20px 0;">
  `;

  if (summaries.length === 0) {
    htmlEmailBody += `
      <p style="color: #666666; font-style: italic;">Bugün hiçbir haber makalesi özetlenemedi.</p>
    `;
  } else {
    for (const item of summaries) {
      let formattedDate = item.pubDate
        ? new Date(item.pubDate).toLocaleString()
        : "";
      htmlEmailBody += `
        <table width="100%" border="0" cellspacing="0" cellpadding="0" style="margin-bottom: 20px;">
          <tr>
            <td>
              <h3 style="color: #0056b3; font-size: 18px; margin-top: 0; margin-bottom: 5px;">${
                item.title
              }</h3>
              <p style="color: #666666; font-size: 12px; margin-top: 0; margin-bottom: 10px;">
                <span style="font-weight: bold;">
                ${
                  formattedDate
                    ? ` | <span style="font-weight: bold;">Yayınlanma:</span> ${formattedDate}`
                    : ""
                }
              </p>
              <p style="color: #333333; font-size: 14px; margin-top: 0;">${item.summary.replace(
                /\n/g,
                "<br>"
              )}</p>
              <a href="${
                item.link
              }" style="color: #007BFF; font-size: 12px; text-decoration: none;">Makalenin tamamını buradan okuyun.</a>
            </td>
          </tr>
        </table>
        <p style="color: #ccc; font-size: 12px; margin-top: 25px; margin-bottom: 5px;">${
          item.source
        }</p>
        <hr style="border: 0; height: 1px; background: #eee; margin: 20px 0;">
      `;
    }
  }

  // HTML gövdesini sonlandır
  htmlEmailBody += `
                  </td>
                </tr>
              </table>
            </td>
          </tr>
        </table>
      </body>
    </html>
  `;
  var options = {
    name: botName,
    from: mailFrom
    htmlBody: htmlEmailBody,
    charset: "UTF-8",
  };
  GmailApp.sendEmail(recipient, subject, "", options);
  //Logger.log("E-posta gönderme denemesi tamamlandı. Gelen kutunuzu ve Apps Script Çalıştırma günlüklerini kontrol edin.");
}

Script'i Manuel Olarak Çalıştırma

  • Yukarıdaki işlevleri kopyalayıp önemli kısımları düzenledikten sonra, editör sayfasının üstünde Kaydet, Çalıştır, Hata Ayıkla, açılır menü (4 işlev adımız var) göreceksiniz.
  • Üstteki açılır menüde sendDailyDigest işlevinin seçili olduğundan emin olun.
  • Çalıştır'a tıkladığınızda bir açılır pencere belirecektir:
  • Yetkilendirme gerekli

    • Bu uygulama, istenen tüm izinler sağlanmadan beklendiği gibi çalışmayabilir.
  • "İzinleri İncele"'ye tıklayın -> Google hesabınızı seçin -> "Google bu uygulamayı doğrulamadı" uyarısı belirecektir -> Sol alttaki Gelişmiş'e tıklayın -> Başlıksız projeye git (güvenli değil)'e tıklayın -> "Başlıksız projenin erişebileceği şeyleri seçin" bölümünde tümünü seçin -> Devam'a tıklayın.

  • Neden "Google bu uygulamayı doğrulamadı" uyarısı alıyorsunuz? Çünkü ücretli bir Google Workspace kullanmıyorsunuz.

  • Script başarıyla çalışırsa e-postayı almalısınız, gelen kutunuzu kontrol edin.

Otomasyon İçin Tetikleyici Ekleme

  • Sol kenar çubuğunda Tetikleyiciler Bağlantı düğmesi bulunur, ona tıklayın.
  • Sağ alttaki Tetikleyici Ekle düğmesine tıklayın.
  • Bir açılır pencere belirecektir:
  • Çalıştırılacak işlevi seçin: sendDailyDigest
  • Hangi dağıtımın çalışacağını seçin: Head
  • Olay kaynağını seçin: Zamana bağlı
  • Zamana bağlı tetikleyici türünü seçin: Günlük zamanlayıcı (İhtiyacınıza göre başka birini seçebilirsiniz)
  • Günün saatini seçin: 20:00 ile 21:00 arası (Ben bunu seçtim)
  • Sağ tarafta Hata bildirim ayarları bulunur: Bana her gün bildir (Olduğu gibi bıraktım)
  • Aşağı kaydırın ve Kaydet'e tıklayın.

Sonuç

Bu proje, günlük bir ihtiyacı ücretsiz, otomatik bir çözüme dönüştürmenin mükemmel bir örneğidir. Gemini, Apps Script ve Gmail'i kullanarak, hiç para harcamadan gerçekten faydalı bir şey oluşturdum. Sadece kişisel bir sorunu çözmekle kalmadım, aynı zamanda API'ler ve script yazma konusunda pratik beceriler de edindim.

En güzel yanı, bunu kendinize göre uyarlamanın ne kadar kolay olması. Buradakiyle sınırlı değilsiniz; onu değiştirebilirsiniz ve değiştirmelisiniz. Daha fazla haber mi istiyorsunuz? Sadece daha fazla RSS beslemesi ekleyin. Farklı bir özet türü mü istiyorsunuz? Gemini'ye gönderdiğiniz prompt ile oynayın. Bu kurulum, daha karmaşık otomasyonlar için harika bir başlangıç noktasıdır.