HTML の <ruby> に思うこと

ニュースサイトから記事本文を抜き出して利用しようという作業をやってみて、<ruby> 要素がほかの要素に比べてかなり異質なものあることに気がつきました。

ニュースの本文は

...<p><span class="colorC"><ruby>気象庁<rt>きしょうちょう</rt></ruby></span>は「もっと<a href="javascript:void(0)" class="dicWin" id="id-0000"><ruby><span class="under">二酸化炭素</span><rt>にさんかたんそ</rt></ruby></a>を<ruby>出<rt>だ</rt></ruby>さないようにしなければなりません」と<ruby>話<rt>はな</rt></ruby>しています。</p>...

のようになっています。BeautifulSoup の .text で単純にタグを削除するだけでは

気象庁きしょうちょうは「もっと二酸化炭素にさんかたんそを出ださないようにしなければなりません」と話はなしています。

と、ルビが本文に混じってしまい、まともな文になりません。事前に rt 要素を削除しておく必要があります。

実際に行った作業は「HTML からタグを除去して本文を取り出す」なのですが、単純に行って得られるものは上記のとおりで、それを「本文」とは呼べないと思うのです。

マークアップ言語

はじめに、

気象庁は「もっと二酸化炭素を出さないようにしなければなりません」と話しています。

という“内容”そのものがあります。これに“意味”をつけるために、たとえば「気象庁」は固有名詞という意味をつけるためにこれを <span> で囲み、それからこの文ひとつでひとつの段落という意味をつけるためにこれを <p> で囲みます。これがマークアップであり、そのためのタグであるはずです。

次の段階で、これをどう表示するか、たとえば固有名詞は文字を赤色にし、段落はそのはじめに改行する、というものはさらに分離してスタイルシートで記述することになっています。

逆向きに考えてみます。ブラウザで表示されるもののソースから表現のスタイルを取り除き、“意味”づけのタグを取り除いたら、残るのは“内容”そのものでなければならないはずです。

ところが今回の例では上述のように、余計なものが残ったままになります。

ルビは“内容”か

私は「ふりがなは内容そのものではなく、それに付け加えているもの」と考えますので、「余計なもの」と書きました。たとえば、ここの言葉は重要と思って下線を付ける、固有名詞ははっきり分かるように文字の色を他と違える、というのと同じで、内容に対して何らか助けのために付け加えているものだと思います。ですから、そういったものを剥ぎ取ったときには残っていてはならないと考えます。

そう考えたときに、現在の仕様の <rt> はかなり異質です。下線や色付けがタグ内の属性 (attribute) とそれに紐付けられるスタイルで表現されるように、ふりがなもタグではなく属性で示されるのが適当だと思います。

仮に <span>ruby という属性が定義されているとして

...<p><span class="colorC" ruby="きしょうちょう">気象庁</span>は「...」と<span ruby="はな">話</span>しています。</p>...

と書くのがもっとも自然のように感じます。

既存の仕様である <a> を逆に考えてみましょう。<a> にとって属性 href は、それがなければほとんど意味がないくらい重要なものです。

もっと<a href="example.html>二酸化炭素</a>を出さないようにしなければなりません

だからと言ってもしそれがタグになっていて

もっと<a>二酸化炭素<href>example.html</href></a>を出さないようにしなければなりません

と書かれるものだとしたら、その違和感がわかろうというものです。

<rt> のあり方

前節に書いたように、ルビは属性で指定されるのが自然だと思います。ではどうすればいいのか。

<ruby>関連要素って駄目駄目」に、この問題の歴史 (なんと不幸な!) から対案まで解説されていました。この記事をぜひじっくり読んでいただきたい。

この記事の「脱法ルビ」が、当面の対応策です。仕様に反せず、仕様内でできる策です。現状ここまでできるのですから、ここに示されているスタイルをレンダリングエンジン側で装備し、属性名を data-ruby ではなく ruby とできるよう仕様のほうが改訂されたら、当分は現在の <ruby>, <rt> との共存期間があったとしても、もうほとんど解決ではないですか。

……と思ったら、このスタイル、すなわち display: ruby-text を実装しているのは Gecko (Firefox) だけで、Webkit (Safari) も Blink (Chrome) も未対応でした (2018年6月現在)。ひどい状況です。

ルビを実際に使う人やデザイナのほとんどは、ほんの少し首をかしげるくらいはあっても、仕様に従って使うだけで、その仕様がどうあるべきかまで問い返したりはしません。そもそも世界のうちでごく限られた言語だけでしか知られておらず、その中でも使う人はごく少数ですから、問題にする人も滅多にいなければ対応もぜんぜん進まないのでしょう。ほんとうに不幸です。

ニュースのスクレイピングでタイピング練習

ひらがな数文字を打ち返すだけのタイピング練習は案の定すぐに飽きてしまったので、何か別のネタを考えなくてはならなくなりました。飽きないためには膨大かまたは頻繁に更新される元データがあればいい、青空文庫かな、でも小学生に向いているものがどれほどあるかしらん、頻繁に更新されるといえばニュース、でもこれまた小学生向きではなさそう……と思ったら実にぴったりのものがありました。NHK NENS WEB EASY です。ひとつの記事で50字ほどの文が10ほど。意味もわかりやすくて量もちょうどいい。かなり手間をかけて作られているようです。

さて、これをなんとか持ってきてタイピング練習の材料にしようと思ったのですが、何しろ本業でも何でもないので情報を集めるところからスタートでした。今回やっていることは実は「スクレイピング」という程のこともないのですが、そのとっかかりということで、せっかくなのでここに記録しておきます。

環境の準備

NHK NENS WEB EASY のページの肝心な部分は JavaScript で生成されているようで、Python で単純に requests.get(url) とやっても、ブラウザで見ている HTML ソースとは別のものしか得ることができません。そこでまず、ブラウザが実際に表示するページを取得できるようにします。

Debian パッケージ chromium-driver をインストールします。

sudo apt-get install chromium-driver

これを Python から使うためにライブラリ Selenium をインストールします。

pip install selenium

取得した HTML から必要な箇所を切り出すのには BeautifulSoup4 を使います ((Python にはじめからある html.parser でもある程度できます。また Selenium にも同様の機能があるようです。))。

pip install beautifulsoup4

でインストールします。

ニュースサイトの構造

ニュースサイトによくあることですが、各記事の URL は数字の羅列のような名前で、先頭ページではそれが日々更新されます。

ブラウザのデベロッパーツールで NHK NENS WEB EASY の先頭ページ https://www3.nhk.or.jp/news/easy/ の構造を見てみます。

<div class="top-news-list__pickup news-list-item" id="js-news-pickup">
  ...
  <h1 class="news-list-item__title is-pickup">
  <a href="./k10011463631000/k10011463631000.html"><em class="title"><ruby>日本<rt>にっぽん</rt></ruby>の<ruby>二酸化炭素<rt>にさんかたんそ</rt></ruby>の<ruby>濃度<rt>のうど</rt></ruby>が<ruby>今<rt>いま</rt></ruby>まででいちばん<ruby>高<rt>たか</rt></ruby>くなる</em><time class="time">6月5日 11時30分</time></a>
  </h1>
</div>

最初に大きく取り上げられている記事は <div id="js-news-pickup"> で、その中の <h1> の中の <a> から記事個別ページの URL が得られます。

その記事個別ページを同様にブラウザのデベロッパーツールで見てみると、記事本文は <div id="#js-article-body"> にあることがわかります。これを切り出してくればいい訳です。

スクレイピング

参考にした (というか、ほぼそのままコピーさせてもらった) コードは「Python Webスクレイピング テクニック集」の「JavaScriptによる描画に対応する」です。

ソース中のコメント「ブラウザを起動する」の箇所で、ブラウザのパスを指定する必要がありました。Debian のパッケージを使っている場合

driver = webdriver.Chrome(executable_path='/usr/bin/chromedriver', chrome_options=options)

です。

切り出し

1回めの

        # ブラウザでアクセスする
        siteurl = "https://www3.nhk.or.jp/news/easy/"
        driver.get(siteurl)

        ...

        # BeautifulSoupで扱えるようにパースします
        soup = BeautifulSoup(html, "html.parser")

        # id で特定の要素を切り出す
        href = soup.select_one("#js-news-pickup h1 a").get('href')

で記事個別ニュースの URL が得られるので、2回めは

        driver.get(newsurl)
        html = driver.page_source.encode('utf-8')
        soup = BeautifulSoup(html, "html.parser")
        # ルビを削除
        for s in soup(['rt']):
            s.decompose()

        ...

        # ニュースの本文
        text = soup.select_one("#js-article-body").text

で、記事本文を切り出します。

ルビを削除

ニュースの本文は

...<p><span class="colorC"><ruby>気象庁<rt>きしょうちょう</rt></ruby></span>は「もっと<a href="javascript:void(0)" class="dicWin" id="id-0000"><ruby><span class="under">二酸化炭素</span><rt>にさんかたんそ</rt></ruby></a>を<ruby>出<rt>だ</rt></ruby>さないようにしなければなりません」と<ruby>話<rt>はな</rt></ruby>しています。</p>...

のようになっています。BeautifulSoup の .text で単純にタグを削除するだけでは

気象庁きしょうちょうは「もっと二酸化炭素にさんかたんそを出ださないようにしなければなりません」と話はなしています。

と、ルビが本文に混じってしまい、まともな文になりません。事前に rt 要素を削除しておく必要があります。

この作業をやってみて、rt がほかとは異質なタグ(要素)であることを実感しました。これについてはまた別の記事に書こうと思います。

1文ずつに分解

記事を「。」で区切り、リストにします。「。」自身も含めたいので split が使えません。NHK のニュース記事で全体の最後に「。」がないことはまさかないだろうと仮定して、

        lines = re.findall(".*?。", text)

とします。あとは Errnot がこれを1文ずつ表示するようにするだけです。これでこの bot を相手に XMPP のチャットでオウム返しにタイピングの練習をすることができるようになりました。

それにしても、いろいろ寄せ集めるだけでこれだけできるのですから、便利な世の中になったものだとつくづく思いました。

XMPP チャットと bot でタイピング練習

スウちゃん (仮名、4年生) がパソコンでやりたいことのひとつが「字を打てるようになる」こと。

学校でローマ字も習ったし、パソコンには以前からちょっとは触っているので、人差し指打法でたどたどしくは打てます。せっかくだから「正しい」タイピング技法 (タッチタイピング) を習得させてやりたいと思っています。父である私はそれをやらずに我流のまま来てしまいました。ある程度までは速く打てるものの指が定まっていないため間違いが多く、打ち直しのため結局は遅いことになってしまっています。きれいにすらすらと打てる人は本当に間違いも少ないので、スウちゃんには最初からその方向でいってほしいと思っているのです。

子どもでもできそうな練習アプリやサイトを見て回りました。Windows 向けはまず却下。ウェブ版のほとんどは Flash ですが、いまどきこれで大丈夫なのでしょうか。Debian の Firefox に Flash を入れて動かすのが面倒なので、これもできるだけ避けたいところです。

いずれにしても、練習の順番に疑問を感じます。たいていのものはまずホームポジション fj からです。しかしこの2字とも日本語での出現頻度はかなり小さいはずです。jjj fff など日本語でもないから、つまらないことこの上なしです。もっと「日本語のローマ字入力のためのタイピング練習」というのがないものでしょうか。

ちょっと検索してみて、こんな意見を見つけました。「これぞ最速!ブラインドタッチ(タッチタイピング)の効率的な練習法」です。

  1. 指を基本となるホームポジションに置く
  2. パソコンのキーボードを見ながらローマ字のAIUEOを入力する
  3. キーボードを見ずに、パソコン画面を見ながらAIUEOを何度も入力する
  4. 子音を入れつつ練習する
  5. ……

そうだよなあ。この順番のほうがうんと納得がいきます。練習ソフトでこの順番になっているものは意外と見つかりません。

というわけで、既存の練習ソフトに頼らないことにしました。

前回、チャット (XMPP) のクライアント使えるようにしたので、チャットの相手側に私がいて「あお」「いおえ」……などと打ってやり、それをオウム返しに打つことにしました。日本語にない単語になってしまいますが、この際しばらく我慢してもらうことにして。

ついでに、日本語でのキーの出現頻度を検索して見つけた「ローマ字頻度表」によると、母音、n の後は t, k, s, … らしいので、この順に1字づつ (ひらがなでいえば5音づつ) 増やしていくことにしましょう。

さて、ずっと付き合うのは私もたいへんなので、Errbot が相手をするようにプラグインを書いてみました。超いい加減ですがメインはこんな感じ。

    @re_botcmd(pattern=r"^(.*)$", prefixed=False, flags=re.IGNORECASE)
    def question(self, msg, match):
        """文字列を表示する。それを入力してみてください。"""
        if not self.active:
            return None

        s = [[] for i in range(self.levelmax+1)]
        s[0] = 'あいうえおん' * 15
        s[1] = s[0] + 'かきくけこたちつてと' * 10
        s[2] = s[1] + 'さしすせそやゆよ' * 10
        s[3] = s[2] + 'なにぬねのはひふへほ' * 8
        s[4] = s[3] + 'らりるれろ' * 8
        s[5] = s[4] + 'まみむめもわを' * 6
        s[6] = s[5] + '、。ー' * 5
        s[7] = s[6] + 'がぎぐげござじずぜぞ' * 5
        s[8] = s[7] + 'だでどばびぶべぼ'  * 3
        s[9] = s[8] + 'ぢづ'
        s[10]= s[9] + 'ぱぴぷぺぽ' * 2

        reward1 = ['😀','😃','😉','🙂','❤','💗','💞','🎀','👌','✌','👏']
        reward2 = [' OK! ',' いいね! ',' ばっちり! ',' うまい! ',' じょうず! ']

        if match.group(0) == self.get('word'):
            yield(random.choice(reward1) + random.choice(reward2) + random.choice(reward1))

        self['word'] = ''.join(random.choices(s[self.level], k=self.length))
        yield(self['word'])

UI は XMPP クライアントだし応答は Errbot にお任せなので、書くのは実質ほんのわずかです。文字どおり子供だましで、そう遠からず飽きてしまいそうですが。

ノートパソコンに Debian をインストール

スウちゃん (仮名、4年生) が「ノートパソコンがほしい」と言い出しました。学校でも PC に少し触ることがあるし、家でも親がスマホやタブレットでなく大きな画面でカタカタやっているので興味はあるのでしょう。ほう、では何をやりたいか書き出してごらん、と言うと、「字を打てるようになる」「ゲーム」「メール」「プリント」とのこと。「プリント」って何と聞いてみると、宿題などのために調べ物をしたらそれをプリンターに出したいということでした。

ラップトップ型パソコン

興味を持ったときがチャンスだと思い、早速探してみました。はじめ中古は考えていなかったのですが本人に「中古でもいい?」と聞いてみたらまったく気にしないとのことで、1年落ちで14インチ、メモリ8GB、SSDが240GBという一段上のスペックのものが安い価格で見つかり早速購入。いまどき OS なしのパソコンを探すのは難しいので Windows10 も込みです。状態説明で「タッチパッドにテカリあり」となっていましたが、仮に新品でも使いはじめたらすぐにこのくらいになってしまうだろうという程度で、それに普段使う角度から見るとまったく気になりません。ほかに特に問題はなく、今のところいい買い物だったと思っています。

さて Windows10 が入っていても、私自身が普段使いしておらず面倒を見きれないので、さっそくおさらばすることにします。購入時に聞いたのですが Windows のプロダクトキーはどこかに記載したものは一切ないとのことでしたので、一度 Windows10 で起動して確認し (その方法はネットで探しました)、控えておきます。これで、そのうちどうしても他所と同じ Windows じゃないとと言い出した時には戻せます。

Debian stretch のインストール

現時点での安定版 Debian stretch をインストールしていきます。

まず親機で netinst イメージ (300MB ほど) を USB メモリにコピーします。これをラップトップ機に挿して起動し、あとは画面の指示に従っていくだけです。途中、ハードウェアを解析し必要なものを要求してくるので、それ (firmware-iwlwifi の deb パッケージ) を親機で 別の USB メモリに入れて、ラップトップ機の別の USB の口から与えました。ネットがつながればあとはあっさりインストール完了です。

task-xfce-desktop を選択したので、LibleOffice はじめ一通りのアプリケーションが揃っています。日本語変換は fcitx と mozc (キー設定はMS-IME互換) の組み合わせ。デスクトップのパネルを下に配置することで Windows にかなり似せました。「学校でさわるのとちょっと違うよ」と言い、スウちゃんも今の時点では難なく受け容れています。

スウちゃんの希望 (「ゲーム」は昨年のクリスマスに Nintendo Switch をもらったこともあって却下) に沿うため、thunderbird をインストールしましたが、実際にはまだ使っていません。XMPP (チャット) クライアントの Gajim を入れてやり、同じ家の中から私が話しかけてやります。スピーディだし、絵文字もすぐ選べるから楽しいし、なんとか返答しようとしてキー入力もやります。動機づけには十分です。

印刷のためには、cups のパッケージを一通りと、うちにあるのは Brother のプリンターなので、メーカーのサイトから入手したドライバーをインストール。そうか、そのドライバーは i386 用なので、その前にマルチアーキテクチャ対応にする必要がありました。

久しぶりにクリーンインストールを行いましたが、それにしても Debian のインストールからデスクトップ環境構築までがこんなに楽になっているとは驚きました。

あらためてチャットサポートを構築する

Converse.js という XMPP の Web クライアントを見つけて「チャットサポートを構築する」の記事を書いたのは2014年初頭でした。Converse.js が公開されて1年めほどでバージョンは 0.7.2 でした。

2018年4月現在の Converse.js は 3.3.4 で、かなり進歩しています。情報もあちこちに出てきて、以前は苦労したことも現在では簡単にできるようになっていました。本家ページ以外に情報が散らばっていましたのでここにまとめてながら、あらためてチャットサポートを設置してみます。

PreBind

その前に、今回の構築には用いませんが、以前は私がよく理解できておらず適当に誤魔化していた PreBind について、簡潔に解説したページ PreBind: connettersi senza password がありましたので、紹介しておきます。

PHP のクラス XMPP Prebind for PHP をダウンロードしておき、次のようにインスタンス化します。

<?php 
include 'xmpp-prebind-php/lib/XmppPrebind.php';

$boshUrl = 'https://example.com:5280/bosh/';  // 環境に合わせて変更
$server = 'example.com';           // 環境に合わせて変更
$username = 'your_username';   // 環境に合わせて変更
$password = 'your_password';   // 環境に合わせて変更
$resource = rand(10000,99999); // ここでは5桁の数字。環境に合わせて変更

$xmppPrebind = new XmppPrebind($server, $boshUrl, $resource, false, false);
$xmppPrebind->connect($username, $password);
$xmppPrebind->auth();
$sessionInfo = $xmppPrebind->getSessionInfo(); // array containing sid, rid and jid

header('Content-Type: application/json');
echo json_encode($sessionInfo);

?>

これだけで動かしてみると分かりますが、sid, rid, jid の3つを JSON 形式で返すものです。

次に、Converse.js の初期化の際に、上のスクリプト名 (prebind.php だとします) を prebind_url で指定します。

<?php
  $boshUrl = 'https://example.com:5280/bosh/';  // 環境に合わせて変更
  $server = 'example.com';      // 環境に合わせて変更
  $username = 'your_username';  // 環境に合わせて変更
  $prebindUrl = 'prebind.php';  // 上のスクリプトの URL
?>
<html>
  <head>
    ...
    <link type="text/css" rel="stylesheet" media="screen" href="https://cdn.conversejs.org/css/inverse.min.css" />
    <script src="https://cdn.conversejs.org/dist/converse.min.js"></script>
    ...
  </head>
  <body>
     ...
    <script>
      converse.initialize({
        i18n: 'ja',
        locales_url: 'https://cdn.conversejs.org/locale/{{{locale}}}/LC_MESSAGES/converse.json',
        authentication: 'prebind',
        bosh_service_url: '<?php echo $boshUrl; ?>', 
        keepalive: true,
        jid: '<?php echo $username."@".$server; ?>',
        prebind_url: '<?php echo $prebindUrl; ?>',
        prebind: true,
        ...
      });
    </script>
  </body>
</html>

この HTML は誰にも見られることになりますが、パスワードは事前接続にのみ用いられているので、ここに書かずに済むというわけです。

チャットサポートの構築

XMPP サーバーで匿名接続を利用できる場合、Converse.js の初期化時に authentication: 'anonymous' とすることで、実は上述の PreBind はまったく使わずに済みます。

以下、この匿名接続を使ってチャットサポートを組み立てます。

  • 客側は Converse.js で匿名 (一時的な JID を使う) とする。
  • 窓口側の JID (たとえば support@example.com) は共有名簿として匿名接続時に与えられる。
  • 客側の Converse.js は自動的に窓口側 JID を呼び出しチャット窓を開く。

という流れです。

サーバー側設定

BOSH または Websocket

前述の PreBind でもそうでしたが、BOSH (または Websocket) を利用しますので、XMPP サーバーに付属のそれらの機能を有効にしておきます。Ejabberd の場合、ejabberd.yml は次のようになります。

listen:
  ...
  -
    port: 5280
    ip: "::"
    module: ejabberd_http
    request_handlers:
      "/ws": ejabberd_http_ws
      "/bosh": mod_bosh
    http_bind: true
    register: true
    tls: true
  ...
modules:
  ...
  mod_bosh: {}
  ...

匿名サーバー

通常クライアントがサーバーに接続する場合は、事前に作成しておいた user@example.com という形をした JID とパスワードが必要になります。匿名サーバーは、そのサーバー名だけを指定して接続を試み、@ より前のユーザー名を乱数のようにそのつど生成して接続します。

サーバーアプリケーション Ejabberd の場合、ejabberd.yml に次のように設定して SASL 匿名サーバー (anonymous.example.com という名前だとします) を設定します。

hosts:
  - "example.com"
  - "anonymous.example.com"
  ...
host_config:
  "anonymous.example.com":
    auth_method: [anonymous]
    anonymous_protocol: sasl_anon

なお今回は特に関係ありませんが、匿名サーバーで接続するユーザーについては、悪用を防ぐため権限を絞っておくのが望ましいでしょう。たとえば、別のサーバーとの通信の禁止、MUC 談話室や PubSub ノードの作成の禁止、などです。

窓口側のJID

窓口側のJID (例: support@example.com) の作成は、通常どおり行います。

共有名簿 (shared roster)

匿名サーバーの場合、接続のたびにアカウントが新たに一時的に生成されるため、その JID には相手先名簿がありません。そこで、共有名簿 (shared roster) という機能を使うことにします。

まず Ejabberd の設定ファイル ejabberd.yml でこの機能を有効にしておきます。

modules:
  ...
  mod_shared_roster: {}
  ...

Ejabberd のウェブ管理画面の「バーチャルホスト -> anonymous.example.com -> 共有名簿グループ」の画面で、まずひとつのグループを作成します。たとえば

  • 名前: support
  • 説明: サポート窓口
  • メンバー: support@example.com
  • 表示グループ: (空欄)

のようにします。ここで support@example.com は、窓口側 (問い合わせを受ける側) の JID です。

さらにもうひとつグループを作成します。

  • 名前: all
  • 説明: 全ユーザー
  • メンバー: @all@
  • 表示グループ: support

ここで @all@ は、「全ユーザー」を意味します。

これで、「anonymous.example.com の全ユーザーの名簿に support グループのメンバー (ここでは support@example.com のみ) を自動的にセットする」ことができました。

Converse.js の設定

チャットウィンドウを設置する Web ページで、Converse.js を読み込むようにいくつかの行を書き加えます。Converse.js そのものを同じサイトに置いてもいいですし、CDN から読み込むようにもできます。

<head>
    ...
    <link type="text/css" rel="stylesheet" media="screen" href="https://cdn.conversejs.org/css/converse.min.css" />
    <script src="https://cdn.conversejs.org/dist/converse.min.js"></script>
    ...
</head>

それから、パラメータとともに Converse.js を初期化することで、そのページにチャットウィンドウを設置します。基本は

converse.initialize({
        websocket_url: 'wss://example.com:5280/ws/',
        authentication: 'anonymous',
        jid: 'anonymous.example.com',
        auto_login: true,
})

です。ここでは BOSH ではなく WebSocket を使う場合です。匿名接続の場合 jid はサーバー名のみにします。自動ログインでサーバーに接続するのですが、このままでは相手先とのチャットを開くところはできません。

自動で相手先とのチャットを開く

この情報は本家ではないところにありました。Converse opening a chat from API の回答で Converse.js の作者がプラグインを紹介しています。

converse.plugins.add('chatsopen', {
      initialize: function() {
        var _converse = this._converse;
        Promise.all([
            _converse.api.waitUntil('rosterContactsFetched'),
            _converse.api.waitUntil('chatBoxesFetched')
        ]).then(function() {
          _converse.api.chats.open('support@example.com');
        });
      }
    });

初期化時にこれを利用するようにします。

whitelisted_plugins: ['chatsopen'],

コントロールボックスは全部隠してしまうこともできます。

blacklisted_plugins: ['converse-controlbox'],

まとめ

以上をまとめて (ついでに他のパラメータも追加して)、チャットウィンドウを設置する Web ページまるごとの例です。

<html>
<head>
    <meta charset="utf-8"/>
    <title>チャットサポート</title>
    <link type="text/css" rel="stylesheet" media="screen" href="https://cdn.conversejs.org/css/converse.min.css" />
    <script src="https://cdn.conversejs.org/dist/converse.min.js"></script>
</head>
<body>
<h1>チャットサポートのページ</h1>
<p>右下の窓で話しかけてください。</p>
</body>
<script>
    converse.plugins.add('chatsopen', {
      initialize: function() {
        var _converse = this._converse;
        Promise.all([
            _converse.api.waitUntil('rosterContactsFetched'),
            _converse.api.waitUntil('chatBoxesFetched')
        ]).then(function() {
          _converse.api.chats.open('support@example.com');
        });
      }
    });

    converse.initialize({
        i18n: 'ja',
        locales_url: 'https://cdn.conversejs.org/locale/{{{locale}}}/LC_MESSAGES/converse.json',
        authentication: 'anonymous',
        websocket_url: 'wss://example.com:5280/ws/',
        auto_away: 300,
        auto_reconnect: true,
        keep_alive: true,
        jid: 'anonymous.example.com',
        auto_login: true,
        allow_logout: false,
        allow_contact_removal: false,
        allow_contact_requests: false,
        allow_muc: false,
        allow_otr: false,
        allow_registration: false,
        blacklisted_plugins: [
            'converse-bookmarks',
            'converse-controlbox',
            'converse-mam',
            'converse-muc',
            'converse-notification',
            'converse-otr',
            'converse-register'
        ],
        whitelisted_plugins: ['chatsopen'],
        forward_messages: false,
        message_carbons: false,
        message_archiving: 'never',
        use_vcards: true,
        view_mode: 'overlayed',
        visible_toolbar_buttons: {
            call: false,
            clear: false,
            emoji: true
        }
    });
</script>
</html>

実際に設置してみたのが次です (サーバー名など変更)。

醤油差し

多くの人に経験があると思うのですが、醤油差しの液だれは地味にストレスです。これまでに100均のものをはじめいくつも使ってきましたが、どうもしっくりきません。何かいい知恵はないかとネットで検索してみると出るわ出るわ……。皆さん同じような思いをされているのでしょう。

そういうページで紹介されているものは案外種類が少なく、ベスト10となると多少順位は変わっても同じような顔ぶれがランクインしています。

最後に使っていた安物の醤油差しは差し口からの液だれなのか蓋と本体の継ぎ目からの漏れなのか判然としなかったのですが、周囲の汚れることおびただしかったのです。そこで、まず頭で考える私にはひとつこれだけはと思っている点がありました。注ぐ際に醤油が蓋と本体の継ぎ目をまたがなければ、後者の問題は起こり得ません。あとは注ぎ口の形状にのみ注意すればいいのです。

そうすると、あちこちで紹介されている白山陶器 G型しょうゆさし 大 白磁が候補に挙がりましたが、評判をじっくり見ていくと少数とはいえ液だれがあるというのが見つかります。陶製だと精度が出にくいのでしょうか、個体差があるのではないかと思われます。

結局、陶器より精度のよさそうなガラス製(残量もわかりやすいですし)の「iwaki クラフトライン しょうゆ差し 100ml K5023-SV」に決めました。しばらく使ってみましたが、注ぎ口からの液だれはまったくありません。もし蓋のパッキンが劣化して密閉性が多少悪くなっても醤油はそこを通過しないのでぜんぜん問題はありません。もう少し値段が安かったら言うことはありません。

さて、見た目が面白いのでスウちゃん (仮名・9歳) に
父「こっち (本体側) とこっち (注ぎ口側) の醤油の高さは同じになるんだよ」
などと話をしていました。
父「傾けても高さは同じになるよ」
スウ「違うよ」
父「えっ」
スウ「違うよ」
父「……」
よく見ると確かに。注ぎ口のほうは水平ですらありません。うーむ。「それは“表面張力”と言ってだな、」と言いかけましたが、残念ながらスウちゃんはそんなところまで興味はないのでした。もし入試にこんな問題があるとすれば、但し書きがないと「出題ミス」と揚げ足を取られることになりそうです。

Rhythmbox を DLNA クライアントとして使う

何を今さらというような内容かもしれませんが、ちょっと苦労したのでメモしておきます。

やりたいことは以前書いた「リモートに置いてある音楽ファイルをローカルPCのスピーカーで鳴らす」と同じです。あれから時間が経ってまたまた環境がずいぶん変わってしまいました。現在は宅内のリモートマシンで DLNAサーバー (Debian の minidlna パッケージ) を動かしており、それから手元の PC で何らかの手段で音楽を聴きたいのです。

手元の PC (OS は Debian testing です) にはインターネットラジオを聴くのに Rhythmbox がインストールされていて (Debian パッケージで言うと rhythmbox と rhythmbox-plugins)、これで DLNA を使えればいい訳で、検索してみると Ubuntu 情報ですが、パッケージ grilo-plugins-0.3 と libgrilo-0.3-0 を追加インストールすれば左欄に DLNA サーバーが現れるかその一番下の + でサーバーが見つかるようになる……はずなのですが、ぜんぜん反応しません。

なかなか手がかりがなく困り果てていたのですが、ふと

(rhythmbox:16045): Grilo-WARNING **: [dleyna] grl-dleyna-servers-manager.c:138: Unable to fetch the list of available servers: GDBus.Error:org.freedesktop.DBus.Error.ServiceUnknown: The name com.intel.dleyna-server was not provided by any .service files

の警告 (エラーではないのであまり気にしていなかった) の中のキーワードから、dleyna-server という Debian のパッケージを追加したら、無事に左欄の「共有」のところに DLNA サーバーが現れました。

結局、必要なのは

  • rhythmbox
  • rhythmbox-plugins

  • grilo-plugins-0.3

のほかに

  • dleyna-server

でした (依存関係でそのほかいくつか)。

しかし、dleyna-server に言及している情報にほとんど行き当たらなかったのはどうしてだろう……。