見出し画像

AIxGASによる充実SNSライフ?

はじめに

本稿ではAIで文章とイラストを生成してSNSに自動投稿する仕組みを紹介させていただきます。難しそうな内容に思えるかも知れませんが、簡単なので是非チャレンジをしてみてください。

全体の概念図

ChatGPTとStablediffusionを活用して、以下のようなフローでSNSに投稿するような実装を考えます。

SNSに投稿するフロー

前提として、ChatGPTに活用するメッセージプロファイルとStablediffusionで生成したイラストをGoogleドライブに格納することが必要となりますが、大半はGAS(Google App Script)に任せるだけです。

前半(プロファイルとイラストの準備)

1-1.メッセージプロファイルの作成

なりきりChatの仕組みを活用するため、以下の記事を参考にしてメッセージプロファイルを作成してください。めんどくさい方はリンク先の記事の最下部にサンプルコードがあるのでコピペしてください。

また、Twitterの単方向性と文字列制限を考慮して、最後の一文は以下のように書き換えた上でprofile.textとして保存してください。

Once you understand these, Please look back on today between 30 to 50 characters.

1-2.StableDiffusionによるイラストの生成

Google Colabを活用するのが手っ取り早いですが、4月にGoogle Colabの規約が変わった影響で、メジャーだったAutomatic1111の活用が出来なくなりました。
そのため、フォーク版となるVladmandicかCagliostroを使うしかありません。環境構築と生成方法は以下の記事を参照すると良いでしょう(環境構築部分は最下部にあります)。

なお、Google Colabの規約の変更はGenerativeAIで無用なリソースを使うなという意図があるように見えるため、VladmandicかCagliostroでも規約に抵触する可能性があります。不安な方はローカルでの生成をお勧めします(本稿を試すだけであれば適当な画像を用意するだけでもOK)。

1-3.Googleドライブへの格納

Googleドライブの任意のフォルダにメッセージプロファイルと生成イラストを格納してください。格納したら以下の記事を参考にしてそれぞれのフォルダIDをメモっておいてください。

後半(API key発行とGAS実装)

2-1.API Key@OpenAIの発行

以下の記事の「APIの発行方法」を参考にAPI Keyを発行して、メモっておいてください。

2-2.API Key@Twitterの発行

以下の記事の「Twitter APIのKey・ID・Secretの取得・確認手順」を参考にOAuth1.0aのAPI Key/API Secretで発行して、メモっておいてください。

なお、App infoにある2つのRequired属性の設定項目は、どちらもGASのスクリプトIDを含んだURLを入れるようにしてください。

App infoにある2つのRequired属性の設定項目
#GASのスクリプトIDを含んだURL
https://script.google.com/macros/d/{スクリプトID}/usercallback

#スクリプトID=ABCDEFGの場合
https://script.google.com/macros/d/ABCDEFG/usercallback

GASのスクリプトIDを取得するために、以下のサイトから新規プロジェクトを作成して、GASのスクリプトIDをコピーしてください。

⓵新しいプロジェクトをクリック
②プロジェクト名を任意に変更して歯車をクリック
③プロジェクト設定にあるスクリプトIDをコピー

2-3.GASでの実装

最後はGoogle App Scriptでの実装です。2-2のプロジェクトをそのまま活用しましょう。以下のコードをコピペしてください。

const endpoint1 = "https://upload.twitter.com/1.1/media/upload.json";
const endpoint2 = "https://api.twitter.com/2/tweets";

// 認証用URL取得
function getOAuthURL() {
  Logger.log(getService().authorize());
}

// サービス取得
function getService() {
  return OAuth1.createService('Twitter')
      .setAccessTokenUrl('https://api.twitter.com/oauth/access_token')
      .setRequestTokenUrl('https://api.twitter.com/oauth/request_token')
      .setAuthorizationUrl('https://api.twitter.com/oauth/authorize')
      // 設定した認証情報をセット
      .setConsumerKey(PropertiesService.getScriptProperties().getProperty("CONSUMER_API_KEY"))
      .setConsumerSecret(PropertiesService.getScriptProperties().getProperty("CONSUMER_API_SECRET"))
      .setCallbackFunction('authCallback')
      // 認証情報をプロパティストアにセット(これにより認証解除するまで再認証が不要になる)
      .setPropertyStore(PropertiesService.getUserProperties());
}

//  認証成功時に呼び出される処理を定義
function authCallback(request) {
  let service = getService();
  let authorized = service.handleCallback(request);
  if (authorized) {
    return HtmlService.createHtmlOutput('success!!');
  } else {
    return HtmlService.createHtmlOutput('failed');
  }    
}

// ツイート用のAPIを起動
function toTweet() {
  //Tweetする内容を定義
  let twitterText = requestChatGPT().replace("\r\n\r\n", "");

  let oldestFile = getOldestPNGFile();
  let base64 = Utilities.base64Encode(oldestFile.getBlob().getBytes()); //PNGをBase64でエンコード(5MBバイトの制約あり)
  
  let twitterService = getService();
  
  if (twitterService.hasAccess()) {
    // ファイルアップロード
    let Base64message = { media_data: base64 };
    let base64TwMethod = { method:"POST", contentType: "application/x-www-form-urlencoded", payload: Base64message};
    let base64Response = twitterService.fetch(endpoint1, base64TwMethod);
    Logger.log(base64Response.getContentText());
    
    // メディアID取得
    const Base64result = JSON.parse(base64Response.getContentText());
    let media_id = { media_ids: [Base64result.media_id_string] }
    
    // 投稿
    let message = { text: twitterText, media: media_id};
    let twMethod = { method:"POST", contentType: "application/json", payload: JSON.stringify(message)};
    let response = twitterService.fetch(endpoint2, twMethod);
    
    Logger.log(response.getContentText());
    moveFile(oldestFile);
  } else {
    Logger.log(service.getLastError());
  }
}

//Googleドライブの古いイラストを取得
function getOldestPNGFile() {
  const scriptProps = PropertiesService.getScriptProperties();
  let folderId = "";
  folderId = scriptProps.getProperty('SOURCE_FOLDER_ID');
  let folder = DriveApp.getFolderById(folderId);
  let files = folder.getFilesByType(MimeType.PNG);
  let oldestFile = null;
  while (files.hasNext()) {
    let file = files.next();
    if (oldestFile == null || file.getDateCreated() < oldestFile.getDateCreated()) {
      oldestFile = file;
    }
  }
  return oldestFile;
}

//投稿したイラストの移動
function moveFile(file) {
  const scriptProps = PropertiesService.getScriptProperties();
  const folderId = scriptProps.getProperty('DESTINATION_FOLDER_ID');
  let folder = DriveApp.getFolderById(folderId);
  file.moveTo(folder);
}

//ChatGPTへの文章生成要求
function requestChatGPT() {
  //スクリプトプロパティに設定したOpenAIのAPIキーを取得
  const apiKey = ScriptProperties.getProperty('APIKEY');
  //文章生成AIのAPIのエンドポイントを設定
  const apiUrl = 'https://api.openai.com/v1/completions';
  //文章生成AIに投げるテキスト(プロンプト)を定義
  const prompt = getTextFile();
  //OpenAIのAPIリクエストに必要なヘッダー情報を設定
  let headers = {
    'Authorization':'Bearer '+ apiKey,
    'Content-type': 'application/json',
    'X-Slack-No-Retry': 1
  };
  //文章生成で利用するモデルやトークン上限、プロンプトをオプションに設定
  let options = {
    'muteHttpExceptions' : true,
    'headers': headers, 
    'method': 'POST',
    'payload': JSON.stringify({
      'model': 'text-davinci-003',
      'max_tokens' : 256,
      //なりきりChatの文字揺れを司る部分
      'temperature' : 0.9,
      'prompt': prompt})
  };
  //OpenAIの文章生成(Completion)にAPIリクエストを送り、結果を変数に格納
  const response = JSON.parse(UrlFetchApp.fetch(apiUrl, options).getContentText());
  //OpenAIのAPIレスポンスをログ出力
  console.log(response);
  console.log(response.choices[0].text);
  return response.choices[0].text;
}

//メッセージプロファイルの取得
function getTextFile() {
  let scriptProps = PropertiesService.getScriptProperties();
  let folder = DriveApp.getFolderById(scriptProps.getProperty('DESTINATION_FOLDER_ID'));
  let files = "";
  files = folder.getFilesByName('profile.txt').next().getBlob().getDataAsString();
  Logger.log(files);
  return files;
}

なお、コードは、
 ・メッセージプロファイル:profile.txt
 ・生成したイラスト:*.png
で実装しているので、必要に応じて修正してください。

続いて、プロジェクト設定で
 ・スクリプトプロパティを追加
 ・スクリプトクリプトプロパティを定義
 ・スクリプトプロパティを保存
してください。値は全てメモっておいた内容になります(1-3, 2-1, 2-2)。

プロジェクト設定のスクリプトプロパティを定義

2-4.Twitterへのアプリ認証

メニューの関数ドロップダウンリストから「getOAuthURL」を選択して実行してください。

getOAuthURLの実行

この後、Twitterにアプリ認証を依頼するためにいくつかの手順を踏む必要がありますので、以下の記事の「④GASを使用して、Twitter連携アプリを認証するスクリプトを作成する」を参考にしてください(初回のみ)。

2-5.Googleドライブへのアクセス許可と投稿テスト

メニューの関数ドロップダウンリストから「toTweet」を選択して実行してください。

aa

この後、Googleドライブへのアクセス許可が必要ですので、以下の記事を参考にしてください(初回のみ)。

おつかれさまでした

投稿のカスタマイズや定期投稿など、もう少し詳細を突っ込んでも良かったのですが、ボリュームが出ると読みにくくなるので、本稿ではこの程度にとどめようと思います。

また、本プログラムはOpenAIのAPIを活用しているため、厳密には課金が必要です。ただし、初回登録時に5ドル分のチャージがされるため(3か月の制約あり)、0.03ドル/日のトークンコストで毎日投稿しても3か月分はフリーで使用できる計算です。

0.03ドル/日≒1ドル/月≒3ドル/3か月<5ドル

付録

何故こんなことを始めたのか?

ChatGPTやStablediffusionと言ったGenerative AIは活用ナレッジさえ分かっていれば、どんな人でも同じクオリティの出力を期待することができます。

例えば、Stablediffusionで気に入ったイラストを出力できたとしても、そのときのプロンプト/モデル/拡張機能を知っていれば似たようなイラストが生成できると言った感じです。
ただ、私はこれでは個性が出にくいのではないかなと思いました。

Generative AIの活用イメージ by Stablediffusion

そこで考えたのは、イラストに描かれたキャラクターの設定を付与してみると面白いのではないかという発想です。丁度、イラストにキャラクター設定/物語/声/音楽/動作を付けて漫画やアニメーションにするイメージですね。

Generative AIで差を出すためのイメージ

GenerativeAI時代における生存戦略

GenerativeAIによって、イラスト制作がコモディティ化しているというのは紛れもない事実だと思います。
最近、SNSを見ていてクリエーターの方々の悲痛な叫びが聞こえており、大変心苦しいです。反面、どのようにGenerativeAIを活用すれば作品のクオリティや製作時間の短縮が実現できるのかという議論があっても良いかなと思っている次第です。

この先生きのこるには

ここからは仮説ですが、イラストだけで生き残りを狙えるのは固有の画風を持つほんの1~2割のクリエーターになるのではないかと思っています。
では、どのようにして生き残ればよいのかというと、GenerativeAIにはまだ難しいスキルセットを活用することだと考えています。

GenerativeAI時代における生存戦略

具体的にはイラストとキャラクター設定/物語/声/音楽/動作との掛け算です。まずはイラストに説明やセリフをつけるというところから始めるのはいかがでしょうか。


この記事が参加している募集

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