ChatGPT4を使って読みたい本リストを作ってみた

初noteです。
あと、初制作物です。

タイトル通り、
ChatGPT4(利用料 : 20ドル/月)を使って、読みたい本リストを作りました。

読みたい本リストについて
ChatGPT4について
コード
の順に書いていきます。



読みたい本リストについて

なにを作ったのか

本のタイトルをGoogle ToDoリストのタスクに入力すると、Googleスプレッドシートに自動で転記されて、Amazonと早稲田大学図書館の検索リンクも自動で入力されるシステム

Google ToDoリスト
Google SpreadSheet

設計

GAS*だけで完結させること。
ToDoリストとスプレッドシートを使用する。
ToDoリストは本のタイトルを入力する機能のみ持たせる。
ToDoリストの採用理由は、PCとスマホ両方からのアクセスが簡単なため。
また、Googleカレンダーをメインのタスク管理ツールとして利用しているため、ToDoリストが身近な存在だった。
スプレッドシートの採用理由は、ToDoリストとの連携が簡単なため。

*GASとは
Google Apps Scriptの略
なんかGoogleのアプリの動作をコードで自動化したり、アプリ同士を連携させたり出来る。
JavaScriptで記述する。
https://script.google.com/home

動作

Google ToDoリストのタスク名に本のタイトルを入力すると、
スプレッドシートのA列にタイトルが転記され、
AmazonのKindleURLクエリがB列に記入される。
AmazonのURLクエリがC列に記入される。
早稲田大学図書館のURLクエリがD列に記入される。
また、ToDoリストのタスクの"詳細"にも早稲田大学図書館のURLクエリが記入される。
トリガーはスプレッドシートの起動時に設定している。
ToDoリストへのタスク追加をトリガーに設定することは出来ないみたい。

なぜ作ったのか

気になる本があった時にAmazonで調べて、図書館にあるか検索して、という過程を踏むのが面倒になって自動化したくなった。

どうなったのか

ToDoリストにタイトル入力するだけで良いのめっっっちゃ楽。
これまではAmazonの欲しいものリストに読みたい本を入れていたが、確認するのが非常に面倒だった。
いまは簡単に一覧を確認できるようになって快適。
スプレッドシートを開かないと動作しないけど、即時反映されなくても困らないから別にいいかな〜。

今後の課題

理想としては、Amazonのセール情報を取得したり、図書館の貸出状況を取得したりしたかったが、実装の手間と運用・保守するほどのモチベが無く、諦めた。


ChatGPT4とコーディングについて

筆者のコーディングレベル

コーディング経験は初歩のプログラムを書いたことがあるぐらい。
JSに関しての知識はProgateを一通りやっただけ。
未だにクラスとかよく分かってない…

ChatGPT4はコーディングに使えるのか

使える。
コーディングは全てChatGPTに任せた。

どういった課題があったのか

やり取りを続けていくうちに、コードの修正によって解決した問題が元のコードに戻ることで、再び同じ問題が出現することが一番ストレスだった。
コードをテストする工程も煩雑で大変だった。
この課題はコーディングスキルが無いのも原因だろうけど…
短時間に一定以上のやり取りをすると、回数制限に達してしまうため、数時間待たないといけないことも…

どうクリアしたのか

愚直にトライ&エラー

次やるならどうするか

設計の段階で想定される問題をChatGPTに尋ねたり、また、検討事項の抜け漏れがないかを確認してもらうことで、実装段階のコーディングやテストの工数を短縮できたと思う。

次にプログラムを作るときは、プラグインとかCode interpreterとかも試してみたい。
ChatGPTじゃなくてもGitHub Copilotとかもあるし。


実際のコード

const TASK_LIST_ID = 'YOUR_TASK_LIST_ID';

YOUR_TASK_LIST_IDをGoogleからユニークに割り当てられたTaskListIDに置換。

fetchTasksAndWriteToSheet()

下4つの関数を全て呼び出す。
トリガーの対象に設定する関数。

getTasksFromToDoList()

ToDoリストからタスクを取得。

writeToSheet(tasks)

スプレッドシートにタスクを書き込む。
既存のデータがある場合、重複するデータはスキップされる。
AmazonクエリURLと早稲田図書館のクエリURLを書き込む。
早稲田図書館のクエリURLが必要ない場合は該当する箇所のコードを削除すること。

addWasedaLibraryQueryToTaskDetails()

ToDoリストのタスクの詳細に早稲田図書館のクエリURLを書き込む。
早稲田図書館のクエリURLが必要ない場合はこの関数を削除すること。

transferCompletedTasksToSheet()

完了したタスクがある場合は、todoシートから該当データを削除し、completedシートに該当データを書き込む。

const TASK_LIST_ID = 'YOUR_TASK_LIST_ID'; // タスクリストIDを調べて''の中に入力してください

// トリガー対象の関数です。全ての関数を呼び出します。
function fetchTasksAndWriteToSheet() {
  var tasks = getTasksFromToDoList();
  writeToSheet(tasks);
  addWasedaLibraryQueryToTaskDetails();
  transferCompletedTasksToSheet();
}


function getTasksFromToDoList() {
  var tasks = [];
  var pageToken;
  do {
    var response = Tasks.Tasks.list(TASK_LIST_ID, {
      showCompleted: true,
      showHidden: true,
      maxResults: 100, // 最大100件を取得
      pageToken: pageToken // 次のページのタスクを取得するためのトークン
    });
    if (response.items) {
      tasks = tasks.concat(response.items);
    }
    pageToken = response.nextPageToken; // 次のページのトークンを取得
  } while (pageToken); // nextPageTokenが存在する限りループを続ける

  return tasks;
}


function writeToSheet(tasks) {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var lastRow = sheet.getLastRow();
  var data = sheet.getRange(2, 1, lastRow, 4).getValues();

  var existingTitlesSet = new Set(data.map(row => row[0])); // 既存のタスクタイトルをSetに変換

  var nextEmptyRow = 2; 
  for (var i = 0; i < data.length; i++) {
    if (!data[i][0]) {
      nextEmptyRow = i + 2;
      break;
    }
  }

  var rowsToWrite = []; // 書き込む行のデータを格納する配列

  for (var i = 0; i < tasks.length; i++) {
    var task = tasks[i];
    var title = task.title;

    // タスクが未完了でない場合、スキップ
    if (task.status !== "needsAction") {
      continue;
    }

    if (existingTitlesSet.has(title)) {
      continue;
    }

    var amazonKindleQuery = "https://www.amazon.co.jp/s?k=" + encodeURIComponent(title) + "&i=digital-text";
    var amazonBookQuery = "https://www.amazon.co.jp/s?k=" + encodeURIComponent(title);
    var wasedaLibraryQuery = "https://waseda.primo.exlibrisgroup.com/discovery/search?query=any,contains," + encodeURIComponent(title) + "&tab=Everything&search_scope=MyInst_and_CI&vid=81SOKEI_WUNI:WINE&mfacet=rtype,include,books,1&lang=ja";

    rowsToWrite.push([title, amazonKindleQuery, amazonBookQuery, wasedaLibraryQuery]);
  }

  // 一度に複数の行を書き込む
  if (rowsToWrite.length > 0) {
    sheet.getRange(nextEmptyRow, 1, rowsToWrite.length, 4).setValues(rowsToWrite);
  }
}




function addWasedaLibraryQueryToTaskDetails() {
  var pageToken;
  do {
    var response = Tasks.Tasks.list(TASK_LIST_ID, {
      showCompleted: true,
      showHidden: true,
      maxResults: 100, // 最大100件を取得
      pageToken: pageToken // 次のページのタスクを取得するためのトークン
    });
    
    if (response.items) {
      for (var i = 0; i < response.items.length; i++) {
        var task = response.items[i];
        var title = task.title;
        var wasedaLibraryQuery = "https://waseda.primo.exlibrisgroup.com/discovery/search?query=any,contains," + encodeURIComponent(title) + "&tab=Everything&search_scope=MyInst_and_CI&vid=81SOKEI_WUNI:WINE&mfacet=rtype,include,books,1&lang=ja";
        
        // 既存のnotesにWaseda Libraryのクエリが存在するか確認
        if (task.notes && task.notes.includes(wasedaLibraryQuery)) {
      
          continue;
        }
        
        // 既存のnotesにクエリを追加
        var updatedNotes = task.notes ? task.notes + "\n" + wasedaLibraryQuery : wasedaLibraryQuery;
        
        // タスクのnotesを更新
        task.notes = updatedNotes;
        Tasks.Tasks.update(task, TASK_LIST_ID, task.id);
      }
    }
    pageToken = response.nextPageToken; // 次のページのトークンを取得
  } while (pageToken); // nextPageTokenが存在する限りループを続ける
}





function transferCompletedTasksToSheet() {
    var tasks = getTasksFromToDoList();
    var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
    
    var todoSheet = spreadsheet.getSheetByName("ToDo"); // ToDoシートを取得
    var completedSheet = spreadsheet.getSheetByName("Completed"); // Completedシートを取得
    
    if (!completedSheet) {
        Logger.log("Target sheet 'Completed' not found.");
        return;
    }

    var lastRowInCompleted = completedSheet.getLastRow();
    var nextEmptyRowInCompleted = lastRowInCompleted + 1;

    for (var i = 0; i < tasks.length; i++) {
        var task = tasks[i];
        if (task.status === "completed") {
            // 完了したタスクの行をToDoシートから取得
            var finder = todoSheet.createTextFinder(task.title);
            var cell = finder.findNext();
            
            // タスクのタイトルが見つかった場合のみ処理を続行
            if (cell) {
                var rowIndex = cell.getRow();
                var dataRange = todoSheet.getRange(rowIndex, 1, 1, todoSheet.getLastColumn());
                var dataValues = dataRange.getValues();

                // 完了したタスクのデータをCompletedシートにコピー
                completedSheet.getRange(nextEmptyRowInCompleted, 1, 1, dataValues[0].length).setValues(dataValues);
                
                // ToDoシートから完了したタスクの行を削除
                todoSheet.deleteRow(rowIndex);
                
                nextEmptyRowInCompleted++;
            } else {
                Logger.log(`Title '${task.title}' not found in ToDo sheet.`);
            }
        }
    }
}


導入手順

Googleスプレッドシートの拡張機能のApps Scriptを選択

コピペする。

タスクリストIDは以下のコードを実行することで、実行ログに表示される。

function getTaskListIds() {
  var taskLists = Tasks.Tasklists.list();
  if (taskLists.items) {
    for (var i = 0; i < taskLists.items.length; i++) {
      var taskList = taskLists.items[i];
      Logger.log('Task List ID: ' + taskList.id + ' Title: ' + taskList.title);
    }
  } else {
    Logger.log('No task lists found.');
  }
}


この記事が気に入ったらサポートをしてみませんか?