DockerのVolumeマウントのオーナーとfixuidとeuid

なんか慣れてきた

Dockerでホスト側のディレクトリをVolumeマウントして、そこに対してコンテナの中から書き込むと、コンテナ内の実行ユーザーのuidとgidで書き込まれてしまって、ホスト側から触れない。さて、困った。ってなった。

あれー?Macのときはこんなことあったっけ?って思ってたら、Linuxの場合だけみたい。最近Dockerのそういうのばっかりひっかかってるから、なんか慣れてきた!

対応策

んで、ここにいくつか対応策が紹介されてて。そっかー。ってなった。

qiita.com

この中の最後の「実行ユーザーのuidとgidを実行時にentrypoint.shで書き換えてホスト側のものと同じにする」ってのが今の僕のやりたいことに合ってるかもなぁと思いながら・・・。

他にも面白い記事ないかなー

って探してみたら、こういう記事を見つけた。

boxboat.com

へー。コンテナの中にこのfixuidプログラムを置いて実行すれば、さっきのQiitaの記事でやってることをやってくれるっぽい。便利そう。と思いながら読んでて。

あれ?でもUSER sample:sampleみたいに実行ユーザーを指定してるのに、どうやってuidとgidを書き換えてるんだろう?と思ったので

コードを読んでみた

https://github.com/boxboat/fixuid/blob/05869a9ead4f95f7175564984a6ec52fb8ed009d/fixuid.go#L349-L379

   if err := ioutil.WriteFile("/etc/passwd", []byte(newLines), 0644); err != nil {
        return err
    }

Golang分からんけど、/etc/passwd を書き換えてるっぽい。権限なさそうなのになんで書き換えられるんだろう?と思って見てみたら、この辺。

https://github.com/boxboat/fixuid/blob/05869a9ead4f95f7175564984a6ec52fb8ed009d/fixuid.go#L48-L55

// check that script is running as root
if os.Geteuid() != 0 {
        logger.Fatalln(`fixuid is not running as root, ensure that the following criteria are met:
    - fixuid binary is owned by root: 'chown root:root /path/to/fixuid'
    - fixuid binary has the setuid bit: 'chmod u+s /path/to/fixuid'
    - NoNewPrivileges is disabled in container security profile
    - volume containing fixuid binary does not have the 'nosuid' mount option`)
}

あれ?rootで実行されてるって書いてる。

このGeteuid()ってなんだろうなー?

と思って調べたら、この記事見つけて、へーってなった。

Linuxの実効ユーザIDについて実験してみた – ブーログ

実行ファイルのオーナーがroot:rootで、パーミッションが4755だったら、一般ユーザーで実行してもroot権限で書き込めるってことなのかな。

試してみよう!

ということで、実行ファイルを作るのをGoでやってみることにした。

Go書くのはじめてだー!って思ってダウンロードしたら、既にインストールされてて、はて・・・?いつの間に?記憶にないぞ?ってなりつつバージョンアップ。

みようみまねでこんな感じのhello.goを書いた。心を込めていっこずつググりながら書いたから結構時間かかったー。

package main

import "fmt"
import "os"

func main() {
  fmt.Printf("uid: %d\n", os.Getuid())
  fmt.Printf("euid: %d\n", os.Geteuid())

  file, err := os.Create("sample.txt")
  if err != nil {
    panic(err)
  }
  defer file.Close()

  _, err = file.WriteString("Hello")
  if err != nil {
    panic(err)
  }
}

で、ビルドして実行ファイルの権限を確認。Goってお手軽だなー。

❯ go build hello.go

❯ stat --format='%a %U:%G' hello
775 bufferings:bufferings

775で、僕がオーナーになってる。

じゃ、まずは

普通のファイルには書き込めることを確認しとこう。

❯ touch sample.txt

❯ ./hello
uid: 1000
euid: 1000

❯ cat sample.txt
Hello

OKだね。

次は

権限がなかったら書き込めないことを確認。

❯ rm sample.txt

❯ sudo touch sample.txt

❯ ls -al sample.txt
-rw-r--r-- 1 root root 0  8月 26 01:33 sample.txt

❯ ./hello
uid: 1000
euid: 1000
panic: open sample.txt: permission denied

goroutine 1 [running]:
main.main()
        /path/to/project/hello.go:12 +0x1d7

OKだね。

最後に

実行ファイルのオーナーと権限を変えて実行してみよう

❯ sudo chown root:root ./hello

❯ sudo chmod 4755 ./hello

❯ stat --format='%a %U:%G' hello
4755 root:root

❯ ./hello
uid: 1000
euid: 0

❯ cat sample.txt
Hello

おー。書きこめた。こんな仕組み全然知らなかったなー。面白かった。

ところで

元々のVolumeマウントの問題は、色々考えたんだけど僕のこのマシンでしか使わないからuidとgidを1000固定でDockerfileに書いちゃうことにしようと思う。

「PHPはどのように動くのか」を読んだ。その後、Zend EngineとGCのこともちょこっと勉強。

娘の絵本を書いに行った本屋で、面白そうだなーと思って買ってみた。

books.rakuten.co.jp

感想

面白かった!流し読みしただけだけど。「俺、今、難しい本を読んでるぞ!」って雰囲気で「なるほど?」とか言うのを楽しんだ(分かってない

第2章 オペコードのパフォーマンスを考える

オペコードを見ながら「こうなるからパフォーマンスに違いがでるんだよ」って教えてくれてるの「へー!」ってなって面白かった。

第3章 PHPコアの仕組みと開発の定石を知る

zval構造体とかコピーオンライトとかHashTableの仕組みとかの説明が書いてあって面白かった。「zvalは、どの型にも対応している、美しく万能な構造体です」ってところが好き。

第4章 オブジェクト指向だとなぜ遅いのか

第5章 PHP7はなぜ速くなったか

ここまでの説明が全部つながってる感じがしたなー。PHP5とPHP7のコピーオンライトの対応の違いがzvalの作りからも分かるの嬉しい。「PHP7ではその(zvalの)美しさを捨てて、型を差別化することにしました」ってところが好き。

その後の章は

ExtensionとZend Engineのハックなので、本当にさらっと読んだ。

全体的に

自分の理解力のせいでふわっとしか分かってないけど、でも裏側でどういうことが起こってるのかを雰囲気知ることができたので、楽しかった!

読んだ後

Zend Engineの名前のルーツはなんだろう?って気になって見つけた記事。Zendって名前はそういうことなのかー。

[ThinkIT] 第1回:意外と知らないZend Engine (1/3)

Zend Engineについてのスライド。めっちゃ分かりやすい。

www.slideshare.net

そのスライドの中で紹介されてたGCのお話のスライド。これもめっちゃ分かりやすい。

www.slideshare.net

なるほど?

❯ cat hello.php
<?php

$name = "World";
echo "Hello, {$name}";

❯ phpdbg -p\* hello.php
function name: (null)
L1-5 {main}() /path/to/hello.php - 0x7fde320700a0 + 5 ops
 L3    #0     ASSIGN                  $name                "World"                                  
 L4    #1     NOP                                                                                   
 L4    #2     FAST_CONCAT             "Hello, "            $name                ~1                  
 L4    #3     ECHO                    ~1                                                            
 L5    #4     RETURN<-1>              1                                                             
[Script ended normally]

さて

普通のPHP入門の勉強に戻ろっと。

「初めてのPHP」を読んだ。うん。読んだ。

PHP初心者だからPHPの基本を知りたいなーと思って「初めてのPHP」を読んだ。

books.rakuten.co.jp

感想

疲れた(ヽ´ω`)

・・・あれ?

読みながら、フォームとは?とか、HTTPとは?Cookieとは?みたいなWebアプリの話は別にいいからPHPの文法とかの基本的な話って、いつでてくるのかなー?って半分くらいまでいったところでうすうすそんな気がしてたんだけど・・・載ってなかった!

買う本まちがえちゃったやw

気になったこと

「プログラミングやりたい!」って人が最初にこの本を読んだら結構その後大変そうだなー。と思った。仕事ではこういう書き方はしないと思うし、こういう書き方だとセキュリティの穴をいっぱい作ってしまいそう。

学び

でもまぁ面白かった。たまにはこういう基本を振り返るような本も良いね。そもそもPHPよく分かってないところからだったから雰囲気が少し分かったし、色々と学びもあったし。疑問もでてきたし。

  • 文字列が引用符の中で改行OKだからヒアドキュメントっぽく使えそうだけど、みんなはヒアドキュメントとの使い分けどうしてるんだろう?
  • 小数が、浮動小数点数しかでてこなかったけどお金の計算とかは、なんかライブラリーがあるんかな?
  • 数値の最大最小値ってどれくらいなんだろうな?longとかdoubleはないんかなー?
  • $は中括弧の外じゃなくて中に入るんやね。"{$preparation}d $meat"
  • false判定されるの色々ある。0、0.0、空文字列、文字列"0"、定数false、定数null、空の配列。あぶない匂いがする。
  • elseifJavaと違って繋がってる。(どうでもいいか
  • &&||は左側の式だけで判断できるときに、右側の式はチェックされないのかされるのかが気になる。
  • グローバル変数と同じ名前の変数を関数の中で使っても、別の変数として扱われる。
  • 引数の型宣言のbool, float, int, stringはPHP7以降。返り値の型も7以降。
  • namespaceの区切り文字はバックスラッシュ。useasで別名つけられるのいいなー。
  • ??の名前がnull合体演算子(PHP7から)。なんかすごい名前。Groovyのエルビス演算子みたいなものなのかな。
  • list構文で配列を分解して受け取ることができるのかー。
  • PHP REPL便利そう。
  • PSRっての調べておいた方が良さそう。
  • Zendとは?
  • PHPにもOOMとかあるんかな?
  • マルチスレッドみたいなの気にする?

この辺をチェックしながら勉強していこうと思うー!

文法とかなら

公式ドキュメント読むと良いよって教えてもらったので読んでみようと思う。最初にチェックしとけよって自分に言いながら。

PHP: PHP マニュアル - Manual

娘とプログラミング。Code.orgが良かった。

娘達にプログラミングの楽しさを教えてあげられたらいいなと思ってて。でも、僕自身がScratchとかでゴールを決めて何かを教えてあげるのは苦手だし、かといっていきなりJavaとかでコードを書くのも違うかなぁ、なんかちょうどいいのないかな?って思ってたらCode.orgを見つけて。上の娘(小4)と一緒にやってみたら良かった。

code.org

Hour of Code?

最初は1時間でできるチュートリアルのHour of Codeをやろうかなと思ったんだけど。コースカタログ(https://studio.code.org/courses)を見たら、コンピュータ サイエンス入門の20時間コースてのもあったので、自分で両方触ってみた。そしたら、20時間コースの方が基本操作からゆっくりって感じだったので、そっちを一緒にやってみることにした。

コース1

Scratchみたいなブロックを組み合わせてプログラムを組むタイプ。

最初は「ドラッグ&ドロップ」の練習で、次に「ブロックがくっついてるときに切り離す方法(下の方のブロックを動かす)」を言葉ではなく、実際にやりながら「あ、こうやって動かせばいいのか」って学んでた。

一歩ずつクリアしていくので達成感を得ながら、何も言わなくても自然と操作方法を覚えることができてて、よく出来てるなぁって思いながら見てた。

「ドラッグ&ドロップ」が終わったら次はAngry Birdが迷路を進む問題。ここでも、最初は前に一歩進むだけでクリア。次に二歩進んで。それから、曲がることを覚えて、という感じで、良く出来てる。間違いながら「あれ?・・・あぁ、そういうことか」って自分で解決したり「このTNTってなんだろう?ぶつかってみようかな」って爆発したりしてて、横で見てて面白かった。

しばらく一緒にやってみよっかな。

JetBrainsのPhpStormワークショップをひととおりざっくりやってみてメモ

前回の続き

bufferings.hatenablog.com

で、CakePHP触ろうとしたところで、いつも通り横道にそれ、JetBrainsのPhpStormワークショップマテリアルに出会ったのだった。

道具を知ってるかどうかで開発のやりやすさが全然違うもんな。ということで、ざっくりひととおり触ってみた。

結論

面白かった。実際に手を動かしてショートカットを叩いたり機能を使ってみたりすることで、「あー。こんなショートカットなんだー」とか「へー!こんな機能があるのか!」って体験できて良かった。

今すぐ覚えるつもりはあんまりなくて、よく使うやつは勝手に覚えるだろうと思ってるんだけど、それよりも、いつかコード書いてるときに「そういえば、これショートカットあったな」とか「こういうときに使える機能なかったっけ?」って探そうとするきっかけになるからいいな。

バックグラウンド

  • IDEA自体は使ったことがあるから、Java用の使い方で何となく雰囲気は分かってるところからのスタート。
  • IDEA + PHPプラグインなのでPhpStormと微妙に違っていて、主にプラグイン周りでドキドキしながらやった(うまくいかなかったときにそれがプラグインがないからなのか、正しく実行できてないのかの切り分けが必要という意味で)
  • OSはUbuntuなので、Macとはショートカットが違う。

01_Navigation

移動とか検索とかのナビゲーション系をショートカットで実行。好きなのだけ適当に書くと、

  • Shift2回押しで「どっからでも名前を探してくる」。好き。これとCtrl+Shift+Fの「プロジェクト内のファイルの内容から探してくる」があればだいたい見つけられる。
  • Ctrl+Bで「宣言にジャンプ」。めっちゃ使う。
  • Ctrl+Alt+B で「実装にジャンプ」は、知らんかった。覚えときたいな。
  • Ctrl+[Ctrl+]で前の場所に戻ったり進んだりするのもよく使う。

02_Editing

編集系。

  • Ctrl+Spaceは書いてる途中で空気読むやつ。
  • Alt+Enterは書いた後に空気読むやつ。
  • Ctrl+Shift+Enter知らんかった。セミコロン打ってくれたりして賢い。これは使いたいな。
  • へー。PHPだから配列とかで型情報が分からないやつはPHPDocコメントで指定したらIDEAが把握するのかー。
  • あとはまぁ、コンテキストメニューでなんとかなるかな。

03_Inspections

  • Ctrl+Alt+Sで「設定を開く」のはワークショップやってるとめっちゃ使うので覚えてしまった。
  • PHP Mess DetectorとPHP Code Snifferってのがあるのかー。静的解析ツールなのかな?詳しくはよく分かってない。

04_Live_Templates

Live Templatesはたまに眺めておいても良いかなーと思った。prifとかpubfとか便利そう。

f:id:bufferings:20180815022031p:plain

05_Refactoring

は、Shift+F6のRenameだけ覚えておいて、あとはコンテキストメニューでいいかな。

06_Debugging

  • ブレークポイントに条件つけられるのはループとかのときに便利そう。
  • PHPデバッグのことをよく分かってないので仕組みがちゃんと分かってないんだけど。Vagrantにsftpでつないでファイルを送り込んで、そこの中で実行してるやつにリモートデバッグしてるっぽい?面白い。
  • 07_Debugging_Web_Applicationは全く分かってない。なんだかよくわからないけど書かれてる通りにやると書かれてる通りに動く。という感じ。ブックマークレットとか。ふむー。Xdebugを勉強したらいいのかな?プロファイリングも合わせて。
  • 突然、Node.js + Karma + Jasmineのテストが出てきて、とりあえず名前と見た目だけ覚えておこうってなった。

07_Todo

まぁ、Todoコメントとかを拾うことができるビューがあるんだなーってくらい。

08_Testing

Behatはやらなかった。なんとなく。

PHPUnitのテストは環境設定にハマった。Vagrant/var/wwwがIDEAから見えなくて、でも直接Vagrantの中に入ってみたら見える。なので、IDEAが使ってる接続方法で接続してみたら権限が???になってて見えてない。ということで、IDEAからもvagrant ssh-configの情報で接続するようにした。ら見えた。結構悩んだ。

あとは、テストの実行とかはいつも通りだった。

09_Version_Control

便利だよね。

10_Database

これも便利よね。接続しといたらDBの補完がコード中でも効くのすごいね。

11_Deployment

リモートホストに(今回で言うとVagrant)にファイルを転送する方法。使うかなー?どうかなー?

12_Tools

REST Client

知らなかった。便利そう。

Composer

PHPで依存関係を管理するのには、Composerを使うんだろうなーって雰囲気を感じてる。

Command Line Tools

便利そうだけど、普通にターミナル使うかな。

IDEAに追加したプラグイン

  • PHP
  • Vagrant
    • このワークショップではVagrantに色々入ってて、そことつないでゴニョゴニョするので入れといた。コマンドラインから色々やるならこれは要らないのかもしれない。でも、もしかしたら何かの機能がこのプラグインの機能を使ってるから必要なのかもしれない。
  • PHP Remote Interpreter
  • Node.js
  • Command Line Tool Support
    • おまけで紹介されてたので入れてみた。

僕の課題

  • このワークショップ、Vagrantの代わりにDockerを使うブランチがあるみたいなので見とく
  • PHPデバッグの仕組み(Xdebug)を学ぶ

PHPに入門してみる。phpbrewとIDEA+PHPプラグインでCakePHP3の準備。

2018-09-22 追記

結局phpを直接インストールするのをやめて、Dockerで遊ぶことにしました。PhpStorm+DockerでCakePHPの開発環境を作ってみた - Mitsuyuki.Shiiba

追記ココマデ

昨日も書いたとおりCakePHPに入門することにしたので。勉強しつつメモ。今日はCakePHPに触る準備までを目標にしてみる。

phpbrew?

github.com

色んなバージョンを簡単に切り替えられる方がいいよなーって思って、SDKMAN!とかrbenvみたいにそういうのないかなー?って見てたら、phpenvってのとphpbrewってのがあるみたいで、なんとなくの参考程度にGoogleトレンドで見てみてphpbrewにすることにしてみた。

phpbrewのインストール

ということで、ここを参考にして事前準備をして

Requirement · phpbrew/phpbrew Wiki · GitHub

このPCがUbuntu18.04だからphp7.0はないみたいなので、7.2にして入れといた。

apt install \
  php7.2 \
  php7.2-curl \
  php7.2-json \
  php7.2-cgi \
  php7.2-fpm \
  autoconf \
  automake \
  libxml2-dev \
  libcurl4-openssl-dev \
  libssl-dev \
  openssl \
  gettext \
  libicu-dev \
  libmcrypt-dev \
  libmcrypt4 \
  libbz2-dev \
  libreadline-dev \
  build-essential \
  libmhash-dev \
  libmhash2 \
  libxslt1-dev

んで、あとはここに書いてある通りにphpbrewをインストールしてinitして.zshrcに設定をいれて

phpbrew/README.ja.md at master · phpbrew/phpbrew · GitHub

phpbrew known したら動いたみたいで良かった。

f:id:bufferings:20180812152222p:plain

phpのインストール

さっきaptで入れたのが7.2.7みたいなので

❯ php -v
PHP 7.2.7-0ubuntu0.18.04.2 (cli) (built: Jul  4 2018 16:55:24) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.2.7-0ubuntu0.18.04.2, Copyright (c) 1999-2018, by Zend Technologies

7.2.8入れたらいいかな?と思って。あと、CakePHPを使いたいのでここを参考にして

[PHP] PHPBrewで複数のバージョンを使い分ける - YoheiM .NET

variantというもの?のphpbrewが用意してくれてるdefaultに、intlとmysqlを足してインストール。

❯ phpbrew install 7.2.8 +default +intl +mysql

結構時間がかかった。

❯ phpbrew list -v
* (system)
  php-7.2.8      
    Variants:  +intl +mysql +xml +opcache +bcmath +bz2 +calendar +cli +ctype +dom \
              +fileinfo +filter +ipc +json +mbregex +mbstring +mhash +mcrypt +pcntl +pcre \
              +pdo +pear +phar +posix +readline +sockets +tokenizer +curl +openssl +zip

ε-(´∀`*)ホッ

それをメインに

じゃ、それをメインにしよっと。

~
❯ phpbrew switch 7.2.8

~
❯ phpbrew list
* php-7.2.8      

~
❯ php -v
PHP 7.2.8 (cli) (built: Aug 12 2018 15:44:22) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies

これで大丈夫そう。

戻すには?

あれ?aptで入れたやつを使うように戻すにはどうするんだろう?と思ったら phpbrew off で良いみたい。ちゃんと日本語ドキュメントに書いてあったや。ヘルプを見たら off は一時的に無効化で phpbrew switch-off がずっと無効化されるみたいね。

~
❯ phpbrew off

~
❯ phpbrew list
* (system)
  php-7.2.8      

~
❯ php -v
PHP 7.2.7-0ubuntu0.18.04.2 (cli) (built: Jul  4 2018 16:55:24) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.2.7-0ubuntu0.18.04.2, Copyright (c) 1999-2018, by Zend Technologies

ふむふむ。満足したので、7.2.8に戻しとこっと。

❯ phpbrew switch 7.2.8

IDEA + PHP Plugin

IDEAのライセンス持ってるので、そこにPHP Pluginを入れといた。

んで、CLIを設定。ここに置いてあるっぽいから

❯ phpbrew list -d
* php-7.2.8      
    Prefix:   /home/bufferings/.phpbrew/php/php-7.2.8

こんなんでいいんかな?

f:id:bufferings:20180812221502p:plain

CakePHPプロジェクト作成

Cakeのドキュメントを見た感じだとComposerというものを使って作るっぽいので雰囲気でやってみた。

https://book.cakephp.org/3.0/ja/quickstart.html#cakephp

PHPのComposerプロジェクトで

f:id:bufferings:20180812205429p:plain

チュートリアルにあるみたいに、cmsって名前のプロジェクトをcakephp/appで作ってみる。こんな感じかな?

f:id:bufferings:20180812222200p:plain

さっき設定したphp-7.2.8をinterpreterに選んで、チュートリアルについてた --prefer-dist ってオプションを足しといた。

しばらく待ったらできたっぽい(∩´∀`)∩ワーイ

f:id:bufferings:20180812222647p:plain

起動してみる

むむむ。IDEAからどうやって起動するんだろう?・・・んー、分からんしとりあえず、ターミナルから bin/cake server でいっか。

f:id:bufferings:20180812223756p:plain

(∩´∀`)∩ワーイ

今日はここまでかなー。次は、適当にチュートリアルに沿って進めてみるか。

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使うほどじゃないかってなってやめたりして。

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