イラストレーターみやびの漫画館 作品集 - 月の高いところのロゴマーク

今日のプリン言

謎のプリン語る。
一人書く人増えました。

JXA、twitterで自動ツイートする

2017年04月17日

みやびプリン 140 87

500 320

JXA、twitterで自動ツイートする - サムネイル

どうも、左折ができないみやびです。
難しい、車の運転・・・。

エントリーとまったく関係ない話をすると、
早く本題見せろって人はこちら
ここ2年くらい、ちょいちょい実力試しに、CodeIQってサイトの問題に挑戦したりしてるのだが、
その中で、プログラム言語を擬人化してるって、漫画があるんだけど、それの作者さんがかわいいのなんのって。
ちょまどさん。
イラストレーター・漫画家兼、プログラマーという、
僕と似た方向なので親近感が湧いたという。
(↑おまえは格下だけどな)
ちょまど帳というブログでいろいろやってるので、皆さんも応援&拡散を!
(というかすでにかなり有名ですが)

いつも以上に、前置きひどいね。
(すでに前置きじゃなくなってるという)

さて、本題。
表記の通り、前回のエントリーでは、twitterへ自動ログインまでやったけど、
ログインまでいきゃ、当然投稿までできちゃいます。
今回はそれをやっていこうと。

まずはスクリプト。
前回のスクリプトに様々加えている。

// Google Chromeを定義
var app = Application("Google Chrome");
app.includeStandardAdditions = true;

var windowChrome = null,
  tabFirst = null;

if (app.windows.length === 0) {
  app.Window().make();
}

// twitterのURLを定義
var twitterUrlAccess = "https://twitter.com/?lang=ja";

windowChrome = app.windows[0];
tabFirst = windowChrome.tabs[0];
tabFirst.url = twitterUrlAccess;


// Chromeをアクティブにする
app.activate();

// 次に移るために、フォーム送信が成功したかを格納する変数
var formSubmitFlg = null;

// ブラウザ上で実行させる関数を定義
var runFunction = function(){
  var loginBtn = document.getElementsByClassName('StreamsHero-buttonContainer').item(0).lastElementChild;
  loginBtn.click();
	
  var formDom = document.getElementsByClassName('LoginDialog-form').item(0).firstElementChild;
  var inputDivs = document.getElementsByClassName('LoginForm-input');
  var inputDivsUser = document.getElementsByClassName('LoginForm-username');
  var inputDivsPass = document.getElementsByClassName('LoginForm-password');
	
  for (var iU = 0; iU < inputDivsUser.length; iU++) {
    inputDivsUser.item(iU).firstElementChild.value = 'ログインアドレス';
  }
	
  for (var iP = 0; iP < inputDivsPass.length; iP++) {
    inputDivsPass.item(iP).firstElementChild.value = 'パスワード';
  }
	
  formDom.submit();
};
// 投稿するメソッド
var runFunctionSubmit = function(){
  var newTweetBtn = document.getElementById('global-new-tweet-button');
  newTweetBtn.click();
	
  var tweetBlock = document.getElementById('tweet-box-global');
  var submitBlock = document.createDocumentFragment();
	
  var text001 = document.createElement('div'),
    text002 = document.createElement('div');
  text001.textContent = 'JXAによるtwitter自動投稿テスト001';
  text002.textContent = 'ああああああ';
	
  submitBlock.appendChild(text001);
  submitBlock.appendChild(text002);
	
  tweetBlock.textContent = null;
	
  tweetBlock.appendChild(submitBlock);
	
  var formDom = tweetBlock.parentNode.parentNode.parentNode.parentNode.parentNode;
  var formChildBtn = formDom.children.item(2).children.item(1).lastElementChild;
	
  var timerF = setTimeout(function(){
    document.activeElement.blur();
    formChildBtn.click();
  }, 500);
};

// 関数を一行テキストに
var runFunctionStr = runFunction.toString();
runFunctionStr = runFunctionStr.replace(/€t/g, "");
runFunctionStr = runFunctionStr.replace(/€n/g, "");
var runFunctionSubmitStr = runFunctionSubmit.toString();
runFunctionSubmitStr = runFunctionSubmitStr.replace(/€t/g, "");
runFunctionSubmitStr = runFunctionSubmitStr.replace(/€n/g, "");



// twitterに投稿するメソッド
function twitterSubmit(){
  // ログインが完了したかを、タイトルから検知させる
  if (tabFirst.title() == 'Twitter' && !tabFirst.loading()) {
    formSubmitFlg = app.execute(tabFirst, {javascript: "(" + runFunctionSubmitStr + ")(); 'true'"});
  } else {
    while (tabFirst.title() != 'Twitter' || tabFirst.loading()) {
      delay(0.5);
      if (tabFirst.title() == 'Twitter' && !tabFirst.loading()) {
        // 投稿メソッドの実行
        formSubmitFlg = app.execute(tabFirst, {javascript: "(" + runFunctionSubmitStr + ")(); 'true'"});
        break;
      }
    }
  }	
}

// ページが完全に読み込まれるまで、delayを繰り返す
while (tabFirst.loading()) {
  delay(0.5);
  if (!tabFirst.loading()) {
    if (tabFirst.title() != 'Twitter') {
      formSubmitFlg = app.execute(tabFirst, {javascript: "(" + runFunctionStr + ")(); 'true'"});
    }
		
    twitterSubmit();
    break;
  }
}

では、前回と違う部分だけ解説していこうか。

まずは、投稿をさせるスクリプト

// 投稿するメソッド
var runFunctionSubmit = function(){
  var newTweetBtn = document.getElementById('global-new-tweet-button');
  newTweetBtn.click();
	
  var tweetBlock = document.getElementById('tweet-box-global');
  var submitBlock = document.createDocumentFragment();
	
  var text001 = document.createElement('div'),
    text002 = document.createElement('div');
  text001.textContent = 'JXAによるtwitter自動投稿テスト001';
  text002.textContent = 'ああああああ';
	
  submitBlock.appendChild(text001);
  submitBlock.appendChild(text002);
	
  tweetBlock.textContent = null;
	
  tweetBlock.appendChild(submitBlock);
	
  var formDom = tweetBlock.parentNode.parentNode.parentNode.parentNode.parentNode;
  var formChildBtn = formDom.children.item(2).children.item(1).lastElementChild;
	
  var timerF = setTimeout(function(){
    document.activeElement.blur();
    formChildBtn.click();
  }, 500);
};

// 関数を一行テキストに
~ 中略 ~
var runFunctionSubmitStr = runFunctionSubmit.toString();
runFunctionSubmitStr = runFunctionSubmitStr.replace(/€t/g, "");
runFunctionSubmitStr = runFunctionSubmitStr.replace(/€n/g, "");

前回同様、ページで実行する関数を作っただけなので、なんのことないのだが、
ポイントは、3点ある。

1点目は、
6行目で、Fragmentのメソッドを使ってるところ。
これは、レンダリングする前に、あらかじめ、仮DOMを生成するというもので、
こいつをエレメントにAppendChildすると、Fragmentの中身が入るってわけで。
これを使う利点としては、レンダリングが一回で済むのでメモリを節約、
どんな要素に対しても、中身のみを挿入できる点だ。
ツイッターの投稿フォームが、textareaではなく、divの入れ子構造だったので、
これを使った。

2点目は、
23行目からで、setTimeoutを使ってる点。
これ、setTimeout使わないと、
どうしても、フォームに書かれたあとってことを検知できない・・・。
そのタイムラグの穴埋めだ。
だから、もし、上の行のappendeChildeのコールバックとかが可能ならそっちを使った方がいい。
実際、回線状況とかによっては、処理タイミングがおかしくなりそうだ。

3点目は、
24行目で、document内でフォーカスされている全てのエレメントからフォーカスを解除している。
これをやることによって、ツイッターの投稿内容を反映させるJS(ツイッターの仕様)が起動する。

さて、投稿関数はこれでオーケーなので、
あとは、どう実行するか、だ。

// twitterに投稿するメソッドを実行するメソッド
function twitterSubmit(){
  // ログインが完了したかを、タイトルから検知させる
  if (tabFirst.title() == 'Twitter' && !tabFirst.loading()) {
    formSubmitFlg = app.execute(tabFirst, {javascript: "(" + runFunctionSubmitStr + ")(); 'true'"});
  } else {
    while (tabFirst.title() != 'Twitter' || tabFirst.loading()) {
      delay(0.5);
      if (tabFirst.title() == 'Twitter' && !tabFirst.loading()) {
        // 投稿メソッドの実行
        formSubmitFlg = app.execute(tabFirst, {javascript: "(" + runFunctionSubmitStr + ")(); 'true'"});
        break;
      }
    }
  }	
}

さてこっからが大事。
JXAから、投稿を実行させるのだが、
まず、ログインが完了しているかを、JXA上で検知させる必要がある。
(トップと、ログイン後じゃ、DOM構成全然違うからね)
ひとまず、ツイッターは、ログイン前と後では、titleが違うので、そいつを使って検知させる。
例の、条件合うまで、whileでdelayを実行して、処理を遅らせ続けるあれだ。

4行目のifで、タイトルがTwitterであるかつ、ローディングが完了してたら、
そのまま投稿メソッドを実行させている。
違ったら、while開始だ。

ここで、なぜ、また.loadingを使って、読み完了してるかをとっているいるというと、
ログインを常にするって状態の場合、
タイミング的に、ローディングが完了する前に、こっちの関数にきてしまうので、
こっちでもローディング完了チェックが必要なのだ。

では、最後に、前回のwhile文を少し変更する

// ページが完全に読み込まれるまで、delayを繰り返す
while (tabFirst.loading()) {
  delay(0.5);
  if (!tabFirst.loading()) {
    if (tabFirst.title() != 'Twitter') {
      formSubmitFlg = app.execute(tabFirst, {javascript: "(" + runFunctionStr + ")(); 'true'"});
    }
    twitterSubmit();
    break;
  }
}

前回と違う点は、 ローディングが完了したら、すぐさまログインメソッドを実行してたが、 今回は、自動ログインされた時のことを考え、 (前回のやつでも考えるべきだったという) titleが、Twitterでない時にログイン、 そのまま、投稿メソッドを実行している。

前回は、ログインだけだったので、
使い道微妙でしたが、
投稿までいけば、たとえば、Macの起動時に、スクリプトを実行するようにしておけば、
起動時に、Mac起動!今日も頑張るぞ!とか、自動でツイートできるわけですよ。
(ただし、ツイッターは基本同じ文言のツイートはできないので、毎日内容変わるスクリプトを入れる必要がある)

PCからもしょっちゅうツイートする人とか、ぜひやってみてはいかがでしょうか。

ただ実際、ツイッターの仕様とか、DOMの構成とか変わったら、書きなおさなきゃいけないんだけどね、これ(汗
とにかく勉強になったばい。

トラックバック(0)

トラックバックURL:

コメントする