Twitter4Jで自動RT・自動ツイートするbotを作った覚え書き

こんにちは、[しゅがーはぁと応援bot](https://twitter.com/sugarheart26bot)の技術担当の人です。
"しゅがーはぁと"というのはアイドルマスターシンデレラガールズに登場するキャラクター、"佐藤心"の愛称です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621290/e212a22b-6ca4-861e-4e86-2e0553bf84a3.png)

しゅがーはぁと応援botは、その佐藤心を応援するための非公式botです。

Twitter4Jで自動RTと自動ツイートをするプログラムを組んだので、他のPの参考になればor後の自分が作るときの参考にすべく、記事を投稿します。

#作るもの

botを運営している知人から、
「『#佐藤心』『しゅがは』のワードが入ったツイートを自動でRTしてほしい」
「8:10ちょうどに画像ツイートしてほしい」
と頼まれました。

前者は[RTbot](https://rtbot.ne.je/)で、後者は[Botbird](https://botbird.metabirds.net/)を使えば簡単に実現できるのですが、
・RTbotがなんかうまく動かない
・ただの検索・RT以外の機能が欲しい（ブラックリストなど）
・Botbirdにいちいちツイートを登録するのが面倒
・画像ツイートした時に変なURL付くのが嫌
・複数のwebサービスを使うのが煩雑で嫌
といった理由により、botを自作することになりました。~~面倒だけどしゃあない~~

#botの作り方

大雑把な流れとしては、APIを登録する→コードを書く、という流れです。

#APIを登録する
APIの登録については、[ここ](https://qiita.com/kzthrk/items/84acb969dc0b23aeae58)とか[ここ](https://digitalnavi.net/internet/3072/)を見るかググるかしてください。
英文書かされるけど「しゅがはをシンデレラガールにするためにbotを作って↑に書いたようなことやりたいです」みたいなことを頑張って書きました。
高校レベルの英語でもなんとかなったので、あまり身構えずに。最悪google翻訳かなんかで頑張ればいいと思います。

今のために文章スクショ撮っておけばよかった……

#botを作る

私は仕事でJavaをよく扱っているので、JavaからTwitter APIを扱える[Twitter4J](http://twitter4j.org/ja/index.html)を用いて作成することにしました。

![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621290/1c6114e3-369b-4874-8a69-3a54f62d3724.png)

使い方はトップページに書いてますし、簡単な操作は全て[コードサンプル](http://twitter4j.org/ja/code-examples.html)に書いているので、そちらを見てください。310年前くらいからあるライブラリなので、ググれば情報はいくらでも出てきますし。

#ツイートを検索・RTする

以下のようなコードでツイートの検索結果を取得できます。

```java:SearchTweet.java
Twitter twitter = TwitterFactory.getSingleton();
Query query = new Query(searchWord);
query.setCount(count);
QueryResult result = null;
try {
	result = twitter.search(query);
//	System.out.println("ツイートを検索しました。");
} catch (TwitterException e) {
	e.printStackTrace();
	System.err.println("ツイートの検索に失敗しました。");
}
```

Query#setCount(int)でツイートの検索件数を変えることができます。上限は100です。
通常は100件が上限となりますが、ページ指定してあげると最大1500件取得できるらしいです。[ここ](http://www.mwsoft.jp/programming/java/twitter4j.html)とかに書いてます。
しゅがは応援botではそこまで遡って検索しなくていいかと思ったので、特に触れてないです。

また、QueryResultから以下のようなコードでRTができます。（ついでに自動いいねもやってます）

```java:RTTweet.java
Twitter twitter = TwitterFactory.getSingleton();
for (Status status : result.getTweets()) {
	long tweetId = status.getId();

//	if (!isAllowed(status)) {
//		continue;
//	}

	try {
		twitter.createFavorite(tweetId);
	} catch (TwitterException e) {
	//	e.printStackTrace();
	}

	try {
		twitter.retweetStatus(tweetId);
		System.out.println("以下のツイートをRTしました");
		System.out.println("tweetID:" + tweetId);
		System.out.println(status.getUser().getName() + ":" + status.getText());
		return true;
	} catch (TwitterException e) {
	//	e.printStackTrace();
	}

	//TwitterAPIに怒られないよう1秒待つ
	Thread.sleep(1000);
}
```

コンソールにはこんな感じで表示されます。

```
以下のツイートをRTしました
tweetID:1065539453798637568
すしこ?illustrator?:デレステしゅがは！（過去絵）
#デレステ　#佐藤心 https://t.co/EUdJBZQ0YQ
```

ちなみに既にRT・いいねしたツイートをRT・いいねしようとすると例外が発生します。自動で回していると頻発するので握りつぶしてます。
Status#isRetweetedByMe()でRT済かどうか見ることができるみたいですが、ツイート検索結果からStatus持ってきた場合、うまく動かないです。（やり方分かる方は教えてください＞＜）

「#佐藤心」入りツイートをRTしていた時はこれで十分でしたが、「しゅがは」入りツイートをRTし始めるとbotが暴走したので、RT・ふぁぼする前に以下のコードを入れました。

```java
	// 画像付きツイートじゃないツイートを弾く
	if (status.getMediaEntities().length == 0) {
		continue;
	}

	// リプも弾く
	if (status.getInReplyToScreenName() != null) {
		continue;
	}
```

##蛇足：特定のツイートを弾く

先ほどのRTTweet.javaのコード中でコメントアウトしていたisAllowed(Status)内では、jacksonを用いてツイートを弾いたり弾かなかったりしています。

```java:SearchTweet#isAllowed(Status)
if (status.isRetweet()) {
	status = status.getRetweetedStatus();
}

String user = status.getUser().getScreenName();
for (JsonNode node : blackListUtil.getNGUsers()) {
	if (user.equals(node.asText())) {
		return false;
	}
}

long tweetId = status.getId();
for (JsonNode node : blackListUtil.getNGTweetId()) {
	if (tweetId == node.asLong()) {
		return false;
	}
}

String tweetText = status.getText();
for (JsonNode node : blackListUtil.getNGWord()) {
	if (tweetText.contains(node.asText())) {
		return false;
	}
}

return true;
```

```java:BlackListUtil.java
	private ObjectMapper mapper;
	private JsonNode root;

	public BlackListUtil() {
		mapper = new ObjectMapper();
		try {
			root = mapper.readTree(new File("WEB-INF/blacklist.json"));
		} catch (JsonProcessingException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public JsonNode getNGUsers() {
		return root.get("users");
	}

	public JsonNode getNGTweetId() {
		return root.get("tweetId");
	}

	public JsonNode getNGWord() {
		return root.get("word");
	}
```

JSONファイルにNGユーザ、NGツイートID、NGワードを設定しており、NGユーザには自分自身を、NGワードには"R18"や"NSFW"などを入れています。全年齢向けbotなので仕方ないね。
（元はNGツイートIDにNSFWなツイートを加えていましたが、今は使っていないです）

ちなみにSearchTweet#isAllowed(Status)の最初3行ですが、ツイート検索した際、StatusにRT元のツイートだけでなく「他のユーザがこのツイートをRTしました」というものも入ってしまうらしく、RT元のツイートとツイートIDもユーザも異なり、すり抜けてしまうことがあります。この場合にRT元のツイートを取得するようにしています。

#画像をツイートする

8時10分ちょうどに以下のような画像ツイートをしています。

![無題2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621290/a4149fc3-8646-7909-c065-1c6321371ab3.png)

以下のようなコードで画像ツイートができます。

```java:TweetSender.java
Twitter twitter = TwitterFactory.getSingleton();

StatusUpdate update = new StatusUpdate(text);
update = update.media(new File(picPath));

Status status = twitter.updateStatus(update);
```

画像は適当なフォルダにまとめて保存していて、JSONファイルにツイート済みの画像のファイル名を配列で保存して画像が重複しないようにしています。

```java:
// ディレクトリ内の画像のファイル名リストを持ってくる
File dir = new File(folderPath);
File[] fileList = dir.listFiles();

// jsonからリスト持ってきて使っていない画像か突き合わせる
String picPath = null;
out : for (File file : fileList) {
	for (JsonNode node : root.get("tweetedFile")) {
		if (node.asText().equals(file.getName())) {
			continue out;
		}
	}

	if (used) {
		
	}

	picPath = file.toString();

	// jsonに書き込み
	ObjectNode objectNode = root.deepCopy();
	ArrayNode arrayNode = root.withArray("tweetedFile");
	arrayNode.add(file.getName());
	objectNode.set("tweetedFile", arrayNode);

	ObjectWriter writer = mapper.writer();
	writer.writeValue(Paths.get("WEB-INF/picUtil.json").toFile(), objectNode);
//	System.out.println("picUtil.json出力OK！");
	}

	break;
}

return picPath;
```

#運用

あとは特定の時間に上記の自動RT・自動ツイートをするようにすればいいだけです。
例えばしゅがーはぁと応援botの場合、8:10ちょうどに自動ツイートをし、それ以降約30分毎に自動RTを行い、25:00ごろに停止しています。
（コードは割愛）

#おわりに

多少コードは拙いかもしれないですが、Java初級者でもサンプルを見ながら・ググりながらbotを作ることができるので、他アイドルのPのみなさんも、ぜひbotを作って、担当アイドルの界隈を盛り上げてみてください。
あとできれば**シンデレラガール総選挙には佐藤心に、ボイス総選挙には技術担当の担当である三好紗南に投票をよろしくお願いします。**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621290/efd31bde-4d17-2c4f-a61f-a155fadc3064.png)