twitter から Misskey.io にダブルポスト (4)

 

前回までのあらすじ

tweet の URL を Phantomjs Cloud(ヘッドレスブラウザの Phantomjs の Web 版)で展開して URL を取得するところまで実装できました。
あとは Misskey.io(と Bluesky)に当該 URL の画像をアップロードすれば良い……ということになります。

Misskey.io で画像つきノートを作成するには、まず「ドライブ」というオンラインストレージに画像をアップロードして、そのリファレンスをノートに添付する形になるみたいです。

ただ、どのようにファイルをアップロードすれば良いかが不明だったので、Postman というサービスに登録して色々と試してみました。どうやら Misskey.io の場合、アップロードは multipart/form-data で行う必要があるとのこと。例によってググるといい感じの情報が見つかりました。
multipart/form-dataのファイルをGoogle Apps Scriptから送信する

上記のページにスクリプトのサンプルも掲載されていますが、どうやら https://www.labnol.org/code/20096-upload-files-multipart-post がオリジナルだったみたいです。このサンプルを次のように修正します。

//
// ■ URL を Misskey Drive にアップロード
//
// Original by Amit Agarwal www.labnol.org
// https://www.labnol.org/code/20096-upload-files-multipart-post
//
function uploadFile_(imageUrl) {
  const boundary = "labnol";
  //var blob = DriveApp.getFileById(GOOGLE_DRIVE_FILE_ID).getBlob();
  let blob = UrlFetchApp.fetch(imageUrl).getBlob();

  var requestBody = Utilities.newBlob(
    "--"+boundary+"¥r¥n"
    + "Content-Disposition: form-data; name=¥"i¥"¥r¥n"
    + "Content-Type: text/plain¥r¥n¥r¥n"
    + MISSKEY_TOKEN + "¥r¥n"
    + "--"+boundary+"¥r¥n"
    + "Content-Disposition: form-data; name=¥"file¥"; filename=¥""+blob.getName()+"¥"¥r¥n"
    + "Content-Type: " + blob.getContentType()+"¥r¥n¥r¥n").getBytes()
  .concat(blob.getBytes())
  .concat(Utilities.newBlob("¥r¥n--"+boundary+"--¥r¥n").getBytes());
  
  var options = {
    method: "post",
    contentType: "multipart/form-data; boundary="+boundary,
    payload: requestBody,
    //muteHttpExceptions: true,
  };

  var request = UrlFetchApp.fetch("https://misskey.io/api/drive/files/create", options);

  //Logger.log(request.getContentText());
  return request;
}

2 行目でスクリプトの出どころが一目瞭然というところがポイントです(boundary、すなわち区切り文字は任意の文字列を選択できるみたいですね)。元々は Google Drive のファイルをアップロードするスクリプトだったみたいですが、これを任意の URL を fetch して作成した blob をアップロードするように手直しした……ということですね。

  • 変数 attributes は不要っぽかったのでサクっと削除しています。
  • multipart ですが、最初の part で text/plain として MISSKEY_TOKEN(=認証情報)を送信しています(この辺のやり方は Postman で確認)。

これで指定した URL を Misskey ドライブにアップロードできるようになりました。
あとは Misskey.io にノートを投稿する際に、画像が添付されている場合は fileIds を追加すれば良いということになります。

【IFTTT】TwitterからMisskeyへ自動投稿するでは以下のようになっていましたが……

function postToMisskey(noteText) {
  // 公式サーバー以外の場合には所属しているサーバーのホスト名に書き換えてください
  const host = "misskey.io";
  const ENDPOINT = `https://${host}/api/notes/create`;
  let options = {
    method: "post",
    contentType: "application/json",
    payload: JSON.stringify({
      localOnly: false,
      noExtractMentions: false,
      noExtractHashtags: false,
      noExtractEmojis: false,
      text: noteText,
      i: MISSKEY_TOKEN
    })
  };
  let response = UrlFetchApp.fetch(ENDPOINT, options);
  return response.getContentText();
}
これをこんな感じに手直ししてみました。

//
// ■ Misskey に投稿
// https://rmc-8.com/twitter-to-misskey
//
function postToMisskey_(noteText,notefileIds) {
  const ENDPOINT = `https://misskey.io/api/notes/create`;
  let options;
  if( notefileIds.length < 1 ) {
    options = {
      method: "post",
      contentType: "application/json",
      muteHttpExceptions: false,
      payload: JSON.stringify({
        "localOnly": false,
        "noExtractMentions": false,
        "noExtractHashtags": false,
        "noExtractEmojis": false,
        "text": noteText,
        "i": MISSKEY_TOKEN
      })
    };
  } else if( noteText.length < 1 ) {
    console.log(notefileIds);
    options = {
      method: "post",
      contentType: "application/json",
      muteHttpExceptions: false,
      payload: JSON.stringify({
        "localOnly": false,
        "noExtractMentions": false,
        "noExtractHashtags": false,
        "noExtractEmojis": false,
        "fileIds": notefileIds,
        "i": MISSKEY_TOKEN
      })
    };
  } else {
    console.log(notefileIds);
    options = {
      method: "post",
      contentType: "application/json",
      muteHttpExceptions: false,
      payload: JSON.stringify({
        "localOnly": false,
        "noExtractMentions": false,
        "noExtractHashtags": false,
        "noExtractEmojis": false,
        "text": noteText,
        "fileIds": notefileIds,
        "i": MISSKEY_TOKEN
      })
    };
  }

  let response = UrlFetchApp.fetch(ENDPOINT, options);
  return response.getContentText();
}

無駄に行数が増えていますが、
  • 文字だけのノート
  • 画像だけのノート
  • 文字と画像のノート
の 3 パターンで分岐させています。見るからにクソダサな書き方で、もっとスマートな書き方がある筈ですが、見やすさを重視してみました(汗)。

あとは tweet の URL を Phantomjs Cloud で展開して得た画像 URL ごとに、アップロード処理を追加すれば良いということになります。

  // ■ LinkToTweet をスクレイピング
  const fileIds = new Array();
  if(TweetText.match(/https:\/\/(twitter|x).com\/\w{1,}\/status\/[0-9]{1,}\/photo\/[0-9]{1}/)) {
    console.log('scraping……');
    var response = scrape_(LinkToTweet);
    const json_response = JSON.parse(response);
    let source = json_response["content"]["data"].toString();
    const imageUrls = source.match(/https:\/\/pbs.twimg.com\/media\/[A-Za-z0-9_\-]{1,}\?format=(jpg|png)\&\;name=[A-Za-z0-9]{1,}/g);
    let lastUrl;

    if (imageUrls != null && 0 < imageUrls.length) {
      for (let i = 0; i < imageUrls.length && i < 8; ++i) {
        if( lastUrl != imageUrls[i] ) {
          let imageUrl = imageUrls[i];
          lastUrl = imageUrl;
          imageUrl = imageUrl.replace(/\&/g,"\&");
          Logger.log(imageUrl);
          var response = uploadFile_(imageUrl);
          let responseJSON = JSON.parse(response.getContentText());
          let fileId = responseJSON.id;
          fileIds.push(fileId);
          console.log(fileId);
        }
      }
    }
    Logger.log(fileIds);
    //TweetText = TweetText.replace(/https:\/\/(twitter|x).com\/(bojan_intl|bojan_net)\/status\/[0-9]{1,}\/photo\/[0-9]{1}/,"");
    TweetText = TweetText.replace(/https:\/\/(twitter|x).com\/\w{1,}\/status\/[0-9]{1,}\/photo\/[0-9]{1}/,"");
  }

最後の
TweetText = TweetText.replace(/https:\/\/(twitter|x).com\/\w{1,}\/status\/[0-9]{1,}\/photo\/[0-9]{1}/,"");
は、画像の URL がツイート本文に残っているので、それを削除するためのものです。

ということでぇ

Bojan.net 新着情報もこんな風に投稿できるようになりました。
ただ、ここで厄介な問題が新たに浮上してしまいます。

続く……?

前の記事

www.bojan.net
Copyright © 1995- Bojan International

0 件のコメント:

新着記事