オープンソース・ソフトウェアの開発とダウンロード

Subversion リポジトリの参照

Contents of /tags/charactorbot_1_00_000/charactorbot/src/org/soichiro/charactorbot/server/TwitterBot.java

Parent Directory Parent Directory | Revision Log Revision Log


Revision 25 - (show annotations) (download)
Sat Apr 17 01:09:22 2010 UTC (14 years, 1 month ago) by sifue
File MIME type: text/plain
File size: 16446 byte(s)
正式版リリースに伴なうバージョン作成
1 package org.soichiro.charactorbot.server;
2
3 import java.util.ArrayList;
4 import java.util.Calendar;
5 import java.util.Collection;
6 import java.util.Date;
7 import java.util.HashMap;
8 import java.util.HashSet;
9 import java.util.List;
10 import java.util.Map;
11 import java.util.Random;
12 import java.util.Set;
13 import java.util.TimeZone;
14 import java.util.logging.Level;
15 import java.util.logging.Logger;
16 import java.util.regex.Matcher;
17 import java.util.regex.Pattern;
18
19 import javax.jdo.PersistenceManager;
20
21 import org.apache.commons.collections.CollectionUtils;
22 import org.apache.commons.collections.Predicate;
23 import org.soichiro.charactorbot.client.PostTypeEnum;
24
25 import twitter4j.Status;
26 import twitter4j.Twitter;
27 import twitter4j.TwitterException;
28 import twitter4j.TwitterFactory;
29 import twitter4j.User;
30 import twitter4j.http.AccessToken;
31
32 import com.google.appengine.api.datastore.Key;
33 import com.google.appengine.api.datastore.KeyFactory;
34
35 public class TwitterBot {
36
37 /** Singleton Instance */
38 private static TwitterBot bot;
39
40 /** Logger */
41 private static final Logger log = Logger.getLogger(TwitterBot.class.getName());
42
43 /** Random */
44 private static Random RANDOM = new Random();
45
46 /**
47 * Map for friends timeline cache. this map must be cleaned after run().
48 * Key: twitter account key string
49 * Value: List of status
50 * */
51 private Map<String, List<Status>> mapFriendsTimeline = new HashMap<String, List<Status>>();
52
53 /**
54 * Map for set of replyed status. this map must be cleaned after run().
55 * Key: twitter account key string
56 * Value: Map of already replyed statu.s
57 * */
58 private Map<String, Set<Status>> mapSetRepliedStatus = new HashMap<String, Set<Status>>();
59
60 /**
61 * Lacal timezone calender. this parameter must be cleand per twitter account.
62 */
63 private Calendar localCalender = null;
64
65 /** Time Tag Pattern */
66 private static final Pattern timeTagPattern = Pattern.compile(".*#(hour|week)_[0-9]{1,2}#.*");
67
68 /**
69 * Constructor
70 */
71 private TwitterBot() {
72 log.setLevel(Level.ALL);
73 }
74
75 /**
76 * Singleton Instance Getter
77 * @return Singleton TwitterBot Instance
78 */
79 static public TwitterBot getInstance(){
80 if(bot == null){
81 bot = new TwitterBot();
82 }
83 return bot;
84 }
85
86 /**
87 * Post to Twitter
88 * @param keyTwitterAccount
89 * @param now
90 */
91 public void run(String keyTwitterAccount, Date now)
92 {
93 PersistenceManager pm = PMF.get().getPersistenceManager();
94 try {
95 Key key = KeyFactory.stringToKey(keyTwitterAccount);
96 TwitterAccount account = pm.getObjectById(TwitterAccount.class, key);
97 List<PostType> listPostTypes = account.getListPostType();
98
99 AccessToken accessToken = new AccessToken(
100 account.getToken(),
101 account.getSecret());
102
103 Twitter twitter = new TwitterFactory().getOAuthAuthorizedInstance(
104 account.getConsumerKey(),
105 account.getConsumerSecret(),
106 accessToken);
107
108 for (PostType postType : listPostTypes) {
109 PostTypeEnum postTypeEnum = PostTypeEnum.valueOf(postType.getPostTypeName());
110
111 switch (postTypeEnum) {
112 case NOMAL_POST:
113 nomalPost(postType, twitter, pm, now);
114 break;
115 case REPLY_FOR_ME:
116 reply(postType, twitter, pm, now, true);
117 break;
118 case REPLY:
119 reply(postType, twitter, pm, now, false);
120 break;
121 case WELCOME_POST:
122 welcomePost(postType, twitter, pm, now);
123 break;
124 default:
125 break;
126 }
127 }
128
129 }catch (Exception e) {
130 log.log(Level.WARNING, "An exception was thrown at run()", e);
131 }
132 finally {
133 if(mapFriendsTimeline.containsKey(keyTwitterAccount))
134 mapFriendsTimeline.remove(keyTwitterAccount);
135 if(mapSetRepliedStatus.containsKey(keyTwitterAccount))
136 mapSetRepliedStatus.remove(keyTwitterAccount);
137 localCalender = null;
138 pm.close();
139 }
140 }
141
142 private Predicate predicateHourTag = null;
143 private Predicate predicateWeekTag = null;
144 private Predicate predicateNoneTag = null;
145
146 /**
147 * perform nomal posting.
148 * @param postType
149 * @param twitter
150 * @param pm
151 * @param now
152 */
153 @SuppressWarnings("unchecked")
154 private void nomalPost(final PostType postType, Twitter twitter, PersistenceManager pm, Date now) {
155 // Check interval
156 boolean isPermittedPost = isPermittedToPost(postType, now);
157 if(!isPermittedPost) return;
158
159 // Sleep Function
160 if(postType.getIsUseSleep())
161 {
162 boolean isSleeping = isSleeping(postType);
163 if(isSleeping) return;
164 }
165
166 // Check keyword list
167 List<Keyword> listKeyword = postType.getListKeyword();
168 if(listKeyword == null || listKeyword.size() < 1) {
169 return;
170 }
171
172 Keyword nullKeyword = listKeyword.get(0);
173 Collection<Post> listPost = nullKeyword.getListPost();
174 if(listPost == null || listPost.size() < 1) {
175 return;
176 }
177
178 // process #hour_xx# tag
179 if(predicateHourTag == null)
180 predicateHourTag = new Predicate() {
181 @Override
182 public boolean evaluate(Object object) {
183 Post post = (Post)object;
184 String tag = "#hour_"
185 + getLocalCalender(postType).get(Calendar.HOUR_OF_DAY)
186 + "#";
187 return post.getMessage().contains(tag);
188 }
189 };
190 Collection<Post> listContainHourTag = CollectionUtils.select(listPost, predicateHourTag);
191
192 // process #week_xx# tag
193 if(predicateWeekTag == null)
194 predicateWeekTag = new Predicate() {
195 @Override
196 public boolean evaluate(Object object) {
197 Post post = (Post)object;
198 String tag = "#week_"
199 + getLocalCalender(postType).get(Calendar.DAY_OF_WEEK)
200 + "#";
201 return post.getMessage().contains(tag);
202 }
203 };
204 Collection<Post> listContainWeekTag = CollectionUtils.select(listPost, predicateWeekTag);
205
206 // fix listPost
207 if(listContainHourTag.size() > 0
208 && listContainWeekTag.size() > 0){
209 listPost = CollectionUtils.union(listContainHourTag, listContainWeekTag);
210 }else if (listContainHourTag.size() > 0
211 && listContainWeekTag.size() == 0) {
212 listPost = listContainHourTag;
213 }else if (listContainHourTag.size() == 0
214 && listContainWeekTag.size() > 0) {
215 listPost = listContainWeekTag;
216 }else {
217 if(predicateNoneTag == null)
218 predicateNoneTag = new Predicate() {
219 @Override
220 public boolean evaluate(Object object) {
221 Post post = (Post)object;
222 String message = post.getMessage();
223 Matcher matcher = timeTagPattern.matcher(message);
224 return !matcher.matches();
225 }
226 };
227 Collection<Post> listContainNotTagContain = CollectionUtils.select(listPost, predicateNoneTag);
228 listPost = listContainNotTagContain;
229 }
230
231 int postIndex = RANDOM.nextInt(listPost.size());
232 Post post = listPost.toArray(new Post[0])[postIndex];
233 try
234 {
235 String message = post.getMessage();
236 message = removeTimeTag(message);
237 message = replaceTag(post, null, message);
238 if(message == null) return;
239
240 Status status = twitter.updateStatus(message);
241 log.info(String.format("Successfully message posted. [screen name:%1$s] [status:%2$s]",
242 status.getUser().getScreenName(), status.getText()));
243 } catch (TwitterException e) {
244 log.log(Level.WARNING, "An exception was thrown at nomalPost()", e);
245 }
246
247 try {
248 post.setCount(Integer.valueOf(post.getCount().intValue() + 1));
249 pm.makePersistent(post);
250 } catch (Exception ignore) {
251 log.log(Level.WARNING, "An exception was thrown at nomalPost()", ignore);
252 }
253 }
254
255 /**
256 * reply for @me post of my timeline.
257 * @param postType
258 * @param twitter
259 * @param pm
260 * @param now
261 */
262 private void reply(PostType postType, Twitter twitter,
263 PersistenceManager pm, Date now, boolean isForMe) {
264 // Check interval
265 boolean isPermittedPost = isPermittedToPost(postType, now);
266 if(!isPermittedPost) return;
267
268 // Sleep Function
269 if(postType.getIsUseSleep())
270 {
271 boolean isSleeping = isSleeping(postType);
272 if(isSleeping) return;
273 }
274
275 // Check keyword list
276 List<Keyword> listKeyword = postType.getListKeyword();
277 if(listKeyword == null || listKeyword.size() < 1) {
278 return;
279 }
280
281 String keyStringAccount = KeyFactory.keyToString(postType.getTwitterAccount().getKey());
282
283 // get timeline
284 List<Status> statuses =
285 mapFriendsTimeline.get(keyStringAccount);
286 if(statuses == null){
287 try {
288 statuses = twitter.getFriendsTimeline();
289 mapFriendsTimeline.put(keyStringAccount, statuses);
290 } catch (TwitterException e) {
291 log.log(Level.WARNING, "An exception was thrown at replyForMe()", e);
292 return;
293 }
294 }
295
296 // get set of replyed Status
297 Set<Status> setRepliedStatus = mapSetRepliedStatus.get(keyStringAccount);
298 if(setRepliedStatus == null){
299 setRepliedStatus = new HashSet<Status>();
300 mapSetRepliedStatus.put(keyStringAccount, setRepliedStatus);
301 }
302
303 s: for (Status status : statuses) {
304 // Except for me
305 if(status.getUser().getScreenName().equals(
306 postType.getTwitterAccount().getScreenName())) continue s;
307
308 // If post for me, finish.
309 String text = status.getText();
310 if(isForMe) {
311 if(!text.contains("@" + postType.getTwitterAccount().getScreenName())){
312 continue s;
313 }
314 }
315
316 // Pass posted status
317 if(setRepliedStatus.contains(status)) continue s;
318
319 // Check the time is between interval
320 long msec = now.getTime() - status.getCreatedAt().getTime();
321 if(!(0 <= msec
322 && msec <= (postType.getInterval().longValue() * 60000L))) continue s;
323
324 k: for (Keyword keyword : listKeyword) {
325 if(!keyword.getIsActivated().booleanValue()) continue k;
326
327 if(keyword.getIsRegex())
328 {
329 Pattern pattern = Pattern.compile(keyword.getKeyword());
330 Matcher matcher = pattern.matcher(text);
331 if(matcher.matches()){
332 replyPost(status, keyword.getListPost(), pm, twitter);
333 setRepliedStatus.add(status);
334 continue s;
335 }
336 } else {
337 if(text.contains(keyword.getKeyword())){
338 replyPost(status, keyword.getListPost(), pm, twitter);
339 setRepliedStatus.add(status);
340 continue s;
341 }
342 }
343 }
344 }
345 }
346
347 /**
348 * remove time tag (#hour_8#,#week_3#...)
349 * @param message
350 * @return
351 */
352 private String removeTimeTag(String message) {
353 message = message.replaceAll("#(hour|week)_[0-9]{1,2}#", "");
354 return message;
355 }
356
357 /**
358 * reply for timeline.
359 * @param status
360 * @param listPost
361 * @param pm
362 * @param twitter
363 */
364 private void replyPost(Status status, List<Post> listPost,
365 PersistenceManager pm, Twitter twitter) {
366
367 int postIndex = RANDOM.nextInt(listPost.size());
368 Post post = listPost.get(postIndex);
369 try
370 {
371 User user = status.getUser();
372 String message = post.getMessage();
373
374 message = replaceTag(post, user, message);
375 if(message == null) return;
376
377 message = "@" + status.getUser().getScreenName()
378 + " " + message;
379
380 Status postedStatus =
381 twitter.updateStatus(message, status.getId());
382
383 log.info(String.format("Successfully message posted. [screen name:%1$s] [status:%2$s]",
384 postedStatus.getUser().getScreenName(), postedStatus.getText()));
385 } catch (TwitterException e) {
386 log.log(Level.WARNING, "An exception was thrown at replyPostInternal()", e);
387 }
388
389 try {
390 post.setCount(Integer.valueOf(post.getCount().intValue() + 1));
391 pm.makePersistent(post);
392 } catch (Exception ignore) {
393 log.log(Level.WARNING, "An exception was thrown at replyPostInternal()", ignore);
394 }
395 }
396
397 /**
398 * replace tag of message.
399 * @param post not null
400 * @param user null is OK
401 * @param message not null
402 * @return
403 */
404 private String replaceTag(Post post, User user, String message) {
405
406 if(message.contains("#stop#"))
407 {
408 log.info(String.format("Successfully message stopped."));
409 return null;
410 }
411
412 if(user != null)
413 message = message.replaceAll("#user_name#", user.getName());
414
415 String tzId = post.getKeyword().getPostType().getTwitterAccount().getTimeZoneId();
416 TimeZone zone = TimeZone.getTimeZone(tzId);
417 Calendar localCal = Calendar.getInstance(zone);
418 if("Asia/Tokyo".equals(tzId)){
419 message = message.replaceAll("#time#",
420 localCal.get(Calendar.HOUR_OF_DAY) + "��"+ localCal.get(Calendar.MINUTE) +"��");
421 message = message.replaceAll("#date#",
422 (localCal.get(Calendar.MONTH) + 1) + "��" + localCal.get(Calendar.DAY_OF_MONTH) + "��");
423 } else {
424 message = message.replaceAll("#time#",
425 localCal.get(Calendar.HOUR_OF_DAY) + ":"+ localCal.get(Calendar.MINUTE));
426 message = message.replaceAll("#date#",
427 (localCal.get(Calendar.MONTH) + 1) + "/" + localCal.get(Calendar.DAY_OF_MONTH));
428 }
429 return message;
430 }
431
432 /**
433 * Follow new follower and post welcome message.
434 * @param postType
435 * @param twitter
436 * @param pm
437 * @param now
438 */
439 @SuppressWarnings("unchecked")
440 private void welcomePost(PostType postType, Twitter twitter,
441 PersistenceManager pm, Date now) {
442 // Check interval
443 boolean isPermittedPost = isPermittedToPost(postType, now);
444 if(!isPermittedPost) return;
445
446 // Sleep Function
447 if(postType.getIsUseSleep())
448 {
449 boolean isSleeping = isSleeping(postType);
450 if(isSleeping) return;
451 }
452
453 // Check keyword list
454 List<Keyword> listKeyword = postType.getListKeyword();
455 if(listKeyword == null || listKeyword.size() < 1) {
456 return;
457 }
458
459 Keyword nullKeyword = listKeyword.get(0);
460 List<Post> listPost = nullKeyword.getListPost();
461 if(listPost == null || listPost.size() < 1) {
462 return;
463 }
464
465 // Get new follower and friends
466 String screenName = postType.getTwitterAccount().getScreenName();
467 Collection<Integer> followerIDs = null;
468 Collection<Integer> friendsIDs = null;
469 try {
470 int[] aryfollowerIDs = twitter.getFollowersIDs(screenName).getIDs();
471 followerIDs = new ArrayList<Integer>();
472 for (int i : aryfollowerIDs) {
473 followerIDs.add(Integer.valueOf(i));
474 }
475
476 int[] aryfriendsIDs = twitter.getFriendsIDs(screenName).getIDs();
477 friendsIDs = new ArrayList<Integer>();
478 for (int i : aryfriendsIDs) {
479 friendsIDs.add(Integer.valueOf(i));
480 }
481 } catch (TwitterException e) {
482 log.log(Level.WARNING, "An exception was thrown at welcomePost()", e);
483 return;
484 }
485
486 Collection<Integer> newFollowerIDs = CollectionUtils.subtract(followerIDs, friendsIDs);
487 for (Integer id : newFollowerIDs) {
488
489 int postIndex = RANDOM.nextInt(listPost.size());
490 Post post = listPost.get(postIndex);
491 try
492 {
493 // create friendship
494 User friend = twitter.createFriendship(id.intValue());
495
496 String message = post.getMessage();
497
498 message = replaceTag(post, friend, message);
499 if(message == null) return;
500
501 Status status = twitter.updateStatus(
502 "@" + friend.getScreenName() + " " + message );
503 log.info(String.format("Successfully message posted. [screen name:%1$s] [status:%2$s]",
504 status.getUser().getScreenName(), status.getText()));
505 } catch (TwitterException e) {
506 log.log(Level.WARNING, "An exception was thrown at welcomePost()", e);
507 }
508
509 try {
510 post.setCount(Integer.valueOf(post.getCount().intValue() + 1));
511 pm.makePersistent(post);
512 } catch (Exception ignore) {
513 log.log(Level.WARNING, "An exception was thrown at welcomePost()", ignore);
514 }
515 }
516
517 }
518
519 /**
520 * get isPermittedToPost about interval.
521 * @param postType
522 * @param now
523 * @return
524 */
525 private boolean isPermittedToPost(PostType postType, Date now) {
526 int intervalMin = postType.getInterval().intValue();
527 Date registeredDate = postType.getUpdatedAt();
528
529 long diffmsec = now.getTime() - registeredDate.getTime();
530 long diffmin = diffmsec / 60000L;
531 boolean isPermittedToPost = diffmin % intervalMin == 0;
532 return isPermittedToPost;
533 }
534
535 /**
536 * get isSleeping.
537 * @param postType
538 * @return
539 */
540 private boolean isSleeping(PostType postType) {
541 TimeZone zone = TimeZone.getTimeZone(postType.getTwitterAccount().getTimeZoneId());
542 Calendar localCal = Calendar.getInstance(zone);
543 int hour24 = localCal.get(Calendar.HOUR_OF_DAY);
544 boolean isSleeping = (2 <= hour24 && hour24 <= 6);
545 return isSleeping;
546 }
547
548 /**
549 * get Local Calender.
550 * @param postType
551 * @return
552 */
553 private Calendar getLocalCalender(PostType postType) {
554 if(localCalender == null){
555 TwitterAccount account = postType.getTwitterAccount();
556 String timeZoneId = account.getTimeZoneId();
557
558 if(timeZoneId == null)
559 {
560 log.warning(String.format("timeZoneId is null. twitter screen name : %s", account.getScreenName()));
561 throw new NullPointerException("timeZoneId must not be null!");
562 }
563
564 TimeZone zone = TimeZone.getTimeZone(timeZoneId);
565 localCalender = Calendar.getInstance(zone);
566 }
567 return localCalender;
568 }
569 }

Properties

Name Value
svn:mime-type text/plain

Back to OSDN">Back to OSDN
ViewVC Help
Powered by ViewVC 1.1.26