2016/12/09 8:45 pm
The TV program time table #9
Uncategorized, ,

虹裏でアナウンスした際に Vivaldi で動かした時、お気に入りのチャンネルを定義する場合のドラッグ&ドロップが正常に動作しないという報告を受けた。

調べてみると、ドラッグ&ドロップのためにはいくつかのイベントを処理しないといけないわけだが、その中でドロップされた際の drop イベントと、ドラッグプロセスが完全に終了した際の dragend イベントの発行される順番の問題のようだ。

仕様では、常に drop -> dragend の順番であり、あべアニもそういう前提で組んであり、実際 Chrome、Opera、Firefox、Edge でテストした際もそういうふうに動作した。Vivaldi ではテストしなかったのだが、これは Vivaldi は結局 Chrome ファミリーであり、Chrome で動けばきっと多分 Vivaldi でも同様のはず…という判断なのだが、びっくりすることに Vivaldi では drop イベントが最後に発行するようなのである。

なんで…?

前述の通り drop -> dragend なのはそういう規格だし、Chrome 自体がそういうふうに動作するのに、何を意図して変更したんだろう? 分からないがとにかく、いずれの順番でも正しくドラッグ&ドロップが動作するように修正。

10:33 am
The TV program time table #8
Uncategorized,

Firefox 版は AMO に置くのを諦め、ここでホストすることにした。

Chrome 版のソースを Firefox の WebExtensions のシステムで動かす際、以下の点が Chrome と異なる。

  • chrome.runtime.onInstalled がまだない。したがって、
    'onInstalled' in chrome.runtime && chrome.runtime.onInstalled.addListener(function () { });

    みたいな書き方をする必要がある。

  • content script で生成した CustomEvent にオブジェクトを含んだ detail を与え、ディスパッチする。それをページスクリプトでリスンすると、detail が正しく引き渡されない。Firefox では権限の異なるスクリプト間ではプリミティブな型以外のものを渡せないようだ。そんなわけで、Firefox で動かす際は detail には JSON.stringify() したものを与え、受け取る際も JSON.parse() しないといけない
  • ソースはほぼ Chrome 版と共有できるが、パッケージングはかなり異なる。ストアに登録する際は Chrome 同様にソースディレクトリを単に zip するだけでもよいが、web-ext ツールを用いてビルドと署名をコマンドラインから行う方法も用意されている。
  • 自前でホストする場合、更新情報も自分で管理しないといけない。そのために、manifest.json に
    "applications": {
        "gecko": {
            "id": "[email protected]",
            "strict_min_version": "42.0",
            "update_url": "https://appsweets.net/abeani/updates.json"
        }
    },

    というものを含める必要がある。このエントリは、Chrome は無視するのでこの manifest を Chrome に与えてもエラーにはならない。updates.json の内容は

    {
        "addons": {
            "[email protected]": {
                "updates": [
                    {
                        "version": "1.0.8",
                        "update_link": "https://appsweets.net/abeani/extension/AbeaniExtension.xpi"
                    }
                ]
            }
        }
    }

    こんな感じ。

    調子に乗って Edge でも動かしてみたのだが、まず Edge は chrome.alarms がないので普通のタイマーで代替しないといけないのだが、それは代替手段があるのだから別にいい。だがなんと、content script から chrome.runtime.sendMessage() でバックグラウンドにメッセージを送る機能がまだ実装されていない。これがないとどうにもならない。ひどい。というわけで Edge 版はしばらくペンディングだ。MS さん真面目にやってくださいよ。

2016/12/06 7:08 am
The TV program time table #7
Uncategorized,

  • 表示できるチャンネルをアニメ以外にも拡大した: 各チャンネルを適当なカテゴリで分けたが、このカテゴリでいいの? というのは若干ある。REALITY SHOW は果たしてドキュメンタリーなのか? とか。あるいは、よくわからないチャンネルはすべてバラエティに突っ込んだがそれでいいのか? とか。これは文句が出たらその都度対応することにしよう
  • 各チャンネル群を従来は table 要素でマークアップしていたが、CSS flex を用いるようにした。これによりナウじゃないブラウザでは表示できなくなった可能性がある。しかし 21 世紀も 16 年も経って flex に対応していないブラウザが存在していたならば、それはそれが一方的に根本的に全て何もかも悪い。という方向でそろそろ行きたい。なにしろ flex 自体は Presto Opera ですら対応しているのだ(といいつつ、Presto Opera での表示確認は一切していないのだが)
  • 各番組の放映開始に併せて、虹裏では番組の実況スレというものが立つことが多い。このスレッドの本文を生成する助けになることを意図して、共有機能を実装した。共有機能というのはなんかよく意味がわからないが、生成したテキストをそのままツイートできるようにした上での命名だ。というのはこのツイート機能がないと「実況スレ本文生成」みたいな更によく分からない機能になってしまって、これを呼び出すためのボタンのラベルが冗長になってしまうからだ
  • ところで本文を生成する際、特定のマーカーは番組の情報に置換される。たとえば [start-at] が 2016/12/06(火) といった具合だ。ここで、2016年12月06日 の形式がいい! といった場合にはマーカーのオプションとして書式を付加することができる。[start-at{%m月%d日(%a)%p%l:%M}] などと書くと {〜} の中身を strftime(3) で評価した結果で置換される
  • 番組開始通知時のオプションとして、番組終了時に自動的にチャンネルのタブを閉じる機能を追加した
  • 特にチャンネルの拡大を虹裏 img でアナウンスした時なのだが、投げ銭してもいい的なレスをいくつか受けた。ありがたいことだが、しかし所詮は「」の言うことであって、9割方口だけである。が、人の心を失っていないまっとうな「」もいることを期待して、一応そのための窓口は設けた。はやくお酒飲みたいなあ
  • 通知機能のためのブラウザ拡張は、相変わらずその申請が降りるまでの時間が各ブラウザで違う。Chrome は機械的に 1 時間もすれば通る。Opera は人がレビューするが、大体 3 営業日くらいで通る(土日はお休みだ)。Firefox は、なんか以前 AMO 公式のブログや、あるいはニュースメールなどでいっぱい KAIZEN してレビューも早くなったよ!ほめて!的なアピールをやたら目にしたことがあったが、全然変化ないように思える。すなわち申請からだいたい 1 ヶ月はかかる見込みである。ぶっ飛ばされたいのか。wasavi と同様 Firefox 版だけはストアに置かない形式にするかもしれない

2016/11/24 2:19 am
The TV program time table #6: regalized program name
Uncategorized,

あべアニで番組開始の通知をする際「のんのんびより 開始1分前です」的な読み上げを行わせることができる。PhoneticNews に引き続き、この読み上げは voicetext を利用している。ただまあ仕方ないことだとは思うが、たまに正しく読み上げないことがある。忍ペンまん丸をしのぶぺんまんまるなどと読み上げたりする。これをなんとかできないだろうか。

アニメ作品のデータベースとしては animedb というプロジェクトがあり、その中で作品名のふりがなも管理されている。これを利用できるかもしれない。

ただ、

  • ドキュメントにも記載されているが若干表記の揺れが残っている。たとえば鷹の爪で grep すると
    $ grep "鷹の爪" google-ime-dict.txt
    ザフロッグマンショーヒミツケッシャタカノツメ  THE FROGMAN SHOW「秘密結社鷹の爪」 固有名詞
    ヒミツケッシャタカノツメザムービーソウトウハニドシヌ  秘密結社 鷹の爪 THE MOVIE ~総統は二度死ぬ~    固有名詞
    ヒミツケッシャタカノツメザムービーツーワタシヲアイシタクロウーロンチャ   秘密結社 鷹の爪 THE MOVIEⅡ ~私を愛した黒烏龍茶~   固有名詞
    ヒミツケッシャタカノツメカウントダウン   秘密結社鷹の爪カウントダウン  固有名詞
    ヒミツケッシャタカノツメ    秘密結社鷹の爪   固有名詞
    ヒミツケッシャタカノツメザムービースリータカノツメジェイピーハエイエンニ    秘密結社 鷹の爪 THE MOVIE 3 http://鷹の爪.jpは永遠に  固有名詞
    ヒミツケッシャタカノツメザムービーフォーカスベルスキーヲモツオトコ 秘密結社鷹の爪 THE MOVIE 4 カスベルスキーを持つ男 固有名詞
    タカノツメネオ   鷹の爪 NEO   固有名詞
    ヒミツケッシャタカノツメジェーピー 秘密結社鷹の爪.jp    固有名詞
    タカノツメマックス 鷹の爪 MAX   固有名詞
    タカノツメゴーウツクシキエリエールショウシュウプラス  鷹の爪GO 美しきエリエール消臭プラス 固有名詞
    ヒミツケッシャタカノツメドットジェイピーブルーレイボックスジョウカンカンゼンシンサクエイゾウ  秘密結社 鷹の爪.jp Blu-ray BOX上巻[完全新作映像] 固有名詞
    シネマトラベルタカノツメタカノツメダンシネマトラベルヘイクノマキ    シネマ・トラベル × 鷹の爪 鷹の爪団! シネマ・トラベルへ行くの巻!    固有名詞
    ヒミツケッシャタカノツメドゥー   秘密結社鷹の爪 DO    固有名詞

    などと「秘密結社」の有無、あるいは「秘密結社」に続いて空白が入っているかなどが揺れている。それともそれぞれの作品で正式名称の表記が揺れているのが正確な状態なんだろうか? よく知らない

  • 上記の例で Blu-ray BOX 上巻云々が含まれているものがあるがこれは作品名なのか? 製品名ではないのか?
  • タイトルに含まれる空白がよみがなでは省略されているが、これをそのまま読み上げさせると不自然なアクセントになってしまう。できればよみがなでも空白は維持してほしい。Google 日本語入力用の辞書ファイルなのであえてそうなっているのかと思ったら元データである animedb.yml でも同一なのでそういうわけでもないようだ

というわけで、作品名の正規化に用いるには若干難しいかもしれない。しかし膨大なデータなのは確かなので何かに利用したいなあ。

2016/11/22 8:15 pm
The TV program time table #5
Uncategorized,

設定機能及び番組開始時の通知機能を実装した。

番組枠のポップアップに「通知」というボタンが追加されており、それを押すと予約される。時間になると自動的に Abema.tv の該当チャンネルを開いたり、音声でガイドしたり、OS が持つ通知機能を通してメッセージを表示したりする。これらの組み合わせは設定パネルで好きにできる。

その他、

  • 予約したら、あべアニのページは閉じて良い
  • 該当チャンネルを開く際、すでに何らかの Abema.tv 上のチャンネルを開いていたらそのタブを再利用する

といった特徴がある。

このような機能を実装するとして、最も単純なのは当然、あべアニのページで setTimeout() することだ。これなら何も実装上で難易度の高いものはない。しかしやはり当然ながら、この方法だとあべアニのページをずっと開きっぱなしじゃないといけないのである。もしも実際のユーザの使用状況が、いったんあべアニを開いたらずっと開きっぱなしであるならこの方法で実装してもいいが、そうではないなら別の方法を考えないといけない。

というわけでアクセス解析を見てみたところ、だいたいあべアニを開いて閉じるまでが5分以内のユーザが62.37%、1時間以内なのが17.8%とかそんな感じだった。つまり、setTimeout 作戦ではダメなのである。

そこで、今流行りの Push API という規格を検討してみた。これは setTimeout 作戦との比較で言えば、ここの Web サーバが setTimeout の役目を肩代わりする。そして通知をすべき時間になったら、Push サーバというものを通してブラウザに通知を送る…という仕組みだ。かつてはブラウザへの Push といえばブラウザ側から接続をかけて long polling という手法がとられた。この規格を実現している Google と Mozilla の Push サーバが未だそういう手法なのかは知らないが、いずれにしても単純な http ではない特殊な接続方法でブラウザと通信する必要があるようなので、Push サーバが必要になる。

しかし検討した結果、これも実装したい機能にはちょっと力不足だった。

  • 新しい規格なので、また仕様が固まっておらず、各種言語向けのライブラリなども揃いきってない。ペイロードを含んだ通知などではかなりめんどくさい暗号化を施さないといけないので一から作るのはちょっと大変。Node.js だと楽そうなのでビルドを試してみたがここのサーバの glibc のバージョンが異様に古くて動かない
  • ここの Web サーバが通知を送るタイミングを管理するということは、一定期間ごとに適当なプログラムを自動起動させないといけない。実はここのサーバでも一応 crontab は編集できるのだが、それは1時間に1回までという限度があるのだった。もっと細かい時間単位で起動させるには別のサーバを使わないといけないので面倒
  • ブラウザが通知を受け取ったあと、チャンネルを開くとして既存のタブを再利用するという芸当が多分現状の仕様ではできない。将来、API が拡充されればできるようになるかもしれないが、今はできない

というわけで、現実的な解としてブラウザの拡張機能との組み合わせで実現することにしてちょちょっと作った。

ナウいブラウザの拡張機能といえば、なんと言っても Firefox の WebExtensions である。この際なので WebExtensions で作ってみた。実際は作ってみたというほどのことではなくて、単純に Chrome 版のソースディレクトリを与えたらだいたい動いた、すごい! という程度のものだが。

それから当然ながら Chrome 版は Opera でもだいたい動くので Opera 版も作った。

だいたい動くというのは、例えば chrome.storage.syncchrome.runtime.onInstalled など Firefox や Opera では微妙に実装されていないものがあるのでそういうものを使うのは避けないといけないということだ。このせいで拡張機能をインストールしたあといったんあべアニをリロードしないといけない。

ちなみに Chrome の拡張をパク…大いに参考にしたという意味では Edge でも動くはずだが、なんと Edge の場合 chrome.alarms すら実装されていないそうな。もうちょっとがんばってくださいよ MS さん。

2016/10/02 1:37 pm
The TV program time table #4
Uncategorized,

abeani-gearいくつか、ユーザごとにカスタマイズできる項目を作りたい。そのためにとりあえず歯車アイコンを配置した。

ところで最近はこの手のページ全体に対する設定やらアクションやらを担当するリンクとしていわゆるハンバーガーアイコンがよく用いられるのだが、あれすごくダサいと思う。ダサいしそれを押すことで何が起きるのか連想できないという機能上の問題も抱えている。早くこの世からなくなって欲しい。

それはさておき、設定可能な項目をつらつらと挙げてみると:

  1. 各チャンネルのロゴ、番組枠及びその詳細リンクそれぞれをクリックした際のウィンドウ名: これを _blank にすればクリックするごとに新しいタブが開く。特定の名前にすればクリックした時に既にそれが存在すれば再利用される(もちろんユーザにはウィンドウ名自体は重要ではないので、設定の際は単に「クリックした時タブを再利用するか否か」と簡略化される)
  2. 翌日の番組表へのリンクの背景として何時間分をチラ見せするか
  3. 番組枠上の詳細ポップアップの可否
  4. 表示するチャンネル、及びその並び
  5. 事前通知する場合の、通知方法

このうち 3. と 4. はどういう仕様にしたものかいろいろと考えることが必要だ。

2016/09/28 8:44 am
The TV program time table #3
Uncategorized, ,

1 クール(12〜13話)分の一挙放送、といった番組は番組表においても縦方向に非常に長い面積を占める。その枠の先頭部分にタイトルとサムネイルを描画するわけなのだが残りの部分は全くの空白なので、始まって数時間もすると何を放送しているのか分からないただの枠が番組表に残るだけになる。これを何とかしたい。

これを何とかするのは、近年よく広告に使われる sticky positioning だ。sticky というのは日本語で言えばネバネバだ。ページをスクロールした時にできるだけビュー内にとどまろうと粘っこい動作をするアレである。現実には広告によく使われるが、本来 sticky な動作をして嬉しいのは上下に長い表のヘッダ部分などである。あべアニで言えばそれは正にチャンネル名の部分で、既にそういう動作をするように仕掛けてある。同じことを、それぞれの番組枠にも適用すればいい。

ところでこの sticky positioning というのは CSS3 の規格でまだ策定中の段階であり、現時点では Firefox を除くブラウザにはまだ実装されていない。なので javascript による Polyfill を使うことになる。そういうわけであべアニでは StickyFill というライブラリを使っている。ところが残念なことに、このライブラリに対してそれぞれの番組枠を sticky にするように指示しても上手くいかないのであった。上手くいかない上にスクロールもかなり重くなる。

Polyfill でやるべきことは

  • window の scroll イベントをリスンして
  • sticky 動作の対象となっている要素を走査し、その領域内にビューの上端が含まれていたら要素を position:fixed にする。このとき、その親要素の領域内に収まることを優先しつつ、要素の内容がビューに収まるように位置を微調整する。それから、領域の周辺の要素の位置を動かさないためにダミーの置換要素を生成したりも必要ならする

ということにつきる。しかしやることは単純だが、重い。まず scroll イベントは相当な頻度で発生する。それから要素を走査する際にその配置情報を逐一取得するのも重い。StickyFill は汎用的に使えるようにするためにその辺を素直にやっているので重い。

そういうわけで仕方ないので番組枠の sticky 動作についてはページに最適化しつつ自前で書いた。たとえばいったん計算したら使いまわせるもの(margin や border のサイズ)は積極的にキャッシュしたり、そもそも sticky 動作させる必要のない番組枠(内容の高さが枠の高さ以上あるもの)は積極的に除外したりだ。それでもブラウザがスムーズスクロールする設定にされていると若干描画がバタつく。早くブラウザでネイティブサポートされて欲しい。

ついでに言うとネイティブサポートの際は stuck 状態かどうかを取れる CSS の擬似クラスを定義してくれるととても助かるのだが。そうすると stuck したときに限り box-shadow をかけるなどの小細工ができる。

5:34 am
Abeanibot introduced #2
Uncategorized, ,

前の記事で、

検索結果をローカルに保持してある sqlite のデータベースと照合する。新しいものがあればそれをツイートする

と書いた。ここのやり方は sqlite ならではのものになっているので一応メモしておく。

普通に考えると、番組 A が既にデータベースにあるかを照合し、その結果によって処理を振り分けるには

  • select * from program where name=:A する
  • レコードセットが行を返さなかったら insert into program (name) values (:A) する。そしてツイートする

と、1 番組につき 2 回クエリを発行しなければならない。これは無駄な感じがするので、例の sqlite 側の関数の実体を php で実装できる機能を使う。

$db = new SQLite3("program.db");
$db->createFunction("php_name", __NAMESPACE__ . "\\phpName");

$insertedNames = [];

function phpName ($name) {
    global $insertedNames;
    $insertedNames[$name] = 1;
    return $name;
}

こんな感じにしといて、insert or ignore into program (name) values (php_name(:A)) とすると、番組 A がテーブルに存在していなかった時のみ php_name 関数が呼ばれ、そして行が追加される。php_name 関数自体は何も副作用をもたらさないのだがこの時 PHP 側に制御がいったん移るので、そこで適当なものに覚えておく。こうすると 1 番組につき 1 クエリで済むようになる。

ところで createFunction の第 2 引数には PHP 側の関数を指定する。PHP で関数オブジェクトを指し示す方法としては、関数名の文字列、array([$this object], [method name string]) のような配列、クラス名を前置した静的メソッド名文字列などが段階的に追加されてきた。加えて最近の PHP では function リテラルそのものも使えるようになっている(これは Closure クラスのインスタンスが実体だ)。

この段階的というのがミソで、sqlite3::createFunction は最も初歩的な関数名文字列しか対応していないっぽいのである(かろうじて、名前空間は認識するが)。このへんの「ううn…」さがまあ PHP らしさなのだが。

2016/09/26 8:14 pm
Abeanibot introduced
Uncategorized, ,

あべ☆アニ関連でちょっとした Twitter 上のボットを作った:

あべアニ ぼっと
https://twitter.com/Abeanibot

これは AbemaTV 上で新しく配信されるらしいアニメをつぶやく。例えばこんなふうに:

新しくというのは、そのままの意味だ。例えば、すでにのんのんびよりは何度も配信されているので、もしまた再度第 1 話から配信されることになったとしてもそれはツイートされない。一方もし将来まじぽかの配信が決定した場合はそれはツイートされる。

どのように対象の番組を抽出しているか。情報源としては番組表データと、検索結果のデータが考えられるが、今回あべアニぼっとでは検索結果を使用している。というのは番組表データは当日から 1 週間先のものまでしか得られないが、検索であれば更にそれを飛び越えた期間の番組データを得ることができるからだ。

そういうわけでバックエンドでは 1 時間ごとに PHP スクリプトを起動し、AbemaTV の検索 API に対して 2 つのクエリを投げる。なぜ 2 つかというとこの API には若干の癖があるようで、どうも検索結果が 100 件を超えないように調整されるような感じがするのである。AbemaTV のアニメ系チャンネルの場合、タイトルに含まれる話数は必ず #\d+(〜\d+)? の形式になっているようなので、第 1 話から新しく配信されていそうな番組を検索するには #1 で検索する。しかしこれには #10 とかも含まれて、直近の番組で 100 件近くに達してしまう。なのでこのクエリだと 1 週間先、2 週間先の新番組を抽出できない可能性がある。

そこで次に、#1〜 でも検索する。これなら確実に第 1 話の番組だけを抽出できる。ただしこれの場合複数話連続している配信形態であることが前提になるので、第 1 話を単体で配信するパターンに対応できない。それをカバーするのは最初に投げたクエリである。

この 2 つの検索結果をマージすることで最終的な結果が得られる。ここまで読めば分かるように、この方法だと 1 週間先、2 週間先で、かつ第 1 話を単体で配信するパターンの番組は検索から漏れてしまう可能性があるのだが、まあ仕方がない。クエリに単語区切りを指定するような特別な演算子を含めることができれば解決するのだが、クエリがどのような文法になっているのか分からない。

いずれにしても得た検索結果をローカルに保持してある sqlite のデータベースと照合する。新しいものがあればそれをツイートするという流れだ。

ところで Twitter ボットを作ったのは自由帳ボットに続いて 2 つめなのだが、どちらも純粋にツイートするだけの機能しかない。巷の高機能なボットはフォロー返しや、メンションに対するある程度の反応を行うものもある。これらのボットにもそういう機能が必要だろうか? 要調査。

2016/08/07 6:54 am
The TV program time table #2
Uncategorized,

CloudFlare 絡みでもうひとつ。オリジンサーバから送出される静的なコンテンツについて、CloudFlare は CDN として振る舞う。つまり CloudFlare 側でキャッシュし、クライアントのアクセスの際はそのキャッシュを利用すると同時にオリジンサーバの負荷を低減する。

この静的の定義とはなんぞやというと、このへんに詳しい。特定の拡張子を持つもの、Cache-Control ヘッダで public と指定されかつ max-age が 0 より大きいもの、PageRule で特別に指定されたもの…という感じ。意外なことに html ファイル自体はキャッシュされない(PageRule の設定でキャッシュさせるようにすることはできる)。

で。あべアニのうち最もファイルサイズがでかいのは、番組表のデータである json ファイルなのであるが(gzip して 200KB 弱くらい)、アクセス解析を見てみたらなんとこれが CloudFlare 側にキャッシュされていないのであった。これはいけません。

とりあえず適切に Cache-Control ヘッダを付加するようにしてみたところ、効果なし。そこで mod_rewrite で擬似的な js ファイルとして扱うようにして、かつクエリ文字列を使わないようにしてみたところ、キャッシュに乗るようになった。間抜けなことにそれらを同時に変更したので、乗るようになった要因が拡張子なのかクエリ文字列なのかはっきりしていないのだが、まあ結果オーライなのだ。

ちなみに PageRule で何とかする方法もあるのだが、フリープランだと何と登録できる PageRule は 3 つまでなのである。ダイヤモンドより貴重なのでホイホイ使うわけにはいかない。

CloudFlare のキャッシュに乗っているかどうかは、レスポンスヘッダに cf-cache-status: HIT が含まれているかどうかで判断できる。また、CloudFlare に限らずコンテンツが CDN によって適切に分散されているかどうかは https://www.webpagetest.org/ でテストすることができる。

Archives