Webエンジニア susumuis の技術ブログ

このブログの内容は個人の見解であり、所属する組織の公式見解ではありません

俺タイムラインメールアプリ

前回(といっても、半年近く前なんですね) http://d.hatena.ne.jp/s-ishigami/20100224 で、TwitterのTimeLineや、Replyをメール配信するGAE/Java アプリを公開させていただきました。

その後、構造化されていないとか、一部からご反響をいただきましたが、あくまでサンプルコードのつもりで気楽に書いているので、メソッド一個でがんばりました。
Slim3とかJDOとか使っていません。

さて、この度
appengine ja hack-a-thon #6 (http://atnd.org/events/5932)
に参加させていただき、多くのすばらしい先生方を尻目に初心者の私ですが、上記アプリを拡張させていただいた次第でございます。

今回実装したのは以下のとおりです。
・OAuth対応
・DirectMessage対応

それではコードです。

package s_ishigami.ore_timeline_mail;

import java.io.IOException;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import twitter4j.DirectMessage;
import twitter4j.Paging;
import twitter4j.ResponseList;
import twitter4j.Status;
import twitter4j.Twitter;
import twitter4j.TwitterException;
import twitter4j.TwitterFactory;
import twitter4j.http.AccessToken;
import twitter4j.http.RequestToken;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.mail.MailServiceFactory;
import com.google.appengine.api.mail.MailService.Message;
import com.google.appengine.api.memcache.Expiration;
import com.google.appengine.api.memcache.MemcacheService;
import com.google.appengine.api.memcache.MemcacheServiceFactory;

@SuppressWarnings("serial")
public class OreTimelineMailServlet extends HttpServlet {
	// 設定情報
	private static final String SYSTEM_ADMIN_MAIL = "xxxx@xxx.xx";
	private static final String YOUR_MAIL = "xxxx@xxxx.xx";
	private static final String CONSUMER_KEY = "XXXXXXX";
	private static final String CONSUMER_SECRET = "XXXXXXXXXXXXXXXXXXXXXX";

	public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
		
		String path = request.getRequestURI().substring(request.getContextPath().length());
		
		DatastoreService dataService = DatastoreServiceFactory.getDatastoreService();
		MemcacheService memcacheService = MemcacheServiceFactory.getMemcacheService();
		try {
			Twitter twitter = new TwitterFactory().getOAuthAuthorizedInstance(CONSUMER_KEY, CONSUMER_SECRET);
			// OAuth認証コールバック
			if (path.equals("/oauth_recieve")) {
				RequestToken requestToken = (RequestToken) memcacheService.get("oauth_request_token");
				if (requestToken == null) {
					response.setContentType("text/plain");
					response.setCharacterEncoding("UTF-8");
					response.getWriter().println("多分timeout");
					return;
				}
				AccessToken accessToken = twitter.getOAuthAccessToken(requestToken);
				Entity entity = new Entity("twitter", "oauth_access_token");
				entity.setProperty("token", accessToken.getToken());
				entity.setProperty("tokenSecret", accessToken.getTokenSecret());
				dataService.put(entity);
				response.setContentType("text/plain");
				response.setCharacterEncoding("UTF-8");
				response.getWriter().println("OAuth 認証を登録しました。");
				return;
			}
			
			// OAuth認証
			AccessToken accessToken = null;
			try {
				Entity entity = dataService.get(KeyFactory.createKey("twitter", "oauth_access_token"));
				accessToken = new AccessToken((String) entity.getProperty("token"), (String) entity.getProperty("tokenSecret"));
			} catch (EntityNotFoundException e) {}
			if (accessToken == null) {
				// 認証済みでない場合は、リクエストトークンを発行してキャッシュに保持・リダイレクト
				RequestToken requestToken = twitter.getOAuthRequestToken();
				memcacheService.put("oauth_request_token", requestToken, Expiration.byDeltaSeconds(600)); // 10分有効
				response.sendRedirect(requestToken.getAuthorizationURL());  
				return;
			}
			twitter.setOAuthAccessToken(accessToken);
			
			// 以下アプリ
			StringBuilder text = new StringBuilder();
			if (path.equals("/clear")) {
				Entity entity = new Entity("twitter", "timeline");
				entity.setProperty("lastId", -1);
				dataService.put(entity);
				entity = new Entity("twitter", "reply");
				entity.setProperty("lastId", -1);
				dataService.put(entity);
				entity = new Entity("twitter", "direct");
				entity.setProperty("lastId", -1);
				dataService.put(entity);
			}
		
			if (path.equals("/tl")) {
				// 保存された最新のIDを取得
				long lastId = -1;
				try {
					Object id = dataService.get(KeyFactory.createKey("twitter", "timeline")).getProperty("lastId");
					lastId = (id == null ? lastId : ((Long) id).longValue());
				} catch (EntityNotFoundException e) {}
				ResponseList<Status> timeline = twitter.getHomeTimeline();
				for (Status status : timeline) {
					if (lastId == status.getId()) { break; }
					text.append(status.getUser().getScreenName() + ":" + status.getText() + "\n");
				}
				if (!"true".equals(request.getParameter("not_save"))) {
					// 最新のIDを保存
					long theLastId = timeline.get(0).getId();
					Entity entity = new Entity("twitter", "timeline");
					entity.setProperty("lastId", theLastId);
					dataService.put(entity);
				}
			} else if (path.equals("/reply")) {
				// 保存された最新のIDを取得
				long lastId = -1;
				try {
					Object id = dataService.get(KeyFactory.createKey("twitter", "reply")).getProperty("lastId");
					lastId = (id == null ? lastId : ((Long) id).longValue());
				} catch (EntityNotFoundException e) {}
				
				ResponseList<Status> mentions = twitter.getMentions(new Paging(1, 8));
				for (Status status : mentions) {
					if (lastId == status.getId()) {
						break;
					}
					text.append(status.getUser().getScreenName() + ":" + status.getText() + "\n");
				}
				
				if (!"true".equals(request.getParameter("not_save"))) {
					// 最新のIDを保存
					long theLastId = mentions.get(0).getId();
					Entity entity = new Entity("twitter", "reply");
					entity.setProperty("lastId", theLastId);
					dataService.put(entity);
				}
			} else if (path.equals("/direct")) {
				// 保存された最新のIDを取得
				long lastId = -1;
				try {
					Object id = dataService.get(KeyFactory.createKey("twitter", "direct")).getProperty("lastId");
					lastId = (id == null ? lastId : ((Long) id).longValue());
				} catch (EntityNotFoundException e) {}
				
				ResponseList<DirectMessage> messages = twitter.getDirectMessages(new Paging(1, 8));
				for (DirectMessage message : messages) {
					if (lastId == message.getId()) {
						break;
					}
					text.append(message.getSender().getScreenName() + ":" + message.getText() + "\n");
				}
				
				if (!"true".equals(request.getParameter("not_save"))) {
					// 最新のIDを保存
					long theLastId = messages.get(0).getId();
					Entity entity = new Entity("twitter", "direct");
					entity.setProperty("lastId", theLastId);
					dataService.put(entity);
				}
			}
			if ("true".equals(request.getParameter("ispc"))) {
				response.setContentType("text/plain");
				response.setCharacterEncoding("UTF-8");
				response.getWriter().println(text.toString());
			} else {
				if (text.length() > 0) {
					Message message = new Message();
					message.setSubject("Twitter Timeline");
					message.setTo(YOUR_MAIL);
					message.setSender(SYSTEM_ADMIN_MAIL);
					message.setTextBody(text.toString());
					MailServiceFactory.getMailService().send(message);
				}
			}
		} catch (TwitterException e) {
			response.setContentType("text/plain");
			response.setCharacterEncoding("UTF-8");
			e.printStackTrace(response.getWriter());
		}
	}
}

使い方
・自分用のApplicationIDを取得
・事前に http://twitter.com/oauth_clients/new でアプリを登録
  ・この際、戻りURLは、"http://[取得したAppID].appspot.com/oauth_recieve" を設定してください。
   あとは環境に合わせて。
・出力された、CONSUMER_KEYと、CONSUMER_SECRETをソースにコピペ
・メール送信元アドレス(開発者メールアドレス)と、送信先アドレス(携帯アドレス)をソースに記入
・デプロイ

PCでも携帯でもいいので、/以外にアクセス/hogeとかでも可
あとは画面に従えば認証が行われます。