スポンサーサイト

Tags :
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

FC2Blogに画像をアップロードするAppleScriptが動作するかをチェックするスクリプト書いた

ちょっと前に
ドラッグ&ドロップでFC2ブログに複数の画像をまとめてアップロードするAppleScript その4
てのを書きましたが、先週あたりにFC2Blogの管理画面がアップデートされて、アップロードする画像を選択するフォームが複数表示されるように変更された為(どうせなら、はてなfotolifeみたいにダイアログ中で複数選択出来る様にすれば良いのに)、スクレイピングに頼っている部分が動作しなくなっていたので元エントリのスクリプトを更新しておきました。

で、今回も使ってる方からの報告で動作しなくなってるのを知ったんですが、そちらに頼ってばっかりなのも何なので、上記のAppleScriptが動作するかどうかをチェックして、もし動作しなくなる様なFC2Blogの仕様変更があった場合にはメールで知らせるようなスクリプトをPythonで書いてcronで回す事にしました。AppleScriptで書いても良かったんですけど、cronで動かすのでほぼ常時電源の入ってるLinux PCで使える様に。

やってる事は、上記のAppleScriptと同じ方法でアップロードを試みて、失敗するとSubjectにどの処理で失敗したのか、本文はその時サーバから返って来たHTMLを入れて自分宛にメールを送ります。始めはサーバとやり取りするのにurllib2を使って書いてたんですが、画像等の『multipart/form-data』をすんなりPOST出来ない感じだったので、手っ取り早くcurlコマンドをpopen2モジュールで呼び出して処理しています。
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys, re
import smtplib
from popen2 import popen3
from email.MIMEText import MIMEText
from email.Header import Header
from email.Utils import formatdate
from BeautifulSoup import BeautifulSoup

#SMTPでメールを送信
def send_mail(subject, body):
    from_addr = 'sender@example.com'
    to_addr = 'receiver@example.com'
    encoding = 'ISO-2022-JP'

    #メッセージを生成
    msg = MIMEText(body.encode(encoding), 'plain', encoding)
    msg['Subject'] = Header(subject, encoding)
    msg['From'] = from_addr
    msg['To'] = to_addr
    msg['Date'] = formatdate()

    #送信
    s = smtplib.SMTP("localhost")
    s.sendmail(from_addr, [to_addr], msg.as_string())
    s.close()


#コマンドを実行し処理結果を返す
def popen(cmd):
    stdout, stdin, stderr = popen3(cmd)    
    rep = []
    for line in stdout: rep.append(line)
    return ''.join(rep)


def main():
    blogurl = 'http://MY_ACCOUNT.blog80.fc2.com/admin.php'
    uploadurl = 'http://blog80.fc2.com/control.php'
    passwd = 'MY_PASSWORD'
    id = 'MY_ACCOUNT'
    testfile = 'fc2blog_uploadtest.jpg'
    cookiefile = 'fc2uploader_cookie.txt'
    title = 'FC2Blog_uploadchaecker'
    
    #ログイン処理
    cmd  = 'curl -d "id=%s&pass=%s&mode=admin&mode=logging&process=in" -c "%s" -L "%s" | tail -n 300' % (id, passwd, cookiefile, blogurl)
    rep = popen(cmd)
    for line in rep.splitlines():
        if  'input type="password"' in line:
            send_mail(u'%s : ログインに失敗' % title, unicode(rep, 'EUC-JP'))
            sys.exit(0)

    #アップロード時にPOSTするcrcの値を取得
    cmd = 'curl -b %s -d "mode=control&process=upload" -L "%s" | grep \'name="crc"\' | sed \'s/\t*<input.*value=\"//\' | sed \'s/".*\'//' % (cookiefile, blogurl)
    rep = popen(cmd)
    crc =  rep.splitlines()[1]
    if crc == '':
        send_mail(u'%s : crc値の取得に失敗' % title, stdout.read())
        sys.exit()

    #テスト用画像をアップロード
    cmd  = 'curl -b %s -F "upfile[0]=@%s" -F mode=control -F process=upload -F width=200 -F height=200 -F overwrite=force -F type=upload -F crc=%s -F insert="" -L "%s"' % (cookiefile, testfile, crc, uploadurl)
    rep = popen(cmd)
    result = False
    for line in rep.splitlines():
        if testfile in line:
            result = True
    if result == True:
        #アップロード成功の場合
        soup = BeautifulSoup(rep)
        td = soup.find('td', 'manage')
        try:
            #ファイル削除用のcrc値、ファイルIDを取得
            m = re.search('fileno=([0-9]+)&crc=([0-9a-z]+)', str(td))
            fileno = m.group(1)
            crc = m.group(2)
            print fileno, crc
            #テスト用ファイルを削除
            cmd = 'curl -b %s -F mode=control -F process=upload -F type=delete -F no=%s -F crc=%s -F page=1 -F tail= -F sort=dd -L "%s"' % (cookiefile, fileno, crc, uploadurl)
            popen(cmd)
        except AttributeError:
            #ファイル削除用のcrc値、ファイルIDを取得出来ない場合
            send_mail(u'%s : 削除用ファイルID,crcの取得に失敗' % title, unicode(rep, 'EUC-JP'))
            sys.exit()
        #テスト用画像が削除出来ているかをチェック
        cmd = 'curl -b %s -d "mode=control&process=upload" -L "%s"' % (cookiefile, blogurl)
        rep = popen(cmd)
        for line in rep.splitlines():
            if testfile in line:
                send_mail(u'%s : ファイルの削除に失敗' % title, unicode(rep, 'EUC-JP'))
                sys.exit()
        send_mail(u'%s : 正常終了' % title, unicode(rep, 'EUC-JP'))
    else:
        #アップロード失敗
        send_mail(u'%s : ファイルのアップロードに失敗' % title, unicode(rep, 'EUC-JP'))
                
if __name__ == '__main__':
    main()
スポンサーサイト

InDesignでSocketを使ってWeb上のファイルをローカルにダウンロードしてみる

やってみたら出来てしまったので。

InDesignのみでWebにある画像等のファイルをローカルに保存出来れば色々と出来る事が広がるんじゃないか、と思ったのでやってみました。業務等でもっとちゃんとした事やるにはWeb Access libraryに含まれるHttpConnectionオブジェクトを使って書いた方が良いと思いますが、セットアップする必要がある様だし、もっとお手軽にコピペで使えるようなもの、みたいな感じで。

今回のコードは、HTTPのリクエストを送ってレスポンスを受け取る部分と、その関数を呼び出して実際にファイルとして保存する部分とに分かれてます。

HTTPのリクエストを送って、レスポンスを受け取る部分は、関数getHttpResponse(requests)として書きましたが、これは先日のエントリのHTTPでのやり取りをするコードに、
・画像等のバイナリデータを受け取れるようにする
・Basic認証が必要なサイトに対してユーザ名とパスワードを送れるようにする
部分を新たに加えました(相変わらずOOPな書き方が全く出来てませんけど)。URLを処理する部分の正規表現はO'REILLYの『JavaScript: The Good Parts』から拝借しています。

この関数には引数を
{
	method: 'GET', 
	url: 'http://www.example.com/hogehoge.jpg',
	encoding: 'BINARY'
	basic_auth: 'ZHVtbXk6ZHVtbXk='
}
の様な連想配列で渡します。

引数の内容は以下の通り
・method リクエストのメソッド 'GET','HEAD','POST'等。省略可。デフォルトは'GET'。
・url リクエストのURL(クエリも全て含んだもの)
・encoding 受け取るデータのエンコーディング。省略可。デフォルトは'UTF-8'。
・basic_auth Basic認証に必要な、『ユーザ名:パスワード』の文字列をBase64でエンコーディングした文字列。Basic認証が必要な場合のみこの引数を与える。

この関数は戻り値として、サーバからのレスポンスをヘッダ付きのまま丸ごと返します。値のチェックは最低限の事しかやってないので変な引数を渡すと動作しません。

そして、上記の関数を呼び出してファイルに書き出し、保存する部分の関数、downloadFile(url, localFile)ですが、これの引数は、
・url ダウンロードしたいファイルのURL
・localfile ローカルでのファイル名を含めたファイルのパス('~/Desktop/hogehoge.jpg'等)
となっていて、戻り値として保存したファイルオブジェクトか返ります。

下のコードを実行すると、AdobeのサイトのCS4のパッケージ画像(http://tryit.adobe.com/jp/cs4/family/images/familyBox.jpg)がデスクトップに保存されます。(保存されるファイルのパスはMacOSX用の記述です)


■2009.3.19 追記
コードを一部修正。

■動作確認
MacOSX 10.5.6
InDesign CS4
#target InDesign

//HTTPレスポンスを得る
function getHttpResponse(requests) {
	var parseUrl = function(url) {
		var urlObj = {};
		//[url, scheme, slash, host, port, path, query, fragment] via O'REILLY JavaScript: The Good Parts
		var url_re = /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;
		var m = url_re.exec(url);
		urlObj.host = m[3];
		urlObj.port = m[4] || '80';
		urlObj.path = m[5];
		urlObj.query = (m[6]) ? '?' +  m[6] : '';
		urlObj.frag = (m[7]) ? '#' + m[7] : '';
		return urlObj;
	}
	var urlObj = parseUrl(requests.url);
	var encoding = requests.encoding || 'UTF-8';
	var method = requests.method || 'GET';
	var auth = (requests.basic_auth != undefined) ? 'Authorization: Basic ' + requests.basic_auth + '\r\n' : '';
	var conn = new Socket;
	conn.timeout = 10;
	if (conn.open (urlObj.host + ':' + urlObj.port, encoding)) {
  	conn.write (method + ' /' + urlObj.path + urlObj.query + urlObj.frag + ' HTTP/1.0\r\n'
		+ 'Host: ' + urlObj.host + '\r\n'
		+ 'User-Agent: ' + 'InDesign/6.0' + '(Macintosh; U; Intel Mac OS X 10_5_6; ja-jp)' + '\r\n'
		+ auth
		+ '\r\n');
		var reply = conn.read(999999);
		conn.close();
		return reply; //ヘッダ込みで返す
	} else {
		return conn.error;
	}
}

//Webからファイルをダウンロードする
function downloadFile(url, localFile) {
	var rep = getHttpResponse({
		method: 'GET',
		url: url,
		encoding: 'BINARY',
	});
	if (rep.match(/HTTP.*\d{3}/).toString().indexOf('200') != -1) {
		//レスポンスのヘッダを除去 
		var body = rep.slice(rep.indexOf('\r\n\r\n') + 4);
		//ファイルに書き出し
		var f = new File(localFile);
		if (f.open("w")) {
			f.encoding = 'BINARY';
			f.write(body);
		}
		f.close();
		return f;
	} else {
		return false;	
	}
}

var f = downloadFile('http://tryit.adobe.com/jp/cs4/family/images/familyBox.jpg', '~/Desktop/familyBox.jpg');
if (f) {
	alert(f.name + ' を保存しました。');
}

InDesignドキュメントのテキストをGoogleの翻訳APIを使って翻訳・置換してみる

『Google AJAX Language API』
http://code.google.com/apis/ajaxlanguage/documentation/
これと、InDesignのSocketオブジェクト使ってドキュメントのテキストを翻訳してみます。

■参考URL
http://d.hatena.ne.jp/C_L/20081012/indesign_socket_http

例えば、InDesignのドキュメントにこんな感じのテキストを用意して、


下にあるJavaScriptを実行すると、

こんな感じで翻訳されたテキストに置換します。

レスポンスは、ヘッダ部分を除くと
{"responseData": {"translatedText":"Ghost in the Shell"}, "responseDetails": null, "responseStatus": 200}
の様なJSONで返って来るので簡単に翻訳結果を取り出せます。一部の記号類は『&#39;』等の文字参照で返ってくるみたいです。
あと、今回のやり方だとテキストフレームの文字数が220文字前後(InDesignの情報パレットで)を境に400 Bad Requestが返って来るようになりました。

何らかの業務で使えるかどうかは非常に怪しいですが、単語レベルなら意外な使い道があるかも知れません。

で、これやってて思ったんですが、InDesignでSocketが使えるって事は、将来InDesignのドキュメントをインターネット越しに何らかの処理をするサービスなんかもあり得るわけですが、例えば第三者から渡されたドキュメントを処理する時、処理結果が悪意のあるスクリプトになるように仕組まれていた場合(今回試した翻訳の場合だと、翻訳結果がスクリプトとして意味のある文字列になる等。これだと環境設定の「添付スクリプトを有効にする」オプションは関係無いので)、取り出した値をeval()関数で評価するような書き方をしていてチェックが甘いとセキュリティの穴にもなり得る訳で、特にInDesignのスクリプトを書いたりするのは自分の様にWeb屋でない人も多いと思うので、その辺気を付ける必要も出てくるのかなぁと思った次第です。

まあこのあたりはインターネットかローカルかに関わらないですが、Webブラウザで実行されるJavaScriptと違って、ファイルシステムにもアクセス出来る訳だし、『inddスクリプトインジェクション』なんて攻撃法なんかが出て来たり…なんてのは夢が広がり過ぎですかね。

#target InDesign 

const HOST = 'ajax.googleapis.com';
const PATH = '/ajax/services/language/translate';
	
//google翻訳APIでテキストを翻訳
function getTranslation(str) {
	var src = 'ja';
	var dest = 'en';
	var query = '?v=1.0&q=' + encodeURI(str) + '&langpair=' + src + '%7C' + dest;
	var rep = "";
	var conn = new Socket;
	if (conn.open(HOST + ':80', 'UTF-8')) {
		conn.write ('GET ' + PATH + query + " HTTP/1.0\n"
			+ 'Host: ' + HOST + "\n"
			+ 'User-Agent: ' + 'InDesign/6.0 ' +'(Macintosh; U; Intel Mac OS X 10_5_6; ja-jp)' + "\n"
			+ "\n");
		rep = conn.read(99999);
		conn.close();
	} else {
		return false;
	}
	var rep_json = eval('(' + rep.match(/\{"responseData".*\}?/) + ')');
	return rep_json.responseData.translatedText;
}

var T = app.activeDocument.textFrames;
for (var i=0; i<T.length; i++) {
	T[i].contents = getTranslation(T[i].contents);
}

配置されている画像を指定したサイズの矩形でマスクし、サイズと位置をフィットさせるIllustrator用JavaScript

Illustratorのドキュメントに配置されている画像を指定したサイズの矩形でマスクして、そのマスクに合うように画像をリサイズし、指定した間隔でファイル名順に並べて行くJavaScript。

例えば、こんな感じでドキュメントにフォルダからドラッグ&ドロップするなどして、無造作に画像を配置しておいて(分かりやすくする為にわざとバラバラに置いてますけど)、


このスクリプトを実行すると、マスクに使う矩形のサイズ、間隔、何列で並べるか等を入力するダイアログを表示するので、それらの数値を入力すると、


画像はドキュメントの左上を起点に、ファイル名順に並んでいきます。


画像は、マスクの矩形のセンターに来るように配置され、サイズはマスクよりほんのちょっとだけ大きくなる様リサイズされます。


■注意点
既にマスクされている画像、グループ化されている画像は処理されません。
ブラウザの環境(Safari等)でスクリプト中に『円マーク』が見えている場合は『バックスラッシュ』に置換する必要があります。
※表示されているコードの上の方にある『view plain』をクリックするとコードがプレーンテキストで表示される筈なんですが、Safari(3.2.1)だと動作しない様なので、対策考えておきます。対策済み。

■動作確認
MacOSX 10.5.6
Illustrator CS4
#target Illustrator

//マスクのサイズを入力するダイアログを表示
function askRectSettings() {
	var mes = 
		"dialog { alignChildren: 'center', \
			field1: Panel { orientation: 'column', alignChildren: 'right', \
				size: [180,72], text: 'マスクのサイズ', \
				size_w: Group { orientation: 'row', \
					s: StaticText { text: '幅:' }, \
					e: EditText { characters: 7, text: '50.0'}, \
					s2: StaticText { text: 'mm'} \
				}, \
				size_h: Group { orientation: 'row', \
					s: StaticText { text: '高さ:' }, \
					e: EditText { characters: 7, text: '50.0' } \
					s2: StaticText { text: 'mm' } \
				} \
			}, \
			field2: Panel { orientation: 'column', alignChildren: 'right', \
				size: [180,72], text: '画像間の余白', \
				margin_w: Group { orientation: 'row', \
					s: StaticText { text: '左右:' }, \
					e: EditText { characters: 7, text: '3.0'}, \
					s2: StaticText { text: 'mm'} \
				}, \
				margin_h: Group { orientation: 'row', \
					s: StaticText { text: '天地:' }, \
					e: EditText { characters: 7, text: '3.0' } \
					s2: StaticText { text: 'mm' } \
				} \
			}, \
			field3: Panel { orientation: 'column', \
				size: [180,40], text: '水平方向に並べる数', \
				column: Group { orientation: 'row', \
					e: EditText { characters: 3, text: '4' }, \
					s: StaticText { text: 'で折り返し' }, \
				} \
			}, \
			buttons: Group { orientation: 'row', \
				cancelBtn: Button { text:'Cancel', properties: { name: 'cancel'} }, \
				okBtn: Button { text: 'OK', properties: {name:'ok'} } \
			} \
		}";
	var dlg = new Window(mes);
	
	dlg.buttons.okBtn.onClick = function() {
		dlg.close(1);
	}

	dlg.center();
	var result = dlg.show();
	var w = dlg.field1.size_w.e.text;
	var h = dlg.field1.size_h.e.text;
	var mw = dlg.field2.margin_w.e.text;
	var mh = dlg.field2.margin_h.e.text;
	var co = dlg.field3.column.e.text;
	if (result == 1) {
		if (w != '' && h != '' && mw != '' && mh != '' && co !='') {
			try {
				return [eval(w), eval(h), eval(mw), eval(mh), eval(co)];
			} catch(e) {
				alert('数字を入力してください!');
				return false;
			}
		} else {
			alert('数値が入力されていません!');
			return false;
		}
	} else {
		return false;
	}
}

//PlacedItemsをファイル名でソートする
function sortPlacedItemsByName(plItems) {
	var retArr = [];
	var fNames = [];
	for (var i=0; i<plItems.length; i++) {
		var fn = plItems[i].file.name + plItems[i].file + i; //同名ファイルをユニークにするためパスと番号を追加
		plItems[i].name = fn
		fNames.push(fn);
	}
	fNames.sort();
	for (var i=0; i<fNames.length; i++) {
		retArr.push(plItems.getByName(fNames[i]));
	}
	return retArr;
}

//マスクされているかどうか
function isMasked(obj) {
	var p = obj.parent;
	while (p.constructor.name != 'Document') {
		if (p.clipped) {
			return true;
		} else {
			p = p.parent;
		}
	}
	return false;
}

//メイン
function clipAndFitImgs() {
	var doc = activeDocument;
	var P = sortPlacedItemsByName(doc.placedItems); //PlacedItemsをソートしておく
	
	var res = askRectSettings();
	if (!res) { return; }
	var ptm = 2.834645;
	var rect_w = res[0] * ptm; //mmに換算
	var rect_h = res[1] * ptm;
	var ofst_x = res[2] * ptm;
	var ofst_y = res[3] * ptm;
	var column = res[4];
	
	var sp = { //整列を開始する起点
		left: 0,
		top: doc.height
		}	
	var tgtImgs = [];		
	var rc = 0; //カウント用
	var cc = 0;
	
	var ro = doc.rulerOrigin;
	var ru = doc.rulerUnits;
	doc.rulerOrigin = [0, 0];
	doc.rulerUnits = 'Points';
	
	//マスク・グループ化されていないものを処理対象にする
	for (var i=0; i<P.length; i++) {
		if (!isMasked(P[i]) && P[i].parent.constructor.name != 'GroupItem') { tgtImgs.push(P[i]); } 
	}

	for (var i=0; i<tgtImgs.length; i++) {
		var ti = tgtImgs[i];
		var l = ti.parent;
		if ( l.constructor.name == 'Layer' ) { //不要?
			var g = l.groupItems.add();
			ti.moveToBeginning(g); //PathItemより先にPlacedItemをGroupItemに追加しないとマスクされない?
			var rect = g.pathItems.rectangle(sp.top - rect_h * rc - ofst_y * rc, sp.left + rect_w * cc + ofst_x * cc, rect_w, rect_h);
			cc++;
			if (cc == column) { 
				cc = 0;
				rc++;
			}
			ti.parent.clipped = true;
			var enl = Math.max((rect_w + 4) / ti.width, (rect_h + 4) / ti.height) * 100;
			ti.resize(enl, enl);
			//マスクに画像をフィットさせる
			//ti.move(rect, ElementPlacement.INSIDE);
			ti.top = (rect.top - rect.height / 2) + ti.height / 2;
			ti.left = (rect.left + rect.width / 2) - ti.width / 2;
		}
	}

	doc.rulerOrigin = ro;
	doc.rulerUnits = ru;
}


if (app.documents.length == 0) {
	alert('ドキュメントが開かれていません。');
} else {
	clipAndFitImgs();
}
Profile
choco
Author : choco

印刷・製版の現場を経て、広告制作会社でPhotoshopを使ったビジュアル制作を担当。

→現在は車載機器開発ベンダにて、組み込み3Dデータ作成やUIデザインなどを行っています。

Categories
Favorites


Search
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。