e621.netで画像保存を楽にする

e621.netってケモノ絵や話題を投稿するサイトがあるんです。
ここは健全な絵もあるけれどR18な絵も多数ある所。
ケモノが好きな人は見てて飽きない所ですねw

ぼーっと見ていたら、とあるアーティストさんの過去絵が沢山あったので、ガンガン保存していたら保存ダイアログを出すのがメンドウになってきましたw
なので自動化・・はダルイしイラナイ画像も拾うから画像をダブルクリックするだけで保存ダイアログが出るようにuserChromeを作ってみました。

e621net-supporter.uc.js

// ==UserScript==
// @name           e621.net: 画像保存サポーター
// @namespace      http://blog.wolfs.jp/
// @description
// ==/UserScript==
 
var e621netSupporterObj = {
    targetHost: 'e621.net',
    targetPage: 'e621.net/post/show/',
 
    isTargetPage: false,
    lastTime: 0,
 
    clickID: {
        left: 0,
        right: 2,
        center: 1
    },
 
    init: function() {
        gBrowser.mPanelContainer.addEventListener("dblclick", this, true);
    },
 
    handleEvent: function(event) {
        if (gBrowser.currentURI.asciiSpec.indexOf(this.targetPage) != -1) {
            event = new XPCNativeWrapper(event);
            if (event.target instanceof HTMLImageElement) {
                if (event.target.id == 'image') {
                    event.preventDefault();
 
                    if (new Date().getTime() - this.lastTime > 5000) {
                        this.lastTime = new Date().getTime();
 
                        var img = this.getImage(event.target.src);
                        if (img != null) {
                            this.saveImage(img, this.getFileName(event.target.src));
                        }
                        img = null;
                    }
                }
            }
        }
    },
 
    getFileName: function(url) {
        return url.match(".+/(.+?)([\?#;].*)?$")[1];
    },
 
    getImage: function(url) {
        var request = new XMLHttpRequest();
        request.open('GET', url, false);
        request.overrideMimeType('text/plain; charset=x-user-defined');
        request.channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE;
        request.send(null);
 
        if (request.status == 200) {
            return request.responseText;
        }
 
        request = null;
        return null;
    },
 
    saveImage: function(imageData, requestFileName) {
        var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(Components.interfaces.nsIFilePicker);
        fp.init(window, 'Select a file', Components.interfaces.nsIFilePicker.modeSave);
        //fp.appendFilters(Components.interfaces.nsIFilePicker.filterImages);
        //fp.appendFilters(Components.interfaces.nsIFilePicker.filterAll);
        fp.appendFilter('JPEG Image', '*.jpg');
        fp.appendFilter('PNG Image', '*.png');
        fp.appendFilter('GIF Image', '*.gif');
        fp.appendFilters(Components.interfaces.nsIFilePicker.filterAll);
 
        fp.defaultString = requestFileName;
        switch (requestFileName.split('.')[1]) {
            case 'jpg':
                fp.defaultExtension = 'jpg';
                fp.filterIndex = 0;
                break;
            case 'png':
                fp.defaultExtension = 'png';
                fp.filterIndex = 1;
                break;
            case 'gif':
                fp.defaultExtension = 'gif';
                fp.filterIndex = 2;
                break;
            default:
                fp.filterIndex = 3;
        }
 
        switch (fp.show()) {
            case Components.interfaces.nsIFilePicker.returnOK:
            case Components.interfaces.nsIFilePicker.returnReplace:
                var outStream = Components.classes["@mozilla.org/network/safe-file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
                outStream.init(fp.file, 0x02 | 0x08 | 0x20, 0664, 0);
                outStream.write(imageData, imageData.length);
                if (outStream instanceof Components.interfaces.nsISafeOutputStream) {
                    outStream.finish();
                } else {
                    outStream.close();
                }
                outStream = null;
                break;
            case Components.interfaces.nsIFilePicker.returnCancel:
                break;
        }
    }
};
 
e621netSupporterObj.init();

本当はグリモンとかでサクッと実装したかったけれど、保存ダイアログをだすfilepickerやfile-output-streamなんかは呼び出せないので、権限があるuserChromeで実装。

内容は簡単。
 1.ダブルクリックされたらページのURLがe621.net/post/show/かどうか比較。
 2.対象のページだった場合はクリック先のエレメントがHTMLImageElementかつIDがimageか確認。
 3.imageだった場合はXMLHttpRequestで画像のバイナリを獲得。
   これは既に表示されている画像のキャッシュが効くのでいいb
 4.画像を正常に読み込めたら保存ダイアログを呼び出して保存先やファイル名を決定。
って感じ。

userChromeだけれど、内容はFirefox系のAPIに依存しているのでFirefox系ブラウザ専用です。

アーカイブ拒否を無視するarchive.isをブロックする

Webサイトを保存するオンラインサービスは幾つもありますが、大抵はrobotsへnoarchive属性を付けておけば保存されません。
それを無視するサービスでも削除要求や「今後一切アーカイブしないでくれ」と行った要求が通るのでちょっと面倒なだけでした。

今回なぜarchive.isを知ってブロックする事になったかと言うと・・
別件で過去3年分のサーバーアクセスログからリンク元一覧を作成していたら「archive.is」からのリンクを発見。
なにかなー?とリンク元をゲットしてみると、対象URLのデータ丸ごとアーカイブされていました。

コードを見てみると、GoogleAnalytics関連は無効化されているので余計なログは残らないしGoogleのポリシーにも違反しないのは良いところだけれど・・・
元ページなんかを示すcanonicalタグは書き換えられていました。
まあどのサイトをキャッシュしたかはキャッシュページに記載されているから「どのサイトかわからない」って事はないけれど、重要なcanonicalを削除するのはひどいなあ。

うちのブログは有料素材も使っているし、なにより自分の死後にデータを残したくないのでarchive.isへ削除要求を送りましたが無視され続けました。
これはもうサーバー側でブロックするしかないって事で、ダミーURLにarchive.isのクローラーを差し向けて色々情報を頂いちゃうことに。
(ブロックに必要なIPアドレスのみ必要な方は記事の最後へ)
専用ページもできました。

まずはアクセスログ。
ちょっと書式を変更してあるので普通とは違うところがありますが、概ねApache標準の書式です。

46.166.139.173 blog.wolfs.jp - [22/Apr/2016:04:18:00 +0900] "GET /XIHI2TmvFBCoYOL6M4JFZkGfvCLn6Q5H HTTP/1.1" 404 49368 "https://www.google.com/" "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"
46.166.139.173 sblog.wolfs.jp - [22/Apr/2016:04:18:01 +0900] "GET /optimizer.css?theme_p,hlstring,slimbox2 HTTP/1.1" 200 8345 "http://blog.wolfs.jp/XIHI2TmvFBCoYOL6M4JFZkGfvCLn6Q5H" "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"
46.166.139.173 sblog.wolfs.jp - [22/Apr/2016:04:18:01 +0900] "GET /wp-includes/js/jquery/jquery.js?ver=1.11.3 HTTP/1.1" 200 33267 "http://blog.wolfs.jp/XIHI2TmvFBCoYOL6M4JFZkGfvCLn6Q5H" "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"
46.166.139.173 sblog.wolfs.jp - [22/Apr/2016:04:18:01 +0900] "GET /optimizer.js?theme_p,captcha,slimbox2,lazy-load,jwplayer,jwplayer_after,akismet,attachment HTTP/1.1" 200 52725 "http://blog.wolfs.jp/XIHI2TmvFBCoYOL6M4JFZkGfvCLn6Q5H" "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"
46.166.139.173 sblog.wolfs.jp - [22/Apr/2016:04:18:02 +0900] "GET /themes/kerberos/images/header.jpg HTTP/1.1" 200 75529 "http://blog.wolfs.jp/XIHI2TmvFBCoYOL6M4JFZkGfvCLn6Q5H" "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"
46.166.139.173 sblog.wolfs.jp - [22/Apr/2016:04:18:02 +0900] "GET /themes/kerberos/images/sprite.png HTTP/1.1" 200 8803 "http://blog.wolfs.jp/XIHI2TmvFBCoYOL6M4JFZkGfvCLn6Q5H" "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"
46.166.139.173 sblog.wolfs.jp - [22/Apr/2016:04:18:02 +0900] "GET /themes/kerberos/images/paw.jpg HTTP/1.1" 200 16631 "http://blog.wolfs.jp/XIHI2TmvFBCoYOL6M4JFZkGfvCLn6Q5H" "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"
46.166.139.173 sblog.wolfs.jp - [22/Apr/2016:04:18:02 +0900] "GET /themes/kerberos/images/hidewolf.png HTTP/1.1" 200 5370 "http://blog.wolfs.jp/XIHI2TmvFBCoYOL6M4JFZkGfvCLn6Q5H" "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"
46.166.139.173 sblog.wolfs.jp - [22/Apr/2016:04:18:09 +0900] "GET /plugins/slimbox/css/closelabel.gif HTTP/1.1" 200 971 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.34 (KHTML, like Gecko) PhantomJS/1.5.1 (development) Safari/534.34"
46.166.139.173 sblog.wolfs.jp - [22/Apr/2016:04:18:09 +0900] "GET /plugins/slimbox/css/closelabel.gif HTTP/1.1" 200 971 "http://blog.wolfs.jp/XIHI2TmvFBCoYOL6M4JFZkGfvCLn6Q5H" "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"

おっと・・対象URLのコンテンツに対してはリファラー偽装してアクセスしてきています。
こうなるとユーザーエージェントのWindows7とChromeも怪しいところ。

しかし、Slimboxのgifに対してのみPhantomJSのユーザーエージェントが入っていました。
PhantomJSはコンソールベースのブラウザでwebkit系をエミュレートできるようです。
archive.isはこれを使ってデータをアーカイブしているんでしょうね。

次はサーバーのIPアドレスをRIRからいただきます。

% This is the RIPE Database query service.
% The objects are in RPSL format.
%
% The RIPE Database is subject to Terms and Conditions.
% See http://www.ripe.net/db/support/db-terms-conditions.pdf
% Note: this output has been filtered.
% To receive output for a database update, use the "-B" flag.
% Information related to '46.166.139.0 - 46.166.139.255'
% Abuse contact for '46.166.139.0 - 46.166.139.255' is 'abuse@nforce.com'
inetnum: 46.166.139.0 - 46.166.139.255
netname: NFORCE_ENTERTAINMENT
descr: Serverhosting
org: ORG-NE3-RIPE
country: NL
admin-c: NFAR
tech-c: NFTR
status: ASSIGNED PA
mnt-by: MNT-NFORCE
mnt-lower: MNT-NFORCE
mnt-routes: MNT-NFORCE
created: 2015-06-05T22:10:53Z
last-modified: 2015-06-05T22:10:53Z
source: RIPE # Filtered
remarks: INFRA-AW
organisation: ORG-NE3-RIPE
org-name: NForce Entertainment B.V.
org-type: LIR
address: Postbus 1142
address: 4700BC
address: Roosendaal
address: NETHERLANDS
phone: +31206919299
fax-no: +31206919409
abuse-mailbox: abuse@nforce.com
admin-c: PT3315-RIPE
admin-c: JVDM119-RIPE
admin-c: JH24522-RIPE
admin-c: DI1505-RIPE
admin-c: NFAR
tech-c: NFTR
mnt-ref: MNT-NFORCE
mnt-ref: RIPE-NCC-HM-MNT
mnt-ref: MNT-NFORCE
mnt-by: RIPE-NCC-HM-MNT
abuse-c: NFAB
created: 2007-06-19T08:39:06Z
last-modified: 2015-03-27T11:27:05Z
source: RIPE # Filtered

person: NFOrce Entertainment BV - Administrative role account
address: Postbus 1142
address: 4700BC Roosendaal
address: The Netherlands
phone: +31 (0)206919299
fax-no: +31 (0)206919409
abuse-mailbox: abuse@nforce.com
nic-hdl: NFAR
mnt-by: MNT-NFORCE
created: 2010-11-13T14:42:50Z
last-modified: 2013-05-15T07:49:25Z
source: RIPE # Filtered

person: NFOrce Entertainment BV - Technical role account
address: Postbus 1142
address: 4700BC Roosendaal
address: The Netherlands
phone: +31 (0)206919299
fax-no: +31 (0)206919409
abuse-mailbox: abuse@nforce.com
nic-hdl: NFTR
mnt-by: MNT-NFORCE
created: 2010-11-13T14:43:05Z
last-modified: 2013-05-15T07:50:27Z
source: RIPE # Filtered

% Information related to '46.166.136.0/21AS43350'
route: 46.166.136.0/21
descr: NFOrce Entertainment BV - route 46.166.136.0/21
origin: AS43350
mnt-by: MNT-NFORCE
created: 2014-10-10T12:35:38Z
last-modified: 2014-10-10T12:35:38Z
source: RIPE
% This query was served by the RIPE Database Query Service version 1.86 (DB-2)

サーバーはオランダにあるNForce Entertainment B.V.って会社のようです。
サーバー自体は普通のホスティングサーバーなのでアビューズ報告もダメでしょうし、archive.isに割り当てられたIP一覧も答えてくれないでしょう。

とりあえず今回はarchive.isのIPアドレスを含む46.166.136.0/21をブロックする事に。
NForce Entertainment B.V.の全IPアドレスはこちら:http://ipinfo.io/AS43350

続きを読む

W3C Validatorに合格

年末に変更したこのテーマ、ふと「W3C Validatorにかけてないなー」と思ったので早速かけてみました。
W3C Validator
W3C HTML Validator
文句なしの合格。(`・ω・)b
情報が2と表示されているのは合格した証のバーナーのソースが表示されています。

Feed Validator
これもHTMLと同じ、これはWordPressが出力してるので問題なしでしょう。

HTTP Checker
警告が1なのは古いヘッダー「Pragmaが使われている」ってやつでした。
WordPress側かなー?
そのうちコードをチェックしてみよう・・w

W3C Internationalization Checker
これはi18n・・言語やエンコードが統一されているか等のチェックのよう。
全部UTF-8で統一しているので大丈夫だったみたい。

W3C CSS Validatorは何度やってもシステムエラーっぽい物を返してくるので、復旧を待つしかなさそう。
まあ出たとしてもCSS3の規格にないベンダープレフィックス関係と、linear-gradientを古い規格用にも指定してある所ぐらいかなー。

主要な所は通ってるのでよかった。ヽ(゚∀゚)ノ

デザイン変更完了

12/18からまったりと作り始めた新テーマ。
1日10分~1時間程度の作業だったので結構時間がかかっちゃいましたw

今まではPCでもモバイル端末でもPCと同じ構成のテーマで表示の折り返し方法は端末任せだったけれど、今回からモバイル端末にも対応させました。
CSSが違うだけじゃなくて、ちょっとだけHTML構成も違うのでそれなりに見ることができるはず。

それに合わせて、今使っているプラグインでメンテナンスされていない物とかを片っ端から最適化しました。
同じテーマでもプラグインを最適化するだけで応答速度が0.8sから0.5sまで速くなった。(`・ω・)b
更に、新しいテーマに変えると0.3~0.4sの応答速度に。

他者のテーマを改造するんじゃなくて、自分で作ってよかったw

フィッシングサイト再び

少し前に正規スクエニから登録確認メールがわんさか来ていましたが、スクエニが使用しているサーバーへアビューズ報告をしてからメールが来なくなりました。
サーバー運営をしているところから何か言われたのでしょうw

しかし、昨日「スクウェア·エニックスアカウント...」って件名のメールが届いていました。
またか!と思ってコードを見てみるとHTML形式メールでした。

HTMLをテキストにパースしてみてみると・・・

件名: スクウェア·エニックスアカウントーー安全確認

お客様

株式会社营团社サービスシステムをご利用いただき、ありがとうございます。
システムはお客様のアカウントが異常にログインされたことを感知しました。
下記のログイン時間を照らし合せてご本人様によるログインであるかどうかご確認お願いします。
ログイン地点 ログインIP ログイン時間 大阪 61.204.255.255 2013-10-06 02:06

ご本人によるログインでなければ、アカウントの安全に問題があると考えられます。
以下のURLをクリックし、画面の案内にそってパスワードの再設定を行ってアカウントを保護してください。
http://secure.square-enix.jp.xzl.cn.com/account/app/svc/Login.htm?cont=account
(上記URLをクリックしてもページが開かないときはURLをコピーし、ご利用のウェブブラウザーのアドレス入力欄に貼り付けてお試しください)

もし、ご本人によるログインでしたら、お手数ですが本メールの破棄をお願いいたします。
ご意見やご要望
スクウェア·エニックス会社
2013年10月07日

うは、これはヒドイ日本語www

しかも今度のメールサーバーは送信元メールアドレスをyahoo.co.jpに偽造では無く、正規のyahoo.co.jpサーバーから送信されてきています。
通りでメールサーバーでのシグネチャ検証に引っかからなかった訳ね・・・
メールはyahoo japanへアビューズ報告をして終了。
 
フィッシングサイトの方のコードを落として見てみると・・・なんと自動解析が可能な簡単なコードでした。
なので成功したときのメッセージと失敗したときのメッセージを獲得して、自動でPOSTしまくるjavaを組みました。
スクエニのフィッシングへ嫌がらせ
1ms前後の間隔で適当なユーザー名とパスワードをPOSTしまくるjavaを3つ実行してみました。

大体どれも200リクエスト前後で相手サーバーに接続を終了され以後のレスポンスを一切返さなくなります。
レスポンスを返さないだけでIP層へのコネクション自体は成功するので、恐らく攻撃検知かF5アタック検知の類いでhttpdサーバー側でブロックしているのでしょう。

IPBANなのでIPを変えればいいや!って事で・・・
公開プロキシリストが載っているサイトから一覧をゲットしてきて、タイムアウトが3回続いた場合プロキシを自動変更してリトライするようにしました。
これを大体30分ぐらい走らせて飽きたので終了しましたw

しかし、キャプチャがないとOCRする必要がないので楽ですねー。
 
チナミニ、送信間隔10ms~20ms前後だとカナリの確率で即BANされましたw
25ms~だとプロキシによっては300リクエストを超えましたが、やはり即BANされる確率が結構高いですね。
100ms~が安定のよう。