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系ブラウザ専用です。

ちょっとだけuserscriptを公開。

20日からX-Legendの新しいゲーム「ハンターヒーロー」のテストが始まってます。
X-Legendのゲームは以前に幻想神域ってのをプレイ済。

しかしこのX-Legendのゲームはログインが少し面倒でユーザー名、パスワード、OTPコード若しくは画像認証が必要。
大抵のゲームサイトはユーザー名、パスワードをパスワード管理ソフトで管理することができるけれどX-Legendはダメ。
 
サイトのソースを見てみると、formエレメントは存在するけれどactionがしていされておらずformエレメントからPOSTしていない。
じゃあどうやってPOSTしているのかというとログインボタンにクリックイベントを登録してjQueryのajaxでPOSTしていました。

全部の処理にjQueryが使用されているのでPOST部分のAPIを乗っ取ればイイカンジにできそう。
と言うことでスクリプトを書いてみた。
配布ページはこちら

$("document").ready(function() {
    var xLegendUser = {
        "user":"",
        'pass':"",
        'otp':""
    };
 
    var xLegendOTPInput = function() {
        xLegendUser.otp = window.prompt("SecureOTP コードを入力して下さい。", xLegendUser.otp);
        if (xLegendUser.otp == null || xLegendUser.otp == "" || isNaN(xLegendUser.otp) == true) {
            xLegendUser.otp = '';
            return false;
        } else {
            if (window.confirm("下記情報でログインします、よろしいですか?\n\nユーザー名: "+xLegendUser.user+"\nOTPコード: "+xLegendUser.otp)) {
                 return true;
            }
        }
 
        return false;
    };
 
    if (document.getElementById("after_login") == null) {
        $("#otp_login").unbind();
        $("#click_tab2").click();
        $("#otp_username").val(xLegendUser.user);
        $("#otp_p_show").hide();
        $("#otp_input").hide();
 
        $("#otp_login").click( function() {
            if (!xLegendOTPInput()) {
                return false;
            }
 
            $.ajax({
                url: "/ajax/eat.php",
                type: 'POST',
                data: { 
                    "method" : "web_login",
                    "otp_username" : xLegendUser.user,
                    "otp_password" : xLegendUser.pass,
                    "otp_input" : xLegendUser.otp,
                    "login_type" : 'otp_login'
                },
                success: function(ret) {
                    data = eval('('+ret+')');
                    $("#otp_error_msg").hide();
                    if(data['status'] == 1){
                        window.location.reload();
                        $("#otp_error_msg").hide();
                        $("#error_msg").hide();
                    }else if (data['status'] == 5){
                        $("#click_tab1").click();
                        $("#left_img").attr("src", '/images/login/loginTab1_active.gif');
                        $("#right_img").attr("src", '/images/login/loginTab2_off.gif');
                        $("#error_msg").html('画像認証でログインして下さい');
                        $("#error_msg").show();
                    }else if (data['status'] == 6 || data['status'] == '-1'){
                        $("#otp_error_msg").html("アカウントまたはパスワードは正しくありません!");
                        $("#otp_error_msg").show();
                        $("#error_msg").html("アカウントまたはパスワードは正しくありません!");
                        $("#error_msg").show();
                    }else if (data['status'] == 10){
                        $("#otp_error_msg").html("アカウントは停止されています。");
                        $("#otp_error_msg").show();
                    }else if (data['status'] == 'protected'){
                        window.location.href = 'https://member.x-legend.co.jp/member/security_chord.php';
                    }else{
                        var otperr = data['status'];
                        $("#otp_error_msg").html(otperr);
                        $("#otp_error_msg").show();
                    }
                    $("#otp_error_msg").effect("shake", { times:5 }, 50);
                    $("#error_msg").effect("shake", { times:5 }, 50);
                }
            });
        });
    } else {
        xLegendUser = null;
    };//end document.getElementById
});

処理はすごく簡単で $("#otp_login").unbind(); でログインボタンのイベントを全部消して $("#otp_login").click(... で自前のクリックイベントを設定。
あとはxLegendUserオブジェクトで指定しておいたユーザー名とパスワードとpromptで入力したOTPコードをajaxのPOSTするデータにいれてやるだけ。

このスクリプトを利用して公式サイトにいくとこうなる。
X-Legend ログインアシスト
ログインボタンを押すとOTPコード入力ダイアログが出るので入力をすれば後は勝手にログインしてくれます。

配布しているのはGreasemonkey用だけれどuserChrome.jsでも多分動きますw
配布ページで公開しているスクリプトはX-Legend公式サイト、幻想神域、プライドオブソウル、ハンターヒーローで動くように変更済み。
 
これで毎回毎回長いユーザー名とパスワードを入力しないで済むw

userChrome.js / Greasemonkey スクリプト配布

ここではuserChrome.js / Greasemonkeyのスクリプトを配布しています。

主に自分用に作った物なので普段から開発している訳ではありません・・w
不具合や改善案などがありましたらコメントやメールでお願いします。
 

注意事項

配布物の紹介について
 こちらで配布している物を動画やWebサイト等での紹介・使用は制限しません。
 リンクを記載する場合、アリフィエイト等の収入が入る機能がない物を使用しリンク先はこのページにしてください。

 連絡はしなくても結構ですが連絡してくださると私が喜びます。
 
再配布、改変について
 再配布は行わないで下さい。
 スクリプトの改変は自由に行って頂いて結構ですが、他者への配布は行わないで下さい。
 
使用について
 使用は全て自己責任でお願い致します。
 導入したことにより生じたいかなる損害・損失に対して作者は一切保証しません。
 インストールした時点でこれに同意したものとします。
 

公開スクリプト一覧

更新日 スクリプト名 説明
更新日 スクリプト名 説明
2017/05/13
v2.1.1
Twitter: 動画保存サポーター Twitterにアップされている動画(.m3u8, .ts, .mp4)の一覧を表示しダウンロードのサポートをします。
使い方などはこちらの記事:Twitterの動画のTSファイル一覧を表示&保存する
2015/07/21
v1.0.2
Fur Affinity.net: Change Method 検索フォームのメソッドをGETに変更します。
これにより検索途中のページをお気に入りやタブに記録することができます。
2017/05/28
v1.0.0
YouTube: Live Chat Filter YouTube Liveのチャットから不快なチャットを削除します。
フィルター設定はスクリプトの変数「filters」を修正してください。
2017/11/11
v1.0.0
Amazon.co.jp: ログインフォーム変更 ログインフォームを段階的ログインから「メール」と「パスワード」同時入力の方式に戻します。
2017/05/10
v1.0.1
Tumblr: Disable Autoplay Tumblrの投稿内にある動画の自動再生を無効化します。
2017/07/09
v1.0.2
Tumblr: Redirect to 1280 Tumblrの画像を1280サイズのURLへリダイレクトします。
2017/07/09
v1.0.2
Tumblr: Change to 1280 Tumblrの投稿内にある画像のURLを1280サイズに変更します。
2014/10/22
v1.0.1
X-Legend ログインアシスト (OTP版) X-Legend各種サービスへ半自動ログインを可能にします。
インストール後にスクリプトをメモ帳等のエディタで開き「ログインするユーザー情報の設定」の部分を変更して下さい。
2015/04/24
v1.0.0
イカロスオンライン: 起動コード表示 イカロスオンライン(ICARUS ONLINE)の起動にはActiveXが必要ですが、このスクリプトを使用するとランチャー起動に必要な起動引数を表示することができます。
起動方法はイカロスオンラインをActiveXなしで起動するに記載してあります。
2014/12/14
v1.0.0
Dビデオ スタイル変更 Dビデオの作品ページ等、黒背景・白文字で目が痛くなるページを白背景・黒文字(参考画像)を基調としたページに変更します。
(設定ページ等一部ページは変更していません)

 

更新履歴

2017/11/11: 公開:Amazon.co.jp: ログインフォーム変更
2017/07/09: 更新:Tumblr: Redirect to 1280 v1.0.2、Tumblr: Change to 1280 v1.0.2
2017/05/28: 公開:YouTube: Live Chat Filter
2017/05/13: 更新:Twitter: 動画保存サポーター v2.1.1
2017/05/08: 公開:特定の人に公開していたスクリプトを公開
2017/03/26: 更新:Twitter: 動画保存サポーター v2.1.0
2015/07/26: 更新:Twitter: 動画保存サポーター v2.0.2
2016/07/13: 公開:Twitter: 動画保存サポーター
2015/07/21: 更新:Fur Affinity.net: Change Method v1.0.2
2015/04/24: 公開:イカロスオンライン: 起動コード表示 v1.0.0
2014/12/14: 公開:Dビデオ スタイル変更 v1.0.0
2014/10/22: 更新:X-Legend ログインアシスト (OTP版) v1.0.1
2014/10/21: 個人使用いていたものを公開