auoneブログからはてなダイアリーへ引っ越し の続き
- [id:igarashitm:20091231:1262250632]
- [id:igarashitm:20091230:1262168984]
完了。はてなダイアリーとはてなフォトライフの投稿をAtomでがんばってみた。結局、Abderaはなんだかいうことを聞いてくれないので早々に捨てた。AuoneブログのRSS処理はROMEを使用し、はてなへのAtomのPOSTは生XMLをApache HttpClientを直接蹴って送信。
ちなみに、Apache HttpClientは4.0.1を使用。ぐぐって出てくるサンプルは3.x系が多いが、どうも細かいところがちょいちょい違うみたい。
それにしても・・・久々にコーディングしたなぁ・・・
AuoneBlogExporter.java
なんてことはない。ROMEを使ってRSS経由でAuoneブログの全エントリを取得。
import java.util.List; import java.util.Date; import java.util.ArrayList; import java.net.URL; import com.sun.syndication.feed.synd.SyndFeed; import com.sun.syndication.feed.synd.SyndEntry; import com.sun.syndication.io.SyndFeedInput; import com.sun.syndication.io.XmlReader; public class AuoneBlogExporter { private static final String FEED_URL = "http://blog.auone.jp/igarashitm/?disp=rss&pageID="; public List<SyndEntry> fetchAll() throws Exception { SyndFeedInput sfi = new SyndFeedInput(); String prevTitle = null; Date prevDate = null; int page = 1; List<SyndEntry> allEntries = new ArrayList<SyndEntry>(); while(true) { SyndFeed sf = sfi.build(new XmlReader(new URL(FEED_URL+page))); @SuppressWarnings("unchecked") List<SyndEntry> entries = sf.getEntries(); SyndEntry topEntry = entries.get(0); if( topEntry.getTitle().equals(prevTitle) && topEntry.getPublishedDate().equals(prevDate) ) { // auoneブログでは(他は知らね)、最終ページより大きいページを指定しても // 最終ページが取得されるので、 これを利用して最終ページを判定する。 break; }else { prevTitle = topEntry.getTitle(); prevDate = topEntry.getPublishedDate(); allEntries.addAll(entries); page++; } } return allEntries; } }
HatenaDiaryImporter.java
postEntry()とpostPhoto()がキモ。それぞれ、はてなダイアリー、はてなフォトライフにAtomでPOSTする。なお、サブルーチンのgenerateWsseHeader()は、yohei-y:weblog: 2005-04こちらを参考にさせていただきました。というかまんまぱちってきました。こんなところで恐縮ですがありがとうございます。
//import java.util.Date; import java.util.List; import java.util.Calendar; import java.util.TimeZone; import java.text.SimpleDateFormat; import java.io.InputStream; import java.io.BufferedReader; import java.io.InputStreamReader; import java.security.MessageDigest; import java.security.SecureRandom; import org.jdom.Element; import com.sun.syndication.feed.synd.SyndFeed; import com.sun.syndication.io.SyndFeedInput; import com.sun.syndication.io.XmlReader; import org.apache.commons.codec.binary.Base64; import org.apache.http.HttpResponse; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; public class HatenaDiaryImporter { private final static String D_ATOM_BASE_URL = "http://d.hatena.ne.jp/igarashitm/atom/blog"; private final static String F_ATOM_POST_URL = "http://f.hatena.ne.jp/atom/post"; private static final String USERNAME = "hoge"; private static final String PASSWORD = "fuga"; private DefaultHttpClient httpClient; public HatenaDiaryImporter() { this.httpClient = new DefaultHttpClient(); } public void postEntry( String _title, String _content, String _updated) throws Exception { StringBuilder outxml = new StringBuilder(); outxml.append("<entry xmlns=\"http://www.w3.org/2005/Atom\">\r\n"); outxml.append("<title>"); outxml.append(_title); outxml.append("</title>\r\n"); outxml.append("<content type=\"text\">"); outxml.append(_content); outxml.append("</content>\r\n"); outxml.append("<updated>"); outxml.append(_updated); outxml.append("</updated>\r\n"); outxml.append("</entry>"); HttpPost post = new HttpPost(D_ATOM_BASE_URL); post.addHeader("X-Wsse", generateWsseHeader(USERNAME,PASSWORD)); StringEntity entity = new StringEntity(outxml.toString(),"UTF-8"); post.setEntity(entity); HttpResponse res = httpClient.execute(post); System.out.println("post entry["+_title+"] >> " + res.getStatusLine().toString()); if( res.getStatusLine().getStatusCode() >= 400 ) { System.err.println(outxml.toString()); } res.getEntity().getContent().close(); } public String postPhoto( String _title, String _photoUrl ) throws Exception { String base64encoded = null; HttpGet get = new HttpGet(_photoUrl); HttpResponse res = httpClient.execute(get); System.out.println("get img["+_photoUrl+"] >> " + res.getStatusLine().toString()); System.out.println("get img >> " + res.getEntity().getContentType()); InputStream in = res.getEntity().getContent(); byte[] bufB = new byte[1024], imgB = new byte[0]; int count=0, total=0; while( (count = in.read(bufB)) != -1 ) { total += count; byte[] newImgB = new byte[total]; System.arraycopy(imgB, 0, newImgB, 0, imgB.length); System.arraycopy(bufB, 0, newImgB, imgB.length, count); imgB = newImgB; } in.close(); base64encoded = new String(Base64.encodeBase64(imgB)); StringBuilder outxml = new StringBuilder(); outxml.append("<entry xmlns=\"http://purl.org/atom/ns#\">\r\n"); outxml.append("<title>"); outxml.append(_title); outxml.append("</title>\r\n"); outxml.append("<content mode=\"base64\" type=\""); outxml.append(res.getEntity().getContentType().getValue()); outxml.append("\">"); outxml.append(base64encoded); outxml.append("</content>\r\n"); outxml.append("</entry>\r\n"); HttpPost post = new HttpPost(F_ATOM_POST_URL); post.addHeader("X-Wsse", generateWsseHeader(USERNAME,PASSWORD)); StringEntity entity = new StringEntity(outxml.toString(),"UTF-8"); post.setEntity(entity); res = httpClient.execute(post); System.out.println("post img >> " + res.getStatusLine().toString()); if( res.getStatusLine().getStatusCode() >= 400 ) { System.err.println(outxml.toString()); } in = res.getEntity().getContent(); SyndFeedInput sfi = new SyndFeedInput(); SyndFeed sf = sfi.build(new XmlReader(in)); //System.out.println("post img >> " + sf.toString()); in.close(); @SuppressWarnings("unchecked") List<Element> fm = (List<Element>)sf.getForeignMarkup(); String output = null; for( Element e : fm ) { if( e.getName().equals("syntax")) { output = e.getText(); } } return output; } private String generateWsseHeader( String _username, String _password ) throws Exception { byte[] nonceB = new byte[8]; SecureRandom.getInstance("SHA1PRNG").nextBytes(nonceB); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); sdf.setTimeZone(TimeZone.getTimeZone("JST")); Calendar now = Calendar.getInstance(); now.setTimeInMillis(System.currentTimeMillis()); String created = sdf.format(now.getTime()); byte[] createdB = created.getBytes("utf-8"); byte[] passwordB = _password.getBytes("utf-8"); byte[] v = new byte[nonceB.length + createdB.length + passwordB.length]; System.arraycopy(nonceB, 0, v, 0, nonceB.length); System.arraycopy(createdB, 0, v, nonceB.length, createdB.length); System.arraycopy(passwordB, 0, v, nonceB.length + createdB.length, passwordB.length); MessageDigest md = MessageDigest.getInstance("SHA1"); md.update(v); byte[] digest = md.digest(); StringBuilder wsse = new StringBuilder(); wsse.append("UsernameToken Username=\""); wsse.append(_username); wsse.append("\", PasswordDigest=\""); wsse.append(new String(Base64.encodeBase64(digest))); wsse.append("\", Nonce=\""); wsse.append(new String(Base64.encodeBase64(nonceB))); wsse.append("\", Created=\""); wsse.append(created); wsse.append('"'); return wsse.toString(); } }
MoveAuoneToHatena.java
こんなもん貼っておいても誰も喜ばないとは思うけど・・・タイトルとか本文の整形をしている、泥臭いとこ。上の二つも決して綺麗なものではないけども。
import java.util.List; import java.util.TimeZone; import java.util.regex.Pattern; import java.text.SimpleDateFormat; import com.sun.syndication.feed.synd.SyndEntry; import com.sun.syndication.feed.module.DCModuleImpl; public class MoveAuoneToHatena { public void execute() throws Exception { AuoneBlogExporter auone = new AuoneBlogExporter(); List<SyndEntry> allEntries = auone.fetchAll(); HatenaDiaryImporter hatena = new HatenaDiaryImporter(); SimpleDateFormat photoSdf = new SimpleDateFormat("yyyyMMddHHmmss"); photoSdf.setTimeZone(TimeZone.getTimeZone("JST")); SimpleDateFormat entrySdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); entrySdf.setTimeZone(TimeZone.getTimeZone("JST")); for( SyndEntry entry : allEntries ) { // auoneブログでマイカテゴリとかいってたやつはここに入っている String category = ((DCModuleImpl)entry.getModules().get(0)).getSubject().getValue(); // カテゴリ無指定は" no data "が入っている。大したこと書いてないので無視 if( category.equals(" no data ") ) continue; StringBuilder title = new StringBuilder(); title.append("["); title.append(category); title.append("]"); if( category.equals("バイオリン") && ( entry.getTitle().matches(".*Lesson.*") || entry.getTitle().matches(".*指摘事項.*") )) { title.append("[レッスン記録]"); } title.append(" "); title.append(entry.getTitle().replace("\t", " ")); entry.setTitle(title.toString()); // 一応、Auoneの元記事へのリンクでも貼っておこうかな。 StringBuilder desc = new StringBuilder(); desc.append("***この記事は、こちらの元記事から引っ越しました →→→ ["); desc.append(entry.getLink()); desc.append("] \r\n \r\n \r\n"); // 貧弱なauoneブログで全角スペースやらを駆使して整形していたやつを、 // 可能な限りはてな記法に置き換える泥臭い仕事。 String newDesc = entry.getDescription().getValue(); newDesc = newDesc.replaceAll("<[bB][rR]>", "\r\n") .replaceAll("<[bB][rR] />", "") .replaceAll(" ", " ") .replaceAll("\\[", "**") .replaceAll("\\]", "") .replaceAll(" ・", "-") .replaceAll(" ", "-"); Pattern ptn = Pattern.compile("^・",Pattern.MULTILINE); newDesc = ptn.matcher(newDesc).replaceAll("-"); ptn = Pattern.compile("^ -",Pattern.MULTILINE); newDesc = ptn.matcher(newDesc).replaceAll("-"); ptn = Pattern.compile("^スズキ$",Pattern.MULTILINE); newDesc = ptn.matcher(newDesc).replaceAll("***スズキ"); ptn = Pattern.compile("^ホーマン$",Pattern.MULTILINE); newDesc = ptn.matcher(newDesc).replaceAll("***ホーマン"); ptn = Pattern.compile("^小野アンナ音階$",Pattern.MULTILINE); newDesc = ptn.matcher(newDesc).replaceAll("***小野アンナ音階"); // 本文から画像とリンクを抽出。画像ははてなフォトライフへアップしてリンク。 String datetimeStr = photoSdf.format(entry.getPublishedDate()); int anchors=0, imgAnchors=0; int start=0, end=0; while( (start = newDesc.indexOf("<a ")) != -1 ) { end = newDesc.indexOf("</a>", start)+4; String anchor = newDesc.substring(start, end); int imgstart=0, urlstart=0, urlend=0; if( (imgstart = anchor.indexOf("<img ")) != -1 ) { urlstart = anchor.indexOf("src='",imgstart)+5; urlend = anchor.indexOf("'",urlstart); String url = anchor.substring(urlstart,urlend) .replaceAll("&", "&"); String fLink = hatena.postPhoto(datetimeStr+"_"+imgAnchors, url); newDesc = newDesc.substring(0,start) + "[" + fLink + "]\r\n" + newDesc.substring(end); imgAnchors++; }else { urlstart = anchor.indexOf("href=\"")+6; urlend = anchor.indexOf("\"",urlstart); String url = anchor.substring(urlstart,urlend); newDesc = newDesc.substring(0,start) + url + "\r\n" + newDesc.substring(end); anchors++; } } desc.append(newDesc); String updatedStr = entrySdf.format(entry.getPublishedDate()); hatena.postEntry(title.toString(), desc.toString(), updatedStr); } } public static void main(String[] args) throws Exception { MoveAuoneToHatena mah = new MoveAuoneToHatena(); mah.execute(); } }