mixi メッセージにはたらく Greasemonkey かきなおした

2013/03/26 追記mixi メッセージの仕様変更により、現在は動作しない。書き直したいが、受信はともかく送信メッセージの単体表示が見当たらないので悩んでいる。

先日のエントリの続き。 Datula 1.52.01.01 でも動作を確認したので貼っておく。

なお Base64 へのエンコードutf-16utf-8 へ変換する関数は高度な JavaScript 技集よりいただきました。助かりました。


相変わらず汚いソースだなあ。

// ==UserScript==
// @name mixi message export helper
// @namespace http://d.hatena.ne.jp/naglfar/
// @description v0.04
// @include http://mixi.jp/view_message.pl*
// ==/UserScript==

(function() {

	/*
	 * 変換関数はこちらのサイトよりいただきました。感謝します。
	 * 高度な JavaScript 技集
	 * <http://www.onicos.com/staff/iz/amuse/javascript/expert/>
	 */
	{
		/*
		 * Copyright (C) 1999 Masanao Izumo <iz@onicos.co.jp> Version: 1.0
		 * LastModified: Dec 25 1999 This library is free. You can redistribute
		 * it and/or modify it.
		 */

		/*
		 * Interfaces: b64 = base64encode(data);
		 */

		var base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

		function base64encode(str) {
			var out, i, len;
			var c1, c2, c3;

			len = str.length;
			i = 0;
			out = "";
			while (i < len) {
				c1 = str.charCodeAt(i++) & 0xff;
				if (i == len) {
					out += base64EncodeChars.charAt(c1 >> 2);
					out += base64EncodeChars.charAt((c1 & 0x3) << 4);
					out += "==";
					break;
				}
				c2 = str.charCodeAt(i++);
				if (i == len) {
					out += base64EncodeChars.charAt(c1 >> 2);
					out += base64EncodeChars.charAt(((c1 & 0x3) << 4)
							| ((c2 & 0xF0) >> 4));
					out += base64EncodeChars.charAt((c2 & 0xF) << 2);
					out += "=";
					break;
				}
				c3 = str.charCodeAt(i++);
				out += base64EncodeChars.charAt(c1 >> 2);
				out += base64EncodeChars.charAt(((c1 & 0x3) << 4)
						| ((c2 & 0xF0) >> 4));
				out += base64EncodeChars.charAt(((c2 & 0xF) << 2)
						| ((c3 & 0xC0) >> 6));
				out += base64EncodeChars.charAt(c3 & 0x3F);
			}
			return out;
		}

		/*
		 * utf.js - UTF-8 <=> UTF-16 convertion
		 * 
		 * Copyright (C) 1999 Masanao Izumo <iz@onicos.co.jp> Version: 1.0
		 * LastModified: Dec 25 1999 This library is free. You can redistribute
		 * it and/or modify it.
		 */

		/*
		 * Interfaces: utf8 = utf16to8(utf16);
		 */

		function utf16to8(str) {
			var out, i, len, c;

			out = "";
			len = str.length;
			for (i = 0; i < len; i++) {
				c = str.charCodeAt(i);
				if ((c >= 0x0001) && (c <= 0x007F)) {
					out += str.charAt(i);
				} else if (c > 0x07FF) {
					out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F));
					out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F));
					out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
				} else {
					out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F));
					out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
				}
			}
			return out;
		}
	}

	const myname = "John Doe";
	const myid = "identification";

	const htmlHeader = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body>';
	const htmlFooter = '</body></html>';

	const crlf = "\r\n";

	var container = document.getElementById("messageDetail");
	var headers = document.getElementsByTagName("dd");
	var address = headers[1].getElementsByTagName("a")[0].getAttribute("href");
	var senddate = parseDate(headers[0].firstChild.nodeValue);
	main();

	function main() {

		var parent = document.createElement("form");
		var filename = document.createElement("input");
		var body = document.createElement("textarea");
		var newline = document.createElement("br");
		var separator = document.createElement("hr");

		filename.setAttribute("type", "text");
		filename.setAttribute("size", "70");
		filename.setAttribute("onfocus", "select();");
		filename.setAttribute("value", getFileName());

		body.setAttribute("rows", "20");
		body.setAttribute("cols", "70");
		body.setAttribute("onfocus", "select();");

		body.innerHTML = getTextAsMailFormat();

		parent.appendChild(filename);
		parent.appendChild(newline);
		parent.appendChild(body);

		container.appendChild(separator);
		container.appendChild(parent);
	}

	function getFileName() {
		return (+address.substring(address.indexOf("id=") + 3) + '_'
				+ senddate.getFullYear() + padZero(senddate.getMonth() + 1)
				+ padZero(senddate.getDate()) + padZero(senddate.getHours())
				+ padZero(senddate.getMinutes()) + '.eml');
	}

	/**
	 * メッセージ本文がすでにエンティティ参照になっているので
	 * TextNode として追加するとエンティティ参照の & が &amp; へ変換される
	 * かといってエンティティ参照を文字へ戻せばタグと混ざる
	 * よって textarea の innerHTML へ設定するようにテキストを作る
	 */
	function getTextAsMailFormat() {
		var escapeNegligence = function(s) {
			return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g,
					'&gt;').replace(/ /g, '&nbsp;');
		};
		var encodeToHeader = function(s) {
			return '=?UTF-8?B?' + base64encode(utf16to8(s)) + '?=';
		};

		var vt = document.getElementsByTagName("h3")[0].firstChild.nodeValue;

		var vhb = document.getElementById("message_body").innerHTML;

		var vf = encodeToHeader(escapeNegligence(headers[1]
				.getElementsByTagName("a")[0].firstChild.nodeValue))
				+ " &lt;http://mixi.jp/" + address + "&gt;"
		var vm = encodeToHeader(myname)
				+ " &lt;http://mixi.jp/show_friend.pl?id=" + myid + "&gt;";

		var isInbox = (document.location.search.indexOf("box=inbox") >= 0);

		var vi = document.location.search.substring(document.location.search
						.indexOf("id=")
						+ 3, document.location.search.indexOf("box") - 1);

		var boundary = "----=_NextPart_MIXI_MESSAGE_" + vi;

		var vo = "";

		vo += "Message-ID: &lt;" + vi + "@mixi.jp&gt;" + crlf;
		vo += "Date: ";
		vo += formatDate(senddate) + crlf;
		vo += "From: " + (isInbox ? vf : vm) + crlf;
		vo += "To: " + (isInbox ? vm : vf) + crlf;
		vo += "Subject: ";
		vo += encodeToHeader(vt) + crlf;
		vo += 'Content-Type: multipart/alternative; boundary="' + boundary
				+ '"' + crlf;

		vo += crlf;

		vo += "--" + boundary + crlf;
		vo += 'Content-Type: text/plain; charset="utf-8"' + crlf;
		vo += crlf;

		vo += vhb.replace(/[\r\n]/g, '').replace(/<br[ /]*>/g, '\r\n').replace(
				/<img src="http:\/\/img\.mixi\.net\/img\/emoji\//gi, '[m:')
				.replace(/\.gif"[^>]*>/g, ']').replace(/<[^>]+>/g, '')
				+ crlf;

		vo += crlf;

		vo += "--" + boundary + crlf;
		vo += 'Content-Type: text/html; charset="utf-8"' + crlf;
		vo += crlf;

		vo += escapeNegligence(htmlHeader) + crlf;
		vo += escapeNegligence(vhb) + crlf;
		vo += escapeNegligence(htmlFooter) + crlf;

		vo += crlf;

		vo += "--" + boundary + "--" + crlf;

		return vo;
	}

	function padZero(s) {
		return (s < 10 ? '0' + s : s);
	}

	function parseDate(s) {
		var result = new Date("1900/01/01 00:00:00");

		result.setFullYear(s.substring(0, 4));
		result.setMonth(s.substring(5, 7) - 1);
		result.setDate(s.substring(8, 10));
		result.setHours(s.substring(12, 14));
		result.setMinutes(s.substring(15, 17));

		return result;
	}

	function formatDate(d) {
		var weekdays = new Array('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri',
				'Sat');
		var monthes = new Array('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
				'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');
		var result = "";

		result += weekdays[d.getDay()];
		result += ", "
		result += padZero(d.getDate());
		result += " ";
		result += monthes[d.getMonth()];
		result += " ";
		result += d.getFullYear();
		result += " ";
		result += padZero(d.getHours());
		result += ":";
		result += padZero(d.getMinutes());
		result += ":";
		result += padZero(d.getSeconds());
		result += " +0900";

		return result;
	}

})();

バージョンアップしてしまった。といっても、 plain text 部のタグを削除するようにしただけだけれど。

絵文字のサーバが mixi.jp から mixi.net になっていたのでリテラルの内容を変更した。……どっちでも動作するようにしておくべきかな。とりあえずの対応。