FacebookとTwitterの両方にポストするのめんどくさいなーと思って横道

今朝

HootSuiteからFacebookTwitterの両方につぶやこうと思ったらFacebookにポストできなくて、調べたらFacebookのポリシーが変わったみたいで、その影響で今月からHootSuiteからもIFTTTからも連携がなくなったみたい。

ちぇっ。両方に書くのめんどくさいなー。

ってことで

Chrome Extensionを作って遊んでた。

https://github.com/bufferings/fbtw

f:id:bufferings:20180812025402p:plain

ポップアップにつぶやきたいことを入れてボタンを押したら、単純に、TwitterFacebookの画面を開いてつぶやきを貼ってボタンを押して閉じる、というのを自動でやってくれる感じ。エラー処理も何も入れてないから、ログインしてなかったらだめだし、タイムアウトとか文字数オーバーとかもあれだけど。まぁ、満足した。たまに気が向いたら使ってみよっと。

使ってみたいなーと思う人は、ソースを読んで「あほなことやってるなーこいつー」ってのが分かってから使ってください。

そもそも

今朝つぶやきたかったのは、これ。つぶやけて満足した。

でも、もう今日は眠いから勉強は明日からだな。おやすみー。

起きてから追記

Chrome拡張のこと折角だからメモっておくかと思って追記。たまに遊びで作るぐらいなので全然分かんなくて、公式ドキュメント読みながらちょっとずつやりました。

やりたいのは

  1. ポップアップに入力してポストしたら
  2. Facebook用のタブとTwitter用のタブを開いて
  3. ポストしたら
  4. 閉じる

ってだけ。

マニフェストファイルは

こんな感じ。

{
  "manifest_version": 2,
  "name": "fbtw",
  "version": "0.0.1",
  "icons" : {
    "128": "icon.png"
  },
  • FacebookTwitterにポストしたいってことで、安易にfbtwって名前にして。
  • アイコンも適当に作っといた。

f:id:bufferings:20180812093804p:plain

  "browser_action": {
    "default_icon": "icon.png",
    "default_title": "fbtw",
    "default_popup": "popup.html"
  },
  • 特定のページにどうこうしたいわけじゃないから page_action じゃなくて browser_action
  • popup.html ってファイルにポップアップのことを書いてる
  "background": {
    "scripts": ["background.js"],
    "persistent": false
  },
  • 最初、ポップアップの中で全部やろうとしてたら「ツイートしようとしてるけど、もう閉じてるよ!」みたいに怒られて「おー、ポップアップって閉じたらそこで終わるってことか。そうか。」ってなって background.js に処理を書くことにして
  • なんか、persistentfalse にすると常駐せずに、イベントページと呼ばれるものになるらしく。そっちのがメモリとかに優しいから推奨されてるみたいなので、そっちにしといた。
  "permissions": [
    "<all_urls>",
    "tabs",
    "activeTab",
    "storage"
  ]
}
  • パーミッションはとりあえずタブ周りかなと
  • URLは、TwitterFacebookにだけスクリプトを挿入するのでそこだけでいいんだけど、個人用だしまいっかと思って全部許可しといた
  • それから、書きかけのままポップアップを閉じてしまったら消えるの悲しいので storage も使うことにした

ポップアップ

見た目それっぽくなるようにふわっと書いた。この記事の上の方に貼ってる画像みたいなの。

<!DOCTYPE html>
<html>
  <head>
    <style>
      body {
        height: 145px;
      }
      button {
        margin-top: 5px;
        height: 30px;
        width: 90px;
        outline: none;
        float: right;
      }
      textarea {
        height: 100px;
        width: 400px;
      }
    </style>
  </head>
  <body>
    <textarea id="tweet_area"></textarea>
    <button id="tweet_button" disabled>Tweet</button>
    <script src="popup.js"></script>
  </body>
</html>

んで popup.js を呼んでる。JavaScriptの流儀が良くわからないのでドキドキしながら。

let tweetButton = document.getElementById("tweet_button");
let tweetArea = document.getElementById("tweet_area");

let をよく見かけたので使っといた。 var ってもう使わないのかな?それか使い分けがあるのかな?よく分かってない。

tweetButton.onTweetChanged = function(){
  tweetButton.disabled = (tweetArea.value.length === 0);
}

ボタンに関数くっつけといたらいっかなと思って。onTweetChanged をくっつけといた。こういうことが流儀的に許されるのかどうかよく分かってない。

入力されてたらボタンを押せるようにしてるだけ。

tweetButton.onclick = function(){
  chrome.runtime.sendMessage({type:"tweet", tweet:tweetArea.value});
  tweetArea.clearTweet();
  window.close();
};

クリックされたら sendMessage ってのでイベントページにメッセージを送って、成否に関係なくテキストエリアをクリアして閉じてる。へー。やりとりはメッセージでやるのかー。

tweetArea.onkeyup = function(){
  chrome.storage.local.set({tweet:tweetArea.value});
  tweetButton.onTweetChanged();
}

入力内容が変わるたびにローカルのストレージに保存してる。書いてる途中でポップアップを閉じてしまっても次に開いたら続きが書けるように。

tweetArea.clearTweet = function(){
  chrome.storage.local.remove("tweet");
  tweetArea.value = "";
  tweetButton.onTweetChanged();
}

ボタンを押したときにテキストエリアをクリアする処理をテキストエリア自身にくっつけといた。ローカルストレージの内容もクリア。

chrome.storage.local.get({tweet:""}, function(result){
  tweetArea.value = result.tweet;
  tweetButton.onTweetChanged();
});

ポップアップを開いたときに、ローカルストレージからツイートを引っ張ってきて初期値にしてる。ツイートが保存されてなかったときに undefined って表示されてしまってたから {tweet:""} で初期値を空文字にした。

イベントページ

ってことで、ポップアップから sendMessage で送られてきたツイートをTwitterFacebookに送るのをイベントページ(background.js)でやればOK。

これがメッセージハンドラー:

chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse){
    if(request.type === "tweet") {
      postToTwitter(request.tweet);
      postToFacebook(request.tweet);
    }
    if(request.type === "close_tab") {
      chrome.tabs.remove(request.tabid);
    }
  }
);

メッセージを受け取って、タイプが tweet の場合にはそれをTwitterFacebookにポストする関数を呼び出してる。 close_tab はタブを閉じるんだけど、呼び出してる場所は後で。

これがTwitter

let postToTwitter = function(tweet){
  chrome.tabs.create({
    active: false,
    url: "https://twitter.com/intent/tweet?text=" + encodeURI(tweet)
  }, function(tab){
    setTimeout(function(){
      chrome.tabs.executeScript(tab.id, {
        code: "document.getElementById('update-form').submit();",
        runAt: "document_end"
      }, function(){
        setTimeout(function(){
          chrome.tabs.remove(tab.id);
        }, 5000);
      });
    }, 1000);
  });
};

URLで内容を指定できるページがあったから、新しいタブを開いてそのページを開くことにした。ページを開いたら、1秒待ってからスクリプトを差し込む。ボタンを押すスクリプト。んで、差し込んでから5秒待ってタブを閉じてる。

最初は、ボタンを押した後に少し待ってから「ボタン押したよ!タブクローズしていいよ!」ってメッセージをイベントページに送るスクリプトも合わせて差し込もうとしたんだけど、メッセージが届かないなーって思ってみてたら、ボタンを押したら別のページに遷移するから差し込んだスクリプトがなくなってしまってたんだった。

ので、ちょっと残念だけど、ツイート成功の画面に遷移したことを検知してから閉じるでもなく、ボタンを押してから5秒でもなく、スクリプトを差し込んでから5秒になった。

これがFacebook

let postToFacebook = function(tweet){
  chrome.tabs.create({
    // Need to activate the page to activate the button.
    active: true,
    url: "https://facebook.com"
  }, function(tab){
    setTimeout(function(){
      chrome.tabs.executeScript(tab.id, {
        file: "forFacebook.js",
        runAt: "document_end"
      }, function(){
        chrome.tabs.sendMessage(tab.id, {tabid:tab.id, tweet:tweet});
      });
    }, 1000);
  });
};

Facebookはもうちょっと面倒で。どうも、画面が実際に見られてないと動かないみたいなので、chrome.tabs.create のオプションで active: true を指定して、開いたタブがアクティブになるようにした。

Twitter用のと違って、ちょっとだけ複雑な処理をするから forFacebook.js ってファイルに書いてそれを差し込むことにした。なので、ツイートの内容は別途そのスクリプトに伝えてあげないといけなくて、sendMessage で送ることにした。

forFacebook.js はこんな感じ:

chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse){
    let box = document.getElementsByName("xhpc_message")[0];
    box.value = request.tweet;
    box.focus();

    setTimeout(function(){
      let button = document.querySelector("button[data-testid='react-composer-post-button']");
      button.click();
      setTimeout(function(){
        chrome.runtime.sendMessage({type:"close_tab", tabid:request.tabid});
      }, 3000);
    }, 5000);
  }
);

テキストボックスに値を入れて、フォーカスを当ててしばらくしたらボタンが押せるようになるので、5秒待ってからボタンをクリックして、その後3秒したら「タブを閉じてー!」ってメッセージをイベントページに送って、イベントページのメッセージハンドラーでタブを閉じてる。

という流れ

エラーハンドリングとか考え出すとお盆休みが終わってしまいそうなので、あきらめた。

途中で、Promise使ったらもうちょっとキレイに書けるかなー?と思って調べてたら横道にそれて => みたいなの出てきて「これでも関数書けるのかー!」ってやってみたりしつつ、んー、読みやすいのか読みにくいのか分からんなーってなったり、結局Promise使うほどじゃないかってなってやめたりして。

ひとつツイートするのに一日かかったんだけど、まぁ、色々楽しかった。