ウインドウの最小化

ルームを沢山開いてウインドウを放っておくと、全ルームの音がなって大変なことになるし、ブラウザがクソ重くなって大変なことになる。

ルームを開く前にウインドウを最小化しておけば大丈夫なので、実行時はブラウザを最小化しておきたい。

しかし、これが意外とできない。マジか。最大化はアッサリできるのに。

どうやらSeleniumAPIでは無理らしいので、キーボードショートカット(Alt+Space→n)を送るという原始的手段で実現するらしい。

  1. ActionChainsを使う
  2. win32comモジュールを使う
  3. ctypesを使ってwin32 dllをインポートする
  4. JavascriptからActiveXオブジェクトを使う

調べてみると、こんな感じの情報が色々出てくるが、どうやらCygwin+Python+Seleniumだと全滅のようで。素直にWindowsPythonを使っておけば良かったか・・・。

PerlだとCygin用でもWin32::GuiTestみたいなモジュールが使えるんですけどね・・・。

ので、苦肉の策として外部プログラムを実行することにした。

os.system('cscript close_window.vbs')
Option Explicit
On Error Resume Next

Dim objWshShell
Set objWshShell = WScript.CreateObject("WScript.Shell")
WScript.Sleep(2000)
objWshShell.SendKeys "% "
WScript.Sleep(500)
objWshShell.SendKeys "n"

Set objWshShell = Nothing

os.systemよりもsubprocess.callを使おう、みたいな情報が山ほど出てくるけど、さらにsubprocessの使い方を調べるの面倒くさいし動くからいいでしょ。(散々調べて全然できなかったので、ここに辿り着くまでに心が折れた)

なんかPythonを使うたびに「え、こんなこともできないの」みたいなことが多くてしんどい。

これなら最初からJavaで書けばよかったかもしれない。。

ま、ともかく必要な機能は無事に実装できた。後は細々とした使い勝手の調整とかリファクタリングとかをしていけばいいかなあ。

星投げと50カウントも自動化できれば便利なんだろうけど、そこまではいいかな。ツールもあるし手動でも何とかなるし。

そもそも、一番大きな動機が「配信が始まる前に★を貯めておくのを忘れる」「捨て★の時間を忘れる」といったものなので、始まってからの自動化はそこまで必要としてないのだ。

Seleniumを使ってみる(Cygin64 + Python)

近年ますますリッチになっていくWebコンテンツ、たいていのことは単純なスクレイピングでできるけど、Javascriptやら何やらを駆使したサイトは扱いづらい。

ので、いつかはブラウザ自動化を試してみたいなあと思っていた、が調べるのが面倒でChromeの拡張を作ったりしてお茶を濁していたけど、そろそろちゃんと使ってみよう。

ということでSeleniumを使ってみる。まずは動かすとこまで、

予習

Seleniumと言っても色々種類があってややこしい。この辺のサイトが参考になる。

要するにSelenium WebDriver(Selenium2)を使うのがスタンダードということですな。

ダウンロード

Selenium - Downloads

Perl用のbindingがあればよかったのだけど、公式のものは無さそう。

サードパーティーでもいいんだろうけど、せっかくなので公式のものを使おう。この中ならPythonかな・・・。使ったことないけど。

WebDriver

Selenium自体は各言語からWebDriverを動かすためのライブラリ群(だと思う、WebDriverも含めてSeleniumなのかな)ので、それぞれのブラウザに対応したWebdriverというものをダウンロードしてくる。

上のSeleniumのサイトにリンクが紹介されているので適当にダウンロードして、パスの通ったディレクトリに保存する。

パスが通ってなくても、プログラム上でパスを指定できるっぽい。

インストール

準備がそろったのでSeleniumのライブラリーをインストールしよう。

If you have pip on your system, you can simply install or upgrade the Python bindings::


pip install -U selenium


Alternately, you can download the source distribution from PyPI(e.g. selenium-3.6.0.tar.gz), unarchive it, and run::


python setup.py install

pipとは。よく分からないからパッケージをダウンロードしてsetyp.pyを実行するヤツにしよう。

$ which python
/usr/bin/python -> python2.7.exe

$ which python3
/usr/bin/python3 -> python3.6m.exe

$ ln -sf /usr/bin/python3.6m.exe /usr/bin/python

よく分からんがCygwinだとpythonはバージョン2にリンクされているようなので、バージョン3の方にリンクさせておく。

$ python setup.py
Traceback (most recent call last):
  File "setup.py", line 22, in <module>
    from setuptools import setup
ModuleNotFoundError: No module named 'setuptools'

setuptoolsって何やねん。面倒くさいなあ・・・。

何かくっそややこしいな。心が折れてきた。

要するに現在はpipを使ってパッケージ管理をするのが主流で、pipはsetuptoolsを使ったツールということか。

PerlでいうとcpanmとCPANモジュールみたいなものかな?知らんけど。

ちらのうら - Cygwin上でpipとsetuptoolsをインストールする方法

なるほど。

Cygwinにpython3-pipとpython3-setuptoolsがあったのでインストールする。

binutils、libuuid-develは元々インストールしてあった。

$ pip3 install -U selenium
Collecting selenium
  Downloading selenium-3.6.0-py2.py3-none-any.whl (924kB)
    100% |################################| 931kB 585kB/s
Installing collected packages: selenium
Successfully installed selenium-3.6.0

なるほど、あっさりできた。えらい遠回りしたけど。。

テスト

from selenium import webdriver

browser = webdriver.Firefox()
browser.get('http://seleniumhq.org/')

おおー動いた。

import time
from selenium import webdriver

driver = webdriver.Chrome()  # Optional argument, if not specified will search path.
driver.get('http://www.google.com/xhtml');
time.sleep(5) # Let the user actually see something!
search_box = driver.find_element_by_name('q')
search_box.send_keys('ChromeDriver')
search_box.submit()
time.sleep(5) # Let the user actually see something!
driver.quit()

Chromeのdriverをダウンロードして試してみたが、こちらも動いた。しゅごい。

Smart::CommentsとかData::DumperのUTF-8文字列をエスケープしない

調べてみると色々でてくる。

Smart::Commentsで調べると上のサイトが出てくる。$SIG{__WARN__}をフックする方法らしい。これは毎回このコードを書かないとダメそうなので面倒っぽい。

Dat::Dumperのqquote関数を上書きする方法。

今回はSmart::Commentsさえどうにかなれば良いので、Smart/Comments.pmを書き換える。

use Data::Dumper 'Dumper';
{
    package Data::Dumper;
    no warnings 'redefine' ;
    sub qquote { return wantarray? @_ : shift; }
}
$Data::Dumper::Useperl = 1;
binmode STDERR, ":utf8";

色々マージしてこんな感じかなあ。

以前も同じようなことをしたはずだけど、Cygwinを再インストールしたら変更がもとに戻ってやり方を忘れてしまったのでメモしておく。

twitterをなんかするやつ

検索すると色々出てくるけど、シンプルにシェルスクリプトというのがよさげ。

基本的なアイデアは生きてるけど、今のtwitterの仕様では動画がダウンロードできないらしい。

GIF動画についてはこちらのページのやり方でいける。

mp4の動画はどうしようかと思って色々と悩んだ結果、youtube-dlに任せることにした。名前からしYoutube専用なのかと思ったら色々なサイトから動画がダウンロードできて便利ね。

youtube-dl

あとは不具合ではないんだけど、-oオプション(:orig)を付けた際にファイル名が "*.jpg%3Aorig" みたいになって見にくいのを直した。

全体的にはこんな感じか。時間があったら後で備忘録として実装の説明を書いておきたい。

youtube-dlにcookieを渡してないので、ログインしないとダメなヤツは保存できない。ま、必要になったら追加しよう・・・

$ diff -u berryjack berryjack.org
--- berryjack   2017-10-18 02:12:01.985022700 +0900
+++ berryjack.org       2017-10-17 23:34:22.090356500 +0900
@@ -4,9 +4,6 @@
 SCRIPT_DIR=$(cd $(dirname $0);pwd)
 source "$SCRIPT_DIR/common.sh"

-YOUTUBE_DL=$SCRIPT_DIR/youtube-dl
-FFMPEG=$SCRIPT_DIR/ffmpeg
-
 function get_profile_image()
 {
   src="$1"
@@ -130,31 +127,6 @@
       #   skip=1
       # fi
     done
-
-    # video(2) - GIF
-    # cf. http://www.unknownengineer.net/entry/2017/06/03/193913
-    for video in $(grep -Eo 'https://pbs\.twimg\.com/tweet_video_thumb/[a-zA-Z0-9_\-]+\.(jpg|png)' $tmp |sort |uniq |sed -e "s/pbs.twimg.com\/tweet_video_thumb/video.twimg.com\/tweet_video/" | sed -e "s/jpg/mp4/")
-    do
-      exist "$dir" "$video"
-    done
-
-    # video(3) - MP4(HLS)
-    # save sumbnails
-    for image in $(grep -Eo 'https://pbs\.twimg\.com/ext_tw_video_thumb/[0-9]+/[a-z]+/[a-z]+/[a-zA-Z0-9_\-]+\.(jpg|png)' $tmp)
-    do
-      exist "$dir" "$image"
-    done
-    # save videos
-    for video in $(grep -Eo 'data-conversation-id="[0-9]+"|ext_tw_video_thumb' $tmp | grep -B 1 ext_tw_video_thumb | grep data-conversation-id | grep -Eo '[0-9]+' | sed -e "s/^/https\:\/\/twitter.com\/$id\/status\//")
-    do
-      m3u8url=$($YOUTUBE_DL -g $video)
-      video2=$(echo $(basename $m3u8url) | sed -e 's/\.m3u8/.mp4/')
-      exist "$dir" "$video2" > /dev/null
-      if [ $? -eq 0 ]; then
-        echo $m3u8url
-      fi
-    done
-
     # image
     for image in $( grep -Eo 'https://pbs\.twimg\.com/media/[a-zA-Z0-9_\-]+\.(jpg|png)' $tmp | sort | uniq | \
       (if [ $orig -eq 0 ]; then
@@ -164,18 +136,9 @@
       fi
       ) )
     do
-      if [ $orig -eq 0 ]; then
-        exist "$dir" "$image"
-        if [ $? -ne 0 ]; then
-          skip=1
-        fi
-      else
-        exist "$dir" "$(echo $(basename $image) | sed -e 's/:orig$//')" > /dev/null
-        if [ $? -ne 0 ]; then
-          skip=1
-        else
-          echo $image
-        fi
+      exist "$dir" "$image"
+      if [ $? -ne 0 ]; then
+        skip=1
       fi
     done
     if [ $force -ne 1 -a $skip -eq 1 ]; then
@@ -276,17 +239,7 @@
     fi
     for media_url in $(get_media_url $id $get_orig "$cookie_file" $userdir $force)
     do
-      echo $media_url | grep -E '\.m3u8' > /dev/null 2>&1
-      if [ $? -eq 0 ]; then
-        $YOUTUBE_DL -o "${userdir:+${userdir}/}%(id)s.%(ext)s" --ffmpeg-location $FFMPEG $media_url
-      else
-        echo $media_url | grep -E ':orig$' > /dev/null 2>&1
-        if [ $? -eq 0 ]; then
-          wget $cookie_opt -nc -nv -P "$userdir" -O ${userdir:+${userdir}/}$(echo $(basename $media_url) | sed -e 's/:orig$//') "$media_url"
-        else
-          wget $cookie_opt -nc -nv -P "$userdir" "$media_url"
-        fi
-      fi
+      wget $cookie_opt -nc -nv -P "$userdir" "$media_url"
     done
   else
     get_media_url $id $get_orig "$cookie_file" "" $force

return; と return undef; の違い

use strict;
use warnings;
use Smart::Comments;

my %n = (val=>return_nothing());
my %u = (val=>return_undef());
### %n
### %u

sub return_nothing{
  return;
}
sub return_undef{
  return undef;
}
$ perl a.pl
Odd number of elements in hash assignment at /home/tmp/a.pl line 5.

### %n: {
###       val => undef
###     }
### %u: {
###       val => undef
###     }

ただの return; だと、ハッシュの初期化に使った場合 "Odd number of elements in hash assignment" などという警告が出るみたい。

結果はどっちでも同じなんだけど、明示的にreturn undef;としておいたほうが良い(場合もある)みたい。

追記

調べてみると結構おなじようなこと考えてる人がいた。

Use a bare return to return failure
返り値で失敗を報告する時は、裸の return をもってせよ

404 Blog Not Found:perl - (undef) is true

ふーむ。

print "return_nothing is " . ((return_nothing()) ? "TRUE" : "FALSE") . "\n";
print "return_undef is " . ((return_undef()) ? "TRUE" : "FALSE") . "\n";
return_nothing is FALSE
return_undef is FALSE
$ perl -v
This is perl 5, version 22, subversion 2 (v5.22.2) built for cygwin-thread-multi

ふーむ。。

return undefしてもfalseになるっぽいけど、バージョン依存なんだろうか。

まあ何にせよ、成功・失敗を返り値で判断するような場合はただのreturn、値を返すときはreturn undefと使い分ける感じが良いということかな。

dropbox-api-commandのファイル更新判定を変更する

Perlで実装されたDropbox操作コマンドです。

ディレクトリのsyncのために定期的にcronで実行して便利に使わせていただいていたのですが、ファイルの更新を「サイズが違う」あるいは「タイムスタンプが違う」のどちらかで判定しているので、古いファイルに同期してしまう問題があります。

普段は古いファイルをダウンロード(sync)される前に手動でpushして対応していたのですが、先ほど油断した隙に1時間の作業を全部ロールバックされてしまったので、さすがに何とかすることにしました。

   1114 sub has_change ($$) {
   1115     my ($local_path, $content) = @_;
   1116
   1117     my $remote_epoch = $strp->parse_datetime($content->{client_modified})->epoch;
   1118     my $local_epoch = $local_path->stat->mtime;
   1119     my $remote_size = $content->{size};
   1120     my $local_size = $local_path->stat->size;
   1121
   1122     if ($debug) {
   1123         printf "remote: %10s %10s %s\n", $remote_epoch, $remote_size, $content->{path_display};
   1124         printf "local:  %10s %10s %s\n", $local_epoch, $local_size, decode('locale_fs', $local_path);
   1125     }
   1126
   1127     if (($remote_size != $local_size) || ($remote_epoch != $local_epoch)) {
   1128         return 1;
   1129     }
   1130
   1131     return;
   1132 }

問題の部分はここ。

   1114 sub has_change ($$$) {
...
   1127 #    if (($remote_size != $local_size) || ($remote_epoch != $local_epoch)) {
   1128     if ($remote_size != $local_size){
   1129         if($is_upload){
   1130             return 1 if $remote_epoch < $local_epoch;
   1131         }
   1132         else{ # download
   1133             return 1 if $remote_epoch > $local_epoch;
   1134         }
   1135     }
   1136     return;
   1137 }

has_changedはダウンロードとアップロードの2箇所呼ばれるので、「ファイルが新しい場合に更新」を実現させようとすると不等号の向きが逆になります。のでフラグを追加。

$ grep -nE "has_change|^sub sync_upload|sub sync_download" /usr/local/bin/dropbox-api
720:sub sync_download {
764:            if ((!-f $local_path) || has_change($local_path, $content, 0)) {
872:sub sync_download_file {
884:    if ((!-f $local_path) || has_change($local_path, $content, 0)) {
927:sub sync_upload {
976:                if (has_change($local_path, $content, 1)) {
1050:sub sync_upload_file {
1091:        if (has_change($local_path, $content, 1)) {

Perlで配列の値をハッシュにしたいような時

表現が難しいんだけど

my @value = (1, 2, 3, 4, 5);

みたいなデータがあって、それをハッシュとして初期化したいときはどうするか。

my %hash = (
  key1 => $value[0],
  key2 => $value[1],
..
..
);

とかするのだろうけど、配列の個数が何個もあると面倒くさいなぁってなる。

例では5個だけど、10個も20個も項目があるとちょっと厳しい。

実はもっと簡単に

my %hash;
@hash{qw/key1 key2 key3 key4 key5/} = @value;

みたいな感じで書ける。ただし代入はできるけど、初期化でやろうとするとコンパイルエラーになる。

数年前に見かけて何じゃそりゃって思ったけど便利な書き方なのでメモしておく。でもこれ、数年後にコード読み返したら分からなくなりそうだな。