package com.appspot.piment.jobs;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import net.arnx.jsonic.JSON;
import weibo4j.Comment;
import weibo4j.Status;
import com.appspot.piment.Constants;
import com.appspot.piment.api.sina.SinaWeiboApi;
import com.appspot.piment.api.tqq.TqqWeiboApi;
import com.appspot.piment.api.tqq.model.Response;
import com.appspot.piment.dao.AuthTokenDao;
import com.appspot.piment.dao.CommentMapDao;
import com.appspot.piment.dao.UserMapDao;
import com.appspot.piment.dao.WeiboMapDao;
import com.appspot.piment.model.ApiLimitedException;
import com.appspot.piment.model.AuthToken;
import com.appspot.piment.model.CommentMap;
import com.appspot.piment.model.UserMap;
import com.appspot.piment.model.WeiboMap;
import com.appspot.piment.model.WeiboSource;
import com.appspot.piment.model.WeiboStatus;
import com.appspot.piment.shared.StringUtils;
import com.appspot.piment.util.MailUtils;
public class SinaMessageSync {
private static final Logger log = Logger.getLogger(Constants.FQCN + SinaMessageSync.class.getName());
private Map<String, String> configMap = null;
private WeiboMapDao weiboMapDao = null;
private CommentMapDao commentMapDao = null;
private UserMapDao userMapDao = null;
private AuthTokenDao authTokenDao = null;
private TqqWeiboApi tqqRobotWeiboApi = null;
private TqqWeiboApi tqqTempWeiboApi = null;
private TqqWeiboApi tqqWeiboApi = null;
private SinaWeiboApi sinaWeiboApi = null;
public SinaMessageSync(Map<String, String> configMap) {
this.configMap = configMap;
this.tqqRobotWeiboApi = new TqqWeiboApi(this.configMap);
this.tqqTempWeiboApi = new TqqWeiboApi(this.configMap);
this.tqqWeiboApi = new TqqWeiboApi(this.configMap);
this.sinaWeiboApi = new SinaWeiboApi(this.configMap);
this.weiboMapDao = new WeiboMapDao();
this.commentMapDao = new CommentMapDao();
this.userMapDao = new UserMapDao();
this.authTokenDao = new AuthTokenDao();
}
public void setTqqRobotToken(AuthToken authToken) {
this.tqqRobotWeiboApi.setAuthToken(authToken);
}
public void setTqqToken(AuthToken authToken) {
this.tqqWeiboApi.setAuthToken(authToken);
}
public void setSinaToken(AuthToken authToken) {
this.sinaWeiboApi.setAuthToken(authToken);
}
public List<WeiboMap> retrySyncUserMessage(UserMap user) {
// リトライ処理を行う
// FIXME
List<WeiboMap> retryWeiboMaps = this.weiboMapDao.getFieldItem(user.getId(), WeiboSource.Sina);
if (retryWeiboMaps.size() > 0) {
Long startId = retryWeiboMaps.get(0).getSinaWeiboId() - 1;
Long endId = retryWeiboMaps.get(retryWeiboMaps.size() - 1).getSinaWeiboId();
// リトライの対象メッセージを取得
List<Status> oldUserMessages = sinaWeiboApi.getUserTimeline(startId, endId);
Map<Long, Status> oldUserMessagesMap = new HashMap<Long, Status>();
for (Status oldUserMessage : oldUserMessages) {
oldUserMessagesMap.put(oldUserMessage.getId(), oldUserMessage);
}
try {
for (WeiboMap retryWeiboMap : retryWeiboMaps) {
if (oldUserMessagesMap.containsKey(retryWeiboMap.getSinaWeiboId())) {
// RETRY
syncSinaUserMessage(user, oldUserMessagesMap.get(retryWeiboMap.getSinaWeiboId()), retryWeiboMap);
} else {
// ABORT
retryWeiboMap.setStatus(WeiboStatus.ABORT);
// 同期化履歴レコードを保存する
retryWeiboMap = weiboMapDao.save(retryWeiboMap);
}
}
} catch (ApiLimitedException e) {
e.printStackTrace();
}
}
return null;
}
public List<WeiboMap> syncUserMessage(UserMap user) {
// 前回同期化された最後の履歴レコードを取り出す
WeiboMap lastestCreateWeiboMap = weiboMapDao.getNewestItem(user.getId(), WeiboSource.Sina);
// sinaから前回の同期化以降対象ユーザが発表した新メッセージを取得する
List<Status> newUserMessages = sinaWeiboApi.getUserTimeline(lastestCreateWeiboMap != null ? lastestCreateWeiboMap.getSinaWeiboId() : null, null);
log.info("Sina message --> 同期化件数:" + newUserMessages.size());
// メッセージ単位で同期化処理を行う
Status status = null;
try {
for (int i = newUserMessages.size() - 1; i >= 0; i--) {
status = newUserMessages.get(i);
if (this.weiboMapDao.getBySinaWeiboId(status.getId(), user.getId()) == null) {
syncSinaUserMessage(user, status, new WeiboMap());
} else {
log.info("Sina message --> [" + status.getId() + "] 同期化済みでスキップする。");
}
}
} catch (ApiLimitedException e) {
e.printStackTrace();
}
return null;
}
public int retrySyncUserComment(UserMap user) {
// リトライ処理を行う
List<CommentMap> retryCommentMaps = this.commentMapDao.getFieldItem(user.getId());
int count = 0;
if (retryCommentMaps.size() > 0) {
Long startId = retryCommentMaps.get(0).getSinaCommentId() - 1;
Long endId = retryCommentMaps.get(retryCommentMaps.size() - 1).getSinaCommentId();
// リトライの対象コメントを取得
List<Comment> oldUserComments = sinaWeiboApi.getCommentTimeline(startId, endId);
Map<Long, Comment> oldUserCommentsMap = new HashMap<Long, Comment>();
for (Comment oldUserComment : oldUserComments) {
oldUserCommentsMap.put(oldUserComment.getId(), oldUserComment);
}
try {
for (CommentMap retryCommentMap : retryCommentMaps) {
if (oldUserCommentsMap.containsKey(retryCommentMap.getSinaCommentId())) {
// RETRY
boolean result = syncSinaUserComment(user, oldUserCommentsMap.get(retryCommentMap.getSinaCommentId()), retryCommentMap);
if (result)
count++;
} else {
// ABORT
retryCommentMap.setStatus(WeiboStatus.ABORT);
// 同期化履歴レコードを保存する
retryCommentMap = commentMapDao.save(retryCommentMap);
}
}
} catch (ApiLimitedException e) {
e.printStackTrace();
}
}
return count;
}
public int syncUserComment(UserMap user) {
int count = 0;
// 前回同期化された最後のコメント履歴レコードを取り出す
CommentMap lastestCreateCommentMap = commentMapDao.getNewestItem(user.getId());
// sinaから前回の同期化以降の新コメントを取得する
List<Comment> newComments = sinaWeiboApi.getCommentTimeline(lastestCreateCommentMap != null ? lastestCreateCommentMap.getSinaCommentId() : null, null);
log.info("Sina comment --> 同期化件数:" + newComments.size());
// メッセージ単位で同期化処理を行う
Comment comment = null;
try {
for (int i = newComments.size() - 1; i >= 0; i--) { // FOR-101
comment = newComments.get(i);
if (this.commentMapDao.getBySinaCommentId(comment.getId(), user.getId()) == null) {
boolean result = syncSinaUserComment(user, comment, new CommentMap());
if (result)
count++;
} else {
log.info("Sina comment --> [" + comment.getId() + "] 同期化済みでスキップする。");
}
}
} catch (ApiLimitedException e) {
e.printStackTrace();
// DO NOTHING
}
return count;
}
private boolean syncSinaUserComment(UserMap user, Comment comment, CommentMap commentMap) throws ApiLimitedException {
boolean result = false;
log.info("Sina comment --> [" + comment.getId() + "]同期化中...");
commentMap.setSinaCommentId(comment.getId());
commentMap.setTqqCommentId(null);
commentMap.setUserMapId(user.getId());
commentMap.setSource(WeiboSource.Sina);
commentMap.setStatus(WeiboStatus.UNKNOW);
commentMap.setWeiboId(comment.getStatus().getId());
try {
// 同期化対象判定
if (user.isNeededMessageVirify()) {
// 同期化不要のキーワードが合ったら処理をスキップする
if (comment.getText().contains(this.configMap.get("app.piment.unsync.keyword"))) {
commentMap.setStatus(WeiboStatus.SKIPPED);
log.info("Sina comment --> [" + comment.getId() + "]同期化対象外とする");
return false;
}
}
Long sinaWeiboId = comment.getStatus().getId();
WeiboMap weiboMap = this.weiboMapDao.getBySinaWeiboId(sinaWeiboId, user.getId());
if (weiboMap != null && StringUtils.isNotBlank(String.valueOf(weiboMap.getTqqWeiboId()))) {
Response response = null;
Throwable throwable = null;
TqqWeiboApi tqqApi = null;
String commentUserId = String.valueOf(comment.getUser().getId());
String tqqWeiboId = String.valueOf(weiboMap.getTqqWeiboId());
String commentMsg = comment.getText();
if (commentUserId.equals(this.tqqWeiboApi.getUsetId())) {
tqqApi = this.tqqWeiboApi;
} else {
UserMap userMap = this.userMapDao.getUserMap(commentUserId);
if (userMap != null && StringUtils.isNotBlank(userMap.getTqqUserId())) {
AuthToken tempAuthToken = this.authTokenDao.getByUserId(userMap.getTqqUserId(), WeiboSource.Tqq);
this.tqqTempWeiboApi.setAuthToken(tempAuthToken);
tqqApi = this.tqqTempWeiboApi;
} else {
tqqApi = this.tqqRobotWeiboApi;
commentMsg = "Sina @" + comment.getUser().getScreenName() + "//" + commentMsg;
}
}
try {
response = tqqApi.sendComment(tqqWeiboId, commentMsg, null);
} catch (Exception e) {
throwable = e;
}
// 処理成功ならば、同期化レコードをデータストアへ保存する
if (response != null && response.isOK()) {
// 同期成功情報を履歴レコードに反映
commentMap.setStatus(WeiboStatus.SUCCESSED);
commentMap.setTqqCommentId(Long.valueOf(response.getData().getId()));
log.info("Sina comment --> コメント同期化成功!!!");
log.info("Sina comment --> コメントメッセージID:" + comment.getId());
result = true;
} else {
log.warning("Sina comment --> コメント同期化失敗!!!");
log.warning("Sina comment --> メッセージID:" + comment.getId());
String msg001 = "Sina comment --> [" + commentMap.getSinaCommentId() + "]をTQQへの送信が失敗しました。";
log.severe(msg001);
String errorDetail = throwable != null ? JSON.encode(throwable, true) : response.toString();
log.severe(errorDetail);
// 13 重复发表
// 10 发表太快,被频率限制
if ("10".equals(response.getErrcode())) {
System.out.println("DOT NOT SEND EMAIL!");
commentMap.setStatus(WeiboStatus.FAILED);
throw new ApiLimitedException("发表太快,被频率限制:" + errorDetail);
} else if ("13".equals(response.getErrcode())) {
System.out.println("DOT NOT SEND EMAIL!");
} else {
MailUtils.sendErrorReport(msg001 + "\n\n処理コメント:" + comment.toString() + "\n\nTQQからのレスポンス:\n" + errorDetail + "\n\n");
}
if (commentMap.getId() != null) {
commentMap.setRetryCount(commentMap.getRetryCount() + 1);
// 失敗フラグを設定
if (commentMap.getRetryCount() >= Integer.valueOf(this.configMap.get("app.sync.message.max.retry"))) {
commentMap.setStatus(WeiboStatus.ABORT);
} else {
commentMap.setStatus(WeiboStatus.FAILED);
}
} else {
commentMap.setStatus(WeiboStatus.FAILED);
}
}
} else {
commentMap.setStatus(WeiboStatus.SKIPPED);
log.info("Sina comment --> [" + comment.getId() + "] 対応するメッセージ履歴がないため、同期化対象外とする");
}
} catch (ApiLimitedException e) {
// DO NOTHING
throw e;
} catch (Exception e) {
String msg001 = "Sina comment --> 同期化失敗しました、コメントメッセージID:" + comment.getId();
log.severe(msg001);
log.severe(JSON.encode(e, true));
MailUtils.sendErrorReport(msg001 + "\n\n処理メッセージ:" + comment.toString() + "\n\n例外:\n" + JSON.encode(e, true));
// 例外が起きても次ぎのメッセージの同期化を行う
} finally {
// 同期化履歴レコードを保存する
commentMap = commentMapDao.save(commentMap);
log.info("Sina comment --> 同期化履歴レコードID:" + commentMap.getId());
}
return result;
}
private void syncSinaUserMessage(UserMap userMap, Status status, WeiboMap weiboMap) throws ApiLimitedException {
try {
log.info("Sina Message --> [" + status.getId() + "]同期化中...");
// テキストメッセージなら
if (StringUtils.isNotBlank(status.getText())) {
// 同期化対象判定
if (userMap.isNeededMessageVirify()) {
// 同期化不要のキーワードが合ったら処理をスキップする
if (status.getText().contains(this.configMap.get("app.piment.unsync.keyword"))) {
weiboMap.setSinaWeiboId(status.getId());
weiboMap.setTqqWeiboId(null);
weiboMap.setUserMapId(userMap.getId());
weiboMap.setSource(WeiboSource.Sina);
weiboMap.setStatus(WeiboStatus.SKIPPED);
log.info("Sina Message --> [" + status.getId() + "]同期化対象外とする");
return;
}
}
// 同期化履歴レコードの初期化
weiboMap.setSinaWeiboId(status.getId());
weiboMap.setTqqWeiboId(null);
weiboMap.setUserMapId(userMap.getId());
weiboMap.setSource(WeiboSource.Sina);
weiboMap.setStatus(WeiboStatus.UNKNOW);
// 同じメッセージをtqqへ発表する
Response response = null;
Response middleResponse = null;
Throwable throwable = null;
String originalMsg = null;
try {
// 转发微博的处理
if (status.isRetweet()) {
String retweetId = null;
Status retweetedStatus = status.getRetweeted_status();
log.info("Sina Message --> Retweet [" + retweetedStatus.getId() + "]");
WeiboMap processedWeibo = this.weiboMapDao.getBySinaWeiboId(retweetedStatus.getId(), userMap.getId());
if (processedWeibo != null) {
retweetId = String.valueOf(processedWeibo.getTqqWeiboId());
} else {
originalMsg = sinaWeiboApi.getOriginalMsg(retweetedStatus.getText().trim());
StringBuilder retweetMsg = new StringBuilder();
retweetMsg.append("Sina@").append(retweetedStatus.getUser().getName()).append("//");
retweetMsg.append(originalMsg);
// 長調整
if(retweetMsg.length() > 140){
String sinaURL = "... " + SinaWeiboApi.getStatusPageURL(retweetedStatus.getUser().getId(), retweetedStatus.getId());
retweetMsg = retweetMsg.delete((140 - sinaURL.length()), retweetMsg.length());
retweetMsg.append(sinaURL);
}
try {
middleResponse = tqqRobotWeiboApi.sendMessage(originalMsg, retweetMsg.toString(), retweetedStatus.getBmiddle_pic(), null);
} catch (Exception e) {
middleResponse = new Response();
middleResponse.setMsg(e.getMessage());
log.warning("Retweet --> 例外!!!" + e.getMessage());
}
log.info("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~X");
if (middleResponse != null && middleResponse.isOK()) {
log.info("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Y");
log.info("Sina Message --> Retweet Successed!!!");
// データストアへ保存する
// 同期化履歴レコードの初期化
WeiboMap retweetWeiboMap = new WeiboMap();
retweetWeiboMap.setSinaWeiboId(retweetedStatus.getId());
retweetWeiboMap.setTqqWeiboId(Long.valueOf(middleResponse.getData().getId()));
retweetWeiboMap.setUserMapId(null);
retweetWeiboMap.setSource(WeiboSource.Sina);
retweetWeiboMap.setStatus(WeiboStatus.SUCCESSED);
weiboMapDao.save(retweetWeiboMap);
retweetId = middleResponse.getData().getId();
}
}
log.info("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Q");
if (retweetId != null) {
log.info("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Z");
originalMsg = sinaWeiboApi.getOriginalMsg(status.getText().trim());
response = tqqWeiboApi.retweetMessage(retweetId, originalMsg, status.getBmiddle_pic(), null);
} else {
log.info("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~H");
response = middleResponse;
response.setApplicationMsg("Sina Message --> Retweet [" + retweetedStatus.getId() + "] Failed!!!!");
}
} else { // 转发微博的处理 - END
// 普通微博的处理
originalMsg = sinaWeiboApi.getOriginalMsg(status.getText().trim());
response = tqqWeiboApi.sendMessage(status.getText().trim(), status.getBmiddle_pic(), null);
}
} catch (Exception e) {
throwable = e;
}
// 処理成功ならば、同期化レコードをデータストアへ保存する
if (response != null && response.isOK()) {
// 同期成功情報を履歴レコードに反映
weiboMap.setStatus(WeiboStatus.SUCCESSED);
weiboMap.setTqqWeiboId(Long.valueOf(response.getData().getId()));
log.info("Sina Message --> 同期化成功!!!");
log.info("Sina Message --> メッセージID:" + status.getId());
} else {
log.warning("Sina Message --> 同期化失敗!!!");
log.warning("Sina Message --> メッセージID:" + status.getId());
String msg001 = "Sina Message --> [" + status.getId() + "]メッセージをTQQへの送信が失敗しました。";
log.severe(msg001);
log.info("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~A");
String errorDetail = throwable != null ? JSON.encode(throwable, true) : response.toString();
log.severe(errorDetail);
// 13 重复发表
// 10 发表太快,被频率限制
if ("10".equals(response.getErrcode())) {
System.out.println("DOT NOT SEND EMAIL!");
weiboMap.setStatus(WeiboStatus.FAILED);
throw new ApiLimitedException("发表太快,被频率限制:" + errorDetail);
} else if ("13".equals(response.getErrcode())) {
System.out.println("DOT NOT SEND EMAIL!");
} else {
log.info("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~B");
MailUtils.sendErrorReport(msg001 + "\n\n処理メッセージ:" + status.toString() + "\n\nTQQからのレスポンス:\n" + errorDetail + "\n\n");
}
log.info("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~C");
if (weiboMap.getId() != null) {
weiboMap.setRetryCount(weiboMap.getRetryCount() + 1);
// 失敗フラグを設定
if (weiboMap.getRetryCount() >= Integer.valueOf(this.configMap.get("app.sync.message.max.retry"))) {
weiboMap.setStatus(WeiboStatus.ABORT);
} else {
weiboMap.setStatus(WeiboStatus.FAILED);
}
} else {
log.info("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~D");
weiboMap.setStatus(WeiboStatus.FAILED);
}
}
}
} catch (ApiLimitedException e) {
log.info("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~E");
// DO NOTHING
throw e;
} catch (Exception e) {
log.info("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~F");
String msg001 = "Sina Message --> 同期化失敗しました、メッセージID:" + status.getId();
log.severe(msg001);
log.severe(JSON.encode(e, true));
MailUtils.sendErrorReport(msg001 + "\n\n処理メッセージ:" + status.toString() + "\n\n例外:\n" + JSON.encode(e, true));
// 例外が起きても次ぎのメッセージの同期化を行う
} finally {
log.info("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~G");
// 同期化履歴レコードを保存する
weiboMap = weiboMapDao.save(weiboMap);
log.info("Sina Message --> 同期化履歴レコードID:" + weiboMap.getId());
}
}
}